# MapMap Nav

**Early access.** The Android app is available now as a direct
[APK download](#early-access-the-android-app) (sideload, roughly 200 MB),
with full offline navigation once a territory is installed. An iOS build
is in development. MapMap Nav is a complete, twelve-screen turn-by-turn
navigation app for iOS and Android, built from one Expo (React Native)
codebase using only published MapMap SDKs. It's a reference, not a demo
shell: on-device routing, guidance and voice all run against the real
`MapMapKit` (iOS) and `ai.mapmap:core` (Android) SDKs. Download the
boilerplate, run it, and fork it as the starting point for your own
branded nav app.

![MapMap Nav: a full journey on real roads](/docs-videos/mapmap-nav-journey.mp4)

**[Download the boilerplate (.zip)](/downloads/mapmap-nav-boilerplate.zip)**:
source-only, secrets stripped, runs on the JS mock with live MapMap
online tiles out of the box. See [how to run it](#how-to-run-it) below.

## Early access: the Android app

The MapMap navigation app for Android is available now as an early-access
build, installed directly rather than from the Play Store.

**[Download for Android (.apk)](https://api.mapmap.ai/downloads/mapmap-nav-early-access.apk)**
· Early access · Android · sideload APK · roughly 200 MB. An iOS build is
in development.

### Install (sideload)

1. Download the APK on your phone using the link above.
2. Open the downloaded file. Android will ask you to allow installs from
   your browser or file manager ("install unknown apps"); allow it for
   this install.
3. Confirm the install, then open **MapMap Nav**.

### Free accounts and the 8 GB allowance

- On first run the app issues you a **personal API key with just an email
  address**. Online maps, search and routing work immediately.
- **Verifying the email unlocks offline territory downloads**, with
  **8 GB per month included free**.
- Territories are per-country offline map packages, signed and verified
  on the device before use (see
  [Territory packages](/docs/territories)). Installing the **UK territory
  downloads 3,077 MiB (about 3.0 GiB)**, which is **38% of a month's free
  allowance**, so it fits comfortably with room for a refresh. Note that
  the download is what counts against the allowance; the package occupies
  roughly 5.8 GB once unpacked on the device.
- Only the large layer files are byte-metered against the allowance;
  checking for updates is not. If a month's allowance is spent, the
  gateway answers `402 E_TERRITORY_ALLOWANCE` before streaming anything.
- Offline search, offline routing and offline guidance need an installed
  territory; until one is installed the app runs in online mode.

### What offline means here

With a territory installed, the app does **full offline navigation**:
offline search, offline turn-by-turn routing and guidance with voice
instructions, all on the device, with no signal at all.

### Speed camera alerts

- **3,425 fixed safety cameras across the UK**, shown along the route
  with the enforced limit where known.
- Audible alerts stay **calm while you are within the limit** and become
  **more urgent when you are over it**.
- **Jurisdiction-aware:** camera alerts run on a GB and Ireland allowlist
  and stay off elsewhere. The app flow is UK-first today; Ireland
  coverage is rolling out.
- Camera locations are derived from OpenStreetMap data, published under
  the ODbL. Attribution: © OpenStreetMap contributors.

### Also in this build

- **Overspeed warning:** the speed readout turns red and a soft chime
  plays when you are over the posted limit.
- **Faster-route suggestions while navigating:** when a faster route is
  found the app offers it; switch with a tap or dismiss it and keep your
  chosen road.
- **Full manoeuvre iconography** from the canonical MapMap direction set:
  88 glyphs including ferry, with per-angle roundabout exits derived from
  the route geometry.
- **Zoom that keeps following:** zoom in and out during guidance without
  losing puck-following; a pinch keeps following too, and re-centre
  resumes guidance at the zoom you chose.

## What it is

- Twelve screens to a full nav-app spec: splash, onboarding, explore,
  search, place preview, route preview, guidance, arrived, settings,
  territories, about, and error states.
- A typed JS↔native bridge (`mapmap-react-native`): no business logic in
  JavaScript beyond UI state; routing, guidance and territory management
  all run in the native SDK.
- On-device routing and turn-by-turn guidance, including a **truck/ADR**
  profile (MapMap's differentiator), a replay-drive demo path for running
  guidance without a real drive, and EAS build profiles + store/privacy
  drafts.
- Built and verified against the public SDKs only, in the position of an
  outside RN shop building on MapMap with nothing beyond published
  artefacts and docs; see [the verdict](#honest-caveats) below.

## Features shipped

- **Search → preview → turn-by-turn.** When the driver leaves the route the
  puck follows live GPS off the line and the app **auto-reroutes** from the
  current position.
- **Multiple route alternatives** with a per-route time/distance overview;
  tap any alternative line to pick it before starting.
- **Posted speed-limit signs and an over-limit warning**: the real road sign
  (UK mph, e.g. 30) for the current road, drawn only for standard signs in
  use, and the speed pill turns red when the driver exceeds the limit.
- **Postcode and address search** (UK plus a dozen other countries) and
  **reverse-geocode tap-to-place**: tapping a point resolves to a real
  street/address, not raw coordinates.
- A **current-road name banner** and **on-road manoeuvre arrows** at major
  turns, alongside a dimming "vanishing" route line for the travelled distance.
- A **3D chase camera** with a pinned, smoothed location puck: the puck
  and camera glide between fixes instead of hopping on each guidance tick.
- **Real GPS by default**, with an explicit **Simulate** demo mode that
  replays a scripted drive for desk demos and testing; no real movement
  required.
- **Tilt and 2D/3D toggles**, plus puck-style options, on a layers control
  alongside 3D buildings, POIs and light/dark theme.
- **Truck and ADR routing**: vehicle-dimension presets, hazmat
  declaration, and ADR tunnel category B–E, all shaping the route in the
  costing itself.
- **Reachable-area isochrones**: 10/20-minute drive-time contours from a
  single tap.
- **Offline, end to end**: territory install/list/remove, a fully local
  map style, and offline search, routing and voice guidance against an
  installed territory. Packages arrive over the signed territory channel
  and are verified on the device before use (see
  [the early-access section](#early-access-the-android-app) above).

## Architecture

One Expo app (SDK 57, dev client, TypeScript) plus one **local Expo
module**, `modules/mapmap-react-native`, that wraps the native SDKs:

- **iOS**: Swift `MapMapKit` + `MapMapValhalla` from
  [`Mapmapai/mapmap-ios`](https://github.com/Mapmapai/mapmap-ios) (pin
  `0.2.1`; see [SDKs](/docs/sdks)).
- **Android**: Kotlin `ai.mapmap:core` from GitHub Packages (see
  [SDKs](/docs/sdks) for the repository/credential setup).
- **Map rendering** stays in JS via
  [`@maplibre/maplibre-react-native`](https://github.com/maplibre/maplibre-react-native),
  fed by a style JSON the native module returns; every source points at
  local territory tiles, so the map renders with the radios off.
- **App state** is an explicit state machine:
  `Explore → Search → Preview → Guidance → Arrived`, with
  Settings/Territories/About pushed as modal-ish stack screens on top
  (`react-navigation` native stack).

All navigation logic (routing, guidance, territory verification) lives in
the native SDKs. The bridge is a thin async facade plus an event stream;
JS never reimplements routing or guidance state.

## The bridge contract

The single source of truth is
`modules/mapmap-react-native/src/MapmapNav.types.ts` in the app repo; this
section summarises it. Native module name:
`MapmapNav`. Every method is Promise-based; continuous data (territory
downloads, guidance ticks, voice state) arrives as events. Everything
crossing the bridge is JSON-serialisable.

```ts
interface MapmapNavModule {
  // Load the native core, remember the API key + gateway base. Idempotent.
  init(apiKey: string, gatewayBaseUrl?: string): Promise<void>;

  // Territories
  listAvailableTerritories(): Promise<TerritorySummary[]>;
  listInstalledTerritories(): Promise<TerritoryInfo[]>;
  installTerritory(territoryId: string): Promise<void>;   // emits territoryProgress
  removeTerritory(territoryId: string): Promise<void>;
  getTerritoryStyle(territoryId: string, theme: "light" | "dark"): Promise<string>;

  // Search: on-device, against the active territory
  search(query: string, near?: LatLng, limit?: number): Promise<SearchResult[]>;

  // Routing: on-device
  computeRoute(request: RouteRequest): Promise<RouteResult>;

  // Guidance
  startGuidance(options: GuidanceOptions): Promise<void>;
  stopGuidance(): Promise<void>;

  // Voice
  setVoiceMuted(muted: boolean): Promise<void>;
  setVoiceVolume(volume: number): Promise<void>;
}
```

Events (Expo Modules event emitter): `territoryProgress`, `guidance`,
`voiceState`, `bridgeError`. Guidance events fire at ≥1Hz during a session,
plus one per state transition (banner change, reroute start/end, arrival).

### Routing: car, bike, walk, and truck with ADR

```ts
type RouteProfile = "car" | "bicycle" | "walking" | "truck";

type TruckOptions = {
  heightM?: number;
  weightT?: number;
  lengthM?: number;
  widthM?: number;
  hazmat?: boolean;      // carrying ADR dangerous goods
  tunnelCode?: string;   // ADR tunnel restriction category, "B".."E"
};

type RouteAvoid = {
  tolls?: boolean;
  motorways?: boolean;
  ferries?: boolean;
};

type RouteRequest = {
  profile: RouteProfile;
  origin: LatLng;
  destination: LatLng;
  waypoints?: LatLng[];      // intermediate stops, visited in order
  alternatives?: number;     // extra full routes beyond the primary (0–3, default 2)
  truck?: TruckOptions;      // truck profile only
  avoid?: RouteAvoid;
};
```

### Guidance: the event stream and banner instructions

```ts
type GuidanceEvent = {
  state: "navigating" | "rerouting" | "arrived";
  location: LatLng & { bearingDegrees: number; speedMps: number };
  banner?: BannerInstruction;      // absent momentarily during reroute
  distanceToManeuverMeters: number;
  remainingDistanceMeters: number;
  remainingDurationSeconds: number;
  etaEpochMs: number;
  isOffRoute: boolean;
};

type BannerInstruction = {
  primary: string;                 // e.g. "Turn left onto High Street"
  secondary?: string;
  maneuverType: ManeuverType;
  roundaboutExit?: number;
  thenPrimary?: string;            // the "Then ↰" chip
  thenManeuverType?: ManeuverType;
  lanes?: LaneInfo[];              // populated close to the turn (~150–300m)
};

type LaneInfo = {
  directions: ("left" | "slight-left" | "through" | "slight-right" | "right" | "uturn")[];
  active: boolean;   // whether this lane keeps you on the upcoming manoeuvre
};
```

### Errors

Every rejected promise carries a stable `code` plus a `message`; for
example `E_TERRITORY_ALLOWANCE` (402, download allowance exhausted),
`E_NO_ROUTE` (no legal route for the request), `E_NOT_INITIALISED` (a
method called before `init()` resolved), or `E_GUIDANCE_ACTIVE`
(`startGuidance` called while a session is already running). If the native
SDK can't honour a contract item, the rule the builders worked to was:
stop and report it; don't redesign the interface silently.

## Features built

- Tap-to-route: tap or long-press anywhere on the map, not just a named
  vector feature, to drop a pin and route there; the pin is reverse-geocoded
  to a real street/address via the gateway.
- Search: place/POI names, category quick-search chips, and **postcode +
  address search** (UK via postcodes.io / MapMap geocode, plus a dozen other
  countries). Saved places (Home/Work/favourites) and recent searches.
- Layers control: 3D buildings, POIs, map theme (light/dark), zoom
  controls and camera tilt.
- Truck/ADR routing (vehicle dimension presets, hazmat declaration, ADR
  tunnel category B–E), MapMap's differentiator, verified end-to-end
  against the gateway.
- Route options (avoid tolls/motorways/ferries), **route alternatives with a
  tap-to-select picker**, waypoints plumbed through the contract.
- Guidance: current-road banner, lane-guidance bar, **posted speed-limit
  signs with an over-limit warning** (limits fetched per-edge via
  `/trace_attributes`), chase camera, **off-route GPS-follow + auto-reroute**,
  arrival, voice.
- Reachable-area isochrones (10/20-minute drive-time contours).
- Offline: territory install/list/remove, fully local map style, offline
  search and routing against an installed territory, and replay-drive
  guidance for demoing without an actual drive.

Full feature-by-feature detail, including what's built versus what's still
on the roadmap (add-a-stop UI, search-along-route, ETA share), is in the
app repo's `NAV-FEATURE-GAPS.md`.

## How to run it

### Quick start: the mock, no native build

The fastest path, and the one the [download](/downloads/mapmap-nav-boilerplate.zip)
is built for: the app ships with a pure-TypeScript mock of the MapMap
native module, so the whole flow (search, route, turn-by-turn) runs
against **live MapMap online tiles** without compiling any native code.

```sh
unzip mapmap-nav-boilerplate.zip && cd mapmap-nav-boilerplate
cp .env.example .env      # paste your MapMap API key into both vars
npm install
npx expo start            # press i / a for a simulator, or scan the QR
```

You'll need an `snk_` API key for the online tiles; issue one card-free
from the [quickstart](/docs/quickstart).

| Capability | JS mock (`npx expo start`) | Real native SDK |
| --- | --- | --- |
| App UI, navigation, screens | Yes | Yes |
| Online MapMap map tiles | Yes | Yes |
| Search, routing, route lines | Yes (simulated) | Yes (real Valhalla routing) |
| Guidance events + voice, Simulate demo drive | Yes (scripted) | Yes (real map-matching) |
| Offline territories / offline routing | No | Yes |
| On-device map-matching against the real core | No | Yes |

### Native build

The app uses native modules (the local `mapmap-react-native` Expo module,
`@maplibre/maplibre-react-native`), which rules out Expo Go for a native
build: you need an Expo **dev client** and a **prebuild**:

```sh
npx expo prebuild
npm run ios      # or: npm run android
```

You'll need the native SDKs available to the build; see
[honest caveats](#honest-caveats) below before you start; both are real
friction today, not a five-minute setup.

## Build your own

This bridge became the official
[`@mapmap/react-native`](https://www.npmjs.com/package/@mapmap/react-native)
module: the territory lifecycle, routing, guidance stream and voice surface
here are what that package now ships. Install it from npm rather than
copying the bridge; see [SDKs](/docs/sdks#react-native-mapmapreact-native).
Two other ways to build on this app:

- **Fork the app.** It's a complete, working starting point: swap the
  branding, trim the screens you don't need, and you have a nav app
  running on real MapMap SDKs from day one.
- **Reuse the bridge.** `modules/mapmap-react-native` is a self-contained
  Expo module; lift it into your own app if you want a different UI layer
  on the same native surface.

With a working bridge in place, a competent React Native team can ship a
branded nav app in about two weeks; most of the SDK-integration work is
already done.

## Honest caveats

This was built in the position of an outside RN shop with nothing beyond
published artefacts and public docs, and it surfaced real friction that's
worth knowing before you start. The mock/online-tiles quick start above
sidesteps all of it; these only bite when you go for a **native build**:

- **This bridge is no longer the only option.** It was written before
  there was an official module, so it is roughly 2,000 lines of Swift and
  Kotlin you would otherwise own.
  [`@mapmap/react-native`](https://www.npmjs.com/package/@mapmap/react-native)
  now ships the same surface from npm; prefer it for new work.
- **The boilerplate's Gradle config still points at GitHub Packages.**
  `ai.mapmap:core` 0.3.0 resolves from `mavenCentral()` with no account and
  no token, so that repository block and its `gpr.user` / `gpr.token`
  credentials are no longer needed. See the
  [Android section of the SDKs docs](/docs/sdks#android-kotlin) for the
  current package coordinates.
- **iOS needs the vendored `MapMapKit.xcframework`.** An Expo module is a
  CocoaPods pod, and a pod can't cleanly consume a Swift Package Manager
  product today, so the iOS bridge links a vendored, prebuilt xcframework
  rather than a plain SwiftPM dependency. Those binaries are **not**
  included in the boilerplate download (source-only, secrets stripped);
  request the xcframework from MapMap and drop it into
  `modules/mapmap-react-native/ios/Vendored/` before `pod install`. See
  the [iOS section of the SDKs docs](/docs/sdks#ios-swift) for the
  package coordinates and versioning.
- **Offline territory downloads need a verified account.** The signed
  territory channel is live and the early-access Android build uses it.
  If you are building from the boilerplate, offline downloads require an
  email-verified key with remaining monthly allowance (a
  `402 E_TERRITORY_ALLOWANCE` means the allowance is exhausted).

None of this blocks the quick start: run the mock first, and only chase
native-SDK access once you're ready for a real device build. None of it
blocks building on MapMap today; it's exactly the kind of plumbing this
reference app already fought through once so you don't have to fight
through it cold.

## Next steps

- [SDKs & installation](/docs/sdks): iOS/Android/web SDK install
  coordinates and the signing-key model this app relies on.
- [Territory packages & OTA updates](/docs/territories): how the offline
  maps this app installs are packaged and verified.
- [Quickstart](/docs/quickstart): issue an `snk_` key for online-maps mode.
- [Conventions](/docs/conventions): base URL, auth, error envelope, ADR
  tunnel codes referenced throughout the bridge contract above.
