Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased

- Improves robustness of set operations (`Union`, `Intersection`, `Difference`,
`SymmetricDifference`) by revamping ghost line construction. The previous
spanning tree approach could produce ghost lines that interfered with the
geometry in problematic ways. The new ray casting approach is more robust.

## v0.56.0

2025-11-21
Expand Down
100 changes: 54 additions & 46 deletions geom/alg_set_op_test.go

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion geom/dcel.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
package geom

func newDCELFromGeometries(a, b Geometry) *doublyConnectedEdgeList {
a, b, ghosts := prepareGeometriesForDCEL(a, b)
return newDCELFromRenodedGeometries(a, b, ghosts)
}

// prepareGeometriesForDCEL pre-processes the input geometries (A and B) such
// that they can be used to create a DCEL. An additional "ghost"
// MultiLineString is also returned, which provides the appropriate connections
// such that A and B (when combined together) are fully connected.
func prepareGeometriesForDCEL(a, b Geometry) (Geometry, Geometry, MultiLineString) {
// Renode just A and B. Them being noded correctly is a pre-requisite for
// creating the ghosts.
a, b, _ = reNodeGeometries(a, b, MultiLineString{})

ghosts := createGhosts(a, b)
a, b, ghosts = reNodeGeometries(a, b, ghosts)

if ghosts.IsEmpty() {
return a, b, ghosts
}

// Renode again, since, the ghosts may have introduced new intersections
// with A and/or B.
return reNodeGeometries(a, b, ghosts)
}

func newDCELFromRenodedGeometries(a, b Geometry, ghosts MultiLineString) *doublyConnectedEdgeList {
interactions := findInteractionPoints([]Geometry{a, b, ghosts.AsGeometry()})

dcel := newDCEL()
Expand Down
Loading