Closes #22 #74

Merged
art merged 3 commits from issue-22-is-places-label-anchor into main 2026-08-09 10:04:41 +00:00
Owner

Closes #22


Iceland settlements: one label anchor per settlement, not per part or per tile

mannvirki_flakar multipolygons are ST_Dump-split on import (110 source rows → 126
polygons; Reykjavík alone is 6 parts), so a settlement mapped as several polygons
rendered its town name once per part, and a large settlement spanning several tiles got
one centroid label per tile. The place_labels style layer drew its text from the
is/settlements polygons, so every polygon part got a label anchor.

Where the fix belongs. Issue #29 moved Iceland's name register into the shared
map_places layer — but that layer is the LMÍ örnefni points (44 506 points), with
no multipolygon problem. The ST_Dump-split settlements live in the per-country
settlements layer (extents + population), so the label-anchor fix goes there, in the
same shape as airport_points.

What changed.

  • conf/ismap.yaml: new derived settlement_points layer — a pg source over
    ismap_settlements_12 (the settlements maxz table) doing
    SELECT ST_Centroid(ST_Collect(geom)), nafnfitju, max(population) — one point per
    (name, cluster), stop_z 4 / maxz 12 mirroring the polygons, with the same
    population ladder (>= 200 at z10, >= 1000 at z8, >= 4000 at z6). Added to the
    is source. The polygon layer keeps the extents + population.
  • Cluster before grouping (review #740). GROUP BY nafnfitju alone would collapse
    distinct towns that share a name — the two Reykholt (Borgarfjörður pop 84 and
    Bláskógabyggð pop 368) are 155 km apart, and a nationwide name-grouped centroid
    merged them into one fictional settlement with the larger population. The query
    first clusters the parts spatially within each name (`ST_ClusterDBSCAN(geom, 15000,
    1. OVER (PARTITION BY nafnfitju)), then groups on (name, cluster) — the same primitive #69 used for metro entrances. eps 15 km: the binding constraint is Reykjavík's 6 parts (widest gap 9.5 km, centroid span 29.7 km), comfortably below the 155 km Reykholt separation, so every multi-part settlement stays whole and the two Reykholt stay two points. The layer now emits two Reykholt` labels — correct,
      they are two towns.
  • html/style.json: place_labels now reads source-layer: settlement_points instead
    of settlements.
  • Docs: geo-primer's ST_Dump worked example (the "open issue" is now fixed by the
    documented pattern, used by both airport_points and settlement_points), schema's
    per-country layer list, style.md, country-iceland's layer table (also corrected the
    stale mannvirki_flakar → places, airports row — airports come from
    samgongur_flakar, and mannvirki_flakar feeds settlements).

Verified against the live DB.

  • Imported the single layer (importer/importer -layer settlement_points conf/ismap.yaml).
  • Row counts per zoom: 110/110/70/70/34/34/13/13/13 (z12→z4) — 110 distinct (name,
    cluster) pairs, down from 126 polygon rows (109 distinct names + the second
    Reykholt). Multi-part names collapse: Reykjavík 6→1, Mosfellsbær 3→1, etc.
  • No NULL/empty names, no empty geometries. 4 NULL populations inherited from the source
    (Hjalteyri, Laugarbakki, Rauðalækur, Borðeyri — ibuarfj_24 is empty there);
    the style already coalesces population to 0.
  • The two Reykholt are present as separate rows with correct populations and positions:
    pop 84 at (-21.291, 64.665) and pop 368 at (-20.444, 64.179) — exactly the two towns
    the review named. Reykjavík is one row (pop 135714).
  • Decoded tiles: z12/1805/1074 carries the pop-84 Reykholt, z12/1815/1087 the pop-368
    one; z11/907/543 carries {Reykholt, 368} alongside the settlements polygons.
  • Real-browser CDP check (no virtual-time-budget): at z11 over Reykjavík the rendered
    place_labels are [Garðabær, Mosfellsbær, Reykjavík] — each once; at z12 each
    Reykholt renders its own label at its own town, and at z10 (where the population >= 200 filter applies) the pop-84 Reykholt is gone while the pop-368 one persists — the
    negative control. make build + go test ./... pass; style validates.

Left undone / notes.

  • The settlements polygon layer stays served and unchanged; it remains the source for
    the derived layer and holds the extents+population data.
  • Keflavík/Njarðvík/Ásbrú share the same combined population figure (21847) in the
    source; they are distinct names so they stay distinct points — pre-existing quirk, not
    changed here.
Closes #22 --- # Iceland settlements: one label anchor per settlement, not per part or per tile `mannvirki_flakar` multipolygons are ST_Dump-split on import (110 source rows → 126 polygons; Reykjavík alone is 6 parts), so a settlement mapped as several polygons rendered its town name once per part, and a large settlement spanning several tiles got one centroid label per tile. The `place_labels` style layer drew its text from the `is`/`settlements` polygons, so every polygon part got a label anchor. **Where the fix belongs.** Issue #29 moved Iceland's *name register* into the shared `map_places` layer — but that layer is the LMÍ örnefni **points** (44 506 points), with no multipolygon problem. The ST_Dump-split settlements live in the per-country `settlements` layer (extents + population), so the label-anchor fix goes there, in the same shape as `airport_points`. **What changed.** - `conf/ismap.yaml`: new derived `settlement_points` layer — a pg source over `ismap_settlements_12` (the settlements maxz table) doing `SELECT ST_Centroid(ST_Collect(geom)), nafnfitju, max(population)` — one point per (name, cluster), `stop_z 4` / `maxz 12` mirroring the polygons, with the same population ladder (`>= 200` at z10, `>= 1000` at z8, `>= 4000` at z6). Added to the `is` source. The polygon layer keeps the extents + population. - **Cluster before grouping (review #740).** `GROUP BY nafnfitju` alone would collapse distinct towns that share a name — the two Reykholt (Borgarfjörður pop 84 and Bláskógabyggð pop 368) are 155 km apart, and a nationwide name-grouped centroid merged them into one fictional settlement with the larger population. The query first clusters the parts spatially within each name (`ST_ClusterDBSCAN(geom, 15000, 1) OVER (PARTITION BY nafnfitju)`), then groups on (name, cluster) — the same primitive #69 used for metro entrances. eps 15 km: the binding constraint is Reykjavík's 6 parts (widest gap 9.5 km, centroid span 29.7 km), comfortably below the 155 km Reykholt separation, so every multi-part settlement stays whole and the two Reykholt stay two points. The layer now emits two `Reykholt` labels — correct, they are two towns. - `html/style.json`: `place_labels` now reads `source-layer: settlement_points` instead of `settlements`. - Docs: geo-primer's ST_Dump worked example (the "open issue" is now fixed by the documented pattern, used by both `airport_points` and `settlement_points`), schema's per-country layer list, style.md, country-iceland's layer table (also corrected the stale `mannvirki_flakar → places, airports` row — airports come from `samgongur_flakar`, and mannvirki_flakar feeds `settlements`). **Verified against the live DB.** - Imported the single layer (`importer/importer -layer settlement_points conf/ismap.yaml`). - Row counts per zoom: 110/110/70/70/34/34/13/13/13 (z12→z4) — 110 distinct (name, cluster) pairs, down from 126 polygon rows (109 distinct names + the second Reykholt). Multi-part names collapse: Reykjavík 6→1, Mosfellsbær 3→1, etc. - No NULL/empty names, no empty geometries. 4 NULL populations inherited from the source (`Hjalteyri`, `Laugarbakki`, `Rauðalækur`, `Borðeyri` — `ibuarfj_24` is empty there); the style already coalesces population to 0. - The two Reykholt are present as separate rows with correct populations and positions: pop 84 at (-21.291, 64.665) and pop 368 at (-20.444, 64.179) — exactly the two towns the review named. Reykjavík is one row (pop 135714). - Decoded tiles: z12/1805/1074 carries the pop-84 `Reykholt`, z12/1815/1087 the pop-368 one; z11/907/543 carries `{Reykholt, 368}` alongside the settlements polygons. - Real-browser CDP check (no virtual-time-budget): at z11 over Reykjavík the rendered `place_labels` are `[Garðabær, Mosfellsbær, Reykjavík]` — each once; at z12 each Reykholt renders its own label at its own town, and at z10 (where the `population >= 200` filter applies) the pop-84 Reykholt is gone while the pop-368 one persists — the negative control. `make build` + `go test ./...` pass; style validates. **Left undone / notes.** - The `settlements` polygon layer stays served and unchanged; it remains the source for the derived layer and holds the extents+population data. - Keflavík/Njarðvík/Ásbrú share the same combined population figure (21847) in the source; they are distinct names so they stay distinct points — pre-existing quirk, not changed here.
mannvirki_flakar multipolygons are ST_Dump-split on import (110 source rows → 126
polygons; Reykjavík alone is 6 parts), so a settlement mapped as several polygons
rendered its town name once per part, and a large settlement spanning several tiles got
one centroid label per tile. The place_labels style layer drew from the is/settlements
polygons, so every part got a label anchor.

Issue #29 moved Iceland's name register into the shared map_places layer, but that layer
is the örnefni POINTS (no multipolygon problem) — the ST_Dump-split settlements live in
the per-country `settlements` layer (extents + population), so the label-anchor fix goes
there, in the same shape as airport_points.

- conf/ismap.yaml: new derived `settlement_points` layer — pg source over
  ismap_settlements_12 doing SELECT ST_Centroid(ST_Collect(geom)), nafnfitju,
  max(population) GROUP BY nafnfitju — one point per name, stop_z 4 / maxz 12 mirroring
  the polygons and the same population ladder. Added to the `is` source. The polygon
  layer keeps the extents + population.
- html/style.json: place_labels now reads source-layer settlement_points.
- Docs: geo-primer's ST_Dump worked example (the open issue is now fixed by the
  documented pattern), schema per-country list, style.md, country-iceland layer table
  (also corrected the stale `mannvirki_flakar → places, airports` row: airports come
  from samgongur_flakar).

Verified: imported the single layer; per-zoom counts 109/109/70/70/34/34/13/13/13
(z12→z4), 109 distinct names down from 126 polygon rows (Reykjavík 6→1, Mosfellsbær 3→1);
no NULL/empty names or geometries; 4 NULL populations inherited from the source (style
coalesces to 0); decoded tile z11/899/544 carries 2 settlement_points (Kópavogur,
Reykjavík) alongside 8 settlements polygons; real-browser CDP check shows each town
label once at z11 and z8. make build + go test pass, style validates.
Collaborator

Review — changes requested

The derived-layer shape is right: layers: [settlements] for the import-order dependency, the population ladder copied verbatim from the polygons, stop_z/maxz mirrored, the style repointed in exactly one place. That all matches airport_points and I have no quarrel with it.

The query has a confirmed defect, though.

GROUP BY nafnfitju merges distinct settlements that share a name (conf/ismap.yaml:633)

The grouping key is the name, nationwide — so it collapses unrelated places, not just the ST_Dump parts of one place. Iceland has such a case:

Reykholt — 2 rows, 158 km apart
  pop  84 at -21.291, 64.665   (Borgarfjörður)
  pop 368 at -20.444, 64.179   (Bláskógabyggð)

merged label -> -20.543, 64.237, population 368

Consequences, both live at z10+ where population >= 200 lets the merged row through:

  • the one emitted label sits ~17 km from the nearer Reykholt and ~100 km from the other — in open country, attached to neither town;
  • the Borgarfjörður Reykholt loses its label entirely;
  • max(population) reports 368 for what is now a fictional merged settlement.

The verification in the PR body ("126 polygon rows → 109 distinct names") measures the collapse but cannot detect this, because it assumes one name means one place.

Fix

Same primitive #75 just landed for the metro entrances — cluster spatially within the name, then group on the cluster:

SELECT ST_Centroid(ST_Collect(geom)) AS geom, nafnfitju, max(population) AS population
FROM (
  SELECT nafnfitju, population, geom,
         ST_ClusterDBSCAN(geom, <eps>, 1) OVER (PARTITION BY nafnfitju) AS cl
  FROM ismap_settlements_12
) d
GROUP BY nafnfitju, cl

An eps in the low tens of km separates the two Reykholts while keeping Reykjavík's 6 parts (29.7 km span) and Hafnarfjörður's 2 (14.8 km) together; the spans are worth checking before picking the number, since Reykjavík's own span is the binding constraint. Note this makes the layer emit two Reykholt points, which is correct — they are two towns.

Worth re-running the name-count verification afterwards as "distinct (name, cluster)" rather than "distinct name".

## Review — changes requested The derived-layer shape is right: `layers: [settlements]` for the import-order dependency, the population ladder copied verbatim from the polygons, `stop_z`/`maxz` mirrored, the style repointed in exactly one place. That all matches `airport_points` and I have no quarrel with it. The query has a confirmed defect, though. ### `GROUP BY nafnfitju` merges distinct settlements that share a name (conf/ismap.yaml:633) The grouping key is the name, nationwide — so it collapses *unrelated places*, not just the ST_Dump parts of one place. Iceland has such a case: ``` Reykholt — 2 rows, 158 km apart pop 84 at -21.291, 64.665 (Borgarfjörður) pop 368 at -20.444, 64.179 (Bláskógabyggð) merged label -> -20.543, 64.237, population 368 ``` Consequences, both live at z10+ where `population >= 200` lets the merged row through: * the one emitted label sits ~17 km from the nearer Reykholt and ~100 km from the other — in open country, attached to neither town; * the Borgarfjörður Reykholt loses its label entirely; * `max(population)` reports 368 for what is now a fictional merged settlement. The verification in the PR body ("126 polygon rows → 109 distinct names") measures the collapse but cannot detect this, because it assumes one name means one place. ### Fix Same primitive #75 just landed for the metro entrances — cluster spatially within the name, then group on the cluster: ```sql SELECT ST_Centroid(ST_Collect(geom)) AS geom, nafnfitju, max(population) AS population FROM ( SELECT nafnfitju, population, geom, ST_ClusterDBSCAN(geom, <eps>, 1) OVER (PARTITION BY nafnfitju) AS cl FROM ismap_settlements_12 ) d GROUP BY nafnfitju, cl ``` An `eps` in the low tens of km separates the two Reykholts while keeping Reykjavík's 6 parts (29.7 km span) and Hafnarfjörður's 2 (14.8 km) together; the spans are worth checking before picking the number, since Reykjavík's own span is the binding constraint. Note this makes the layer emit two `Reykholt` points, which is correct — they are two towns. Worth re-running the name-count verification afterwards as "distinct (name, cluster)" rather than "distinct name".
art force-pushed issue-22-is-places-label-anchor from 5d66897649 to bdf9498525 2026-08-09 09:21:57 +00:00 Compare
Author
Owner

Fixed on the branch — settlement_points now clusters spatially within the name before grouping:

SELECT ST_Centroid(ST_Collect(geom)) AS geom, nafnfitju, max(population) AS population
FROM (
  SELECT nafnfitju, population, geom,
         ST_ClusterDBSCAN(geom, 15000, 1) OVER (PARTITION BY nafnfitju) AS cl
  FROM ismap_settlements_12
) d
GROUP BY nafnfitju, cl

eps 15 km, chosen against the live spans: Reykjavík's 6 parts are the binding constraint (widest gap 9.5 km between two parts, 29.7 km centroid span; Hafnarfjörður's 2 parts gap 0) and must stay one label; the two Reykholt are 155 km apart. 15 km sits between, so every multi-part settlement stays whole and the Reykholts split — the layer now emits two Reykholt points, which is correct.

Verified against the live DB (reimported the single layer):

  • 110 distinct (name, cluster) pairs at z12 (109 names + the second Reykholt); per-zoom 110/110/70/70/34/34/13/13/13.
  • The two Reykholt are separate rows at the right places: pop 84 at -21.291/64.665, pop 368 at -20.444/64.179. Reykjavík stays one row (pop 135714).
  • Decoded tiles: z12/1805/1074 carries {Reykholt, 84}, z12/1815/1087 {Reykholt, 368}, z11/907/543 {Reykholt, 368}.
  • CDP check (no virtual-time-budget): z11 over Reykjavík renders each town once; z12 each Reykholt renders at its own town; at z10 the pop-84 Reykholt is thinned out and the pop-368 one persists (negative control).
  • make build + go test pass; gl-style-validate clean.

Docs and the PR body now describe the layer as "one centroid per (name, cluster)" with the two Reykholt named as the case.

Fixed on the branch — `settlement_points` now clusters spatially within the name before grouping: ```sql SELECT ST_Centroid(ST_Collect(geom)) AS geom, nafnfitju, max(population) AS population FROM ( SELECT nafnfitju, population, geom, ST_ClusterDBSCAN(geom, 15000, 1) OVER (PARTITION BY nafnfitju) AS cl FROM ismap_settlements_12 ) d GROUP BY nafnfitju, cl ``` eps 15 km, chosen against the live spans: Reykjavík's 6 parts are the binding constraint (widest gap 9.5 km between two parts, 29.7 km centroid span; Hafnarfjörður's 2 parts gap 0) and must stay one label; the two Reykholt are 155 km apart. 15 km sits between, so every multi-part settlement stays whole and the Reykholts split — the layer now emits two Reykholt points, which is correct. Verified against the live DB (reimported the single layer): - 110 distinct (name, cluster) pairs at z12 (109 names + the second Reykholt); per-zoom 110/110/70/70/34/34/13/13/13. - The two Reykholt are separate rows at the right places: pop 84 at -21.291/64.665, pop 368 at -20.444/64.179. Reykjavík stays one row (pop 135714). - Decoded tiles: z12/1805/1074 carries {Reykholt, 84}, z12/1815/1087 {Reykholt, 368}, z11/907/543 {Reykholt, 368}. - CDP check (no virtual-time-budget): z11 over Reykjavík renders each town once; z12 each Reykholt renders at its own town; at z10 the pop-84 Reykholt is thinned out and the pop-368 one persists (negative control). - make build + go test pass; gl-style-validate clean. Docs and the PR body now describe the layer as "one centroid per (name, cluster)" with the two Reykholt named as the case.
Every distance in the settlement_points rationale was a Web Mercator figure labelled
as kilometres. At Iceland's latitude a 3857 unit is ~0.43 ground metres, so the numbers
overstated reality by ~2.3x: the two Reykholt are 67 km apart, not 155, and eps 15 000
is a ~6.5 km ground radius, not 15 km. (The review comment that supplied the figures had
the same error; the fix itself is unaffected, since the eps and the gaps it separates
live in the same projected space.)

Also corrects the eps rationale, which named the wrong constraint. DBSCAN chains, so
what binds is the largest gap between a part and its nearest sibling, not the span of
the settlement: Reykjavík's 12.9 km span is chained through 110 m neighbours and never
binds. Measured over every multi-part name, the widest binding gap is Ísafjörður's
842 m, so the usable eps range is ~0.9 km to the 67 km Reykholt separation and 15 000
units sits near its geometric middle.

conf/ismap.yaml, geo-primer, schema, style and country-iceland all restated; the
query is unchanged, so no reimport is needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
art merged commit ca3eacbf43 into main 2026-08-09 10:04:41 +00:00
art deleted branch issue-22-is-places-label-anchor 2026-08-09 10:04:41 +00:00
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!74
No description provided.