A Rust crate to dynamically generate SVG layer legends from a MapLibre GL style JSON.
Given a MapLibre style (e.g. style.json), this library parses all layers and produces standalone SVG symbols or a combined legend, complete with optional labels and raster entries.
This project is a work in progress. It doesn't yet support all layer types, but it covers the most common cases. By using each layer's metadata attribute, you can flexibly customize how the legend is generated — especially the labels.
- Parse MapLibre GL style (v8) JSON into a structured
Stylemodel. - Render individual layer legends as SVG: fill, line, circle, symbol, fill-extrusion, background, heatmap, raster.
- Sprite support:
spritefield accepts both a single URL string and an array of URLs. - Stack all layers into one combined SVG with separators.
- Optionally include raster layers.
- Customizable dimensions and label rendering via [
LegendConfig]. - Per-layer label overrides through the
metadata.legendobject.
[dependencies]
maplibre-legend = "0.5"| Feature | Default | Description |
|---|---|---|
async |
✓ | Async sprite fetching via reqwest |
sync |
Blocking sprite fetching — disable default features first |
Async (default):
maplibre-legend = "0.5"Sync:
maplibre-legend = { version = "0.5", default-features = false, features = ["sync"] }The two features are mutually exclusive.
use maplibre_legend::{LegendConfig, MapLibreLegend};
use tokio::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let style_json = fs::read_to_string("style.json").await?;
let legend = MapLibreLegend::new(
&style_json,
LegendConfig {
default_width: 250,
..Default::default()
},
)
.await?;
// Render all layers into one combined SVG (reversed order)
let svg = legend.render_all(true)?;
fs::write("legend.svg", svg).await?;
// Or render a single layer by ID
let svg = legend.render_layer("my-layer-id", None)?;
fs::write("layer.svg", svg).await?;
Ok(())
}use maplibre_legend::{LegendConfig, MapLibreLegend};
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let style_json = fs::read_to_string("style.json")?;
let legend = MapLibreLegend::new(
&style_json,
LegendConfig {
default_width: 250,
..Default::default()
},
)?;
let svg = legend.render_all(true)?;
fs::write("legend.svg", svg)?;
Ok(())
}LegendConfig {
default_width: 200, // SVG width in pixels
default_height: 40, // SVG height for single-entry layers
has_label: true, // render a title label above each layer
include_raster: false, // include raster layers in render_all()
}All fields are public. Use ..Default::default() to keep the rest at their defaults.
Given a MapLibre style.json with various fill, line, and circle layers:
let svg = legend.render_all(true)?;| style.json | style2.json | style3.json |
|---|---|---|
Each layer can override legend behavior through a "legend" object in its metadata:
"metadata": {
"legend": {
"label": "Land value 2016 [USD/m²]",
"default": "Other",
"custom-labels": [
"Up to $100",
"$100 – $250",
"$250 – $750",
"Over $750"
]
}
}| Key | Type | Description |
|---|---|---|
label |
string | Title for the legend entry. Falls back to the layer id. |
default |
string | Label for the expression's fallback/default color. |
custom-labels |
array of strings | Labels for each stop or case in the expression, in order. |
The following MapLibre paint expression types are parsed into legend entries:
| Expression | Behavior |
|---|---|
match |
One entry per value + default |
case |
One entry per condition + default |
interpolate |
One entry per stop |
step |
One entry per threshold + base |
coalesce |
Delegates to the first inner match/case/interpolate/step |
literal |
Single entry |
| Module | Layer type(s) |
|---|---|
fill |
fill |
line |
line |
circle |
circle |
symbol |
symbol |
fill_extrusion |
fill-extrusion |
background |
background |
heatmap |
heatmap |
raster |
raster |
default |
unknown types (gray fallback) |
common |
shared types, expression parser, sprite utilities |
error |
LegendError |
- Fork the repo and create a feature branch.
- Run
cargo fmtandcargo clippybefore committing. - Add tests for new behavior.
- Submit a pull request — feedback is welcome!
BSD-3-Clause. See the LICENSE file for details.