From 3e3e03c01ad3ac588abca5361c08457f044e601f Mon Sep 17 00:00:00 2001 From: "daniel.korn" Date: Sat, 31 Oct 2026 18:20:41 +0100 Subject: [PATCH] perf: index big outer rings when assigning inner rings of multipolygons wayListMultiPolygon called within(inner, outer) for every pair, and Boost analyses the outer ring afresh each time. Skip pairs whose bounding boxes aren't nested, and for big outer rings with many inners, index the outer segments once: an inner ring that touches no outer segment is decided by one vertex; touching rings still go to within(inner, outer). Planet: 273,813 indexed calls checked against within(), 0 mismatches; Germany output identical. Guri Reservoir 48 -> 9 s (-81%); planet relations phase 105 -> 61 s (-42%, wall -2.4%) together with the correct.hpp closing fix. --- src/osm_store.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/osm_store.cpp b/src/osm_store.cpp index 5ec7a81c..3bb086f2 100644 --- a/src/osm_store.cpp +++ b/src/osm_store.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "node_store.h" #include "way_store.h" @@ -16,6 +17,42 @@ static inline bool isClosed(const std::vector& way) { return !way.empty() && way.front() == way.back(); } +// geom::within(inner, outer) for many inner rings against one big outer ring. +// Boost analyses the outer ring afresh on every call (a reservoir with 2500 +// islands and a 600,000-point shore takes 47 s); here its segments are indexed +// once. An inner ring that shares no point with the outer ring lies wholly +// inside or wholly outside it, so one of its vertices decides; when the rings +// touch or cross, Boost decides as before. +class RingWithinIndex { + using Segment = bg::model::segment; + using Value = std::pair; + const Ring &outer; + bg::index::rtree> segments; + + static std::vector segmentBoxes(const Ring &ring) { + std::vector values; + for (std::size_t i = 0; i + 1 < ring.size(); i++) + values.emplace_back(bg::return_envelope(Segment(ring[i], ring[i + 1])), i); + return values; + } + +public: + RingWithinIndex(const Ring &outer) : outer(outer), segments(segmentBoxes(outer)) { } + + bool within(const Ring &inner) const { + if (inner.size() < 4) return geom::within(inner, outer); + for (std::size_t j = 0; j + 1 < inner.size(); j++) { + const Segment s(inner[j], inner[j + 1]); + for (auto it = segments.qbegin(bg::index::intersects(bg::return_envelope(s))); it != segments.qend(); ++it) { + const std::size_t i = it->second; + if (bg::intersects(s, Segment(outer[i], outer[i + 1]))) + return geom::within(inner, outer); + } + } + return geom::within(inner.front(), outer); + } +}; + UsedObjects::UsedObjects(Status status): status(status), mutex(256), ids(256 * 1024) { } @@ -76,17 +113,31 @@ MultiPolygon OSMStore::wayListMultiPolygon(WayVec::const_iterator outerBegin, Wa // add all inners and outers to the multipolygon std::vector filledInners; + std::vector innerBoxes; for (auto it = inners.begin(); it != inners.end(); ++it) { Ring inner; fillPoints(inner, it->begin(), it->end()); + innerBoxes.emplace_back(geom::return_envelope(inner)); filledInners.emplace_back(inner); } bool onlyOneOuter = outers.size()==1; for (auto ot = outers.begin(); ot != outers.end(); ot++) { Polygon poly; fillPoints(poly.outer(), ot->begin(), ot->end()); - for (auto it = filledInners.begin(); it != filledInners.end(); ++it) { - if (onlyOneOuter || geom::within(*it, poly.outer())) { poly.inners().emplace_back(*it); } + const Box outerBox = geom::return_envelope(poly.outer()); + // Index big outer rings that many inner rings must be tested against + std::unique_ptr index; + if (!onlyOneOuter && filledInners.size() >= 16 && poly.outer().size() >= 4096) + index.reset(new RingWithinIndex(poly.outer())); + for (std::size_t i = 0; i < filledInners.size(); i++) { + const Ring &inner = filledInners[i]; + // A ring within the outer has its bounding box within the outer's, + // so the (costly) within test is only needed when it does + if (onlyOneOuter || + ((inner.empty() || geom::covered_by(innerBoxes[i], outerBox)) && + (index ? index->within(inner) : geom::within(inner, poly.outer())))) { + poly.inners().emplace_back(inner); + } } mp.emplace_back(move(poly)); }