Cartographic Choices
How the Woosmap tile schema decides what appears, when, and how.
The tile generator does the heavy lifting — classification, ranking, height arithmetic, name translation — so the style layer gets clean attributes it can filter on directly.
Places — Why This City Before That One?
Place labels come from two sources depending on zoom:
| Zoom | Source | What appears |
|---|---|---|
| z0–z6 | Natural Earth | Countries and states, ranked by the dataset's built-in rank field |
| z7+ | OpenStreetMap | Cities, towns, villages, hamlets, suburbs, islands |
Country visibility (Natural Earth)
| Rank | Min zoom | Examples |
|---|---|---|
| 1 | z0 | China, USA, Russia, India |
| 2 | z1 | France, Germany, Japan |
| 3+ | z2 | Belgium, Singapore |
Rank comes from Natural Earth's own ranking. We don't recompute it.
City and town visibility (OSM)
Each OSM place gets a rank derived from its population tag:
| Population | Rank |
|---|---|
| >= 1,000,000 | 1 |
| >= 500,000 | 2 |
| >= 100,000 | 3 |
| >= 50,000 | 4 |
| >= 10,000 | 5 |
| >= 5,000 | 6 |
| >= 1,000 | 7 |
| < 1,000 | 8 |
| unknown | 10 |
But the min zoom is driven by place class, not rank:
| Class | Min zoom |
|---|---|
| city | z6 |
| town | z7 |
| village | z10 |
| hamlet, neighbourhood, suburb, island, islet | z12 |
| state (rank <= 2) | z3 |
| state (rank > 2) | z5 |
So Paris (city, pop 2.1M, rank 1) and Limoges (city, pop 130k, rank 3) both appear at z6. The style layer can further filter on rank to show Paris first and Limoges a couple of zooms later. The tile provides the data; the style makes the final call.
Roads — Classification Hierarchy
OSM highway tags are grouped into 8 classes:
| Class | OSM tags | Min zoom |
|---|---|---|
| motorway | motorway, motorway_link |
z4 |
| trunk | trunk, trunk_link |
z5 |
| primary | primary, primary_link |
z7 |
| secondary | secondary, secondary_link |
z9 |
| tertiary | tertiary, tertiary_link |
z11 |
| minor | residential, living_street, unclassified |
z12 |
| service | service |
z12 |
| path | pedestrian, footway, cycleway, steps, bridleway, track |
z13 |
Pre-split by structure
Roads are emitted to three separate MVT layers based on structure:
| MVT layer | Contains |
|---|---|
roads_tunnel |
Roads inside tunnels |
roads |
Normal roads (plus fords) |
roads_bridge |
Roads on bridges |
This eliminates the need for brunnel filters in the style — the layer name encodes the structure. The style renders tunnel layers first, then normal roads, then bridges on top, achieving correct z-ordering without runtime filtering.
Additional road attributes
- ramp:
1for*_linkhighways (on/off ramps) — style can draw them thinner - oneway:
1for forward,-1for reverse — enables directional arrows - service:
parking_aisle,driveway, oralley— finer control over service road rendering
Road Labels — Shields and Route Refs
Road labels are separate from road geometry so the style can place them independently (symbol layer vs line layer).
Route reference shields
When a road has a ref tag (e.g. "A1", "I-95", "US-101"), the tile emits:
ref: the raw reference stringref_length: character count — the style uses this to size the shield background
US network classification
US highway shields need different icons. The tile classifies the network:
| Condition | Network value |
|---|---|
network tag contains US:I |
us-interstate |
network tag contains US:US |
us-highway |
network tag contains US: |
us-state |
ref starts with I or I- |
us-interstate |
ref starts with US or US- |
us-highway |
The network tag takes precedence over ref prefix guessing when both exist.
Buildings — Height and 3D Extrusion
Height computation
The tile computes render-ready heights so the style doesn't have to:
| Priority | Source | Computation |
|---|---|---|
| 1 | height tag |
Direct numeric value (meters) |
| 2 | building:levels tag |
Levels × 3 meters |
| 3 | (fallback) | 5 meters |
Same fallback chain for render_min_height (from min_height or building:min_level × 3, default 0).
The hide_3d flag
Generic buildings tagged just building=yes with no height or levels info get hide_3d=1. The style uses this to suppress 3D extrusion for these features — extruding thousands of unknown-height buildings to a default 5m looks bad. Named building types (residential, commercial, hospital, etc.) always get extruded.
Building classes
| Class | When |
|---|---|
| residential, commercial, industrial, retail, warehouse, church, school, hospital, garage | Matching building=<value> |
| building | Everything else |
Water Labels
Water feature labels use different geometries depending on the type:
| Class | Geometry | Min zoom | Why |
|---|---|---|---|
| ocean | point | z0 | Label placed at a single point |
| sea | point | z3 | Same as ocean |
| lake | point | z8 | Centroid label |
| river | line | z10 | Label follows the river path for curved text |
Rivers are the only water labels emitted as lines — this lets the style render text that curves along the waterway using symbol-placement: line.
POI — What Makes the Cut
Not every OSM node becomes a POI. The tile classifies raw OSM tags into a single type attribute using a direct mapping. Only features that match a known tag/value combination get through — everything else is dropped at tile generation time. This keeps tiles small and focused on what the style actually renders.
There's no two-level class/subclass taxonomy. That's OpenMapTiles baggage. One field — type — is all the style needs to filter on.
OSM tag → type mapping
The tile checks these OSM tag keys in order and maps the first match:
| OSM tag | type value |
|---|---|
amenity=restaurant |
restaurant |
amenity=cafe |
cafe |
amenity=fast_food |
fast_food |
amenity=bar |
bar |
amenity=pub |
pub |
amenity=bank |
bank |
amenity=atm |
atm |
amenity=hospital |
hospital |
amenity=pharmacy |
pharmacy |
amenity=school |
school |
amenity=university |
university |
amenity=college |
college |
amenity=library |
library |
amenity=place_of_worship |
place_of_worship |
amenity=police |
police |
amenity=post_office |
post_office |
amenity=cinema |
cinema |
amenity=fuel |
fuel |
amenity=parking |
parking |
amenity=townhall |
townhall |
shop=mall |
mall |
shop=supermarket |
grocery |
shop=greengrocer |
grocery |
shop=convenience |
grocery |
shop=butcher |
butcher |
shop=bakery |
bakery |
shop=toys |
toys |
shop=electronics |
electronics |
shop=furniture |
furniture |
shop=sports |
sports |
shop=clothes |
clothes |
tourism=hotel |
hotel |
tourism=museum |
museum |
tourism=attraction |
attraction |
tourism=zoo |
zoo |
leisure=park |
park |
leisure=sports_centre |
sports_centre |
leisure=stadium |
stadium |
leisure=golf_course |
golf_course |
historic=castle |
castle |
historic=monument |
monument |
railway=station |
station |
railway=halt |
halt |
railway=tram_stop |
tram_stop |
highway=bus_stop |
bus_stop |
Note that some shop values merge into a single type: supermarket, greengrocer, and convenience all become grocery.
Large-geometry pull-up
Most POIs enter the tile at z12. But some POI types have large physical footprints — a university campus, a city park, a golf course. These are visible on the map long before z12, so their labels should appear earlier too.
For polygon POIs with one of these types, the min zoom drops to z10 with a pixel-size gate: the feature must occupy at least 12×12 pixels (~2% of a 256px tile) to appear below z13. This means a large park shows its label at z10, but a pocket park waits until z13 like everything else.
| Pull-up types |
|---|
university, college, school, hospital, park, castle, mall, sports_centre, golf_course, attraction |
Point POIs (even with the same type) are not pulled up — only polygons, because there's no geometry to measure.
POI ranking
Each POI gets a rank derived from its type, not from the source data. Lower rank = more important. This avoids rank gaps when the style hides certain classes.
| Rank | Types |
|---|---|
| 1 | hospital, university, station |
| 2 | museum, attraction, zoo, castle, stadium |
| 3 | school, college, library, police, townhall, post_office, cinema |
| 4 | hotel |
| 5 | restaurant, cafe, fast_food, bar, pub, bank, pharmacy |
| 6 | fuel, mall, grocery, supermarket |
| 7 | bakery, butcher, clothes, electronics, furniture, sports, toys |
| 8 | place_of_worship, monument, park, sports_centre, golf_course |
| 9 | halt, tram_stop |
| 10 | bus_stop, atm, parking (and any unlisted type) |
The rank also drives sort order within a tile and grid-based deduplication: at z13+ POIs are deduplicated within a 64-pixel grid (max 4 per cell), so higher-ranked types win the slot.
Transit network normalization
Railway POIs carry a network attribute. OSM's tagging is inconsistent, so the tile normalizes known aliases:
| Raw OSM value | Normalized |
|---|---|
ratp;ratp-metro |
ratp-metro |
ratp;ratp-rer |
ratp-rer |
московский-метрополитен |
moscow-metro |
london-underground |
london-underground |
national-rail |
national-rail |
metro-de-madrid |
metro-de-madrid |
metro-de-barcelona |
metro-de-barcelona |
Unknown networks pass through as-is. This lets the style use a single icon per network without handling alias variants.
Airports — Dual Source
Airport labels come from two sources to balance global coverage with regional detail:
| Source | Tag | Classification | Coverage |
|---|---|---|---|
Natural Earth (ne_10m_airports) |
iata_code |
type=major → international, else regional |
~893 major airports worldwide |
| OpenStreetMap | aeroway=aerodrome + iata |
passengers >= 1M → international, else regional |
Regional airports (MPL, BVA, etc.) |
Both emit to the same airports layer at min zoom 8. OSM aerodromes are polygons — pointOnSurface() extracts a centroid for the label point.
Name Translations
Every label layer (places, road labels, water labels, POI, airports) carries name plus 85 language-specific attributes (name:en, name:ja, name:ar, etc.). The client picks the right one based on user locale. No second pass or lookup needed — everything is in the tile.
Computed label fields
The tile generator also computes three derived fields for multi-script label display:
| Field | Value | Example (Paris) | Example (東京) |
|---|---|---|---|
name_int |
name:en → name:latin → name |
Paris | Tokyo |
name:latin |
name if Latin, else name:en or first Latin name:* |
Paris | Tokyo |
name:nonlatin |
name if non-Latin, else omitted |
(omitted) | 東京 |
The style expression uses these to render dual-script labels: "Tokyo \n 東京" in non-Latin regions, just "Paris" in Latin ones. Script detection uses Unicode block analysis (Basic Latin through Latin Extended).
Design Principles
-
Tile computes, style renders. Complex arithmetic (height fallback, population ranking, network normalization) happens at tile generation time. The style gets clean attributes it can filter on directly.
-
Allowlist over blocklist. POI types are explicitly mapped from OSM tags rather than trying to exclude the long tail. Cleaner tiles, predictable output.
-
Dual source for places. Natural Earth provides authoritative country/state data with curated rankings at low zoom. OSM takes over at z7+ for city-level detail. No overlap — each source owns its zoom range.
-
Separate geometry from labels. Roads and road labels are distinct layers so the style can render geometry (line layer) and labels (symbol layer) independently, with different min zooms and collision rules.