50,000 free calls a month, card-free. Get an API key →

Documentation menu
docs / android · raw .md

Android API reference

The public Kotlin surface of ai.mapmap:core 0.2.0. Install coordinates, credentials and the root-repository Gradle rule are on SDKs & installation; this page is the class-by-class reference.

Requires minSdk 26. The library is a thin, idiomatic Kotlin layer over the Rust navigation core (libsn_nav_core.so, packaged for arm64-v8a, armeabi-v7a and x86_64). The UniFFI-generated bindings live in uniffi.mapmap (and uniffi.ferrostar, served by the same native library); everything under ai.mapmap is the supported surface. Types named below without a package (e.g. TerritoryInfo, AdrProfile, GuidanceUpdate) are generated records/enums in uniffi.mapmap.

PackageClasses
ai.mapmapMapMap
ai.mapmap.territoryTerritoryStore
ai.mapmap.routingOfflineRouting, OfflineRouter, ValhallaMobileRouter, ValhallaConfigWriter
ai.mapmap.adrAdrCompliance
ai.mapmap.navNavigationEngine, NavigationState, GuidanceDisplay, LocationProvider, LocationSample, AndroidLocationProvider, ReplayLocationProvider, VoiceGuidance, VoiceProsody
ai.mapmap.replayDriveCorpus, DriveHeader, DriveFix, CorpusFormatException

ai.mapmap.MapMap

Entry object.

kotlin
object MapMap {
    fun ensureLoaded()
    fun defaultGuidanceConfig(): GuidanceConfig
    fun defaultAdrProfile(): AdrProfile
}
  • ensureLoaded(): eagerly load the native core and verify the bindings match it. Optional (the first native call loads lazily), but calling it at app start surfaces packaging problems as an immediate UnsatisfiedLinkError instead of a mid-drive crash.
  • defaultGuidanceConfig(): a GuidanceConfig with the core's defaults.
  • defaultAdrProfile(): an AdrProfile with the core's defaults: EU maximum authorised dimensions (Council Directive 96/53/EC), no dangerous goods.

ai.mapmap.territory.TerritoryStore

Coroutine-friendly store of signed offline territory packages, wrapping the core's TerritoryManager. Every package is ed25519-signature and BLAKE3-layer-hash verified before promotion; verification is fail-closed (a package that fails leaves no trace on disk). All calls hop to the constructor's dispatcher (Dispatchers.IO by default); installs read whole archives and must never run on the main thread. Implements AutoCloseable.

kotlin
class TerritoryStore(
    rootDir: File,
    verifyingKeyHex: String,               // factory public key, 64 hex; pin at build time
    dispatcher: CoroutineDispatcher = Dispatchers.IO,
) : AutoCloseable {
    val territories: StateFlow<List<TerritoryInfo>>

    suspend fun refresh(): List<TerritoryInfo>
    suspend fun install(snpkg: File): TerritoryInfo
    suspend fun installDirectory(packageDir: File): TerritoryInfo
    suspend fun setActive(territoryId: String)
    suspend fun active(): String?
    suspend fun activeTerritory(): TerritoryInfo?
    suspend fun layerPath(territoryId: String, kind: MapLayerKind): File
    suspend fun remove(territoryId: String)
    suspend fun planUpdate(newManifestJson: String): UpdatePlan
    suspend fun checkForUpdate(
        territoryId: String,
        indexJson: String,
        indexSigBase64: String,
        fetcher: LayerFetcher,
    ): AvailableUpdate?
    suspend fun applyUpdate(
        territoryId: String,
        newManifestJson: String,
        newManifestSigBase64: String,
        fetcher: LayerFetcher,
    ): UpdateResult
    override fun close()
}
  • territories: hot StateFlow of the installed set; refreshes after every mutating call and on refresh().
  • install(snpkg): install a packed .snpkg archive; throws NavCoreException.Verification if the signature or any layer hash does not verify. installDirectory installs an unpacked package directory under the same rules.
  • layerPath(id, kind): absolute path of one layer, e.g. MapLayerKind.ValhallaTiles for the on-device router, MapLayerKind.Pmtiles for MapLibre.
  • remove(id): remove an installed territory (clears the active marker if needed).
  • checkForUpdate(...): check the signed update channel. Fail-closed: the channel-index signature is verified before the fetcher is ever invoked. indexJson/indexSigBase64 are the exact served bytes of index.json and index.json.sig. Returns null when already on the channel's latest version.
  • applyUpdate(...): differential update; unchanged layers hard-linked, changed layers fetched through your LayerFetcher, staged package fully re-verified, atomic swap. Any failure leaves the install untouched.
  • LayerFetcher is the generated callback interface: one method, fetch(relPath: String, expectedBlake3: String, destPath: String), synchronous by contract (called from a background thread); expectedBlake3 is advisory, the core re-verifies everything.

See the worked install/update flows in territories.

ai.mapmap.routing

OfflineRouting

On-device route computation. The core builds the Valhalla request (merging ADR dangerous-goods and dimensional costing options), the injected router executes it against local tiles, and the core parses the response.

kotlin
object OfflineRouting {
    suspend fun route(
        router: LocalRouter,
        waypoints: List<RoutePoint>,          // at least two, in visit order
        costing: CostingModel = CostingModel.AUTO,
        adr: AdrProfile? = null,
        dispatcher: CoroutineDispatcher = Dispatchers.Default,
    ): RouteResult

    fun buildRequest(
        waypoints: List<RoutePoint>,
        costing: CostingModel = CostingModel.AUTO,
        adr: AdrProfile? = null,
    ): String                                  // raw Valhalla request JSON

    fun parseResponse(responseJson: String): RouteResult
    fun requireCompatibleCosting(costing: CostingModel, adr: AdrProfile?)
}
  • ADR profiles ride on truck costing: supplying adr with any costing other than CostingModel.TRUCK throws IllegalArgumentException.
  • route throws NavCoreException.Routing when the engine returns a Valhalla error body (no route, no tiles for the area).
  • RouteResult.valhallaResponseJson feeds NavigationEngine.

OfflineRouter

The on-device engine interface, the core's LocalRouter callback with a Kotlin name: execute a Valhalla request JSON synchronously, return the raw response JSON.

kotlin
interface OfflineRouter : LocalRouter   // fun route(requestJson: String): String

ValhallaMobileRouter

OfflineRouter backed by valhalla-mobile's on-device engine. valhalla-mobile is a compileOnly dependency: apps using this class must declare io.github.rallista:valhalla-mobile themselves.

kotlin
class ValhallaMobileRouter(configPath: String) : OfflineRouter {
    companion object {
        fun fromConfig(configFile: File): ValhallaMobileRouter
        fun fromTileDir(tileDir: File, scratchDir: File): ValhallaMobileRouter
    }
}

fromTileDir is the usual entry point: point it at the territory's MapLayerKind.ValhallaTiles layer and a scratch dir (e.g. context.cacheDir) and it writes the valhalla.json for you.

ValhallaConfigWriter

Writes the minimal valhalla.json the on-device engine needs.

kotlin
object ValhallaConfigWriter {
    const val DEFAULT_MAX_CACHE_BYTES: Long = 268_435_456  // 256 MiB
    fun configJson(tileDir: File, maxCacheBytes: Long = DEFAULT_MAX_CACHE_BYTES): String
    fun write(tileDir: File, configFile: File,
              maxCacheBytes: Long = DEFAULT_MAX_CACHE_BYTES): File
}

ai.mapmap.adr.AdrCompliance

On-device ADR 8.6.4 dangerous-goods enforcement (pre-route via OfflineRouting.route(adr = …), en-route via checkTunnel).

kotlin
object AdrCompliance {
    fun checkTunnel(profile: AdrProfile, category: AdrTunnelCategory): AdrDecision
    fun forbiddenCategories(profile: AdrProfile): List<AdrTunnelCategory>
    fun isTunnelRestricted(profile: AdrProfile): Boolean
    fun validateProfile(profile: AdrProfile): List<String>
}
  • checkTunnel: may this vehicle pass a tunnel of category? A AdrDecision.Blocked carries a human-readable reason citing ADR 8.6.4.
  • forbiddenCategories: all categories the vehicle is barred from, ascending; empty when unrestricted.
  • validateProfile: physical-plausibility check; returns human-readable problems (empty = valid). Pure Kotlin, safe without the native library.

ai.mapmap.nav

Turn-by-turn guidance over one computed route, adapting the core's Ferrostar-based GuidanceSession to coroutines. An engine is bound to a single route; on off-route, compute a new route and build a new engine.

kotlin
class NavigationEngine(
    routeResponseJson: String,
    config: GuidanceConfig = GuidanceConfig(),
    dispatcher: CoroutineDispatcher = Dispatchers.Default,
) {
    constructor(route: RouteResult,
                config: GuidanceConfig = GuidanceConfig(),
                dispatcher: CoroutineDispatcher = Dispatchers.Default)

    fun navigate(
        provider: LocationProvider,
        manualAdvance: Flow<Unit> = emptyFlow(),
    ): Flow<NavigationState>

    fun totalSteps(): UInt
}
  • navigate is a cold flow (each collection starts a fresh session at the first route step) and completes after arrival. manualAdvance is the tunnel/no-GNSS escape hatch: emit Unit to force-advance a step.
  • Throws NavCoreException if the route response cannot start a session.
kotlin
data class NavigationState(val fix: LocationSample, val update: GuidanceUpdate) {
    val isArrived: Boolean       // update is GuidanceUpdate.Arrived
    val needsReroute: Boolean    // update is GuidanceUpdate.OffRoute
}

object GuidanceDisplay {
    fun instruction(update: GuidanceUpdate): String?
    fun severity(update: GuidanceUpdate): InstructionSeverity
    fun formatDistance(metres: Double): String
    fun summarise(update: GuidanceUpdate): String
}

GuidanceUpdate is the core's sealed state: Navigating (carries currentInstruction, distanceToNextManeuverM, distanceRemainingM, severity and the road-snapped fix snapped), Arrived, or OffRoute (carries deviationM). GuidanceDisplay is pure Kotlin (unit-testable on any JVM).

LocationProvider and LocationSample

kotlin
data class LocationSample(
    val lat: Double,
    val lon: Double,
    val timestampMs: Long,                    // ms since the Unix epoch
    val speedMps: Double? = null,
    val bearingDeg: Double? = null,           // [0, 360), clockwise from true north
    val horizontalAccuracyM: Double? = null,  // 1-sigma; gates step advance
)

interface LocationProvider {
    val locations: Flow<LocationSample>       // collected once per session
}

Any cold Flow of samples in ascending timestamp order qualifies.

AndroidLocationProvider

Live provider on the platform location stack; no Google Play Services dependency. Uses LocationManager.FUSED_PROVIDER on API 31+, falling back to GPS_PROVIDER. Requires ACCESS_FINE_LOCATION granted before the flow is collected; the SDK manifest declares no permissions.

kotlin
class AndroidLocationProvider(
    context: Context,
    minIntervalMs: Long = 1_000L,   // 1 Hz default
    minDistanceM: Float = 0f,
) : LocationProvider

ReplayLocationProvider

Replays a .drive.jsonl corpus (see File formats) as a LocationProvider: the QA/simulator path.

kotlin
class ReplayLocationProvider(
    corpus: DriveCorpus,
    timeScale: Double = 1.0,    // 1.0 real time, 2.0 double speed, 0.0 = as fast as possible
    epochStartMs: Long = 0L,    // epoch ms assigned to the first fix
) : LocationProvider

VoiceGuidance

Speaks guidance updates through Android TextToSpeech, with severity-scaled prosody, earcons and audio focus handled for you. Feed it from the same flow that drives your UI.

kotlin
class VoiceGuidance(
    context: Context,
    language: String? = null,   // BCP 47, e.g. "en-GB"; null = device default
    baseRate: Float = 1.0f,     // base speech rate (1.0 = normal)
) : AutoCloseable {
    var muted: Boolean          // toggle freely mid-drive
    var volume: Float           // 0.0–1.0, speech and earcons
    fun handle(update: GuidanceUpdate)
    fun handle(state: NavigationState)
    override fun close()        // release TTS engine, earcon player, audio focus
}

VoiceProsody (speech rate/pitch per InstructionSeverity) and the startup/audio-focus helpers (TtsStartup, AudioFocusPolicy) are public for testing and custom voice stacks.

ai.mapmap.replay.DriveCorpus

Parser for the .drive.jsonl replay corpus format; schema in File formats.

kotlin
data class DriveCorpus(val header: DriveHeader, val fixes: List<DriveFix>) {
    companion object {
        const val FORMAT_VERSION: Int = 1
        fun parse(jsonl: String): DriveCorpus   // throws CorpusFormatException
    }
}

data class DriveHeader(val formatVersion: Int, val name: String,
                       val description: String, val routeFixture: String, val seed: Long)

data class DriveFix(val tMs: Long, val lat: Double, val lon: Double,
                    val speedMps: Double?, val bearingDeg: Double?,
                    val horizontalAccuracyM: Double?)

The parser enforces the same invariants as the Rust reference implementation: supported format version, non-empty name, at least one fix, strictly increasing t_ms, sane coordinate/channel ranges.

Putting it together

kotlin
// Territory → router → route → guidance:
val tiles = store.layerPath(territoryId, MapLayerKind.ValhallaTiles)
val router = ValhallaMobileRouter.fromTileDir(tiles, context.cacheDir)

val route = OfflineRouting.route(
    router = router,
    waypoints = listOf(RoutePoint(52.2, 0.13), RoutePoint(52.2045, 0.1388)),
    costing = CostingModel.TRUCK,
    adr = AdrProfile(heightM = 4.0, grossWeightT = 40.0,
                     hazmat = true, tunnelCode = AdrTunnelCode.C),
)

NavigationEngine(route).navigate(AndroidLocationProvider(context))
    .collect { state ->
        ui.instruction = GuidanceDisplay.summarise(state.update)
        if (state.needsReroute) recalculate()
    }

Android Auto (ai.mapmap:car, ai.mapmap:car-maplibre)

Beta modules (0.2.0) that put the same guidance state on the car's display: NavigationManagerBridge for the Car App Library's NavigationManager, TurnByTurnNotificationManager, NavigationIntentParser, the NavigationTemplateBuilder template layer, and CarMapViewHost for projecting a MapLibre MapView onto the car surface. Quickstart, entitlement and Play-review gates on CarPlay & Android Auto.

Next steps