Custom layers
When a style layer can’t express it, write a CustomLayer. It draws through a
DrawContext and can answer queries with pick. Its markers and labels are
placed together with the map’s own, so they never overlap.
The trait
Section titled “The trait”pub trait CustomLayer { /// A short name, shown when inspecting its features. fn name(&self) -> &str;
/// Add this layer's lines, fills, markers and labels for the current view. fn draw(&self, ctx: &mut DrawContext);
/// Features of this layer under `ctx.target`, most specific first. fn pick(&self, _ctx: &PickContext) -> Vec<Pick> { Vec::new() }
/// Lon/lat bounds (west, south, east, north), for fitting the view to the layer. fn bounds(&self) -> Option<(f64, f64, f64, f64)> { None }}An example: moving vehicles
Section titled “An example: moving vehicles”use mapterm::{CustomLayer, DrawContext};use ratatui::style::{Color, Style};
struct Vehicles(Vec<(String, f64, f64)>); // (id, lon, lat)
impl CustomLayer for Vehicles { fn name(&self) -> &str { "vehicles" }
fn draw(&self, ctx: &mut DrawContext) { let style = Style::default().fg(Color::Rgb(0, 229, 255)); for (id, lon, lat) in &self.0 { // glyph, style, priority, optional label ctx.marker(*lon, *lat, "▲", style, 100, Some((id.clone(), style))); } }}
map.add_custom_layer("vehicles", Vehicles(positions), None)?;
// Later, update it in place:if let Some(v) = map.custom_layer_mut::<Vehicles>("vehicles") { v.0 = new_positions;}Drawing with DrawContext
Section titled “Drawing with DrawContext”Positions are in braille dots relative to the map area (2 per column, 4 per row). Helpers take lon/lat or world coordinates (Web Mercator, 0 to 1).
| Method | Draws |
|---|---|
line_lonlat(&pts, color) |
A polyline through lon/lat points |
line(&pts, color), line_dots(&pts, color) |
The same in world or dot coordinates |
fill(&rings, color), fill_dots(&rings, color) |
Polygons (outer ring and holes) as cell backgrounds |
cell_bg(col, row, color) |
One cell’s background |
marker(lon, lat, glyph, style, priority, label) |
A glyph with an optional label to its right |
marker_dot(...), label_dot(p, text, style, priority) |
Markers and labels at dot positions |
label_dots(candidates, text, style, priority) |
A label at the first of several positions that fits |
label_spaced(candidates, text, style, priority, repeat) |
Labels at many positions, skipping copies within repeat dots of one already placed (road names) |
dot(x, y), inside(p), visible(bbox), world_view() |
Coordinate conversion and culling |
Lines and fills draw immediately, in layer order. Markers and labels are collected and placed after every layer, highest priority first; a later one that would overlap an earlier one is dropped.
Ready-made: Markers
Section titled “Ready-made: Markers”For simple overlays there’s Markers, a custom layer holding a list of
Overlay::Line and Overlay::Marker values:
use mapterm::{Markers, Overlay};
let pins = Markers(vec![Overlay::Marker { lon: -77.0353, lat: 38.8895, glyph: "◆".into(), style: Style::default().fg(Color::Cyan), label: Some(("Washington Monument".into(), Style::default())), priority: 50,}]);map.add_custom_layer("pins", pins, None)?;The viewer uses one for its highlights and crosshair.