Skip to content

Map and sources

A Map holds named sources (where the data comes from) and an ordered list of layers (how to draw it), like a MapLibre map.

// From a MapLibre style: an OpenFreeMap name, a URL, or a style.json file.
let map = Map::from_style("positron").await?;
let map = Map::from_style("https://example.com/style.json").await?;
let map = Map::from_style("./style.json").await?;
// mapterm's built-in terminal styles.
let map = Map::from_style("mapterm-dark").await?;
// From a style document you already have.
let map = Map::from_style_json(&doc).await?;
// Empty: add sources and layers yourself.
let mut map = Map::new();

Map::from_style opens the style’s vector sources. If the style suggests a starting view, it’s in map.center as (lon, lat, zoom).

Styles can use expressions or properties mapterm doesn’t support. Those are skipped rather than guessed at, and listed in map.warnings().

To use the built-in style with a source of your own, detect the schema and build it with Map::terminal:

use mapterm::stylejson::Schema;
let src = Source::open("dc.pmtiles").await?;
let map = Map::terminal(src, Schema::OpenMapTiles, true)?; // dark = true
// Vector tiles: PMTiles file or URL, XYZ template, or TileJSON.
let src = Source::open("https://tiles.openfreemap.org/planet").await?;
map.add_source("omt", MapSource::vector(src))?;
// GeoJSON: a FeatureCollection, a Feature or a bare geometry.
map.add_source("sites", MapSource::geojson(&doc)?)?;
// Raster image tiles (see the raster module).
let r = mapterm::raster::RasterSource::new("https://tile.openstreetmap.org/{z}/{x}/{y}.png", false, 19)?;
map.add_source("osm", MapSource::raster(std::sync::Arc::new(r)))?;

Source ids must be unique; add_source fails otherwise.

GeoJSON sources are spatially indexed, so drawing and queries only touch features near the view. Clustering works like MapLibre’s cluster: true:

use mapterm::cluster::ClusterOptions;
let src = MapSource::geojson(&points)?.with_cluster(ClusterOptions { radius_px: 50.0, max_zoom: 14 });
map.add_source("stops", src)?;

Up to max_zoom, nearby points become cluster features with cluster, cluster_id, point_count and point_count_abbreviated properties; style them with filters such as ["has", "point_count"]. When one is clicked, map.cluster_expansion_zoom("stops", cluster_id) says where it splits, ready for fly_to. In style JSON, cluster, clusterRadius and clusterMaxZoom do the same, and set_data keeps clustering on.

MapSource::geojson(&doc)?.with_promote_id("code") uses a property as the feature id (MapLibre’s promoteId), for feature state. GeoJSON ids and vector tile ids are used otherwise.

Method Does
source(id), sources() Look sources up
remove_source(id) Remove one
set_data(id, &doc) Replace a GeoJSON source’s data, like MapLibre’s setData
source_bounds(id) A GeoJSON source’s lon/lat bounds, for fitting the view
zoom_range() The zoom range of the vector sources

Sources carry their attribution: TileJSON and PMTiles metadata, a style source’s attribution, or an imagery provider’s credit. map.attributions(zoom) lists the credits of the sources that layers shown at that zoom draw from, like MapLibre’s attribution control, and set_attribution(id, text) changes one.

Layers draw bottom to top in the order they’re added.

map.add_layer(StyleLayer::line("route", "trip").color("#00e5ff"))?;
map.add_layer_before(StyleLayer::fill("zones", "zones").color("#202838"), "roads")?;
map.add_layers(layers, Some("labels"))?; // several, below "labels"
map.add_custom_layer("vehicles", my_layer, None)?; // a CustomLayer, on top

Change them at runtime, as in MapLibre:

Method Does
set_paint(id, "line-color", &json!("#f00")) Change a paint property
set_layout(id, "text-field", &json!(["get", "ref"])) Change a layout property
set_filter(id, Some(&json!(["==", ["get", "class"], "park"]))) Change or clear (None) the filter
set_visibility(id, false) Hide or show
set_zoom_range(id, 10.0, 24.0) Change min and max zoom
move_layer(id, Some("labels")) Move below another layer, or to the top with None
remove_layer(id), remove_layers(&[...]) Remove
layer_ids(), layer(id) Inspect the stack
custom_layer_mut::<T>(id) Get your custom layer back to update it

Changes apply to loaded tiles at once; there’s nothing to refresh.

map.add_map(other, before) moves another map’s sources and layers in, below the layer before (or on top). The viewer uses it to switch basemap styles: it loads the new style on a separate task (Map is Send), then swaps only the basemap’s layers, keeping imagery underneath and data layers on top.

set_feature_state, remove_feature_state and feature_state work as in MapLibre; see Events and feature state.

frame.render_widget(map.view(&camera), area);

MapView is a ratatui Widget. It draws whatever tiles are loaded, using lower-zoom tiles in place of ones that haven’t arrived yet.