Query and highlight features
A feature is one point, line, or polygon in a source, with its properties. You
can ask the map which features it draws at a place on the screen, or which
features a source has loaded. You can also attach a value such as selected to
one feature and draw that feature differently.
To respond when the user taps a feature in one of your layers, give the layer
an onClick handler instead, as shown in
Handle gestures and clicks.
The handler receives the features at the tap.
Find features on screen
Section titled “Find features on screen”MapState.queryRenderedFeatures returns the features that the map
draws inside a rectangle on the screen. This map lists the names of the places
near a long press:
@Composablefun NearbyPlaces(onPlacesFound: (List<String>) -> Unit) { val state = rememberMapState(baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")) val scope = rememberCoroutineScope() MaplibreMap( state = state, interactions = MapInteractions { callbacks { longClick { onEvent { event -> val area = DpRect( origin = event.screenOffset - DpOffset(24.dp, 24.dp), size = DpSize(48.dp, 48.dp), ) scope.launch { val places = state.queryRenderedFeatures( rect = area, layerIds = setOf("poi_r1", "poi_r7", "poi_r20"), ) onPlacesFound( places.mapNotNull { it.properties?.get("name")?.jsonPrimitive?.contentOrNull } ) } ClickResult.Consume } } } }, )}layerIds limits the result to features drawn by those layers. Here they are
the point-of-interest layers of the OpenFreeMap Liberty style. Without
layerIds, the result includes every layer, such as roads, water, and labels.
The topmost feature comes first. To query a single point, pass a DpOffset
instead of a DpRect. To keep only features with certain properties, pass an
expression as predicate.
queryRenderedFeatures is a suspend function, so call it from a coroutine.
Find features in a source
Section titled “Find features in a source”VectorTileSourceHandle.querySourceFeatures returns the features of
a vector tile source, whether or not a layer draws them. Use it to search the
data behind the map, such as every café in the loaded tiles:
suspend fun loadedCafes(state: MapState): List<Feature<Geometry, JsonObject?>> { val tiles = state.style.sources["openmaptiles"] as? VectorTileSourceHandle ?: return emptyList() return tiles.querySourceFeatures( sourceLayerIds = setOf("poi"), predicate = feature["class"] eq const("cafe"), )}state.style.sources returns a source’s handle by ID, or by the source object
that you declared. It returns null until the style has loaded. The result
contains only features from the tiles that the map has loaded for the current
view, and it can contain the same feature more than once when the feature
crosses a tile edge.
Highlight a selected feature
Section titled “Highlight a selected feature”Feature state is data that your app attaches to one feature while the map
runs. A layer can read it with feature.state
and draw that feature differently. This map draws three stops in blue and turns
the tapped stop red:
@Composablefun SelectableStops() { val stops = remember { buildFeatureCollection<Point, JsonObject?> { // Feature state needs an integer id on each feature. addFeature(geometry = Point(Position(longitude = -122.4194, latitude = 37.7793))) { setId(1) } addFeature(geometry = Point(Position(longitude = -122.4089, latitude = 37.7841))) { setId(2) } addFeature(geometry = Point(Position(longitude = -122.3937, latitude = 37.7955))) { setId(3) } } } val state = rememberMapState( baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"), initialCameraPosition = CameraPosition(center = Position(longitude = -122.41, latitude = 37.785), zoom = 13.0), ) { val mapState = LocalMapState.current val source = rememberGeoJsonSource(GeoJsonData.Features(stops)) var selectedId by remember { mutableStateOf<String?>(null) } CircleLayer( id = "stops", source = source, radius = const(10.dp), color = switch( condition(feature.state("selected").asBoolean(const(false)), const(Color.Red)), fallback = const(Color.Blue), ), onClick = { features -> val handle = mapState?.style?.sources?.get(source) val id = features.first().id?.content if (handle != null && id != null) { selectedId?.let { handle.removeFeatureState(it, "selected") } handle.setFeatureState(id, buildJsonObject { put("selected", true) }) selectedId = id } ClickResult.Consume }, ) } MaplibreMap(state = state)}setFeatureState adds
values to a feature’s state. The map clears feature state when it loads another
base style, so the example remembers selectedId inside the content block,
which resets at the same time. removeFeatureState removes them, and
resetFeatureStates clears the state of every feature in the source. Inside the
content block of rememberMapState, LocalMapState gives the map
state, and style.sources gives the source’s handle.
Feature state finds a feature by its id. Give each GeoJSON feature an integer
id, and pass the id to setFeatureState as text, as
features.first().id?.content does in the example. For a vector tile source,
also pass the source layer, and the tile data must give each feature an id.
Only some layer properties can read feature state, such as the color and
radius of a circle layer. The API reference for each layer property says
whether it can. Filters cannot read feature state.