Table Source Layers
Not fully implemented
The bulk mode of martin cp is in the release binaries since 2.0.0-beta.6, but parts of what these
pages describe are not implemented yet, and the configuration of layers may still change.
- Rendering points, lines and polygons, with fill ranges for covered tiles
- Tile grouping, MLT and MVT encoding, compression and deduplication
- Planning tables into layers and running a whole generation end to end
- The PostgreSQL source with partitioned whole-table scans
- Bulk mode in
martin cp, and PMTiles output - Layers with CEL expressions,
rulesandsort_by - OSM PBF, shapefile, GeoPackage and CSV files as sources
-
geometryother thanpoint,lineandpolygon -
tileoperations (merge_lines,merge_polygons,merge_multi,label_grid,dedup_by,limit)
layers is a key of a PostgreSQL table source. It turns one table into one or
more output layers, all fed by a single read of the table, each choosing its features, zooms, attributes
and draw order. The bulk mode of martin cp writes them into a tileset, and the
tile server renders them on request, see Serving.
A table source without layers is one layer, named by its layer_id or by its source id, holding all
of its properties and its feature ids. A table source cannot set both layer_id and layers.
postgres:
tables:
roads:
schema: public
table: roads
srid: 4326
geometry_column: geom
id_column: osm_id
properties:
class: text
name: text
"name:de": text
layers:
roads:
geometry: line
attributes: [class]
road_labels:
minzoom: 10
simplify: 2
attributes: [name, "name:*"]
id: false
Every feature is offered to each layer, which may take it or not. Inside each tile, the layers of one
table come in the order they are listed. Tables come in the order of --source, or ordered by source id
without it. Layer names must be unique across all sources, and a run holds at most 256 layers.
Layer keys
| Key | Meaning | Details |
|---|---|---|
minzoom, maxzoom |
A number, or an expression for each feature's own zoom | Zooms |
extent, buffer, clip_geom |
Override the table source's | |
simplify, simplify_at_maxzoom |
Pixels of tolerance when simplifying lines and polygons | Simplification |
min_size, min_size_at_maxzoom |
Pixels, below which lines and polygons are dropped | Simplification |
geometry |
point, line or polygon: only features of that type |
Features |
where |
An expression: only features for which it is true |
Features |
attributes |
Which properties to write, and under which names | Attributes |
id |
true (default), false, or an expression |
Feature ids |
rules |
Different zooms, simplification and attributes for some features | Rules |
sort_by |
The draw order of the layer's features in each tile | Draw order |
extent, buffer and clip_geom default to the table source's, then to 4096, 64 and true.
extent must be a power of two from 64 to 2097152, and buffer less than half the extent.
With clip_geom: false, every tile a line or polygon reaches holds all of it, not just its part there.
Properties
A layer can only use the table source's properties, both as attributes and in expressions. An
auto-published table has all of its columns as properties, but a table source in the configuration file
only has the properties it lists. The id_column is not a property. To use it in an expression, list it
under properties as well.
When the table has a jsonb property, a name that is not a column is read as a key of it. Otherwise,
an unknown name fails the run. A prefix such as name:* only matches columns, not jsonb keys.
Only the columns that some layer uses are read.
Zooms
Zooms narrow from the run down to each feature: every level can raise the lowest zoom and lower the highest, never widen what the level above allows.
--min-zoomand--max-zoomset the run's zooms. A table source'sminzoomandmaxzoomnarrow them. A table source left with no zoom fails the run.- A layer's
minzoomandmaxzoomnumbers narrow the table source's. A layer left with no zoom is skipped with a warning. - A
minzoomormaxzoomexpression gives each feature its own zooms within the layer's. Its result is clamped to the layer's zooms, andnullkeeps the layer's bound. A feature whoseminzoomends up above itsmaxzoomis left out of the layer. - An attribute with
minzoomormaxzoomis only written at those zooms of the feature.
A rule can set minzoom and maxzoom too. For the features it takes, they replace the
layer's expressions.
The highest zoom is 27.
Simplification and minimum size
Lines and polygons are simplified at each zoom, and dropped once they are smaller than min_size.
Both are in pixels of a 256-pixel tile, whatever the extent, and default to the values
Planetiler uses.
| Key | Below the layer's max zoom | At the layer's max zoom |
|---|---|---|
simplify |
simplify, default 0.1 |
simplify_at_maxzoom, default simplify or 1/16 |
min_size |
min_size, default 1 |
min_size_at_maxzoom, default min_size or 1/16 |
The layer's max zoom is its highest zoom after narrowing, not the highest zoom of a single feature.
A rule's simplify and min_size apply at every zoom, including the max zoom.
Features
A feature belongs to a layer when all of these hold:
- the table source's
filter, which the database applies while reading, - the layer's
geometrytype, if set, - the layer's
whereexpression, if set.nulland failed evaluations count asfalse.
A rule's where is not one of them.
A feature that matches no rule stays in the layer.
The database also applies the geometry and where of every layer at the zoom being read, so rows no
layer takes are never read. This needs a SQL equivalent for each where of the table. These have one:
- comparisons of a column with a literal,
==or!=between two columns, unless both are floats,inwith a list of literals,has(feature.x)and'x' in feature,&&,||,!and? :,size,startsWith,endsWith,contains,lowerAsciiandupperAsciion a string column, compared with a literal.
Columns of type bpchar or name have none, as PostgreSQL compares them differently.
When a layer reads a jsonb property, no where has one, as any key may share a column's name.
Listing attributes without the jsonb property's keys avoids that.
Without a SQL equivalent, Martin reads every row and checks where itself, with the same result.
Attributes
Without attributes, a layer writes all of the table source's properties. [] writes none.
A list copies those properties as they are. A map names each output attribute:
- a string is an expression, so
kind: classwrites the columnclassaskind, { value: ... }, a number or a boolean is a literal,{ expr: ..., minzoom: ..., maxzoom: ... }is only written at those zooms.
In both, name:* copies the columns starting with name:, written "name:*": "name:*" in a map.
An expression that is null leaves the attribute out.
Feature ids
id: true writes the id_column as the feature id, id: false writes none, and an expression
computes it. A computed id is only written when it is an integer of zero or more.
Rules
rules is a list. The first rule whose where holds for a feature applies to it, and a last rule
without where takes every other feature. A feature that matches no rule keeps the layer's settings.
postgres:
tables:
roads:
schema: public
table: roads
geometry_column: geom
properties:
class: text
layers:
roads:
attributes:
kind: class
rules:
- where: "class == 'motorway'"
minzoom: 4
attributes:
kind: { value: major }
- where: "class == 'path'"
minzoom: 12
simplify: 0
- minzoom: 8
A rule can set:
minzoomandmaxzoom, numbers or expressions. They replace the layer's zoom expressions and are clamped to the layer's zooms. A rule's expression that isnullfalls back to the layer's.simplifyandmin_size, at every zoom.attributes, as a map without*prefixes. They replace the layer's attributes of the same name, at all zooms, and add the others.
Draw order
Without sort_by, the features of a layer keep the order the source reads them in.
- A table is read in its physical order.
- A view or foreign table with an integer
id_columnis read in 32 partitions by id remainder (abs(id % 32)), which interleaves the ids and overrides the view'sORDER BY. - Any other view is read in its own order.
The tile server reads each tile's features in the order PostgreSQL finds them, which can
differ from request to request, so only sort_by gives served tiles a fixed draw order.
sort_by is a list of expressions, each a string or { expr: ..., desc: true }, compared in turn.
Features that compare equal keep the order they are read in.
postgres:
tables:
buildings:
schema: public
table: buildings
geometry_column: geom
properties:
z_order: int4
height: float8
layers:
buildings:
sort_by: ["z_order", { expr: "height", desc: true }]
Values order by type first: null (and failed evaluations), booleans, integers, floating-point
numbers, then strings. Any integer sorts before any floating-point number, so convert mixed numbers
with double(...) to compare them by value. desc reverses the whole order, so null comes last.
Expressions
Expressions are written in CEL, the Common Expression Language. CEL looks like the conditions of C, Java or JavaScript, but it has no statements, loops or side effects. Every expression returns one value, and it always finishes.
A property is read by its name, such as rank > 5, or with feature['name:en'] when the name is not
an identifier. A missing property is null. Strings take single or double quotes, so quote the whole
expression in YAML, for example where: "kind == 'park'".
population > 1000000 ? 2 : 8 // 2 for a city of 5 million, else 8
height > 0 ? height : 'unknown' // the two branches can have different types
1 ? 2 : 3 // fails, the condition must be a boolean
When an attribute gets different types in one tile, such as numbers and strings, all its values in that tile are written as strings.
'Main' + ' St' // 'Main St'
size('Straße') // 6
'Main St'.startsWith('Main') // true
'Main St'.endsWith('St') // true
'Main St'.contains('n S') // true
'Main St'.lowerAscii() // 'main st'
' Main St '.trim() // 'Main St'
'Main St'.replace('St', 'Street') // 'Main Street'
'Main St'.substring(0, 4) // 'Main'
'Main St'.indexOf('St') // 5
Martin adds these functions to CEL:
coalesce(a, b, ...)returns its first argument that is notnull.
Regular expressions (matches) and timestamps are not available.
| Expression | Result |
|---|---|
where |
A boolean, where null counts as false |
minzoom, maxzoom |
An integer, clamped to the layer's zooms, or null for the layer's |
id |
A non-negative integer, else the feature has no id |
| attribute | Any value, where null leaves the attribute out |
sort_by |
Any value, see draw order |
An expression that fails on a feature, for example by dividing by zero or by returning a string where
a zoom is needed, counts as null there. The first failure of each expression is logged, and the run
ends with how often each one failed.
postgres:
tables:
places:
schema: public
table: places
geometry_column: geom
properties:
osm_id: int8
class: text
name: text
"name:en": text
population: int8
layers:
places:
where: "population > 1000"
minzoom: "population > 1000000 ? 2 : 8"
id: "osm_id * 10"
attributes:
kind: class
label: { expr: "coalesce(feature['name:en'], name)", minzoom: 10 }
source: { value: osm }
rank: 3
Serving
The tile server renders a table source with layers itself instead of asking PostGIS for an
ST_AsMVT tile. Each request reads the whole geometry of every feature that reaches the tile or its
buffer, and renders the tile bulk mode writes for the same maxzoom. Serving tiles
shows how.
- The highest zoom is the table source's
maxzoom. Without one, it is the highest zoom theextentfits: 18 for anextentof 4096. A highermaxzoomfails at startup. - Tiles are MVT, on the Web Mercator tile grid only, and the source takes no URL query parameters.
max_feature_countlimits the features read for each tile, or for each block withprefetch. The database must apply everywherefirst, see Features, or startup fails. With it,order_bychooses which are kept.order_byalone changes nothing, assort_bysets the draw order.prefetch: N, on the table source or as the default underpostgres, renders the N×N block holding a tile on a cache miss, in one query, and caches all of its tiles. N is from 1 to 16, and 1 renders each tile alone. Blocks are aligned to multiples of N, so the tiles of one block share it. Without a tile cache, or outside its zooms, each tile is rendered alone. A table withprefetchneedslayers.
postgres:
prefetch: 2
tables:
roads:
schema: public
table: roads
geometry_column: geom
id_column: osm_id
prefetch: 4
properties:
class: text
layers:
roads: {}
Validation
- Reading the configuration fails on invalid values, with their line and column: a zoom above 30,
a negative
simplify, aminzoomabovemaxzoom, an expression that is not valid CEL, orlayer_idtogether withlayers. Unknown keys are warned about. - Before rendering, after connecting to the database, the run fails on unknown property names, duplicate layer names, more than 256 layers, a zoom above 27, and the settings that are not supported yet.
martin test checks what the layers make of real rows of the database, and keeps it in
a spec file.