Documentation menu
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.
| Package | Classes |
|---|---|
ai.mapmap | MapMap |
ai.mapmap.territory | TerritoryStore |
ai.mapmap.routing | OfflineRouting, OfflineRouter, ValhallaMobileRouter, ValhallaConfigWriter |
ai.mapmap.adr | AdrCompliance |
ai.mapmap.nav | NavigationEngine, NavigationState, GuidanceDisplay, LocationProvider, LocationSample, AndroidLocationProvider, ReplayLocationProvider, VoiceGuidance, VoiceProsody |
ai.mapmap.replay | DriveCorpus, DriveHeader, DriveFix, CorpusFormatException |
ai.mapmap.MapMap
Entry object.
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 immediateUnsatisfiedLinkErrorinstead of a mid-drive crash.defaultGuidanceConfig(): aGuidanceConfigwith the core's defaults.defaultAdrProfile(): anAdrProfilewith 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.
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: hotStateFlowof the installed set; refreshes after every mutating call and onrefresh().install(snpkg): install a packed.snpkgarchive; throwsNavCoreException.Verificationif the signature or any layer hash does not verify.installDirectoryinstalls an unpacked package directory under the same rules.layerPath(id, kind): absolute path of one layer, e.g.MapLayerKind.ValhallaTilesfor the on-device router,MapLayerKind.Pmtilesfor 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/indexSigBase64are the exact served bytes ofindex.jsonandindex.json.sig. Returnsnullwhen already on the channel's latest version.applyUpdate(...): differential update; unchanged layers hard-linked, changed layers fetched through yourLayerFetcher, staged package fully re-verified, atomic swap. Any failure leaves the install untouched.LayerFetcheris the generated callback interface: one method,fetch(relPath: String, expectedBlake3: String, destPath: String), synchronous by contract (called from a background thread);expectedBlake3is 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.
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
adrwith any costing other thanCostingModel.TRUCKthrowsIllegalArgumentException. routethrowsNavCoreException.Routingwhen the engine returns a Valhalla error body (no route, no tiles for the area).RouteResult.valhallaResponseJsonfeedsNavigationEngine.
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.
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.
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.
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).
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 ofcategory? AAdrDecision.Blockedcarries 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
NavigationEngine
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.
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
}
navigateis a cold flow (each collection starts a fresh session at the first route step) and completes after arrival.manualAdvanceis the tunnel/no-GNSS escape hatch: emitUnitto force-advance a step.- Throws
NavCoreExceptionif the route response cannot start a session.
NavigationState and GuidanceDisplay
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
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.
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.
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.
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.
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
// 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
- SDKs & installation: install coordinates, credentials, the verifying-key trust anchor
- Territories: the signed package and update-channel model this SDK consumes
- File formats: channel index, package layout,
.drive.jsonl - CarPlay & Android Auto: the in-car surface