Skip to content

Add the OverlayNG engine core (phase 2): graph, labeller, and result builders#443

Draft
asinghvi17 wants to merge 3 commits into
overlayngfrom
overlayng-engine
Draft

Add the OverlayNG engine core (phase 2): graph, labeller, and result builders#443
asinghvi17 wants to merge 3 commits into
overlayngfrom
overlayng-engine

Conversation

@asinghvi17

Copy link
Copy Markdown
Member

Phase 2 of the OverlayNG port: the engine core — half-edge graph, labeller, and result builders, consuming phase 1's exact NodedArrangement (#442) and producing overlay results through an internal driver. Stacked on #442 (→ #416#434); merge in stack order. Design doc: docs/plans/2026-07-16-overlayng-noding-substrate.md §3 (on the base branch).

What's here

Graph layer (half_edge.jl, edge_source.jl, overlay_label.jl, overlay_graph.jl, ~750 SLOC):

  • Integer-indexed winged-edge substrate (port of edgegraph.HalfEdge): stores o_next directly, bulk-builds and sorts each node star once through the exact rk_compare_edge_dir with NodeKey apexes and parent-endpoint direction points — no constructed coordinate enters angular ordering, including at coincidence-merged nodes (foreign directions take the kernel's exact-rational slow path).
  • OverlayLabel ported faithfully (plain mutable struct, four states, one shared label per sym pair with the forward/reverse L/R swap).
  • The merger keys coincident edges on unordered node-id pairs — JTS's float-hashed EdgeKey is not ported (the segment/minor-arc between two nodes is unique; relativeDirection becomes an exact integer comparison). Depth deltas derive once per ring from _ring_material_interior_on_left (the material-interior authority landed in Add the OverlayNG noding substrate (phase 1): exact noded arrangements with symbolic nodes #442), so labels are winding-independent by construction and Spherical(; oriented) semantics flow through with no extra plumbing.

Engine core (overlay_labeller.jl, maximal_edge_ring.jl, polygon_builder.jl, line_builder.jl, intersection_point_builder.jl, overlay_ng.jl, ~1,050 SLOC):

  • The five OverlayLabeller passes in JTS order (area propagation with side-conflict errors, two linear BFS passes, collapse labelling, disconnected-edge PIP against the original inputs via the indexed locators — both endpoints, never collapsed edges).
  • Maximal→minimal ring linking (with JTS's deliberately-ungated per-edge linking preserved), hole/shell classification via the shared orientation authority, and hole nesting decided by kernel-point containment (rk_point_in_ring / indexed locators) — never planar even-odd on emitted coordinates, so the builders are manifold-correct.
  • LineBuilder (raw noded-edge extraction; JTS's dead merged-line path not ported), IntersectionPointBuilder, and the internal _overlay_ng(m, op, a, b) driver for intersection/union/difference/symmetric difference on both manifolds, including spherical empty-vs-full disambiguation (boundaryless results resolved by locating one input vertex; a full-sphere result throws — unrepresentable under enclosed-region semantics, a documented phase-3 refinement).

Nothing is exported and no existing behavior changes — the public opt-in API, point inputs, and the differential-validation harness are phase 3.

Validation

  • 24/24 applicable JTS OverlayNGTest cases pass (GEOS topological equals + isValid on every result): nested shells, adjacent boxes, touching holes, box/line and polygon/line ops, line/line unions incl. figure-8 and rings, flat-collapse intersection. Skips are principled: the fixed-precision family (snapping/collapse tests) does not apply to a floating-only exact engine by design, and testTouchingPolyDifference is a known substrate limitation (the noder does not self-node a single input, so a hole touching its own shell yields a correct-area but non-simple split — tracked for phase 3).
  • The spike-derived case suite vs LibGEOS across all four ops (analytic-area squares with vertex-set equality, the §2.7 hole-regression case, collinear shared boundaries, a degree-6 coincident crossing, concave and multi-polygon cases), ring-builder specifics (self-touching minimal-ring split, free-hole assignment, a 20-island union checked GEOS-equal and valid — the exact failure class the design spikes identified), and spherical end-to-end gates: area conservation area(A∪B)+area(A∩B) = area(A)+area(B) at rtol 1e-12, difference/intersection reconstruction, symdifference identity, and Natural Earth shifted-self conservation on both manifolds.
  • Graph-layer suite: 36,668 assertions incl. NE real data on both manifolds; phases 1 and 2a remain green.
  • Perf snapshot (France 110m shifted-self, warm): planar _overlay_ng 0.19 ms intersection/union vs LibGEOS 0.09/0.08 ms — ~2× GEOS's C for pure Julia with exact predicates; spherical 6–11 ms (exact spherical kernel, as expected).

🤖 Generated with Claude Code

asinghvi17 and others added 3 commits July 16, 2026 17:03
Build the half-edge topology graph over phase 1's `NodedArrangement`:

- `overlay_label.jl` — faithful `OverlayLabel` port (mutable plain struct, the
  four edge states, the `is_forward` Left/Right swap on one shared label).
- `edge_source.jl` — `EdgeSourceInfo` per ring/line; `depth_delta` derived once
  from `_ring_material_interior_on_left` (`material_interior_on_left ? -1 : +1`).
- `half_edge.jl` — integer-indexed winged-edge substrate (port of
  `edgegraph.HalfEdge`); bulk-build then sort each node's star once with the
  exact `rk_compare_edge_dir` comparator.
- `overlay_graph.jl` — `MergeEdge` + `Edge.merge` semantics (depth-delta summing
  with direction flip, collapse detection, hole-role merge), the `EdgeMerger`
  keyed on the unordered node-id pair, and `OverlayEdge`/`OverlayGraph`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cover the phase-2a engine core: degree-6 coincidence-merged node ordering
(foreign-direction slow path), collinear shared boundary merging to one
`OverlayEdge` with correct per-side locations, spherical crossing-node star
ordering, the depth-delta / `Edge.merge` algebra (sum, collapse, hole role),
the four label states and the `getLocation` `is_forward` swap, and graph
invariants on Natural Earth country pairs (both manifolds).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the labeller, result builders, and the internal driver on top of the
phase-2a graph, completing the OverlayNG engine core.

- `overlay_labeller.jl`: `_compute_labelling!` (the five JTS passes in order),
  `_is_result_of_op` (SYMDIFFERENCE handled natively), result-area marking and
  duplicate unmarking.
- `maximal_edge_ring.jl` + `polygon_builder.jl`: maximal/minimal edge rings with
  the self-touching minimal-ring split, `linkResultAreaMaxRingAtNode` called
  ungated (JTS-faithful), and shell/hole nesting via kernel-point containment
  (`rk_point_in_ring` / indexed locators), never planar even-odd.
- `line_builder.jl`, `intersection_point_builder.jl`: result lines and the
  intersection-only result points.
- `overlay_ng.jl`: the driver `_overlay_ng` (input model, `computeEdgeOverlay`
  phase order, `extractResult` dimensional priority, empty short-circuits) with
  spherical empty-vs-full disambiguation.

All internal (no exports, plain-backtick docstrings); the existing
`intersection`/`union`/`difference` are untouched. Line and area inputs are
supported in any A x B combination; point inputs raise a clear error (phase 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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