Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
96 changes: 96 additions & 0 deletions docs/navigation/api.md
Original file line number Diff line number Diff line change
@@ -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<RouteResponse>` | 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<void>` | 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;
};
```
114 changes: 114 additions & 0 deletions docs/navigation/getting-started.md
Original file line number Diff line number Diff line change
@@ -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<RouteProgressEvent>();

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 (
<View style={{ flex: 1 }}>
<MapView style={{ flex: 1 }} mapViewImpl={NAVIGATION_MAP_VIEW_IMPL}>
<Camera defaultSettings={{ centerCoordinate: ORIGIN, zoomLevel: 14 }} />
{/* Keeps the puck and the route clear of UI drawn over the map. */}
<NavigationViewport
padding={{ top: 16, bottom: 120, left: 16, right: 16 }}
/>
</MapView>
{progress && (
<Text>
{progress.maneuverInstruction} in{' '}
{Math.round(progress.stepDistanceRemaining)} m
</Text>
)}
</View>
);
}
```

`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). [`<NavigationView>`](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

`<MapView mapViewImpl={NAVIGATION_MAP_VIEW_IMPL}>` 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`.

`<NavigationViewport>` 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.
134 changes: 134 additions & 0 deletions docs/navigation/install.md
Original file line number Diff line number Diff line change
@@ -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

<Tabs groupId="install-module" queryString defaultValue="npm" values={[
{label:'NPM', value:'npm'},
{label:'Yarn', value:'yarn'}
]}>
<TabItem value="npm">

```bash
npm install @rnmapbox/navigation @rnmapbox/maps
```

</TabItem>
<TabItem value="yarn">

```bash
yarn add @rnmapbox/navigation @rnmapbox/maps
```

</TabItem>
</Tabs>

# Configure @rnmapbox/navigation

<Tabs groupId="configure-module" queryString defaultValue="ios" values={[
{label:'iOS', value:'ios'},
{label:'Android', value:'android'},
{label:'Expo', value: 'expo'}
]}>
<TabItem value="ios">

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 `<NavigationView>`, 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.

</TabItem>
<TabItem value="android">

### 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.

</TabItem>
<TabItem value="expo">

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).

</TabItem>
</Tabs>

# 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).
Loading
Loading