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.