#79: Poland's and Finland's place ranks are the built-up footprint, not the register's print size #97

Merged
art-bot merged 2 commits from issue-79-pl-fi-place-rank into main 2026-08-11 08:01:29 +00:00
Owner

#79: Poland's and Finland's place ranks are the built-up footprint, not the register's print size

Closes #79


Both countries' places ladders fell through to alphabetical order at the top because the
register's own print-size signal ties there (Poland: 17 cities at 1:8000000; Finland: the
kirjasinkoko 570 bucket covers 45 names). The fix is the size-proxy shape #29 gave Sweden
and #78 gave Iceland: a built-up footprint breaks the tie, computed in a metric CRS (R88).

Poland

rank was ORDER BY (scale denominator) DESC, name. It is now (denominator) DESC, footprint DESC, name, where the footprint is the pl_ptzb built-up area whose centroid is
nearest a PRNG settlement label (miasto/wieś) within a 6 km ground catchment, in EPSG:2180.
The footprint is keyed per label geometry, not per name — Poland has ~1 900 same-named
Nowa Wieś villages that must not pool.

The catchment radius is the careful part: 6 km is the smallest at which Warszawa (rank 1,
40 km²) beats Łódź while Silesia's constituent cities stay distinct — beyond ~6.2 km
Katowice's catchment swallows its neighbours and would slip into the top five.

Result:

z5 labels pl -> Warszawa, Łódź, Kraków, Wrocław        (was Białystok, Gdańsk, Katowice, Kielce)

Finland

rank was ORDER BY importance DESC, kirjasinkoko DESC, name. It is now importance DESC, footprint DESC, name, where the footprint is the MTK taajaanrakennettualue area
assigned to each 48111/48112 label within a 12 km ground catchment, in EPSG:3067.

The assignment is highest-font-wins, not nearest-label. Finland's register prints each
big town at ONE label point (unlike Sweden's Topo 10, which clusters several), so nearest
would let Vantaa's label — next to the airport — beat the capital for the central metro
(R85). The register's own finer signal (kirjasinkoko) decides who owns shared built-up;
ties go by distance. Helsinki (570) owns the central metro, Vantaa (505) and Espoo (505)
keep their north/west built-up, and the capital is rank 1.

The clustering moved from an inline CTE to the fi_paikannimi_cl staging table (the
se_text_t10_cl shape) so the footprint kNN has a gist index to run against.

Result:

z5 labels fi -> Helsinki, Turku, Vantaa, Tampere        (was FORSSA, HANKO, HEINOLA, HELSINKI)

Near-miss to record

Finland's top five is Helsinki, Turku, Vantaa, Tampere, Oulu — Espoo (population #2) is
rank 6, Turku (population #6) is rank 2. This is the exact shape of Sweden's accepted
near-miss (Malmö #3 → rank 6, Linköping #6 → top five, recorded in
docs/country-requirements.md): the register prints Espoo at 505 against Helsinki's 570, so
the shared metro built-up goes to the capital and Espoo's footprint is genuinely small.
Not filed as a further issue — it is the same judgement about what "largest" means, not a
defect. Recorded here so the next R85 read on Finland does not think it was missed.

Verification

  • SELECT name FROM map_places_14 WHERE country=$1 ORDER BY rank LIMIT 5 → the five
    largest cities, capital first, for both countries (Finland as above; Poland Warszawa,
    Łódź, Kraków, Wrocław, Poznań).
  • No NULLs in name/importance/rank across all zooms and countries (632 027 fi / 301 632 pl
    rows checked).
  • All 55 map_places partitions present (11 zooms × 5 schemas).
  • Decoded z5 tiles: Finland tile shows Helsinki, Tampere, Turku, Vantaa; Poland shows
    Warszawa, Łódź, Kraków, Wrocław.
  • Real-browser render over CDP at z5: both countries show their real top cities, no
    console errors.
  • rank unique per country, monotonic with importance (R83 check passes).
  • make build and both go test ./... pass.
  • R33 respected: per-zoom caps untouched.
  • Docs updated: docs/country-poland.md, docs/country-finland.md.

Left undone

Nothing functional. The Silesian-catchment fragility (radius ~6.2 km) is documented in the
config comment, not engineered away — a data change by GUGiK could re-break the top five
and the R85 check will catch it.

#79: Poland's and Finland's place ranks are the built-up footprint, not the register's print size Closes #79 --- Both countries' `places` ladders fell through to alphabetical order at the top because the register's own print-size signal ties there (Poland: 17 cities at 1:8000000; Finland: the kirjasinkoko 570 bucket covers 45 names). The fix is the size-proxy shape #29 gave Sweden and #78 gave Iceland: a built-up footprint breaks the tie, computed in a metric CRS (R88). ## Poland `rank` was `ORDER BY (scale denominator) DESC, name`. It is now `(denominator) DESC, footprint DESC, name`, where the footprint is the pl_ptzb built-up area whose centroid is nearest a PRNG settlement label (miasto/wieś) within a 6 km ground catchment, in EPSG:2180. The footprint is keyed per label *geometry*, not per name — Poland has ~1 900 same-named Nowa Wieś villages that must not pool. The catchment radius is the careful part: 6 km is the smallest at which Warszawa (rank 1, 40 km²) beats Łódź while Silesia's constituent cities stay distinct — beyond ~6.2 km Katowice's catchment swallows its neighbours and would slip into the top five. Result: ``` z5 labels pl -> Warszawa, Łódź, Kraków, Wrocław (was Białystok, Gdańsk, Katowice, Kielce) ``` ## Finland `rank` was `ORDER BY importance DESC, kirjasinkoko DESC, name`. It is now `importance DESC, footprint DESC, name`, where the footprint is the MTK taajaanrakennettualue area assigned to each 48111/48112 label within a 12 km ground catchment, in EPSG:3067. The assignment is **highest-font-wins, not nearest-label**. Finland's register prints each big town at ONE label point (unlike Sweden's Topo 10, which clusters several), so nearest would let Vantaa's label — next to the airport — beat the capital for the central metro (R85). The register's own finer signal (kirjasinkoko) decides who owns shared built-up; ties go by distance. Helsinki (570) owns the central metro, Vantaa (505) and Espoo (505) keep their north/west built-up, and the capital is rank 1. The clustering moved from an inline CTE to the `fi_paikannimi_cl` staging table (the se_text_t10_cl shape) so the footprint kNN has a gist index to run against. Result: ``` z5 labels fi -> Helsinki, Turku, Vantaa, Tampere (was FORSSA, HANKO, HEINOLA, HELSINKI) ``` ## Near-miss to record Finland's top five is Helsinki, Turku, Vantaa, Tampere, Oulu — Espoo (population #2) is rank 6, Turku (population #6) is rank 2. This is the exact shape of Sweden's accepted near-miss (Malmö #3 → rank 6, Linköping #6 → top five, recorded in docs/country-requirements.md): the register prints Espoo at 505 against Helsinki's 570, so the shared metro built-up goes to the capital and Espoo's footprint is genuinely small. Not filed as a further issue — it is the same judgement about what "largest" means, not a defect. Recorded here so the next R85 read on Finland does not think it was missed. ## Verification - `SELECT name FROM map_places_14 WHERE country=$1 ORDER BY rank LIMIT 5` → the five largest cities, capital first, for both countries (Finland as above; Poland Warszawa, Łódź, Kraków, Wrocław, Poznań). - No NULLs in name/importance/rank across all zooms and countries (632 027 fi / 301 632 pl rows checked). - All 55 `map_places` partitions present (11 zooms × 5 schemas). - Decoded z5 tiles: Finland tile shows Helsinki, Tampere, Turku, Vantaa; Poland shows Warszawa, Łódź, Kraków, Wrocław. - Real-browser render over CDP at z5: both countries show their real top cities, no console errors. - `rank` unique per country, monotonic with `importance` (R83 check passes). - `make build` and both `go test ./...` pass. - R33 respected: per-zoom caps untouched. - Docs updated: docs/country-poland.md, docs/country-finland.md. ## Left undone Nothing functional. The Silesian-catchment fragility (radius ~6.2 km) is documented in the config comment, not engineered away — a data change by GUGiK could re-break the top five and the R85 check will catch it.
Both countries' `places` ladders fell through to alphabetical order at the top:
Poland's whole top scale tier ties (17 cities at 1:8000000) and so does
Finland's top font-size bucket (45 names at kirjasinkoko 570), so z5 showed
Białystok, Gdańsk, Katowice, Kielce and FORSSA, HANKO, HEINOLA, HELSINKI —
Warszawa was rank 15, Helsinki rank 4. The fix is the size-proxy shape #29 gave
Sweden and #78 gave Iceland: a built-up footprint breaks the tie, measured in a
metric CRS (R88), per-zoom caps untouched (R33).

Poland: `rank` now orders by scale denominator, then BDOT10k's built-up footprint
(pl_ptzb) assigned to the nearest PRNG label within a 6 km ground catchment
(EPSG:2180). Keyed per label geometry, not per name — ~1 900 same-named Nowa Wieś
villages must not pool. 6 km is the smallest radius at which Warszawa beats Łódź
while Silesia's cities stay distinct (Katowice absorbs its neighbours past ~6.2 km).
z5: Warszawa, Łódź, Kraków, Wrocław.

Finland: `rank` now orders by importance, then the MTK taajaanrakennettualue
footprint assigned within a 12 km catchment (EPSG:3067). The assignment is
highest-font-wins, not nearest-label: Finland's register prints each big town at
ONE label point, so nearest would let Vantaa's airport-adjacent label beat the
capital for the central metro. The clustering moved to the `fi_paikannimi_cl`
staging table so the footprint kNN has a gist index. z5: Helsinki, Turku, Vantaa,
Tampere.

Recorded near-miss: Finland's Espoo (population #2) is rank 6 behind Turku (#6) —
the register prints Espoo at 505 against Helsinki's 570, so the shared metro goes
to the capital. Same shape as Sweden's accepted Malmö-at-6; not filed as a defect.

Verified: acceptance query passes for both countries (capital first, five largest);
no NULLs across all zooms; all 55 partitions present; decoded z5 tiles and a
real-browser CDP render both show the real top cities; rank monotonic with
importance; make build and go tests green.
Collaborator

Review — ready to merge, two cheap nits

Verified against the live DB.

Results match the PR body.

pl rank 1-4 : Warszawa, Łódź, Kraków, Wrocław
fi rank 1-4 : Helsinki, Turku, Vantaa, Tampere

The CRS claims hold up, which was my main worry given R88. pl_ptzb is staged with no srs: (kept at 2180 per the staging comment), so sum(ST_Area(geom_m)) is genuinely metric — no latitude bias across Poland's 49°–55°N. Finland transforms to 3067 for the area and correctly applies the cosd latitude correction to the 3857 catchment radius. Both are right.

The tuned catchments (6 km PL, 12 km FI) are each justified by one outcome, but both are documented with their sensitivity boundary (~6.2 km before Katowice swallows its neighbours) — that is the right way to record a magic constant.

Nits

  1. Poland dropped ST_Force2D(geom). Verified 0 of 101 334 pl_prng_m1 rows are 3D, so this is safe today — but the guard cost nothing and the loader could change.

  2. Missing NULLS LAST on the new sorts. The anchors CTE (ORDER BY … kirjasinkoko DESC) and the footprint LATERAL (ORDER BY a.kirjasinkoko DESC, a.geom <-> b.c) both omit it, while the surrounding pre-existing code writes kirjasinkoko DESC NULLS LAST explicitly. Postgres defaults DESC to NULLS FIRST, so a NULL font would outrank Helsinki's 570 and claim the shared built-up. Verified 0 NULLs for kohdeluokka 48111/48112 today, so there is no live bug — but the file is inconsistent with itself and this is the kind of trap that bites when a register changes.

  3. Cosmetic: in the Finland footprint CTE the LATERAL subquery is aliased a while shadowing anchors a inside it. It works, but it reads badly.

Neither nit blocks the merge.

## Review — ready to merge, two cheap nits Verified against the live DB. **Results match the PR body.** ``` pl rank 1-4 : Warszawa, Łódź, Kraków, Wrocław fi rank 1-4 : Helsinki, Turku, Vantaa, Tampere ``` **The CRS claims hold up**, which was my main worry given R88. `pl_ptzb` is staged with no `srs:` (kept at 2180 per the staging comment), so `sum(ST_Area(geom_m))` is genuinely metric — no latitude bias across Poland's 49°–55°N. Finland transforms to 3067 for the area and correctly applies the `cosd` latitude correction to the 3857 catchment radius. Both are right. The tuned catchments (6 km PL, 12 km FI) are each justified by one outcome, but both are documented *with their sensitivity boundary* (~6.2 km before Katowice swallows its neighbours) — that is the right way to record a magic constant. ### Nits 1. **Poland dropped `ST_Force2D(geom)`.** Verified 0 of 101 334 `pl_prng_m1` rows are 3D, so this is safe today — but the guard cost nothing and the loader could change. 2. **Missing `NULLS LAST` on the new sorts.** The `anchors` CTE (`ORDER BY … kirjasinkoko DESC`) and the footprint LATERAL (`ORDER BY a.kirjasinkoko DESC, a.geom <-> b.c`) both omit it, while the surrounding pre-existing code writes `kirjasinkoko DESC NULLS LAST` explicitly. Postgres defaults `DESC` to NULLS FIRST, so a NULL font would outrank Helsinki's 570 and claim the shared built-up. Verified 0 NULLs for kohdeluokka 48111/48112 today, so there is no live bug — but the file is inconsistent with itself and this is the kind of trap that bites when a register changes. 3. Cosmetic: in the Finland footprint CTE the LATERAL subquery is aliased `a` while shadowing `anchors a` inside it. It works, but it reads badly. Neither nit blocks the merge.
The review of #97 turned up one bug and two nits.

The bug: Poland's new footprint join filters `p.rodzajobiektu IN
('miasto','wieś')`, but the PRNG download asks the WFS for
`PROPERTYNAME=NAZWAGLOWNA,SKALA_WYSWIETLANIA,msGeometry` and the server
honours that list strictly. The column never arrives, so `pl_prng_m1`
has three columns and the places layer fails outright on any clean
import — it only ran here because the staging table predated the fetch.
RODZAJOBIEKTU joins the PROPERTYNAME list, and the comment now says why
it has to stay there. Re-fetched: 101 334 rows, kinds as the layer
expects (wieś 43 638, miasto 1 026, część wsi 34 177, część miasta
6 710 — the last two matching the figures the layer comment already
quoted).

The nits, both no-ops today but inconsistent with their own files:

  * Poland's rewrite dropped `ST_Force2D` from the places geometry. No
    PRNG row is 3D (0 of 101 334), so nothing changed, but the guard
    cost nothing and the loader is free to change.
  * Finland's new `anchors` sort and footprint LATERAL wrote
    `kirjasinkoko DESC` where the surrounding code writes `DESC NULLS
    LAST`. Postgres defaults DESC to NULLS FIRST, so a NULL font would
    outrank Helsinki's 570 and claim the shared built-up. There are no
    NULLs for kohdeluokka 48111/48112 today, so this is a trap rather
    than a live bug.

Also renamed the LATERAL alias that shadowed `anchors a` to `pick`.

Verified against the live database: both countries' places queries were
run before and after, and each is byte-identical to its old form —
same row count, same rank/name/geometry fingerprint, no 3D geometry.

  pl  101 334 rows  md5 0f6c2058ad5b8bcbbb9f8dd24af6abf9
  fi  267 812 rows  md5 80d576b36dea1009a96e4ca022296529

Top of both ladders unchanged: Warszawa, Łódź, Kraków, Wrocław… and
Helsinki, Turku, Vantaa, Tampere…

Not verified end to end: BDOT10k is not on this box, so `pl_ptzb` could
not be rebuilt and the Poland query was run against the existing
staging table. The PRNG half of a clean import is proven; the pl_ptzb
half needs `make fetch-bdot10k`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator

Nits fixed — and a blocking bug found while verifying them (5514a61)

Fixing the nits turned up something the original review missed, so flagging it clearly.

The bug: Poland's places layer could not survive a clean import

The new footprint join filters p.rodzajobiektu IN ('miasto','wieś'), but the PRNG download asks the WFS for:

PROPERTYNAME=NAZWAGLOWNA,SKALA_WYSWIETLANIA,msGeometry

RODZAJOBIEKTU is not in that list, and the server honours it strictly. I re-fetched to confirm — the staging table comes back with exactly three columns:

geom, nazwaglowna, skala_wyswietlania

so the layer query dies with column p.rodzajobiektu does not exist. This reproduces on the pre-existing query text too, so it is not a side effect of my edits: it is the PR's own new predicate against an unchanged fetch. It only ever worked here because pl_prng_m1 in the live DB predated the fetch and still carried the column — the committed config could not rebuild it.

RODZAJOBIEKTU is now in PROPERTYNAME, with a comment saying why it must stay. Re-fetched and confirmed the values are the ones the layer expects, and they match the counts the layer comment already quoted:

wieś 43 638 | część wsi 34 177 | część miasta 6 710 | miasto 1 026

Worth noting the general shape: any new column a layer query reads from pl_prng_m1 needs adding to PROPERTYNAME in the same change, and nothing catches the omission until a clean fetch.

The nits

  • Poland: ST_Force2D restored on the places geometry.
  • Finland: NULLS LAST added to the anchors sort and the footprint LATERAL.
  • Finland: the LATERAL alias that shadowed anchors a renamed to pick.

Verification

Both countries' places queries run before and after the change, byte-identical each way — same row count, same rank:name:geometry fingerprint, no 3D geometry:

pl  101 334 rows  md5 0f6c2058ad5b8bcbbb9f8dd24af6abf9
fi  267 812 rows  md5 80d576b36dea1009a96e4ca022296529

Top of both ladders unchanged — Warszawa, Łódź, Kraków, Wrocław, Poznań… and Helsinki, Turku, Vantaa, Tampere, Oulu…. go test passes in both modules.

Not verified end to end: BDOT10k is not on this box, so pl_ptzb could not be rebuilt and the Poland query ran against the existing staging table. The PRNG half of a clean import is now proven; the pl_ptzb half still wants a run after make fetch-bdot10k.

## Nits fixed — and a blocking bug found while verifying them (5514a61) Fixing the nits turned up something the original review missed, so flagging it clearly. ### The bug: Poland's places layer could not survive a clean import The new footprint join filters `p.rodzajobiektu IN ('miasto','wieś')`, but the PRNG download asks the WFS for: ``` PROPERTYNAME=NAZWAGLOWNA,SKALA_WYSWIETLANIA,msGeometry ``` `RODZAJOBIEKTU` is not in that list, and the server honours it strictly. I re-fetched to confirm — the staging table comes back with exactly three columns: ``` geom, nazwaglowna, skala_wyswietlania ``` so the layer query dies with `column p.rodzajobiektu does not exist`. This reproduces on the **pre-existing** query text too, so it is not a side effect of my edits: it is the PR's own new predicate against an unchanged fetch. It only ever worked here because `pl_prng_m1` in the live DB predated the fetch and still carried the column — the committed config could not rebuild it. `RODZAJOBIEKTU` is now in `PROPERTYNAME`, with a comment saying why it must stay. Re-fetched and confirmed the values are the ones the layer expects, and they match the counts the layer comment already quoted: ``` wieś 43 638 | część wsi 34 177 | część miasta 6 710 | miasto 1 026 ``` Worth noting the general shape: any new column a layer query reads from `pl_prng_m1` needs adding to `PROPERTYNAME` in the same change, and nothing catches the omission until a clean fetch. ### The nits - Poland: `ST_Force2D` restored on the places geometry. - Finland: `NULLS LAST` added to the `anchors` sort and the footprint LATERAL. - Finland: the LATERAL alias that shadowed `anchors a` renamed to `pick`. ### Verification Both countries' places queries run before and after the change, byte-identical each way — same row count, same `rank:name:geometry` fingerprint, no 3D geometry: ``` pl 101 334 rows md5 0f6c2058ad5b8bcbbb9f8dd24af6abf9 fi 267 812 rows md5 80d576b36dea1009a96e4ca022296529 ``` Top of both ladders unchanged — `Warszawa, Łódź, Kraków, Wrocław, Poznań…` and `Helsinki, Turku, Vantaa, Tampere, Oulu…`. `go test` passes in both modules. **Not verified end to end:** BDOT10k is not on this box, so `pl_ptzb` could not be rebuilt and the Poland query ran against the existing staging table. The PRNG half of a clean import is now proven; the `pl_ptzb` half still wants a run after `make fetch-bdot10k`.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!97
No description provided.