Skip to content
This site is a preview of the main branch. The published documentation is at maplibre.org.

How the map composes

MapState holds one map: its base style, its camera, and the sources and layers that you declare. MaplibreMap draws a MapState on the screen. The state is a separate object, so code outside the map can use it. For example, a button elsewhere on the screen can move the camera.

Every map starts from a base style. A base style is a style document, usually published by a map provider at a URL. It contains sources, which supply the data, and layers, which draw that data: roads, water, labels, and so on.

The trailing block of rememberMapState declares your own sources and layers. The library adds them to the base style. It does not change the layers of the base style.

App.kt
val state =
rememberMapState(baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")) {
val earthquakes =
rememberGeoJsonSource(
GeoJsonData.Uri("https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson")
)
CircleLayer(id = "earthquakes", source = earthquakes, color = const(Color.Red))
}
MaplibreMap(state = state)

Your layers draw above the layers of the base style, in the order that you declare them: a later layer draws above an earlier one. To put a layer between layers of the base style, for example under the labels, wrap it in an Anchor. Draw under the labels compares the two layer orders.

The library adds a source to the map only while one of your layers uses it. A source that no layer uses loads no data.

The content block is a composable. When state that it reads changes, the block recomposes, and the library changes the map to match.

App.kt
@Composable
fun EarthquakeMap(showStrongOnly: Boolean, dotColor: Color) {
val state =
rememberMapState(baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")) {
val earthquakes =
rememberGeoJsonSource(
GeoJsonData.Uri("https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson")
)
CircleLayer(id = "earthquakes", source = earthquakes, color = const(dotColor))
if (showStrongOnly) {
CircleLayer(
id = "strong-earthquakes",
source = earthquakes,
filter = feature["mag"].asNumber() gte const(5f),
radius = const(10.dp),
color = const(Color.Yellow),
)
}
}
MaplibreMap(state = state)
}

When showStrongOnly becomes true, the library adds the strong-earthquakes layer. When it becomes false, the library removes that layer. When dotColor changes, the library sets the new color on the existing earthquakes layer.

You do not call add or remove functions. Use if, loops, and parameters, as you do with UI composables.

Switching the base style keeps your content

Section titled “Switching the base style keeps your content”

When baseStyle changes, the map loads the new style document. The library then adds your content to the new style, so your sources and layers stay on the map. Style the map shows how to switch styles.

The content block composes again from the start for each style that the map loads. Values that you remember inside the block reset at that point, so keep app state that must outlast a style change outside the block. An Anchor places your layers again among the layers of the new style.

To show the same sources and layers in more than one place, write them as a composable function and call it from each content block. Here an interactive map and a MapSnapshotter, which captures map images, show the same earthquakes:

App.kt
@Composable
@MaplibreComposable
fun EarthquakeLayers() {
val earthquakes =
rememberGeoJsonSource(
GeoJsonData.Uri("https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson")
)
CircleLayer(id = "earthquakes", source = earthquakes, color = const(Color.Red))
}
@Composable
fun EarthquakeMapAndSnapshotter() {
val baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")
val state = rememberMapState(baseStyle = baseStyle) { EarthquakeLayers() }
val snapshotter = remember {
DefaultMapRuntime.instance.createSnapshotter(baseStyle = baseStyle) { EarthquakeLayers() }
}
DisposableEffect(snapshotter) { onDispose { snapshotter.close() } }
MaplibreMap(state = state)
}

A snapshotter has no MapState, so LocalMapState is null inside it, and shared content must work without it. A snapshotter also does not wait for effects in the content, such as one that loads data. Pass any data that the image needs as a parameter of the shared function. Capture a map image shows how to capture.

Some changes are commands, not declarations: hiding a layer of the base style, setting the state of one feature, or reading the points inside a cluster. For these, get a handle from mapState.style. MapStyleState.layers returns a LayerHandle for a layer ID. MapStyleState.sources returns a SourceHandle for a source ID or for a source object that you declared.

This example hides the 3D buildings of the base style:

App.kt
val state =
rememberMapState(baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"))
// Null until the style is ready. Each loaded style publishes a new handle.
val buildings3d = state.style.layers["building-3d"]
LaunchedEffect(buildings3d) {
buildings3d?.asMutable?.setLayoutProperty("visibility", JsonPrimitive("none"))
}
MaplibreMap(state = state)

A handle belongs to one loaded style. It is null until the style is ready, and after a style switch the map gives you a new handle. The example reads the handle during composition, so the effect runs again for each new style.

asMutable gives write access to layers and sources of the base style. For layers and sources that you declared, it is null: change the declaration instead. Handles of declared sources still set feature state and read clusters. See Query and highlight features and Cluster points.

MapLibre identifies each layer by a text ID, and you choose the ID of each layer that you declare. The ID must be different from every other layer on the map, including the layers of the base style. See Layer for what happens when an ID repeats. Keep each ID the same across recompositions: a different ID is a different layer, so the library removes the old layer and adds a new one.

Other functions find layers by ID. An Anchor can name a layer of the base style, MapState.queryRenderedFeatures can return features from only some layers, and mapState.style.layers returns a handle by ID.

Sources that you remember get their ID from the library, so refer to them by the source object. To draw data from a source of the base style, get it by its ID with getBaseSource.