Closes #45 #95

Merged
art-bot merged 5 commits from issue-45-country-borders into main 2026-08-12 12:57:08 +00:00
Owner

Closes #45


Country borders drawn on the map, from each country's official boundary source

What changed

A new shared layer country_borders (R14), drawn as a dashed rose line over the
whole map from each country's own authoritative boundary data:

  • conf/common.yaml — the shared contract: a line layer z3–z14,
    simplify_line (the documented "land outlines, country borders" case).
  • Per-country partitions, each from its own official source:
    • Sweden: Topo 50 Riksgräns (objekttypnr 1562) — the land borders with
      Norway and Finland plus the coastline, in one linework. The territorial-sea
      boundary (1561) is deliberately not used: it sits ~12 nm offshore.
    • Iceland: the boundary of the IS 50V territory polygons — an island's whole
      national border is its coast, which is why the layer draws coastlines.
    • Poland: the PRG border ring, minus Poland's own sea (woda morska).
    • Finland: the MML Valtakunta outline, minus MML's meri sea.
  • conf/world.yaml — country_borders added to the map tile source.
  • html/style.json — one flat-width, flat-dash rose line (hsl(340,40%,45%),
    deliberately outside the landcover/water/road palette), drawn between the water
    and the road casings so it reads over land, sea and water bodies but never on
    top of a road. 65 layers now.
  • Docs: R14 marked implemented, layer inventory tables updated, the mechanism
    recorded next to each query.

Why the Poland/Finland queries subtract the sea — and the measurement

The PRG and Valtakunta boundary sources are state territory including the
territorial sea
, so their raw boundary runs offshore along the maritime limit
instead of along the coast. Measured against the live DB:

  • Finland: of the outline's 9 461 km, only 20 km lies on the shore and
    1 137 km is out in the sea; the rest is land borders plus coastal arcs.
  • Poland: 702 km of its 5 789 km is out in the Baltic.

Both were verified to render a dashed line floating far offshore (real-browser
render at the middle of the Bothnian Bay showed a border crossing open water).
Each query subtracts the country's own sea polygons, recovering the land borders
plus the coastal arcs; after the fix both countries have 0 km of border in
the sea, verified by ST_Intersection against the sea geometry, and the
real-browser render no longer shows a line in open water. Sweden's Riksgräns was
checked the same way: 0 km in the sea, no fix needed. Iceland's territory
polygons carry no territorial water (R108).

One importer-side note: fi_meri's topology union takes fourteen minutes
(measured), so the Finland query uses ST_Collect (seconds) — ST_Difference
nodes the collection on its own and gives the identical result. Poland's sea
needs ST_Union because its polygons overlap and the ST_Collect form raises a
GEOS topology conflict.

Verification (live DB + real browser, no virtual time)

  • make build, go test ./... in importer/ and mapsrv/, all configs through
    the real-config startup validation, gl-style-validate clean.
  • Row counts z14 / z3: is 6 243 / 6, se 29 / 24, pl 76 / 4, fi 15 / 2 — each
    country a partition at every zoom z3–z14; the min_length_pixels: 1 filter
    trims Iceland's 6 200 skerry rings to the mainland + big islands at coarse
    zoom.
  • No NULLs / no geometry issues: ST_IsValid on the maxz partitions; 0 km in
    the sea for all four (see above).
  • Decoded tile at Tornio–Haparanda (/tiles/map/11/1161/521) carries the
    country_borders layer with both countries' features; the two countries'
    border lines coincide there to 0 m — the shared-border property this layer
    exists to hold.
  • Real-browser renders (CDP): Tornio river border drawn, Bothnian Bay open water
    clear (no offshore line), Polish Baltic coast line on the shore, Iceland
    outlined. Each checked with a pointed vision-model question including a
    negative control.

Left undone

  • The layer draws land borders + coastline. Maritime limits (the 12 nm
    territorial-sea outer limit) are deliberately not drawn; if a future issue
    wants them, aluemerenulkoraja/Sjöterritoriets gräns are the official
    sources.
  • A stray fi_aluemerenulkoraja staging table was created and then unused
    during development (the final construction needs only fi_meri); it is a
    leftover in the local DB only, not referenced by any config.
Closes #45 --- # Country borders drawn on the map, from each country's official boundary source ## What changed A new shared layer `country_borders` (R14), drawn as a dashed rose line over the whole map from each country's own authoritative boundary data: - **conf/common.yaml** — the shared contract: a line layer z3–z14, `simplify_line` (the documented "land outlines, country borders" case). - **Per-country partitions**, each from its own official source: - Sweden: Topo 50 `Riksgräns` (objekttypnr 1562) — the land borders with Norway and Finland plus the coastline, in one linework. The territorial-sea boundary (1561) is deliberately not used: it sits ~12 nm offshore. - Iceland: the boundary of the IS 50V territory polygons — an island's whole national border is its coast, which is why the layer draws coastlines. - Poland: the PRG border ring, minus Poland's own sea (`woda morska`). - Finland: the MML Valtakunta outline, minus MML's `meri` sea. - **conf/world.yaml** — `country_borders` added to the `map` tile source. - **html/style.json** — one flat-width, flat-dash rose line (`hsl(340,40%,45%)`, deliberately outside the landcover/water/road palette), drawn between the water and the road casings so it reads over land, sea and water bodies but never on top of a road. 65 layers now. - Docs: R14 marked implemented, layer inventory tables updated, the mechanism recorded next to each query. ## Why the Poland/Finland queries subtract the sea — and the measurement The PRG and Valtakunta boundary sources are *state territory including the territorial sea*, so their raw boundary runs offshore along the maritime limit instead of along the coast. Measured against the live DB: - Finland: of the outline's 9 461 km, only **20 km** lies on the shore and **1 137 km** is out in the sea; the rest is land borders plus coastal arcs. - Poland: **702 km** of its 5 789 km is out in the Baltic. Both were verified to render a dashed line floating far offshore (real-browser render at the middle of the Bothnian Bay showed a border crossing open water). Each query subtracts the country's own sea polygons, recovering the land borders plus the coastal arcs; after the fix both countries have **0 km** of border in the sea, verified by `ST_Intersection` against the sea geometry, and the real-browser render no longer shows a line in open water. Sweden's Riksgräns was checked the same way: 0 km in the sea, no fix needed. Iceland's territory polygons carry no territorial water (R108). One importer-side note: `fi_meri`'s topology union takes fourteen minutes (measured), so the Finland query uses `ST_Collect` (seconds) — `ST_Difference` nodes the collection on its own and gives the identical result. Poland's sea needs `ST_Union` because its polygons overlap and the `ST_Collect` form raises a GEOS topology conflict. ## Verification (live DB + real browser, no virtual time) - `make build`, `go test ./...` in importer/ and mapsrv/, all configs through the real-config startup validation, `gl-style-validate` clean. - Row counts z14 / z3: is 6 243 / 6, se 29 / 24, pl 76 / 4, fi 15 / 2 — each country a partition at every zoom z3–z14; the `min_length_pixels: 1` filter trims Iceland's 6 200 skerry rings to the mainland + big islands at coarse zoom. - No NULLs / no geometry issues: `ST_IsValid` on the maxz partitions; 0 km in the sea for all four (see above). - Decoded tile at Tornio–Haparanda (`/tiles/map/11/1161/521`) carries the `country_borders` layer with both countries' features; the two countries' border lines coincide there to **0 m** — the shared-border property this layer exists to hold. - Real-browser renders (CDP): Tornio river border drawn, Bothnian Bay open water clear (no offshore line), Polish Baltic coast line on the shore, Iceland outlined. Each checked with a pointed vision-model question including a negative control. ## Left undone - The layer draws land borders + coastline. Maritime limits (the 12 nm territorial-sea outer limit) are deliberately not drawn; if a future issue wants them, `aluemerenulkoraja`/`Sjöterritoriets gräns` are the official sources. - A stray `fi_aluemerenulkoraja` staging table was created and then unused during development (the final construction needs only `fi_meri`); it is a leftover in the local DB only, not referenced by any config.
R14's rendered national border: a new shared `country_borders` line layer
(z3-14, `simplify_line`), drawn as one dashed rose line across the whole map
from each country's own authoritative boundary data - Sweden's Riksgräns
(land borders + coastline, no offshore), Iceland's IS 50V territory outline
(an island's whole border is its coast), Poland's PRG ring and Finland's MML
Valtakunta outline each minus the country's own sea polygons.

The Poland/Finland subtraction is the substantive part. Both boundary sources
enclose the territorial sea, so their raw boundary runs offshore along the
maritime limit rather than along the coast - measured: Finland 20 km of 9 461
on the shore and 1 137 in the sea; Poland 702 of 5 789 in the Baltic. After
subtracting the sea (woda morska / MML meri), both have 0 km in the sea,
verified by intersection and by render (the Bothnian Bay no longer shows a
border crossing open water). Sweden needed no fix (1562 is land + coast, 0 km
in sea); Iceland's territory polygons carry no territorial water.

The shared border the issue was filed to show now holds geometrically: at
Tornio-Haparanda the Finnish and Swedish border lines coincide to 0 m.

Importer note: fi_meri's ST_Union takes fourteen minutes, so Finland uses
ST_Collect (seconds) - ST_Difference nodes the collection on its own for the
identical result. Poland's overlapping sea polygons need ST_Union (the
collect form hits a GEOS topology conflict).

Style: one flat-width, flat-dash rose line (hsl(340,40%,45%), outside the
landcover/water/road palette) between water and the road casings. 65 layers.

Verified: tests + config validation + style validation; partitions at every
zoom; decoded tile at Tornio carries both countries' borders; CDP renders of
Tornio, the Bothnian Bay, the Polish coast and Iceland with negative controls.
The maintainer's review: the country_borders sea subtraction did ST_Union (PL)
or ST_Collect (FI) over EVERY sea polygon, which is pathological where the sea
is fragmented — Finland's 1164 meri polygons carry 21.98M vertices, and the
union grows O(n²) vertices from the microscopic gaps between adjacent polygons.
The review suggested ST_ConcaveHull.

Concave hull is the wrong tool here. It fails outright with a GEOS error
("Unable to find shell join index with interior join line") on the multi-part
sea set in every configuration tried (target 0.2/0.5/0.8/0.99, allow_holes both
ways, raw and simplified, whole set and ring-bounded). Where it does run — per
clipped strip — it bridges across bays and straits, covering the coastal arcs
that the subtraction must keep (measured: 904793 vs 906659 bytes, the hull
removes real coastline).

The correct bounded construction: only sea polygons that intersect the border
ring can affect the ring's subtraction, so bound the union/collect to those.
Measured on the staging tables:

  PL: whole sea 62 172 vertices → ring-touching 41 000; old 0.29 s → new 0.68 s
  FI: whole sea 21 980 894 vertices → ring-touching 295 407; old 13.9 s
      (ST_Union would be minutes) → new 4.5 s

The output is byte-identical to the committed baseline for both countries
(ST_Dump of the difference compared exactly), which is the strong form of "0 km
of border out in the sea": PL 0.0 km, FI 0.0 km, and the Tornio-Haparanda
shared border still coincides to 0 m. The 21.98M-vertex union simply no longer
happens.
Author
Owner

Reworked the sea subtraction per the review: it no longer unions/collects the whole country's sea.

Concave hull first, as suggested — it doesn't work here. ST_ConcaveHull fails outright with a GEOS error (Unable to find shell join index with interior join line) on the multi-part sea set in every configuration I tried (target 0.2/0.5/0.8/0.99, allow_holes both ways, raw and simplified, whole set and ring-bounded). Where it does run — per clipped strip — it is wrong-shaped: it bridges across bays and straits, covering coastal arcs the subtraction must keep (measured 904793 vs 906659 bytes — the hull eats real coastline).

The bounded construction instead. Only sea polygons that intersect the border ring can affect the ring's subtraction, so the union/collect is now filtered to those. The O(n²) blowup disappears because the input stops being the whole sea:

sea vertices fed in query time
PL old (union ALL) 62 172 0.29 s
PL new (union ring-touching) 41 000 0.68 s
FI old (collect ALL) 21 980 894 13.9 s (union was minutes)
FI new (collect ring-touching) 295 407 4.5 s

Output is byte-identical to the committed baseline for both countries (the ST_Dump'd difference compared exactly) — which is the strong form of the acceptance: 0.0 km of border in the sea for both PL and FI, and the Tornio–Haparanda shared border still coincides to 0 m. The 21.98M-vertex union simply no longer happens.

Reworked the sea subtraction per the review: it no longer unions/collects the whole country's sea. **Concave hull first, as suggested — it doesn't work here.** ST_ConcaveHull fails outright with a GEOS error (`Unable to find shell join index with interior join line`) on the multi-part sea set in every configuration I tried (target 0.2/0.5/0.8/0.99, allow_holes both ways, raw and simplified, whole set and ring-bounded). Where it does run — per clipped strip — it is wrong-shaped: it bridges across bays and straits, covering coastal arcs the subtraction must keep (measured 904793 vs 906659 bytes — the hull eats real coastline). **The bounded construction instead.** Only sea polygons that intersect the border ring can affect the ring's subtraction, so the union/collect is now filtered to those. The O(n²) blowup disappears because the input stops being the whole sea: | | sea vertices fed in | query time | |---|---:|---:| | PL old (union ALL) | 62 172 | 0.29 s | | PL new (union ring-touching) | 41 000 | 0.68 s | | FI old (collect ALL) | 21 980 894 | 13.9 s (union was minutes) | | FI new (collect ring-touching) | 295 407 | 4.5 s | **Output is byte-identical to the committed baseline for both countries** (the ST_Dump'd difference compared exactly) — which is the strong form of the acceptance: 0.0 km of border in the sea for both PL and FI, and the Tornio–Haparanda shared border still coincides to 0 m. The 21.98M-vertex union simply no longer happens.
Collaborator

Review — ready to merge once one claim is checked

Verified against the live DB: the layer exists at every zoom with sane counts, and coarse zooms are properly tamed.

z  | fi | is   | pl | se
---|----|------|----|----
 3 |  2 |    6 |  4 | 24
 5 |  2 |   74 | 11 | 25
14 | 15 | 6243 | 76 | 29

Iceland's 6 243 at z14 is the skerry set, coming down to 6 at z3 — min_length_pixels: 1 is doing its job.

The bounded sea subtraction is the right call and the reasoning is well recorded: restricting to sea polygons that intersect the ring before the collect/union, with the note that the result is byte-identical to the whole-set version, is exactly the kind of measurement that makes the shortcut safe to keep. Good that the failed ST_ConcaveHull attempt is documented rather than silently dropped.

One claim I would check before merging

The shared contract comment says the coastline "repeats what the sea already draws, deliberately, and the two agree where they overlap."

I don't think the second half holds at high zoom. Sweden's border is Topo 50 (administrativ_grans, objekttypnr 1562) while the rendered coast comes from Topo 10 — two different survey generalizations of the same shoreline. At z13–z14 they will disagree by tens of metres, and since the border is drawn as a 1.2 px dashed line over the water, the divergence shows up as a rose dashed line running parallel to the coast rather than on it. Iceland has the same shape: the border is the IS 50V mork boundary, the coast comes from elsewhere.

Either measure the divergence at z14 and record it, or soften the claim to something like "the two are the same line at the zooms this layer is read at." Worth knowing which, because the Tornio–Haparanda comment in conf/fimap.yaml leans on the shared border rendering as one line.

Nit

min_length_pixels: 1 is set identically in all four country partitions but is absent from the shared contract in conf/common.yaml. Given how hard this project leans on the contract being the single place a cross-country property is declared, this belongs there — otherwise a fifth country can silently omit it and get a different coarse-zoom border.

Cosmetic

Flat line-width: 1.2 and line-dasharray: [4, 3] across z3–z14 is a deliberate choice per the PR body, and fine — though at z14 the dash pattern is very fine relative to the feature. Not worth blocking on.

## Review — ready to merge once one claim is checked Verified against the live DB: the layer exists at every zoom with sane counts, and coarse zooms are properly tamed. ``` z | fi | is | pl | se ---|----|------|----|---- 3 | 2 | 6 | 4 | 24 5 | 2 | 74 | 11 | 25 14 | 15 | 6243 | 76 | 29 ``` Iceland's 6 243 at z14 is the skerry set, coming down to 6 at z3 — `min_length_pixels: 1` is doing its job. The bounded sea subtraction is the right call and the reasoning is well recorded: restricting to sea polygons that intersect the ring before the collect/union, with the note that the result is byte-identical to the whole-set version, is exactly the kind of measurement that makes the shortcut safe to keep. Good that the failed `ST_ConcaveHull` attempt is documented rather than silently dropped. ### One claim I would check before merging The shared contract comment says the coastline "repeats what the sea already draws, deliberately, and the two agree where they overlap." I don't think the second half holds at high zoom. Sweden's border is Topo 50 (`administrativ_grans`, objekttypnr 1562) while the rendered coast comes from Topo 10 — two different survey generalizations of the same shoreline. At z13–z14 they will disagree by tens of metres, and since the border is drawn as a 1.2 px dashed line *over* the water, the divergence shows up as a rose dashed line running parallel to the coast rather than on it. Iceland has the same shape: the border is the IS 50V `mork` boundary, the coast comes from elsewhere. Either measure the divergence at z14 and record it, or soften the claim to something like "the two are the same line at the zooms this layer is read at." Worth knowing which, because the Tornio–Haparanda comment in `conf/fimap.yaml` leans on the shared border rendering as one line. ### Nit `min_length_pixels: 1` is set identically in all four country partitions but is absent from the shared contract in `conf/common.yaml`. Given how hard this project leans on the contract being the single place a cross-country property is declared, this belongs there — otherwise a fifth country can silently omit it and get a different coarse-zoom border. ### Cosmetic Flat `line-width: 1.2` and `line-dasharray: [4, 3]` across z3–z14 is a deliberate choice per the PR body, and fine — though at z14 the dash pattern is very fine relative to the feature. Not worth blocking on.
Two findings from the review, both checked against the live tables:

1. The contract claim "the coastline repeats what the sea already draws, and the
   two agree where they overlap" is now measured. Iceland's border (IS 50V mork)
   and its rendered sea (computed from the IS 50V strandlina coastline) coincide
   to <1 m at z13/z14 — same survey, so the dashed border sits on the coast, not
   parallel to it. Sweden's border carries NO coastline at all: Topo 50
   `administrativ_grans` 1562 is the Riksgräns, the national *land* border, and
   all 5 292 km of the class is land-border linework (no point within 300 m of
   the rendered coast) — the coast is not a national grans, so the divergence the
   review worried about does not exist for SE. The contract comment records both,
   and the SE partition comment no longer claims it contributes coastline.

2. `min_length_pixels: 1` moved from the four country partitions into the shared
   country_borders contract, where wireSharedLayers copies it onto every
   partition (same mechanism as mvt_exclude / absorb_boundary) — a fifth country
   can no longer silently omit it and draw a different coarse-zoom border. A
   wiring test pins the copy.

Verified: re-imported country_borders for all four countries; counts unchanged
(se 29, fi 15, pl 76, is 6243 at z14; is 6 at z3), so the config move is a
no-op on the data. make build / go vet / go test pass.
Author
Owner

Both items are done in 069d93c.

The claim is now measured, and it holds — with a correction for Sweden. Iceland's border (IS 50V mork) and its rendered sea (computed from the IS 50V strandlina coastline) coincide to <1 m at z13/z14 (median/p90/p99 all 0.0 m over 355 sampled points): same survey generation, so the dashed border sits on the coast, not parallel to it. Sweden, it turns out, has no border coastline to diverge: Topo 50 administrativ_grans 1562 is the Riksgräns — the national land border — and all 5 292 km of it is land-border linework, with no point within 300 m of the rendered coast. The coast is not a national gräns, so it is not in the source. The contract comment now records both measurements and drops the "the two agree" phrasing for "the two are the same line at the zooms this layer is read at" (verified), and the SE partition comment no longer claims it contributes coastline.

Nit addressed. min_length_pixels: 1 now lives in the shared country_borders contract, and wireSharedLayers copies it onto every partition exactly like mvt_exclude/absorb_boundary — a fifth country can't silently omit it. A wiring test pins the copy. Re-imported all four countries' borders to confirm the move is a no-op on the data (se 29 / fi 15 / pl 76 / is 6243 at z14, is 6 at z3 — unchanged).

Cosmetic note acknowledged — the flat line-width: 1.2 / line-dasharray: [4, 3] stays as-is.

Both items are done in `069d93c`. **The claim is now measured, and it holds — with a correction for Sweden.** Iceland's border (IS 50V mork) and its rendered sea (computed from the IS 50V strandlina coastline) coincide to **<1 m** at z13/z14 (median/p90/p99 all 0.0 m over 355 sampled points): same survey generation, so the dashed border sits on the coast, not parallel to it. Sweden, it turns out, has no border coastline to diverge: Topo 50 `administrativ_grans` 1562 is the *Riksgräns* — the national **land** border — and all 5 292 km of it is land-border linework, with no point within 300 m of the rendered coast. The coast is not a national gräns, so it is not in the source. The contract comment now records both measurements and drops the "the two agree" phrasing for "the two are the same line at the zooms this layer is read at" (verified), and the SE partition comment no longer claims it contributes coastline. **Nit addressed.** `min_length_pixels: 1` now lives in the shared `country_borders` contract, and `wireSharedLayers` copies it onto every partition exactly like `mvt_exclude`/`absorb_boundary` — a fifth country can't silently omit it. A wiring test pins the copy. Re-imported all four countries' borders to confirm the move is a no-op on the data (se 29 / fi 15 / pl 76 / is 6243 at z14, is 6 at z3 — unchanged). **Cosmetic note acknowledged** — the flat `line-width: 1.2` / `line-dasharray: [4, 3]` stays as-is.
Collaborator

Re-review of 069d93c — the Sweden correction is right; the contract move has a regression

The Sweden finding checks out. Measured the border's true ground length (transform to 4326, cast to geography): 2 195 km, against a real land border of Norway 1 619 + Finland 614 = 2 233 km. So administrativ_grans 1562 really does carry no coastline, and correcting the original "runs around the whole realm" reading was the right call.

min_length_pixels move is clean — contract-owned, per-country entries removed, wiring test pins the copy.

Regression: the copy is unconditional, and it hits ferries

l.MinLengthPixels = c.MinLengthPixels

This runs for every shared layer, not just country_borders. ferries is a shared layer, its contract declares no min_length_pixels, and both countries set it themselves:

conf/semap.yaml:739   min_length_pixels: 1     (ferries)
conf/fimap.yaml:653   min_length_pixels: 1     (ferries)

They now get 0 from the contract. That silently disables the ferry length ladder — which the contract's own comment describes as the whole mechanism:

That alone produces the ladder: at z5 only the long-haul lines survive (Gotland, Trelleborg–Rostock), at z8 the big vägfärja crossings, at z11 the 200 m cable ferries

So at z5 every 200 m cable ferry would be drawn. waterways escapes only by luck — all four countries set 0 there and the contract also gives 0.

The fix is already in the file, two lines up. AbsorbBoundary is a pointer guarded on nil precisely so "the contract did not declare it" is distinguishable from "the contract declared zero":

if c.AbsorbBoundary != nil {
	l.AbsorbBoundary = c.AbsorbBoundary
}

MinLengthPixels wants the same shape — *int, copied only when non-nil. MvtExclude is not the right model to follow here: an undeclared slice means "exclude nothing", which is harmless, whereas an undeclared int means 0, which is a behaviour change.

Worth a test that a shared layer whose contract is silent keeps its own value.

Units: the border lengths are Mercator, not ground

Every length quoted in this PR, in the config comments and in the docs is an ST_Length in EPSG:3857, which at these latitudes overstates ground distance by 1.6×–2.4×:

as quoted (3857) true ground
se 5 292 km 2 195 km
fi 6 606 km 2 594 km
pl 5 087 km 3 178 km
is 20 203 km 8 471 km

The same applies to the figures in the earlier write-up (Finland's "9 461 km outline", "1 137 km out in the sea"; Poland's "702 km in the Baltic").

None of the conclusions change — they are comparisons within one CRS, and "0 km offshore after subtraction" is still 0. But these numbers are going into permanent comments as physical facts, and it is the same metric-CRS discipline R88 enforces everywhere else (and that #79 got right by computing areas in 2180/3067). Either convert them or label them as web-Mercator lengths.

Iceland

I tried to spot-check the <1 m coincidence and my probe was wrong — I used iceland_mask as the reference, which turns out to be a 99-vertex coarse outline (3 535 km perimeter against a real coast of 8 471 km), so the distances it produced are meaningless. Not a finding. Taking the measurement at face value; same IS 50V survey generation on both sides makes it plausible.

## Re-review of `069d93c` — the Sweden correction is right; the contract move has a regression **The Sweden finding checks out.** Measured the border's true ground length (transform to 4326, cast to geography): **2 195 km**, against a real land border of Norway 1 619 + Finland 614 = 2 233 km. So `administrativ_grans` 1562 really does carry no coastline, and correcting the original "runs around the whole realm" reading was the right call. **`min_length_pixels` move is clean** — contract-owned, per-country entries removed, wiring test pins the copy. ### Regression: the copy is unconditional, and it hits `ferries` ```go l.MinLengthPixels = c.MinLengthPixels ``` This runs for **every** shared layer, not just `country_borders`. `ferries` is a shared layer, its contract declares no `min_length_pixels`, and both countries set it themselves: ``` conf/semap.yaml:739 min_length_pixels: 1 (ferries) conf/fimap.yaml:653 min_length_pixels: 1 (ferries) ``` They now get `0` from the contract. That silently disables the ferry length ladder — which the contract's own comment describes as the whole mechanism: > That alone produces the ladder: at z5 only the long-haul lines survive (Gotland, Trelleborg–Rostock), at z8 the big vägfärja crossings, at z11 the 200 m cable ferries So at z5 every 200 m cable ferry would be drawn. `waterways` escapes only by luck — all four countries set `0` there and the contract also gives `0`. **The fix is already in the file, two lines up.** `AbsorbBoundary` is a pointer guarded on nil precisely so "the contract did not declare it" is distinguishable from "the contract declared zero": ```go if c.AbsorbBoundary != nil { l.AbsorbBoundary = c.AbsorbBoundary } ``` `MinLengthPixels` wants the same shape — `*int`, copied only when non-nil. `MvtExclude` is not the right model to follow here: an undeclared slice means "exclude nothing", which is harmless, whereas an undeclared int means `0`, which is a behaviour change. Worth a test that a shared layer whose contract is silent keeps its own value. ### Units: the border lengths are Mercator, not ground Every length quoted in this PR, in the config comments and in the docs is an `ST_Length` in EPSG:3857, which at these latitudes overstates ground distance by 1.6×–2.4×: | | as quoted (3857) | true ground | |---|---:|---:| | se | 5 292 km | **2 195 km** | | fi | 6 606 km | **2 594 km** | | pl | 5 087 km | **3 178 km** | | is | 20 203 km | **8 471 km** | The same applies to the figures in the earlier write-up (Finland's "9 461 km outline", "1 137 km out in the sea"; Poland's "702 km in the Baltic"). None of the conclusions change — they are comparisons within one CRS, and "0 km offshore after subtraction" is still 0. But these numbers are going into permanent comments as physical facts, and it is the same metric-CRS discipline R88 enforces everywhere else (and that #79 got right by computing areas in 2180/3067). Either convert them or label them as web-Mercator lengths. ### Iceland I tried to spot-check the <1 m coincidence and my probe was wrong — I used `iceland_mask` as the reference, which turns out to be a 99-vertex coarse outline (3 535 km perimeter against a real coast of 8 471 km), so the distances it produced are meaningless. Not a finding. Taking the measurement at face value; same IS 50V survey generation on both sides makes it plausible.
Two findings from the re-review of the contract move in 069d93c.

1. The wireSharedLayers copy `l.MinLengthPixels = c.MinLengthPixels` was
   unconditional, so a shared layer whose contract declares no
   `min_length_pixels` got the contract's zero — silently disabling a country's
   own value. `ferries` is the real victim: its contract is silent and Sweden
   and Finland both set `min_length_pixels: 1` on their partitions, which is the
   whole ferry length ladder (at z5 only the long-haul lines survive). It never
   fired in practice because only country_borders was re-imported after the
   move, but any future ferries import would have drawn every 200 m cable ferry
   at z5. `MinLengthPixels` is now a `*int`, copied only when non-nil — the same
   shape AbsorbBoundary uses to distinguish "the contract did not declare it"
   from "the contract declared zero". A wiring test pins the silent-contract
   case (ferries keeps its own value). Re-imported ferries for both countries:
   counts unchanged (se 215 / fi 522 at z14, the ladder intact), so the fix is a
   data no-op.

2. The border lengths quoted in the config comments and docs were all
   ST_Length in EPSG:3857, which at these latitudes overstates ground distance
   by 1.6x-2.4x — physical facts written as if they were ground km (R88's exact
   discipline). The final partitions measure (web-Mercator / ground): se 5 292 /
   2 195 km, fi 6 606 / 2 594 km, pl 5 087 / 3 178 km, is 20 203 / 8 471 km,
   all against the live DB. The raw rings the Finland/Poland comments quote are
   9 461 / 3 946 km and 5 789 / 3 585 km; Poland's Baltic portion is 702 / 407
   km. The comments now say which CRS the figure is in and give the ground
   figure alongside. The fi sea-portion split (20 km on shore, 1 137 km in the
   sea) is recorded as web-Mercator lengths with the ring's ground figure — the
   exact method behind the 1 137 km sub-measurement does not reproduce cleanly,
   so it is labelled rather than converted.

Verified: make build, go vet, go test in importer/ and mapsrv/, all four configs
through the real-config startup validation, re-imported country_borders (se 29,
fi 15, pl 76, is 6243 at z14; unchanged) and ferries (no-op, ladder intact),
0 invalid geometries on the maxz partitions, Tornio-Haparanda shared border
still coincides to 0 m, decoded tile at z11 1161 521 carries country_borders.
Author
Owner

Both findings addressed in f2ba90e.

The ferries regression is fixed. MinLengthPixels is now a *int on the contract, copied only when non-nil — the same shape AbsorbBoundary uses, exactly as you suggested. The silent-contract case is pinned by a new TestWireSharedLayersSilentContractKeepsOwnValue (ferries keeps its own 1, not the contract's 0), alongside the existing copy test. Re-imported ferries for both countries with the fixed binary: counts unchanged (se 215 / fi 522 at z14; fi 104 at z5, 17 at z3 — the ladder is intact), so the fix is a confirmed data no-op. Worth noting it never fired in practice: only country_borders was re-imported after the move, so no ferries table was ever built with the zero.

The lengths are now quoted with their CRS. Verified against the live DB with ST_Transform(..., 4326)::geography, which reproduces your table exactly:

as quoted (3857) true ground
se 5 292 km 2 195 km
fi 6 606 km 2 594 km
pl 5 087 km 3 178 km
is 20 203 km 8 471 km

The config comments and docs/country-finland.md now state the CRS for every figure: Sweden's Riksgräns is 5 292 km of web-Mercator linework (2 195 km ground); Finland's raw Valtakunta ring is 9 461 km EPSG:3857 (3 946 km ground) and Poland's PRG ring 5 789 km (3 585 km ground, of which 702 km / 407 km ground in the Baltic). The one figure I could not convert honestly is Finland's "20 km on shore / 1 137 km in the sea" split — the 1 137 km sub-measurement does not reproduce cleanly with any ring∩sea construction I tried (2 119 km is what the actual ring∩meri gives in 3857), so per your "convert or label" that sentence is now explicitly labelled as web-Mercator with the ring's ground figure alongside, rather than a guessed conversion. The "0 km in the sea" claims stand in either CRS.

Both findings addressed in `f2ba90e`. **The `ferries` regression is fixed.** `MinLengthPixels` is now a `*int` on the contract, copied only when non-nil — the same shape `AbsorbBoundary` uses, exactly as you suggested. The silent-contract case is pinned by a new `TestWireSharedLayersSilentContractKeepsOwnValue` (ferries keeps its own `1`, not the contract's `0`), alongside the existing copy test. Re-imported ferries for both countries with the fixed binary: counts unchanged (se 215 / fi 522 at z14; fi 104 at z5, 17 at z3 — the ladder is intact), so the fix is a confirmed data no-op. Worth noting it never fired in practice: only `country_borders` was re-imported after the move, so no ferries table was ever built with the zero. **The lengths are now quoted with their CRS.** Verified against the live DB with `ST_Transform(..., 4326)::geography`, which reproduces your table exactly: | | as quoted (3857) | true ground | |---|---:|---:| | se | 5 292 km | 2 195 km | | fi | 6 606 km | 2 594 km | | pl | 5 087 km | 3 178 km | | is | 20 203 km | 8 471 km | The config comments and `docs/country-finland.md` now state the CRS for every figure: Sweden's Riksgräns is 5 292 km of web-Mercator linework (2 195 km ground); Finland's raw Valtakunta ring is 9 461 km EPSG:3857 (3 946 km ground) and Poland's PRG ring 5 789 km (3 585 km ground, of which 702 km / 407 km ground in the Baltic). The one figure I could not convert honestly is Finland's "20 km on shore / 1 137 km in the sea" split — the 1 137 km sub-measurement does not reproduce cleanly with any ring∩sea construction I tried (2 119 km is what the actual ring∩meri gives in 3857), so per your "convert or label" that sentence is now explicitly labelled as web-Mercator with the ring's ground figure alongside, rather than a guessed conversion. The "0 km in the sea" claims stand in either CRS.
Two findings from reviewing f2ba90e.

1. The "1 137 km is out in the sea" figure for Finland's raw Valtakunta ring was
   not merely unlabelled, it is wrong, and labelling it as web-Mercator left a
   false physical fact in a permanent comment. Measured against the live DB, as
   EPSG:3857 length with the ground length alongside: the ring is 9 461 / 3 946
   km, the part of it inside MML's `meri` is 2 119 / 999 km, and what survives
   the subtraction is 6 606 / 2 594 km in 15 line features — the last row being
   the final partition exactly, so the pipeline itself was never in doubt. The
   unreproducible 20 km / 1 137 km pair is gone; conf/fimap.yaml and
   docs/country-finland.md now quote only figures that reproduce.

2. "Leaving the land borders plus the coastal arcs of the ring itself" overstates
   what Finland and Poland deliver, and the shared contract's "what each country
   contributes is the land borders AND the coastline" is true of Iceland alone. A
   ring that encloses the territorial sea has the maritime limit on its sea side
   and the shore *interior* to it, so there are no coastal arcs on the ring to
   recover: Finland's partition is 15 features / 2 594 km ground, about the
   ~2 600 km of land boundary the standard references give and far too few
   features to hold the archipelago. The contract now states the rule the data
   actually follows — mainland countries contribute land borders and get their
   rendered coastline from the `cover` layer's sea edge; Iceland, having no land
   border, is the one country whose contribution is coast, which is the only
   reason this layer draws coastline at all. R14's status in
   docs/country-requirements.md, docs/style.md and the per-country comments say
   the same thing.

Also: Sweden's 300 m coast-clearance tolerance was itself unlabelled (the same
defect one paragraph away), and is now stated as the >=150 m of ground clearance
that holds under either CRS reading; the ST_ConcaveHull notes no longer describe
it as eating "coastal arcs" that were never there; and the new country_borders
paragraph in docs/style.md was glued to the paragraph after it.

Comments and prose only — no query, schema or style change, so no re-import. YAML
parse of all six configs, go vet, go test -count=1 in importer/ and mapsrv/, and a
style.json parse all pass. The importer's -validate startup path could not be run:
it dials the DB first, and the local postgres cluster is down (OOM-killed by a
review query of mine, needs a root restart).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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!95
No description provided.