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

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.