Skip to content

Detect closed multipolygon ways#896

Open
Symmetricity wants to merge 1 commit into
systemed:masterfrom
Symmetricity:fix/closed-multipolygon-ways
Open

Detect closed multipolygon ways#896
Symmetricity wants to merge 1 commit into
systemed:masterfrom
Symmetricity:fix/closed-multipolygon-ways

Conversation

@Symmetricity
Copy link
Copy Markdown

This PR is AI generated.

Summary

This fixes closed-way detection when assembling multipolygon relation members.

The existing helper compared way.begin() with way.end(). For a normal
non-empty closed ring those iterators are never equal, so the helper only
reported empty vectors as closed. Closed relation member ways therefore went
through the endpoint-joining path intended for open fragments.

The patch changes the helper to check the first and last coordinates:

return !way.empty() && way.front() == way.back();

Background

OSMStore::mergeMultiPolygonWays() documents the intended behavior:

  • closed polygons are added as-is
  • open linestring fragments are joined by matching start/end nodes

The old predicate did not implement that documented behavior. A closed way has
front() == back(), not begin() == end().

That means already-closed relation member ways could be mixed into the open-way
assembly path. That path is useful for fragmented multipolygon members, but it
uses start/end maps and iterative joining. Closed rings do not need that work,
and treating them as open fragments can change the order and shape of the
assembled result before correction, simplification, and clipping.

The incorrect predicate was introduced with the parallel PBF loading work in
#243. Later node-store refactoring in #590 changed the helper's vector type, but
kept the same iterator comparison.

Implementation

The implementation is intentionally limited to the helper predicate in
src/osm_store.cpp.

It keeps the existing behavior for empty vectors by returning false, and it
does not change the multipolygon assembly algorithm for actual open fragments.

Evidence

I retested this locally against the submitted-PR stack using the Liechtenstein
Geofabrik fixture and the OpenMapTiles profile.

Within each variant, repeated runs were stable:

  • submitted stack: byte-identical repeat output
  • submitted stack plus this fix: byte-identical repeat output

Comparing submitted stack against submitted stack plus this fix:

  • 85 raw tile differences
  • 4 decoded semantic tile differences
  • no feature-count differences
  • changed layers were water and landcover
  • changes were geometry-only

The affected semantic tiles were:

  • z8/134/89 XYZ, water, class=lake
  • z9/269/179 XYZ, water, class=lake
  • z11/1078/718 XYZ, landcover, grass/meadow geometry
  • z12/2156/1437 XYZ, landcover, grass/meadow geometry

The z12 changed feature was identified with include_ids as:

Visual checks against OpenStreetMap reference rendering showed negligible
visual impact. The main reason for this patch is correcting the predicate so
closed relation-member rings follow the code path that
mergeMultiPolygonWays() already says they should follow.

Performance

A larger local Austria timing check was run for performance only, without
semantic verification.

Configuration:

  • input: Austria Geofabrik extract, about 763 MiB
  • profile: resources/config-openmaptiles.json
  • process: resources/process-openmaptiles.lua
  • output: PMTiles
  • default threading
  • warmed source PBF for both measured runs

Measured results:

Build Wall time User time System time CPU Max RSS
submitted stack 59.36s 312.05s 53.03s 614% 3,407,380 KB
submitted stack plus this fix 59.20s 322.40s 46.88s 623% 3,341,112 KB

This is effectively performance-neutral on that fixture. The small RSS
difference is within the range I would treat as noise from one local run, so I
would not claim a memory improvement from this patch.

Related Issues And PRs

I did not find an existing open pull request for this exact predicate fix.

Possible Regressions

This can change multipolygon geometry for relations that contain already-closed
member ways, because those rings are now kept as closed members instead of being
handled by the open-fragment endpoint assembly path.

That is the intended behavior change and matches the local comment above
mergeMultiPolygonWays(). Actual open fragments still use the existing joining
logic.

The local fixture comparison found only geometry changes, no feature-count
changes. Performance impact should be neutral or negligible because the change
removes unnecessary open-fragment assembly work for closed rings.

Testing

Local checks used for the draft:

git diff --check origin/master..fix/closed-multipolygon-ways

Fixture comparison:

tilemaker \
  --input liechtenstein.osm.pbf \
  --output liechtenstein.mbtiles \
  --config resources/config-openmaptiles.json \
  --process resources/process-openmaptiles.lua

The fixture was generated for the submitted-PR stack and the submitted-PR stack
plus this patch, then compared by decoded MVT content.

The previous test compared begin() and end(), so it only treated empty way vectors as closed. Closed relation member rings then fell through the endpoint assembly path used for open fragments.

That path is keyed by unordered endpoint containers, which can assemble otherwise complete rings in platform-dependent order before geometry correction and simplification. Checking the first and last coordinate keeps already-closed members in relation order and avoids unnecessary assembly work.
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