Bounding boxes and pyramids
ezu tile renders one tile. Three other commands render many, and they share
every flag with tile — --style, --assets-dir, --param, source overrides.
One image over a bounding box
Section titled “One image over a bounding box”ezu bbox renders every tile covering a lon/lat box at one zoom and stitches
them into a single image:
ezu bbox --style watercolor.json \ --bbox 139.74,35.65,139.78,35.69 \ --zoom 13 --out tokyo.pngThe box is min-lng,min-lat,max-lng,max-lat. The result is tile-aligned: it
covers at least the box, snapped outward to tile edges. This is the command for
a figure in a slide deck.
A page at a print scale
Section titled “A page at a print scale”ezu frame renders a map at a scale and paper size, cropped exactly to the page:
ezu frame --style watercolor.json \ --center 139.70,35.66 --scale 1000 --dpi 200 \ --size-mm 245x174 --out page.pngThat is 1:1000 on a 245 × 174 mm page at 200 dpi: a 1929 × 1370 px image
centred on the given point. --size-px 1929x1370 gives the size in pixels
instead. --dpi is required either way, because the scale is a ratio of paper to
ground and the dpi is what turns paper into pixels.
A tile renderer draws whole tiles at integer zooms, so ezu reaches the scale
through the tile size instead. It picks the zoom whose tile size comes nearest
the style’s own tile-size, renders at that size for this run, then copies each
tile’s share straight into the page — the full tile mosaic is never held in
memory. The log says what it settled on:
frame: 1929×1370 px at z=19, tile-size 489 (style: 512), scale 1:1000.0 (+0.002% vs 1:1000), …The tile size is a whole number of pixels, so the scale can be off by up to half a
pixel in a tile’s width: at most about 0.1 % for a tile size near 512, 0.2 % near
256. --zoom
fixes the zoom and derives the tile size from it, for a style whose zoom-dependent
choices (which layers draw, which labels appear) you want pinned. The scale is
matched at the centre’s latitude, and Web Mercator drifts from it towards the top
and bottom of a tall page.
--dpi only sets the scale. A style’s sizes are in pixels — a 2 px road edge,
14 px text — and stay that many pixels at any dpi, so at 200 dpi they print
smaller than at 96. Make lines and labels heavier in the style, or with a
param, when a page needs them bigger.
The page’s features as GeoJSON
Section titled “The page’s features as GeoJSON”--features NODE=PATH writes what a features node produced across the page as
a GeoJSON FeatureCollection, next to the image. The node can be any node in the
style whose output is features, wired to the output or not. The usual one is
label-boxes, which lists every label with what
placement did to it: placed, moved off its first position (variant-index),
left overlapping (overlapped) or dropped (placed: false with
drop-reason, given include: all). A print pipeline can read the dropped
ones from it and put their names in a list beside the map.
ezu frame --style labels.json \ --center 139.70,35.66 --scale 1000 --dpi 200 --size-mm 245x174 \ --out page.png \ --features boxes=labels.geojson --features-key boxes=label-id,partThe node is evaluated tile by tile, and a label near a tile edge is seen by
every tile it reaches, so without a key the same label appears once per tile.
A shape cut at tile edges, such as a polygon read from a vector tile, appears
as its pieces. --features-key NODE=PROP[,PROP…] makes features whose listed
properties are all equal one feature: the first in tile order (rows top to
bottom, then left to right, and each tile’s features in their own order) is
kept, and the file is sorted by those properties, numbers numerically, text
alphabetically, a missing property first. A label’s boxes share its
label-id on every tile, so label-id,part lists each box once.
A line label has one glyph box per glyph, and a label with an icon has
more than one collision box, all with the same label-id and part; the
key keeps only the first of them. For a list of labels, ask label-boxes
for the parts that come one to a label, "parts": ["text", "icon", "background"].
Features whose bounding box lies wholly outside the page are left out. The
rest are written whole, not clipped to the page. --features-coords picks
the coordinates:
lonlat(the default): WGS84 longitude and latitude, as RFC 7946 asks, to 7 decimal places.px: pixels of the output image, from its top-left corner with y down, to 2 decimal places, for drawing over the page.
The same style, data and flags give a byte-identical file on every run, so a diff of two runs shows whether anything on the page changed.
Many pages in one run
Section titled “Many pages in one run”A print job is usually many pages from one style. Run once per page, ezu
reads the style, builds its graph and fetches the background tiles again for
every page, though neighbouring pages share most of them. --pages renders a
whole job in one run from a JSON file:
ezu frame --style labels.json --pages job.json{ "defaults": { "dpi": 200, "size-mm": [245, 174], "features-coords": "px" }, "pages": [ { "center": [139.70, 35.66], "scale": 1000, "out": "p1.png", "features": { "boxes": { "out": "p1-labels.geojson", "key": ["label-id", "part"] } } }, { "center": [139.71, 35.66], "scale": 2500, "zoom": 17, "out": "p2.png" } ]}A page has a field for each flag of a single page: center ([lng, lat]),
scale, dpi, size-mm or size-px ([width, height]), zoom, out,
format, features-coords, and features, which maps a node to its file
out and, if it has one, its key. Every field but out can be given once
in defaults instead, and a page’s own value wins. A page’s paper size and
its features replace the defaults’ whole, so a page with size-px does not
also take a default size-mm. Relative paths are read from the pages file’s
directory. Each page writes exactly what a single-page ezu frame with the
same values writes.
The whole file is checked before the first page renders: an unknown field, a
page without out, or two pages writing the same file is an error that names
the page. --pages takes the place of the page flags, so it cannot be given
with --center, --out and the rest; --concurrency and the style flags
still apply, to every page.
Pages render one after another, each page’s tiles in parallel as before. The
style is read and its sources opened once. Vector tiles one page fetched are
kept in memory for the next, up to 256 MB (EZU_TILE_CACHE_MB sets another
limit), so pages over the same area at the same zoom fetch them once; raster
and DEM tiles are kept too. The intermediate cache of the graph belongs to
one tile size, and a page at a new tile size starts a fresh one, so a job
ordered by scale reuses the most. Each page logs a line with its time and the
tiles it fetched and reused, and the run ends with the totals.
A whole pyramid
Section titled “A whole pyramid”ezu tiles bulk-renders <out>/<z>/<x>/<y>.png across a zoom range:
ezu tiles --style watercolor.json \ --bbox 139.74,35.65,139.78,35.69 \ --min-zoom 10 --max-zoom 14 \ --out pyramid--concurrency sets how many tiles render at once (default: your core count).
Because the intermediate cache is shared across tiles within one style, and
world-anchored nodes drop the tile id from their cache key, a pyramid run reuses
far more work than the same tiles rendered one process at a time. See
caching and parallelism.
The output tree is a plain XYZ directory — serve it with any static file server
and point MapLibre, Leaflet, or OpenLayers at
https://example.com/pyramid/{z}/{x}/{y}.png.
Parameters per run
Section titled “Parameters per run”Style params let one style produce variants without editing JSON:
for paper in '#fbf6e6' '#ffe0f0' '#e8f2ff'; do ezu bbox --style watercolor.json --bbox 139.74,35.65,139.78,35.69 \ --zoom 13 --param "paper=$paper" --out "tokyo-$paper.png"doneValues are validated against the declarations — a number outside its min/max
or a malformed colour fails before rendering starts.
Practical notes
Section titled “Practical notes”- WebP —
--out tile.webp(or--format webp) writes lossless WebP, usually 20–40 % smaller than PNG on painterly content, through a pure-Rust encoder. - Overzoom — when a requested tile is missing from the source, ezu walks up
to
--overzoom-levelsparent zooms (default 4) and reprojects the ancestor’s geometry into the requested frame.--overzoom-levels 0disables it. - Attribution —
Document::attributions()(and the tile server’s/style/attribution) return the merged attribution for everything a style pulled in, including TileJSON and PMTiles metadata. Render it wherever the tiles end up.
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.