Skip to content

perf: index big outer rings when assigning inner rings of multipolygons - #975

Open
dktue wants to merge 1 commit into
systemed:masterfrom
dktue:perf/ring-within
Open

dktue wants to merge 1 commit into
systemed:masterfrom
dktue:perf/ring-within

Conversation

@dktue

@dktue dktue commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Gain (planet, PMTiles, 32 threads), measured together with the correct.hpp ring-closing PR #974: relations phase 105 → 61 s (−42%), total wall 1659 → 1620 s (−39 s, −2.4%). Guri Reservoir alone 48 → 9 s (−81%); time at 1–3 busy threads ~46 → ~15 s (−67%). Output identical.

OSMStore::wayListMultiPolygon assigns each inner ring of a multipolygon to the outer rings that contain it with geom::within(inner, outer). Boost analyses both rings afresh on every call, so a relation with one huge outer ring and many inner rings pays for the outer ring once per inner ring. On the planet, Guri Reservoir (r3400721: a 627,000-point shore, 2,525 islands) spends 47 s there, on one thread at the end of the relations phase.

This PR changes two things, and keeps Boost's answer in every case where the rings touch.

  • A ring within another has its bounding box within the other's, so pairs whose boxes aren't nested skip within.
  • When at least 16 inner rings are tested against an outer ring of at least 4,096 points, the outer ring's segments go into an R-tree once. For each inner ring, only the outer segments near its own segments are tested for intersection. If none intersects, the rings share no point, so the inner ring lies wholly inside or wholly outside, and one of its vertices decides (within(point, ring)). If any intersects, geom::within(inner, outer) decides as before.

Verification

A debug build ran both the indexed test and geom::within(inner, outer) for every indexed call on the whole planet: 273,813 calls, 0 disagreements. Germany output (compare_area, per-tile area/length per layer and tag group) is identical; only poi differs, as between identical runs.

Planet, PMTiles, 32 threads, --store --fast, together with the correct.hpp ring-closing fix

before after
relations phase 105–107 s 61 s (−42%)
of which at 1–3 busy threads ~46 s ~15 s (−67%)
Guri Reservoir r3400721 48 s 9 s (−81%)
wall 1659 s 1620 s (−2.4%)

The relations phase used to end with one thread finishing the few huge relations while the others waited; now it ends with Lake Huron (r1205151, 25 s). The tile phase, which this doesn't touch, took about the same in both runs (1038 s and 1048 s).

🤖 Generated with Claude Code

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant