Skip to content

Add images and icons

A SymbolLayer draws an icon at each feature in its source. The image expression supplies the icon, from a Compose painter, an ImageBitmap, or the base style’s sprite.

Pass a painter to image(). Use painterResource to load an app resource, or supply your own painter:

App.kt
val state = rememberMapState {
val stations =
rememberGeoJsonSource(GeoJsonData.Uri(Res.getUri("files/data/amtrak_stations.geojson")))
SymbolLayer(
id = "station-icons",
source = stations,
iconImage = image(painterResource(Res.drawable.map_24px), size = DpSize(24.dp, 24.dp)),
)
}
MaplibreMap(state = state)

Base styles usually bundle a sprite: a named set of icons. Pass the icon name to image():

App.kt
val state = rememberMapState {
SymbolLayer(id = "station-markers", source = stations, iconImage = image("marker"))
}
MaplibreMap(state = state)

An ImageSource positions one image on the map by its four corner coordinates. A RasterLayer draws it:

App.kt
val state = rememberMapState {
val corners =
PositionQuad(
topLeft = Position(longitude = -74.0178, latitude = 40.7040),
topRight = Position(longitude = -74.0118, latitude = 40.7100),
bottomRight = Position(longitude = -74.0060, latitude = 40.7067),
bottomLeft = Position(longitude = -74.0120, latitude = 40.7006),
)
val plan = rememberImageSource(position = corners, uri = Res.getUri("files/castello-plan.jpg"))
RasterLayer(id = "castello-plan", source = plan, opacity = const(0.8f))
}
MaplibreMap(state = state)

A style can reference an icon that its sprite does not contain. Set MapState.missingImageResolver to supply one. The map calls the resolver with the icon’s ID and adds the ResolvedStyleImage that it returns:

App.kt
val mapState = rememberMapState()
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
DisposableEffect(mapState, fallback, density, layoutDirection) {
mapState.missingImageResolver = {
ResolvedStyleImage.fromPainter(fallback, density, layoutDirection)
}
onDispose { mapState.missingImageResolver = null }
}
MaplibreMap(state = mapState)

For imperative image updates, prepare an image with ResolvedStyleImage.fromBitmap(bitmap) or ResolvedStyleImage.fromPainter, then submit it with state.style.images.set(id, image). Preparation owns a pixel copy, so the result can be reused across image IDs and loaded styles.

set, setAll, and remove enqueue commands without waiting for the map. A replacement keeps the previous image until it succeeds; an engine rejection is logged. Looking up an image with state.style.images[id] suspends until the commands enqueued before it have run, so code that needs an image in place awaits its lookup. A finished command does not mean the map has rendered a frame.