Skip to content

From a MapLibre style

If you already have a MapLibre GL style, you do not have to write an ezu style to use ezu. ezu translate lowers the MapLibre style into an ezu recipe — an ordinary ezu document, which you can then read, diff, and edit.

Terminal window
ezu translate maplibre-style.json --out recipe.json
ezu tile --style recipe.json --tile 14/14550/6452 --out basemap.png

Or in one pipe, validating instead of rendering:

Terminal window
ezu translate https://example.com/style.json | ezu check /dev/stdin --no-fetch

The recipe is zoom-independent. Zoom and data functions are not baked to the zoom you converted at — they are emitted as raw MapLibre expressions on the target node’s *-expr field and evaluated per tile, with that tile’s zoom in the evaluation context. One recipe renders correctly at every zoom.

The ordered layer list becomes a blend chain, which is the painter’s algorithm written as a graph. Each layer becomes a features node (carrying the layer’s filter, minzoom, maxzoom) plus the paint op it maps to. The full table is in layer mapping.

Warnings for anything skipped or approximated go to stderr:

Terminal window
ezu translate style.json --out recipe.json
# skipped/approximated: layer "buildings-3d": fill-extrusion height dropped

Read them. They are the difference between “converted” and “converted the way you expected” — see gaps and differences.

Text is the one place where you usually want to make a decision. By default an unmapped text-font stack is served from the style’s own glyphs endpoint as an SDF glyphs source — the same pre-rendered glyphs MapLibre draws, zero config.

For higher-fidelity outline rendering, map a fontstack entry to a real font:

Terminal window
ezu translate style.json --out recipe.json \
--font "Noto Sans Regular=https://example.com/NotoSans-Regular.ttf" \
--font "Noto Sans Medium=system:Noto Sans?weight=500"

system:FAMILY resolves from the machine’s installed fonts, which is portable to write but machine-dependent to render, and unavailable on wasm. A font file URL (http(s)://, file:, data:) is the reproducible choice.

If a layer’s text-font has no mapping and the style declares no glyphs endpoint, that layer’s text is skipped, with a warning.

Flag Effect
--tile-size emitted canvas size (default 512, matching MapLibre)
--pad margin for geometry painted wider than its own extent, e.g. a thick stroke (default 16). Filter reach is sized by the renderer from the graph, so this is only about paint width — see padding
--keep-hidden keep visibility: none layers, gated off behind a switch instead of dropped
--pretty pretty-print the emitted JSON — do this if you intend to read it
--font NAME=SOURCE map a fontstack entry to a font source (repeatable)

The recipe is a normal ezu document, so the interesting move is to convert once and then edit. Swap a fill-solid for fill-dabs, run the finished tile through blur, or add a paper texture underneath — the data plumbing, filters, and expressions all keep working:

// before: what translate emitted
"landuse": { "op": "fill-solid", "features": "@landuse_f", "fill-expr": ["get", "color"] }
// after: same data, painted
"landuse": { "op": "fill-dabs", "features": "@landuse_f", "color-expr": ["get", "color"],
"radius-px": 6, "spacing-px": 3, "opacity": 0.3 }

ezu-compare (an internal, unpublished crate) converts a MapLibre style, renders it with ezu, and pixel-compares against a maplibre-gl-js reference render:

Terminal window
cargo run --release -p ezu-compare -- \
--style crates/ezu-compare/samples/protomaps-basemap.json \
--tiles 12/3637/1613,13/7275/3225,14/14550/6452

See expression conformance for the part that is exact rather than approximate.

Map renders on this site are made fromOpenStreetMap data viaProtomaps (© OpenStreetMap contributors), elevation from Re:Earth Terrain,Mapterhorn andEGM2008 (NGA), and aerial imagery from GSI Japan(© 国土地理院). The painterly styles use CC0 brushes byDavid Revoy.