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:
-
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 } -
The Kotlin declaration beside your module wiring:
external fun nativeWindowPointerFromSurface(surface: Surface): Long -
The surface host: construct the view over the SDK, attach once the surface exists, then pump one
drawper 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.