From 0006c681c803c9f97c5133308805d04288da82fd Mon Sep 17 00:00:00 2001 From: geoneutrino Date: Wed, 2 Sep 2026 22:53:41 +0200 Subject: [PATCH] Fix scaleGeometry dropping polygons whose ring collapses scaleRing() discards a point that repeats its predecessor only once two points are already on the output ring, so a duplicated FIRST vertex slips through. That shifts every later vertex one place along, the ring's closing vertex then matches the duplicate at j==4, and resize() truncates the whole ring to two points. scaleGeometry() drops any polygon whose outer ring has fewer than 4 points, so the feature vanishes from the tile entirely - at any size, not just for sub-pixel slivers. A clipped quadrilateral spanning ~1700 x 1600 tile units disappeared from 2 of the 4 z14 tiles covering the Rhine at 48.2098/7.6475, rendering as dry land inside the river while z13 and every lower zoom were correct. Do not fix this inside scaleRing(): its backtracking window is positional, so skipping the duplicate there shifts the whole ring and changes where later matches truncate it, silently rewriting rings that work today. That was tried first and it cost Kaptai Lake 80 of its 448 outer points, drawing a chord straight across the lake. Instead retry a ring that collapsed below 4 points without the backtracking, and only then. Rings that already survive are untouched. Verified on a Bangladesh extract with --threads 1, since tilemaker's output varies slightly between multi-threaded runs: no feature loses geometry or disappears, 1195 features are restored, and the 411 features whose geometry changes do so only by gaining area, where a restored piece is unioned into a combined feature. --- include/coordinates_geom.h | 1 + src/coordinates_geom.cpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/include/coordinates_geom.h b/include/coordinates_geom.h index 1c035d9c..5b4d311c 100644 --- a/include/coordinates_geom.h +++ b/include/coordinates_geom.h @@ -25,6 +25,7 @@ class TileBbox { std::pair scaleLatpLon(double latp, double lon) const; void scaleRing(Ring &dst, Ring const &src) const; Ring scaleRing(Ring const &src) const; + void scaleRingNoBacktrack(Ring &dst, Ring const &src) const; void scaleGeometry(MultiPolygon &dst, MultiPolygon const &src) const; MultiPolygon scaleGeometry(MultiPolygon const &src) const; std::pair floorLatpLon(double latp, double lon) const; diff --git a/src/coordinates_geom.cpp b/src/coordinates_geom.cpp index 05180d84..4916318c 100644 --- a/src/coordinates_geom.cpp +++ b/src/coordinates_geom.cpp @@ -50,6 +50,24 @@ void TileBbox::scaleRing(Ring &points, Ring const &src) const { } } +// Scaling that only drops points repeating their immediate predecessor. Used as +// a fallback for rings that scaleRing() collapses below 4 points: its +// backtracking window is positional, so a duplicated FIRST vertex shifts every +// later vertex by one, the closing vertex then matches that duplicate at j==4, +// and resize() truncates the whole ring. Without the backtracking there is +// nothing to mis-align, and the ring survives with its shape intact. +void TileBbox::scaleRingNoBacktrack(Ring &points, Ring const &src) const { + points.clear(); + points.reserve(src.size()); + for(auto const &i: src) { + auto scaled = scaleLatpLon(i.y(), i.x()); + if (!points.empty() && + points.back().x()==scaled.first && points.back().y()==scaled.second) + continue; + points.push_back(Point(scaled.first,scaled.second)); + } +} + Ring TileBbox::scaleRing(Ring const &src) const { Ring points; scaleRing(points, src); @@ -66,8 +84,15 @@ void TileBbox::scaleGeometry(MultiPolygon &dst, MultiPolygon const &src) const { // Copy the outer ring scaleRing(p.outer(), poly.outer()); - if (p.outer().size()<4) - continue; + if (p.outer().size()<4) { + // A collapsed outer ring means the whole feature disappears from the + // tile, at any size - so before giving up, retry without the + // backtracking that can truncate a ring to two points. Every ring that + // already survives is left untouched. + scaleRingNoBacktrack(p.outer(), poly.outer()); + if (p.outer().size()<4) + continue; + } // Copy the inner rings if (p.inners().size() < poly.inners().size())