Skip to content

Geometry

Features -> Features transforms. They rewrite geometry before anything is painted, so downstream paint ops stay simple.

22 ops: bbox, boundary, buffer, centroid, contour, convex-hull, dash, densify, dot-density, feature-boolean, hatch, junctions, medial-axis, resample, segment-to, simplify, smooth, transform, triangulate, voronoi, voronoi-fracture, wave.

Ops with no demo image below produce no visible result on their own — they feed other nodes rather than paint.

Axis-aligned bounding box of every input vertex (pooled across polygons, lines, and points), emitted as a single rectangular polygon.

A tile rendered with bbox
The bounding box of every input vertex — here, a shrunken cloud of water centroids. style
Field Type Required Description
features @node yes Reference to another node (@name).

Convert each polygon to its boundary rings (exterior + holes) as polylines. Existing polylines and points pass through.

A tile rendered with boundary
Polygon rings turned into lines — the coastline of every water body. style
Field Type Required Description
clip-edges keep | drop no What to do with the straight edges a vector tile adds where it clips a polygon at the tile buffer, just outside the tile. keep outlines them like any other edge; drop leaves them out, splitting a ring into open polylines where they were. Use drop when a wide stroke on the outline (a coastal band, say) shows a band along every tile seam: the stroke on those edges reaches into the tile. drop removes every segment that lies entirely past one side of the tile and runs within 15° of that side; the tolerance is what lets it work after a buffer, which can bend the clip line slightly. A real shoreline outside the tile running nearly parallel to its edge is dropped too, but nothing inside the tile goes missing, and a rectangle exactly on the tile border is kept.
features @node yes Reference to another node (@name).

Minkowski sum / offset with a disk. Polygons accept negative distance for erosion; lines and points are inflated by |distance|.

A tile rendered with buffer
Roads dilated into polygons, then filled. style
Field Type Required Description
distance number | $param | @node yes Offset distance in tile pixels. Positive inflates, negative erodes (polygons only).
features @node yes Reference to another node (@name).
join miter | round | bevel no —

Replace input geometry with one point per polygon / polyline centroid.

A tile rendered with centroid
One point per input polygon, drawn as a dot. style
Field Type Required Description
features @node yes Reference to another node (@name).
include-lines boolean no —
include-polygons boolean no —

Isolines from a ScalarField (marching squares with linear sub-cell interpolation), chained into polylines. One feature group per level with properties {"level": <number>}, so data-driven paint can style by level. Levels are base + k·interval, or the explicit levels array (which overrides both), clamped by min/max. Chain dem → contour → stroke for elevation contours, or density → contour → stroke for point-density isolines.

Field Type Required Description
base number | $param | @node no Offset the interval grid: levels sit at base + k·interval.
field @node yes Reference to another node (@name).
interval number | $param | @node no Spacing between levels, in field units. Required unless levels is given.
levels array of number | string no Explicit levels to extract; overrides interval/base. A level may be a $param; the list is sorted and de-duplicated per render.
max number no Emit no level above this value.
min number no Emit no level below this value.

Convex hull of every input vertex as a single polygon.

A tile rendered with convex-hull
The convex hull of every input vertex — here, a shrunken cloud of water centroids. style
Field Type Required Description
features @node yes Reference to another node (@name).

Cut polylines into dash/gap segments. Lengths are in canvas pixels (consistent with *-px brush parameters). Polygons and points pass through unchanged.

A tile rendered with dash
Coastlines cut into dashes before painting. style
Field Type Required Description
dash-px number (0…∞) | $param | @node yes —
features @node yes Reference to another node (@name).
gap-px number (0…∞) | $param | @node yes —
phase-px number | $param | @node no Initial offset into the dash/gap pattern, in pixels. May be negative.

Insert intermediate vertices so no segment of any polyline or polygon ring exceeds target-px. Original vertices are preserved.

Field Type Required Description
features @node yes Reference to another node (@name).
target-px number (0…∞) yes —

Scatter points inside polygons at a per-feature areal density (dot density map).

A tile rendered with dot-density
Dots spread over the landmass at 12,000 people per km², one dot per 100 people. A real dot density map reads the density from each feature with density-expr instead of stating one constant. style
Field Type Required Description
density number (0…∞) | $param | @node no Constant density in units per square kilometre, used where density-expr is absent.
density-expr — no Per-feature MapLibre expression giving density in units per square kilometre, e.g. [“/”, [“get”, “POP”], [“get”, “AREA_KM2”]].
dot-value number | $param | @node no Units one dot stands for. 100 means one dot per 100 people.
features @node yes Reference to another node (@name).
jitter number (0…1) | $param | @node no How far a dot may stray from its cell centre, as a fraction of the cell. 0 leaves a visible grid.
seed integer (0…∞) no Lattice salt; change it to reshuffle the dots without changing their density.
spacing-px number | $param | @node no Lattice cell size in screen pixels. One dot per cell at most, so this caps how dense the scatter can get.

Polygon boolean operation between two Features inputs. Subject = a, clip = b. Lines and points on either input are dropped — booleans are defined on polygons only.

Field Type Required Description
a @node yes Reference to another node (@name).
b @node yes Reference to another node (@name).
mode union | intersection | difference | symmetric-difference | xor no —

Parallel-line hatching of input polygons.

A tile rendered with hatch
Parallel-line hatching of the landmass polygons. style
Field Type Required Description
angle-deg number | $param | @node no Hatch direction (degrees, CCW from +X).
features @node yes Reference to another node (@name).
phase number | $param | @node no Per-line offset in spacing units (0..1 cycles through one period).
spacing number (0…∞) | $param | @node yes Perpendicular spacing between consecutive lines, in tile pixels.

One point wherever polylines end or meet, for marking a line network split into sections (a block perimeter cut into frontages, a route cut into stages) with a cross tick at each section boundary. Both ends of every polyline count; polygons and points are ignored. Each junction is its own feature carrying axis-deg and arms and none of the source properties, since it may join several features. axis-deg is the tick’s axis in degrees clockwise from the x axis, in [0, 180), so stamp with a horizontal bar sprite and "rotation-deg-expr": ["get", "axis-deg"] draws the tick: across the line at a loose end or where the line runs straight on, along the bisector at a corner, and through the middle of the widest gap between arms where the bisector would lie on or near an arm (a T-junction) or the corner is sharper than 40°. arms counts the line ends meeting there (1 for a loose end), for filtering. Loose ends a vector tile makes by cutting a line at its buffer are dropped: an end outside the tile, as far out as any geometry on that side, whose line runs back inward. A junction near a tile border comes out the same, with the same axis, from the tiles on either side. Feed it unstitched features: stitch cuts lines at the tile border, and the cut ends would meet as junctions there.

A tile rendered with junctions
Coastlines cut into 40 px sections, with a tick stamped across each section boundary at its axis-deg. style
Field Type Required Description
features @node yes Reference to another node (@name).
snap-px number (0…∞) | $param | @node no Line ends closer than this, in tile pixels, merge into one junction (chained: ends each within reach of a third merge too). Adjacent sections’ shared ends do not always coincide exactly: separate digitising, or rounding before the data reached the tile, can leave them a fraction of a pixel apart. 1 px merges those without joining ends a reader would see apart. Raise it for data digitised less carefully.

Approximate medial axis of each input polygon, emitted as polylines. Smaller densify-px → more accurate but slower; min-branch-px prunes short side branches after stitching.

A tile rendered with medial-axis
The skeleton of each polygon — a river’s centreline from its bank. style
Field Type Required Description
densify-px number (0.5…∞) | $param | @node no —
features @node yes Reference to another node (@name).
min-branch-px number (0…∞) | $param | @node no —

Resample each polyline / polygon ring to evenly-spaced vertices at spacing-px along arc length. Original vertices are not preserved.

Field Type Required Description
features @node yes Reference to another node (@name).
spacing-px number (0…∞) yes —

Turn each point into a two-vertex polyline from a position its feature’s properties give in longitude and latitude to the point itself: a leader from a label anchor to what it labels, a connector from a building’s representative point to its entrance. A polygon gets a line from that position towards its centroid that stops where it first meets the polygon’s outer edge, so a leader to a label box from label-boxes ends at the box instead of running under the text; one line per polygon, and none when the position lies inside or on the polygon (holes count as inside). Each line keeps its feature’s properties; dash it with dash and paint it with stroke. Lines are ignored, as are features whose fields are missing, not numbers (or numeric strings), or out of range, and points that coincide with their other end. A connector can cross a tile from a point outside it, beyond the tile’s thin buffer; set stitch: "pieces" on the upstream features node to bring in the neighbouring tiles’ points too.

Field Type Required Description
features @node yes Reference to another node (@name).
gap-px number (0…∞) | $param | @node no Pixels taken off the feature end of each line, at the point or the polygon’s edge, to leave air between the line and what it reaches. The other end stays put; a line no longer than the gap is dropped.
lat-field string yes Property holding the other end’s latitude, in WGS84 degrees.
lng-field string yes Property holding the other end’s longitude, in WGS84 degrees.

Douglas-Peucker simplify polylines and polygon rings; points pass through.

A tile rendered with simplify
Coastlines simplified with a generous tolerance, so the loss is visible. style
Field Type Required Description
epsilon number (0…∞) | $param | @node yes Max perpendicular distance a vertex may be from the simplified line, in tile pixels.
features @node yes Reference to another node (@name).

Chaikin corner-smoothing over each input polyline and polygon ring. Each iteration roughly doubles the vertex count; 2–3 iterations are usually enough.

A tile rendered with smooth
Coastlines smoothed, rounding off the vertex-to-vertex corners. style
Field Type Required Description
features @node yes Reference to another node (@name).
iterations integer (0…8) no —

Translate / rotate / scale every input vertex. Rotation happens around pivot (defaults to the origin) before the final translation. The scalar fields take a $param or an @node port and follow it per render; the [x, y] arrays are literal, and seed the matching scalars. Uniform scale multiplies the per-axis factors.

A tile rendered with transform
Water polygons scaled about their own centre. style
Field Type Required Description
features @node yes Reference to another node (@name).
pivot array of number no Literal [x, y] rotation centre in feature space.
rotation-deg number | $param | @node no —
scale number | $param | @node no —
scale-x number | $param | @node no —
scale-xy array of number no Literal per-axis scale. scale-x / scale-y override it; uniform scale multiplies it.
scale-y number | $param | @node no —
translate array of number no Literal [x, y] shift. translate-x / translate-y override it.
translate-x number | $param | @node no —
translate-y number | $param | @node no —

Delaunay triangulation of the input’s point set. Each triangle becomes a closed polygon. Polygons and lines on input are ignored.

A tile rendered with triangulate
Delaunay mesh over the building centroids. The op triangulates points, so polygons come through centroid first. style
Field Type Required Description
features @node yes Reference to another node (@name).

Voronoi diagram of the input’s point set. Output is a Features value carrying the Voronoi edges as polylines (each polyline is a single 2-point edge). Polygons and lines in the input are ignored — use centroid to derive points from them.

A tile rendered with voronoi
Voronoi cells around each input point. style
Field Type Required Description
features @node yes Reference to another node (@name).

Fracture each polygon in features into Voronoi sub-cells using the points in seeds as Voronoi sites. Cells are clipped against the source polygon. Each cell is its own feature, carrying the source polygon’s properties plus random: a value in [0, 1) drawn from its seed’s world position, the same in every tile, for per-cell colour through a fill-expr such as ["interpolate", ["linear"], ["get", "random"], 0, "#8c3a24", 1, "#b65a38"]. Lines/points on features and lines/polygons on seeds are ignored.

A tile rendered with voronoi-fracture
Polygons shattered into Voronoi shards — the crackle-glaze effect. style
Field Type Required Description
aspect number | $param | @node no Weight on vertical distance when choosing each point’s nearest seed. > 1 stretches cells along X, < 1 along Y — seeds on a staggered point-grid with aspect well above 1 give the rectangles of a brick bond rather than hexagons.
features @node yes Reference to another node (@name).
seeds @node yes Reference to another node (@name).

Displace polylines laterally with a sine wave. Lengths in canvas pixels. Sharp corners may kink (no tangent smoothing). Polygons/points pass through.

A tile rendered with wave
Road geometry perturbed sideways — the wobble behind hand-drawn styles. style
Field Type Required Description
amplitude-px number | $param | @node yes Peak lateral deviation in pixels. May be negative to flip phase.
features @node yes Reference to another node (@name).
noise-amp-px number | $param | @node no Peak 1D-value-noise jitter added on top of the sine, in pixels. 0 = pure sine (default).
noise-scale-px number (0.5…∞) | $param | @node no Cell length of the noise jitter along arc length, in pixels. Defaults to wavelength-px.
phase-px number | $param | @node no Offset into the wave at the polyline start, in pixels.
samples-per-wavelength number (2…256) no Subdivisions per wavelength. Higher = smoother but more vertices. Default 16.
seed integer (0…∞) no Optional explicit u32 seed for the world-anchored 2D value noise. Default: a fixed constant so adjacent tiles agree across the seam.
wavelength-px number (0.5…∞) | $param | @node yes —

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.