Skip to content

Commit

Permalink
Merge 45ccc4b into c9889c2
Browse files Browse the repository at this point in the history
  • Loading branch information
peterstace committed Nov 8, 2022
2 parents c9889c2 + 45ccc4b commit 37c01ca
Show file tree
Hide file tree
Showing 7 changed files with 815 additions and 782 deletions.
15 changes: 10 additions & 5 deletions geom/alg_set_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Union(a, b Geometry) (Geometry, error) {
if b.IsEmpty() {
return a, nil
}
g, err := setOp(a, b, selectUnion)
g, err := setOp(a, or, b)
return g, wrap(err, "executing union")
}

Expand All @@ -24,7 +24,7 @@ func Intersection(a, b Geometry) (Geometry, error) {
if a.IsEmpty() || b.IsEmpty() {
return Geometry{}, nil
}
g, err := setOp(a, b, selectIntersection)
g, err := setOp(a, and, b)
return g, wrap(err, "executing intersection")
}

Expand All @@ -38,7 +38,7 @@ func Difference(a, b Geometry) (Geometry, error) {
if b.IsEmpty() {
return a, nil
}
g, err := setOp(a, b, selectDifference)
g, err := setOp(a, andNot, b)
return g, wrap(err, "executing difference")
}

Expand All @@ -55,11 +55,11 @@ func SymmetricDifference(a, b Geometry) (Geometry, error) {
if b.IsEmpty() {
return a, nil
}
g, err := setOp(a, b, selectSymmetricDifference)
g, err := setOp(a, xor, b)
return g, wrap(err, "executing symmetric difference")
}

func setOp(a, b Geometry, include func([2]label) bool) (Geometry, error) {
func setOp(a Geometry, include func([2]bool) bool, b Geometry) (Geometry, error) {
overlay, err := createOverlay(a, b)
if err != nil {
return Geometry{}, wrap(err, "internal error creating overlay")
Expand All @@ -71,3 +71,8 @@ func setOp(a, b Geometry, include func([2]label) bool) (Geometry, error) {
}
return g, nil
}

func or(b [2]bool) bool { return b[0] || b[1] }
func and(b [2]bool) bool { return b[0] && b[1] }
func xor(b [2]bool) bool { return b[0] != b[1] }
func andNot(b [2]bool) bool { return b[0] && !b[1] }
144 changes: 79 additions & 65 deletions geom/dcel.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ type doublyConnectedEdgeList struct {
}

type faceRecord struct {
cycle *halfEdgeRecord
labels [2]label
cycle *halfEdgeRecord

// inSet encodes whether this face is part of the input geometry for each
// operand.
inSet [2]bool

extracted bool
}

Expand All @@ -29,9 +33,20 @@ type halfEdgeRecord struct {
incident *faceRecord // only populated in the overlay
next, prev *halfEdgeRecord
seq Sequence
edgeLabels [2]label
faceLabels [2]label
extracted bool

// srcEdge encodes whether or not this edge is explicitly appears as part
// of the input geometries.
srcEdge [2]bool

// srcFace encodes whether or not this edge explicitly borders onto a face
// in the input geometries.
srcFace [2]bool

// inSet encodes whether or not this edge is (explicitly or implicitly)
// part of the input geometry for each operand.
inSet [2]bool

extracted bool
}

// String shows the origin and destination of the edge (for debugging
Expand All @@ -46,7 +61,15 @@ func (e *halfEdgeRecord) String() string {
type vertexRecord struct {
coords XY
incidents []*halfEdgeRecord
labels [2]label

// src encodes whether on not this vertex explicitly appears in the input
// geometries.
src [2]bool

// inSet encodes whether or not this vertex is part of each input geometry
// (although it might not be explicitly encoded there).
inSet [2]bool

locations [2]location
extracted bool
}
Expand Down Expand Up @@ -123,9 +146,9 @@ func (d *doublyConnectedEdgeList) addMultiPolygon(mp MultiPolygon, operand opera
vr := &vertexRecord{
coords: xy,
incidents: nil, // populated later
labels: newHalfPopulatedLabels(operand, true),
locations: newLocationsOnBoundary(operand),
}
vr.src[operand] = true
d.vertices[xy] = vr
}
}
Expand All @@ -139,25 +162,24 @@ func (d *doublyConnectedEdgeList) addMultiPolygon(mp MultiPolygon, operand opera
vertA := d.vertices[segment.GetXY(0)]
vertB := d.vertices[reverseSegment.GetXY(0)]
internalEdge := &halfEdgeRecord{
origin: vertA,
twin: nil, // populated later
incident: nil, // only populated in the overlay
next: nil, // populated later
prev: nil, // populated later
seq: segment,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newHalfPopulatedLabels(operand, true),
origin: vertA,
twin: nil, // populated later
incident: nil, // only populated in the overlay
next: nil, // populated later
prev: nil, // populated later
seq: segment,
}
externalEdge := &halfEdgeRecord{
origin: vertB,
twin: internalEdge,
incident: nil, // only populated in the overlay
next: nil, // populated later
prev: nil, // populated later
seq: reverseSegment,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newHalfPopulatedLabels(operand, false),
origin: vertB,
twin: internalEdge,
incident: nil, // only populated in the overlay
next: nil, // populated later
prev: nil, // populated later
seq: reverseSegment,
}
internalEdge.srcEdge[operand] = true
internalEdge.srcFace[operand] = true
externalEdge.srcEdge[operand] = true
internalEdge.twin = externalEdge
vertA.incidents = append(vertA.incidents, internalEdge)
vertB.incidents = append(vertB.incidents, externalEdge)
Expand Down Expand Up @@ -197,13 +219,12 @@ func (d *doublyConnectedEdgeList) addMultiLineString(mls MultiLineString, operan
} else {
locs[operand].interior = true
}
d.vertices[xy] = &vertexRecord{
xy,
nil, // populated later
newHalfPopulatedLabels(operand, true),
locs,
false,
vr := &vertexRecord{
coords: xy,
locations: locs,
}
vr.src[operand] = true
d.vertices[xy] = vr
} else {
if onBoundary {
if v.locations[operand].boundary {
Expand Down Expand Up @@ -243,25 +264,23 @@ func (d *doublyConnectedEdgeList) addMultiLineString(mls MultiLineString, operan
vDestin := d.vertices[endXY]

fwd := &halfEdgeRecord{
origin: vOrigin,
twin: nil, // set later
incident: nil, // only populated in overlay
next: nil, // set later
prev: nil, // set later
seq: segment,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newUnpopulatedLabels(),
origin: vOrigin,
twin: nil, // set later
incident: nil, // only populated in overlay
next: nil, // set later
prev: nil, // set later
seq: segment,
}
rev := &halfEdgeRecord{
origin: vDestin,
twin: fwd,
incident: nil, // only populated in overlay
next: fwd,
prev: fwd,
seq: reverseSegment,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newUnpopulatedLabels(),
origin: vDestin,
twin: fwd,
incident: nil, // only populated in overlay
next: fwd,
prev: fwd,
seq: reverseSegment,
}
fwd.srcEdge[operand] = true
rev.srcEdge[operand] = true
fwd.twin = rev
fwd.next = rev
fwd.prev = rev
Expand All @@ -286,12 +305,11 @@ func (d *doublyConnectedEdgeList) addMultiPoint(mp MultiPoint, operand operand)
record = &vertexRecord{
coords: xy,
incidents: nil,
labels: [2]label{}, // set below
locations: [2]location{}, // set below
}
d.vertices[xy] = record
}
record.labels[operand] = label{populated: true, inSet: true}
record.src[operand] = true
record.locations[operand].interior = true
}
}
Expand All @@ -317,10 +335,10 @@ func (d *doublyConnectedEdgeList) addGhosts(mls MultiLineString, operand operand
endXY := reverseSegment.GetXY(0)

if _, ok := d.vertices[startXY]; !ok {
d.vertices[startXY] = &vertexRecord{coords: startXY, incidents: nil, labels: [2]label{}}
d.vertices[startXY] = &vertexRecord{coords: startXY}
}
if _, ok := d.vertices[endXY]; !ok {
d.vertices[endXY] = &vertexRecord{coords: endXY, incidents: nil, labels: [2]label{}}
d.vertices[endXY] = &vertexRecord{coords: endXY}
}

if edges.containsStartIntermediateEnd(segment) {
Expand All @@ -340,24 +358,20 @@ func (d *doublyConnectedEdgeList) addGhostLine(segment, reverseSegment Sequence,
vertB := d.vertices[reverseSegment.GetXY(0)]

e1 := &halfEdgeRecord{
origin: vertA,
twin: nil, // populated later
incident: nil, // only populated in the overlay
next: nil, // popluated later
prev: nil, // populated later
seq: segment,
edgeLabels: newHalfPopulatedLabels(operand, false),
faceLabels: [2]label{},
origin: vertA,
twin: nil, // populated later
incident: nil, // only populated in the overlay
next: nil, // popluated later
prev: nil, // populated later
seq: segment,
}
e2 := &halfEdgeRecord{
origin: vertB,
twin: e1,
incident: nil, // only populated in the overlay
next: e1,
prev: e1,
seq: reverseSegment,
edgeLabels: newHalfPopulatedLabels(operand, false),
faceLabels: [2]label{},
origin: vertB,
twin: e1,
incident: nil, // only populated in the overlay
next: e1,
prev: e1,
seq: reverseSegment,
}
e1.twin = e2
e1.next = e2
Expand Down
26 changes: 13 additions & 13 deletions geom/dcel_extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package geom
import "fmt"

// extractGeometry converts the DECL into a Geometry that represents it.
func (d *doublyConnectedEdgeList) extractGeometry(include func([2]label) bool) (Geometry, error) {
func (d *doublyConnectedEdgeList) extractGeometry(include func([2]bool) bool) (Geometry, error) {
areals, err := d.extractPolygons(include)
if err != nil {
return Geometry{}, err
Expand Down Expand Up @@ -52,12 +52,12 @@ func (d *doublyConnectedEdgeList) extractGeometry(include func([2]label) bool) (
}
}

func (d *doublyConnectedEdgeList) extractPolygons(include func([2]label) bool) ([]Polygon, error) {
func (d *doublyConnectedEdgeList) extractPolygons(include func([2]bool) bool) ([]Polygon, error) {
var polys []Polygon
for _, face := range d.faces {
// Skip any faces not selected to be include in the output geometry, or
// any faces already extracted.
if !include(face.labels) || face.extracted {
if !include(face.inSet) || face.extracted {
continue
}

Expand All @@ -81,7 +81,7 @@ func (d *doublyConnectedEdgeList) extractPolygons(include func([2]label) bool) (
if seen[edge] {
return
}
if include(edge.twin.incident.labels) {
if include(edge.twin.incident.inSet) {
// Adjacent face is in the polygon, so this edge cannot be part
// of the boundary.
seen[edge] = true
Expand Down Expand Up @@ -135,7 +135,7 @@ func extractPolygonBoundary(faceSet map[*faceRecord]bool, start *halfEdgeRecord,

// findFacesMakingPolygon finds all faces that belong to the polygon that
// contains the start face (according to the given inclusion criteria).
func findFacesMakingPolygon(include func([2]label) bool, start *faceRecord) map[*faceRecord]bool {
func findFacesMakingPolygon(include func([2]bool) bool, start *faceRecord) map[*faceRecord]bool {
expanded := make(map[*faceRecord]bool)
toExpand := make(map[*faceRecord]bool)
toExpand[start] = true
Expand All @@ -151,7 +151,7 @@ func findFacesMakingPolygon(include func([2]label) bool, start *faceRecord) map[
adj := adjacentFaces(popped)
expanded[popped] = true
for _, f := range adj {
if !include(f.labels) {
if !include(f.inSet) {
continue
}
if expanded[f] {
Expand All @@ -177,7 +177,7 @@ func orderCCWRingFirst(rings []LineString) {
}
}

func (d *doublyConnectedEdgeList) extractLineStrings(include func([2]label) bool) ([]LineString, error) {
func (d *doublyConnectedEdgeList) extractLineStrings(include func([2]bool) bool) ([]LineString, error) {
var lss []LineString
for _, e := range d.halfEdges {
if shouldExtractLine(e, include) {
Expand All @@ -196,20 +196,20 @@ func (d *doublyConnectedEdgeList) extractLineStrings(include func([2]label) bool
return lss, nil
}

func shouldExtractLine(e *halfEdgeRecord, include func([2]label) bool) bool {
func shouldExtractLine(e *halfEdgeRecord, include func([2]bool) bool) bool {
return !e.extracted &&
include(e.edgeLabels) &&
!include(e.incident.labels) &&
!include(e.twin.incident.labels)
include(e.inSet) &&
!include(e.incident.inSet) &&
!include(e.twin.incident.inSet)
}

// extractPoints extracts any vertices in the DCEL that should be part of the
// output geometry, but aren't yet represented as part of any previously
// extracted geometries.
func (d *doublyConnectedEdgeList) extractPoints(include func([2]label) bool) ([]Point, error) {
func (d *doublyConnectedEdgeList) extractPoints(include func([2]bool) bool) ([]Point, error) {
var pts []Point
for _, vert := range d.vertices {
if include(vert.labels) && !vert.extracted {
if include(vert.inSet) && !vert.extracted {
vert.extracted = true
pt, err := vert.coords.AsPoint()
if err != nil {
Expand Down
Loading

0 comments on commit 37c01ca

Please sign in to comment.