Skip to content

Events and feature state

MapLibre apps write map.on("click", "sites", ...) and light features up on hover with feature state. mapterm has the same two pieces. Rust apps poll instead of registering callbacks: you feed events::Events the pointer and clicks each frame, and act on the events it returns.

use mapterm::events::{Events, MapEvent};
// The layers you care about; set the "hover" feature state on the feature under the pointer.
let mut events = Events::new(&["sites", "route"]).hover_state("hover");
loop {
// ... handle input with the Controller, draw ...
// The pointer moved: Enter and Leave events, and the hover state updated.
for e in events.pointer(&mut map, &ctl.cam, area, ctl.pointer()) {
match e {
MapEvent::Enter { layer, feature } => status = format!("{layer}: {}", feature.title()),
MapEvent::Leave { .. } => status.clear(),
_ => {}
}
}
// A click: every feature under it, per layer, top first.
if let Some(click) = ctl.take_click() {
for e in events.click(&map, &ctl.cam, area, &click) {
if let MapEvent::Click { layer, features, lon, lat } = e {
// open a popup for features[0] ...
}
}
}
}
Event When
Click { layer, features, lon, lat } A click on the layer’s features; all of them under the click, top first
Enter { layer, feature } The pointer moved onto a feature (the top one under it)
Leave { layer, feature } The pointer left that feature

Repeats with nothing new are skipped, so you get one Enter per feature, not one per mouse move. Controller::pointer() is the pointer position the controller tracked from mouse events.

Feature state is per-feature data that paint expressions can read, without touching the source data. hover_state("hover") sets it for you; you can also set any state yourself:

use mapterm::mvt::Value;
use serde_json::json;
map.set_feature_state("sites", &Value::Num(42.0), &json!({"selected": true}))?;
map.remove_feature_state("sites", Some(&Value::Num(42.0)), Some("selected"));
let state = map.feature_state("sites", &Value::Num(42.0)); // {} when none is set

Read it in a layer with the feature-state expression:

map.add_layer(StyleLayer::fill("sites", "sites").color(json!(
["case", ["boolean", ["feature-state", "hover"], false], "#ffcc00", "#204060"])))?;

Only layers that read feature state are re-evaluated when it changes.

Feature state needs ids. They come from:

  • GeoJSON features’ id,
  • vector tile feature ids,
  • or a property you choose, MapLibre’s promoteId: MapSource::geojson(&doc)?.with_promote_id("site_code"), or promoteId on a source in style JSON.

Picks carry the id too (Pick::id, and _id in the JSON from --pick and to_json).

examples/hello_map.rs does all of this in about 60 lines: a pin that turns yellow under the pointer, and clicks on the pin or a place name reported in the status line. See Your first map.