Closes #45 #95
No reviewers
Labels
No labels
blocked
claude-wip
map-report
map:is
map:se
needs-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
art/ismap!95
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-45-country-borders"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 thewhole map from each country's own authoritative boundary data:
simplify_line(the documented "land outlines, country borders" case).Riksgräns(objekttypnr 1562) — the land borders withNorway and Finland plus the coastline, in one linework. The territorial-sea
boundary (1561) is deliberately not used: it sits ~12 nm offshore.
national border is its coast, which is why the layer draws coastlines.
woda morska).merisea.country_bordersadded to themaptile source.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.
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:
1 137 km is out in the sea; the rest is land borders plus coastal arcs.
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_Intersectionagainst the sea geometry, and thereal-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_Differencenodes the collection on its own and gives the identical result. Poland's sea
needs
ST_Unionbecause its polygons overlap and theST_Collectform raises aGEOS topology conflict.
Verification (live DB + real browser, no virtual time)
make build,go test ./...in importer/ and mapsrv/, all configs throughthe real-config startup validation,
gl-style-validateclean.country a partition at every zoom z3–z14; the
min_length_pixels: 1filtertrims Iceland's 6 200 skerry rings to the mainland + big islands at coarse
zoom.
ST_IsValidon the maxz partitions; 0 km inthe sea for all four (see above).
/tiles/map/11/1161/521) carries thecountry_borderslayer with both countries' features; the two countries'border lines coincide there to 0 m — the shared-border property this layer
exists to hold.
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
territorial-sea outer limit) are deliberately not drawn; if a future issue
wants them,
aluemerenulkoraja/Sjöterritoriets gränsare the officialsources.
fi_aluemerenulkorajastaging table was created and then unusedduring development (the final construction needs only
fi_meri); it is aleftover in the local DB only, not referenced by any config.
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.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:
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.
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.
Iceland's 6 243 at z14 is the skerry set, coming down to 6 at z3 —
min_length_pixels: 1is 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_ConcaveHullattempt 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 50Vmorkboundary, 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.yamlleans on the shared border rendering as one line.Nit
min_length_pixels: 1is set identically in all four country partitions but is absent from the shared contract inconf/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.2andline-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.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_grans1562 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: 1now lives in the sharedcountry_borderscontract, andwireSharedLayerscopies it onto every partition exactly likemvt_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.Re-review of
069d93c— the Sweden correction is right; the contract move has a regressionThe 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_grans1562 really does carry no coastline, and correcting the original "runs around the whole realm" reading was the right call.min_length_pixelsmove is clean — contract-owned, per-country entries removed, wiring test pins the copy.Regression: the copy is unconditional, and it hits
ferriesThis runs for every shared layer, not just
country_borders.ferriesis a shared layer, its contract declares nomin_length_pixels, and both countries set it themselves:They now get
0from the contract. That silently disables the ferry length ladder — which the contract's own comment describes as the whole mechanism:So at z5 every 200 m cable ferry would be drawn.
waterwaysescapes only by luck — all four countries set0there and the contract also gives0.The fix is already in the file, two lines up.
AbsorbBoundaryis a pointer guarded on nil precisely so "the contract did not declare it" is distinguishable from "the contract declared zero":MinLengthPixelswants the same shape —*int, copied only when non-nil.MvtExcludeis not the right model to follow here: an undeclared slice means "exclude nothing", which is harmless, whereas an undeclared int means0, 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_Lengthin EPSG:3857, which at these latitudes overstates ground distance by 1.6×–2.4×: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_maskas 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.Both findings addressed in
f2ba90e.The
ferriesregression is fixed.MinLengthPixelsis now a*inton the contract, copied only when non-nil — the same shapeAbsorbBoundaryuses, exactly as you suggested. The silent-contract case is pinned by a newTestWireSharedLayersSilentContractKeepsOwnValue(ferries keeps its own1, not the contract's0), 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: onlycountry_borderswas 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:The config comments and
docs/country-finland.mdnow 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.