← Files MapboxARCHIVED FILE
skills/mapbox-navigation-patterns/AGENTS.md
14.3 KB · Sep 30, 2026 · 23:11 UTC
# Mapbox Navigation Patterns
Quick reference for implementing navigation and routing with Mapbox Directions API and Navigation SDKs.
## Product Decision
| Need | Solution |
| ----------------------------- | ------------------------------------------------------ |
| **Show a route on a web map** | Directions API |
| **Turn-by-turn iOS** | Navigation SDK for iOS (SwiftUI + drop-in NVC default) |
| **Turn-by-turn Android** | Navigation SDK for Android |
| **Voice guidance** | Navigation SDK only |
| **Multi-stop optimization** | Optimization API |
## Directions API (Web)
Coordinates are always `longitude,latitude` order. Default to the `driving-traffic` profile — it factors in live traffic, congestion, and incidents. Use `driving` only when you need `arrive_by` (not supported by `driving-traffic`); both profiles support `depart_at`.
### Basic Route
```javascript
const query = await fetch(
`https://api.mapbox.com/directions/v5/mapbox/driving-traffic/` +
`${start[0]},${start[1]};${end[0]},${end[1]}?` + // lon,lat
`steps=true&geometries=geojson&access_token=${token}`
);
const route = (await query.json()).routes[0];
// Display on map
map.addSource('route', {
type: 'geojson',
data: { type: 'Feature', geometry: route.geometry }
});
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
paint: {
'line-color': '#3b9ddd',
'line-width': 8
}
});
```
### Alternative Routes
```javascript
// Add alternatives=true
const url = `...&alternatives=true&...`;
const routes = json.routes; // Returns multiple routes
// Main route = routes[0], alternatives = routes[1], routes[2]
```
### Multi-Stop Routing
```javascript
// Up to 25 waypoints
const waypoints = [start, stop1, stop2, stop3, end];
const coords = waypoints.map((w) => `${w[0]},${w[1]}`).join(';'); // lon,lat
const url = `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${coords}?...`;
```
### Route Optimization
```javascript
// Optimize waypoint order — hard limit: 12 coordinates max (v1 API)
// source/destination only accept 'first'/'any' and 'last'/'any' — no numeric indices
const url =
`https://api.mapbox.com/optimized-trips/v1/mapbox/driving-traffic/${coords}?` +
`source=first&destination=last&roundtrip=true&...`;
const optimized = json.trips[0];
const order = json.waypoints.map((wp) => wp.waypoint_index);
// More than 12 stops, or need time windows/vehicle capacities? See Optimization
// API v2 (separate async job-submission API, Public Beta, up to 1,000 locations)
```
### Congestion-Based Route Coloring
```javascript
// annotations must be paired with overview=full or the geometry won't line up
// point-for-point with the per-segment annotation array
const url =
`https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${coords}?` +
`overview=full&annotations=duration,distance,congestion&...`;
// Color by congestion level
const congestion = route.legs[0].annotation.congestion;
// Values: 'low', 'moderate', 'heavy', 'severe', 'unknown'
```
### Turn-by-Turn Instructions
```javascript
const steps = route.legs[0].steps;
steps.forEach((step) => {
console.log(step.maneuver.instruction); // "Turn left onto Main St"
console.log(step.distance); // meters
console.log(step.duration); // seconds
});
```
## Navigation SDK for iOS
**Default:** SwiftUI app shell + wrap `NavigationViewController` with `UIViewControllerRepresentable` (official getting-started). Fully custom Core UI ([CoreSDKExample](https://github.com/mapbox/mapbox-navigation-ios/tree/main/Examples/CoreSDKExample)) only when explicitly requested.
**Setup first:** SPM (`MapboxNavigationCore` + `MapboxNavigationUIKit`), `MBXAccessToken`, location permissions, background `audio`/`location` — see `references/ios-navigation-sdk.md` checklist and [install guide](https://docs.mapbox.com/ios/navigation/guides/install/). Stable Core/UIKit releases do not need a `.netrc` download token.
For specialized topics (road cameras, history, e-horizon, CarPlay, offline, styled chrome, etc.), use the **Example patterns catalog** in `references/ios-navigation-sdk.md`. Load `references/ios-navigation-specialized.md` for multi-stop, route line, camera, road cameras, and route alerts. Do not fetch upstream sample source unless the user asks to open a specific example.
**Sample host ≠ API stack:** `AdditionalExamples` are often UIKit demos. APIs on `NavigationMapView` (waypoints, final-waypoint image, route line, camera, callouts, road cameras) are stack-independent — wrap `NavigationMapView` in `UIViewRepresentable`. Road cameras: `navigationMapView.mapView.mapboxMap` + `RoadCamerasManager(navigatorHandle: provider.navigatorHandle)`. True UIKit-only: NVC chrome (top/bottom bars, styled UI elements, embed NVC).
### Default: SwiftUI + drop-in NavigationViewController
```swift
import MapboxNavigationCore
import MapboxNavigationUIKit
import SwiftUI
struct NavigationViewControllerWrapper: UIViewControllerRepresentable {
let navigationRoutes: NavigationRoutes
let navigationOptions: NavigationOptions
func makeUIViewController(context: Context) -> NavigationViewController {
NavigationViewController(
navigationRoutes: navigationRoutes,
navigationOptions: navigationOptions
)
}
func updateUIViewController(_ uiViewController: NavigationViewController, context: Context) {}
}
let provider = MapboxNavigationProvider(
coreConfig: CoreConfig(locationSource: .live, ttsConfig: .default)
)
let routes = try await provider.mapboxNavigation
.routingProvider()
.calculateRoutes(options: NavigationRouteOptions(coordinates: [start, end]))
.value
let options = NavigationOptions(
mapboxNavigation: provider.mapboxNavigation,
voiceController: provider.routeVoiceController,
eventsManager: provider.eventsManager()
)
// NavigationViewControllerWrapper(navigationRoutes: routes, navigationOptions: options)
```
### UIKit: present drop-in UI
```swift
import MapboxNavigationCore
import MapboxNavigationUIKit
let provider = MapboxNavigationProvider(
coreConfig: CoreConfig(locationSource: .live, ttsConfig: .default)
)
let navigationRoutes = try await provider.mapboxNavigation
.routingProvider()
.calculateRoutes(options: NavigationRouteOptions(coordinates: [start, end]))
.value
let navVC = NavigationViewController(
navigationRoutes: navigationRoutes,
navigationOptions: NavigationOptions(
mapboxNavigation: provider.mapboxNavigation,
voiceController: provider.routeVoiceController,
eventsManager: provider.eventsManager()
)
)
present(navVC, animated: true)
```
### Opt-in: fully custom Core UI
```swift
import MapboxNavigationCore
import Combine
@MainActor
final class Navigation: ObservableObject {
@Published private(set) var visualInstruction: VisualInstructionBanner?
@Published private(set) var routeProgress: RouteProgress?
@Published private(set) var currentPreviewRoutes: NavigationRoutes?
// Keep a strong reference — do not create the provider only inside init and discard it.
private let provider: MapboxNavigationProvider
private let core: MapboxNavigation
private let voiceController: RouteVoiceController
init() {
let provider = MapboxNavigationProvider(
coreConfig: CoreConfig(locationSource: .live, ttsConfig: .default)
)
self.provider = provider
core = provider.mapboxNavigation
voiceController = provider.routeVoiceController
core.navigation().bannerInstructions
.map(\.visualInstruction)
.assign(to: &$visualInstruction)
core.navigation().routeProgress
.map { $0?.routeProgress }
.assign(to: &$routeProgress)
}
func startActiveNavigation() {
guard let routes = currentPreviewRoutes else { return }
core.tripSession().startActiveGuidance(with: routes, startLegIndex: 0)
}
}
```
### Voice Guidance
```swift
MapboxNavigationProvider(
coreConfig: CoreConfig(ttsConfig: .default) // or .localOnly, .custom(synthesizer)
)
var options = NavigationRouteOptions(coordinates: [start, end])
options.locale = Locale(identifier: "es-ES")
options.distanceMeasurementSystem = .metric
```
## Navigation SDK for Android
### Basic Navigation
```kotlin
import com.mapbox.geojson.Point
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.base.route.NavigationRouterCallback
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
// Lifecycle-aware handle — prefer this over MapboxNavigationProvider.
// Survives configuration changes; attaches/detaches automatically.
val mapboxNavigation: MapboxNavigation by requireMapboxNavigation(
onResumedObserver = object : MapboxNavigationObserver {
override fun onAttached(mapboxNavigation: MapboxNavigation) {
mapboxNavigation.startTripSession()
}
override fun onDetached(mapboxNavigation: MapboxNavigation) {}
},
onInitialize = {
MapboxNavigationApp.setup(NavigationOptions.Builder(context).build())
}
)
// Request route
val routeOptions = RouteOptions.builder()
.applyDefaultNavigationOptions()
.coordinatesList(listOf(
Point.fromLngLat(originLng, originLat),
Point.fromLngLat(destLng, destLat)
))
.build()
mapboxNavigation.requestRoutes(
routeOptions,
object : NavigationRouterCallback {
override fun onRoutesReady(
routes: List<NavigationRoute>,
routerOrigin: String
) {
// Set routes; startTripSession() already ran in onAttached
mapboxNavigation.setNavigationRoutes(routes)
}
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
// Handle failure
}
override fun onCanceled(routeOptions: RouteOptions, routerOrigin: String) {
// Handle cancellation
}
}
)
```
### Custom Navigation UI
```kotlin
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
// Register route progress observer
private val routeProgressObserver = RouteProgressObserver { routeProgress ->
// Update custom UI
val instruction = routeProgress.currentLegProgress
?.currentStepProgress?.step
?.bannerInstructions?.firstOrNull()?.primary?.text
val distanceRemaining = routeProgress.currentLegProgress
?.currentStepProgress?.distanceRemaining
val durationRemaining = routeProgress.durationRemaining
}
override fun onStart() {
super.onStart()
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
}
override fun onStop() {
super.onStop()
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
}
```
## Routing Profiles
| Profile | Use Case |
| ----------------- | ---------------------------------- |
| `driving` | Car routing without traffic |
| `driving-traffic` | Car routing with real-time traffic |
| `walking` | Pedestrian routing |
| `cycling` | Bicycle routing |
## Best Practices
### Route Caching
```javascript
const cache = new Map();
async function getCachedRoute(start, end) {
const key = `${start}-${end}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.time < 5 * 60 * 1000) {
return cached.route;
}
// Fetch new route
const route = await getRoute(start, end);
cache.set(key, { route, time: Date.now() });
return route;
}
```
### Error Handling
```javascript
try {
const response = await fetch(directionsURL);
const json = await response.json();
if (json.code !== 'Ok') {
throw new Error(`Directions error: ${json.code}`);
}
if (!json.routes || json.routes.length === 0) {
throw new Error('No routes found');
}
return json.routes[0];
} catch (error) {
// Show user-friendly message
alert('Unable to calculate route');
}
```
### Performance
```javascript
// Debounce route requests
let timeout;
function requestRouteDebounced(start, end) {
clearTimeout(timeout);
timeout = setTimeout(() => getRoute(start, end), 500);
}
// Use simplified geometry
const url = `...&overview=simplified&geometries=polyline6`;
```
## Common Patterns
### Delivery Route
```javascript
// Optimize delivery stops
const stops = [warehouse, ...deliveries, warehouse];
const optimized = await getOptimizedRoute(stops, 0, stops.length - 1);
// Get optimized order
const order = optimized.order.slice(1, -1);
```
### Ride-Sharing ETA
```javascript
const route = await getTrafficRoute(driver, passenger);
const eta = Math.ceil(route.duration / 60); // minutes
const distance = (route.distance * 0.000621371).toFixed(1); // miles
```
### Walking/Cycling
```javascript
// Walking
const url = `https://api.mapbox.com/directions/v5/mapbox/walking/${coords}?...`;
// Cycling
const url = `https://api.mapbox.com/directions/v5/mapbox/cycling/${coords}?...`;
```
## API Limits
| Feature | Limit |
| ---------------------- | ----------------------------------- |
| **Waypoints** | 25 max (including start/end) |
| **Alternative routes** | Max 2 alternatives (3 total routes) |
| **Optimization** | 12 waypoints (v1 API hard limit) |
| **Rate limit** | 300 requests/minute (default) |
## Quick Decisions
**Need turn-by-turn navigation?**
→ iOS/Android: Navigation SDK | Web: Directions API + custom UI
**Need voice guidance?**
→ Must use Navigation SDK (iOS/Android only)
**Need route optimization?**
→ Use Optimization API with `source` and `destination` params
**Need real-time traffic?**
→ Use `driving-traffic` profile
**Need offline navigation?**
→ Must use Navigation SDK (iOS/Android only)
## Resources
- Directions API: <https://docs.mapbox.com/api/navigation/directions/>
- Navigation SDK iOS: <https://docs.mapbox.com/ios/navigation/>
- Navigation SDK Android: <https://docs.mapbox.com/android/navigation/>
- Optimization API: <https://docs.mapbox.com/api/navigation/optimization/>
SHA-256: f8d9079bc39f2c1d5536074e4b782640df4d3e59c36bfcf8455d425378936fc1