generalization: the parallel-merge corridor is flat-capped — issue #170 #196
No reviewers
Labels
No labels
blocked
claude-wip
map-report
map:is
map:se
needs-review
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
art/ismap!196
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-170-parallel-merge-endcap"
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 #170
What was wrong
parallel_merge_pixelsdrops a chain when 95% of its length lies inside abetter-ranked chain's corridor. The corridor was built with
ST_Buffer'sdefault round end caps, so it bulged a full radius past the better-ranked
chain's endpoints — and the coverage test then read "same line at this zoom"
for two situations that are nothing alike:
collapse, and
Any chain shorter than about 2 d that touched a better-ranked one was swallowed
whole.
dis one screen pixel, so it doubles every zoom down — 611 m at z8,1 223 m at z7, 2 446 m at z6 — and each level derives from the one above, so the
loss compounds. Cities are worst because that is where
merge_linescuts a roadinto short chains:
name,surface, and above alllevel, which changes atevery interchange bridge.
That is the whole of #170: the E4 in loose pieces at z7 and shattered at z6
(Sundsvall), E4 and E12 the same near Umeå, "all the orange roads chopped up"
around Örebro.
The fix
One argument —
ST_Buffer(b.geom, d, 'endcap=flat')inmergeLinesSQL(
importer/generalize.go). The corridor now ends wherebends, so lateralcontainment still counts and continuation does not. The
ST_DWithinprefilteris unchanged: a flat corridor is a subset of the round one it bounded.
Shared mechanism, so it applies to every layer that sets
parallel_merge_pixels— roads and railways in all countries, transit in se/pl/fi.
Numbers
Measured on Sweden's z7 roads pass, replaying the real cascade (the replay
reproduces the live tables exactly — z6 E4 came out at 474 features / 2 401 km,
byte-for-byte the live
map_roads_6_se):Trunk-and-above network, live, z10 → z6:
Railways gained too, less dramatically (z11 → z6, km): fi 12 778 → 13 819,
pl 31 509 → 32 128, se 21 565 → 21 591. Transit is unchanged to within a
kilometre — its corridors are 0.25–1 px on networks a few hundred km long.
The acceptance test
R147 on the
roadscontract (conf/shared/contracts/08-roads.yaml), newrequirement in
docs/country-requirements.md§11:Half a pixel is the z6 simplification tolerance, so the allowance is
displacement and nothing else. Coverage rather than length retention, because a
collapsed dual carriageway should halve the length and a length test cannot
tell that from a hole. z10 against z6 rather than adjacent levels, because the
defect was invisible one step at a time — z7 against z6 scored 99.1% while the
map was in pieces.
It fails on the old data and passes on the new:
(That is not a reconstruction: the fix was stashed, the binary rebuilt, Sweden's
roads regeneralized, the test re-run, and then the fix restored and the data
rebuilt — which is also how the before/after renders below were taken.)
Verification
make build,make test— clean. New unit testTestParallelCorridorIsFlatCappedlocks the end-cap style into the generatedSQL.
Regeneralized: roads z5–13 for is/se/pl/fi, railways z5–11 and transit z9–13
for se/pl/fi. ~3 min per country for roads, seconds for the rest.
Per-zoom sanity, Sweden roads: all ten z5–z14 partitions populated, 0 NULL
road_class, 0 empty or invalid geometries. Row counts z5–z14: 8 977 / 9 453/ 16 229 / 16 992 / 41 286 / 45 662 / 94 250 / 628 008 / 2 191 611 /
3 397 786.
Decoded tiles (
scripts/mvt-decode.py): z7 70/35 → roads 295 features; z635/17 → roads 587.
R101 pixel diff, real Chrome over CDP at the three reported locations,
before and after, map area only:
The gained pixels trace whole corridors — E4 Stockholm–Uppsala–Gävle, E18
Örebro–Västerås, E20 — filling exactly the gaps that were reported. The lost
pixels are scattered clusters of ≤ 8 px: label and shield placement shifting,
and antialiasing. No stretch of road went missing.
Acceptance tallies (fast bucket unless noted):
The two failures, both pre-existing and neither mine
level), every config. Greece's roads partition is all-NULL becausethe TEN-T delivery has no grade-separation column. The written exemption for
that is on
gr-roads-rail-waterways, which has not merged; the database hasGreece's roads from that branch while
main's contract does not know aboutthem. It fails identically on
mainwith this branch's changes reverted.conf/pl.context deadline exceeded, reproducibleon a re-run in isolation. The buildings layer is untouched here — nothing in
this change reads or writes it.
Left undone, deliberately
99.8%, so it passes on the strength of a sparse network, not on the strength
of the code that built it).
conf/gr/04-roads.yamland05-railways.yamllive on the unmerged
gr-roads-rail-waterwaysbranch, so there is no config onmainto regeneralize from. Whoever lands that branch should runimporter/importer import --layer roads --regeneralize 5-13 conf/grand therailways equivalent.
roads table goes from 1 043 features to 8 977, average feature length 9.1 km →
1.6 km against R95's 1 km floor, and the z5 Stockholm tile from 335 kB to
381 kB against a 512 kB budget. Those are the short chains that were being
deleted — they are the road, and they are contiguous now — but they are
fragments because
levelsplits a chain at every interchange. Droppinglevelfrom the merge key at coarse zoom halves the z6 chain count (9 853 → 4 679).
That is issue #167, which until now had an argument but no number; it now
has one, and it is worth doing before R95's floor is actually reached.