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

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.