TomTom Orbis Maps provider for the MapConductor unified mapping API, built on TomTom Orbis Maps Display SDK.
MapConductor provides a unified API for Android Jetpack Compose. You can use TomTom with Compose, but you can also switch to other Maps SDKs (such as MapLibre, Google Maps, and so on), anytime. Even using the wrapper API, you can still access the native TomTom map if you want.
TomTomMapView supports the same MapConductor content types as the iOS ios-for-tomtom
provider: Marker, Polyline, Polygon, Circle, GroundImage, RasterLayer and InfoBubble.
InfoBubble is drawn through the shared MapViewBase, so this module carries no
InfoBubble-specific code.
Dependency: com.tomtom.sdk.maps:map-display-standard (the version is managed by
tomtomMaps in gradle/libs.versions.toml).
https://mapconductor.com/setup/android/tomtom/
- Get a TomTom Orbis Maps API key from the TomTom Developer Portal.
- Add the key to your app's
AndroidManifest.xml.
<meta-data
android:name="TOMTOM_API_KEY"
android:value="YOUR_TOMTOM_API_KEY" />- Add the TomTom Maven repository to
settings.gradle.kts.
maven { url = uri("https://repositories.tomtom.com/artifactory/maven") }val mapState = rememberTomTomMapViewState(
mapDesign = TomTomMapDesign.Standard,
cameraPosition = MapCameraPosition(position = GeoPoint(52.3676, 4.9041), zoom = 11.0),
)
TomTomMapView(state = mapState, modifier = Modifier.fillMaxSize()) {
Marker(
MarkerState(
position = GeoPoint(52.3676, 4.9041),
icon = DefaultMarkerIcon().copy(label = "Amsterdam"),
),
)
}See sample-app/ for a runnable example.
TomTomMapDesign exposes the TomTom Orbis styles: Standard, Driving, Satellite.
Marker (including clustering and tile-rendered large marker sets), Polyline, Polygon (holes supported), Circle, GroundImage, RasterLayer and InfoBubble — the same unified API as the other providers.
TomTomMapView [docs]
@Composable
fun MapExample() {
val initCameraPosition = MapCameraPosition(
position = GeoPoint(
latitude = 34.091,
longitude = -117.886,
),
zoom = 9.0,
tilt = 60.0,
bearing = 30.0,
)
val mapViewState = rememberTomTomMapViewState(
cameraPosition = initCameraPosition,
)
TomTomMapView(mapViewState)
}Marker [docs]
@Composable
fun MarkerExample() {
val markerState = remember { MarkerState(
position = GeoPoint(...),
icon = DefaultMarkerIcon().copy(
label = "TomTom",
),
onClick = {
it.animate(MarkerAnimation.Bounce)
},
) }
TomTomMapView(...) {
Marker(markerState)
}
}InfoBubble [docs]
@Composable
fun InfoBubbleExample() {
var selectedMarker by remember { mutableStateOf<MarkerState?>(null) }
val markerState = remember { MarkerState(
...,
onClick = {
selectedMarker = it
},
) }
TomTomMapView(...) {
Marker(markerState)
selectedMarker?.let {
InfoBubble(
marker = it,
) {
Text("Hello, world!")
}
}
}
}Circle [docs]
@Composable
fun CircleExample() {
val circleState = remember { CircleState(
center = GeoPoint(...),
radiusMeters = 50.0,
fillColor = Color.Blue.copy(alpha = 0.5f),
onClick = {
it.state.fillColor = Color.Red.copy(alpha = 0.5f)
}
) }
TomTomMapView(...) {
Circle(circleState)
}
}Polyline [docs]
@Composable
fun PolylineExample() {
val polylineState = remember { PolylineState(
points = airports,
strokeColor = Color.Blue.copy(alpha = 0.5f),
strokeWidth = 4.dp,
geodesic = true,
) }
TomTomMapView(...) {
Polyline(polylineState)
}
}Polygon [docs]
@Composable
fun PolygonExample() {
val polygonState = remember { PolygonState(
points = goryokaku,
strokeColor = Color.Blue.copy(alpha = 0.5f),
fillColor = Color.Red.copy(alpha = 0.7f),
) }
TomTomMapView(...) {
Polygon(polygonState)
}
}@Composable
fun PolygonHoleExample() {
val polygonState =
remember {
PolygonState(
points = listOf(...),
holes = listOf(
listOf(...),
listOf(...),
),
fillColor = Color(0xCC787880),
strokeColor = Color.Red,
strokeWidth = 2.dp,
)
}
TomTomMapView(...) {
Polygon(polygonState)
}
}GroundImage [docs]
@Composable
fun GroundImageExample() {
val groundImageState = remember { GroundImageState(
bounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(...),
northEast = GeoPoint.fromLatLong(...),
),
image = image,
opacity = 0.5f,
) }
TomTomMapView(state = mapViewState) {
GroundImage(groundImageState)
}
}| File | Role |
|---|---|
TomTomMapView.kt |
Compose entry point / controller construction |
TomTomMapViewController.kt |
Central controller for camera, markers and design |
TomTomMapViewStateImpl.kt |
rememberTomTomMapViewState and state retention |
TomTomMapViewHolder.kt |
MapView / TomTomMap wrapper, coordinate conversion |
TomTomMapDesign.kt |
Style (map design) definitions |
MapCameraPosition.kt |
Camera position conversions |
GeoPoint.kt / GeoRectBounds.kt |
Coordinate type conversions |
marker/ |
Native marker rendering and events |
polyline/ / polygon/ / circle/ |
Vector overlay rendering (geodesics use the shared core interpolation) |
groundimage/ |
Ground image as an image-backed Polygon |
raster/TomTomStyleComposer.kt |
Builds a composed style JSON with injected raster sources |
raster/TomTomRasterLayerSink.kt |
Applies raster layer state to the composed style |
zoom/ZoomAltitudeConverter.kt |
Zoom ↔ altitude conversion |
-
Compose embedding:
MapOptions(renderToTexture = true)is set. The default SurfaceView rendering conflicts with the measurement pass of Compose'sSubcomposeLayout, which shifts the drawing position. -
Sample theme: TomTom's UI overlays (logo, compass, and so on) log a warning recommending an AppCompat-family theme (rendering itself still works). For production, prefer
Theme.AppCompat/Theme.MaterialComponents. -
fitBounds: delegates toCameraOptionsFactory.lookAt(bounds, zoom = null, padding). Passingzoom = nullleaves the zoom to TomTom's own calculation;padding(px) is passed through as is. -
Polygon holes / geodesics: geodesic interpolation of vertices, antimeridian splitting and multi-hole unioning reuse the shared core utilities (
WGS84Geodesic/buildUnwrappedPolygonRings/unionHoles) so the resulting shape matches the other providers. TomTom connects coordinates with straight lines, so geodesics are approximated by the interpolated coordinate list. -
Ground images: drawn as an image-backed Polygon with
PolygonOptions.isImageOverlayenabled (the image is stretched over the polygon's bounding rectangle). Bounds, image and color changes update the existing native Polygon in place, as with markers — no remove-then-recreate. -
Raster layers: the TomTom Map Display SDK has no public API for adding sources/layers at runtime, so a style JSON with raster sources injected into the base style is composed and loaded as a local
file://URI (raster/TomTomStyleComposer.kt). -
Marker updates / movement: TomTom's
Markerhas mutablecoordinate/isVisibleand asetPinImage(), so position, visibility and icon updates mutate the existing instance rather than removing and recreating it. This matters: removing and recreating every frame during a drag blocks the main thread and causes an ANR. -
Drag (custom implementation): TomTom has no native marker drag, so it is implemented by handling
MotionEventon theMapViewdirectly (the same approach as the ArcGIS module). A touch on a draggable marker owns the gesture and suppresses map panning; movement beyond the slop starts the drag and the marker follows the finger; releasing commits it. Releasing without moving is treated as a click (onClick). The finger's screen coordinate is converted withTomTomMap.coordinateForPoint, and the marker is repositioned by updatingMarker.coordinate. -
Map designs use the classic
StandardStyles.TomTomMaps.*, notTomTomOrbisMaps.*: loading an Orbis descriptor (tomtom://styles/standard/orbismap/browsing) makesloadStylereportonSuccess()while nothing is drawn — the map is left as a flat background colour. Orbis vector maps require a separate entitlement on the API key; without it the failure is silent rather than an error (api.tomtom.com/maps/orbis/map-display/...answers 400 while the classicapi.tomtom.com/map/1/tile/basic/main/...answers 200). Verified on a Pixel 5a with map-display 2.4.x. Switch these back toTomTomOrbisMaps.*once an Orbis-entitled key is available. -
★ The app must pin OkHttp to 4.x. TomTom Orbis Maps SDK 2.4.x is built against OkHttp 4.x. With OkHttp 5.x on the classpath, switching from the satellite (raster) style back to a vector style leaves the map as a flat background colour —
loadStylestill reportsonSuccess(),map.layers.sizestill reports the full 137 layers, the camera is unchanged, and no exception or HTTP error is logged. Panning does not recover it. Vector→vector (Standard ⇄ Driving) is unaffected; only raster→vector breaks.The trigger is transitive: ArcGIS Maps SDK for Kotlin 300.1.0 brings
okhttp 5.3.2, and Gradle's highest-version-wins resolution lifts TomTom's4.12.0to5.3.2. That is why the symptom only ever appeared in apps hosting several map SDKs, and never in a TomTom-only app.Verified both ways on a Pixel 5a with map-display 2.4.2, in a TomTom-only sample app:
ArcGIS on the classpath resolved OkHttp satellite → standard no 4.12.0 (default) renders no 5.3.2 (forced) blank yes 5.3.2 (default) blank yes 4.12.0 (pinned) renders A library cannot pin this for its consumers, so the app must do it:
configurations.configureEach { resolutionStrategy { force("com.squareup.okhttp3:okhttp:4.12.0") } }example-app/build.gradle.ktsand the React Native example carry exactly this. A report ready to send to TomTom is indocs/tomtom-okhttp5-raster-to-vector-report.md.
Apache License 2.0