diff --git a/starpilot/navigation/destination_store.py b/starpilot/navigation/destination_store.py index 38542b85fc..54675f9d0d 100644 --- a/starpilot/navigation/destination_store.py +++ b/starpilot/navigation/destination_store.py @@ -14,6 +14,16 @@ NAV_INSTRUCTION_COLLAPSED_KEY = "NavInstructionCollapsed" RECENT_DESTINATIONS_LIMIT = 10 +def normalize_route_id(value: Any) -> str | None: + """Return only route IDs generated by Galaxy's Mapbox route list.""" + route_id = str(value or "").strip() + if route_id == "main": + return route_id + if route_id.startswith("alt-") and route_id[4:].isdigit() and int(route_id[4:]) >= 1: + return route_id + return None + + def _coerce_float(value: Any) -> float | None: try: parsed = float(value) @@ -62,12 +72,16 @@ def normalize_destination_payload(payload: Any) -> dict[str, Any] | None: if not name or latitude is None or longitude is None: return None - return { + normalized = { "name": name, "place_name": name, "latitude": latitude, "longitude": longitude, } + route_id = normalize_route_id(payload.get("routeId")) + if route_id is not None: + normalized["routeId"] = route_id + return normalized def parse_destination_json(raw_value: str | bytes | dict[str, Any] | None) -> dict[str, Any] | None: @@ -285,6 +299,10 @@ def normalize_recent_destination_entry(entry: Any) -> dict[str, Any] | None: normalized["longitude"] = longitude normalized["name"] = place_name + route_id = normalize_route_id(entry.get("routeId")) + if route_id is not None: + normalized["routeId"] = route_id + return normalized diff --git a/starpilot/navigation/navigationd.py b/starpilot/navigation/navigationd.py index 7cf0c89fd2..ea66f18d09 100644 --- a/starpilot/navigation/navigationd.py +++ b/starpilot/navigation/navigationd.py @@ -31,7 +31,7 @@ class Navigationd: self._route_lock = threading.Lock() self._route: NavigationRoute | None = None self._active_destination: dict[str, object] | None = None - self._requested_destination_key: tuple[str, float, float] | None = None + self._requested_destination_key: tuple[str, str, float, float] | None = None self._route_fetch_inflight = False self._route_generation = 0 self._published_route_generation = -1 @@ -44,11 +44,12 @@ class Navigationd: self._last_nav_state: dict[str, object] | None = None @staticmethod - def _destination_key(destination: dict[str, object] | None) -> tuple[str, float, float] | None: + def _destination_key(destination: dict[str, object] | None) -> tuple[str, str, float, float] | None: if destination is None: return None return ( str(destination["place_name"]).casefold(), + str(destination.get("routeId") or "main"), round(float(destination["latitude"]), 6), round(float(destination["longitude"]), 6), ) diff --git a/starpilot/navigation/route_engine.py b/starpilot/navigation/route_engine.py index f0154b4698..9bd5cc8cf2 100644 --- a/starpilot/navigation/route_engine.py +++ b/starpilot/navigation/route_engine.py @@ -400,13 +400,15 @@ class MapboxRouteEngine: return None end = Coordinate(float(destination["latitude"]), float(destination["longitude"])) + route_id = str(destination.get("routeId") or "main") + requested_route_index = int(route_id[4:]) if route_id.startswith("alt-") and route_id[4:].isdigit() else 0 params: dict[str, str] = { "access_token": token, "geometries": "geojson", "steps": "true", "overview": "full", "annotations": "maxspeed", - "alternatives": "false", + "alternatives": "true" if requested_route_index > 0 else "false", "banner_instructions": "true", } if bearing is not None: @@ -420,7 +422,7 @@ class MapboxRouteEngine: return None routes = data.get("routes") or [] - route = routes[0] if routes else None + route = routes[requested_route_index] if requested_route_index < len(routes) else (routes[0] if routes else None) legs = route.get("legs") if route else None leg = legs[0] if legs else None if data.get("code") != "Ok" or route is None or leg is None: diff --git a/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js b/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js index 87cf11986d..b9f2516f76 100644 --- a/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js +++ b/starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js @@ -947,7 +947,8 @@ function NavigationDestination({ body: JSON.stringify({ name, longitude: destinationCoordinates[0], - latitude: destinationCoordinates[1] + latitude: destinationCoordinates[1], + routeId, }) }); await loadFavorites(); diff --git a/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js b/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js index 2b17a2bbe8..a40e32ba0d 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/components/NavigationDestinationPanel.js @@ -167,6 +167,8 @@ export const NavigationDestinationPanel = { if (savedDestination) { const raw = saved || nav?.destination || {} const savedName = String(raw?.name || raw?.text || "").trim() + const savedRouteId = String(raw?.routeId || "main") + this.selectedRouteId = /^(?:main|alt-[1-9]\d*)$/.test(savedRouteId) ? savedRouteId : "main" this.destination = { ...raw, ...savedDestination, name: savedName || labelFor(raw) || "Current destination" } this.query = this.destination.name this.navigationStarted = true @@ -259,6 +261,7 @@ export const NavigationDestinationPanel = { async chooseSuggestion(place) { this.searching = true try { + this.selectedRouteId = "main" this.destination = await this.resolvePlace(place) this.query = this.destination.name this.suggestions = [] @@ -285,11 +288,13 @@ export const NavigationDestinationPanel = { try { this.destination = place || await this.resolveQuery() if (!this.destination) throw new Error("Enter a destination first.") + const selectedRouteId = this.selectedRouteId || this.routeSummary?.routeId || "main" + this.destination = { ...this.destination, routeId: selectedRouteId } await api.setNavigation(this.destination) this.navigationStarted = true this.query = this.destination.name this.suggestions = [] - await this.previewDestination(this.destination) + await this.previewDestination(this.destination, selectedRouteId) showSnackbar("Destination set.") } catch (e) { this.error = e?.message || "Failed to set destination." @@ -351,6 +356,7 @@ export const NavigationDestinationPanel = { selectRoute(route, routeId = "main") { if (!route) return this.selectedRouteId = routeId + if (this.destination) this.destination = { ...this.destination, routeId } this.routeSummary = { distance: Number(route.distance) || 0, duration: Number(route.duration) || 0, @@ -358,7 +364,7 @@ export const NavigationDestinationPanel = { } if (this.map && this.routes.length) highlightRoute(this.map, this.routes, routeId) }, - async previewDestination(place) { + async previewDestination(place, preferredRouteId = null) { if (!this.mapReady || !this.map || !place) return const mapboxgl = window.mapboxgl this.destinationMarker?.remove() @@ -374,8 +380,11 @@ export const NavigationDestinationPanel = { const payload = await api.mapboxDirections(this.lastPosition, place, this.mapboxPublic) const routes = Array.isArray(payload?.routes) ? payload.routes : [] if (routes.length) { + const requestedRouteId = preferredRouteId || place.routeId || this.selectedRouteId || "main" + const selectedIndex = routes.findIndex((_, index) => this.routeId(index) === requestedRouteId) + const selectedRouteId = selectedIndex >= 0 ? requestedRouteId : "main" this.routes = routes - this.selectRoute(routes[0], "main") + this.selectRoute(routes[selectedIndex >= 0 ? selectedIndex : 0], selectedRouteId) removeRouteFromMap(this.map) addRouteToMap( this.map,