OSM Vector Tiles & Rendering Pipelines Jump to heading

The stages between normalized OSM features and a tile a map client can draw Normalized OSM features enter a layer assignment step that decides which tile layer each feature belongs to and at which zoom levels it should appear. A generalization step simplifies geometry and merges features for the zoom being produced. An encoding step projects coordinates into each tile's local integer grid and writes the vector tile. A packaging step assembles the tiles into an archive. A serving step delivers them to clients, where a style turns the abstract layers into pixels. Five stages, and the style is not one of them normalized OSM features and tags assign layers which layer, which zooms generalize simplify and merge encode MVT tile-local integers serve archive plus style The style lives with the client, not the tiles: a tile carries abstract layers and attributes, never colours or widths.
Keeping styling out of the pipeline is what lets one tile set serve a dark map, a print map and a data inspector.

Everything earlier on this site produces data: parsed elements, normalized tags, validated geometry, rows in a warehouse. This section covers the point at which that data has to be looked at — and the specific, unusual constraints that appear when a continent of geometry must be delivered to a browser a few hundred kilobytes at a time.

It serves mapping engineers building a tile pipeline, ETL developers whose normalized output feeds a map, and GIS analysts who need to understand why a feature visible at one zoom vanishes at the next. The unifying idea is that vector tiles are not a picture format and not a data format, but a transport format with its own geometry model, and most of the surprises come from treating them as either of the other two.

The Tile Model Jump to heading

A tile set is a pyramid. At zoom 0 the whole world is one tile; each zoom level quadruples the tile count, so zoom 14 has over 268 million tiles and zoom 20 has more than a trillion. That growth is the central engineering fact of the whole section: you cannot render every tile at every zoom, so a tile pipeline is mostly a set of decisions about what to leave out.

Tiles are addressed by a zoom level and an x/y pair in a quadtree, which is the same structure discussed in Spatial Index Selection: R-tree vs H3 vs Quadkey — a quadkey is a tile address written as a single string, and the interchangeability is useful when you want to join tiles to indexed data.

Each tile carries layers, each layer carries features, and each feature carries geometry plus attributes. Crucially, the tile carries no styling: no colours, no line widths, no fonts. Those live in a style document applied by the client. A pipeline that bakes styling decisions into tile attributes has confused the two, and will need to regenerate the whole pyramid the first time a designer changes their mind.

What lives inside one vector tile and what deliberately does not A single tile broken into four parts. Layers are named groups such as roads, buildings and water, each with its own feature set. Features carry a geometry type and a tile-local integer geometry. Attributes are key-value pairs carried per feature, encoded against a per-layer key and value table so repeated values are stored once. The fourth part, styling, is shown as deliberately absent: colours, widths and fonts live in the client's style document, not in the tile. Three things inside, one thing deliberately outside layers named groups roads, buildings, water each with its own features the style targets these names features type plus geometry point, line or polygon tile-local integer coordinates clipped to the tile plus buffer attributes key-value pairs deduplicated per layer keys and values in tables keep them few and small not styling lives in the client no colours, no widths no fonts, no z-order changing style needs no rebuild Attribute bloat is the most common cause of oversized tiles, because every repeated string costs space in every tile that carries it.
The fourth column is the one that makes vector tiles worth the trouble: restyling is a client-side change, not a rebuild.

Geometry: the Tile-Local Integer Grid Jump to heading

The single most consequential detail of the Mapbox Vector Tile format is that geometry is not stored in geographic coordinates. Each tile defines an extent — conventionally 4096 — and all coordinates inside it are integers on a grid from 0 to that extent, relative to the tile’s own corner.

Three things follow, and each one causes a recognisable class of bug.

Precision is a function of zoom. At the equator, a zoom-14 tile is roughly 2.4 kilometres across, so one grid unit at extent 4096 is about 60 centimetres. At zoom 8 the same grid spans a tile roughly 156 kilometres wide, and one unit is about 38 metres. Geometry is quantised to that grid, which is why a coastline traced to centimetre precision looks blocky at low zoom regardless of how carefully it was simplified.

Features must be clipped. A road crossing a tile boundary appears in both tiles, clipped at the edge. Clipping exactly at the boundary produces visible seams when the client draws a thick line, which is why tiles carry a buffer: geometry is kept slightly beyond the edge so the client has something to draw into the margin. Choosing that buffer is a real trade-off, developed in Choosing Tile Extent and Buffer Values.

A feature can be split. A long motorway is not one feature in the tile set; it is one feature per tile it passes through, each carrying the same identifier and attributes. Clients that assume feature uniqueness across tiles — for hover highlighting, for counting — get this wrong in ways that only appear at tile boundaries.

Generators and What They Assume Jump to heading

Three tools dominate OSM tile production, and they differ less in output than in what they expect you to have already done.

Tippecanoe takes GeoJSON and produces an MBTiles archive. It is unopinionated about schema — whatever properties your features carry become tile attributes — and extremely good at the zoom-dependent decisions: dropping features as you zoom out, coalescing, and hitting a tile size budget automatically. It expects you to have already turned OSM into GeoJSON, which is a job for the export workflows in Exporting OSM to GeoParquet & PostGIS. Building OSM Tiles with Tippecanoe covers its model.

Planetiler reads a PBF directly and produces a tile archive in one pass, with the schema expressed in code. It is built for planet-scale throughput and is the fastest route from an extract to a complete tile set, at the cost of a less flexible intermediate stage.

Tilemaker also reads PBF directly, with the schema expressed as a Lua profile plus a JSON configuration. It is lighter than Planetiler and the profile is easy to iterate on, which makes it a good fit for a custom schema over a regional extract. Both are covered in Planetiler & Tilemaker Workflows.

The three OSM tile generators compared on input, schema definition and scale A grid comparing Tippecanoe, Planetiler and Tilemaker across four properties. Tippecanoe takes GeoJSON input, derives the schema from feature properties, excels at zoom-dependent feature dropping, and suits regional and thematic tile sets. Planetiler reads PBF directly, defines the schema in Java code, is built for planet-scale throughput, and suits complete base maps. Tilemaker reads PBF directly, defines the schema in a Lua profile, is moderate in throughput, and suits custom schemas over regional extracts. Pick on where your schema lives, not on speed Tippecanoe Planetiler Tilemaker Input GeoJSON PBF directly PBF directly Schema lives in feature properties Java code a Lua profile Strongest at zoom dropping planet throughput custom schemas Natural fit thematic layers complete base maps regional maps All three produce standards-compliant tiles, so the choice is about your workflow rather than about what clients can read.
The schema row is the one that decides: it determines who on your team can change what a layer contains.

Generalization: What to Draw When You Zoom Out Jump to heading

A base map at zoom 5 cannot contain every building in Europe, and the tile size budget will not permit it even if you wanted to. Generalization is the set of decisions that make a low-zoom tile both small and legible, and it has three distinct mechanisms.

Selection drops whole features below a zoom: minor roads disappear before major ones, small lakes before large ones. This is usually driven by a rank attribute computed once during layer assignment rather than recomputed per zoom.

Simplification reduces vertex counts. The important subtlety is that simplification tolerance should be expressed in tile grid units, not metres, because the grid is what the geometry will be quantised to anyway. Simplifying to a finer tolerance than the grid does nothing except cost time.

Aggregation merges adjacent features into one — a block of buildings into a built-up area, a cluster of small water bodies into a single polygon. This is the hardest of the three and the one that most improves low-zoom legibility. Merging Adjacent OSM Polygons for Low Zoom works through it, and Simplifying OSM Geometry per Zoom Level covers the first two.

Schema Design: the Contract Between Pipeline and Style Jump to heading

Between the OSM tag model and the map a reader sees sits a tile schema: the list of layer names, the attributes each layer carries, and the zoom range over which each feature type appears. It is the most under-appreciated artefact in the whole pipeline, because it is simultaneously a data contract and a design document, and the two audiences want different things from it.

The pipeline wants a schema that is cheap to produce: attributes derived directly from tags, few enough distinct values that the per-layer value tables stay small, and rules simple enough to evaluate per feature without a database lookup. The style wants a schema that is expressive: enough distinction between a motorway and a residential street to draw them differently, enough attributes to place labels, and stable layer names that do not change when the pipeline is refactored.

Three conventions keep both audiences satisfied. Name layers by what they are, not by how they look — a layer called transportation survives a redesign that a layer called thick_orange_lines does not. Collapse tag values into a small closed vocabulary rather than passing raw OSM values through: mapping several dozen highway values onto half a dozen classes shrinks the value table, makes the style simpler, and insulates the map from a new tag appearing upstream. And version the schema explicitly, because a style built against one version of a layer list will break silently against another — a feature that stops appearing is much harder to notice than one that errors.

Reusing an established schema rather than inventing one is usually the right call for a general-purpose base map, because it lets you adopt existing styles without writing one. A custom schema earns its keep when the map is thematic — a cycling map, an accessibility map, a utility network — and the attributes you need are precisely the ones a general schema discards. The tag-to-schema mapping itself is the same work as Mapping OSM Tags to a Fixed Schema with YAML, applied with a cartographic rather than an analytic target.

Counting the Pyramid Before You Build It Jump to heading

The arithmetic of tile counts is worth doing explicitly once, because it converts vague worries about scale into a number you can plan around. A zoom level (z) contains (4^{z}) tiles covering the whole world, so the cumulative count from zoom 0 to a maximum (Z) is

N(Z)=z=0Z4z=4Z+113.N(Z) = \sum_{z=0}^{Z} 4^{z} = \frac{4^{Z+1} - 1}{3}.

For (Z = 14) that is over 357 million tiles worldwide — but the great majority of them are ocean, and a generator that skips empty tiles produces a small fraction of that. For a single country the land-covering share is what matters, and it scales with area rather than with the world total.

Two planning rules follow. First, the maximum zoom is the dominant cost decision: each additional level roughly quadruples the tile count, so extending from zoom 14 to zoom 16 is a sixteen-fold increase for detail most readers reach by overzooming anyway. Second, empty tiles must be genuinely skipped, not stored empty, because three hundred million zero-byte objects is a storage problem in its own right regardless of the bytes involved. Both are decisions to make before the first full run, not after it has been going for six hours.

Serving, Packaging and Invalidation Jump to heading

A generated tile set has to reach clients, and the packaging choice shapes the operational model.

MBTiles is a SQLite database of tiles, easy to generate and to query, requiring a server process to read it. PMTiles is a single file with an embedded index designed to be read by HTTP range requests, which means it can be served directly from object storage with no server at all — an operationally dramatic simplification covered in Serving PMTiles from Object Storage. Loose directories of individual tile files are simple but produce hundreds of millions of small objects, which most storage systems handle poorly.

Invalidation is where tiles meet the rest of this site. When an OSM diff arrives, some tiles are now wrong and most are not. Regenerating everything is wasteful; regenerating nothing is incorrect. The middle path is to compute a dirty tile list from the changed elements’ coordinates, which is exactly what Computing a Dirty Tile List from an .osc File does, and then re-render only those — plus their ancestors, since a change at zoom 14 also affects the zoom 13 tile containing it. Invalidating Tile Caches After an OSM Diff covers the cache side.

Validation and Error Handling Jump to heading

Condition Root cause Detection Remediation
Visible seams at tile edges Buffer too small for the rendered line width Gaps along tile boundaries at some zooms Increase the buffer, or reduce the styled width
Tiles exceed the size budget Too many features or attributes at that zoom Generator warnings, or oversized tiles in the archive Drop features by rank; prune attributes
Features vanish unpredictably Automatic feature dropping to meet the budget Missing features correlate with dense areas Make the dropping explicit and rank-driven
Blocky geometry at low zoom Grid quantisation, not simplification Vertices snap to a coarse lattice Expected; simplify in grid units and accept it
Duplicate features on hover One feature split across several tiles The same id appears in adjacent tiles Deduplicate on feature id in the client
Restyling requires a rebuild Styling baked into attributes Colour or width values present in tile data Move styling to the client style document
Stale tiles after an update No invalidation from the diff stream Old geometry persists in cached tiles Compute dirty tiles and purge those keys

Performance and Scale Jump to heading

Two budgets govern a tile pipeline, and they pull against each other.

The per-tile size budget is what clients can fetch and decode quickly — a few hundred kilobytes is the usual working ceiling, and well under a hundred is a better target for a base map. Every feature and every attribute competes for it.

The total generation cost is the pyramid. Generating zoom 0 to 14 for a continent is a large batch job; generating zoom 0 to 20 is not feasible, which is why high-zoom detail is normally served by overzooming — the client scales a zoom-14 tile up rather than fetching a zoom-18 one that was never generated.

The practical consequences: choose a maximum zoom deliberately and accept overzoom above it; rank features once so zoom-dependent dropping is a lookup rather than a computation; and treat attribute count as a first-class budget line, because a string attribute repeated across a million features is measured in gigabytes.

Licensing and Attribution Jump to heading

Vector tiles derived from OpenStreetMap are a derived database under the Open Database Licence, and the attribution obligation travels with them. Practically this means the map client must display attribution, and the tile set’s metadata should record its source and the extract it came from. Because a tile set is typically served to many consumers who never see your pipeline, the attribution in the style document is the only place most people will encounter it — which makes it a required output of the pipeline rather than a courtesy. The obligations themselves are worked through in OSM Licensing & ODbL Compliance.

Topics in This Section Jump to heading

Frequently Asked Questions Jump to heading

Why is my geometry blocky at low zoom even though I did not simplify it?

Because vector tile geometry is quantised to an integer grid defined by the tile’s extent. At zoom 8 with the conventional extent, one grid unit is tens of metres, so every coordinate snaps to a lattice of that spacing regardless of the precision in your source data. This is inherent to the format rather than a defect, and it is why simplification tolerance should be expressed in grid units — simplifying finer than the grid costs time and changes nothing.

Why does the same road appear as several features?

Because features are clipped to tile boundaries, so a road crossing three tiles exists as three separate geometries, one per tile, each carrying the same identifier and attributes. This is how the format keeps tiles independent and cacheable. Clients that highlight on hover or count features need to deduplicate on the feature identifier, otherwise a long road appears once per tile it touches.

How do I stop features from disappearing as I zoom out?

Make the dropping explicit rather than leaving it to the generator’s size-budget heuristic. Compute a rank attribute for every feature during layer assignment — road classification, water body area, settlement population — and drive minimum zoom from that rank. The generator then drops by your rule rather than by whichever features happened to be encountered when the tile ran out of room, which is what makes the result predictable and reviewable.

Do I need a tile server?

Not necessarily. A single-file archive with an embedded index can be served directly from object storage using HTTP range requests, which removes the server entirely and leaves you with a static file and a content delivery network. A server process is still worth having when you need per-request logic — access control, on-the-fly filtering, or dynamic layers — but for a static base map it is an operational cost with no corresponding benefit.

How do I update tiles after an OSM diff without regenerating everything?

Compute the set of tiles the changed elements touch, expand it to include ancestor tiles at lower zooms, and re-render only those. A minutely diff typically touches a tiny fraction of a continent’s tiles, so the saving is enormous. The ancestors matter because a change at high zoom also alters the generalized representation at lower zooms, and forgetting them leaves a map that is correct when you zoom in and wrong when you zoom out.

Up one level: OSM Data Processing & QA Pipelines.