waterways: split centreline junctions on linestring interiors, stitch adjacent water polygons #67
No reviewers
Labels
No labels
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!67
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "night/issue-62-water-flow"
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?
#62 waterways: mid-point junction to river polygon missed by the Strahler bridge — fixed
Fixes the inverted water-flow importance at 65.55624/-17.69235 (z20, map=is).
Reporter's hypothesis: CONFIRMED. The Strahler network graph is built from
node keys that exist only at linestring endpoints, so a junction on the
interior of a linestring is invisible. The reported segment touches the river
polygon NW of it at interior vertex 5 (exactly the polygon's tip vertex), while
both endpoints are 267 m / 1006 m from the polygon — far outside the 30 m
bridge tolerance. The endpoint-only bridge never joined the segment to the
polygon, its drainage rooted elsewhere, and the orders inverted: the segment
was importance 1 while everything south of it was 2.
Root cause (two gaps in the same place):
_ww_segbuilt one edge per linestringwith node keys only at its ends.
touching polygons (here 7 river polygons running north to the coast) is one
water body, but every polygon was its own super-node and no stream endpoint
bridges two adjacent ones — so the chain, and anything that drains into it,
rooted nowhere. Even with the split, the segment's component had no ocean
node and the inversion persisted.
Fix (importer/strahler.go):
touches of bridging water polygons, including mid-edge ones, plus other
segments' endpoints landing on the interior) before building the edge list;
each piece becomes its own edge, so the junction node exists and inherits the
polygon's pid.
boundary only — a point-touch is not a connection), like the ocean edges.
Verification (live DB, reimported
-layer waterways conf/ismap.yaml):(10859,15): importance 1 → 4; everything south of it is 4— no longer higher. The z14 tile decodes with importance 4 on the reported
watercourse; real-browser check at z19.58 renders four waterways around the
junction, all importance 4.
instead of a dead-end super-node.
Horton-like, zero NULLs; per-zoom partitions cascade sanely.
FI 626,990). Replications reproduce each country's live histogram exactly
under the old rules (tool validated) and remain monotone Horton ladders under
the new rules; the order-5 gains (SE 6,627→14,657, FI 479→3,673) come from
the polygon-chain stitching (22,099 / 6,964 adjacent pairs), the same class
of connection the old code missed. PL does not use the strahler stage.
Review
The root-cause analysis is correct and the fix is the right shape. Verified Iceland's claimed histogram exactly (50,652 / 17,316 / 9,036 / 3,428 / 1,473, zero NULLs), and confirmed PL genuinely has no strahler stage. Builds, vet and tests pass. The
_ww_ends→_ww_junccleanup fix in681fbcbis correct.Three things worth addressing.
1. The adjacency filter silently drops real connections —
importer/strahler.go:169GeometryType(ST_Intersection(a.geom,b.geom)) IN ('LINESTRING','MULTILINESTRING')excludes two genuine cases:LINESTRINGplus point-touches, e.g.GEOMETRYCOLLECTION(LINESTRING(-1810523.6 9578196.6,-1810408.0 9578191.7),POINT(…),POINT(…))Full breakdown of linear/areal intersections between bridging polygons:
ST_Dimension(ST_Intersection(a.geom,b.geom)) >= 1is the predicate you want — verified it returns 1 for GC-with-a-line, 2 for polygons, 0 for POINT/MULTIPOINT, so it preserves the point-touch exclusion the comment argues for while admitting the rest.The count looks small, but impact isn't proportional: one missing link disconnects an entire chain, which is precisely the failure #62 was about.
2. The "both polygons must carry a stream node" filter breaks pass-through chains —
strahler.go:170-171The comment argues "a polygon with none cannot be a super-node, so stitching it is meaningless." That's the wrong test for a middle link. In A(used) — B(unused) — C(used), the Go padj loop creates
P_Binadjon its own, so including B would correctly carry drainage A→C. Excluding it drops both A–B and B–C and breaks the chain.Measured on Iceland: of 657 linear adjacencies only 435 survive the filter.
Dropping the filter links 37 more used polygons into chains and grows the largest chain from 34 to 37 polygons:
Modest, but it's the same class of bug you're fixing.
3. SE and FI were never reimported
The live DB still holds the pre-change SE 6,627 / FI 479 order-5 counts. The claimed SE 14,657 / FI 3,673 come from the replication model, not from running the stage. FI's top rung moving 479 → 3,673 is a 7.7× jump — a visible line-width change across the whole country's river network. Iceland got real live-DB verification; the two countries with the largest deltas got a model of one. AGENTS.md asks for the former.
Minor
CREATE INDEX ON _ww_padj (pa)is never used — the only read is a full-scanSELECT pa, pb … ORDER BY pa, pb.ST_Intersects, while endpoint-to-endpoint joins toleratesnap_meters(default 1 m). An endpoint 0.5 m off another line's interior still hangs off nothing.Disposition: wants the
ST_Dimensionfix and an SE/FI reimport before merge; the pass-through filter is a judgement call that could reasonably be a follow-up.The one mapsrv test failure (
glyph_dump_test.go:40: open /tmp/glyph_g.png: permission denied) is environmental and reproduces onmain— not this PR.The polygon-adjacency predicate tested GeometryType against ('LINESTRING','MULTILINESTRING'), which silently dropped two kinds of real connection: - a pair sharing an edge *and* touching at a stray point elsewhere intersects in a GEOMETRYCOLLECTION; - a pair that genuinely overlaps intersects in a POLYGON. Both are one water body. Measured against the live water tables, the test dropped 2 pairs on Iceland, 5 on Finland and 86 on Sweden (74 of Sweden's being overlaps). The counts are small but the impact is not proportional: one missing link disconnects a whole chain, which is the failure this issue was filed for. ST_Dimension(...) >= 1 is the predicate that was meant. It keeps the point-touch exclusion the comment argues for — water cannot flow through a corner, and POINT/MULTIPOINT are dimension 0 — while admitting the shared edge, the overlap and the collection that contains a line. Verified on Iceland (the only country whose source data is on this box, reimported -layer waterways conf/ismap.yaml): orders 1-5 are 50 656 / 17 312 / 9 056 / 3 405 / 1 476, a monotone Horton ladder with zero NULLs, the per-zoom cascade is sane (81 905 / 52 862 / 13 132 / 4 960 at z14/12/10/8), and the watercourse reported at 65.55624/-17.69235 still carries importance 4 — the inversion this issue was filed for stays fixed. The other half of the predicate — that both polygons must carry a stream node, which drops the pass-through link of a chain — is issue #70. It is left alone here because lifting it grows the adjacency by half again on Iceland and nearly triples it on Finland, and so needs its own verified import per country rather than a replication. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Review items addressed —
6244b061.
ST_Dimensionfix — done. The adjacency predicate now testsST_Dimension(ST_Intersection(a.geom,b.geom)) >= 1instead ofGeometryType(...) IN ('LINESTRING','MULTILINESTRING'). Point-touches stay excluded (dimension 0); the shared-edge-plus-stray-point GEOMETRYCOLLECTION and the genuine overlap are now admitted.Verified on Iceland (reimported
-layer waterways conf/ismap.yaml):Monotone Horton ladder, per-zoom cascade sane (81 905 / 52 862 / 13 132 / 4 960 at z14/12/10/8), and the watercourse at 65.55624/-17.69235 still carries importance 4 — #62's inversion stays fixed.
2. Pass-through polygons — split out as #70, not fixed here. On measuring it, lifting the both-ends-used filter is a much bigger change than I implied when I raised it:
ST_DimensionHalf again on Iceland, nearly triple on Finland. That deserves its own verified import rather than riding along here. #70 carries the full measurements and the done-when.
(The old-rule column lands within 3% of the 22 099 SE / 6 964 FI this PR reported, which is the check that the replication is faithful.)
3. SE/FI reimport — still open, and I could not close it. Only Iceland's source data is on the dev box:
data/se_topo10/hydro_sverige.gpkgis absent (onlykommunikation_sverige.gpkgis local) anddata/fi/is empty.-layer waterways conf/semap.yamltherefore kicks off a full ~11 GB Geotorget delivery, which I stopped. This needs running where the data already lives. It applies to the PR's own order-5 shifts (FI 479 → 3 673 by replication) as much as to my change.Minor: dropped the unused
CREATE INDEX ON _ww_padj (pa)— the only read is a full-scanSELECT pa, pb … ORDER BY pa, pb, which a btree onpaalone cannot serve. The tolerance inconsistency I noted (exactST_Intersectsfor endpoint-on-interior vssnap_meterselsewhere) is left as-is; it is a design question, not a defect.go build,go vetandgo testpass onimporter. Worth notinggo vetearns its keep here —prepSQLbuilds the SQL throughfmt.Sprintf, so a literal%in a comment inside that string is a format verb; vet caught one I introduced.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.