Skip to content

Render your first tile

hillshade.json keeps all of its data remote, so this needs only the CLI:

Terminal window
ezu tile \
--style https://raw.githubusercontent.com/reearth/ezu/main/crates/ezu/examples/styles/hillshade.json \
--tile 11/1813/807 --out fuji.png

--tile takes z/x/y in the usual XYZ scheme. Output format follows the extension (.png or .webp, lossless).

A style is a JSON document with a nodes map and an output reference. Nothing else is required. Start with a solid colour:

mine.json
{
"name": "mine",
"nodes": {
"bg": { "op": "solid", "color": "#fbf6e6" }
},
"output": "@bg"
}
Terminal window
ezu tile --style mine.json --tile 13/7276/3225 --out mine.png
A tile filled with a flat cream colour
One node, one colour. Every style starts here.

A cream square. Now add data. Declare an MVT source, select a layer, fill it, and composite the fill over the background:

mine.json
{
"name": "mine",
"pad": 8,
"sources": {
"basemap": { "type": "mvt", "url": "https://papers.reearth.land/protomaps/tilejson.json" }
},
"nodes": {
"bg": { "op": "solid", "color": "#fbf6e6" },
"water_f": { "op": "features", "layer": "water" },
"water": { "op": "fill-solid", "features": "@water_f", "fill": "#5876a0" },
"out": { "op": "blend", "base": "@bg", "over": "@water" }
},
"output": "@out"
}
The same tile with water drawn as flat blue polygons
Water polygons from the vector tile, filled flat and composited over the background.

That URL is a live TileJSON endpoint, so the snippet renders as written — the data is © OpenStreetMap contributors, © Protomaps. Swap it for your own MVT or PMTiles source when you have one.

Three things to notice:

  • @name wires nodes together. fill-solid reads its geometry from the features node; blend reads two rasters. That is the whole graph syntax.
  • The order in nodes means nothing. Evaluation order comes from the references, not the file. The blend chain is what puts water on top of the background.
  • output must be a Raster. features produces Features, which cannot be the output — the graph type-checks this at build time, before any pixel is drawn.

Swap the flat fill for a scatter-dab fill and the map stops looking styled:

"water": {
"op": "fill-dabs",
"features": "@water_f",
"color": "#5876a0", "opacity": 0.22,
"radius-px": 7, "spacing-px": 3
}
The same tile with water painted as soft overlapping dabs
The same water, painted as scatter dabs instead of filled.

fill-dabs places dabs on a world-anchored grid, so the wash continues across tile borders instead of restarting at each edge. Blur the result and it reads as wet paint:

"soft": { "op": "blur", "input": "@water", "sigma": 3 },
"out": { "op": "blend", "base": "@bg", "over": "@soft" }
The dabbed tile softened with a blur
A small blur over the composite reads as the paper wicking.

Blur needs pixels from beyond the tile edge to stay seamless, which is what pad is for — and you do not have to size it. The graph knows how far each filter reaches and the renderer pads the canvas to match, so the pad: 8 above could be left out entirely. ezu check prints what it settled on:

Terminal window
ezu check mine.json --no-fetch
# INFO pad: 8 declared, 17 needed — rendering with 17

Write pad when you paint wider than your geometry — a thick stroke, a large dab radius — since a width can be an expression and so cannot be known in advance. See padding and neighbours.

For real brush strokes, load a brush through sources and use line or stroke:

"sources": {
"pencil": { "type": "brush", "src": "file:brushes/pencil.myb" }
},
"nodes": {
"roads": { "op": "line", "features": "@roads_f", "brush": "@pencil",
"color": "#333", "radius-px": 1.2 }
}
Terminal window
ezu check mine.json # parse + build graph + resolve assets
ezu check mine.json --no-fetch # offline: parse + graph only

ezu check exits non-zero on a bad reference, a type mismatch, a cycle, or a missing asset — the same validation the renderer runs, without rendering. Wire it into CI (validate in CI).

To see the shape of what you built:

Terminal window
ezu graph mine.json # Mermaid `graph LR` of the node dependencies

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.