From 64fc7111114f3cc3b471f4ea44bdc3ec94e0d4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Fazekas?= Date: Wed, 21 Oct 2026 06:54:03 +0200 Subject: [PATCH] Add @rnmapbox/navigation docs Overview, install, getting started, NavigationView and API reference pages under docs/navigation, with a Navigation navbar entry and links from the landing page and the maps install guide. --- docs/install.md | 4 + docs/navigation/api.md | 96 +++++++++++++++++++++ docs/navigation/getting-started.md | 114 ++++++++++++++++++++++++ docs/navigation/install.md | 134 +++++++++++++++++++++++++++++ docs/navigation/navigation-view.md | 57 ++++++++++++ docs/navigation/overview.md | 59 +++++++++++++ docusaurus.config.js | 10 +++ sidebars.js | 1 + src/pages/index.js | 5 ++ 9 files changed, 480 insertions(+) create mode 100644 docs/navigation/api.md create mode 100644 docs/navigation/getting-started.md create mode 100644 docs/navigation/install.md create mode 100644 docs/navigation/navigation-view.md create mode 100644 docs/navigation/overview.md diff --git a/docs/install.md b/docs/install.md index 49348b24..03c4a5bf 100644 --- a/docs/install.md +++ b/docs/install.md @@ -7,6 +7,10 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; > Note that those instructions applies to the 10.2.* versions of @rnmapbox/maps. + +:::tip +Need turn-by-turn navigation? See [@rnmapbox/navigation](navigation/overview.md). +::: # Configure credentials Before installing @rnmapbox/maps you'll need to get your Mapbox access token. diff --git a/docs/navigation/api.md b/docs/navigation/api.md new file mode 100644 index 00000000..6424b4a3 --- /dev/null +++ b/docs/navigation/api.md @@ -0,0 +1,96 @@ +--- +id: api +sidebar_position: 5 +title: API reference +--- + +`Navigation` is the default export and also a named export. + +```tsx +import Navigation, { NAVIGATION_MAP_VIEW_IMPL, NavigationViewport } from '@rnmapbox/navigation'; +``` + +## Navigation + +| Method | Description | +| -------------------------------------------------------- | --------------------------------------------------------------------------- | +| `setAccessToken(accessToken: string): void` | Must be called before any request; navigation is billed to this token. | +| `getSdkVersion(): string` | Version of the native Navigation SDK linked into the app. | +| `requestRoutes(options: RouteRequestOptions): Promise` | Routes through `options.waypoints`. Rejects with fewer than two waypoints. | +| `previewRoute(route: Route \| null): void` | Shows `route` on navigation maps until guidance starts or `stopNavigation`; `null` removes it. Throws if `route` was released. | +| `startActiveGuidance(route: Route, options?: { simulate?: boolean }): Promise` | Starts guidance along `route` and switches the camera to `following`. `simulate` drives it with simulated locations. | +| `stopNavigation(): void` | Stops guidance and removes the route from navigation maps. | +| `getRouteStats(): RouteStats` | `{ live, released, collected }` counts of native routes, for leak checks. | +| `setCameraState(state: CameraState): void` | `'following'`, `'overview'` or `'idle'`. | +| `getCameraState(): CameraState` | Current camera state. | +| `addCameraStateListener(listener: (state: CameraState) => void): EventSubscription` | Called whenever the camera state changes, whether from JS, guidance or a map gesture. | +| `addRouteProgressListener(listener: (event: RouteProgressEvent) => void): EventSubscription` | Route progress during guidance. | + +## Components and constants + +| Export | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------- | +| `NAVIGATION_MAP_VIEW_IMPL` | Pass as `mapViewImpl` to an `@rnmapbox/maps` `MapView` to make it a navigation map. | +| `NavigationViewport` | Child of a navigation map. `padding?: { top?, right?, bottom?, left? }` in points from the map's edges, ignoring the safe area. | +| `NavigationView` | Full-screen navigation UI, see [NavigationView](navigation-view.md). Not released yet. | + +## Types + +```ts +/** GeoJSON order, same as @rnmapbox/maps. */ +type Position = [longitude: number, latitude: number]; + +type Waypoint = Position | { coordinate: Position; name?: string }; + +type RouteRequestOptions = { + /** Origin first, destination last. */ + waypoints: Waypoint[]; + /** Defaults to 'driving-traffic'. */ + profile?: 'driving-traffic' | 'driving' | 'walking' | 'cycling'; + /** Also return alternative routes. Defaults to false. */ + alternatives?: boolean; + /** Road classes to avoid, e.g. ['toll', 'ferry']. */ + exclude?: string[]; + /** Language of instructions, e.g. 'en' or 'de-DE'. */ + language?: string; + units?: 'metric' | 'imperial'; +}; + +type RouteResponse = { main: Route; alternatives: Route[] }; + +interface Route { + readonly distance: number; // meters + readonly expectedTravelTime: number; // seconds + readonly geometry: LineString; // GeoJSON, a new object on every read + readonly legs: RouteLeg[]; // a new array on every read + readonly isReleased: boolean; + /** Frees the native route. Calling it again does nothing. */ + release(): void; +} + +type RouteLeg = { + distance: number; // meters + expectedTravelTime: number; // seconds + stepCount: number; + /** Names of the main roads, e.g. "Market Street, Embarcadero". */ + summary: string; +}; + +type RouteStats = { live: number; released: number; collected: number }; + +type CameraState = 'following' | 'overview' | 'idle'; + +type RouteProgressEvent = { + distanceRemaining: number; // meters + distanceTraveled: number; // meters + durationRemaining: number; // seconds + fractionTraveled: number; // 0..1 + stepDistanceRemaining: number; // meters to the next maneuver + /** Primary banner text of the next maneuver, e.g. "Turn right onto Market Street". */ + maneuverInstruction: string; + /** Mapbox Directions maneuver type, e.g. "turn", "arrive". Empty if unknown. */ + maneuverType: string; + /** Mapbox Directions maneuver modifier, e.g. "right", "slight left". Empty if unknown. */ + maneuverModifier: string; +}; +``` diff --git a/docs/navigation/getting-started.md b/docs/navigation/getting-started.md new file mode 100644 index 00000000..15a26388 --- /dev/null +++ b/docs/navigation/getting-started.md @@ -0,0 +1,114 @@ +--- +id: getting-started +sidebar_position: 3 +title: Getting started +--- + +Request a route, show it on a navigation map, and start simulated guidance: + +```tsx +import { useEffect, useState } from 'react'; +import { Text, View } from 'react-native'; +import Mapbox, { Camera, MapView } from '@rnmapbox/maps'; +import Navigation, { + NAVIGATION_MAP_VIEW_IMPL, + NavigationViewport, + type Position, + type Route, + type RouteProgressEvent, +} from '@rnmapbox/navigation'; + +const ACCESS_TOKEN = 'pk....'; +Mapbox.setAccessToken(ACCESS_TOKEN); +Navigation.setAccessToken(ACCESS_TOKEN); + +const ORIGIN: Position = [-122.39726, 37.79205]; // [longitude, latitude] +const DESTINATION: Position = [-122.39351, 37.79554]; + +export function NavigationScreen() { + const [progress, setProgress] = useState(); + + useEffect(() => { + const subscription = Navigation.addRouteProgressListener(setProgress); + let route: Route | undefined; + let cancelled = false; + + Navigation.requestRoutes({ waypoints: [ORIGIN, DESTINATION] }) + .then(({ main, alternatives }) => { + alternatives.forEach((alternative) => alternative.release()); + route = main; + if (cancelled) { + main.release(); + return; + } + // Switches the camera to 'following'. + return Navigation.startActiveGuidance(main, { simulate: true }); + }) + .catch((error) => console.warn(error)); + + return () => { + cancelled = true; + subscription.remove(); + Navigation.stopNavigation(); + route?.release(); + }; + }, []); + + return ( + + + + {/* Keeps the puck and the route clear of UI drawn over the map. */} + + + {progress && ( + + {progress.maneuverInstruction} in{' '} + {Math.round(progress.stepDistanceRemaining)} m + + )} + + ); +} +``` + +`Navigation.previewRoute(route)` shows a route on navigation maps before guidance starts, and `Navigation.setCameraState('overview')` fits the rest of the route. + +The headless API has no arrival event in the current release; one is in progress in [rnmapbox/navigation#9](https://github.com/rnmapbox/navigation/pull/9). [``](navigation-view.md) reports arrivals with `onArrive`. + +## Routes + +`requestRoutes` resolves to `{ main, alternatives }`. A `Route` is backed by the native SDK route and holds native memory: + +- Its getters (`distance`, `expectedTravelTime`, `geometry`, `legs`) read a copy taken when the route was created. They're synchronous. +- `release()` frees the native route. Release the routes you don't keep, such as unused alternatives. The garbage collector releases the rest eventually, but it doesn't see the native memory, so that can take long. +- After `release()`, reading anything but `isReleased` throws an `Error` with `code: 'route_released'`, and `startActiveGuidance` and `previewRoute` reject or throw it. +- Guidance and the preview keep their own reference to the native route until `stopNavigation`, so releasing a route they use is safe. +- Routes don't survive a JS reload. + +## Navigation maps + +`` is a regular `@rnmapbox/maps` map: its props, children and events keep working. The Navigation SDK also draws the route line and the puck on it and drives its camera. + +There's one navigation session per app. Every navigation map shows its route, puck and camera state. Without guidance the maps show the route passed to `previewRoute`. + +`` configures the map it's a child of. Its `padding` is in points from the map's edges and ignores the safe area. It applies to `following`, `overview` and the route preview. Without it the SDK defaults apply: the safe area plus a small margin on iOS, and no padding on Android. + +## Camera + +| State | Camera | +| ----------- | ----------------------------------------------------------------------------------- | +| `following` | Follows the puck, tilted and rotated with the route. Set by `startActiveGuidance`. | +| `overview` | Fits the rest of the route. | +| `idle` | Left alone, for the user's gestures and `@rnmapbox/maps` `Camera`. | + +The state switches to `idle` when a pan, pinch, rotate, pitch or double-tap gesture happens on any navigation map, when an `@rnmapbox/maps` `Camera` moves the map from its props or `setCamera` (`defaultSettings` doesn't count), and when `stopNavigation` is called. + +`addCameraStateListener` reports every change, and `getCameraState` returns the current state. Call `setCameraState('following')` to recenter. + +- On Android a `Camera` move with `animationDuration: 0` doesn't switch the state, and the navigation camera takes the map back at the next location update. Animate the move, or call `setCameraState('idle')` first. +- `Camera` `followUserLocation` and the navigation camera both drive the map. Don't combine them. + +After `stopNavigation`, the route and the puck are removed and the camera eases back to north-up, without tilt or padding. diff --git a/docs/navigation/install.md b/docs/navigation/install.md new file mode 100644 index 00000000..a543c96e --- /dev/null +++ b/docs/navigation/install.md @@ -0,0 +1,134 @@ +--- +id: install +sidebar_position: 2 +title: Install +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Set up `@rnmapbox/maps` first, following its [install guide](../install.md). `@rnmapbox/navigation` needs `@rnmapbox/maps` 10.3.11 or newer. + +# Install + + + + +```bash +npm install @rnmapbox/navigation @rnmapbox/maps +``` + + + + +```bash +yarn add @rnmapbox/navigation @rnmapbox/maps +``` + + + + +# Configure @rnmapbox/navigation + + + + +The Navigation SDK v3 is only distributed as a Swift package. `@rnmapbox/navigation` links it, and through it MapboxMaps, into the app. `@rnmapbox/maps` must only compile against that MapboxMaps, otherwise the app links two copies of MapboxMaps and fails with duplicate symbols. + +### Update your podfile + +```ruby +# highlight-next-line +$RNMapboxMapsSwiftPackageManager = { link: false } + +target 'YourApp' do + # ... + pre_install do |installer| + $RNMapboxMaps.pre_install(installer) + end + + post_install do |installer| + react_native_post_install(installer, ...) + $RNMapboxMaps.post_install(installer) + end +end +``` + +`pod install` adds `MapboxCommon`, `MapboxCoreMaps`, `MapboxNavigationNative` and `Turf` to the frameworks CocoaPods embeds. + +### Info.plist + +Add `NSLocationWhenInUseUsageDescription`. For ``, also add `audio` to `UIBackgroundModes`: the SDK's voice controller asserts on it in debug builds. + +### Versions + +Set these before `use_native_modules!` if you need to: + +```ruby +# Navigation SDK version, defaults to 3.23.1. Nav 3.N pins Maps 11.N. +$RNMapboxNavigationVersion = '3.23.1' +# Don't add the Swift package; add mapbox-navigation-ios (products MapboxNavigationCore and +# MapboxNavigationUIKit) yourself. +$RNMapboxNavigationSwiftPackageManager = 'manual' +``` + +`@rnmapbox/maps`' package requirement comes from `$RNMapboxMapsVersion` (default `~> 11.23.1`) and must be compatible with the Maps version the Navigation SDK pins, otherwise Xcode reports a package resolution error. With a newer Navigation SDK, set `$RNMapboxMapsVersion = '= 11.N.x'` to match. + + + + +### Adding mapbox maven repo and versions + +Add the Mapbox Maven repository, the same one `@rnmapbox/maps` uses, and set the Maps version the Navigation SDK pins: + +```groovy +// android/build.gradle +buildscript { + ext { + // Nav 3.23.1 pins Maps 11.23.1. Both packages must resolve the same Maps. + RNMapboxMapsVersion = '11.23.1' + // Optional, defaults to 3.23.1. + RNMapboxNavigationVersion = '3.23.1' + } +} + +allprojects { + repositories { + maven { url 'https://api.mapbox.com/downloads/v2/releases/maven' } + } +} +``` + +`@rnmapbox/navigation` adds a strict constraint on the Maps version its Navigation SDK is built against, so a different `RNMapboxMapsVersion` fails dependency resolution instead of silently mixing versions. + +With `targetSdkVersion` 35 or higher both libraries use the `-ndk27` artifacts (16 KB pages). Keep `targetSdkVersion` in the root project `ext`, where both libraries read it. + + + + +There's no Expo config plugin in the current release. Use a [development build](https://docs.expo.dev/develop/development-builds/introduction/), as Expo Go doesn't include the native SDKs, and apply the iOS and Android setup yourself. + +A config plugin is in progress in [rnmapbox/navigation#7](https://github.com/rnmapbox/navigation/pull/7). + + + + +# Access token + +Pass your public access token to both libraries before requesting routes. Navigation is billed to this token. + +```tsx +import Mapbox from '@rnmapbox/maps'; +import Navigation from '@rnmapbox/navigation'; + +Mapbox.setAccessToken('pk....'); +Navigation.setAccessToken('pk....'); +``` + +Next: [Getting started](getting-started.md). diff --git a/docs/navigation/navigation-view.md b/docs/navigation/navigation-view.md new file mode 100644 index 00000000..0ae92c87 --- /dev/null +++ b/docs/navigation/navigation-view.md @@ -0,0 +1,57 @@ +--- +id: navigation-view +sidebar_position: 4 +title: NavigationView +--- + +:::info Not released yet +`` is on the `main` branch of [rnmapbox/navigation](https://github.com/rnmapbox/navigation) and isn't part of `0.1.0-alpha.0`. It ships with the next release. +::: + +`` is a full-screen turn-by-turn navigation UI: `NavigationViewController` from `MapboxNavigationUIKit` on iOS, and an equivalent built from the Navigation SDK UI components on Android (maneuver banner with lanes and shields, speed limit, trip progress, mute, overview, recenter and end buttons). + +On iOS, add `audio` to `UIBackgroundModes` in `Info.plist`, see [Install](install.md). + +```tsx +import { NavigationView } from '@rnmapbox/navigation'; + + console.log(event.distanceRemaining)} + onArrive={({ waypointIndex, isFinalDestination }) => {}} + onCancel={() => close()} + onError={({ code, message }) => {}} +/> +``` + +## Props + +| Prop | Type | Description | +| ----------------- | -------------------------------------------- | ----------------------------------------------------------------------------------- | +| `route` | `Route` | A route from `requestRoutes`. Keep it unreleased while it's passed to the view. | +| `waypoints` | `Waypoint[]` | Used without `route`: the view requests a route through them, origin first, and releases it on unmount. | +| `simulate` | `boolean` | Drives the route with simulated locations. Read when guidance starts. Defaults to `false`. | +| `mute` | `boolean` | Mutes voice instructions. The view's own mute button also toggles them. Defaults to `false`. | +| `style` | `StyleProp` | | +| `testID` | `string` | | +| `onRouteProgress` | `(event: RouteProgressEvent) => void` | Same event as `addRouteProgressListener`. | +| `onArrive` | `(event: ArriveEvent) => void` | `{ waypointIndex, isFinalDestination }`; `waypointIndex` counts the origin as 0. | +| `onCancel` | `() => void` | The user ended the trip. Guidance has stopped; unmount the view. | +| `onError` | `(error: NavigationViewError) => void` | `{ code, message }`, e.g. `route_request_failed`, `route_released`, `start_failed`. | + +## Session + +`` uses the app's single navigation session: + +- Mounting it, or giving it a new route, starts guidance along that route and replaces any other active guidance. Navigation maps show its route and puck too. +- Unmounting it, or the user ending the trip, stops guidance. +- `addRouteProgressListener` and `addCameraStateListener` also report its guidance. +- Show one `NavigationView` at a time. On iOS it's embedded as a child of the nearest view controller. + +Android `` has no end-of-route, feedback or alternative-route UI, no day/night styles, no road name label, and doesn't show reroutes as such. diff --git a/docs/navigation/overview.md b/docs/navigation/overview.md new file mode 100644 index 00000000..38ac026f --- /dev/null +++ b/docs/navigation/overview.md @@ -0,0 +1,59 @@ +--- +id: overview +sidebar_position: 1 +title: Overview +--- + +[`@rnmapbox/navigation`](https://github.com/rnmapbox/navigation) is a community-supported React Native library for the [Mapbox Navigation SDK v3](https://docs.mapbox.com/navigation/) on iOS and Android, built on `@rnmapbox/maps`. + +It requests routes, runs an active-guidance session and reports route progress. The Navigation SDK draws the route line, the puck and the navigation camera on a regular `@rnmapbox/maps` `MapView`, so your existing layers, sources and annotations keep working on the same map. + +:::caution Alpha +`@rnmapbox/navigation` is in early alpha. The current release is `0.1.0-alpha.0`, and APIs may change between releases. +::: + +## Requirements + +- React Native 0.79+ with the New Architecture. +- `@rnmapbox/maps` 10.3.11 or newer (peer dependency). +- iOS: React Native's minimum deployment target. The Navigation SDK for iOS 3.23.1 is added as a Swift package. +- Android: `minSdkVersion` 24, `compileSdkVersion` 36. +- A Mapbox account and a public access token (`pk.…`). No secret downloads token is needed. + +Navigation is billed per trip or MAU to the access token you pass, on top of Maps usage. See [Navigation SDK pricing](https://www.mapbox.com/pricing#navigation). + +## Versions + +The Navigation SDK pins the Maps SDK it's built against, and `@rnmapbox/maps` must use that same Maps SDK. + +| @rnmapbox/navigation | Navigation SDK | Maps SDK (pinned by Nav) | +| -------------------- | -------------- | ------------------------ | +| 0.1.x | 3.23.1 | 11.23.1 | + +## What's included + +- Route requests, with `Route` objects backed by the native SDK route. +- Route preview and active guidance, simulated or on the device's location. +- Navigation maps: an `@rnmapbox/maps` `MapView` with the SDK's route line, puck and navigation camera. +- Route progress and camera state events. +- [``](navigation-view.md), the SDK's full-screen turn-by-turn UI. Not released yet, see its page. + +## Coming next + +These are in open pull requests and not released yet: + +- ``, a navigation UI written in React Native on top of the headless API, plus location, arrival, off-route, reroute and voice instruction events ([rnmapbox/navigation#9](https://github.com/rnmapbox/navigation/pull/9)). +- An Expo config plugin ([rnmapbox/navigation#7](https://github.com/rnmapbox/navigation/pull/7)). + +## Known limitations + +- No explicit offline routing API. Requests use the SDK's default hybrid router. +- Navigation maps have no voice guidance, rerouting UI, alternatives or multi-leg UI. +- `@rnmapbox/maps` `LocationPuck` and `UserLocation` don't work on navigation maps: the navigation puck replaces the map's location provider. +- One navigation session for all navigation maps. Per-map routes or cameras aren't supported. + +See the [README](https://github.com/rnmapbox/navigation#known-limitations) for the full list. + +## License + +`@rnmapbox/navigation` is MIT licensed. The Mapbox Navigation SDKs it builds on are proprietary and governed by the [Mapbox Terms of Service](https://www.mapbox.com/legal/tos). This is a community-maintained package, not an official Mapbox product. diff --git a/docusaurus.config.js b/docusaurus.config.js index 65971031..3a0d9058 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -87,6 +87,12 @@ const config = { position: 'left', label: 'Components', }, + { + type: 'docSidebar', + sidebarId: 'navigationSidebar', + position: 'left', + label: 'Navigation', + }, { href: 'https://github.com/rnmapbox/maps', label: 'GitHub', @@ -104,6 +110,10 @@ const config = { label: 'Examples', to: '/docs/examples/', }, + { + label: 'Navigation', + to: '/docs/navigation/overview', + }, ], }, { diff --git a/sidebars.js b/sidebars.js index 166ca269..8b32a0e1 100644 --- a/sidebars.js +++ b/sidebars.js @@ -29,6 +29,7 @@ const sidebars = { }*/ ], componentSidebar: [{type: 'autogenerated', dirName: 'components'}], + navigationSidebar: [{type: 'autogenerated', dirName: 'navigation'}], // But you can create a sidebar manually /* diff --git a/src/pages/index.js b/src/pages/index.js index 786c0e36..3e1918a7 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -17,6 +17,11 @@ function HomepageHeader() {
rnmapbox screenshots
+
+ + Turn-by-turn navigation: @rnmapbox/navigation (alpha) + +
React-Native logo Mapbox logo