Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rustic Maps SDK

Rustic Maps SDK is a native map SDK written in Rust. It gives your application map rendering, search, routing, turn-by-turn navigation, and offline maps from one Rust core, delivered to Android (Kotlin), iOS (Swift), and React Native through UniFFI’s UDL-first bindings. WebAssembly is a first-class target, so the same core renders map tiles in a browser with WebGPU.

Open the live wasm demo Start building Downloads

What the SDK gives you

One contract, four platforms

The FFI surface is a single frozen UniFFI UDL file. The Kotlin, Swift, and React Native Turbo Module bindings are generated from it, and the WebAssembly TypeScript mirror is regression-checked against it. The contract changes addition-only: existing members keep their signature and meaning.

Search, routing, and traffic

Ten vendor-neutral provider contracts cover geocoding (forward and reverse), routing, the distance matrix, place search, traffic incidents, traffic flow, elevation, raster tiles, vector tiles, and offline routing. A full map-platform provider implementation backs them, rate-limited and retry-aware, with a disk response cache under your own byte budget.

A rendering core that draws anywhere

The renderer spans web-mercator projection, an anchored camera with animations and fit-bounds framing, exact frustum tile listing, a frame-driven tile scheduler with per-frame budgets and parent fallback, and a byte-budget tile texture pool feeding a wgpu renderer. The same scene draws headless in tests, on Android and iOS surfaces, and in the browser through WebGPU.

Turn-by-turn navigation

The navigation state machine follows the Ferrostar model. It composes banner and spoken instructions verbatim from the provider’s text, publishes speech-instruction triggers at documented distance bands, and hands deviation handling and reroute policy to you at clear seams.

Offline maps

A deterministic compiler turns an OpenStreetMap PBF extract into a compact self-describing routing archive. An offline routing provider answers routes over the memory-mapped graph, honoring recorded turn restrictions, and wraps the online provider as an automatic fallback.

Structured diagnostics

A bounded, per-subsystem diagnostics ring streams structured log events to a foreign listener with coordinate scrubbing at the sink. Readbacks expose frame tile statistics, cache terms, and provider-failure records, so a screen in your product can show exactly what the SDK is doing.

Architecture at one glance

LayerWhat it is
geospatial-typesThe record and math layer: coordinates, bounding boxes, routes, places, traffic, tiles, and the typed error model
geospatial-traitsThe ten provider contracts every provider implementation fulfills
map-core, map-cacheProvider resolution with cache-then-network behavior, backed by a SQLite response cache with per-category time-to-live and byte-budget eviction
mapquest-providerThe map-platform provider implementation (rate-limited, retry-aware, fixture-tested)
map-renderingProjection, camera, tile scheduler, texture pool, and the wgpu renderer
map-navigationThe turn-by-turn trip-state machine and its instruction composition
map-offline-graph-compiler, map-offline-graph-archiveOpenStreetMap extracts compiled into the offline routing archive and its memory-mapped reader
map-native-coreThe UniFFI binding crate: the frozen UDL contract is the single source of truth
map-web-bindingsThe wasm-bindgen surface for the browser

The full call surface these crates expose across the FFI is the FFI contract; each member’s documentation lives in the generated API reference.

Requirements at a glance

  • Android: API level 24 or higher. The release AAR is 16 MiB across three ABIs (8.7 to 13 MiB each after strip).
  • iOS: iOS 17 or higher. The XCFramework ships two arm64 slices.
  • React Native: a source TurboModule package compiled against your application’s own React Native version.
  • WebAssembly: any browser with WebGPU; the wasm package is 3.6 MiB.

Per-platform tables, artifact sizes, and the version policy are in Requirements and distributions.

Where to go next

  1. Install the artifacts and build a rendered map: Your first map.
  2. Resolve places and plan a route: Search and routing.
  3. Simulate a guided turn-by-turn drive: Turn-by-turn navigation.
  4. Keep rendering and routing working without a network: Offline maps.
  5. See the core running from a browser, no build step: Live demo.

Status and license

The SDK is in active development. The canonical short link is https://sdk.mapquest.live, which serves this site in place (subpaths preserved, so the hostname stays in your browser). The rendered site is rebuilt from the source repository’s build pipeline; a section appears here only once the pipeline that produces it is green, and each page carries the build’s provenance stamp.

The SDK is dual-licensed under MIT OR Apache-2.0. Map imagery © MapQuest; map data © OpenStreetMap contributors (ODbL). See third-party notices for the full attribution and license table, and keep the attribution visible in your application.

Requirements and distributions

The SDK ships as release archives you download and install into your project; it publishes through no package registry. This page lists what each platform needs to build against the SDK, what the artifacts weigh, and how versions are stamped. The Downloads page names the released files.

Android

RequirementValue
Minimum API level24
Compile SDK36
Android Gradle Plugin9.0.0 (Gradle wrapper 9.5.1)
Kotlin binding runtimenet.java.dev.jna:jna:5.18.1@aar (UniFFI runtime)
Coroutinesorg.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2

The Android artifact is a release AAR (Maven coordinate com.angoratek:rustic-maps-sdk) carrying the Kotlin bindings plus the Rust dynamic library for three ABIs. Measured sizes (2026-10-29): the AAR is 16 MiB; the stripped per-ABI .so libraries measure arm64-v8a 12 MiB, armeabi-v7a 8.7 MiB, and x86_64 13 MiB.

iOS

RequirementValue
Deployment targetiOS 17 or higher
Swift toolingswift-tools-version: 5.10 (Swift 5 language mode)
Architecturesarm64 (device) and arm64 (simulator)

The iOS artifact is an XCFramework containing the static library, its headers and modulemaps, consumed by Swift Package Manager through the release’s checksummed zip (the RusticMapsSDK product). Measured sizes (2026-10-29): 217 MiB per slice, 207 MiB after strip -x. A static archive carries every compiled object and the consumer’s final link discards unused code, so these archive sizes are not the size your application adds; the per-architecture measurement is a recorded open decision on whether the iOS artifact later moves to a dynamic slice.

React Native

The React Native artifact is a source TurboModule package (@angoratek/rustic-maps-sdk) generated from the frozen contract. It carries the generated TypeScript surface, the JSI C++ glue, and the podspec/CMake wiring your application compiles against its own React Native version, so no binary slice inside the tarball constrains which React Native version you run. The Android side of the current package builds the Rust static archive for arm64-v8a.

WebAssembly

The wasm artifact (@angoratek/rustic-maps-sdk-web) is the wasm-bindgen ES module emit. Measured size (2026-10-29): 3.6 MiB downloaded, of which the wasm module is 3.4 MiB.

The browser surface renders through WebGPU. Chromium-based browsers have supported it flag-free since 113 (2023); Safari ships it from version 26; Firefox ships it since 141, with desktop first. WebGPU is a secure-context API, so your page must be served over HTTPS or from localhost, and navigator.gpu is the feature test to use — no user-agent sniffing. MDN’s WebGPU page carries the live compatibility table.

Versions policy

  • Pin exact versions. Release archives are immutable and carry a version in their file name (rustic-maps-sdk-android-v0.4.0.aar and the like); keep the version you validated against, and upgrade deliberately.
  • One version line. The Cargo workspace version is the single source of truth: the Android Maven publication, the npm packages, and the Swift manifest are all stamped from it at release time. The compiled SDK also answers the version probe (getSdkCompiledVersion across the FFI), so your diagnostics screen can verify the loaded library matches the artifacts you shipped.
  • The FFI contract changes addition-only. Existing members keep their signature and meaning, so a minor-version upgrade keeps your compiled bindings valid; packaging changes (an artifact set or renamed target) bump the minor version while the release is 0.x.
  • Semantic versioning from 0.1.0. Versioned tags (vX.Y.Z) are the release trigger; tags are never moved, and a broken release is superseded by a new tag.
  • Checksums and signatures. Every release carries a per-asset SHA-256 checksum, an aggregated checksum manifest, and Sigstore signature bundles; verifying them is part of the Downloads flow.

Where the numbers come from: the sizes and tool versions above are copied from the packaging design’s recorded measurements and the build scripts that produce the artifacts, and they travel with the release notes of each tag.

Performance and profiling

This page is integration guidance: the practices that keep map rendering, routing, and navigation responsive, and the readbacks the SDK exposes so you can measure your own integration on your own hardware and scenes. The project publishes no benchmark numbers; any figure you quote should come from your own device and your own route mix.

Construct the SDK once

  • Build one MapSdk object and register your providers once at startup. Re-registration replaces every slot (including a cached tile slot), so an accidental double registration silently reopens the cache and resets its tuning: register, then tune with setRasterTileCacheTimeToLive.
  • Keep a single MapNavigationController around the lifetime of a trip screen; subscriptions are explicit and cheap, but re-subscribing listeners is wasted work on every frame.

Frame the camera with the contract’s helpers

  • Prefer the fit_bounds camera constructor for route previews and search-result framing. It computes a top-down, north-up camera that keeps the whole box visible inside the insets you give it, instead of a hand-tuned zoom level that breaks on another screen size.
  • Give it real insets. Route cards, attribution, and sheets are overlays your application owns; the insets tell the camera what the map may not hide. The insets-aware framing is what keeps the destination inside the visible region on every device.
  • Read the camera back with the attribute getters (center, zoomLevel, bearingDegrees, pitchDegrees) when you need to persist or compare a pose; the camera carries no setters, so a new pose is a new constructor call.

Let the render loop stay explicit

  • Frames are explicit by design: the platform view drives draw from its own cadence (a display link or choreographer callback). Do not add a second render loop; serialize draws and never call draw again while a previous call is still in flight — a draw in flight reports the next frame busy, and the cadence retries it.
  • Poll frameSettled in the render pump to skip draw when a re-draw would present the same pixels: true when the camera, overlay list, and location indicator are unchanged and no pending tile pixels remain. This keeps battery cost near zero on a static map.
  • Read TileFrameStatistics from every draw. The four readiness counts (present, fallback, deferred, unavailable) always add up to the requested count, so a persistent slice of deferred tells you your per-frame fetch budget is the limiter on your network, and a slice of unavailable points at provider refusals rather than slow ones.
  • Feed gesture deltas through the SDK’s camera seam (pan by pixels, focal-point zoom and rotation) instead of moving the viewport yourself; the SDK clamps zoom and pitch through the camera’s own ranges and holds the focal point exactly.

Size the tile cache deliberately

  • Register the tile cache with an absolute byte budget that fits your product (the reference application’s demo budget is 16 MiB for the whole recorded fixture set). The budget is a registration-time fact: retuning it means re-registering.
  • Set the tile time-to-live live through setRasterTileCacheTimeToLive. Imagery-provider terms put a ceiling on what the cache may retain; a request above the ceiling is accepted and the ceiling clamps what is stored. Read rasterTileCacheTerms to display the effective value, the ceiling, and the default in a settings or diagnostics screen.
  • Do not set a zero time-to-live; the call is refused because it would expire every entry on capture.

Keep the provider path under your quota

  • The provider implementation carries a shared token-bucket rate limiter and bounded idempotent retries. Treat RateLimitExhausted as “retry after the next refill”, not as an error to surface in the UI.
  • Cache-then-network is the default shape of every provider call, so repeated identical requests inside the time-to-live cost no quota. The tile category defaults to a 30-day time-to-live, the imagery terms ceiling.
  • Batch what the contracts batch: the distance matrix answers every origin-destination pair in one call; fetching matrix cells one by one multiplies your request count for the same data.

Use offline where it pays

  • The offline routing provider answers routes over a memory-mapped archive with A*; no network round trip. Compile an extract sized to the region your users drive in, not the planet: the compiler is deterministic and the archive is self-describing.
  • The routing decorator retries over the offline graph when the online provider fails, so a coverage-limited archive is a graceful degradation, not a failure mode. Its maneuver texts stay honest where the archive carries no street names.

Profile with the SDK’s own readbacks

  • Subscribe to diagnostics events at Info severity (or Debug for frame-path detail). Events stream fire-and-forget from bounded ring buffers, coordinates are scrubbed at the sink, and a slow listener can never slow the SDK.
  • Drain the backlog with the bounded flush readback from a non-frame context: the flush is synchronous and must stay out of the render path.
  • For provider failures, read latestProviderFailureDiagnostics after a suspicious session; the ring carries the contract, method, timestamp, and verbatim failure text for every failed foreign-callback provider call.
  • Measure on your target device and your real scenes. The SDK reports what it did (tile counts, frame statistics, cache terms); the timing numbers that matter are the ones your profiling run produces.

Live demo

The demo runs the SDK’s own browser build — the same wasm module the WebAssembly package ships — drawing rendered map imagery on a WebGPU canvas with pan, zoom, bearing and pitch controls, in-page overlays, and offline routing over a compiled archive. It is static, keyless and served from this site’s /demo path.

What the demo needs in a browser

  • WebGPU. Chromium-based browsers support it flag-free since 113; Safari ships it from version 26; Firefox ships it since 141, with desktop first. MDN’s WebGPU page carries the live compatibility table.
  • A secure context. The browser resolves WebGPU only over HTTPS or localhost.
  • Keyless by default. The demo renders recorded fixture imagery and answers routes over a committed archive. The Provider credentials panel can take a provider key at runtime for live geocoding, routing and tile probes (MapQuest today; the provider registry is designed for more); the key stays in the page’s memory and is never persisted.

Running the same loop locally

Every tutorial in the Guides group ships a WebAssembly tab with the same surface the demo shows, so you can run the stack yourself from the wasm package: install it once and the Your first map tutorial covers the whole loop. The browser harness the demo is built from — and the fixture routes it serves — is described in How the demo loop runs.

How it works

The SDK is one Rust core behind a frozen interface. This page explains the concepts that interface exposes, so the tutorials’ calls read as applications of them rather than recipes to copy. Every concept below is part of the frozen FFI contract.

Coordinates and the camera

A position is a GeographicCoordinate: latitude and longitude in decimal degrees on the WGS 84 ellipsoid. A region is a BoundingBox between a south-west and a north-east corner, and a box never spans the antimeridian.

The camera is a MapCamera value: an anchored pose of center, zoom, bearing, and pitch. The ground point under the center sits at the viewport center, zoom and pitch are clamped, and bearing wraps into the range from 0 up to but not including 360 degrees.

Camera attributeWhat it means
centerThe coordinate anchored at the viewport center
zoom_levelScale; a continuous level, not necessarily a whole number
bearing_degreesRotation clockwise from north
pitch_degreesTilt from top-down (0) toward the horizon

A camera carries no setters. A new pose is a new constructor call, and the attribute getters read the state back as it was built. The second constructor, fit_bounds, frames a bounding box inside the inset region you give it: top-down, north-up, zoomed to the largest level that keeps the whole box visible, and anchored on the inset region’s center. Insets are the pixels your own overlays keep clear, so route cards and attribution never hide the destination.

The camera answers screen mappings without drawing. project_to_screen and screen_to_ground_coordinate convert between a coordinate and viewport pixels, visible_tile_keys lists the tiles the view covers, and ground_meters_per_screen_pixel sizes a scale bar.

Zoom levels and tiles

A tile is one payload at a fixed zoom, addressed by a TileKeyRecord: a column, a row counted from the north, and the zoom level. The addressing is the web-mercator XYZ scheme. At zoom 0 the world is one tile, and each level doubles the columns and rows.

The camera’s zoom is continuous, so it answers two different questions. tile_zoom_level gives the nearest whole level a tile fetch should use. visible_tile_keys gives every tile at a chosen level that intersects the visible ground, sorted row-major from the north.

A raster tile is a pre-rendered image. The contract carries it as RasterTileData with its encoded bytes, an image format (PNG or JPEG), and a resolution: Standard, or Retina for the doubled-texel @2x form. A vector tile carries geometry and style separately from the pixels and is rendered on the device. The shipped renderer draws raster imagery, and vector rendering is a later path.

The provider registry

Provider-backed capability reaches you through MapSdk, an object with no providers at construction. You register implementations, then call the contract methods: forward and reverse geocoding, routing, the distance matrix, place search, traffic incidents, traffic flow, and raster tiles. Every call resolves through a registry. A call into a contract with no registration throws ProviderNotConfigured naming the contract, and the elevation contract has no registered implementation today.

There are two registration paths:

  • register_mapquest_providers installs the whole map-platform provider set from one API key, with the SDK defaults for rate limiting and retries.
  • register_geocoding_provider_from_foreign and register_raster_tile_provider_from_foreign install an implementation your own Kotlin or Swift code supplies through the GeocodingProvider and RasterTileProvider interfaces.

Registration is per object, and re-registration replaces every slot. A second MapSdk object needs its own registration.

The cache path

register_mapquest_providers_with_tile_cache installs the same provider set and wraps the raster-tile contract in a disk response cache: a SQLite database at a path you own, under an absolute byte budget. A cache hit answers from disk, and a cache miss goes to the network and back-fills.

Cached tile responses are retained under the tiles category’s time-to-live, 30 calendar days by default. That default is also the imagery provider’s terms ceiling. set_raster_tile_cache_time_to_live tunes the value, an override above the ceiling is accepted and clamped, and raster_tile_cache_terms reads back three values: the effective time-to-live, the provider’s declared ceiling, and the category default. The byte budget is fixed when the store opens, so retuning it means registering again, and a fresh registration starts at the category default.

Raster imagery is the layer the cache holds. Offline routing is a separate layer with its own archive (see below).

The frozen contract and four platforms

One UniFFI UDL file defines the surface, and it is the single source of truth. The Kotlin, Swift, and React Native bindings are generated from it, and the WebAssembly TypeScript mirror is regression-checked against it. The contract changes addition-only: existing members keep their signature and meaning, and new capability arrives as new members.

PlatformHow you call the contract
Android (Kotlin)Suspend functions and typed records; failures are the contract’s SdkError variants
iOS (Swift)Async throwing methods and records
React NativePromise-returning TurboModule methods with lower camel case field names
WebAssemblyA hand-reviewed TypeScript mirror over wasm-bindgen, covering the camera, rendering, and offline-routing slice

Plain data crosses as records and enums. The error model crosses as the SdkError variants the failing call can produce, with verbatim detail text. get_sdk_compiled_version on every surface answers the compiled SDK version, so a diagnostics screen can compare the loaded core with the artifacts your application shipped.

Routing returns a RouteData: legs between consecutive waypoints, and steps inside each leg, each carrying its distance, duration, maneuver label, and instruction texts. Navigation consumes that value rather than extending it.

MapNavigationController holds a trip. You start one with start_trip(route, unit_system, step_advance_policy, deviation_policy) and deliver one position fix at a time with provide_location_update. The controller publishes four topics: banner instructions, speech triggers, deviation status, and phase changes. A subscription is cancelled explicitly through its handle.

A trip has three phases: Navigating, OffRoute, and Arrived. Rerouting is not a phase; a trip moves between navigating and off route until it arrives. Deviation has two grades:

  • FullyOffRoute freezes progress until a graded fix recovers or a reroute replaces the route.
  • OffStepOnRoute means the fix is off the current step but still on a later part of the route, and the trip keeps navigating.

Guidance uses two policies you choose at trip start. StepAdvancePolicy decides when the trip moves to the next step, and DeviationPolicy decides when a fix counts as off route. A fix without a reported accuracy never advances a step and is never judged for deviation, so a simulated source must carry accuracy like a real one.

Instruction text has two channels. The banner channel carries the display text, and the speech channel carries the text-to-speech text through SpeechInstruction. The SDK decides when a line is newsworthy and supplies the text verbatim. Your application owns playback and synthesis, and utterance_id is a stable identity for deduplication.

A deviation asks for a replacement route through a small gate. begin_reroute_calculation answers true when the request passes the in-flight, cooldown, and movement checks, provide_reroute applies the replacement, and cancel_reroute_calculation abandons it. The trip state’s is_calculating_new_route reports the in-flight fact.

Offline routing

Offline routing answers from a compiled archive rather than a network round trip. The archive is built from an OpenStreetMap extract and is self-describing. It answers routes with an A* search over a memory-mapped graph and honors recorded turn restrictions. Maneuver text derives from the geometry where the extract carries no street names.

The archive’s direct route call joins the native contract in a later slice. Today it answers the automatic fallback when the online provider fails and the WebAssembly surface’s offline route seam, and one compiled archive serves every target.

Where to go next

The tutorials apply these concepts in order: Your first map, Search and routing, Turn-by-turn navigation, and Offline maps.

Your first map

Install the SDK, register a map provider with your API key, and draw a first rendered map surface. Choose your platform’s tab and follow its steps top to bottom; every snippet is a complete step and compiles against the frozen FFI contract.

Before you start

Android Studio with the SDK 36 toolchain, a project with minSdk = 24, and a downloaded release AAR copied into app/libs/, and a map-platform API key for the provider you register.

Install the artifact

In your application module’s build.gradle.kts, add the local AAR and the two runtime dependencies the Kotlin bindings need:

repositories {
    flatDir { dirs("libs") }
}

dependencies {
    implementation(files("libs/rustic-maps-sdk-android-v0.1.0.aar"))
    implementation("net.java.dev.jna:jna:5.18.1@aar")   // the UniFFI runtime
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
}

The version in the file name is the release you downloaded; keep it exact.

Build the SDK and register the provider

Construct the SDK object and install the map-platform provider set in one call, as the reference application does at startup:

import uniffi.map_native_core.MapSdk

val mapSdk = MapSdk()
mapSdk.registerMapquestProviders(mapQuestApiKey)

The call throws verbatim when the key is refused, so surface it in your error state rather than continuing.

Read the key at runtime — from your app’s own secure configuration or a settings surface — never from source and never baked into the build. The reference application’s settings view takes it at runtime, and its UI automation injects it through a launch intent. The registration call itself is explicit, as shown.

Draw a map surface

The SDK renders onto a platform SurfaceView you own. Three parts:

  1. A small JNI shim that hands the SDK the ANativeWindow* pointer of your surface (the pointer has no public Kotlin API):

    // src/main/cpp/native_window_shim.c — the whole shim:
    #include <android/bitmap.h>
    #include <android/native_window.h>
    #include <android/native_window_jni.h>
    #include <jni.h>
    
    JNIEXPORT jlong JNICALL
    Java_com_example_map_nativeWindowPointerFromSurface(JNIEnv* env, jobject, jobject surface) {
        ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
        return (jlong) window;   // release it again on surface-destroyed
    }
    
  2. The Kotlin declaration beside your module wiring:

    external fun nativeWindowPointerFromSurface(surface: Surface): Long
    
  3. The surface host: construct the view over the SDK, attach once the surface exists, then pump one draw per composed frame:

    import kotlinx.coroutines.withFrameNanos
    import uniffi.map_native_core.MapCamera
    import uniffi.map_native_core.MapSurfaceTargetHandle
    import uniffi.map_native_core.MapSurfaceView
    
    val mapSurface = MapSurfaceView(mapSdk)
    
    // On the platform surface's created callback (once):
    val nativeWindowPointer = nativeWindowPointerFromSurface(surface)
    mapSurface.attach(
        MapSurfaceTargetHandle(
            appkitViewPointer = null,
            iosViewPointer = null,
            androidNativeWindowPointer = nativeWindowPointer.toULong(),
        ),
        widthPixels = surfaceWidthPixels.toUInt(),
        heightPixels = surfaceHeightPixels.toUInt(),
    )
    
    // The per-frame pump (the SDK deliberately has no render loop):
    withFrameNanos { _ ->
        try {
            mapSurface.draw(
                MapCamera(
                    center,
                    zoomLevel = 12.0,
                    bearingDegrees = 0.0,
                    pitchDegrees = 0.0,
                ),
            )
        } catch (drawException: Exception) {
            // A draw in flight reports busy: retry on the next frame.
        }
    }
    

Call mapSurface.resize(width, height) from your surface-changed callback and mapSurface.detach() on surface-destroyed.

Add your first overlay

Draw more than tiles by setting the overlay list the surface draws every frame. A route polyline over two points:

import uniffi.map_native_core.GeographicCoordinate
import uniffi.map_native_core.MapOverlayDescriptor
import uniffi.map_native_core.MapOverlayKind
import uniffi.map_native_core.OverlayColor

mapSurface.setOverlays(
    listOf(
        MapOverlayDescriptor(
            kind = MapOverlayKind.ROUTE_POLYLINE,
            coordinates = listOf(
                GeographicCoordinate(latitude = 47.3769, longitude = 8.5417),
                GeographicCoordinate(latitude = 47.3774, longitude = 8.5411),
            ),
            rgbaColor = OverlayColor(red = 0.2, green = 0.4, blue = 0.9, alpha = 0.9),
            widthPixels = 3.0,
        ),
    ),
)

Before you start

Xcode with the Swift 5.10 tooling the packaging pins (swift-tools-version: 5.10), a project with an iOS 17 deployment target, and a downloaded release XCFramework, and a map-platform API key for the provider you register.

Unzip the release into a Frameworks/ directory in your project and add the Swift package product (RusticMapsSDK) through your package manifest, resolved from the release’s checksummed zip:

// Package.swift
// swift-tools-version: 5.10
import PackageDescription

let package = Package(
    name: "MyMapApp",
    dependencies: [
        // The release's Package.swift lands on the repository's
        // swift-package branch; its binaryTarget pins the release URL
        // and checksum.
    ],
    targets: [
        .executableTarget(name: "MyMapApp")
    ]
)

Until the release lands, the checked-in packaging manifest in the source tree consumes the local unzipped framework the same way; follow its binaryTarget shape.

Build the SDK and register the provider

The reference application constructs the session SDK exactly like this:

import RusticMapsSDK

let mapSdk = MapSdk()
try mapSdk.registerMapquestProviders(mapQuestApiKey)

The call throws verbatim when the key is refused.

Pass a key read at runtime (app settings, a secure configuration store, or a launch environment used by automation). Do not add it to Info.plist or to an xcconfig that ships in the app; the reference app’s settings sheet takes it at runtime.

Draw a map surface

The SDK renders onto a Metal-backed UIView you own:

let mapSurface = MapSurfaceView(mapSdk: mapSdk)

// Once the view is laid out and backed by a Metal layer:
let surfaceHandle = MapSurfaceTargetHandle(
    appkitViewPointer: nil,
    iosViewPointer: unsafeBitCast(metalBackedView, to: UInt64.self),
    androidNativeWindowPointer: nil
)
try mapSurface.attach(
    surfaceHandle,
    widthPixels: UInt32(pixelExtent.width),
    heightPixels: UInt32(pixelExtent.height)
)

The reference application drives frames from its own CADisplayLink; the SDK deliberately has no render loop:

let drawTask = Task { [mapSurface] in
    while true {
        let camera = currentCamera()   // build MapCamera per frame
        let statistics = try await mapSurface.draw(camera: camera)
        updateReadouts(statistics: statistics)
        try await Task.sleep(nanoseconds: frameIntervalNanoseconds)
    }
}

Call mapSurface.resize(width:height:) from your layout callback and mapSurface.detach() when the view leaves the hierarchy. Check frameSettled(camera:) in the pump to skip a redraw when nothing changed.

Fit the map to what you show

For a bounded region, let the camera fit the box instead of hand-tuning zoom, exactly as the route-preview flow does:

let fittedCamera = try MapCamera.fitBounds(
    boundingBox: regionBox,
    viewport: viewport,
    edgeInsets: EdgeInsetsPixels(
        left: 48, top: 48, right: 48, bottom: 96
    ),
    maximumZoomLevel: 22
)

Before you start

An initialized React Native (bare workflow) application on Android arm64-v8a or iOS, and a downloaded release tarball, and a map-platform API key for the provider you register.

Install the artifact

Install the release tarball from your checked-in copy; no registry is involved:

npm install ./rustic-maps-sdk-rn-v0.1.0.tgz

The package carries the TurboModule glue; link it with your React Native version’s normal autolinking (pod install on the iOS side).

Build the SDK and register the provider

Importing the package entry installs the Rust module into Hermes once, and the generated classes are named exports of it. Construct the SDK and register the provider set:

import { MapSdk } from '@angoratek/rustic-maps-sdk';

export async function createMapSdk(mapQuestApiKey) {
  const mapSdk = await new MapSdk();
  await mapSdk.registerMapquestProviders(mapQuestApiKey);
  return mapSdk;
}

The returned promise rejects verbatim when the key is refused.

Take the key at runtime — a settings screen, secure storage, or a launch environment for automated tests. Do not bake it into the JS bundle or a native build file; the reference app reads it from its settings surface and keeps it only when the user opts in.

Draw a map surface

The React Native artifact exposes the contract surface; the rendered surface lifecycle (native view, attach, per-frame draw) is a native view the host application composes. The reference app shell wires it through its own bridge module and drives the demo from there, which is the recommended starting point for a first integration: read its controller’s lifecycle (create the SDK, register providers, attach the surface, pump camera frames) and copy the sequence, not the demo UI.

Check the installation with the compiled-version probe:

import { getSdkCompiledVersion } from '@angoratek/rustic-maps-sdk';

getSdkCompiledVersion();
// → "0.1.0": the Rust core loaded and answered through the TurboModule.

Where to continue

With the SDK installed, continue in Search and routing; the navigation tutorial’s React Native tab shows the full guided-walk demo flow over this same surface.

Before you start

A Chromium-based browser with WebGPU (flag-free in Chromium since 113; Safari from version 26; Firefox from 141, desktop first) and a downloaded release tarball of the wasm package.

Install the artifact

Unpack the release and serve its directory from any static file server over HTTPS or localhost:

npm install ./rustic-maps-sdk-web-v0.1.0.tgz
cp -r node_modules/@angoratek/rustic-maps-sdk-web ./web
node ./web/server.js   # your own static server; localhost is fine

Load the module and start the core

<canvas id="map-canvas"></canvas>
<script type="module">
  import {
    init,
    start_sdk,
  } from './web/map_web_bindings.js';
</script>

The page’s entry, exactly as the reference harness does it:

import init, {
  MapCamera,
  GpuMapCanvas,
  CanvasTileResolution,
  retinaDevicePixelRatioThreshold,
  start_sdk,
  get_sdk_compiled_version,
} from './web/map_web_bindings.js';

const compiledVersion = await init().then(() => start_sdk());
console.log(`Maps SDK ready (version ${compiledVersion})`);

The status probe answers the same version string the native surfaces do.

Draw a map surface

The GPU canvas claims the canvas element, acquires the WebGPU device, and renders tiles fetched from a same-origin path you host:

const canvas = document.querySelector('#map-canvas');
const tileResolution = window.devicePixelRatio
  >= retinaDevicePixelRatioThreshold()
  ? CanvasTileResolution.Retina
  : CanvasTileResolution.Standard;
const gpuCanvas = new GpuMapCanvas(canvas, tileResolution);
const adapterDescription = await gpuCanvas.initialize();
console.log(`rendering on ${adapterDescription}`);

// The canvas fetches its raster tiles from the same-origin
// /tiles/{zoom}_{tileX}_{tileY}.png path; record or serve imagery there.

// The render frame: build the camera, present, repeat on change.
const fieldOfViewDegrees = 60;
const camera = new MapCamera(47.3769, 8.5417, 14.0, 0.0, 0.0);
const frameStatistics = await gpuCanvas.render_frame(camera, fieldOfViewDegrees);

If initialize() fails in a browser without WebGPU, give the page the documented degradation message (name navigator.gpu and the missing capability; link the MDN compatibility table) — never a user-agent sniff.

Project and unproject

The same camera answers screen mappings without rendering:

const screenPoint = camera.project_to_screen(coordinate, viewport, fieldOfView);
const groundCoordinate = camera.screen_to_ground_coordinate(screenPoint, viewport, fieldOfView);
const tileKeys = camera.visible_tile_keys(viewport, fieldOfView, tileZoom);

The TypeScript types of these value shapes are mirrored in the package’s hand-reviewed type surface; field names are lower camel case.

What you verified

You built the SDK entry object, registered the map provider with your own key, and drew rendered map imagery through the SDK’s renderer. The FFI contract carries every member you called; the generated API reference documents them.

Search and routing

Resolve a typed query into places with the geocoding and search contracts, then plan a route through the results. The tutorial builds on the first map tutorial’s installed SDK; every snippet compiles against the frozen FFI contract.

Resolve a query into places

The provider surface runs on Kotlin coroutines: every MapSdk call you see here is a suspend fun. Run it from a coroutine scope:

import uniffi.map_native_core.GeographicCoordinate
import uniffi.map_native_core.PlaceResult

suspend fun resolveDestination(mapSdk: MapSdk, queryText: String): PlaceResult? =
    mapSdk.forwardGeocode(queryText).firstOrNull()

forwardGeocode answers every candidate the provider resolved, most likely first; searchAutocomplete answers fast completion candidates while the user types, and searchPlaces answers full results for a settled query. Both search calls take an optional BoundingBox boundary that restricts the search.

Plan the route

calculateRoute takes the ordered waypoints and the routing options. The reference application plans pedestrian routes like this:

import uniffi.map_native_core.AvoidOptions
import uniffi.map_native_core.RouteOptions
import uniffi.map_native_core.RouteStrategy
import uniffi.map_native_core.TransportationMode
import uniffi.map_native_core.UnitSystem

val routeOptions = RouteOptions(
    transportationMode = TransportationMode.PEDESTRIAN,
    routeStrategy = RouteStrategy.SHORTEST,
    unitSystem = UnitSystem.METRIC,
    avoidOptions = AvoidOptions(
        tollRoads = false,
        limitedAccessHighways = false,
        ferries = false,
        unpavedRoads = false,
    ),
    departureTimestampSeconds = null,
)

val waypoints = listOf(
    originCoordinate,
    destinationCoordinate,
)
suspend fun planRoute(mapSdk: MapSdk, waypoints: List<GeographicCoordinate>) =
    mapSdk.calculateRoute(waypoints, routeOptions)

The returned RouteData carries total distance and duration, the geometry’s bounding box, and one leg per waypoint pair, each with its maneuver steps.

Frame the route preview

Let the camera fit the route’s bounding box through the fitBounds constructor instead of hand-tuning zoom:

import uniffi.map_native_core.EdgeInsetsPixels
import uniffi.map_native_core.MapCamera
import uniffi.map_native_core.ViewportPixels

val fittedCamera = try {
    MapCamera.fitBounds(
        route.boundingBox,
        ViewportPixels(widthPixels, heightPixels),
        EdgeInsetsPixels(left = 224.0, top = 48.0, right = 48.0, bottom = 96.0),
        maximumZoomLevel = 24.0,
    )
} catch (sdkException: SdkException) {
    // A refused fit (no positive box area, unusable insets) surfaces
    // verbatim; keep the previous camera.
    null
}

The insets you pass are the pixels your overlays keep clear, so the destination never hides under your own panels.

Show the route over the surface

Draw the geometry with the route-polyline overlay (the first map tutorial’s overlay step covers the descriptor), then start a trip on the route in the navigation tutorial.

Resolve a query into places

The provider surface throws on failure and awaits its provider calls:

import RusticMapsSDK

let places = try await mapSdk.forwardGeocode(addressText)
guard let destination = places.first else {
    // No candidate: surface the empty state.
    return
}

searchAutocomplete gives fast completion candidates for a query under construction, searchPlaces gives full results for a settled one, and both take an optional BoundingBox boundary.

Plan the route

let routeOptions = RouteOptions(
    transportationMode: TransportationMode.pedestrian,
    routeStrategy: RouteStrategy.shortest,
    unitSystem: UnitSystem.metric,
    avoidOptions: AvoidOptions(
        tollRoads: false,
        limitedAccessHighways: false,
        ferries: false,
        unpavedRoads: false
    ),
    departureTimestampSeconds: nil
)

let route = try await mapSdk.calculateRoute(
    [originCoordinate, destination.coordinate],
    routeOptions: routeOptions
)
try planRoutePreview(route)

The returned RouteData carries distance, duration, the bounding box, and one leg per waypoint pair with its steps.

Frame and draw the preview

Fit the camera to the route’s bounding box through the insets-aware constructor (shown in the first map tutorial), then hand the route’s geometry to the surface’s overlay list:

let fittedCamera = try MapCamera.fitBounds(
    boundingBox: route.boundingBox,
    viewport: viewport,
    edgeInsets: EdgeInsetsPixels(left: 48, top: 48, right: 48, bottom: 96),
    maximumZoomLevel: 22
)
pinnedFitBoundsCamera = fittedCamera

Resolve a query into places

The TurboModule surface mirrors the contract as promise-returning methods. Everything you call on MapSdk resolves to the same records the other platforms see, with lower camel case field names:

import { MapSdk, GeographicCoordinate, BoundingBox } from '@angoratek/rustic-maps-sdk';

const mapSdk = new MapSdk();
await mapSdk.registerMapquestProviders(mapQuestApiKey);

// Fast completion candidates while the user types:
const completions = await mapSdk.searchAutocomplete(queryText, null);

// Full results for a settled query, restricted to the visible region:
const results = await mapSdk.searchPlaces(settledQueryText, visibleRegionBoundingBox);

// Address text into a coordinate:
const destination = (await mapSdk.forwardGeocode(destinationText))[0];

Pass null as the boundary to search worldwide, and pass a BoundingBox (southWest/northEast corners, lower camel case) to restrict the search.

Plan the route

const waypoints = [
  { latitude: originLatitude, longitude: originLongitude },
  { latitude: destination.coordinate.latitude, longitude: destination.coordinate.longitude },
];

const route = await mapSdk.calculateRoute(waypoints, {
  transportationMode: 'Pedestrian',
  routeStrategy: 'Shortest',
  unitSystem: 'Metric',
  avoidOptions: {
    tollRoads: false,
    limitedAccessHighways: false,
    ferries: false,
    unpavedRoads: false,
  },
  departureTimestampSeconds: null,
});

console.log(route.distanceMeters, route.durationSeconds);

A rejected promise carries the typed SdkError; surface it in your error state instead of retrying blindly (the rate limiter refills on its own).

What the wasm seam covers today

The WebAssembly surface ships the camera, projection, rendering, and offline-routing slice of the contract; the async provider surface (MapSdk’s geocoding, search, and routing members) has no wasm exposure yet and stays a native-platform surface. The types mirror the contract, and the contract-sync test keeps the two in lockstep.

Route over an offline graph archive

The wasm seam answers routes over a compiled offline routing archive loaded from in-memory bytes — the same object shape the native routing call returns:

import { offline_route_from_archive_bytes } from './web/map_web_bindings.js';

// The archive, fetched from a same-origin path (the demo serves the
// routing-grid fixture at /offline-graph/routing_grid.rmog):
const archiveBytes = new Uint8Array(
  await fetch('/offline-graph/routing_grid.rmog').then(
    (archiveResponse) => archiveResponse.arrayBuffer(),
  ),
);

const route = await offline_route_from_archive_bytes(
  archiveBytes,
  startLatitude,
  startLongitude,
  endLatitude,
  endLongitude,
);

The route records mirror the contract’s RouteData/RouteLeg/ RouteStep dictionaries in lower camel case (instructionDisplayText, shape), so a routing UI written for the native surface reads the wasm answer with no reshaping. Failures throw the verbatim refusal string: bytes that fail archive validation, a coordinate outside every node’s snap tolerance, or a request pair the graph cannot connect.

Turn-by-turn navigation

Start a trip on a route, deliver position fixes, and receive the banner instruction, deviation status, phase, and speech-trigger channels. This tutorial rehearses the reference application’s guided-walk demo: the route you planned in Search and routing becomes a simulated drive with guidance. Every snippet compiles against the frozen FFI contract.

Build the controller and subscribe first

Subscribe to the topics before you start the trip, so the initial instruction dispatch of the trip start is received. The listener implements the four callbacks the contract carries:

import uniffi.map_native_core.MapNavigationController
import uniffi.map_native_core.NavigationStateEventListener

class GuidanceListener(
    private val onInstruction: (NavigationInstruction) -> Unit,
    private val onSpeech: (SpeechInstruction) -> Unit,
) : NavigationStateEventListener {
    override fun onNavigationInstruction(instruction: NavigationInstruction) {
        onInstruction(instruction)
    }

    override fun onSpeechInstruction(speechInstruction: SpeechInstruction) {
        onSpeech(speechInstruction)
    }

    override fun onDeviationStatusChange(deviationStatusChange: DeviationStatusChange) {
        // Update your reroute affordance here.
    }

    override fun onNavigationPhaseChange(navigationPhaseChange: NavigationPhaseChange) {
        // Drive the navigation drawer's phase state here.
    }
}

val navigationController = MapNavigationController()
val listener = GuidanceListener(onInstruction = ::showBanner, onSpeech = ::speak)

val subscriptions = listOf(
    navigationController.subscribeToNavigationInstructions(listener),
    navigationController.subscribeToSpeechInstructions(listener),
    navigationController.subscribeToDeviationStatus(listener),
    navigationController.subscribeToPhaseChanges(listener),
)

A subscription is cancelled with its handle when your screen leaves; dropping the reference does not unsubscribe.

Start the trip

Use the SDK’s default policies unless your product measured different ones:

import uniffi.map_native_core.defaultDeviationPolicy
import uniffi.map_native_core.defaultStepAdvancePolicy
import uniffi.map_native_core.UnitSystem

navigationController.startTrip(
    route,
    UnitSystem.METRIC,
    defaultStepAdvancePolicy(),
    defaultDeviationPolicy(),
)

The initial instruction payload dispatches before the call returns; the banner shows the first maneuver.

Deliver position fixes

One fix at a time, exactly like a live location source or a recorded trace replay would:

import uniffi.map_native_core.NavigationLocationUpdate

suspend fun replayFixes(
    navigationController: MapNavigationController,
    samples: Sequence<GeographicCoordinate>,
    sampleIntervalMilliseconds: Long,
) {
    for (sample in samples) {
        navigationController.provideLocationUpdate(
            NavigationLocationUpdate(
                coordinate = sample,
                horizontalAccuracyMeters = 8.0,
                courseHeadingDegrees = null,
                timestampSeconds = null,
            ),
        )
        delay(sampleIntervalMilliseconds)
    }
}

Fixes without a reported accuracy never advance a step and are never judged for deviation, so a simulated source must carry its accuracy like a real fix does.

Voice the speech channel

The platform owns playback; the SDK owns when a line is newsworthy. When onSpeechInstruction delivers a SpeechInstruction, hand its verbatim text to the platform’s speech engine:

val textToSpeechEngine = TextToSpeech(context, this)
// onSpeechInstruction:
textToSpeechEngine.speak(
    speechInstruction.utteranceSpeechText,
    TextToSpeech.QUEUE_ADD,
    null,
    speechInstruction.utteranceId.toString(),
)

The utterance id is the contract’s monotonic id, so your dedup keeps its identity across repeats.

Reroute when guidance freezes

A deviating trip asks for a replacement route through the controller’s gates. Keep the state machine in your own coordinator, exactly as the reference application’s trip driver does:

if (tripState.deviation?.gradeKind == DeviationGradeKind.FULLY_OFF_ROUTE &&
    navigationController.beginRerouteCalculation()
) {
    coroutineScope.launch {
        val replacement = mapSdk.calculateRoute(
            listOf(currentFixCoordinate, originalDestination),
            routeOptions,
        )
        navigationController.provideReroute(replacement, defaultStepAdvancePolicy())
    }
} else {
    navigationController.cancelRerouteCalculation()
}

Build the controller and subscribe first

The same four callbacks arrive as a Swift conforming object:

import RusticMapsSDK

final class GuidanceListener: NavigationStateEventListener {
    func onNavigationInstruction(_ instruction: NavigationInstruction) {
        // Update the banner instruction.
    }

    func onSpeechInstruction(_ speechInstruction: SpeechInstruction) {
        // Hand the verbatim text to your speech engine.
    }

    func onDeviationStatusChange(_ deviationStatusChange: DeviationStatusChange) {
        // Update the reroute affordance.
    }

    func onNavigationPhaseChange(_ navigationPhaseChange: NavigationPhaseChange) {
        // Drive the phase state.
    }
}

let navigationController = MapNavigationController()
let listener = GuidanceListener()

let subscription = navigationController.subscribeToNavigationInstructions(listener)
let speechSubscription = navigationController.subscribeToSpeechInstructions(listener)

Start the trip

try navigationController.startTrip(
    route,
    unitSystem: UnitSystem.metric,
    stepAdvancePolicy: defaultStepAdvancePolicy(),
    deviationPolicy: defaultDeviationPolicy()
)

Deliver position fixes

func deliver(_ fix: RecordedFix) {
    navigationController.provideLocationUpdate(
        NavigationLocationUpdate(
            coordinate: fix.coordinate,
            horizontalAccuracyMeters: fix.horizontalAccuracyMeters,
            courseHeadingDegrees: fix.courseHeadingDegrees,
            timestampSeconds: nil
        )
    )
    if let tripState = navigationController.currentTripState() {
        updateReadouts(tripState)
    }
}

Voice the speech channel

let speechEngine = AVSpeechSynthesizer()
// In onSpeechInstruction:
let utterance = AVSpeechUtterance(string: speechInstruction.utteranceSpeechText)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
speechEngine.speak(utterance)

Reroute when guidance freezes

if tripState.deviation?.gradeKind == .fullyOffRoute {
    if navigationController.beginRerouteCalculation() {
        let replacement = try await mapSdk.calculateRoute(
            [currentFixCoordinate, originalDestination],
            routeOptions: routeOptions
        )
        try navigationController.provideReroute(
            replacement,
            stepAdvancePolicy: defaultStepAdvancePolicy()
        )
    } else {
        navigationController.cancelRerouteCalculation()
    }
}

The trip state readback (currentTripState()) carries the phase, the progress, and isCalculatingNewRoute so your UI can show the recalculation honestly.

What the React Native surface carries

The TurboModule exposes the same contract members as promise-returning methods, including the navigation controller’s. A guided-walk integration builds a controller object, subscribes the topics, and delivers fixes:

import {
  MapNavigationController,
  defaultStepAdvancePolicy,
  defaultDeviationPolicy,
} from '@angoratek/rustic-maps-sdk';

const navigationController = await new MapNavigationController();

const instructionsSubscription = await navigationController.subscribeToNavigationInstructions(listener);
const speechSubscription = await navigationController.subscribeToSpeechInstructions(listener);

The listener implements the four callback methods the contract carries (onNavigationInstruction, onSpeechInstruction, onDeviationStatusChange, onNavigationPhaseChange); each invocation delivers the same payloads the native platforms receive, with lower camel case field names.

Start the trip and deliver fixes

await navigationController.startTrip(
  route,
  'Metric',
  defaultStepAdvancePolicy(),
  defaultDeviationPolicy(),
);

// One fix per sample of your location source (or its recorded trace):
await navigationController.provideLocationUpdate({
  coordinate: { latitude: sampleLatitude, longitude: sampleLongitude },
  horizontalAccuracyMeters: 8.0,
  courseHeadingDegrees: null,
  timestampSeconds: null,
});

const tripState = await navigationController.currentTripState();

Voice the speech channel

The SDK triggers; your application voices. In the speech callback, hand the verbatim text to the platform’s speech engine (Expo’s Speech, or your own TTS wrapper), and use the delivery’s utteranceId for dedup:

function onSpeechInstruction(speechInstruction) {
  Speech.speak(speechInstruction.utteranceSpeechText, {
    language: 'en-US',
    onDone: () => markUtteranceComplete(speechInstruction.utteranceId),
  });
}

Scope note

The turn-by-turn controller surface is native-platform territory today: the WebAssembly seam ships the camera, rendering, and offline-routing slice, and no navigation controller rides it yet. The contract’s navigation surface stays native (the first map tutorial’s note covers the provider surface the same way).

What runs today

Turn-by-turn state is exercised end to end in the reference applications, and the browser harness runs the same renderer core the navigation follow-camera draws with: the live demo will include a guided walk once its bundle lands. Until then, run the navigation simulation on the native platforms; the demo loop page shows how the browser harness runs and what it exercises.

Offline maps

Keep map imagery and routing working without a network. The SDK’s offline story has two layers: a disk response cache that holds raster tile imagery under your byte budget, and a compiled routing archive that answers routes with no network round trip at all.

Register the providers with a tile cache

One registration call installs the map-platform provider set and wraps the raster-tile contract in the disk response cache. The reference application’s offline-maps demo runs exactly this:

val tileCacheDatabaseFile = File(context.cacheDir, "reference-tile-cache.sqlite")
val tileCacheBudgetBytes = 16777216UL   // 16 MiB, the demo's absolute budget

mapSdk.registerMapquestProvidersWithTileCache(
    mapQuestApiKey,
    tileCacheDatabaseFile.absolutePath,
    tileCacheBudgetBytes,
)

The call opens (or creates) the SQLite response cache at the path. A refusal (unusable path, non-positive budget) surfaces verbatim.

Read and tune the caching terms

The cache starts at the tiles category’s default time-to-live, 30 calendar days — the imagery-provider terms ceiling. Tune the live value and read what went into force:

mapSdk.setRasterTileCacheTimeToLive(timeToLiveSeconds)
val terms = mapSdk.rasterTileCacheTerms()
// terms.effectiveTimeToLiveSeconds
// terms.providerTermsCeilingSeconds
// terms.defaultTimeToLiveSeconds

A request above the provider’s declared ceiling is accepted, and the ceiling clamps what the cache retains; the readback shows the effective value, so display it in your offline-maps settings screen.

Pre-warm the tiles you need

Cached tile responses render whenever the network is unavailable: a cache miss goes to the network and back-fills, and a cache hit answers from disk. Render the regions your users expect to revisit once while online, and the cache holds them until the budget or time-to-live says otherwise.

Route without the network

The offline routing layer runs inside the SDK: a compiled routing archive — OpenStreetMap extract in, self-describing archive file out — answers routes with A* over a memory-mapped graph, honoring recorded turn restrictions and deriving maneuver text from the geometry’s bearing change.

The direct route call from foreign code joins the contract as an addition-only member in a later slice; today the offline layer answers the automatic fallback (the routing decorator retries over the offline graph when the online provider fails) and the WebAssembly surface’s offline route seam, and its maneuver texts stay honest where the extract carries no street names.

Register the providers with a tile cache

let tileCacheDatabasePath = FileManager.default
    .cachesDirectory.appendingPathComponent("reference-tile-cache.sqlite")
    .path
let tileCacheBudgetBytes: UInt64 = 16_777_216   // 16 MiB

try mapSdk.registerMapquestProvidersWithTileCache(
    mapQuestApiKey,
    tileCacheDatabasePath: tileCacheDatabasePath,
    tileCacheAbsoluteMaximumBytes: tileCacheBudgetBytes
)

Read and tune the caching terms

try mapSdk.setRasterTileCacheTimeToLive(timeToLiveSeconds)
let terms = try mapSdk.rasterTileCacheTerms()
// terms.effectiveTimeToLiveSeconds
// terms.providerTermsCeilingSeconds
// terms.defaultTimeToLiveSeconds

Pre-warm and render offline

Cached tiles render whenever the network is unavailable. Render the regions your users revisit while online; the cache answers from disk.

Route without the network

The offline routing layer runs inside the SDK over a compiled archive: A* on a memory-mapped graph, recorded turn restrictions honored, maneuver text derived from the geometry. The direct route call from Swift joins the contract as an addition-only member in a later slice; today the layer serves the automatic fallback when the online provider fails, and the WebAssembly surface’s offline route seam.

Register the providers with a tile cache

import { MapSdk } from '@angoratek/rustic-maps-sdk';

const mapSdk = new MapSdk();
await mapSdk.registerMapquestProvidersWithTileCache(
  mapQuestApiKey,
  tileCacheDatabasePath,   // a writable SQLite path your app owns
  16_777_216,              // the absolute byte budget (16 MiB)
);

Read and tune the caching terms

await mapSdk.setRasterTileCacheTimeToLive(timeToLiveSeconds);
const terms = await mapSdk.rasterTileCacheTerms();
// terms.effectiveTimeToLiveSeconds
// terms.providerTermsCeilingSeconds
// terms.defaultTimeToLiveSeconds

Show the effective terms in your offline-maps settings screen; a value above the provider’s ceiling is accepted and clamped, and the readback is what went into force.

Scope note

The wasm surface renders from the same-origin /tiles/ imagery path you host, and loads the offline routing archive from in-memory bytes; the SQLite tile cache is a native-platform surface (the provider registration seam has no wasm exposure yet).

Route over the archive

The Search and routing tutorial’s WebAssembly tab shows the full offline routing call: archive bytes in, contract-shaped route records out, no network. The same compiled archive serves the native platforms, so one compiler run feeds every target.

Downloads

First release pending. The artifacts below are published only as GitHub Releases — there is no Maven Central, no npm registry, and no SPM registry. The release workflow that produces this matrix is being rehearsed; when the first tag is published, the links on this page resolve to the released assets, in the exact file names the table lists.

Artifact matrix

PlatformArtifact fileStatusWhat it contains
Androidrustic-maps-sdk-android-vX.Y.Z.aarshippedKotlin bindings plus the Rust library for arm64-v8a, armeabi-v7a, x86_64
iOSrustic-maps-sdk-ios-vX.Y.Z.xcframework.zipshippedThe XCFramework (arm64 device and simulator slices), packaged for a Swift Package Manager binary target
WebAssemblyrustic-maps-sdk-web-vX.Y.Z.tar.gz (raw pkg/) and rustic-maps-sdk-web-vX.Y.Z.tgz (npm)shippedThe wasm module, its JavaScript shim and both type files; the npm package adds the manifest, licenses and notices
React Nativerustic-maps-sdk-rn-vX.Y.Z.tgz (npm, @angoratek/rustic-maps-sdk)shipped (source package)The TurboModule package: generated TypeScript, JSI C++, podspec and CMake glue; the native Rust payload lands with the build-rn pipeline item
All platformsrustic-maps-sdk-notices-vX.Y.Z.mdshippedThe third-party dependency notices that accompany your own distribution

What accompanies every release

Every release carries a per-asset .sha256 sidecar, the aggregated SHA256SUMS.txt manifest covering all assets, and a Sigstore signature bundle (.sigstore.json) per asset and for SHA256SUMS.txt itself. The release is immutable: a problem is fixed by a new tag, never by replacing an asset.

Verifying an artifact

Verify the checksum first, then the signature. The signature bundles bind the asset to the release workflow and the vX.Y.Z tag, so the verification command pins both:

sha256sum --check SHA256SUMS.txt --ignore-missing

cosign verify-blob <artifact-file> \
  --bundle <artifact-file>.sigstore.json \
  --certificate-identity-regexp \
  "https://github.com/angoratek/rustic-maps-sdk/.github/workflows/release.yml@refs/tags/vX.Y.Z" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --yes

The --ignore-missing flag keeps the sha256sum run to the files you actually downloaded.

Installing from a release

The tutorials carry the per-platform install steps against these exact artifact names: the checked-in AAR path for Android, the checksummed zip for the Swift Package Manager binary target, and the pkg/ archive for the wasm package. The npm packages ship the same generated trees with a manifest, licenses and notices; the React Native package is source-only until its native payload joins the release pipeline.

Screenshots

Every screenshot on this page was captured from the reference applications at the same source revision on a standing test device or in the browser loop. Provider credentials were supplied at runtime through each app’s settings surface or its documented automation seam; no build on this page embeds a credential.

Android — Kotlin and Jetpack Compose

iOS — Swift and SwiftUI

WebAssembly — the browser playground

The browser build runs the SDK’s own wasm module and draws through WebGPU, so the playground is the same core rather than a simulation. The capture shows its consumer surface: Plan a trip with a selected demo destination, with the expert controls folded under Advanced. It is served keyless at the live demo over the committed fixture imagery.

React Native — one TypeScript surface, two native shells

Reading the screenshots

The four captures share one camera, one design language (the reference-app UX tokens) and one demo artifact. Differences are the platform’s own chrome: Compose’s Material surface on Android, SwiftUI’s safe-area handling on iOS, the React Native bridge’s host view, and the browser’s WebGPU canvas. The map content, the control cluster, the scale ladder and the attribution line are the same.

Versions and support

This page states how versions are chosen, what a released version line receives, and how changes reach it. The workspace version is 0.1.0, and the first stable release is still being prepared; the Downloads page lists the artifacts when it lands. The Requirements and distributions page carries the per-platform requirement facts.

Version numbering

Versions follow semantic versioning as MAJOR.MINOR.PATCH. One workspace version is the single source of truth: the Android, iOS, React Native, and WebAssembly packages are all stamped from it at release time, so one version string identifies one core.

The SDK is pre-1.0. The FFI contract is addition-only, so existing members keep their signature and meaning across an upgrade. Packaging can still change while the release is 0.x: an artifact-set change or a renamed target bumps the minor version, and a pre-1.0 minor may break a build even when the contract did not change. Treat a minor upgrade as one to test, not one to assume.

Pin exact versions

Release archives are immutable and carry the version in the file name, for example rustic-maps-sdk-android-vX.Y.Z.aar. Keep the exact version you validated against and upgrade deliberately. The version you pin also names the notices file that accompanies it.

Two release tracks

TrackWhat it isWhat it means for you
StableA vX.Y.Z tag and its release assetsThe version to ship; the support statements on this page apply to it
PreviewA pre-release tag (vX.Y.Z-rc.N) from the same workflow, marked as a pre-releaseEvaluation only; a preview is superseded by the stable release of the same version

Tags are never moved, and a broken release is superseded by a new tag; the failed release’s assets and checksums stay.

Support window

Each released minor line is intended to receive fixes for a stated window after the next minor line releases. That window length is not yet fixed and is published here with the first stable release. Until it is published, plan to upgrade to the newest minor line for fixes.

Deprecation

The contract’s addition-only rule means a released contract member keeps its signature and meaning; a new release adds members rather than removing them. A deprecation can still mark a member or a packaging detail as superseded, and it is announced in the release notes and the changelog before the release that retires it. The exact deprecation lead time is not yet fixed and is published here with the first stable release.

Checking the loaded version

The compiled SDK answers get_sdk_compiled_version on every surface. Compare it with the artifact you shipped. A mismatch means the loaded core and your dependency declaration disagree, and a diagnostics screen should show the value.

Getting support

Report a problem through the project’s issue tracker with four things: the get_sdk_compiled_version output, your platform and operating system version, the exact SDK version you pinned, and a minimal reproduction. For a rendering problem, include the TileFrameStatistics of an affected frame. For a provider problem, include the SdkError variant and its verbatim detail.

Changelog

The SDK is in active development; everything below is unreleased. The FFI contract surface it describes changes addition-only: existing members keep their signature and meaning. This page is a curated, public-facing summary. The complete development log lives in the source repository.

Imagery and caching

  • All seven MapQuest provider APIs are implemented and fixture-tested. They cover routes with strategies and multiple legs, the route matrix, forward and reverse geocoding, search prediction, traffic incidents, traffic-flow congestion polylines, and raster tiles in standard and retina resolutions.
  • A SQLite-backed response cache holds provider responses under an absolute byte budget, a share of free disk space, or both. It provides per-category time-to-live, LRU eviction, and cache-then-network behavior around every provider contract. Tile imagery caches heavily by default: 30 calendar days, the common maximum under imagery-provider terms. Each provider contract declares its own caching terms, so an integrator tunes the cache knowingly per provider (setRasterTileCacheTimeToLive, read back through rasterTileCacheTerms). Values above a provider’s declared ceiling stay compliance-clamped.

Rendering

  • The project maintains its own minimal wgpu raster renderer. It implements web-mercator projection with exact frustum tile listing (Sutherland–Hodgman over the six frustum planes, with antimeridian world-copy handling), an anchored camera with fit-bounds and insets-aware framing and a flyTo-style hyperbolic arc, a byte-bounded GPU texture cache, and a tile scheduler with per-frame dedup, parent fallback, and a fetch budget. Route, polygon, marker, and traffic overlay layers sit on top of the tiles, and the renderer owns the location indicator (a tracking dot with accuracy ring, or a guided course arrow).
  • The same renderer scene draws headless (deterministic golden-image tests), into live Android/iOS surfaces, and in the browser through WebGPU. One WGSL/vertex pattern is byte-identical across the wasm and native paths.

Offline routing

  • The offline routing pipeline is in place. A deterministic compiler turns an OpenStreetMap PBF extract into a self-describing routing archive (nodes, a per-edge pack with road class, speed, and access columns, and turn restrictions). A memory-mapped reader validates the container’s alignment invariants. An offline routing provider answers routes with A* over the graph. It honors recorded turn restrictions and derives turn maneuvers (left/right/slight/sharp/U-turn) from the geometry’s bearing change; its texts stay honest where the archive carries no street names. A routing decorator retries over the offline graph when the primary (online) provider fails.
  • The same graph runs in the browser: the wasm surface loads the compiled archive from in-memory bytes and answers the same route-shaped objects and typed refusals, so offline routing behavior is regression-tested in a plain browser run.
  • Navigation state is a pure reducer over Option<NavigationSession>. It implements Ferrostar-pattern step-advance conditions (manual, eager distance-to-end, delayed current-step, Or/And combinators, and the fresh-instance reset contract), deviation detection with accuracy-aware allowances, and provider-verbatim instruction composition (banner and speech text; nothing rewritten).
  • Deviation is graded. A fully-off-route state freezes the guidance and asks for a reroute; an off-step-but-on-route state keeps counting down and reconciles to the projected step. Speed runs advance through many steps per fix, and the advance is bounded and stack-safety-tested. The fully-off-route instruction text suppresses the countdown honestly rather than projecting it from a stale anchor.
  • The SDK owns speech triggering: it publishes a speech-instruction topic over documented distance bands. Platforms voice that topic and keep their own pacing and focus policies.

Camera and gestures

  • The gesture-input camera seam keeps the camera math in the SDK. Platform recognizers forward plain deltas, and the SDK applies them: pan by screen pixels, focal-point zoom and rotation with an exact hold-the-focal-point correction, clamped through the camera’s own ranges.

Bindings and platforms

  • UniFFI drives the generated Kotlin/Swift bindings and the React Native bindings from a frozen, addition-only UDL contract. The wasm surface is a hand-reviewed TypeScript mirror regression-checked against the same UDL. Callback providers (geocoding, tiles) and a navigation controller with per-topic subscriptions cross the foreign callbacks seam. Exported async methods run through a crate-owned runtime handback, so foreign event loops never meet an unserved reactor.
  • Reference applications on Android (Compose), iOS (SwiftUI), and the web playground implement one shared design language. Each reaches search → route → simulated turn-by-turn with speech through a bottom-sheet home, a five-control cluster (zoom, scale bar, my-location, 2D/3D, compass), a follow camera, an offline tile-cache card, and a readiness-chip diagnostics readout. The web surface reaches reference-app parity with the native demos.

Vector tiles

  • A Mapbox Vector Tile v2.1 decoder turns fetched tiles into layers, features and geometries with typed errors on every malformed path. A data-only style pass maps decoded layers into the renderer’s fill, polyline and point overlays (an optional one-attribute text filter is supported). The OpenStreetMap US Sourdough tileset is the first vector-tile provider, with attribution and terms carried by its source descriptor.

Provider credentials

  • The reference applications never embed a provider key in a build. They start keyless and take credentials at runtime from a settings surface whose rows come from one shared provider registry; keys stay in memory unless the user opts in to device secure storage (Keychain or Android Keystore). Automated UI runs can inject a credential through a launch intent/environment; it is never persisted or logged.

Releases and the live demo

  • A tag-driven release pipeline runs the gate set on the tagged tree, builds the Android AAR, the iOS XCFramework archive and the wasm package, and assembles a draft GitHub Release with an exact asset-set check, SHA-256 sidecars and manifest, keyless Sigstore signature bundles, regenerated third-party notices and changelog-derived notes.
  • The same WebAssembly build powers a live, keyless demo at this site’s /demo path. Its provider panel can take a runtime key for live geocoding, routing and tile probes; the static demo defaults to the recorded fixture data.

Diagnostics and ops

  • A tracing-facade diagnostics layer provides bounded, per-subsystem ring sinks with coordinate scrubbing at the sink. Its severity gate never raises once finer events have been requested. Subscribe and flush seams on the native and wasm surfaces back the reference apps’ readouts and a later log-sharing story.
  • CI runs fmt, clippy with warnings-as-errors, the workspace test suite, a wasm-targeted clippy closure, a browser e2e suite, and the documentation-site pipeline (build → scrub → stage) on every push.

Attribution

Each provider descriptor carries its own attribution line, and the reference applications render it from the shared registry. The bundled demo currently shows MapQuest imagery and OpenStreetMap data: map imagery © MapQuest; map data © OpenStreetMap contributors (ODbL). Third-party dependency notices are regenerated into their chapter.

FAQ and troubleshooting

The caveats integrators actually hit, collected from the code and the reference applications. The performance page carries the tuning guidance; this page carries what to check when something misbehaves.

Web GPU and browser support

The browser surface needs WebGPU; there is no WebGL fallback. The wasm canvas fails its initialize() when no WebGPU adapter supports the canvas or the device request fails, and then reports the error verbatim. Your page should catch it and present the degradation message: name the missing capability (navigator.gpu is the feature test), and link MDN’s compatibility table. Version floor: Chromium-based browsers since 113 (flag-free), Safari since 26, Firefox since 141 (desktop first), and always a secure context (HTTPS or localhost). No user-agent sniffing.

The map stays empty on an Android emulator after rendering starts. Android emulators render through SwiftShader over ANGLE, a software graphics stack; that is an expected emulator fact, not a failure. On the emulator the GPU surface negotiates through the GL backend: set WGPU_BACKEND=gl in your build’s environment as the reference application does. On real hardware the line is redundant and harmless.

API key and provider failures

How do I supply the provider key? Read it at runtime — from your app’s own secure configuration or a settings surface — and pass it to the provider registration call. Never commit it or bake it into an app bundle. The reference applications ship keyless and take the key from their settings view, with opt-in secure-device storage; their UI suites inject one through a launch intent/environment for automated runs.

The SDK throws immediately at registration when the key is refused. registerMapquestProviders answers with the provider’s verbatim detail; surface it, do not catch-and-continue.

Calls throw RateLimitExhausted. The shared token-bucket limiter is exhausted. The bucket refills on its own schedule, so retry after the next refill instead of surfacing an error; cache-then-network behavior around every provider contract keeps repeated identical requests free of quota.

Calls throw ProviderNotConfigured naming a contract. That contract has no registered implementation on the MapSdk object. Registration is per object: re-registration replaces every slot, so a second registerMapquestProviders call is safe, but a second MapSdk() needs its own registration.

Elevation throws ProviderNotConfigured even with the provider set installed. The elevation contract has no provider implementation yet; its calls keep refusing until one lands.

Offline behavior

Tiles render but routes fail offline, or the other way around. The two offline layers are separate. Tile imagery answers from the disk cache you registered (registerMapquestProvidersWithTileCache), within its byte budget and time-to-live. Routing answers from a compiled routing archive, independent of the imagery cache; check that the archive you compiled covers the queried region (see the next item).

A route refusal comes back offline. The offline graph refuses, rather than guessing, in three cases: bytes that fail the archive validation, a coordinate outside every node’s snap tolerance, and a request pair the graph cannot connect. Check the archive covers the areas you query; a small extract is the usual cause.

Maneuver text has no street names offline. The archived graph carries no street-name strings, so the offline maneuver texts name the turn direction and the distance and stay honest about what they do not know. Named maneuver text arrives from the online provider.

Tile cache

A tuned time-to-live does not stick after re-registration. Registration is a reset: a freshly registered cache starts again at the category default, so re-apply the override after registering. The rasterTileCacheTerms readback shows the effective value.

A large requested time-to-live is accepted but the cache still expires entries. The installed provider’s declared terms ceiling (compliance-bound at 30 calendar days for the imagery) clamps what the cache retains. Read the ceiling from rasterTileCacheTerms and display it next to the effective value.

Setting a zero time-to-live throws. A zero value would expire every entry on capture; the refusal names the mistake.

Compliance

Attribution. Keep the attribution line visible in your application: imagery © MapQuest; map data © OpenStreetMap contributors (ODbL). If you redistribute the SDK or ship an application built on it, reproduce the third-party notices with your distribution.

Third-Party Notices

Generated by scripts/generate-third-party-notices from the workspace dependency graph (Cargo.toml manifests + Cargo.lock, via cargo metadata --offline — no network access). License identities are additionally gated by cargo-deny (deny.toml). Regenerate and commit this file whenever any dependency changes. Output is deterministic, so a re-run on an unchanged graph is a byte-identical no-op.

This file lists the third-party software the workspace depends on: 384 registry packages (326 reachable through normal dependency edges, 58 test/dev-only). The first-party workspace crates are not listed here; they carry the SDK’s own license (MIT OR Apache-2.0, see Cargo.toml).

Map-data attribution

Separate from the software above: map imagery © MapQuest; map data © OpenStreetMap contributors, licensed under the Open Database License (ODbL). Every reference app renders this attribution in-app from the shared provider registry descriptors (platform/providers.json; the web playground reads the same file). Integrators must keep this attribution visible in their own apps and reproduce this notices file with their distribution.

License summary

SPDX license expressionPackages
(MIT OR Apache-2.0) AND Unicode-3.01
0BSD OR MIT OR Apache-2.01
Apache-2.010
Apache-2.0 / MIT1
Apache-2.0 AND ISC1
Apache-2.0 OR BSL-1.01
Apache-2.0 OR ISC OR MIT2
Apache-2.0 OR MIT22
Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT5
Apache-2.0/MIT3
BSD-2-Clause OR Apache-2.0 OR MIT2
BSD-3-Clause1
BSD-3-Clause OR Apache-2.02
CDLA-Permissive-2.01
ISC3
MIT62
MIT OR Apache-2.0196
MIT OR Apache-2.0 OR LGPL-2.1-or-later1
MIT OR Apache-2.0 OR Zlib3
MIT OR Zlib OR Apache-2.02
MIT/Apache-2.017
MPL-2.09
Unicode-3.018
Unlicense OR MIT6
Unlicense/MIT2
Zlib3
Zlib OR Apache-2.0 OR MIT9
Total384

Packages by license

(MIT OR Apache-2.0) AND Unicode-3.0 (1 package(s))

PackageVersionHomepage
unicode-ident1.0.27https://github.com/dtolnay/unicode-ident

0BSD OR MIT OR Apache-2.0 (1 package(s))

PackageVersionHomepage
adler22.0.1https://github.com/oyvindln/adler2

Apache-2.0 (10 package(s))

PackageVersionHomepage
borsh-derive†1.8.1https://borsh.io
ciborium†0.2.2https://github.com/enarx/ciborium
ciborium-io†0.2.2https://github.com/enarx/ciborium
ciborium-ll†0.2.2https://github.com/enarx/ciborium
codespan-reporting0.13.1https://github.com/brendanzab/codespan
gl_generator†0.14.0https://github.com/brendanzab/gl-rs/
glutin_wgl_sys0.6.1https://github.com/rust-windowing/glutin
khronos_api†3.1.0https://github.com/brendanzab/gl-rs/
spirv0.4.0+sdk-1.4.341.0https://github.com/gfx-rs/rspirv
sync_wrapper1.0.2https://docs.rs/sync_wrapper

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

Apache-2.0 / MIT (1 package(s))

PackageVersionHomepage
fnv1.0.7https://github.com/servo/rust-fnv

Apache-2.0 AND ISC (1 package(s))

PackageVersionHomepage
ring0.17.14https://github.com/briansmith/ring

Apache-2.0 OR BSL-1.0 (1 package(s))

PackageVersionHomepage
ryu1.0.23https://github.com/dtolnay/ryu

Apache-2.0 OR ISC OR MIT (2 package(s))

PackageVersionHomepage
hyper-rustls0.27.10https://github.com/rustls/hyper-rustls
rustls0.23.46https://github.com/rustls/rustls

Apache-2.0 OR MIT (22 package(s))

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT (5 package(s))

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

Apache-2.0/MIT (3 package(s))

BSD-2-Clause OR Apache-2.0 OR MIT (2 package(s))

PackageVersionHomepage
zerocopy0.8.63https://github.com/google/zerocopy
zerocopy-derive0.8.63https://github.com/google/zerocopy

BSD-3-Clause (1 package(s))

PackageVersionHomepage
subtle2.6.1https://dalek.rs/

BSD-3-Clause OR Apache-2.0 (2 package(s))

PackageVersionHomepage
moxcms0.8.1https://github.com/awxkee/moxcms
pxfm0.1.30https://github.com/awxkee/pxfm

CDLA-Permissive-2.0 (1 package(s))

PackageVersionHomepage
webpki-roots1.0.11https://github.com/rustls/webpki-roots

This is a data license, not a software license: it covers the root-certificate bundle published by webpki-roots, not code. It permits use, sharing and commercial use with attribution. Full text: https://spdx.org/licenses/CDLA-Permissive-2.0.txt.

ISC (3 package(s))

PackageVersionHomepage
libloading0.8.9https://github.com/nagisa/rust_libloading/
rustls-webpki0.103.16https://github.com/rustls/webpki
untrusted0.9.0https://github.com/briansmith/untrusted

MIT (62 package(s))

PackageVersionHomepage
assert-json-diff†2.0.2https://github.com/davidpdrsn/assert-json-diff
block20.6.2https://github.com/madsmtm/objc2
bytes1.12.1https://github.com/tokio-rs/bytes
cargo_metadata0.23.1https://github.com/oli-obk/cargo_metadata
cfg_aliases†0.2.2https://github.com/katharostech/cfg_aliases
crunchy0.2.4https://github.com/eira-fransham/crunchy
dlib0.5.3https://github.com/elinorbgr/dlib
goblin0.8.2https://github.com/m4b/goblin
h20.4.20https://github.com/hyperium/h2
http-body1.1.0https://github.com/hyperium/http-body
http-body-util0.1.5https://github.com/hyperium/http-body
hyper1.12.0https://hyper.rs
hyper-util0.1.21https://hyper.rs
is-terminal†0.4.17https://github.com/sunfishcode/is-terminal
libm0.2.16https://github.com/rust-lang/compiler-builtins
libsqlite3-sys0.38.2https://github.com/rusqlite/rusqlite
mio1.2.4https://github.com/tokio-rs/mio
nom7.1.3https://github.com/Geal/nom
objc20.6.5https://github.com/madsmtm/objc2
objc2-encode4.1.0https://github.com/madsmtm/objc2
objc2-foundation0.3.2https://github.com/madsmtm/objc2
oorandom†11.1.5https://hg.sr.ht/~icefox/oorandom
ordered-float5.5.1https://github.com/reem/rust-ordered-float
plotters†0.3.7https://plotters-rs.github.io/
plotters-backend†0.3.7https://plotters-rs.github.io
plotters-svg†0.3.7https://plotters-rs.github.io
protobuf3.7.2https://github.com/stepancheg/rust-protobuf/
protobuf-codegen†3.7.2https://github.com/stepancheg/rust-protobuf/
protobuf-parse†3.7.2https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-parse/
protobuf-support3.7.2https://github.com/stepancheg/rust-protobuf/
r2d2_sqlite0.35.0https://github.com/ivanceras/r2d2-sqlite
redox_syscall0.5.18https://gitlab.redox-os.org/redox-os/syscall
rsqlite-vfs0.1.1—
rusqlite0.40.2https://github.com/rusqlite/rusqlite
scroll0.12.0https://github.com/m4b/scroll
scroll_derive0.12.1https://github.com/m4b/scroll
simd-adler320.3.10https://github.com/mcountryman/simd-adler32
slab0.4.12https://github.com/tokio-rs/slab
smawk0.3.3https://github.com/mgeisler/smawk
sqlite-wasm-rs0.5.5https://github.com/Spxg/sqlite-wasm-rs
strsim0.11.1https://github.com/rapidfuzz/strsim-rs
synstructure0.14.0https://github.com/mystor/synstructure
textwrap0.16.4https://github.com/mgeisler/textwrap
tokio1.54.1https://tokio.rs
tokio-macros2.7.2https://tokio.rs
tokio-util0.7.20https://tokio.rs
tower0.5.3https://github.com/tower-rs/tower
tower-http0.6.11https://github.com/tower-rs/tower-http
tower-layer0.3.3https://github.com/tower-rs/tower
tower-service0.3.3https://github.com/tower-rs/tower
tracing0.1.44https://tokio.rs
tracing-attributes0.1.31https://tokio.rs
tracing-core0.1.36https://tokio.rs
try-lock0.2.5https://github.com/seanmonstar/try-lock
valuable0.1.1https://github.com/tokio-rs/valuable
want0.3.2https://github.com/seanmonstar/want
wayland-sys0.31.11https://github.com/smithay/wayland-rs
weedle25.0.0https://github.com/mozilla/uniffi-rs/tree/main/weedle2
which†4.4.2https://github.com/harryfei/which-rs.git
winnow1.0.4https://github.com/winnow-rs/winnow
xml-rs†0.8.29https://lib.rs/crates/xml
zmij1.0.23https://github.com/dtolnay/zmij

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

MIT OR Apache-2.0 (196 package(s))

PackageVersionHomepage
allocator-api20.2.21https://github.com/zakarumych/allocator-api2
android_system_properties0.1.6https://github.com/nical/android_system_properties
anes†0.1.6https://github.com/zrzka/anes-rs
anstyle1.0.14https://github.com/rust-cli/anstyle.git
anyhow1.0.104https://github.com/dtolnay/anyhow
arrayvec0.7.8https://github.com/bluss/arrayvec
ash0.38.0+1.3.281https://github.com/ash-rs/ash
askama0.16.1https://askama.rs/
askama_derive0.16.1https://github.com/askama-rs/askama
askama_macros0.16.1https://askama.rs/
askama_parser0.16.1https://askama.rs/
async-trait0.1.92https://github.com/dtolnay/async-trait
base640.22.1https://github.com/marshallpierce/rust-base64
base640.23.1https://github.com/marshallpierce/rust-base64
basic-toml0.1.10https://github.com/dtolnay/basic-toml
bitflags2.13.2https://github.com/bitflags/bitflags
block-buffer0.12.1https://github.com/RustCrypto/utils
borsh†1.8.1https://borsh.io
bumpalo3.20.3https://github.com/fitzgen/bumpalo
camino1.2.6https://github.com/camino-rs/camino
cargo-platform0.3.3https://github.com/rust-lang/cargo
cast†0.3.0https://github.com/japaric/cast.rs
cc†1.10.0https://github.com/rust-lang/cc-rs
cfg-if1.0.5https://github.com/rust-lang/cfg-if
chacha200.10.2https://github.com/RustCrypto/stream-ciphers
clap4.6.7https://github.com/clap-rs/clap
clap_builder4.6.7https://github.com/clap-rs/clap
clap_derive4.6.7https://github.com/clap-rs/clap
clap_lex1.1.1https://github.com/clap-rs/clap
cpufeatures0.3.1https://github.com/RustCrypto/utils
crc32fast1.5.3https://github.com/srijs/rust-crc32fast
crossbeam-deque0.8.8https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-deque
crossbeam-epoch0.9.21https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-epoch
crossbeam-utils0.8.23https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-utils
crypto-common0.2.2https://github.com/RustCrypto/traits
deadpool†0.12.3https://github.com/bikeshedder/deadpool
deadpool-runtime†0.1.4https://github.com/bikeshedder/deadpool
digest0.11.3https://github.com/RustCrypto/traits
displaydoc0.2.7https://github.com/yaahc/displaydoc
document-features0.2.12https://slint.rs
either1.19.0https://github.com/rayon-rs/either
errno0.3.14https://github.com/lambda-fairy/rust-errno
fdeflate0.3.7https://github.com/image-rs/fdeflate
find-msvc-tools†0.1.15https://github.com/rust-lang/cc-rs
flate21.1.10https://github.com/rust-lang/flate2-rs
form_urlencoded1.2.2https://github.com/servo/rust-url
fs-err3.3.2https://github.com/andrewhickman/fs-err
fs41.1.0https://github.com/al8n/fs4
futures0.3.34https://rust-lang.github.io/futures-rs
futures-channel0.3.34https://rust-lang.github.io/futures-rs
futures-core0.3.34https://rust-lang.github.io/futures-rs
futures-executor0.3.34https://rust-lang.github.io/futures-rs
futures-io0.3.34https://rust-lang.github.io/futures-rs
futures-macro0.3.34https://rust-lang.github.io/futures-rs
futures-sink0.3.34https://rust-lang.github.io/futures-rs
futures-task0.3.34https://rust-lang.github.io/futures-rs
futures-util0.3.34https://rust-lang.github.io/futures-rs
getrandom0.2.17https://github.com/rust-random/getrandom
getrandom0.4.3https://github.com/rust-random/getrandom
glam0.30.10https://github.com/bitshifter/glam-rs
glob0.3.4https://github.com/rust-lang/glob
gloo-net0.6.0https://github.com/rustwasm/gloo
gloo-timers0.3.0https://github.com/rustwasm/gloo
gloo-utils0.2.0https://github.com/rustwasm/gloo
gpu-allocator0.28.0https://github.com/Traverse-Research/gpu-allocator
half2.7.1https://github.com/VoidStarKat/half-rs
hashbrown0.16.1https://github.com/rust-lang/hashbrown
hashbrown0.17.1https://github.com/rust-lang/hashbrown
hashlink0.12.2https://github.com/djc/hashlink
heck0.5.0https://github.com/withoutboats/heck
hermit-abi†0.5.3https://github.com/hermit-os/hermit-rs
home†0.5.12https://github.com/rust-lang/cargo
http1.5.0https://github.com/hyperium/http
httparse1.10.1https://github.com/seanmonstar/httparse
httpdate1.0.3https://github.com/pyfisch/httpdate
hybrid-array0.4.15https://github.com/RustCrypto/hybrid-array
idna1.1.0https://github.com/servo/rust-url/
image0.25.10https://github.com/image-rs/image
ipnet2.12.2https://github.com/krisprice/ipnet
itoa1.0.18https://github.com/dtolnay/itoa
jni-sys0.3.1https://github.com/jni-rs/jni-sys
jni-sys0.4.1https://github.com/jni-rs/jni-sys
jni-sys-macros0.4.1https://github.com/jni-rs/jni-sys
js-sys0.3.106https://wasm-bindgen.github.io/wasm-bindgen/
lazy_static†1.5.1https://github.com/rust-lang-nursery/lazy-static.rs
libc0.2.191https://github.com/rust-lang/libc
litrs1.0.1https://github.com/LukasKalbertodt/litrs
lock_api0.4.14https://github.com/Amanieu/parking_lot
log0.4.34https://github.com/rust-lang/log
memmap20.5.10https://github.com/RazrFalcon/memmap2-rs
memmap20.9.12https://github.com/RazrFalcon/memmap2-rs
naga30.0.1https://github.com/gfx-rs/wgpu
naga-types30.0.1https://wgpu.rs/
ndk-sys0.6.0+11769913https://github.com/rust-mobile/ndk
num-traits0.2.19https://github.com/rust-num/num-traits
num_cpus†1.17.0https://github.com/seanmonstar/num_cpus
once_cell1.21.4https://github.com/matklad/once_cell
osmpbf0.3.8https://github.com/b-r-u/osmpbf
parking_lot0.12.5https://github.com/Amanieu/parking_lot
parking_lot_core0.9.12https://github.com/Amanieu/parking_lot
percent-encoding2.3.2https://github.com/servo/rust-url/
pkg-config†0.3.34https://github.com/rust-lang/pkg-config-rs
png0.18.1https://github.com/image-rs/image-png
presser0.3.1https://github.com/EmbarkStudios/presser
proc-macro-crate†3.5.0https://github.com/bkchr/proc-macro-crate
proc-macro21.0.107https://github.com/dtolnay/proc-macro2
profiling1.0.18https://github.com/aclysma/profiling
proptest†1.12.0https://proptest-rs.github.io/proptest/proptest/index.html
quinn0.11.12https://github.com/quinn-rs/quinn
quinn-proto0.11.19https://github.com/quinn-rs/quinn
quinn-udp0.5.16https://github.com/quinn-rs/quinn
quote1.0.47https://github.com/dtolnay/quote
rand0.10.3https://rust-random.github.io/book
rand_core0.10.1https://rust-random.github.io/book
rand_pcg0.10.2https://rust-random.github.io/book
rand_xorshift†0.5.0https://rust-random.github.io/book
range-alloc0.1.5https://github.com/gfx-rs/range-alloc
raw-window-metal1.1.0https://github.com/rust-windowing/raw-window-metal
rayon1.12.0https://github.com/rayon-rs/rayon
rayon-core1.13.0https://github.com/rayon-rs/rayon
regex†1.13.1https://github.com/rust-lang/regex
regex-automata†0.4.18https://github.com/rust-lang/regex/tree/master/regex-automata
regex-syntax†0.8.11https://github.com/rust-lang/regex/tree/master/regex-syntax
renderdoc-sys1.1.0https://github.com/ebkalderon/renderdoc-rs/tree/master/renderdoc-sys
reqwest0.12.28https://github.com/seanmonstar/reqwest
rustls-pki-types1.15.1https://github.com/rustls/pki-types
rustversion†1.0.23https://github.com/dtolnay/rustversion
scopeguard1.2.0https://github.com/bluss/scopeguard
semver1.0.28https://github.com/dtolnay/semver
serde1.0.229https://serde.rs
serde_core1.0.229https://serde.rs
serde_derive1.0.229https://serde.rs
serde_json1.0.154https://github.com/serde-rs/json
serde_spanned1.1.2https://github.com/toml-rs/toml
sha20.11.1https://github.com/RustCrypto/hashes
shlex†2.0.1https://github.com/comex/rust-shlex
siphasher1.0.4https://docs.rs/siphasher
smallvec1.16.4https://github.com/servo/rust-smallvec
socket20.6.5https://github.com/rust-lang/socket2
stable_deref_trait1.2.1https://github.com/storyyeller/stable_deref_trait
static_assertions1.1.0https://github.com/nvzqz/static-assertions-rs
syn2.0.119https://github.com/dtolnay/syn
syn3.0.9https://github.com/dtolnay/syn
tempfile3.27.0https://stebalien.com/projects/tempfile-rs/
thiserror1.0.69https://github.com/dtolnay/thiserror
thiserror2.0.21https://github.com/dtolnay/thiserror
thiserror-impl1.0.69https://github.com/dtolnay/thiserror
thiserror-impl2.0.21https://github.com/dtolnay/thiserror
tokio-rustls0.26.6https://github.com/rustls/tokio-rustls
toml1.1.8+spec-1.1.0https://github.com/toml-rs/toml
toml_datetime1.1.2+spec-1.1.0https://github.com/toml-rs/toml
toml_edit†0.25.17+spec-1.1.0https://github.com/toml-rs/toml
toml_parser1.1.5+spec-1.1.0https://github.com/toml-rs/toml
toml_writer1.1.3+spec-1.1.0https://github.com/toml-rs/toml
typenum1.20.1https://github.com/paholg/typenum
unarray†0.1.4https://github.com/cameron1024/unarray
unicode-width0.2.2https://github.com/unicode-rs/unicode-width
url2.5.8https://github.com/servo/rust-url
wasm-bindgen0.2.129https://wasm-bindgen.github.io/wasm-bindgen
wasm-bindgen-futures0.4.79https://wasm-bindgen.github.io/wasm-bindgen/
wasm-bindgen-macro0.2.129https://wasm-bindgen.github.io/wasm-bindgen/
wasm-bindgen-macro-support0.2.129https://wasm-bindgen.github.io/wasm-bindgen/
wasm-bindgen-shared0.2.129https://wasm-bindgen.github.io/wasm-bindgen/
web-sys0.3.106https://wasm-bindgen.github.io/wasm-bindgen/web-sys/index.html
web-time1.1.0https://github.com/daxpedda/web-time
wgpu30.0.1https://wgpu.rs/
wgpu-core30.0.1https://wgpu.rs/
wgpu-core-deps-apple30.0.1https://wgpu.rs/
wgpu-core-deps-emscripten30.0.1https://wgpu.rs/
wgpu-core-deps-wasm30.0.1https://wgpu.rs/
wgpu-core-deps-windows-linux-android30.0.1https://wgpu.rs/
wgpu-hal30.0.1https://wgpu.rs/
wgpu-naga-bridge30.0.1https://wgpu.rs/
wgpu-types30.0.1https://wgpu.rs/
windows0.62.2https://github.com/microsoft/windows-rs
windows-collections0.3.2https://github.com/microsoft/windows-rs
windows-core0.62.2https://github.com/microsoft/windows-rs
windows-future0.3.2https://github.com/microsoft/windows-rs
windows-implement0.60.2https://github.com/microsoft/windows-rs
windows-interface0.59.3https://github.com/microsoft/windows-rs
windows-link0.2.1https://github.com/microsoft/windows-rs
windows-numerics0.3.1https://github.com/microsoft/windows-rs
windows-result0.4.1https://github.com/microsoft/windows-rs
windows-strings0.5.1https://github.com/microsoft/windows-rs
windows-sys0.52.0https://github.com/microsoft/windows-rs
windows-sys0.61.2https://github.com/microsoft/windows-rs
windows-targets0.52.6https://github.com/microsoft/windows-rs
windows-threading0.2.1https://github.com/microsoft/windows-rs
windows_aarch64_gnullvm0.52.6https://github.com/microsoft/windows-rs
windows_aarch64_msvc0.52.6https://github.com/microsoft/windows-rs
windows_i686_gnu0.52.6https://github.com/microsoft/windows-rs
windows_i686_gnullvm0.52.6https://github.com/microsoft/windows-rs
windows_i686_msvc0.52.6https://github.com/microsoft/windows-rs
windows_x86_64_gnu0.52.6https://github.com/microsoft/windows-rs
windows_x86_64_gnullvm0.52.6https://github.com/microsoft/windows-rs
windows_x86_64_msvc0.52.6https://github.com/microsoft/windows-rs

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

MIT OR Apache-2.0 OR LGPL-2.1-or-later (1 package(s))

PackageVersionHomepage
r-efi6.0.0https://github.com/r-efi/r-efi/wiki

These packages offer LGPL-2.1-or-later only as one alternative inside a permissive SPDX expression; this SDK relies on the permissive alternatives. Full text: https://spdx.org/licenses/LGPL-2.1-or-later.txt.

MIT OR Apache-2.0 OR Zlib (3 package(s))

MIT OR Zlib OR Apache-2.0 (2 package(s))

MIT/Apache-2.0 (17 package(s))

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

MPL-2.0 (9 package(s))

The Mozilla Public License 2.0 applies only to the source files of the covered crates (file-level copyleft — not viral). This SDK consumes those crates as libraries without modifying their sources. Full text: https://spdx.org/licenses/MPL-2.0.txt. Note: the published uniffi crate family ships no license text file inside its package; the full text lives in each crate’s repository.

Unicode-3.0 (18 package(s))

Unlicense OR MIT (6 package(s))

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

Unlicense/MIT (2 package(s))

PackageVersionHomepage
same-file†1.0.6https://github.com/BurntSushi/same-file
walkdir†2.5.0https://github.com/BurntSushi/walkdir

† development/test-only dependency: compiled inside this repository’s own test harnesses, but not linked into a published SDK artifact.

Zlib (3 package(s))

Zlib OR Apache-2.0 OR MIT (9 package(s))

PackageVersionHomepage
bytemuck1.25.2https://github.com/Lokathor/bytemuck
bytemuck_derive1.12.1https://github.com/Lokathor/bytemuck
dispatch20.3.1https://github.com/madsmtm/objc2
objc2-core-foundation0.3.2https://github.com/madsmtm/objc2
objc2-core-graphics0.3.2https://github.com/madsmtm/objc2
objc2-io-surface0.3.2https://github.com/madsmtm/objc2
objc2-metal0.3.2https://github.com/madsmtm/objc2
objc2-quartz-core0.3.2https://github.com/madsmtm/objc2
tinyvec1.13.3https://github.com/Lokathor/tinyvec

First attribution lines extracted from the license files vendored in the local cargo cache; each crate’s source carries the complete text. Crates without an extractable line (or absent from the local cache) simply appear only in the license tables above, which remain authoritative.

  • unicode-ident 1.0.27 — Copyright © 1991-2023 Unicode, Inc.
  • borsh-derive 1.8.1 — Copyright 2019 Near
  • glutin_wgl_sys 0.6.1 — Copyright 2022 Kirill Chibisov
  • fnv 1.0.7 — Copyright (c) 2017 Contributors
  • ring 0.17.14 — Copyright (c) 2009 The Go Authors. All rights reserved. / Copyright 2015 The Chromium Authors. All rights reserved. / Copyright 2015-2025 Brian Smith.
  • hyper-rustls 0.27.10 — Copyright (c) 2016, Joseph Birr-Pixton jpixton@gmail.com / Copyright (c) 2016 Joseph Birr-Pixton jpixton@gmail.com
  • rustls 0.23.46 — Copyright (c) 2016, Joseph Birr-Pixton jpixton@gmail.com / Copyright (c) 2016 Joseph Birr-Pixton jpixton@gmail.com
  • atomic-waker 1.1.2 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • autocfg 1.5.1 — Copyright (c) 2018 Josh Stone
  • bit-set 0.10.0 — Copyright (c) 2026 The Rust Project Developers
  • bit-set 0.11.1 — Copyright (c) 2023 The Rust Project Developers
  • bit-vec 0.10.1 — Copyright (c) 2023 The Rust Project Developers
  • bit-vec 0.9.1 — Copyright (c) 2023 The Rust Project Developers
  • const-oid 0.10.2 — Copyright (c) 2020-2026 The RustCrypto Project Developers
  • criterion 0.5.1 — Copyright (c) 2014 Jorge Aparicio
  • equivalent 1.0.3 — Copyright (c) 2016–2023
  • indexmap 2.14.2 — Copyright (c) 2016–2017
  • tinytemplate 1.2.1 — Copyright (c) 2019 Brook Heisler
  • uuid 1.29.0 — Copyright (c) 2014 The Rust Project Developers / Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon
  • zeroize 1.9.1 — Copyright (c) 2018-2026 The RustCrypto Project Developers
  • console_error_panic_hook 0.1.7 — Copyright (c) 2018 Nick Fitzgerald
  • pollster 0.4.0 — Copyright (c) 2020-2021 Joshua Barretto
  • zerocopy 0.8.63 — Copyright 2023 The Fuchsia Authors / Copyright 2019 The Fuchsia Authors.
  • zerocopy-derive 0.8.63 — Copyright 2023 The Fuchsia Authors / Copyright 2019 The Fuchsia Authors.
  • subtle 2.6.1 — Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. / Copyright (c) 2016-2024 Isis Agora Lovecruft. All rights reserved.
  • moxcms 0.8.1 — Copyright 2024 Radzivon Bartoshyk
  • pxfm 0.1.30 — Copyright 2024 Radzivon Bartoshyk
  • libloading 0.8.9 — Copyright © 2015, Simonas Kazlauskas
  • rustls-webpki 0.103.16 — Copyright 2015 Brian Smith.
  • untrusted 0.9.0 — // Copyright 2015-2016 Brian Smith.
  • assert-json-diff 2.0.2 — Copyright (c) 2019 David Pedersen
  • bytes 1.12.1 — Copyright (c) 2018 Carl Lerche
  • cfg_aliases 0.2.2 — Copyright (c) 2020 Katharos Technology
  • crunchy 0.2.4 — Copyright 2017-2023 Eira Fransham.
  • dlib 0.5.3 — Copyright (c) 2015 Victor Berger
  • h2 0.4.20 — Copyright (c) 2017 h2 authors
  • http-body 1.1.0 — Copyright (c) 2019-2026 Sean McArthur & Hyper Contributors
  • http-body-util 0.1.5 — Copyright (c) 2019-2026 Sean McArthur & Hyper Contributors
  • hyper 1.12.0 — Copyright (c) 2014-2026 Sean McArthur
  • hyper-util 0.1.21 — Copyright (c) 2023-2025 Sean McArthur
  • is-terminal 0.4.17 — Copyright (c) 2015-2019 Doug Tangren
  • libm 0.2.16 — Copyright (c) 2018 Jorge Aparicio / Copyright © 2005-2020 Rich Felker, et al. / Copyright © 1993,2004 Sun Microsystems or
  • libsqlite3-sys 0.38.2 — Copyright (c) 2014 The rusqlite developers
  • mio 1.2.4 — Copyright (c) 2014 Carl Lerche and other MIO contributors
  • nom 7.1.3 — Copyright (c) 2014-2019 Geoffroy Couprie
  • oorandom 11.1.5 — Copyright (c) 2019 Simon Heath
  • ordered-float 5.5.1 — Copyright (c) 2015 Jonathan Reem
  • protobuf 3.7.2 — Copyright (c) 2019 Stepan Koltsov
  • protobuf-codegen 3.7.2 — Copyright (c) 2019 Stepan Koltsov
  • protobuf-parse 3.7.2 — Copyright (c) 2019 Stepan Koltsov / Copyright 2008 Google Inc. All rights reserved.
  • protobuf-support 3.7.2 — Copyright (c) 2019 Stepan Koltsov
  • r2d2_sqlite 0.35.0 — Copyright (c) 2015 Jovansonlee Cesar
  • redox_syscall 0.5.18 — Copyright (c) 2017 Redox OS Developers
  • rusqlite 0.40.2 — Copyright (c) 2014 The rusqlite developers
  • scroll_derive 0.12.1 — Copyright (c) 2017
  • slab 0.4.12 — Copyright (c) 2019 Carl Lerche
  • smawk 0.3.3 — Copyright (c) 2017 Martin Geisler
  • sqlite-wasm-rs 0.5.5 — Copyright (c) 2024 Spxg
  • strsim 0.11.1 — Copyright (c) 2015 Danny Guo / Copyright (c) 2016 Titus Wormer tituswormer@gmail.com / Copyright (c) 2018 Akash Kurdekar
  • synstructure 0.14.0 — Copyright 2016 Nika Layzell
  • textwrap 0.16.4 — Copyright (c) 2016 Martin Geisler
  • tokio-macros 2.7.2 — Copyright (c) 2019 Yoshua Wuyts
  • tower 0.5.3 — Copyright (c) 2019 Tower Contributors
  • tower-http 0.6.11 — Copyright (c) 2019-2021 Tower Contributors
  • tower-layer 0.3.3 — Copyright (c) 2019 Tower Contributors
  • tower-service 0.3.3 — Copyright (c) 2019 Tower Contributors
  • tracing 0.1.44 — Copyright (c) 2019 Tokio Contributors
  • tracing-attributes 0.1.31 — Copyright (c) 2019 Tokio Contributors
  • tracing-core 0.1.36 — Copyright (c) 2019 Tokio Contributors
  • try-lock 0.2.5 — Copyright (c) 2018-2023 Sean McArthur / Copyright (c) 2016 Alex Crichton
  • want 0.3.2 — Copyright (c) 2018-2019 Sean McArthur
  • wayland-sys 0.31.11 — Copyright (c) 2015 Elinor Berger
  • weedle2 5.0.0 — Copyright 2018-Present Sharad Chand / Copyright 2022 Jan-Erik Rediger
  • which 4.4.2 — Copyright (c) 2015 fangyuanziti
  • xml-rs 0.8.29 — Copyright (c) 2014 Vladimir Matveev
  • android_system_properties 0.1.6 — Copyright 2016 Nicolas Silva / Copyright (c) 2013 Nicolas Silva
  • ash 0.38.0+1.3.281 — Copyright 2016 Maik Klein / Copyright (c) 2016 ASH
  • askama 0.16.1 — Copyright 2017-2020 Dirkjan Ochtman / Copyright (c) 2017-2020 Dirkjan Ochtman
  • askama_derive 0.16.1 — Copyright 2017-2020 Dirkjan Ochtman / Copyright (c) 2017-2020 Dirkjan Ochtman
  • askama_macros 0.16.1 — Copyright 2017-2020 Dirkjan Ochtman / Copyright (c) 2017-2020 Dirkjan Ochtman
  • askama_parser 0.16.1 — Copyright 2017-2020 Dirkjan Ochtman / Copyright (c) 2017-2020 Dirkjan Ochtman
  • base64 0.22.1 — Copyright (c) 2015 Alice Maz
  • base64 0.23.1 — Copyright (c) 2025 Alice Maz, Marshall Pierce
  • bitflags 2.13.2 — Copyright (c) 2014 The Rust Project Developers
  • block-buffer 0.12.1 — Copyright (c) 2018-2025 The RustCrypto Project Developers
  • borsh 1.8.1 — Copyright 2019 Near
  • bumpalo 3.20.3 — Copyright (c) 2019 Nick Fitzgerald
  • cast 0.3.0 — Copyright (c) 2014-2017 Jorge Aparicio
  • cc 1.10.0 — Copyright (c) 2014 Alex Crichton
  • cfg-if 1.0.5 — Copyright (c) 2014 Alex Crichton
  • chacha20 0.10.2 — Copyright (c) 2019-2026 The RustCrypto Project Developers
  • cpufeatures 0.3.1 — Copyright (c) 2020-2026 The RustCrypto Project Developers
  • crc32fast 1.5.3 — Copyright (c) 2018 Sam Rijs, Alex Crichton and contributors
  • crossbeam-deque 0.8.8 — Copyright (c) 2019 The Crossbeam Project Developers
  • crossbeam-epoch 0.9.21 — Copyright (c) 2019 The Crossbeam Project Developers
  • crossbeam-utils 0.8.23 — Copyright (c) 2019 The Crossbeam Project Developers
  • crypto-common 0.2.2 — Copyright (c) 2021-2026 RustCrypto Developers
  • deadpool 0.12.3 — Copyright 2019 Michael P. Jung / Copyright (c) 2019 Michael P. Jung
  • deadpool-runtime 0.1.4 — Copyright 2019 Michael P. Jung / Copyright (c) 2019 Michael P. Jung
  • digest 0.11.3 — Copyright (c) 2017-2025 RustCrypto Developers / Copyright (c) 2017 Artyom Pavlov
  • document-features 0.2.12 — Copyright (c) 2020 Olivier Goffart ogoffart@sixtyfps.io
  • either 1.19.0 — Copyright (c) 2015
  • errno 0.3.14 — Copyright (c) 2014 Chris Wong
  • find-msvc-tools 0.1.15 — Copyright (c) 2014 Alex Crichton
  • flate2 1.1.10 — Copyright (c) 2014-2026 Alex Crichton
  • form_urlencoded 1.2.2 — Copyright (c) 2013-2016 The rust-url developers
  • fs4 1.1.0 — Copyright (c) 2015 The Rust Project Developers
  • futures 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-channel 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-core 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-executor 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-io 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-macro 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-sink 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-task 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • futures-util 0.3.34 — Copyright (c) 2016 Alex Crichton / Copyright (c) 2017 The Tokio Authors
  • getrandom 0.2.17 — Copyright (c) 2018-2024 The rust-random Project Developers / Copyright (c) 2014 The Rust Project Developers
  • getrandom 0.4.3 — Copyright (c) 2018-2026 The rust-random Project Developers / Copyright (c) 2014 The Rust Project Developers
  • glam 0.30.10 — Copyright 2020 Cameron Hart
  • glob 0.3.4 — Copyright (c) 2014 The Rust Project Developers
  • gpu-allocator 0.28.0 — Copyright 2021 Traverse Research B.V. / Copyright (c) 2021 Traverse Research B.V.
  • hashbrown 0.16.1 — Copyright (c) 2016 Amanieu d’Antras
  • hashbrown 0.17.1 — Copyright (c) 2016 Amanieu d’Antras
  • heck 0.5.0 — Copyright (c) 2015 The Rust Project Developers
  • http 1.5.0 — Copyright 2017 http-rs authors / Copyright (c) 2017 http-rs authors
  • httparse 1.10.1 — Copyright (c) 2015-2025 Sean McArthur
  • httpdate 1.0.3 — Copyright (c) 2016 Pyfisch
  • hybrid-array 0.4.15 — Copyright (c) 2022-2026 The RustCrypto Project Developers
  • idna 1.1.0 — Copyright (c) 2013-2025 The rust-url developers
  • ipnet 2.12.2 — Copyright 2017 Juniper Networks, Inc.
  • jni-sys 0.3.1 — Copyright (c) 2015 The rust-jni-sys Developers
  • jni-sys 0.4.1 — Copyright (c) 2015 The rust-jni-sys Developers
  • js-sys 0.3.106 — Copyright (c) 2014 Alex Crichton
  • litrs 1.0.1 — Copyright (c) 2020 Project Developers
  • lock_api 0.4.14 — Copyright (c) 2016 The Rust Project Developers
  • log 0.4.34 — Copyright (c) 2014 The Rust Project Developers
  • memmap2 0.5.10 — Copyright (c) 2020 Yevhenii Reizner / Copyright (c) 2015 Dan Burkert
  • memmap2 0.9.12 — Copyright (c) 2020 Yevhenii Reizner / Copyright (c) 2015 Dan Burkert
  • naga 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • naga-types 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • num-traits 0.2.19 — Copyright (c) 2014 The Rust Project Developers
  • num_cpus 1.17.0 — Copyright (c) 2015-2025 Sean McArthur
  • osmpbf 0.3.8 — Copyright (c) 2017 Johannes Hofmann mail@b-r-u.org
  • parking_lot 0.12.5 — Copyright (c) 2016 The Rust Project Developers
  • parking_lot_core 0.9.12 — Copyright (c) 2016 The Rust Project Developers
  • percent-encoding 2.3.2 — Copyright (c) 2013-2025 The rust-url developers
  • pkg-config 0.3.34 — Copyright (c) 2014 Alex Crichton
  • png 0.18.1 — Copyright (c) 2015 nwin
  • presser 0.3.1 — Copyright (c) 2019 Embark Studios
  • proptest 1.12.0 — Copyright (c) 2016 FullContact, Inc
  • quinn 0.11.12 — Copyright (c) 2018 The quinn Developers
  • quinn-proto 0.11.19 — Copyright (c) 2018 The quinn Developers
  • quinn-udp 0.5.16 — Copyright (c) 2018 The quinn Developers
  • rand 0.10.3 — Copyright 2018 Developers of the Rand project / Copyright (c) 2014 The Rust Project Developers
  • rand_core 0.10.1 — Copyright (c) 2018-2026 The Rand Project Developers
  • rand_pcg 0.10.2 — Copyright (c) 2014-2017 Melissa O’Neill and PCG Project contributors / Copyright 2018 Developers of the Rand project
  • rand_xorshift 0.5.0 — Copyright 2018 Developers of the Rand project / Copyright (c) 2014 The Rust Project Developers
  • range-alloc 0.1.5 — Copyright (c) 2023 The gfx-rs developers
  • rayon 1.12.0 — Copyright (c) 2010 The Rust Project Developers
  • rayon-core 1.13.0 — Copyright (c) 2010 The Rust Project Developers
  • regex 1.13.1 — Copyright (c) 2014 The Rust Project Developers
  • regex-automata 0.4.18 — Copyright (c) 2014 The Rust Project Developers
  • regex-syntax 0.8.11 — Copyright (c) 2014 The Rust Project Developers
  • renderdoc-sys 1.1.0 — Copyright (c) 2022 Eyal Kalderon
  • reqwest 0.12.28 — Copyright 2016 Sean McArthur / Copyright (c) 2016-2025 Sean McArthur
  • rustls-pki-types 1.15.1 — Copyright 2023 Dirkjan Ochtman / Copyright (c) 2023 Dirkjan Ochtman dirkjan@ochtman.nl
  • scopeguard 1.2.0 — Copyright (c) 2016-2019 Ulrik Sverdrup “bluss” and scopeguard developers
  • sha2 0.11.1 — Copyright (c) 2016-2026 The RustCrypto Project Developers / Copyright (c) 2016 Artyom Pavlov / Copyright (c) 2009-2013 Mozilla Foundation
  • shlex 2.0.1 — Copyright 2015 Nicholas Allegra (comex). / Copyright (c) 2015 Nicholas Allegra (comex).
  • siphasher 1.0.4 — Copyright 2012-2016 The Rust Project Developers. / Copyright 2016-2026 Frank Denis.
  • smallvec 1.16.4 — Copyright (c) 2018 The Servo Project Developers
  • socket2 0.6.5 — Copyright (c) 2014 Alex Crichton
  • stable_deref_trait 1.2.1 — Copyright (c) 2017 Robert Grosse
  • static_assertions 1.1.0 — Copyright (c) 2017 Nikolai Vazquez
  • tempfile 3.27.0 — Copyright (c) 2015 Steven Allen
  • tokio-rustls 0.26.6 — Copyright 2017 quininer kel / Copyright (c) 2017 quininer kel
  • typenum 1.20.1 — Copyright 2014 Paho Lurie-Gregg / Copyright (c) 2014 Paho Lurie-Gregg
  • unicode-width 0.2.2 — Copyright (c) 2015 The Rust Project Developers
  • url 2.5.8 — Copyright (c) 2013-2025 The rust-url developers
  • wasm-bindgen 0.2.129 — Copyright (c) 2014 Alex Crichton
  • wasm-bindgen-futures 0.4.79 — Copyright (c) 2014 Alex Crichton
  • wasm-bindgen-macro 0.2.129 — Copyright (c) 2014 Alex Crichton
  • wasm-bindgen-macro-support 0.2.129 — Copyright (c) 2014 Alex Crichton
  • wasm-bindgen-shared 0.2.129 — Copyright (c) 2014 Alex Crichton
  • web-sys 0.3.106 — Copyright (c) 2014 Alex Crichton
  • web-time 1.1.0 — Copyright 2023 dAxpeDDa / Copyright (c) 2023 dAxpeDDa
  • wgpu 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-core 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-core-deps-apple 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-core-deps-emscripten 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-core-deps-wasm 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-core-deps-windows-linux-android 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-hal 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-naga-bridge 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • wgpu-types 30.0.1 — Copyright (c) 2025 The gfx-rs developers
  • lru-slab 0.1.3 — Copyright (c) 2024 The lru-slab Developers
  • raw-window-handle 0.6.2 — Copyright (c) 2019 Osspial / Copyright (c) 2020 Osspial
  • miniz_oxide 0.8.9 — Copyright 2013-2014 RAD Game Tools and Valve Software / Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC / Copyright (c) 2017 Frommi
  • miniz_oxide 0.9.1 — Copyright 2013-2014 RAD Game Tools and Valve Software / Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC / Copyright (c) 2017 Frommi
  • core_detect 1.0.0 — Copyright (c) 2017-2020 The Rust Project Developers
  • criterion-plot 0.5.0 — Copyright (c) 2014 Jorge Aparicio
  • fallible-iterator 0.3.0 — Copyright (c) 2015 The rust-openssl-verify Developers
  • fallible-streaming-iterator 0.1.9 — Copyright (c) 2016 The fallible-streaming-iterator Developers
  • itertools 0.10.5 — Copyright (c) 2015
  • minimal-lexical 0.2.1 — Copyright (c) 2009 The Go Authors. All rights reserved.
  • plain 0.2.3 — Copyright (c) 2017 Plain contributors
  • quick-error 1.2.3 — Copyright (c) 2015 The quick-error Developers
  • r2d2 0.8.10 — Copyright (c) 2014-2016 Steven Fackler
  • rusty-fork 0.3.1 — Copyright (c) 2016 FullContact, Inc
  • scheduled-thread-pool 0.2.7 — Copyright (c) 2014-2017 Steven Fackler
  • serde_urlencoded 0.7.1 — Copyright (c) 2016 Anthony Ramine
  • vcpkg 0.2.15 — Copyright (c) 2017 Jim McGrath
  • version_check 0.9.5 — Copyright (c) 2017-2018 Sergio Benitez
  • wait-timeout 0.2.1 — Copyright (c) 2014 Alex Crichton
  • icu_collections 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • icu_locale_core 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • icu_normalizer 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • icu_normalizer_data 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • icu_properties 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • icu_properties_data 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • icu_provider 2.4.0 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • litemap 0.8.3 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • potential_utf 0.1.6 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • tinystr 0.8.5 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • writeable 0.6.4 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • yoke 0.8.4 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • yoke-derive 0.8.4 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • zerofrom 0.1.8 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • zerofrom-derive 0.1.8 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • zerotrie 0.2.5 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • zerovec 0.11.9 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • zerovec-derive 0.11.7 — Copyright © 2020-2024 Unicode, Inc. / ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
  • aho-corasick 1.1.5 — Copyright (c) 2015 Andrew Gallant
  • byteorder 1.5.0 — Copyright (c) 2015 Andrew Gallant
  • byteorder-lite 0.1.0 — Copyright (c) 2015 Andrew Gallant
  • memchr 2.8.3 — Copyright (c) 2015 Andrew Gallant
  • termcolor 1.4.1 — Copyright (c) 2015 Andrew Gallant
  • winapi-util 0.1.11 — Copyright (c) 2017 Andrew Gallant
  • same-file 1.0.6 — Copyright (c) 2017 Andrew Gallant
  • walkdir 2.5.0 — Copyright (c) 2015 Andrew Gallant
  • foldhash 0.2.0 — Copyright (c) 2024 Orson Peters
  • slotmap 1.1.1 — Copyright (c) 2021 Orson Peters orsonpeters@gmail.com
  • bytemuck 1.25.2 — Copyright (c) 2019 Daniel “Lokathor” Gee.
  • bytemuck_derive 1.12.1 — Copyright (c) 2019 Daniel “Lokathor” Gee.
  • tinyvec 1.13.3 — Copyright (c) 2019 Daniel “Lokathor” Gee.

Prior art & inspiration

Credits for projects this SDK learns from. Where only a design was learned from, no code was copied; where an entry says logic was ported, the derived work is credited and its license text is retained under docs/THIRD_PARTY_LICENSES/.

  • MapLibre — its open map renderers and how they frame tile scheduling, frustum listing and tile budgeting are the mature prior art this SDK’s renderer seams knowingly re-implement from first principles.
  • OpenStreetMap and its ODbL database — the map-data backbone this SDK renders; the ODbL attribution obligations above are real and are honored in every reference app.
  • UniFFI — whose UDL-first, contract-as-single-source-of-truth binding approach this SDK adopts for its Kotlin/Swift/React Native FFI surface.
  • wgpu — the portable WebGPU-in-Rust stack that lets one GPU rendering path run natively, on mobile, and in the browser.
  • Navara — MapLibre-affiliated web 3D-globe / GIS visualization engine (Rust/WASM headless GIS core); credited as surveyed prior art for headless-engine architecture, not reused and not a reference for this SDK’s navigation capabilities.
  • Ferrostar — Stadia Maps’ Rust navigation core. Its trip-state / step-advance-condition design is the reference this SDK’s map-navigation crate follows closely: the step-advance conditions and their recommended defaults are modeled on Ferrostar’s (including its rapid-double-advance hazard, which this SDK’s ports avoid), and its deviation-detection and spoken-instruction models informed this SDK’s seams. Because this SDK’s navigation logic is derived from Ferrostar’s design, its license text is retained in docs/THIRD_PARTY_LICENSES/ferrostar-LICENSE.txt (BSD 3-Clause, Copyright (c) 2023, Stadia Maps, Inc.); no file in this workspace is a verbatim copy, and this SDK’s own code remains MIT OR Apache-2.0.

API reference (rustdoc)

The rustdoc for the workspace crates ships at the api/ path of this site: start at geospatial-types, the record and math layer every other crate builds on.

This tree is generated, not hand-maintained. The build script runs cargo doc against the source commit named in provenance.txt on every publish, and stages the rendered tree at api/ without post-processing. Pages under api/ therefore describe exactly that commit; a newer publish replaces the whole tree at the same paths.

The FFI contract

The SDK’s binding surface is one frozen UniFFI UDL contract, map_native_core.udl, published verbatim below and as a plain-text download. This file is the single source of truth: the Kotlin, Swift, and React Native bindings are generated from it, and the WebAssembly TypeScript mirror is regression-checked against it.

The contract is addition-only. Existing members keep their signature and meaning, so your compiled bindings stay valid across upgrades; new capability is new members. The API reference documents the same surface as generated rustdoc; the UDL download carries the raw form machine tooling consumes.

The pages below show the contract verbatim, byte-equality checked against the source file on every publish; an edit to the contract propagates to the site through the build or the build fails.

Show the raw UDL contract
// FROZEN (M4, slice 1 — 2026-10-27) — the UniFFI contract of the Maps SDK;
// extended in M4 slice 2 with the fit-bounds constructor (`EdgeInsetsPixels`
// and the named `MapCamera` constructor), in M4 slice 3 with the async
// provider surface (the `MapSdk` object resolving the `geospatial-traits`
// provider contracts one-to-one through the `map-core` `ProviderRegistry`,
// the provider records and enums mirror of `geospatial-types`, and the
// provider failure variants of `SdkError`), in M4 slice 4 with the
// foreign-callback provider injection: the `GeocodingProvider` and
// `RasterTileProvider` `[Trait, WithForeign]` interfaces foreign code
// (Kotlin, Swift) implements and registers on `MapSdk` through the
// `register_*_from_foreign` methods, and in M6 with the navigation wiring:
// the `NavigationStateEventListener` `[Trait, WithForeign]` listener and the
// `MapNavigationController` object driving the `map-navigation` session, with
// the mirror records of the navigation state, the instruction payload and the
// policy records standing in for the native decision objects.
//
// M2's remaining developer-facing sub-item (2026-10-28 tile TTL/budget
// ruling) adds the tile-cache settings surface additively: the
// `TileCacheTerms` record and the `MapSdk::set_raster_tile_cache_time_to_live`
// / `raster_tile_cache_terms` pair over the installed tile cache (byte
// budget stays registration-time through the frozen registration method).
//
// Round 12 (owner ruling of 2026-10-29) adds the provider-failure diagnostic
// seam additively: the `ProviderFailureClassification` enum and the
// `ProviderFailureDiagnostic` record, plus the
// `MapSdk::latest_provider_failure_diagnostics` readback over the bounded
// per-object ring the foreign-callback provider adapters write when a
// foreign-implemented provider contract call fails.
//
// This file is the single source of truth for the native binding surface
// (Kotlin, Swift, and React Native via `uniffi-bindgen-react-native`; PRD §6,
// decision 1). The hand-reviewed wasm surface in `crate map-web-bindings`
// mirrors this contract, and a M4 slice keeps a contract-sync test asserting
// that parity (PRD §4.7).
//
// This slice freezes the camera/projection core of `map-rendering`: the
// anchored `MapCamera`, the free projection helpers, and the version probe,
// with names and semantics aligned one-to-one with the wasm surface.
//
// Records mirror the projection core's value shapes by field name; they are
// plain FFI records, converted on the Rust side to and from the
// `geospatial-types` `GeographicCoordinate`/`BoundingBox` and the
// `map-rendering` viewport/tile types through explicit converters, because
// FFI traits cannot be implemented for foreign-crate types. The error
// carries detail messages and mirrors the `geospatial-types` `SDKError`
// variants these calls can actually produce; camera/projection failures are
// `InvalidCoordinateInput` — the viewport-extent and box-corner failures of
// the core map to the same variant and keep their verbatim message under
// `detail` — while the provider surface maps the six `SDKError` variants the
// registry path can produce. Further variants enter the contract as later
// slices surface real error paths; none are invented here.

namespace map_native_core {
    /// Version of the compiled SDK, equal to the crate's package version.
    string get_sdk_compiled_version();

    /// Maps a coordinate to normalized world coordinates in `[0, 1]`² (x
    /// eastward from the antimeridian, y southward from the north pole);
    /// latitude is clamped to the Mercator cutoffs.
    [Throws=SdkError]
    NormalizedWorldCoordinate coordinate_to_normalized_world(GeographicCoordinate coordinate);

    /// The XYZ address of the tile containing the coordinate at the zoom
    /// level.
    [Throws=SdkError]
    TileKeyRecord tile_key_for_coordinate(GeographicCoordinate coordinate, u8 zoom_level);

    /// Every XYZ tile whose area intersects the spanning box, ordered
    /// row-major from the north.
    [Throws=SdkError]
    sequence<TileKeyRecord> tiles_covering(BoundingBox bounding_box, u8 zoom_level);

    /// The SDK default step-advance policy: the distance-to-end-of-step
    /// condition with the Ferrostar recommended driving defaults.
    StepAdvancePolicy default_step_advance_policy();

    /// The SDK default deviation policy: the distance-threshold detector with
    /// the SDK's default deviation threshold.
    DeviationPolicy default_deviation_policy();

    /// The SDK default reroute-recalculation policy (round 11): the
    /// Ferrostar-recommended hysteresis — a 5 s cooldown and a 50 m movement
    /// threshold.
    ReroutePolicy default_reroute_policy();
};

/// A position on the WGS 84 ellipsoid in decimal degrees.
dictionary GeographicCoordinate {
    /// Latitude in decimal degrees, valid within `[-90, 90]`.
    double latitude;
    /// Longitude in decimal degrees, valid within `[-180, 180]`.
    double longitude;
};

/// Rectangular area between a south-west and a north-east corner; boxes
/// never span the antimeridian.
dictionary BoundingBox {
    /// South-west corner (minimum latitude and longitude).
    GeographicCoordinate south_west;
    /// North-east corner (maximum latitude and longitude).
    GeographicCoordinate north_east;
};

/// Position on the viewport in physical pixels (origin top-left, y down).
dictionary ScreenPoint {
    /// Horizontal position in pixels.
    double x;
    /// Vertical position in pixels.
    double y;
};

/// A world position in normalized world coordinates, both axes within
/// `[0, 1]`.
dictionary NormalizedWorldCoordinate {
    /// Eastward position from the antimeridian.
    double x;
    /// Southward position from the north pole.
    double y;
};

/// One web-mercator XYZ tile address.
dictionary TileKeyRecord {
    /// Tile column, in `0..2^zoom_level`.
    u32 tile_x;
    /// Tile row, counted from the north, in `0..2^zoom_level`.
    u32 tile_y;
    /// Zoom level this tile belongs to.
    u8 zoom_level;
};

/// Viewport dimensions in physical pixels.
dictionary ViewportPixels {
    /// Horizontal extent in pixels.
    double width_pixels;
    /// Vertical extent in pixels.
    double height_pixels;
};

/// Screen-space insets kept clear of map content: distances from each
/// viewport edge in pixels, so UI overlays — route cards, attribution, sheet
/// panels — never cover the content the camera guarantees visible.
dictionary EdgeInsetsPixels {
    /// Pixels kept clear from the left viewport edge.
    double left;
    /// Pixels kept clear from the top viewport edge.
    double top;
    /// Pixels kept clear from the right viewport edge.
    double right;
    /// Pixels kept clear from the bottom viewport edge.
    double bottom;
};

/// Failures thrown across the native binding surface of this slice. The
/// variants mirror the `geospatial-types` `SDKError` subset actually returned
/// by the frozen calls; the detail message is always verbatim.
[Error] interface SdkError {
    /// A coordinate, viewport extent or box corner failed validation.
    InvalidCoordinateInput(string detail);
    /// The network transport failed; carries the provider's verbatim detail.
    NetworkRequestFailed(string detail);
    /// The provider answered with a payload the parser could not use.
    ResponseParsingFailure(string detail);
    /// The provider returned a documented application-level failure status.
    ProviderRequestRejected(string detail);
    /// The token-bucket rate limiter is exhausted; retry after the next refill.
    RateLimitExhausted();
    /// No provider implementation is registered for the requested contract.
    ProviderNotConfigured(string contract_name);
    /// A navigation input (route record or navigation policy) failed native
    /// validation.
    InvalidNavigationInput(string detail);
    /// An overlay descriptor did not describe a usable overlay (wrong kind
    /// payload, an out-of-range color channel, or a non-positive or
    /// non-finite width or radius), or a surface input was structurally
    /// unusable (a null, zero or ambiguous platform surface pointer, or a
    /// non-positive viewport extent). Round 8 (surface slice).
    InvalidRenderInput(string detail);
    /// The live rendering could not proceed on the platform surface: the
    /// platform handle or surface was unusable, the graphics context could
    /// not be acquired, the surface format could not be negotiated or
    /// configured, the frame texture could not be acquired even after the
    /// stale-surface retry, or the frame's render state was busy in another
    /// draw. Round 8 (surface slice).
    RenderingSurfaceUnavailable(string detail);
};

/// Anchored map camera (geographic center, zoom, bearing, pitch) mirroring
/// the native `map-rendering` camera. The ground point under the center
/// always sits at the viewport center; zoom and pitch are clamped and the
/// bearing wraps into `[0, 360)` exactly like the native constructor.
interface MapCamera {
    [Throws=SdkError]
    constructor(GeographicCoordinate center, double zoom_level, double bearing_degrees, double pitch_degrees);

    /// Builds the camera that frames a bounding box inside the viewport's
    /// inset region: top-down and north-up by construction (pitch and bearing
    /// zero), zoomed to the largest level keeping the whole box inside the
    /// inset area and clamped to `maximum_zoom_level`; the center is anchored
    /// on the inset region's center. Throws when the insets or the maximum
    /// zoom are not finite usable values, when the insets are negative or
    /// leave no inset area, or when the box spans no positive world area.
    [Throws=SdkError, Name=fit_bounds]
    constructor(BoundingBox bounding_box, ViewportPixels viewport, EdgeInsetsPixels edge_insets, double maximum_zoom_level);

    /// Projects a coordinate to viewport pixels (origin top-left, y down).
    /// Returns null when the point lies behind the camera.
    [Throws=SdkError]
    ScreenPoint? project_to_screen(GeographicCoordinate coordinate, ViewportPixels viewport, double field_of_view_degrees);

    /// Unprojects a viewport pixel back onto the ground plane. Returns null
    /// when the ray misses the ground (typically below the horizon at steep
    /// pitch).
    [Throws=SdkError]
    GeographicCoordinate? screen_to_ground_coordinate(ScreenPoint screen_point, ViewportPixels viewport, double field_of_view_degrees);

    /// Every tile at `tile_zoom_level` whose area intersects the visible
    /// ground, sorted row-major from the north.
    [Throws=SdkError]
    sequence<TileKeyRecord> visible_tile_keys(ViewportPixels viewport, double field_of_view_degrees, u8 tile_zoom_level);

    /// Nearest whole zoom level a tile fetch should use for this camera.
    u8 tile_zoom_level();

    /// Reads the camera's attribute state back verbatim as it was built
    /// (round-9 asks). The getters make `fit_bounds`' chosen framing
    /// readable: the zoom level the constructor actually fitted, its
    /// anchored center, and the constructed bearing and pitch read back
    /// through these members. Read-only: the camera carries no setters —
    /// a new camera pose is a new constructor call. The UniFFI 0.32 UDL
    /// parser supports no `[Attribute]` member syntax, so the getters are
    /// plain methods.
    GeographicCoordinate center();
    double zoom_level();
    double bearing_degrees();
    double pitch_degrees();

    /// Ground meters represented by one screen pixel at the camera's center
    /// (round 10, the scale bar's helper): the equatorial ground meters per
    /// pixel at the camera's zoom level times the cosine of the center
    /// latitude, so the answer shrinks toward the poles exactly like the
    /// projected ground. At any nonzero pitch this is an approximation valid
    /// at the viewport's center column: the helper is flat-centered (no
    /// unprojection fold of pitch into the answer) and documents that
    /// honestly; the platform scale bars fit the meter magnitude through a
    /// labeled ladder rather than reading a bare value.
    [Throws=SdkError]
    double ground_meters_per_screen_pixel(ViewportPixels viewport, double field_of_view_degrees);
};

// ---------------------------------------------------------------------------
// Foreign-callback provider surface (M4 slice 4). The `[Trait, WithForeign]`
// interfaces below are the provider contracts foreign code (Kotlin, Swift)
// implements to serve calls of the matching `MapSdk` method. The plain
// contract name is used — no `Foreign` prefix — because this namespace
// freezes nothing else with those names (the `geospatial-traits` traits of
// the same names live in a different crate and never cross this FFI), and
// the name a Kotlin implementor reads is the contract the SDK documents.
// Per-member `[Async]` only: interface-level `[Async]` is rejected by the
// uniffi 0.32 UDL parser (documented in `docs/udl/contracts-draft.udl`).
// The remaining provider contracts follow this same two-step pattern as
// their first native or foreign implementations demand.
// ---------------------------------------------------------------------------

/// Geocoding contract implemented by foreign code and registered on `MapSdk`
/// through `register_geocoding_provider_from_foreign`.
[Trait, WithForeign]
interface GeocodingProvider {
    /// Resolves a free-form address text into candidate places.
    [Async, Throws=SdkError]
    sequence<PlaceResult> forward_geocode(string address_text);

    /// Resolves a coordinate into candidate places describing that position.
    [Async, Throws=SdkError]
    sequence<PlaceResult> reverse_geocode(GeographicCoordinate coordinate);
};

/// Raster-tile contract implemented by foreign code and registered on
/// `MapSdk` through `register_raster_tile_provider_from_foreign`.
[Trait, WithForeign]
interface RasterTileProvider {
    /// Fetches one web-mercator XYZ raster tile at the requested resolution.
    [Async, Throws=SdkError]
    RasterTileData fetch_raster_tile(
        u32 tile_x,
        u32 tile_y,
        u8 zoom_level,
        RasterImageFormat image_format,
        RasterTileResolution tile_resolution
    );
};

// ---------------------------------------------------------------------------
// Async provider surface (M4 slice 3). The enums and records below mirror the
// `geospatial-types` provider records one-to-one by name and field; `MapSdk`
// resolves every call through the `map-core` `ProviderRegistry`, so a missing
// registration throws `ProviderNotConfigured` with the contract name.
// ---------------------------------------------------------------------------

/// Structural precision a geocoder reached for one resolved location.
enum GeocodeQuality {
    /// Rooftop or feature point.
    "Point",
    /// Street address matched to the building level.
    "StreetAddress",
    /// Street intersection.
    "Intersection",
    /// Street level without house number.
    "Street",
    /// Postal code area.
    "PostalCode",
    /// Settlement level (village, town, city).
    "City",
    /// County or administrative district.
    "County",
    /// First-level administrative division.
    "State",
    /// Sovereign country level.
    "Country",
};

/// Mode of transport used to calculate a route.
enum TransportationMode {
    /// Private car routing.
    "Automobile",
    /// Bicycle routing.
    "Bicycle",
    /// Walking routing.
    "Pedestrian",
};

/// Length and speed conventions used when displaying a route.
enum UnitSystem {
    /// Kilometers and kilometers per hour.
    "Metric",
    /// Miles and miles per hour.
    "Imperial",
};

/// What a routing request optimizes for; the strategy applies to automobile
/// routing only (the current providers have no time-based cost model for the
/// other modes).
enum RouteStrategy {
    /// Minimize expected travel time.
    "Fastest",
    /// Minimize traveled distance.
    "Shortest",
};

/// Class of disruption reported in a traffic incident.
enum TrafficIncidentType {
    /// Road construction.
    "Construction",
    /// Traffic-impacting event.
    "Event",
    /// Collision.
    "Accident",
    /// Weather-related disruption.
    "Weather",
    /// Lane or road closure.
    "Closure",
};

/// Severity ranking of a traffic incident.
enum TrafficIncidentSeverity {
    /// Minimal delay expected.
    "Low",
    /// Noticeable delay expected.
    "Medium",
    /// Significant delay expected.
    "High",
};

/// Congestion classification of a road segment.
enum TrafficFlowCongestionLevel {
    /// At or above free-flow speed.
    "FreeFlow",
    /// Moderately below free-flow speed.
    "Slow",
    /// Stop-and-go traffic.
    "Congested",
    /// Roadway effectively closed.
    "Blocked",
};

/// Encoded image formats a raster tile provider may deliver.
enum RasterImageFormat {
    /// PNG imagery.
    "Png",
    /// JPEG imagery.
    "Jpeg",
};

/// Pixel resolution a raster tile is rendered at; `Standard` is the plain
/// tile form, `Retina` the `@2x` form with twice the texels per edge for the
/// same logical tile span.
enum RasterTileResolution {
    /// One texel per logical tile pixel.
    "Standard",
    /// Twice the texels per logical tile pixel (the `@2x` request form).
    "Retina",
};

/// One resolved location returned by forward or reverse geocoding.
dictionary PlaceResult {
    /// Display string the provider produced for this place.
    string structured_address_text;
    /// Resolved position.
    GeographicCoordinate coordinate;
    /// Structural precision reached for this resolution.
    GeocodeQuality geocode_quality;
};

/// Road features a routing request can ask the provider to avoid; all
/// disabled by default.
dictionary AvoidOptions {
    /// Avoid toll roads.
    boolean toll_roads;
    /// Avoid limited-access highways and motorways.
    boolean limited_access_highways;
    /// Avoid ferry crossings.
    boolean ferries;
    /// Avoid unpaved roads.
    boolean unpaved_roads;
};

/// Options controlling how a route is calculated.
dictionary RouteOptions {
    /// Mode of transport.
    TransportationMode transportation_mode;
    /// Whether routing optimizes travel time or distance.
    RouteStrategy route_strategy;
    /// Display unit convention the provider should annotate.
    UnitSystem unit_system;
    /// Features to avoid.
    AvoidOptions avoid_options;
    /// Departure instant as seconds since the Unix epoch; null for
    /// "depart now".
    u64? departure_timestamp_seconds;
};

/// One maneuver within a route leg.
dictionary RouteStep {
    /// Distance of this step in meters.
    double distance_meters;
    /// Travel time of this step in seconds.
    double duration_seconds;
    /// Maneuver classification as a stable snake-case text label; the
    /// roundabout exit index (one-based) and the turn direction are carried
    /// in the label (`roundabout-2`, `turn-left`); lane guidance is not part
    /// of this surface yet.
    string maneuver_type;
    /// Human-readable instruction in the display language.
    string instruction_display_text;
    /// Instruction tuned for text-to-speech engines.
    string instruction_speech_text;
    /// Step geometry from the previous step end to this maneuver point.
    sequence<GeographicCoordinate> shape;
};

/// Leg between two consecutive waypoints.
dictionary RouteLeg {
    /// Leg distance in meters.
    double distance_meters;
    /// Leg travel time in seconds.
    double duration_seconds;
    /// Maneuver steps in traversal order.
    sequence<RouteStep> steps;
};

/// A complete calculated route.
dictionary RouteData {
    /// Total distance across all legs, in meters.
    double distance_meters;
    /// Total travel time across all legs, in seconds.
    double duration_seconds;
    /// Geographic bounds of the full route geometry.
    BoundingBox bounding_box;
    /// Legs, one per consecutive waypoint pair.
    sequence<RouteLeg> legs;
};

/// One origin-destination cell of a distance matrix.
dictionary DistanceMatrixEntry {
    /// Origin index into the request's origins.
    u32 origin_index;
    /// Destination index into the request's destinations.
    u32 destination_index;
    /// Distance for this cell in meters.
    double distance_meters;
    /// Travel time for this cell in seconds.
    double duration_seconds;
};

/// Travel-time matrix for origins x destinations.
dictionary DistanceMatrix {
    /// Entries in row-major origin-major order.
    sequence<DistanceMatrixEntry> entries;
};

/// One reported disruption inside a queried bounding box.
dictionary TrafficIncident {
    /// Provider-stable incident identifier.
    string identifier;
    /// Class of disruption.
    TrafficIncidentType incident_type;
    /// Severity ranking.
    TrafficIncidentSeverity severity;
    /// Provider display text for this incident.
    string short_description_text;
    /// Position where the incident starts.
    GeographicCoordinate start_coordinate;
    /// Position where the incident ends, if reported.
    GeographicCoordinate? end_coordinate;
};

/// Flow condition for one road segment (or polyline of road segments) inside
/// the queried box, in kilometers per hour when the provider reports speeds
/// and as a congestion classification otherwise.
dictionary TrafficFlowData {
    /// Segment geometry, ordered along the road.
    sequence<GeographicCoordinate> shape_coordinates;
    /// Currently observed speed in kilometers per hour, when reported.
    double? current_speed_kilometers_per_hour;
    /// Unconstrained (free-flow) speed in kilometers per hour, when reported.
    double? free_flow_speed_kilometers_per_hour;
    /// Congestion classification from the speed ratio or provider buckets.
    TrafficFlowCongestionLevel congestion_level;
};

/// One fetched web-mercator XYZ raster tile.
dictionary RasterTileData {
    /// Tile column coordinate at the given zoom level.
    u32 tile_x;
    /// Tile row coordinate at the given zoom level.
    u32 tile_y;
    /// Zoom level this tile belongs to.
    u8 zoom_level;
    /// Encoding of the byte payload.
    RasterImageFormat image_format;
    /// Pixel resolution the payload was rendered at.
    RasterTileResolution tile_resolution;
    /// Encoded image bytes.
    sequence<u8> image_bytes;
};

/// Async provider entry point of the SDK: resolves every call of the
/// implemented `geospatial-traits` contracts through the `map-core`
/// `ProviderRegistry`. Construct the object empty, then the native
/// integrator registers providers through the Rust-side `register_*`
/// methods and foreign integrators through the `register_*_from_foreign`
/// methods (see the `[Trait, WithForeign]` interfaces above); every call
/// into an unregistered contract throws `SdkError::ProviderNotConfigured`
/// with the contract name.
///
/// The methods mirror the provider contracts one-to-one: geocoding (forward
/// and reverse), routing, distance matrix, place search, traffic incidents,
/// traffic flow and raster tiles. Elevation and offline routing follow with
/// their first concrete provider implementations (M6/M7).
interface MapSdk {
    /// Builds the SDK with an unconfigured provider registry; the native
    /// integrator registers providers before the first await call.
    constructor();

    /// Registers the geocoding implementation built by the foreign code on
    /// the `GeocodingProvider` interface; the foreign implementation starts
    /// serving `forward_geocode` and `reverse_geocode` once this returns.
    void register_geocoding_provider_from_foreign(GeocodingProvider geocoding_provider);

    /// Registers the raster-tile implementation built by the foreign code on
    /// the `RasterTileProvider` interface; the foreign implementation starts
    /// serving `fetch_raster_tile` once this returns.
    void register_raster_tile_provider_from_foreign(RasterTileProvider raster_tile_provider);

    /// Resolves a free-form address text into candidate places.
    [Async, Throws=SdkError]
    sequence<PlaceResult> forward_geocode(string address_text);

    /// Resolves a coordinate into candidate places describing that position.
    [Async, Throws=SdkError]
    sequence<PlaceResult> reverse_geocode(GeographicCoordinate coordinate);

    /// Calculates a route through the ordered waypoints with the given
    /// options; the waypoints must all be canonical coordinates.
    [Async, Throws=SdkError]
    RouteData calculate_route(sequence<GeographicCoordinate> waypoints, RouteOptions route_options);

    /// Calculates durations and distances for every origin-destination pair.
    [Async, Throws=SdkError]
    DistanceMatrix calculate_distance_matrix(
        sequence<GeographicCoordinate> origins,
        sequence<GeographicCoordinate> destinations,
        TransportationMode transportation_mode
    );

    /// Returns fast completion candidates while the user types; the boundary
    /// restricts the search when given.
    [Async, Throws=SdkError]
    sequence<PlaceResult> search_autocomplete(string query_text, BoundingBox? search_boundary);

    /// Returns full search results for a settled query; the boundary
    /// restricts the search when given.
    [Async, Throws=SdkError]
    sequence<PlaceResult> search_places(string query_text, BoundingBox? search_boundary);

    /// Fetches incidents intersecting the bounding box.
    [Async, Throws=SdkError]
    sequence<TrafficIncident> fetch_traffic_incidents(BoundingBox bounding_box);

    /// Fetches per-segment flow observations inside the bounding box.
    [Async, Throws=SdkError]
    sequence<TrafficFlowData> fetch_traffic_flow(BoundingBox bounding_box);

    /// Fetches one web-mercator XYZ raster tile at the requested resolution.
    [Async, Throws=SdkError]
    RasterTileData fetch_raster_tile(
        u32 tile_x,
        u32 tile_y,
        u8 zoom_level,
        RasterImageFormat image_format,
        RasterTileResolution tile_resolution
    );

    /// Rounds 8 (surface slice): registers the seven native MapQuest
    /// provider implementations (geocoding, routing, distance matrix, place
    /// search, traffic incidents, traffic flow, raster tiles) onto the
    /// registry, constructed from the MapQuest consumer key with the SDK
    /// defaults for base URL, rate limiting and retries — the foreign
    /// mirror of the native integrator's `register_*` calls, so foreign
    /// code installs the whole provider set with one call and an API key.
    /// Re-registration replaces every slot. Elevation stays unregistered:
    /// the elevation contract has no MapQuest provider, and elevation calls
    /// keep throwing `ProviderNotConfigured`.
    [Throws=SdkError]
    void register_mapquest_providers(string mapquest_api_key);

    /// Round 9 (offline-maps demo): registers the seven native MapQuest
    /// provider implementations from one key exactly as
    /// `register_mapquest_providers`, wrapping the raster-tile contract in
    /// the disk response cache opened (or created) as a SQLite database at
    /// `tile_cache_database_path` under an absolute byte budget of
    /// `tile_cache_absolute_maximum_bytes`. Cached tile responses follow the
    /// tiles category's time-to-live (thirty days by default — the
    /// imagery-provider terms ceiling, PRD §6.7); a cache miss goes to the
    /// network and back-fills the cache, so pre-warmed tiles keep rendering
    /// whenever the network is unavailable. The path must be non-empty and
    /// the byte budget positive; a cache open or quota failure throws with
    /// its verbatim detail. Re-registration replaces the slots (including
    /// the cached raster-tile slot).
    [Throws=SdkError]
    void register_mapquest_providers_with_tile_cache(string mapquest_api_key, string tile_cache_database_path, u64 tile_cache_absolute_maximum_bytes);

    // --- Tile-cache settings surface (M2's remaining developer-facing
    // sub-item; addition-only). The frozen
    // `register_mapquest_providers_with_tile_cache` signature is untouched:
    // the byte budget stays a registration-time fact (the SQLite store's
    // quota is fixed when the store opens, so retuning it means
    // re-registering through the frozen method), while the tile time-to-live
    // is a property of the installed cache and is tuned live through the two
    // members below. A per-integration registration-time alternative (a
    // second registration method carrying the whole frozen parameter set
    // plus one optional TTL field) was rejected: it duplicates the
    // registration surface for one optional value, and the terms readback
    // below already makes the applied value inspectable, so the override
    // rides the installed cache instead. Re-registration replaces the slots
    // and the tuning: a freshly registered cache starts again at the
    // category default, and the integrator re-applies the override after
    // registering.
    //
    // Validation ruling (PRD §6.7's provider-terms-ceiling intent): a
    // requested time-to-live above the installed provider's declared ceiling
    // is ACCEPTED, and the ceiling clamps what the cache stores — the terms
    // ceiling is a compliance bound on what the cache may retain, not a
    // preference, so the SDK must never retain beyond the provider's terms
    // whatever the integrator asks for; refusing the call would instead
    // break a live integration every time the provider's terms move or the
    // integrator reads a stale ceiling. The ceiling and the effective
    // (clamped) time-to-live are both in the `raster_tile_cache_terms`
    // readback, so an integrator sees exactly what went into force. A zero
    // time-to-live is refused: it would expire every entry on capture
    // (a cache that stores nothing), which is almost certainly a unit
    // mistake in the caller.

    /// Tile-cache settings surface (M2): overrides the raster-tile cache's
    /// time-to-live on the installed cache, in seconds. The tile cache must
    /// be installed (registered through
    /// `register_mapquest_providers_with_tile_cache`); otherwise this throws
    /// `ProviderNotConfigured` naming `TileCache` as the unconfigured
    /// backing store. A zero value throws `InvalidCoordinateInput` with a
    /// precise detail (a zero time-to-live expires every entry on capture,
    /// storing nothing). A value above the installed provider's declared
    /// terms ceiling is accepted with the ceiling clamping what the cache
    /// stores (see the ruling above); `raster_tile_cache_terms` reports the
    /// effective value.
    [Throws=SdkError]
    void set_raster_tile_cache_time_to_live(u64 time_to_live_seconds);

    /// Tile-cache settings surface (M2): reads the effective raster-tile
    /// caching terms so a developer can display them (a settings page, or
    /// the offline-maps demo's cache panel). Reports the time-to-live
    /// currently in force on the installed cache (the category default
    /// thirty days, or the last override this object accepted, clamped when
    /// it exceeded the provider's terms ceiling), the installed provider's
    /// declared terms ceiling (null when the provider declares no ceiling,
    /// in which case the requested time-to-live applies verbatim), and the
    /// cache category's default time-to-live. The tile cache must be
    /// installed (registered through
    /// `register_mapquest_providers_with_tile_cache`); otherwise this
    /// throws `ProviderNotConfigured` naming `TileCache` as the
    /// unconfigured backing store. Read-only: the readback never mutates
    /// the cache.
    [Throws=SdkError]
    TileCacheTerms raster_tile_cache_terms();

    /// Round-10 diagnostics (capability 6): subscribes the foreign listener
    /// to the SDK's structured internal log events streamed fire-and-forget
    /// from the crates' bounded ring buffers. Only events at
    /// `minimum_severity` or above are delivered, and coordinates within
    /// event messages are always rounded at the sink (never the API key).
    /// The listener callbacks run on the threads that produce the events and
    /// must never block on the caller; re-subscribing an already subscribed
    /// listener replaces its severity gate.
    void subscribe_to_diagnostic_events(SdkDiagnosticEventListener listener, SdkLogSeverity minimum_severity);

    /// Round-10 diagnostics: drains up to `maximum_count` of the most recent
    /// ring events at `minimum_severity` or above through the listener's
    /// `on_diagnostic_event` (oldest first) and returns how many were
    /// delivered, then (like Organic Maps' and OsmAnd's opt-in capture)
    /// nothing is removed from the ring, so a later flush can redeliver. The
    /// flush is bounded and synchronous: a listener must not block on it.
    u32 flush_recent_diagnostic_events(SdkDiagnosticEventListener listener, SdkLogSeverity minimum_severity, u32 maximum_count);

    /// Round-12 provider-failure diagnostic seam (addition-only): reads the
    /// retained diagnostics of every failed foreign-callback provider call
    /// this object has served — the failures foreign implementations of the
    /// `[Trait, WithForeign]` provider contracts threw, both the typed ones
    /// (the foreign code threw one of the contract's `SdkError` variants) and
    /// the unclassified ones (the foreign code failed without a mapped
    /// error). Returns the ring's retained entries oldest first, in
    /// recency-bounded order: the ring keeps the last
    /// `PROVIDER_FAILURE_DIAGNOSTIC_RING_CAPACITY` failures and drops the
    /// oldest beyond it, and reading is non-destructive. Read-only: the
    /// readback never records anything and never clears the ring. See
    /// `ProviderFailureDiagnostic` for what an entry carries; the frozen
    /// `SdkError` variants are untouched — failures still surface typed at
    /// the throwing call, and this seam only adds where they can be
    /// classified after the fact.
    sequence<ProviderFailureDiagnostic> latest_provider_failure_diagnostics();
};

// ---------------------------------------------------------------------------
// Tile-cache settings surface (M2 — addition-only). The record mirrors the
// effective caching terms of the installed raster-tile cache; the ceiling is
// the provider-specific caching term (PRD §6.7), read only when a cache is
// installed, which is the only state the settings surface addresses.
// ---------------------------------------------------------------------------

/// The effective raster-tile caching terms of the installed cache
/// (`MapSdk::raster_tile_cache_terms`'s return), shaped so a developer can
/// display the caching posture: the default, the provider's ceiling and the
/// value currently in force.
dictionary TileCacheTerms {
    /// Time-to-live currently in force on the installed cache, in seconds:
    /// the category default, or the last accepted override (clamped to the
    /// ceiling when the override exceeded it).
    u64 effective_time_to_live_seconds;
    /// The installed provider's declared caching-terms ceiling in seconds,
    /// or null when the provider declares no ceiling — the requested
    /// time-to-live then applies verbatim. For the MapQuest raster tiles
    /// this is the documented terms ceiling (thirty calendar days).
    u64? provider_terms_ceiling_seconds;
    /// The cache category's default time-to-live for raster tiles, in
    /// seconds (thirty days by the owner ruling of 2026-10-28, PRD §6.7);
    /// the value a freshly registered cache starts at.
    u64 default_time_to_live_seconds;
};

// ---------------------------------------------------------------------------
// Navigation wiring (M6). The `[Trait, WithForeign]`
// `NavigationStateEventListener` is the listener contract foreign code
// implements; its subscriptions register on the `MapNavigationController`
// object, which drives the native `map-navigation` reducer session and its
// per-topic broadcaster. The records and enums below mirror the
// `map-navigation` plain-data types field-for-field; the step-advance and
// deviation policies stand in for the native decision objects (trait
// objects), which cannot cross the FFI, as tagged records. Payload-carrying
// native enums mirror as a kind enum plus nullable fields; the instruction
// payload mirrors without the native active maneuver's lane-guidance list,
// which stays native until the frozen route records carry lane guidance.
// ---------------------------------------------------------------------------

/// State-listener contract implemented by foreign code and subscribed on
/// `MapNavigationController` through the `subscribe_to_*` methods; each
/// registered subscription delivers only its own topic.
[Trait, WithForeign]
interface NavigationStateEventListener {
    /// Receives the composed instruction payload of every dispatched state
    /// change (the instructions topic).
    void on_navigation_instruction(NavigationInstruction instruction);

    /// Receives a deviation-status stream update (the deviation topic):
    /// off-route on entry and on every recorded measurement update while the
    /// deviation persists, on-route when it clears.
    void on_deviation_status_change(DeviationStatusChange deviation_status_change);

    /// Receives one phase transition per dispatched trip-state transition
    /// (the phase topic).
    void on_navigation_phase_change(NavigationPhaseChange navigation_phase_change);

    /// Round 10 (speech pacing, OQ5's SDK-owned triggers): receives one
    /// utterance trigger when the controller's distance-band policy judges
    /// the fix newsworthy (a trigger band crossed this fix) — the verbatim
    /// composed speech text, never rewritten, so the platform owns only
    /// playback. Subscribers of the speech topic receive exactly the
    /// `subscribe_to_speech_instructions` deliveries.
    void on_speech_instruction(SpeechInstruction speech_instruction);
};

/// Handle coupled to exactly one `(topic, listener)` subscription; cancelling
/// is explicit and idempotent, dropping the handle does not unsubscribe.
interface NavigationTopicSubscription {
    /// Cancels this subscription: the listener stops receiving the
    /// subscription's topic from the next dispatch on. Idempotent.
    void cancel();
};

/// The exported navigation controller: drives the native navigation reducer
/// session, broadcasts every state change to the registered listeners, and
/// answers read-only state and instruction queries. Obtain routes from
/// `MapSdk::calculate_route` and start a trip on them with this controller.
///
/// The listener callbacks run synchronously on the thread that drives the
/// controller call; a listener calling back into the controller while a
/// dispatch is still in flight is suppressed by the broadcaster's
/// single-in-flight guard (the state change behind it is already applied,
/// only its broadcast is skipped). The controller never holds its state lock
/// across a listener callback.
interface MapNavigationController {
    /// Builds the controller with no active trip and no subscriptions.
    constructor();

    /// Subscribes the listener to the instructions topic: the composed
    /// instruction payload of every dispatched state change, in registration
    /// order.
    NavigationTopicSubscription subscribe_to_navigation_instructions(NavigationStateEventListener listener);

    /// Subscribes the listener to the deviation-status topic, in registration
    /// order.
    NavigationTopicSubscription subscribe_to_deviation_status(NavigationStateEventListener listener);

    /// Subscribes the listener to the phase-transition topic, in registration
    /// order.
    NavigationTopicSubscription subscribe_to_phase_changes(NavigationStateEventListener listener);

    /// Begins navigating `route` with the given policies, replacing any
    /// active trip. The initial instruction payload is dispatched; no
    /// deviation or phase transitions exist yet. A route without traversable
    /// steps represents an already-arrived trip. Refuses policies with
    /// non-finite or negative thresholds, and route records carrying a step
    /// maneuver label the conversion cannot map.
    [Throws=SdkError]
    void start_trip(RouteData route, UnitSystem unit_system, StepAdvancePolicy step_advance_policy, DeviationPolicy deviation_policy);

    /// Delivers one position fix to the active trip; without one the fix is
    /// dropped. Fixes without an accuracy report never trigger a step
    /// advance and are never judged by the deviation detector.
    void provide_location_update(NavigationLocationUpdate location_update);

    /// Replaces the route of the active trip (a reroute). The replacement
    /// route must be anchored at the trip's current snapped coordinate; the
    /// step advance strategy for the new route is configured by
    /// `step_advance_policy` and the deviation policy persists. A reroute
    /// offered to an arrived trip is ignored (no transition); without an
    /// active trip the event is dropped. Round 11: an in-flight reroute
    /// calculation is settled by this call (applied or ignored alike), so
    /// the request-in-flight gate reopens after the integrator acts.
    [Throws=SdkError]
    void provide_reroute(RouteData route, StepAdvancePolicy step_advance_policy);

    /// Round 11 (Ferrostar B3): applies the reroute-recalculation gates to
    /// one reroute-request signal at the wall clock: true when every gate
    /// passed (no calculation in flight, outside the cooldown window, and
    /// — when both fix references exist — beyond the movement threshold)
    /// and a calculation is now in flight, readable as the trip state's
    /// `is_calculating_new_route`; false when the signal was refused. A
    /// signal without an open, still-navigable trip (no trip, or the trip
    /// has arrived) is refused before the gates are consulted. A passing
    /// caller owes a settle: the eventual `provide_reroute` (applied or
    /// ignored) or `cancel_reroute_calculation`. The initial (trip's first)
    /// attempt always passes the cooldown and movement gates.
    boolean begin_reroute_calculation();

    /// Round 11: settles an in-flight reroute calculation without applying a
    /// route (the integrator abandoned the request): the request-in-flight
    /// gate reopens. The cooldown keeps running from the calculation's
    /// beginning and the movement reference stays where it was. Idempotent.
    void cancel_reroute_calculation();

    /// Round 11: swaps the reroute-recalculation hysteresis policy in force,
    /// keeping the gate's memory as it stands. Both thresholds must be
    /// finite and non-negative (refused with `InvalidNavigationInput`
    /// otherwise, and the policy in force is then untouched).
    [Throws=SdkError]
    void configure_reroute_policy(ReroutePolicy reroute_policy);

    /// The plain state of the active trip, or null without one.
    TripNavigationState? current_trip_state();

    /// The composed turn-by-turn instruction payload of the current trip
    /// state, or null without an active trip; repeated reads of an unchanged
    /// state return the same payload.
    NavigationInstruction? current_instructions();

    /// Round 10 (speech pacing, OQ5): subscribes the listener to the
    /// speech-instruction trigger topic, in registration order. Each fix's
    /// dispatch consults the controller's named-potential policy: the trigger
    /// bands reuse the display phrase's distance bands (so a spoken "in 100
    /// meters" always matches the payload's shown phrase band), each band
    /// announces at most once per step (monotone progress), and a minimum
    /// inter-utterance spacing and repeat distance suppress chatter. The
    /// platform keeps playback (including its fallback speak-latest behavior
    /// when no listener subscribes) and synthesis stays out of scope.
    NavigationTopicSubscription subscribe_to_speech_instructions(NavigationStateEventListener listener);
};

/// High-level phase of a trip that has already been started. Rerouting is not
/// a phase of its own: the machine flows Navigating ⇄ OffRoute within a trip
/// and Navigating → Arrived at its end.
enum NavigationPhase {
    /// Following the route's step sequence.
    "Navigating",
    /// The deviation detector judged the last graded fix off route; progress
    /// is frozen at the last on-route values until a graded fix recovers or a
    /// reroute replaces the route.
    "OffRoute",
    /// The final step has been completed; location updates no longer change
    /// the state.
    "Arrived",
};

/// Which grade of deviation a recorded deviation carries: the two-grade
/// distinction between off the current step yet still on the route line and
/// completely off the route. Only the fully-off grade freezes navigation; the
/// wrong-step grade never freezes on its own.
enum DeviationGradeKind {
    /// The fix projects onto neither the current step nor any future step of
    /// the remaining route line: the freezing grade. The trip's progress is
    /// frozen until a graded fix recovers or a reroute replaces the route.
    "FullyOffRoute",
    /// The fix is off the current step but projects onto a future step of the
    /// remaining route line: "on route, wrong step". The trip keeps
    /// navigating and counting down, and is never frozen by this grade alone.
    "OffStepOnRoute",
};

/// Plain-data description of an active deviation from the route. The grade
/// kind distinguishes the freezing fully-off grade from a merely wrong-step
/// record: a wrong-step record is guidance feedback ("on the route, on a
/// later step") carried while the trip phase stays `Navigating`.
dictionary DeviationInfo {
    /// Which grade of deviation this is (see `DeviationGradeKind`).
    DeviationGradeKind grade_kind;
    /// Distance, in meters, from the raw fix that triggered (or most recently
    /// confirmed) the deviation to the remaining route line.
    double deviation_from_route_meters;
};

/// Location of one route step in traversal order across a route's legs.
dictionary RouteStepPosition {
    /// Index of the leg that owns this step in the route's legs.
    u64 leg_index;
    /// Index of the step within its leg in the leg's steps.
    u64 step_index;
};

/// Progress along the remaining trip.
dictionary TripProgress {
    /// Distance, in meters, from the snapped position to the end of the
    /// current step — the point of the next maneuver.
    double distance_to_next_maneuver_meters;
    /// Total remaining distance, in meters, including the current step's
    /// remainder.
    double distance_remaining_meters;
    /// Total remaining travel time, in seconds, measured the same way as
    /// `distance_remaining_meters`.
    double duration_remaining_seconds;
};

/// The full navigable state of an active trip, read-only for foreign code.
/// The route's steps are carried as traversal-ordered indices, never
/// duplicated; the live step-advance condition and deviation detector are not
/// records and cannot cross the FFI — they are configured through the
/// navigation policies on the controller.
dictionary TripNavigationState {
    /// Current phase of the trip.
    NavigationPhase phase;
    /// The route being navigated.
    RouteData route;
    /// All steps across every leg, flattened in traversal order.
    sequence<RouteStepPosition> step_positions;
    /// Index into `step_positions` of the step currently traversed.
    u64 current_step_position;
    /// Progress along the remaining trip.
    TripProgress progress;
    /// Position snapped onto the current step's shape; null until the first
    /// location update has been processed.
    GeographicCoordinate? snapped_coordinate;
    /// Active deviation, as judged by the deviation detector; a fully-off
    /// grade is set exactly while the phase is `OffRoute`, and a wrong-step
    /// (`OffStepOnRoute`) grade may be set while the phase stays `Navigating`.
    DeviationInfo? deviation;
    /// Round 11 (Ferrostar B3): whether a reroute recalculation is currently
    /// in flight. This is the controller's wall-clock gate fact, not reducer
    /// state — the reducer stays clock-free — so it is read fresh from the
    /// controller on every readback.
    boolean is_calculating_new_route;
};

/// Length unit a distance-phrase magnitude is expressed in.
enum DistanceUnit {
    /// Meters, used by the metric near and moderate bands.
    "Meters",
    /// Kilometers, used by the metric far band.
    "Kilometers",
    /// Feet, used by the imperial near and moderate bands.
    "Feet",
    /// Miles, used by the imperial far band.
    "Miles",
};

/// Which form a distance phrase takes.
enum DistancePhraseKind {
    /// The position is closer than half a near-band increment to the maneuver
    /// point; no distance is phrased and composed text drops the prefix. The
    /// magnitude is null on this kind.
    "Immediate",
    /// A rounded distance magnitude in a specific unit; the magnitude and
    /// unit are set on this kind.
    "Quantity",
    /// No distance is phrased because the freezing (fully-off-route) deviation
    /// grade holds: the projection is frozen, so any countdown composed off it
    /// would be a phantom measurement. The magnitude is null on this kind, and
    /// the composed text channels carry no distance prefix.
    "Suppressed",
};

/// The unit-system-aware phrasing of the distance remaining to a maneuver,
/// rounded into the named distance phrasing bands.
dictionary DistancePhrase {
    /// Which form the phrase takes.
    DistancePhraseKind phrase_kind;
    /// Rounded magnitude of the phrase; null for the immediate and suppressed
    /// forms.
    double? magnitude;
    /// Unit the magnitude is expressed in; null for the immediate and
    /// suppressed forms.
    DistanceUnit? magnitude_unit;
};

/// Which form an instruction text takes.
enum ManeuverInstructionTextKind {
    /// The provider authored instruction text for this maneuver.
    "Composed",
    /// The provider authored no instruction text for this maneuver; nothing
    /// is invented to fill it. All four text fields are null on this kind.
    "Unavailable",
};

/// The composed text channels of one maneuver: provider-authored text is
/// carried verbatim beside the composed strings.
dictionary ManeuverInstructionText {
    /// Which form the text takes.
    ManeuverInstructionTextKind text_kind;
    /// Provider-authored display text, verbatim and never rephrased.
    string? provider_display_text;
    /// Provider-authored speech text, verbatim and never rephrased.
    string? provider_speech_text;
    /// Banner variant: the live distance prefix plus the display variant.
    string? composed_display_text;
    /// Text-to-speech variant: the live distance prefix plus the speech
    /// variant.
    string? composed_speech_text;
};

/// Instruction payload of the maneuver currently being traversed (the banner
/// maneuver: the step the snapped position sits inside, with the distance
/// measured to that step's end — the maneuver point).
dictionary ActiveManeuverInstruction {
    /// Composed display and speech text of the active step.
    ManeuverInstructionText instruction;
    /// Unit-system-aware distance phrasing of the distance to the next
    /// maneuver.
    DistancePhrase distance_phrase;
    /// Structured exit index of a roundabout active step, starting at one.
    u8? roundabout_exit_index;
};

/// Instruction payload of the maneuver after the active one (the "then"
/// instruction), text-carrying only.
dictionary UpcomingManeuverInstruction {
    /// Composed display and speech text of the following step.
    ManeuverInstructionText instruction;
    /// Structured exit index when the following step is a roundabout.
    u8? roundabout_exit_index;
};

/// The emitted turn-by-turn instruction payload for one trip state; the same
/// state yields the same payload on every read.
dictionary NavigationInstruction {
    /// The banner maneuver currently being traversed.
    ActiveManeuverInstruction active_maneuver;
    /// The maneuver after the active one, where the route has a following
    /// step (always null on the route's final step and after arrival).
    UpcomingManeuverInstruction? upcoming_maneuver;
    /// True exactly while the trip has arrived and the remaining progress is
    /// zero.
    boolean is_arrival;
    /// True exactly while the trip is off route: the payload beneath the
    /// marker is the frozen last-on-route projection, whose distance phrase
    /// is suppressed rather than presented, and must not be presented as
    /// current guidance.
    boolean is_rerouting_required;
};

/// Which form a deviation-status update takes.
enum DeviationStatusChangeKind {
    /// The trip is judged deviating; the deviation description is set on this
    /// kind. The description's grade kind distinguishes the freezing
    /// fully-off grade from a merely wrong-step record, which is delivered
    /// while the trip phase stays `Navigating`.
    "OffRoute",
    /// A previously recorded deviation cleared; the trip is judged on route.
    "OnRoute",
};

/// A deviation-topic update: the detector's judgment changed between the
/// previous and the current trip state — entry, measurement drift while the
/// deviation persists, and clearing.
dictionary DeviationStatusChange {
    /// Which form the update takes.
    DeviationStatusChangeKind status_change_kind;
    /// The recorded measurement of the deviation, set exactly on the
    /// off-route kind.
    DeviationInfo? deviation_info;
};

/// A phase-transition event delivered on the phase topic.
enum NavigationPhaseChange {
    /// The trip entered the off-route phase from the navigating phase.
    "OffRouteEntered",
    /// A graded fix back on the route line lifted the deviation and returned
    /// the trip to the navigating phase.
    "OffRouteCleared",
    /// A reroute event replaced the route (delivered only when the event
    /// actually applied — an ignored reroute produces no transition).
    "RerouteApplied",
    /// The final step completed; the trip reached the arrived phase.
    "Arrived",
};

/// One raw position fix delivered to the navigation controller.
dictionary NavigationLocationUpdate {
    /// Raw position on the WGS 84 ellipsoid, before route snapping.
    GeographicCoordinate coordinate;
    /// Radius, in meters, of the 68% horizontal-accuracy envelope of this fix
    /// (null when the source does not report accuracy).
    double? horizontal_accuracy_meters;
    /// Direction of travel in degrees clockwise from true north (null when
    /// the source does not report a course).
    double? course_heading_degrees;
    /// Fix instant in seconds since the Unix epoch (null for untimed
    /// sources).
    double? timestamp_seconds;
};

/// Which step-advance strategy a navigation policy configures.
enum StepAdvancePolicyKind {
    /// Steps change only when driven programmatically (not available on this
    /// surface yet); the trip still tracks progress. The threshold fields are
    /// ignored on this kind.
    "Manual",
    /// Advances when the position fix comes within the threshold of the end
    /// of the current step, eager-advance semantics.
    "DistanceToEndOfStep",
    /// Advances when the position fix moves farther from the current step's
    /// polyline than the threshold, delayed-advance semantics.
    "DistanceFromStep",
};

/// The step-advance policy of a trip, standing in for the native decision
/// object that cannot cross the FFI.
dictionary StepAdvancePolicy {
    /// Which step-advance strategy the policy configures.
    StepAdvancePolicyKind policy_kind;
    /// Distance, in meters, of the advance threshold (the distance to the
    /// step's end, or from the step's polyline, depending on the kind).
    double advance_threshold_meters;
    /// Fixes with a reported horizontal accuracy worse than this, in meters,
    /// never advance.
    double minimum_horizontal_accuracy_meters;
};

/// Which deviation-detection strategy a deviation policy configures.
enum DeviationPolicyKind {
    /// Flags a deviation when the raw fix is farther from the remaining route
    /// line than the configured threshold, widened by the fix's reported
    /// accuracy.
    "DistanceThreshold",
};

/// The deviation-detection policy of a trip, standing in for the native
/// decision object that cannot cross the FFI.
dictionary DeviationPolicy {
    /// Which deviation-detection strategy the policy configures.
    DeviationPolicyKind policy_kind;
    /// Distance, in meters, from the remaining route line beyond which a fix
    /// is judged off route (before any accuracy widening, strictly greater).
    double deviation_threshold_meters;
};

// ---------------------------------------------------------------------------
// Round 11 (Ferrostar adoption batch 2, mechanism adopted from `FerrostarCore`
// under its BSD 3-Clause license; the expressions are our own). The
// reroute-recalculation hysteresis: three gates stand between a fully
// off-route signal and an actual recalculation request — the request-in-flight
// gate, the cooldown and the movement threshold — plus the in-flight
// readback on the trip state, so a jittery off-route bounce cannot spam the
// integrator's routing engine. The gates live at the controller seam with
// the wall clock (exactly like the speech pacing's planner), never in the
// reducer: the reducer stays a pure, clock-free event function. The gates
// close the round-9 contract gap ("no reroute timestamp on the phase
// channel") without an addition there: a reroute timestamp on the phase
// channel is not addition-only (`NavigationPhaseChange` stays a payloadless
// flat enum), so the phase channel stays untouched and the honest closure is
// the gate seam plus the `is_calculating_new_route` readback plus
// `begin_reroute_calculation`'s boolean answer.
// ---------------------------------------------------------------------------

/// The reroute-recalculation hysteresis policy of a navigation controller
/// (round 11, Ferrostar B3's mechanism). Both thresholds must be finite and
/// non-negative; the controller refuses otherwise (`InvalidNavigationInput`),
/// leaving the policy in force untouched.
dictionary ReroutePolicy {
    /// Minimum wall-clock seconds between the beginnings of two reroute
    /// calculations (the SDK default: 5).
    double recalculation_cooldown_seconds;
    /// Minimum displacement, in meters, between the raw position fix current
    /// at a calculation's beginning and the one current at the previous
    /// calculation's beginning (the SDK default: 50).
    double recalculation_movement_meters;
};
// ---------------------------------------------------------------------------
// Native surface views (round 8, slice 0). The additions below mirror the
// wasm canvas' overlay plain-object names — `routePolyline`,
// `filledPolygon`, `pointMarkers`, `trafficFlow` — as a kind enum plus
// nullable fields (the round-7 navigation payload pattern: a field-carrying
// native enum lower as a tag with `null` marking the unused payload
// fields), and expose one `MapSurfaceView` object: the FFI face of the
// `map-rendering` surface renderer, drawn by a platform view the foreign
// code owns (Kotlin `SurfaceView`, Swift `UIView`/`MKMapView`-style view,
// PRD §4.4–4.6). The renderer itself stays native (same `wgpu` machinery as
// the headless capture path); the FFI surface carries the lifecycle
// (attach/resize/detach), the overlay list, and an explicit per-frame draw.
// The platform's own render cadence drives the view — `draw` is per-frame,
// called from a display link or choreographer callback, never an SDK-
// internal loop: no SDK thread this slice, documented below.
// ---------------------------------------------------------------------------

/// Which overlay shape one descriptor configures. The kind tags follow the
/// wasm canvas' plain-object `kind` strings (the wasm seam passes the
/// lowercase camel kinds `routePolyline`/`filledPolygon`/`pointMarkers`/
/// `trafficFlow`; the FFI enum variants are the contract's own CamelCase),
/// mirroring the native `MapOverlay` payloads one to one.
enum MapOverlayKind {
    /// A routed path drawn as a thick line.
    "RoutePolyline",
    /// A filled polygon: one outer ring, ear-clip triangulated.
    "FilledPolygon",
    /// Screen-facing point markers (octagon fans).
    "PointMarkers",
    /// Congestion-colored traffic-flow lines.
    "TrafficFlow",
};

/// Solid RGBA overlay style, every channel in `[0, 1]`. Mirrors the wasm
/// canvas' `color` plain object. Null style fields keep the documented
/// `map-rendering` default style of the matching overlay kind.
dictionary OverlayColor {
    /// Red channel in `[0, 1]`.
    double red;
    /// Green channel in `[0, 1]`.
    double green;
    /// Blue channel in `[0, 1]`.
    double blue;
    /// Alpha channel in `[0, 1]` (the overlay pipeline alpha-blends).
    double alpha;
};

/// One overlay shape drawn over the map surface's tiles in every frame,
/// selected by `kind`. Each kind reads exactly its own payload fields and
/// ignores the others; a descriptor whose kind-required payload is absent
/// (or whose style field is unusable) rejects the whole `set_overlays` call
/// and keeps the previous list in force — the wasm canvas' all-or-nothing
/// semantics. Absent style fields keep the documented map-rendering
/// default style; present style fields must be usable (channels in
/// `[0, 1]`, widths and radii positive and finite). The record mirrors the
/// native `map-rendering` overlay structs field-for-field; the wasm canvas
/// plain objects carry the same names in camel case (`ringCoordinates`,
/// `pointMarkerPositions`, `pointMarkerRadiusPixels`,
/// `trafficFlowSegments`).
dictionary MapOverlayDescriptor {
    /// Which overlay shape this descriptor configures and which payload
    /// fields carry it.
    MapOverlayKind kind;

    /// `RoutePolyline`: the traversal-order polyline; consecutive equal
    /// coordinates skip their degenerate segment. Required for the kind.
    sequence<GeographicCoordinate>? coordinates;
    /// Route polyline, filled polygon and point markers: solid color
    /// override, or null for the kind's documented default.
    OverlayColor? rgba_color;
    /// Route polyline and traffic flow: line width in screen pixels
    /// (positive and finite), or null for the kind's documented default.
    double? width_pixels;

    /// `FilledPolygon`: the outer ring in traversal order without a
    /// repeated closing vertex (a repeated one is tolerated and dropped).
    /// Required for the kind.
    sequence<GeographicCoordinate>? ring_coordinates;

    /// `PointMarkers`: the geographic anchor of every marker. Required for
    /// the kind.
    sequence<GeographicCoordinate>? point_marker_positions;
    /// Point markers: marker radius in screen pixels (positive and finite),
    /// or null for the documented default.
    double? point_marker_radius_pixels;

    /// `TrafficFlow`: the flow observations to paint, one per road segment
    /// (or polyline of segments), colored by `congestion_level` — the same
    /// `TrafficFlowData` records the `fetch_traffic_flow` call returns.
    /// Required for the kind.
    sequence<TrafficFlowData>? traffic_flow_segments;
};

/// The platform window surface handle foreign code hands to
/// `MapSurfaceView::attach`: exactly one platform pointer is set, the rest
/// stay null (a null-or-ambiguous handle is refused with
/// `SdkError::InvalidRenderInput`; a null pointer is `u64 0` or null, never
/// a zeroed pointer passed through). The handle shape is a single record
/// with one nullable pointer per supported platform — one dictionary keeps
/// add-a-platform growth additive and mirrors how uniffi records lower
/// everywhere. The object behind the pointer must outlive the attached
/// view: the raw handles are the caller's validity guarantee (see
/// `wgpu::SurfaceTargetUnsafe`), and detach/drop releases the Rust side
/// first, so dropping the platform view before `detach` is the caller's
/// bug.
dictionary MapSurfaceTargetHandle {
    /// macOS: pointer to the `NSView` the surface renders into (Metal
    /// creates the layer from the view); null on the other platforms.
    u64? appkit_view_pointer;
    /// iOS: pointer to the `UIView` the surface renders into; null on the
    /// other platforms.
    u64? ios_view_pointer;
    /// Android: pointer to the `ANativeWindow` obtained from the platform
    /// `Surface` (`ANativeWindow_fromSurface`); null on the other platforms.
    u64? android_native_window_pointer;
};

/// The native map surface view: renders the camera-driven tiled map plus
/// the overlay list onto a live platform window surface through the same
/// `wgpu` machinery as the headless capture path. Construct with the
/// `MapSdk` whose registered raster-tile provider the attached surface
/// draws from (the provider resolves through the registry at attach time —
/// with no registered provider, `attach` throws
/// `SdkError::ProviderNotConfigured`).
///
/// # Lifecycle
///
/// `attach` wraps the platform handle, acquires the surface-compatible
/// graphics context, negotiates the surface format, configures the initial
/// extent and resolves the tile provider — every step's failure throws
/// (`SdkError::RenderingSurfaceUnavailable` for the GPU/surface path,
/// `InvalidRenderInput` for a bad handle or extent, `ProviderNotConfigured`
/// for the missing provider slot). `resize` (explicit, called from the
/// platform's surface-size callback) reconfigures the surface; a zero
/// extent rejects. `detach` releases the surface state until a re-attach.
/// Overlay state outlives attach/detach cycles: `set_overlays` and
/// `clear_overlays` work detached, and the next attach draws the stored
/// list.
///
/// # Draw model
///
/// Frames are explicit: the foreign platform view calls `draw` once per
/// rendered frame from its own cadence (a display link, a choreographer, a
/// resize-then-once pattern) with the current `MapCamera` value — there is
/// deliberately no SDK-internal render thread or continuous redraw loop in
/// this slice. Each draw lists the camera's visible tiles, fetches the
/// pooled misses from the surface-attached provider (budgeted per frame,
/// parent fallback like the headless path), and renders tiles then
/// overlays alpha-blended, presenting to the surface. A failed frame
/// throws and leaves the previous frame on screen. Draws on one view are
/// serialized: a draw holding the view's render state (the fetch phase
/// spans network await) reports a concurrent `draw` or `resize` with
/// `RenderingSurfaceUnavailable`, the foreign cadence retrying the next
/// frame.
interface MapSurfaceView {
    /// Builds the view over the SDK state it draws from.
    constructor(MapSdk map_sdk);

    /// Attaches the platform surface of the given initial physical-pixel
    /// extent (the extent must be non-zero; a platform view not laid out
    /// yet waits for `resize`). The wrap validates the handle, acquires
    /// the context, negotiates the format, configures and resolves the
    /// raster provider. Attaching an attached view is refused.
    [Throws=SdkError]
    void attach(MapSurfaceTargetHandle surface_handle, u32 width_pixels, u32 height_pixels);

    /// Reconfigures the surface to the new physical-pixel extent (both
    /// non-zero); a mid-draw resize surfaces the busy state and the next
    /// frame's draw or the next resize call picks it up.
    [Throws=SdkError]
    void resize(u32 width_pixels, u32 height_pixels);

    /// Detaches: releases the surface, its renderer and its scheduler.
    /// Overlay state persists; re-attach rebuilds the drawing machinery.
    /// Safe against a draw still in flight (its frame completes and the
    /// state is then dropped without being restored).
    void detach();

    /// Replaces the overlay list drawn over the tiles in every following
    /// `draw`, replacing any earlier list in order (later entries draw
    /// over earlier ones); an empty list clears. Every descriptor must be
    /// valid for its kind: no partial lists are kept.
    [Throws=SdkError]
    void set_overlays(sequence<MapOverlayDescriptor> overlay_descriptors);

    /// Clears the overlay list; following frames draw the tiles alone
    /// until `set_overlays` supplies a new list.
    void clear_overlays();

    /// Draws one frame for the camera: the camera's visible tiles fetched
    /// and rendered, overlaid, presented. Returns the frame's tile and
    /// overlay statistics (round-9 ask) so the platform render loop can
    /// report exactly what a frame drew: drawn pixels this frame come from
    /// the present and fallback tiles, while the deferred and unavailable
    /// counts are where later frames may still change pixels. Throws when
    /// the view is detached, a draw is already in flight, or the frame's
    /// rendering failed; a thrown draw leaves the previous frame on screen.
    [Async, Throws=SdkError]
    TileFrameStatistics draw(MapCamera camera);

    /// Round-10 (renderer-owned location indicator): sets the single
    /// location layer drawn after the tile pass and the overlay pass
    /// (topmost SDK layer), replacing any earlier state — mirroring
    /// `set_overlays`' all-or-nothing semantics, so a state or style that
    /// fails validation throws and keeps the earlier indicator. The state
    /// outlives attach/detach cycles like the overlay list; the styled
    /// visuals resolve to the documented map-rendering defaults for every
    /// null style field.
    [Throws=SdkError]
    void set_location_indicator(LocationIndicatorState state, LocationIndicatorStyle style);

    /// Clears the location layer; following frames draw tiles and overlays
    /// alone until `set_location_indicator` supplies new state.
    void clear_location_indicator();

    /// Round-10 (idle-settled redraw discipline): reads whether the view's
    /// presented state would not change if `draw` ran again right now —
    /// true when this camera value equals the last drawn camera, the overlay
    /// list and the location indicator are unchanged since that draw, and
    /// the last frame's tile statistics resolved no pending pixels (no
    /// fallback, deferred or unavailable tiles). A pure query: it never
    /// draws, fetches or mutates state, so a platform render pump can call
    /// it every display frame and skip `draw` when it answers true, letting
    /// the presented frame stand and resuming on any change or on the next
    /// pending detail's arrival. Throws when the view is detached.
    [Throws=SdkError]
    boolean frame_settled(MapCamera camera);
};

/// One presented frame's tile and overlay accounting, mirroring how the
/// surface renderer resolves its scheduler verdicts. Every requested tile
/// lands in exactly one of the four readiness counts, so
/// `present + fallback + deferred + unavailable` always equals `requested`.
/// The four readiness names mirror the tile scheduler's fetch-state
/// taxonomy (`Present`/`FallsBackTo`/`Deferred`/`Unavailable`).
dictionary TileFrameStatistics {
    /// Tile requests the frame resolved (per request; a request listing
    /// carrying duplicates costs one fetch but reports per request).
    u32 requested_tile_count;
    /// Tiles drawn at their own zoom level.
    u32 present_tile_count;
    /// Tiles drawn from their nearest pooled ancestor.
    u32 fallback_tile_count;
    /// Tiles whose fetch was deferred by the per-frame budget.
    u32 deferred_tile_count;
    /// Tiles the provider rejected this frame.
    u32 unavailable_tile_count;
    /// Clip-space triangles the overlay layer contributed to the frame.
    u32 overlay_triangle_count;
};

// ---------------------------------------------------------------------------
// Round-10 contract additions (addition-only; renderer-owned location
// indicator, SDK-owned speech triggers, structured diagnostics).
// ---------------------------------------------------------------------------

/// Which visual form the renderer-owned location layer draws (OQ3's
/// style-selected single layer).
enum LocationIndicatorVisualKind {
    /// Plain disc (tracking mode: no course known or no route).
    "TrackingDot",
    /// Disc plus course-directed heading wedge (navigation follow mode).
    "GuidedCourseArrow",
};

/// Style of the location layer; null fields keep the documented
/// `map-rendering` defaults (the overlay-style pattern). The disc and wedge
/// are screen-sized pixel constants (a position marker scales with the
/// screen, not the ground); the accuracy ring is ground-sized.
dictionary LocationIndicatorStyle {
    /// Which visual form this layer draws.
    LocationIndicatorVisualKind visual_kind;
    /// Disc radius in screen pixels; null keeps the default.
    double? radius_pixels;
    /// Disc fill color; null keeps the default.
    OverlayColor? rgba_color;
    /// Accuracy ring drawn around the disc (shown only when a fix carries a
    /// reported accuracy); null keeps the default (shown whenever the fix
    /// reports one that reaches the minimum draw size).
    boolean? show_accuracy_ring;
};

/// One frame's location-layer state (the presentable subset of a position
/// fix): position, optional course over ground, optional horizontal accuracy.
dictionary LocationIndicatorState {
    /// Fix position.
    GeographicCoordinate coordinate;
    /// Course over ground in degrees clockwise from true north; null when
    /// the source does not report one (the wedge renders only when set and
    /// the style carries `GuidedCourseArrow`).
    double? course_heading_degrees;
    /// Horizontal accuracy radius in meters; the accuracy ring renders when
    /// set and the style shows rings.
    double? horizontal_accuracy_meters;
};

/// When a spoken-trigger event fires and what carries it (OQ5). The
/// utterance text is the contract's composed speech text verbatim — the SDK
/// owns when a line is newsworthy, the platform owns playback and synthesis.
dictionary SpeechInstruction {
    /// The verbatim speech text (from the contract's composition; never
    /// rewritten by the trigger policy).
    string utterance_speech_text;
    /// The live distance to the maneuver in meters at emission time.
    double trigger_distance_meters;
    /// Round 11 (Ferrostar B4): the utterance identity — a stable monotonic
    /// id composed at emission, strictly increasing over every line the
    /// controller has emitted since construction and never reused.
    /// Identical text re-emitted is a new id; dedup and flush decisions stay
    /// at the platform speaker.
    u64 utterance_id;
    /// What triggered this line (a crossed band, the immediate band, an
    /// off-route recovery, or the trip's arrival).
    SpeechInstructionKind instruction_kind;
};

/// What triggered a `SpeechInstruction`.
enum SpeechInstructionKind {
    /// A trigger band was crossed this fix (the moderate or far threshold).
    "ManeuverApproach",
    /// The immediate band: the maneuver is imminent.
    "ImmediateManeuver",
    /// Guidance just recovered from off-route (guidance resumed).
    "OffRouteRecovery",
    /// The trip arrived.
    "Arrival",
};

/// Severity of an SDK diagnostic event, mirroring the `tracing` levels the
/// crates log under.
enum SdkLogSeverity {
    /// Very fine-grained frame-path instrumentation (GPU-hot path logs live
    /// here, only when a listener subscribed at this severity).
    "Trace",
    /// Fine-grained internal state transitions.
    "Debug",
    /// Coarse-grained progress (the default minimum for a subscription).
    "Info",
    /// A problem this event explains.
    "Warn",
    /// A failure this event explains.
    "Error",
};

/// One structured SDK internal log event, as delivered to a foreign
/// diagnostics listener or read back by a flush. Coordinates in event
/// messages are rounded at the sink (never the API key, and never raw
/// coordinates at full precision).
dictionary DiagnosticLogEvent {
    /// Wall-clock milliseconds since the Unix epoch, as the ring recorded
    /// the event.
    u64 timestamp_milliseconds_since_epoch;
    /// The event's severity.
    SdkLogSeverity severity;
    /// The named per-subsystem target that logged it, e.g. "tile_fetch".
    string subsystem_target;
    /// The one-line already-scrubbed message.
    string message_line;
};

/// Diagnostics contract implemented by foreign code, receiving the SDK's
/// structured internal log events fire-and-forget (the Mozilla
/// application-services ADR 0009 sink pattern): the SDK copies an event
/// across the FFI and moves on immediately without awaiting the listener —
/// a slow listener can never slow the events it observes, and a frame-path
/// event never blocks on a foreign callback.
[Trait, WithForeign]
interface SdkDiagnosticEventListener {
    /// Receives one structured event (severity-gated at the subscription).
    void on_diagnostic_event(DiagnosticLogEvent event);
};

// ---------------------------------------------------------------------------
// Round-12 provider-failure diagnostics (addition-only; the owner ruling of
// 2026-10-29 on unclassified foreign provider failures). Foreign
// implementations of the `[Trait, WithForeign]` provider contracts fail in
// two shapes: the typed shape (the foreign code threw one of the contract's
// `SdkError` variants, which round-trips one-to-one) and the unclassified
// shape (the foreign code failed some other way, which the Rust conversion
// reports under `ProviderRequestRejected` with its verbatim reason — the
// honest but thin detail the seam exists for). Every failed foreign-callback
// provider call is recorded on the seam with the contract and the callback
// method it occurred on, a timestamp, the classification, and the verbatim
// failure text; `MapSdk::latest_provider_failure_diagnostics` reads the
// bounded ring back.
//
// Honest limitation of the classification: the unclassified shape is
// recognized by the unclassified-callback conversion's verbatim detail text
// (one Rust-side constant shared by the conversion and the adapter-side
// classifier), so a foreign implementation that itself throws
// `ProviderRequestRejected` carrying exactly that conversion phrasing is
// indistinguishable and lands as unclassified. Richer typed payloads (a
// provider status code, a retriable flag) would need a channel the frozen
// provider traits cannot carry without a contract change; until one exists
// the foreign side's only input to the seam is the failure text it throws,
// which the entries carry verbatim — no data is parsed or invented.
// ---------------------------------------------------------------------------

/// Which shape of foreign provider failure a diagnostic captured
/// (`ProviderFailureDiagnostic::failure_classification`).
enum ProviderFailureClassification {
    /// The foreign implementation failed without a mapped error (an
    /// exception the contract does not spell): the failure text is the
    /// uniffi callback machinery's verbatim reason for it.
    "UnclassifiedCallbackFailure",
    /// The foreign implementation threw one of the contract's `SdkError`
    /// variants, which round-trips typed through the seam: the failure text
    /// is the thrown binding error's verbatim display form.
    "MappedContractError",
};

/// One failed foreign-callback provider call, as retained in
/// `MapSdk::latest_provider_failure_diagnostics`'s bounded per-object ring.
/// Everything carried here is knowable at the adapter boundary that bridges
/// the foreign implementation onto the native provider registry; no data is
/// invented beyond it.
dictionary ProviderFailureDiagnostic {
    /// Wall-clock milliseconds since the Unix epoch, as the seam recorded
    /// the failure.
    u64 timestamp_milliseconds_since_epoch;
    /// Which failure shape this entry captured.
    ProviderFailureClassification failure_classification;
    /// The provider contract the foreign code implements, e.g.
    /// `GeocodingProvider`.
    string provider_contract_name;
    /// The contract method whose call failed, e.g. `forward_geocode`.
    string provider_method_name;
    /// The verbatim failure text: the uniffi callback machinery's reason for
    /// the unclassified shape, or the thrown binding error's display form
    /// for the mapped shape.
    string failure_detail;
};

Contributing Conventions — Rustic Maps SDK

The project guides set the repository’s behavioral rules. This document translates them into per-language practice, including the generated binding surfaces.

1. No abbreviations in public identifiers

Every public identifier is spelled out, and stays concise yet precise:

GoodBad
GeographicCoordinateGeoCoord
RouteStepRStep
instruction_display_textinstrDispTxt
fetch_traffic_incidentsfetchTI
time_to_live_secondsttl

The standard forms SDK, URL, HTTP, wasm, and MVT are accepted in documentation text, but never as identifier shorthand.

2. SOLID mapping

PrincipleRust coreKotlin binding surfaceSwift binding surface
Single responsibilityone crate per concernone class per screen/featureone struct per screen/feature
Open–closeda new vendor means a new *Provider impl; the core stays untoucheda new capability means a new composable or view-modelfinal class + protocol; no forks in existing code
Liskov substitutionmocks in geospatial-traits::mock must satisfy every tested consumersubclasses and refapps honor suspend contractsSwiftUI view models honor async throws contracts
Interface segregationten narrow contracts, not one mega-traitexpose role-targeted protocols, not god objectsrole-targeted protocols
Dependency inversioncrates depend on geospatial-traits, never on a providerview models depend on injected SDK protocolsdependency injection through initializers

3. Errors

  • Rust: every failure is geospatial_types::SDKError. Crate code uses no unwrap, no expect, and no raw String errors, and panics never cross the FFI boundary.
  • Kotlin: UniFFI maps the UDL contract to typed exceptions, and the refapps surface them as sealed UI states.
  • Swift: UniFFI maps the contract to typed Swift errors thrown from async methods.

4. Property- and fixture-first testing

  • Coordinate, camera, and projection math gets proptest suites.
  • Every external payload gets a recorded fixture and a parser test.
  • Reference applications double as UI-test hosts (Espresso / XCUITest / Detox).

How the demo loop runs

How the WebAssembly harness behind the site’s future demo is built, served, and tested. This page stays deliberately short: it is the map of the loop, not its internals.

The three steps

  1. Build. The map-web-bindings crate compiles to wasm32-unknown-unknown and the wasm-bindgen CLI emits the ES module the pages import. The CLI’s version and the crate’s wasm-bindgen dependency are a locked pair: one bump regenerates the emit.
  2. Serve. A small dependency-free static server serves the generated pkg/ plus a set of allow-listed same-origin routes: recorded raster-tile imagery, the six committed golden images, the shared reference-scenes demo artifact, and the compiled offline-routing archive. The browser never touches an external network; the loop is fully offline against recorded fixtures, and the camera’s default anchor sits inside the recorded tile neighborhood, so the page shows real imagery immediately.
  3. Test. The Playwright suite runs headless over Chromium: version and projection probes, round-trip projection, antimeridian tile listing, a WebGPU canvas smoke, and camera readouts. A separate contract-sync test (no browser, no npm) parses the frozen UDL, the hand-reviewed TypeScript mirror, and the wasm exports, and asserts the three agree.

What keeps the parity honest

The browser canvas mirrors the native renderer with one rule: same WGSL text, same vertex layout, same index pattern. Tests render the same camera against committed golden reference images in both places, so a browser-vs-native pixel compare stays meaningful.

Known limitations

  • The static server binds a fixed localhost port, so two harness sessions cannot share it; a test-only environment-variable port override is the workaround.
  • The GPU canvas draws only through WebGPU. Its documented degradation path is a 2D overlay grid, never WebGL.
  • WebKit runs resolve WebGPU nowadays, so a WebKit run exercises the GPU path, not the 2D fallback.