Skip to content

params

"params": {
"paper": { "type": "color", "default": "#fbf6e6" },
"softness": { "type": "number", "default": 0, "min": 0, "max": 4,
"description": "Blur over the finished tile, in px." },
"labels": { "type": "bool", "default": true },
"ink-mode": { "type": "string", "default": "multiply",
"enum": ["multiply", "darken", "normal"] }
}

Reference a param with $name anywhere a scalar field lives:

"bg": { "op": "solid", "color": "$paper" },
"out": { "op": "blur", "input": "@composite", "sigma": "$softness" }
Field Applies to Meaning
type all color, number, bool, or string — required
default all used when the caller supplies nothing — required
min / max number validated on every override; max also serves as the static bound for padding-determining fields
enum string the values the param may take; the default must be one of them, and every override is checked against the list
description all surfaced in the generated schema and the editor’s params panel

Colours are #rrggbb or #rrggbbaa. Bools are true / false. A string is any text unless it declares an enum.

Terminal window
# CLI — repeatable, validated against the declarations
ezu tile --style watercolor.json --tile 13/7276/3225 \
--param 'paper=#ffe0f0' --param softness=2 --out tile.png
# Tile server — query-string overrides on the tile endpoint
curl 'http://127.0.0.1:8080/tiles/13/7276/3225.png?paper=%23ffe0f0&softness=2'
let mut params = ezu::graph::ParamValues::new();
params.set("softness".into(), parse_param_value(&doc.params, "softness", "2")?);

Every path validates: a number outside min/max, a malformed colour, a non-boolean, or a string outside its enum fails before rendering. An unset param falls back to its default.

Params resolve at render time, so nothing rebuilds when a value changes, and the cache keys on the values each node actually reads — flipping one param re-evaluates only its subtree. That is what makes the editor’s params panel responsive on a style that takes hundreds of milliseconds cold.

Document::params_schema() derives a JSON Schema of the declarations — types, defaults, ranges, a string’s enum, descriptions:

Terminal window
curl http://127.0.0.1:8080/style/params

The same document is available wherever the style is loaded: the tile server serves it at /style/params, and in the browser it is the paramsSchema getter on the wasm Renderer.

Drive your own sliders and colour pickers off that rather than parsing the style. The live editor’s panel is generated from exactly this: a string with an enum becomes a menu of its values, and one without a text field.

Canvas padding is fixed at build time, so a field that decides it — blur sigma and friends — must carry an upper bound the build can see. A literal is its own; a $param qualifies only if it declares max:

"softness": { "type": "number", "default": 0, "min": 0, "max": 4 } // ✅ usable as a blur sigma
"softness": { "type": "number", "default": 0 } // ❌ build error

A @node port carries no bound, so the style states one beside it with a <field>-max — see padding and neighbours.

The bound is what the graph budgets padding for, so keep it as tight as the style actually needs — an unnecessarily large max costs render time on every tile.

A field that takes one of a few names — a blend mode, a stroke cap, a noise type — takes a string param:

"params": {
"ink-mode": { "type": "string", "default": "multiply",
"enum": ["multiply", "darken", "normal"] }
},
"nodes": {
"inked": { "op": "blend", "base": "@paper", "over": "@ink", "mode": "$ink-mode" }
}

The build checks the param against the field: its default, and every value its enum allows, must be names the field accepts, so a param that could be set to something the field has no meaning for fails there rather than at the render that sets it. Without an enum any string can arrive, and a name the field does not know fails that render. A @node cannot feed such a field, since no node produces a string.

Most of these fields follow the param per render. A few decide what the graph is — a node’s output kind, its ports, the padding it asks for, the neighbour tiles it requests — and the graph is fixed before anything renders. Those are read when the graph is built: a $param there gives its default, the build warns that an override will not reach the field, and --strict refuses it.

Read per render Read when the graph is built
blend mode, composite; stroke cap, join; buffer join; noise and warp type, anchor; warp and displace boundary; color-ramp interpolation, space; gradient-* anchor, space, interpolation; mix space; quantize and dither space, dither method; tiling anchor; hillshade mode; flow-field direction; flow-smear sides, taper; channel-shuffle r/g/b/a; pick-channel channel; place fit, anchor; stamp anchor; point-grid and point-scatter anchor; tile-bounds cover; graticule axes; boundary clip-edges; feature-boolean mode; label-boxes include; text and text-labels placement, anchor, justify, transform, overlap, polygon-anchor, icon-anchor, icon-text-fit; switch select, naming a branch kind of solid, circle, gradient-* and noise; math and field-math fn; expr type; mosaic anchor, mode; features stitch

A name inside an array — one entry of anchor-offsets, of parts — is a literal.

A label layer whose placement follows a param can place points on one render and lines on the next. Its point-only fields — the icon, the background box, rotate-deg, dedupe-key-expr, anchor-expr — apply on the renders that place points, and a literal placement: "line" still drops them with a warning when the graph is built. A *-expr field such as justify-expr still takes the place of its field for the features it names, whatever the param resolves the field to.

Some fields are not scalars but tables — a colour ramp’s stops, a palette, a dash pattern. A $param works in each entry too:

"tint": { "op": "color-ramp", "field": "@dem",
"stops": [ { "value": 0, "color": "$lowland" },
{ "value": "$treeline", "color": "$upland" } ] }

Two differences from a scalar field. A table entry takes a literal or a $param but not a @node: a port needs a fixed name and its own slot on the node, and a table of unknown length has neither. And a ramp is sorted by value on every render rather than at build time, since a param can reorder it — so stops need not be declared in order.

Params are inputs, not expressions. To combine them, wire scalars through the graph — a $param argument keeps its runtime-override behaviour all the way through:

"z": { "op": "zoom" },
"zfrac": { "op": "math", "fn": "div", "a": "@z", "b": 16 },
"lu_alpha": { "op": "math", "fn": "mul", "a": "$landuse-alpha", "b": "@zfrac" }

watercolor.json is a complete parametric style.

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.