Skip to content
This site is a preview of pull request #1380.

Expressions in Kotlin

Layer properties in MapLibre are not plain values. Each property takes an expression: a formula that the map evaluates while it renders, per feature and per zoom level. The MapLibre Style Specification defines expressions as JSON arrays. MapLibre Compose provides a Kotlin DSL that builds them with type checking, in org.maplibre.compose.expressions.dsl.

The DSL uses Compose types when a Compose type matches the property: Dp for sizes, Color for colors, and DpOffset for offsets.

A constant value must also be an expression. Wrap a plain value in const:

App.kt
val state = rememberMapState {
val earthquakes =
rememberGeoJsonSource(
GeoJsonData.Uri("https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson")
)
CircleLayer(
id = "quakes-constant",
source = earthquakes,
radius = const(4.dp),
color = const(Color.Red),
)
}
MaplibreMap(state = state)

feature accesses the properties of the feature being rendered. The result is untyped, so convert it with functions such as asNumber() or asString(). Functions such as step and switch select an output from the value:

App.kt
val state = rememberMapState {
CircleLayer(
id = "quakes-by-magnitude",
source = earthquakes,
radius =
step(
input = feature["mag"].asNumber(),
fallback = const(4.dp),
4 to const(8.dp),
6 to const(16.dp),
),
color =
switch(
condition(test = feature["mag"].asNumber() gt const(5), output = const(Color.Red)),
fallback = const(Color.Yellow),
),
)
}
MaplibreMap(state = state)

The map evaluates this formula once per feature: each earthquake receives a radius and a color computed from its own magnitude.

zoom is the current zoom level. interpolate computes a smooth value between stops:

App.kt
val state = rememberMapState {
CircleLayer(
id = "quakes-by-zoom",
source = earthquakes,
radius =
interpolate(
type = exponential(2f),
input = zoom(),
5 to const(2.dp),
10 to const(8.dp),
),
)
}
MaplibreMap(state = state)

The filter parameter takes a boolean expression. The layer renders only the features for which it is true:

App.kt
val state = rememberMapState {
CircleLayer(
id = "large-quakes",
source = earthquakes,
filter = feature["mag"].asNumber() gt const(5),
)
}
MaplibreMap(state = state)

Feature data can be incomplete. Use coalesce or an assertion’s fallback argument, such as asNumber(const(0f)), to choose what to show when a value is missing:

App.kt
val state = rememberMapState {
SymbolLayer(
id = "quake-places",
source = earthquakes,
// A missing "place" renders as no text.
textField = feature["place"].cast<StringValue?>(),
)
CircleLayer(
id = "quakes-with-fallback",
source = earthquakes,
// A missing "mag" counts as 1.
radius = coalesce(feature["mag"].cast<FloatValue?>(), fallback = const(1f)).dp,
filter = feature["place"] neq nil(),
)
}
MaplibreMap(state = state)

Use nil to compare an expression with null. To leave a layer property unset, pass Kotlin null to the layer. The API reference describes nullable expressions and type assertions in detail.

Kotlin code outside an expression runs during composition and produces an expression. The expression runs inside the map renderer, once per feature. A Kotlin if selects which expression to build; an expression switch selects a value per feature. Use Kotlin for decisions that depend on app state, and expressions for decisions that depend on feature data or zoom.

The API reference documents the full set of expression functions under org.maplibre.compose.expressions.dsl.