Skip to content

Commit

Permalink
Merge pull request #468 from peterstace/use_sequence_instead_of_inter…
Browse files Browse the repository at this point in the history
…mediate_xys_for_dcel_edges

Use Sequence instead of []XY for DCEL edges
  • Loading branch information
peterstace committed Nov 7, 2022
2 parents d81ad57 + f016939 commit c9889c2
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 149 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Use `Sequence` instead of `[]XY` for DCEL edges.

## v0.40.1

2022-11-08
Expand Down
183 changes: 81 additions & 102 deletions geom/dcel.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,14 @@ func (f *faceRecord) String() string {
}

type halfEdgeRecord struct {
origin *vertexRecord
twin *halfEdgeRecord
incident *faceRecord // only populated in the overlay
next, prev *halfEdgeRecord
intermediate []XY
edgeLabels [2]label
faceLabels [2]label
extracted bool
}

// secondXY gives the second (1-indexed) XY in the edge. This is either the
// first intermediate XY, or the origin of the next/twin edge in the case where
// there are no intermediates.
func (e *halfEdgeRecord) secondXY() XY {
if len(e.intermediate) == 0 {
return e.twin.origin.coords
}
return e.intermediate[0]
origin *vertexRecord
twin *halfEdgeRecord
incident *faceRecord // only populated in the overlay
next, prev *halfEdgeRecord
seq Sequence
edgeLabels [2]label
faceLabels [2]label
extracted bool
}

// String shows the origin and destination of the edge (for debugging
Expand All @@ -50,7 +40,7 @@ func (e *halfEdgeRecord) String() string {
if e == nil {
return "nil"
}
return fmt.Sprintf("%v->%v->%v", e.origin.coords, e.intermediate, e.twin.origin.coords)
return fmt.Sprintf("%v", sequenceToXYs(e.seq))
}

type vertexRecord struct {
Expand Down Expand Up @@ -143,33 +133,30 @@ func (d *doublyConnectedEdgeList) addMultiPolygon(mp MultiPolygon, operand opera

for _, ring := range rings {
var newEdges []*halfEdgeRecord
forEachNonInteractingSegment(ring, interactions, func(segment []XY) {
// Construct the internal points slices.
intermediateFwd := segment[1 : len(segment)-1]
intermediateRev := reverseXYs(intermediateFwd)

forEachNonInteractingSegment(ring, interactions, func(segment Sequence) {
// Build the edges (fwd and rev).
vertA := d.vertices[segment[0]]
vertB := d.vertices[segment[len(segment)-1]]
reverseSegment := reverseSequence(segment)
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
intermediate: intermediateFwd,
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,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newHalfPopulatedLabels(operand, true),
}
externalEdge := &halfEdgeRecord{
origin: vertB,
twin: internalEdge,
incident: nil, // only populated in the overlay
next: nil, // populated later
prev: nil, // populated later
intermediate: intermediateRev,
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,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newHalfPopulatedLabels(operand, false),
}
internalEdge.twin = externalEdge
vertA.incidents = append(vertA.incidents, internalEdge)
Expand Down Expand Up @@ -241,41 +228,39 @@ func (d *doublyConnectedEdgeList) addMultiLineString(mls MultiLineString, operan
// Add edges.
for i := 0; i < mls.NumLineStrings(); i++ {
seq := mls.LineStringN(i).Coordinates()
forEachNonInteractingSegment(seq, interactions, func(segment []XY) {
startXY := segment[0]
endXY := segment[len(segment)-1]
forEachNonInteractingSegment(seq, interactions, func(segment Sequence) {
reverseSegment := reverseSequence(segment)
startXY := segment.GetXY(0)
endXY := reverseSegment.GetXY(0)

intermediateFwd := segment[1 : len(segment)-1]
intermediateRev := reverseXYs(intermediateFwd)

if edges.containsStartIntermediateEnd(startXY, intermediateFwd, endXY) {
if edges.containsStartIntermediateEnd(segment) {
return
}
edges.insertStartIntermediateEnd(startXY, intermediateFwd, endXY)
edges.insertStartIntermediateEnd(endXY, intermediateRev, startXY)
edges.insertStartIntermediateEnd(segment)
edges.insertStartIntermediateEnd(reverseSegment)

vOrigin := d.vertices[startXY]
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
intermediate: intermediateFwd,
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,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newUnpopulatedLabels(),
}
rev := &halfEdgeRecord{
origin: vDestin,
twin: fwd,
incident: nil, // only populated in overlay
next: fwd,
prev: fwd,
intermediate: intermediateRev,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newUnpopulatedLabels(),
origin: vDestin,
twin: fwd,
incident: nil, // only populated in overlay
next: fwd,
prev: fwd,
seq: reverseSegment,
edgeLabels: newHalfPopulatedLabels(operand, true),
faceLabels: newUnpopulatedLabels(),
}
fwd.twin = rev
fwd.next = rev
Expand Down Expand Up @@ -326,11 +311,10 @@ func (d *doublyConnectedEdgeList) addGhosts(mls MultiLineString, operand operand

for i := 0; i < mls.NumLineStrings(); i++ {
seq := mls.LineStringN(i).Coordinates()
forEachNonInteractingSegment(seq, interactions, func(segment []XY) {
startXY := segment[0]
endXY := segment[len(segment)-1]
intermediateFwd := segment[1 : len(segment)-1]
intermediateRev := reverseXYs(intermediateFwd)
forEachNonInteractingSegment(seq, interactions, func(segment Sequence) {
reverseSegment := reverseSequence(segment)
startXY := segment.GetXY(0)
endXY := reverseSegment.GetXY(0)

if _, ok := d.vertices[startXY]; !ok {
d.vertices[startXY] = &vertexRecord{coords: startXY, incidents: nil, labels: [2]label{}}
Expand All @@ -339,41 +323,41 @@ func (d *doublyConnectedEdgeList) addGhosts(mls MultiLineString, operand operand
d.vertices[endXY] = &vertexRecord{coords: endXY, incidents: nil, labels: [2]label{}}
}

if edges.containsStartIntermediateEnd(startXY, intermediateFwd, endXY) {
if edges.containsStartIntermediateEnd(segment) {
// Already exists, so shouldn't add.
return
}
edges.insertStartIntermediateEnd(startXY, intermediateFwd, endXY)
edges.insertStartIntermediateEnd(endXY, intermediateRev, startXY)
edges.insertStartIntermediateEnd(segment)
edges.insertStartIntermediateEnd(reverseSegment)

d.addGhostLine(startXY, intermediateFwd, intermediateRev, endXY, operand)
d.addGhostLine(segment, reverseSegment, operand)
})
}
}

func (d *doublyConnectedEdgeList) addGhostLine(startXY XY, intermediateFwd, intermediateRev []XY, endXY XY, operand operand) {
vertA := d.vertices[startXY]
vertB := d.vertices[endXY]
func (d *doublyConnectedEdgeList) addGhostLine(segment, reverseSegment Sequence, operand operand) {
vertA := d.vertices[segment.GetXY(0)]
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
intermediate: intermediateFwd,
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,
edgeLabels: newHalfPopulatedLabels(operand, false),
faceLabels: [2]label{},
}
e2 := &halfEdgeRecord{
origin: vertB,
twin: e1,
incident: nil, // only populated in the overlay
next: e1,
prev: e1,
intermediate: intermediateRev,
edgeLabels: newHalfPopulatedLabels(operand, false),
faceLabels: [2]label{},
origin: vertB,
twin: e1,
incident: nil, // only populated in the overlay
next: e1,
prev: e1,
seq: reverseSegment,
edgeLabels: newHalfPopulatedLabels(operand, false),
faceLabels: [2]label{},
}
e1.twin = e2
e1.next = e2
Expand All @@ -388,7 +372,7 @@ func (d *doublyConnectedEdgeList) addGhostLine(startXY XY, intermediateFwd, inte
d.fixVertex(vertB)
}

func forEachNonInteractingSegment(seq Sequence, interactions map[XY]struct{}, fn func([]XY)) {
func forEachNonInteractingSegment(seq Sequence, interactions map[XY]struct{}, fn func(Sequence)) {
n := seq.Length()
i := 0
for i < n-1 {
Expand All @@ -403,13 +387,8 @@ func forEachNonInteractingSegment(seq Sequence, interactions map[XY]struct{}, fn
}
}

// Construct the segment.
segment := make([]XY, end-start+1)
for j := range segment {
segment[j] = seq.GetXY(start + j)
}

// Execute the callback with the segment.
segment := seq.Slice(start, end+1)
fn(segment)

// On the next iteration, start the next edge at the end of
Expand Down
29 changes: 12 additions & 17 deletions geom/dcel_edge_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,32 @@ package geom
// edgeSet represents a set of edges in a DCEL. It makes use of assumptions
// around proper noding in conjunction with interaction points.
//
// Implementation detail: the map key is 3 XYs. The first is the start point of
// the edge, the second is the first intermediate point (or a repeat of the
// first XY if there are no intermediate points), and the third is the start
// point of the next edge.
type edgeSet map[[3]XY]*halfEdgeRecord
// Implementation detail: the map key is 2 XYs. The first is the start point of
// the edge, the second is the second point of the edge (which may or may not
// be the final point of the edge).
type edgeSet map[[2]XY]*halfEdgeRecord

func (s edgeSet) containsStartIntermediateEnd(start XY, intermediate []XY, end XY) bool {
_, ok := s[s.key(start, intermediate, end)]
func (s edgeSet) containsStartIntermediateEnd(segment Sequence) bool {
_, ok := s[s.key(segment)]
return ok
}

func (s edgeSet) insertEdge(e *halfEdgeRecord) {
k := s.key(e.origin.coords, e.intermediate, e.next.origin.coords)
k := s.key(e.seq)
s[k] = e
}

func (s edgeSet) insertStartIntermediateEnd(start XY, intermediate []XY, end XY) {
k := s.key(start, intermediate, end)
func (s edgeSet) insertStartIntermediateEnd(segment Sequence) {
k := s.key(segment)
s[k] = nil // TODO: this is a bit weird...
}

func (s edgeSet) lookupEdge(e *halfEdgeRecord) (*halfEdgeRecord, bool) {
k := s.key(e.origin.coords, e.intermediate, e.next.origin.coords)
k := s.key(e.seq)
e, ok := s[k]
return e, ok
}

func (edgeSet) key(start XY, intermediate []XY, end XY) [3]XY {
key := [3]XY{start, start, end}
if len(intermediate) > 0 {
key[1] = intermediate[0]
}
return key
func (edgeSet) key(segment Sequence) [2]XY {
return [2]XY{segment.GetXY(0), segment.GetXY(1)}
}
18 changes: 3 additions & 15 deletions geom/dcel_extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,8 @@ func extractPolygonBoundary(faceSet map[*faceRecord]bool, start *halfEdgeRecord,
e := start
for {
seen[e] = true
xy := e.origin.coords
coords = append(coords, xy.X, xy.Y)
for _, xy := range e.intermediate {
for i := 0; i < e.seq.Length()-1; i++ {
xy := e.seq.GetXY(i)
coords = append(coords, xy.X, xy.Y)
}

Expand Down Expand Up @@ -187,18 +186,7 @@ func (d *doublyConnectedEdgeList) extractLineStrings(include func([2]label) bool
e.origin.extracted = true
e.twin.origin.extracted = true

coords := make([]float64, 4+2*len(e.intermediate))
coords[0] = e.origin.coords.X
coords[1] = e.origin.coords.Y
for i, xy := range e.intermediate {
coords[2+2*i] = xy.X
coords[3+2*i] = xy.Y
}
coords[len(coords)-2] = e.twin.origin.coords.X
coords[len(coords)-1] = e.twin.origin.coords.Y

seq := NewSequence(coords, DimXY)
ls, err := NewLineString(seq)
ls, err := NewLineString(e.seq)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions geom/dcel_overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ func (d *doublyConnectedEdgeList) fixVertex(v *vertexRecord) {
sort.Slice(v.incidents, func(i, j int) bool {
ei := v.incidents[i]
ej := v.incidents[j]
di := ei.secondXY().Sub(ei.origin.coords)
dj := ej.secondXY().Sub(ej.origin.coords)
di := ei.seq.GetXY(1).Sub(ei.seq.GetXY(0))
dj := ej.seq.GetXY(1).Sub(ej.seq.GetXY(0))
aI := math.Atan2(di.Y, di.X)
aJ := math.Atan2(dj.Y, dj.X)
return aI < aJ
Expand Down
Loading

0 comments on commit c9889c2

Please sign in to comment.