Skip to content

perf: much faster combine_polygons_below (#868) - #945

Merged
systemed merged 1 commit into
systemed:masterfrom
dktue:perf/combine-polygons
Oct 31, 2026
Merged

systemed merged 1 commit into
systemed:masterfrom
dktue:perf/combine-polygons

Conversation

@dktue

@dktue dktue commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Makes combine_polygons_below much cheaper (#868).

Cause
union_many grouped multipolygons by bbox overlap and ran a binary reduction of union_() within each group. Adjacent landuse parcels (farmland, forest, ...) share edges, so at low zooms a tile holds one group of ~20,000 polygons. In the last rounds of the reduction both sides are huge, and Boost's union_() tests every ring without intersections against all vertices of the other side (select_rings -> range_in_geometry, no bbox prefilter in Boost 1.83). That is quadratic: on Luxembourg with the OpenTopoMap config, 30% of all CPU time was in point_in_range, and two single union_many calls took ~10 s each.

Change (union_many in src/geom.cpp only)

  • Split the inputs into single polygons and sort them along a Z-order curve, so each reduction step unions two neighbouring regions.
  • At each step, pass only polygons whose bbox intersects a bbox on the other side to union_(); everything else can't overlap and is copied through.

Results
OpenTopoMap config with combine_polygons_below: 13 on its landuse layer (land_low), as in #868:

before after
Replay of all 2544 union_many calls from Luxembourg 45.3 s 11.4 s
Slowest single call (19,520 polygons, 498k vertices) 9.9 s 1.2 s
Luxembourg, whole run 14.5 s wall, 99 s CPU, 650 MB 5.8 s wall, 60 s CPU, 443 MB

Germany with --store --shard-stores (the setup from #868, on top of #943): 571 s without combine_polygons_below, 694 s with it. #868 reported ~10 min without and >2 h with.

Output
Boost's union is not exactly order-independent, so results differ slightly from before, within the variation master already has:

  • Union results (replayed inputs): master vs this PR differ in 42 of 2544 calls, by at most 1.0% of the area (symmetric difference). Master vs master with the inputs in reverse order: 34 calls, up to 1.5%.
  • Validity: master produced 1 invalid multipolygon (intersecting interiors), this PR none.
  • Final Luxembourg tiles: only land polygon areas differ. Master vs master with reversed union order: 165 (tile, attributes) groups off by >0.1%, worst 6.3%; master vs this PR: 191, worst 6.0%. The larger differences come from simplification of rings that start at a different vertex: with simplification off on that layer, master vs this PR differ in a single group (0.76%). Points, lines and all other layers are identical, and so is output without combine_polygons_below.

🤖 Generated with Claude Code

…e seam, Z-order sorted)

Fixes systemed#868

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@systemed
systemed merged commit 5b9c61f into systemed:master Oct 31, 2026
10 of 11 checks passed
@systemed

Copy link
Copy Markdown
Owner

Thank you! With this and #949 together, Europe (RAM-resident) on my machine falls from 58m06 to 48m30. I've eyeballed the usual failure points and can't see any regressions.

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.

2 participants