Skip to content

Add simply connected interior validation and test suite#1472

Open
bretttully wants to merge 10 commits intogeorust:mainfrom
bretttully:fix-simply-connected-interior-validation
Open

Add simply connected interior validation and test suite#1472
bretttully wants to merge 10 commits intogeorust:mainfrom
bretttully:fix-simply-connected-interior-validation

Conversation

@bretttully
Copy link

@bretttully bretttully commented Dec 9, 2025

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

Add validation to detect when a polygon's interior is not simply connected, which violates the OGC Simple Feature Specification requirement that "the interior of every Surface is a connected point set."

What This Detects

The validation detects two types of interior disconnection:

  1. Multi-touch: Two rings sharing 2+ touch points at different coordinates
  2. Cycle: Rings forming a cycle through distinct single-touch coordinates

Algorithm

Uses the GeometryGraph edge intersection data to find where rings touch:

  1. Collect intersections: For each edge intersection, record the coordinate, which ring (edge) it belongs to, and whether it's a vertex of that ring
  2. Sort and group: Sort intersections by (x, y, edge_idx) using total_cmp for consistent ordering, then group by coordinate
  3. Build touch map: For coordinates where 2+ rings meet (and at least one has it as a vertex), record the touch for each ring pair
  4. Detect disconnection:
    • If any ring pair has 2+ touch coordinates → disconnected
    • Build a graph of single-touch ring pairs and DFS for cycles through distinct coordinates

A "touch" requires at least one ring to have the point as an actual vertex (not a mid-segment crossing).

Key Insight

Multiple rings meeting at a single coordinate does NOT disconnect the interior. For example, three triangular holes meeting at one point still have a connected interior around their perimeter. Disconnection only occurs when touches happen at distinct coordinates.

Error Reporting

The error message includes the problematic coordinates to aid debugging:

polygon interior is not simply connected at coordinate(s): (2, 2), (3, 3)

Performance

Uses PreparedGeometry to cache R-tree structures for interior/exterior containment checks. The sorting-based coordinate grouping provides better cache locality than hash-based approaches.

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! Thank you for the pull request. This would be a great feature to incorporate, but I've got some questions to work through.

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the slow turn around! I'd like to have this behavior, but I have some questions about the implementation. I haven't done a full review, but wanted to check in before dedicating more time to the review.

I think directionally it makes sense. Can I ask, did you use some kind of reference implementation for the graph algorithm, or did it spring mostly from you and the AI?

@michaelkirk
Copy link
Member

Hi @bretttully - are you able to respond to any of my feedback? As I said, I think this is useful functionality, but I'm not comfortable merging it without understanding more how it works.

@bretttully
Copy link
Author

@michaelkirk I haven't had a chance to carve out time just yet. I am keen to get the functionality in, so will have something together next week. Sorry for the delay!

bretttully and others added 3 commits February 12, 2026 11:20
…extraction

Bumps i_overlay from 4.0 to 4.4 and enables `OverlayOptions::ogc()` for all
boolean operations. This ensures output polygons have connected interiors per
ISO 19125-1, fixing cases where holes sharing vertices produced invalid geometry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bretttully
Copy link
Author

@michaelkirk I am just picking this up again. I have started by getting in the OGC changes for iOverlay here: #1500

I'm going to rebase this branch off that one, and possibly split this PR into smaller ones to make review easier.

@bretttully bretttully force-pushed the fix-simply-connected-interior-validation branch from bb29608 to 118d5bb Compare February 12, 2026 02:20
Cache R-tree structures via PreparedGeometry when validating polygon
ring relationships. This avoids rebuilding the geometry graph for each
interior containment check against the exterior, and for each
interior-interior intersection check.

Also adds an early return when no non-empty interiors exist, and skips
empty interior rings in pairwise comparisons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bretttully bretttully force-pushed the fix-simply-connected-interior-validation branch from 118d5bb to 333d345 Compare February 12, 2026 03:47
The prepared_interior_1 construction was moved upward so the
exterior_vs_interior relate compares Polygon-vs-Polygon instead of
Polygon-vs-LineString. This changes the DE-9IM semantics: a Polygon's
boundary is the ring curve (not its interior), so the line-intersection
check must use (OnBoundary, OnBoundary) instead of (OnBoundary, Inside).

Also applies cargo fmt to bool_ops files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bretttully bretttully force-pushed the fix-simply-connected-interior-validation branch from 75a5028 to d92b4f4 Compare February 12, 2026 10:03
bretttully and others added 5 commits February 12, 2026 21:11
With PreparedGeometry wrapping interior rings as Polygons, an interior
ring identical to the exterior now passes is_contains() (a polygon
contains itself), so only IntersectingRingsOnALine is reported — not
InteriorRingNotContainedInExteriorRing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect polygons with disconnected interiors per OGC Simple Features
(ISO 19125-1, section 6.1.11.1): "The interior of every Surface is a
connected point set."

The new InteriorNotSimplyConnected(RingRole, RingRole) error fires when:
- Two rings share 2+ distinct touch coordinates (creating a corridor)
- Rings form a cycle of single-vertex touches through distinct
  coordinates (enclosing interior area)

Ring pairs already reported as IntersectingRingsOnALine or
IntersectingRingsOnAnArea are skipped to avoid duplicate errors.

Includes:
- Checkerboard polygon fixture for stress-testing cycle detection
- 9 new tests covering valid and invalid cases
- Validation benchmarks (simple polygons, checkerboards, real fixtures)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use Vec<Vec<usize>> instead of Vec<HashSet<usize>> for adjacency
  (only iterated, never queried)
- Store edge_coords in one direction with canonical (min, max) keys
- Flatten nested if-let chains into tuple-matching patterns
- Replace unreachable if-let with .expect() for invariant
- Hoist unique_edges allocation outside grouping loop
- Minor: hoist coords() call, use |= operator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simplifies check_ring_touches_disconnect_interior by replacing the
45-line DFS with entry_coord tracking with a dual Union-Find approach.
Also moves Case 1 (pair with 2+ touches) into the caller as an early
exit using a HashSet, and builds coord_edges directly instead of going
through an intermediate HashMap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bretttully bretttully force-pushed the fix-simply-connected-interior-validation branch from d92b4f4 to 9400e3a Compare February 12, 2026 11:18
@bretttully
Copy link
Author

Hi @michaelkirk

Can I ask, did you use some kind of reference implementation for the graph algorithm, or did it spring mostly from you and the AI?

This is has been a mostly iterative process with Claude; I have included the iOverlay repo in context and have found that referencing its implementation has guided the solution here.

I have been using Claude to validate test cases against shapely as I go, but have shied away from using GEOS/JTS in context given licensing of those libraries. I have also been getting it to generate explanatory images such as this for the union find implementation that I think is easier to understand than what was there previously.

image

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

Comments