Expressions in Kotlin
A layer property, such as the color of a circle, can have one value for the
whole layer or a different value for each feature. It can also change with the
zoom level. MapLibre describes these properties with expressions: small
formulas inside the style that the map evaluates while it draws. The
MapLibre Style Specification writes expressions as JSON
arrays. MapLibre Compose builds them with typed Kotlin functions from
org.maplibre.compose.expressions.dsl, so a layer parameter has a
type such as Expression<ColorValue>.
Constants
Section titled “Constants”For a fixed value, wrap the value in const. Sizes take a Compose
Dp, colors take a Compose Color, and offsets take a DpOffset.
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)The examples below draw the same earthquakes source.
Read feature data
Section titled “Read feature data”Each feature in a source has properties, such as the magnitude of an
earthquake. The feature object reads the data of the feature that
the map is drawing: feature["mag"] is its mag property. The map learns the
type of a property only when it evaluates the expression, so give the property
a type with a type assertion such as asNumber before you compare or
calculate with it. An equality test such as eq works without a
type.
step picks an output by ranges of its input. switch
picks the output of the first condition that is true.
val state = rememberMapState { CircleLayer( id = "quakes-by-magnitude", source = earthquakes, // Below magnitude 4: 4 dp. From 4 up to 6: 8 dp. 6 and above: 16 dp. radius = step( input = feature["mag"].asNumber(), fallback = const(4.dp), 4 to const(8.dp), 6 to const(16.dp), ), // Blue where the "tsunami" property is 1, red everywhere else. color = switch( condition(test = feature["tsunami"] eq const(1), output = const(Color.Blue)), fallback = const(Color.Red), ), )}MaplibreMap(state = state)The map evaluates these expressions separately for each earthquake, so each circle gets its size and color from its own data.
Change with zoom
Section titled “Change with zoom”zoom is the current zoom level of the map. Pass it to
interpolate to compute a value between stops, so that circles grow
as the user zooms in.
val state = rememberMapState { CircleLayer( id = "quakes-by-zoom", source = earthquakes, // 2 dp at zoom 5 and below, 8 dp at zoom 10 and above, in between on a straight line. radius = interpolate(linear(), zoom(), 5 to const(2.dp), 10 to const(8.dp)), )}MaplibreMap(state = state)MapLibre accepts zoom() only as the input of a step or interpolate at the
top of a property’s expression.
Filter features
Section titled “Filter features”The filter parameter of a layer takes a true-or-false expression. The layer
draws only the features for which it is true. The source keeps every feature,
so another layer on the same source can draw the rest.
val state = rememberMapState { CircleLayer( id = "strong-quakes", source = earthquakes, filter = feature["mag"].asNumber() gte const(5f), )}MaplibreMap(state = state)Missing data
Section titled “Missing data”A feature does not always have every property. In this data, most earthquakes
have no felt value, so feature["felt"] is null for them. A type assertion
such as asNumber() fails on null. When an expression fails for a feature,
MapLibre uses the default value of the property for that feature, and a filter
that fails hides the feature.
To choose the value yourself, give the type assertion a fallback. To skip features that lack the value, filter them out.
val state = rememberMapState { // Every earthquake, sized by its "felt" value. A missing value counts as 0 reports. CircleLayer( id = "quakes-by-felt", source = earthquakes, radius = interpolate( linear(), feature["felt"].asNumber(const(0f)), 0 to const(4.dp), 100 to const(16.dp), ), )
// Only the earthquakes that have a "felt" value. CircleLayer(id = "felt-quakes", source = earthquakes, filter = feature["felt"] neq nil())}MaplibreMap(state = state)To use the first of several properties that has a value, use
coalesce. The Expression reference lists which
functions accept a missing value.
Kotlin code and expression code
Section titled “Kotlin code and expression code”Kotlin code in the content block runs when the content composes. It builds an expression and passes it to the layer. The map evaluates that expression later, while it draws, for each feature.
val state = rememberMapState { // Kotlin chooses one stroke color for the whole layer while the content composes. val strokeColor = if (isSystemInDarkTheme()) Color.White else Color.Black
CircleLayer( id = "quakes-themed", source = earthquakes, strokeColor = const(strokeColor), strokeWidth = const(1.dp), // The map chooses a fill color for each earthquake while it draws. color = switch( condition(test = feature["mag"].asNumber() gte const(5f), output = const(Color.Red)), fallback = const(Color.Yellow), ), )}MaplibreMap(state = state)The Kotlin if reads app state and picks one stroke color for the whole layer.
When the system theme changes, the content recomposes and the layer gets the
new color. The switch expression reads the data of each earthquake, which
Kotlin code cannot see, and picks a fill color for each one.
Use Kotlin for choices that depend on app state, and expressions for choices that depend on feature data or zoom. To change how one feature looks when the user selects it, use feature state; see Query and highlight features.