Closes #71 #72

Merged
art merged 2 commits from issue-71-place-label-budget into main 2026-08-08 21:33:38 +00:00
Owner

Closes #71


Place names importance ladder: budget labels by on-ground size

What and why

Finland served 308 importance-3 place names at z5 — the whole large-font tier of the
paikannimi register — and MapLibre's collision handling let Helsinki get crowded out.
The three-tier importance ladder (1–3) was too coarse to thin further: even the largest
register font size (570) covers 45 names. This change gives the places layer a
continuous per-country rank and cuts the cascade on it, with the per-zoom caps derived
from a design estimate of how much of the country a label covers.

The measurement

A place-name label's on-ground size is estimated, not measured at generalization time:

  • one importance-3 label box ≈ 70 × 25 px = 1726 px² — from the style's text-size
    step (14 px at importance 3), text-max-width 8, text-padding 4, an 8-character
    average name, 0.55 em glyph advance (Go Bold), 1.2 line height;
  • one screen pixel at zoom z = pixelSize(z) = 40075016.68 / (2^z × 256) m, so the
    label's on-ground area is 1726 × pixelSize(z)²;
  • at most X% of the country's land area covered → at most
    N(z) = X × area / (1726 × pixelSize(z)²) labels, floored at 1.

The design constant and the reasoning live in the places contract comment
(conf/common.yaml); each country's zoom_filters turn it into rank <= N(z).

The ladder

rank is ROW_NUMBER() OVER (ORDER BY importance DESC, <the survey's own finer signal> DESC, name), computed per country in the source query:

  • Finland: the register's kirjasinkoko (label-font size 330–570) — the survey prints
    Helsinki (rank 4) bigger than a village, and that distinction is exactly what the
    1–3 collapse threw away;
  • Iceland: the örnefnaflokkurkenni code (land before sea/water/ice), then name.

rank is mvt_exclude'd — it is cascade machinery, not map data; the style still reads
importance for text-size and symbol-sort-key. The contract gained the column and
both country partitions now carry it.

The budget

Default X = 60%. Per-zoom caps (Finland / Iceland land areas):

zoom 4 5 6 7 8 9 10 11 12
fi 1 4 17 70 282 1129 4519 18076 72305
is 1 1 5 23 95 382 1528 6114 24459

z13+ keeps the whole register (the budget exceeds the total there). At z5 Finland now
shows 4 names (FORSSA, HANKO, HEINOLA, HELSINKI — the survey's own order); Helsinki is
rank 4, so it is always included once a zoom has budget.

Options to try (edit the country zoom_filters, the formula is in the contract
comment):

  • X = 50% → z5 = 3 names — this drops Helsinki (rank 4), so I do not recommend it;
  • X = 75% → z5 = 5 names — if z5 feels too sparse.

Verification (live DB)

  • Reimported -layer places for both countries; parent tables gained the rank
    column via an additive ALTER TABLE (the contract's ensureParentSQL only creates,
    it does not migrate).
  • Per-zoom counts match the caps exactly: fi 1/4/17/70/282/1129/4519/18076/72305,
    is 1/1/5/23/95/382/1529/6121/24474 — Iceland's z10+ shows one extra row per zoom
    because 34 MultiPoint features (e.g. Biskupavörður) are ST_Dump'd into two rows that
    share a pre-dump rank; pre-existing behaviour, ~0.07% overage.
  • No NULL name/importance/rank in either country's z14 table; partitions present at
    every zoom z4–14.
  • Decoded a z5 tile: 4 places features, MVT keys [name, importance, country] —
    rank correctly excluded from the tile.
  • Real-browser CDP render: z5 over Finland renders exactly 4 place_names including
    HELSINKI; Iceland z5 renders Ísland. Higher zooms render the expected denser ladders
    (z11 shows importance-2 village names).

Notes

  • make build, go test ./..., go vet ./... pass in importer/ and mapsrv/.
  • The within-tier tiebreak is alphabetical (name) where the survey gives no finer
    signal — e.g. among the 45 font-570 towns. A population join would be the real fix
    and is future Workstream A material, not this issue's scope.
  • The z10 Iceland budget constant (1528 vs the 1529 rows) reflects a slightly rounded
    land-area figure; the mechanism is the estimate, so the cap is what matters.
Closes #71 --- # Place names importance ladder: budget labels by on-ground size ## What and why Finland served 308 importance-3 place names at z5 — the whole large-font tier of the paikannimi register — and MapLibre's collision handling let Helsinki get crowded out. The three-tier importance ladder (1–3) was too coarse to thin further: even the largest register font size (570) covers 45 names. This change gives the places layer a continuous per-country rank and cuts the cascade on it, with the per-zoom caps derived from a design estimate of how much of the country a label covers. ## The measurement A place-name label's on-ground size is estimated, not measured at generalization time: - one importance-3 label box ≈ 70 × 25 px = **1726 px²** — from the style's text-size `step` (14 px at importance 3), text-max-width 8, text-padding 4, an 8-character average name, 0.55 em glyph advance (Go Bold), 1.2 line height; - one screen pixel at zoom z = `pixelSize(z) = 40075016.68 / (2^z × 256)` m, so the label's on-ground area is `1726 × pixelSize(z)²`; - at most **X% of the country's land area** covered → at most `N(z) = X × area / (1726 × pixelSize(z)²)` labels, floored at 1. The design constant and the reasoning live in the `places` contract comment (`conf/common.yaml`); each country's `zoom_filters` turn it into `rank <= N(z)`. ## The ladder `rank` is `ROW_NUMBER() OVER (ORDER BY importance DESC, <the survey's own finer signal> DESC, name)`, computed per country in the source query: - Finland: the register's `kirjasinkoko` (label-font size 330–570) — the survey prints Helsinki (rank 4) bigger than a village, and that distinction is exactly what the 1–3 collapse threw away; - Iceland: the `örnefnaflokkurkenni` code (land before sea/water/ice), then name. `rank` is mvt_exclude'd — it is cascade machinery, not map data; the style still reads `importance` for text-size and `symbol-sort-key`. The contract gained the column and both country partitions now carry it. ## The budget Default **X = 60%**. Per-zoom caps (Finland / Iceland land areas): | zoom | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |---|---|---|---|---|---|---|---|---|---| | fi | 1 | 4 | 17 | 70 | 282 | 1129 | 4519 | 18076 | 72305 | | is | 1 | 1 | 5 | 23 | 95 | 382 | 1528 | 6114 | 24459 | z13+ keeps the whole register (the budget exceeds the total there). At z5 Finland now shows 4 names (FORSSA, HANKO, HEINOLA, HELSINKI — the survey's own order); Helsinki is rank 4, so it is always included once a zoom has budget. **Options to try** (edit the country `zoom_filters`, the formula is in the contract comment): - **X = 50%** → z5 = 3 names — this *drops Helsinki* (rank 4), so I do not recommend it; - **X = 75%** → z5 = 5 names — if z5 feels too sparse. ## Verification (live DB) - Reimported `-layer places` for both countries; parent tables gained the `rank` column via an additive `ALTER TABLE` (the contract's `ensureParentSQL` only creates, it does not migrate). - Per-zoom counts match the caps exactly: fi `1/4/17/70/282/1129/4519/18076/72305`, is `1/1/5/23/95/382/1529/6121/24474` — Iceland's z10+ shows one extra row per zoom because 34 MultiPoint features (e.g. Biskupavörður) are ST_Dump'd into two rows that share a pre-dump rank; pre-existing behaviour, ~0.07% overage. - No NULL name/importance/rank in either country's z14 table; partitions present at every zoom z4–14. - Decoded a z5 tile: 4 places features, MVT keys `[name, importance, country]` — `rank` correctly excluded from the tile. - Real-browser CDP render: z5 over Finland renders exactly 4 `place_names` including HELSINKI; Iceland z5 renders Ísland. Higher zooms render the expected denser ladders (z11 shows importance-2 village names). ## Notes - `make build`, `go test ./...`, `go vet ./...` pass in importer/ and mapsrv/. - The within-tier tiebreak is alphabetical (name) where the survey gives no finer signal — e.g. among the 45 font-570 towns. A population join would be the real fix and is future Workstream A material, not this issue's scope. - The z10 Iceland budget constant (1528 vs the 1529 rows) reflects a slightly rounded land-area figure; the mechanism is the estimate, so the cap is what matters.
Finland served 308 importance-3 place names at z5 (the whole large-font tier of the
paikannimi register) and MapLibre's collision handling let Helsinki get crowded out.
The three-tier importance ladder was too coarse to thin further — even the largest
register font (570) has 45 names. This gives the shared places layer a continuous
per-country `rank` and cuts the cascade on it, with the per-zoom caps derived from a
design estimate of how much of the country a label covers.

The measurement: one importance-3 label box ≈ 70×25 px = 1726 px² (style text-size
step at 14px, text-max-width 8, text-padding 4, 8-char average name, 0.55em glyph
advance, 1.2 line height); one pixel at zoom z = 40075016.68/(2^z·256) m, so a label
covers 1726·pixelSize(z)² on the ground; at most X% of the country's land area covered
gives N(z) = X·area/(1726·pixelSize(z)²) labels per zoom, floored at 1. The constant
and reasoning live in the places contract comment; each country's zoom_filters turn it
into `rank <= N(z)` caps.

`rank` = ROW_NUMBER over (importance, the survey's own finer signal, name), per country:
Finland's kirjasinkoko label-font size (Helsinki is rank 4 — the crowding fix), Iceland's
örnefnaflokkurkenni code. mvt_exclude keeps rank out of the tile; the style still reads
importance. Default X = 60%: z5 Finland goes from 308 names to 4 (including HELSINKI),
Iceland to 1 (Ísland). Alternatives X = 50% (drops Helsinki) and 75% documented.

Verified against the live DB: per-zoom counts match the caps (fi
1/4/17/70/282/1129/4519/18076/72305, is 1/1/5/23/95/382/1529/6121/24474), no NULLs in
name/importance/rank, partitions at every zoom, a decoded z5 tile carries 4 places
features with keys [name, importance, country], and a real-browser CDP render at z5
shows exactly 4 place_names including HELSINKI. The parent tables gained the rank column
via additive ALTER (the contract's ensureParentSQL only creates, it does not migrate).
art merged commit e5b5eda038 into main 2026-08-08 21:33:38 +00:00
art deleted branch issue-71-place-label-budget 2026-08-08 21:33:38 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
art/ismap!72
No description provided.