/
shapeindex.go
1588 lines (1410 loc) · 56.8 KB
/
shapeindex.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright 2016 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package s2
import (
"math"
"sort"
"sync"
"sync/atomic"
"github.com/golang/geo/r1"
"github.com/golang/geo/r2"
)
// dimension defines the types of geometry dimensions that a Shape supports.
type dimension int
const (
pointGeometry dimension = iota
polylineGeometry
polygonGeometry
)
// Edge represents a geodesic edge consisting of two vertices. Zero-length edges are
// allowed, and can be used to represent points.
type Edge struct {
V0, V1 Point
}
// Cmp compares the two edges using the underlying Points Cmp method and returns
//
// -1 if e < other
// 0 if e == other
// +1 if e > other
//
// The two edges are compared by first vertex, and then by the second vertex.
func (e Edge) Cmp(other Edge) int {
if v0cmp := e.V0.Cmp(other.V0.Vector); v0cmp != 0 {
return v0cmp
}
return e.V1.Cmp(other.V1.Vector)
}
// sortEdges sorts the slice of Edges in place.
func sortEdges(e []Edge) {
sort.Sort(edges(e))
}
// edges implements the Sort interface for slices of Edge.
type edges []Edge
func (e edges) Len() int { return len(e) }
func (e edges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (e edges) Less(i, j int) bool { return e[i].Cmp(e[j]) == -1 }
// Chain represents a range of edge IDs corresponding to a chain of connected
// edges, specified as a (start, length) pair. The chain is defined to consist of
// edge IDs {start, start + 1, ..., start + length - 1}.
type Chain struct {
Start, Length int
}
// ChainPosition represents the position of an edge within a given edge chain,
// specified as a (chainID, offset) pair. Chains are numbered sequentially
// starting from zero, and offsets are measured from the start of each chain.
type ChainPosition struct {
ChainID, Offset int
}
// A ReferencePoint consists of a point and a boolean indicating whether the point
// is contained by a particular shape.
type ReferencePoint struct {
Point Point
Contained bool
}
// OriginReferencePoint returns a ReferencePoint with the given value for
// contained and the origin point. It should be used when all points or no
// points are contained.
func OriginReferencePoint(contained bool) ReferencePoint {
return ReferencePoint{Point: OriginPoint(), Contained: contained}
}
// Shape defines an interface for any S2 type that needs to be indexable. A shape
// is a collection of edges that optionally defines an interior. It can be used to
// represent a set of points, a set of polylines, or a set of polygons.
//
// The edges of a Shape are indexed by a contiguous range of edge IDs
// starting at 0. The edges are further subdivided into chains, where each
// chain consists of a sequence of edges connected end-to-end (a polyline).
// Shape has methods that allow edges to be accessed either using the global
// numbering (edge ID) or within a particular chain. The global numbering is
// sufficient for most purposes, but the chain representation is useful for
// certain algorithms such as intersection (see BoundaryOperation).
type Shape interface {
// NumEdges returns the number of edges in this shape.
NumEdges() int
// Edge returns the edge for the given edge index.
Edge(i int) Edge
// HasInterior reports whether this shape has an interior.
HasInterior() bool
// ReferencePoint returns an arbitrary reference point for the shape. (The
// containment boolean value must be false for shapes that do not have an interior.)
//
// This reference point may then be used to compute the containment of other
// points by counting edge crossings.
ReferencePoint() ReferencePoint
// NumChains reports the number of contiguous edge chains in the shape.
// For example, a shape whose edges are [AB, BC, CD, AE, EF] would consist
// of two chains (AB,BC,CD and AE,EF). Every chain is assigned a chain Id
// numbered sequentially starting from zero.
//
// Note that it is always acceptable to implement this method by returning
// NumEdges, i.e. every chain consists of a single edge, but this may
// reduce the efficiency of some algorithms.
NumChains() int
// Chain returns the range of edge IDs corresponding to the given edge chain.
// Edge chains must consist of contiguous, non-overlapping ranges that cover
// the entire range of edge IDs. This is spelled out more formally below:
//
// 0 <= i < NumChains()
// Chain(i).length > 0, for all i
// Chain(0).start == 0
// Chain(i).start + Chain(i).length == Chain(i+1).start, for i < NumChains()-1
// Chain(i).start + Chain(i).length == NumEdges(), for i == NumChains()-1
Chain(chainID int) Chain
// ChainEdgeReturns the edge at offset "offset" within edge chain "chainID".
// Equivalent to "shape.Edge(shape.Chain(chainID).start + offset)"
// but more efficient.
ChainEdge(chainID, offset int) Edge
// ChainPosition finds the chain containing the given edge, and returns the
// position of that edge as a ChainPosition(chainID, offset) pair.
//
// shape.Chain(pos.chainID).start + pos.offset == edgeID
// shape.Chain(pos.chainID+1).start > edgeID
//
// where pos == shape.ChainPosition(edgeID).
ChainPosition(edgeID int) ChainPosition
// dimension returns the dimension of the geometry represented by this shape.
//
// Note that this method allows degenerate geometry of different dimensions
// to be distinguished, e.g. it allows a point to be distinguished from a
// polyline or polygon that has been simplified to a single point.
dimension() dimension
}
// A minimal check for types that should satisfy the Shape interface.
var (
_ Shape = &Loop{}
_ Shape = &Polygon{}
_ Shape = &Polyline{}
)
// CellRelation describes the possible relationships between a target cell
// and the cells of the ShapeIndex. If the target is an index cell or is
// contained by an index cell, it is Indexed. If the target is subdivided
// into one or more index cells, it is Subdivided. Otherwise it is Disjoint.
type CellRelation int
// The possible CellRelations for a ShapeIndex.
const (
Indexed CellRelation = iota
Subdivided
Disjoint
)
const (
// cellPadding defines the total error when clipping an edge which comes
// from two sources:
// (1) Clipping the original spherical edge to a cube face (the face edge).
// The maximum error in this step is faceClipErrorUVCoord.
// (2) Clipping the face edge to the u- or v-coordinate of a cell boundary.
// The maximum error in this step is edgeClipErrorUVCoord.
// Finally, since we encounter the same errors when clipping query edges, we
// double the total error so that we only need to pad edges during indexing
// and not at query time.
cellPadding = 2.0 * (faceClipErrorUVCoord + edgeClipErrorUVCoord)
// cellSizeToLongEdgeRatio defines the cell size relative to the length of an
// edge at which it is first considered to be long. Long edges do not
// contribute toward the decision to subdivide a cell further. For example,
// a value of 2.0 means that the cell must be at least twice the size of the
// edge in order for that edge to be counted. There are two reasons for not
// counting long edges: (1) such edges typically need to be propagated to
// several children, which increases time and memory costs without much benefit,
// and (2) in pathological cases, many long edges close together could force
// subdivision to continue all the way to the leaf cell level.
cellSizeToLongEdgeRatio = 1.0
)
// clippedShape represents the part of a shape that intersects a Cell.
// It consists of the set of edge IDs that intersect that cell and a boolean
// indicating whether the center of the cell is inside the shape (for shapes
// that have an interior).
//
// Note that the edges themselves are not clipped; we always use the original
// edges for intersection tests so that the results will be the same as the
// original shape.
type clippedShape struct {
// shapeID is the index of the shape this clipped shape is a part of.
shapeID int32
// containsCenter indicates if the center of the CellID this shape has been
// clipped to falls inside this shape. This is false for shapes that do not
// have an interior.
containsCenter bool
// edges is the ordered set of ShapeIndex original edge IDs. Edges
// are stored in increasing order of edge ID.
edges []int
}
// newClippedShape returns a new clipped shape for the given shapeID and number of expected edges.
func newClippedShape(id int32, numEdges int) *clippedShape {
return &clippedShape{
shapeID: id,
edges: make([]int, numEdges),
}
}
// numEdges returns the number of edges that intersect the CellID of the Cell this was clipped to.
func (c *clippedShape) numEdges() int {
return len(c.edges)
}
// containsEdge reports if this clipped shape contains the given edge ID.
func (c *clippedShape) containsEdge(id int) bool {
// Linear search is fast because the number of edges per shape is typically
// very small (less than 10).
for _, e := range c.edges {
if e == id {
return true
}
}
return false
}
// ShapeIndexCell stores the index contents for a particular CellID.
type ShapeIndexCell struct {
shapes []*clippedShape
}
// NewShapeIndexCell creates a new cell that is sized to hold the given number of shapes.
func NewShapeIndexCell(numShapes int) *ShapeIndexCell {
return &ShapeIndexCell{
shapes: make([]*clippedShape, numShapes),
}
}
// numEdges reports the total number of edges in all clipped shapes in this cell.
func (s *ShapeIndexCell) numEdges() int {
var e int
for _, cs := range s.shapes {
e += cs.numEdges()
}
return e
}
// add adds the given clipped shape to this index cell.
func (s *ShapeIndexCell) add(c *clippedShape) {
s.shapes = append(s.shapes, c)
}
// findByShapeID returns the clipped shape that contains the given shapeID,
// or nil if none of the clipped shapes contain it.
func (s *ShapeIndexCell) findByShapeID(shapeID int32) *clippedShape {
// Linear search is fine because the number of shapes per cell is typically
// very small (most often 1), and is large only for pathological inputs
// (e.g. very deeply nested loops).
for _, clipped := range s.shapes {
if clipped.shapeID == shapeID {
return clipped
}
}
return nil
}
// faceEdge and clippedEdge store temporary edge data while the index is being
// updated.
//
// While it would be possible to combine all the edge information into one
// structure, there are two good reasons for separating it:
//
// - Memory usage. Separating the two means that we only need to
// store one copy of the per-face data no matter how many times an edge is
// subdivided, and it also lets us delay computing bounding boxes until
// they are needed for processing each face (when the dataset spans
// multiple faces).
//
// - Performance. UpdateEdges is significantly faster on large polygons when
// the data is separated, because it often only needs to access the data in
// clippedEdge and this data is cached more successfully.
// faceEdge represents an edge that has been projected onto a given face,
type faceEdge struct {
shapeID int32 // The ID of shape that this edge belongs to
edgeID int // Edge ID within that shape
maxLevel int // Not desirable to subdivide this edge beyond this level
hasInterior bool // Belongs to a shape that has an interior
a, b r2.Point // The edge endpoints, clipped to a given face
edge Edge // The original edge.
}
// clippedEdge represents the portion of that edge that has been clipped to a given Cell.
type clippedEdge struct {
faceEdge *faceEdge // The original unclipped edge
bound r2.Rect // Bounding box for the clipped portion
}
// ShapeIndexIterator is an iterator that provides low-level access to
// the cells of the index. Cells are returned in increasing order of CellID.
//
// for it := index.Iterator(); !it.Done(); it.Next() {
// fmt.Print(it.CellID())
// }
//
type ShapeIndexIterator struct {
index *ShapeIndex
position int
}
// CellID returns the CellID of the cell at the current position of the iterator.
func (s *ShapeIndexIterator) CellID() CellID {
if s.position >= len(s.index.cells) {
return 0
}
return s.index.cells[s.position]
}
// IndexCell returns the ShapeIndexCell at the current position of the iterator.
func (s *ShapeIndexIterator) IndexCell() *ShapeIndexCell {
return s.index.cellMap[s.CellID()]
}
// Center returns the Point at the center of the current position of the iterator.
func (s *ShapeIndexIterator) Center() Point {
return s.CellID().Point()
}
// Reset the iterator to be positioned at the first cell in the index.
func (s *ShapeIndexIterator) Reset() {
if !s.index.IsFresh() {
s.index.maybeApplyUpdates()
}
s.position = 0
}
// AtBegin reports if the iterator is positioned at the first index cell.
func (s *ShapeIndexIterator) AtBegin() bool {
return s.position == 0
}
// Next advances the iterator to the next cell in the index.
func (s *ShapeIndexIterator) Next() {
s.position++
}
// Prev advances the iterator to the previous cell in the index.
// If the iterator is at the first cell the call does nothing.
func (s *ShapeIndexIterator) Prev() {
if s.position > 0 {
s.position--
}
}
// Done reports if the iterator is positioned at or after the last index cell.
func (s *ShapeIndexIterator) Done() bool {
return s.position >= len(s.index.cells)
}
// seek positions the iterator at the first cell whose ID >= target starting from the
// current position of the iterator, or at the end of the index if no such cell exists.
// If the iterator is currently at the end, nothing is done.
func (s *ShapeIndexIterator) seek(target CellID) {
// In C++, this relies on the lower_bound method of the underlying btree_map.
// TODO(roberts): Convert this to a binary search since the list of cells is ordered.
for k, v := range s.index.cells {
// We've passed the cell that is after us, so we are done.
if v >= target {
s.position = k
break
}
// Otherwise, advance the position.
s.position++
}
}
// seekForward advances the iterator to the next cell with cellID >= target if the
// iterator is not Done or already satisfies the condition.
func (s *ShapeIndexIterator) seekForward(target CellID) {
if !s.Done() && s.CellID() < target {
s.seek(target)
}
}
// LocatePoint positions the iterator at the cell that contains the given Point.
// If no such cell exists, the iterator position is unspecified, and false is returned.
// The cell at the matched position is guaranteed to contain all edges that might
// intersect the line segment between target and the cell's center.
func (s *ShapeIndexIterator) LocatePoint(p Point) bool {
// Let I = cellMap.LowerBound(T), where T is the leaf cell containing
// point P. Then if T is contained by an index cell, then the
// containing cell is either I or I'. We test for containment by comparing
// the ranges of leaf cells spanned by T, I, and I'.
target := cellIDFromPoint(p)
s.seek(target)
if !s.Done() && s.CellID().RangeMin() <= target {
return true
}
if !s.AtBegin() {
s.Prev()
if s.CellID().RangeMax() >= target {
return true
}
}
return false
}
// LocateCellID attempts to position the iterator at the first matching indexCell
// in the index that has some relation to the given CellID. Let T be the target CellID.
// If T is contained by (or equal to) some index cell I, then the iterator is positioned
// at I and returns Indexed. Otherwise if T contains one or more (smaller) index cells,
// then position the iterator at the first such cell I and return Subdivided.
// Otherwise Disjoint is returned and the iterator position is undefined.
func (s *ShapeIndexIterator) LocateCellID(target CellID) CellRelation {
// Let T be the target, let I = cellMap.LowerBound(T.RangeMin()), and
// let I' be the predecessor of I. If T contains any index cells, then T
// contains I. Similarly, if T is contained by an index cell, then the
// containing cell is either I or I'. We test for containment by comparing
// the ranges of leaf cells spanned by T, I, and I'.
s.seek(target.RangeMin())
if !s.Done() {
if s.CellID() >= target && s.CellID().RangeMin() <= target {
return Indexed
}
if s.CellID() <= target.RangeMax() {
return Subdivided
}
}
if !s.AtBegin() {
s.Prev()
if s.CellID().RangeMax() >= target {
return Indexed
}
}
return Disjoint
}
// tracker keeps track of which shapes in a given set contain a particular point
// (the focus). It provides an efficient way to move the focus from one point
// to another and incrementally update the set of shapes which contain it. We use
// this to compute which shapes contain the center of every CellID in the index,
// by advancing the focus from one cell center to the next.
//
// Initially the focus is at the start of the CellID space-filling curve. We then
// visit all the cells that are being added to the ShapeIndex in increasing order
// of CellID. For each cell, we draw two edges: one from the entry vertex to the
// center, and another from the center to the exit vertex (where entry and exit
// refer to the points where the space-filling curve enters and exits the cell).
// By counting edge crossings we can incrementally compute which shapes contain
// the cell center. Note that the same set of shapes will always contain the exit
// point of one cell and the entry point of the next cell in the index, because
// either (a) these two points are actually the same, or (b) the intervening
// cells in CellID order are all empty, and therefore there are no edge crossings
// if we follow this path from one cell to the other.
//
// In C++, this is S2ShapeIndex::InteriorTracker.
type tracker struct {
isActive bool
a Point
b Point
nextCellID CellID
crosser *EdgeCrosser
shapeIDs []int32
// Shape ids saved by saveAndClearStateBefore. The state is never saved
// recursively so we don't need to worry about maintaining a stack.
savedIDs []int32
}
// newTracker returns a new tracker with the appropriate defaults.
func newTracker() *tracker {
// As shapes are added, we compute which ones contain the start of the
// CellID space-filling curve by drawing an edge from OriginPoint to this
// point and counting how many shape edges cross this edge.
t := &tracker{
isActive: false,
b: trackerOrigin(),
nextCellID: CellIDFromFace(0).ChildBeginAtLevel(maxLevel),
}
t.drawTo(Point{faceUVToXYZ(0, -1, -1).Normalize()}) // CellID curve start
return t
}
// trackerOrigin returns the initial focus point when the tracker is created
// (corresponding to the start of the CellID space-filling curve).
func trackerOrigin() Point {
// The start of the S2CellId space-filling curve.
return Point{faceUVToXYZ(0, -1, -1).Normalize()}
}
// focus returns the current focus point of the tracker.
func (t *tracker) focus() Point { return t.b }
// addShape adds a shape whose interior should be tracked. containsOrigin indicates
// whether the current focus point is inside the shape. Alternatively, if
// the focus point is in the process of being moved (via moveTo/drawTo), you
// can also specify containsOrigin at the old focus point and call testEdge
// for every edge of the shape that might cross the current drawTo line.
// This updates the state to correspond to the new focus point.
//
// This requires shape.HasInterior
func (t *tracker) addShape(shapeID int32, containsFocus bool) {
t.isActive = true
if containsFocus {
t.toggleShape(shapeID)
}
}
// moveTo moves the focus of the tracker to the given point. This method should
// only be used when it is known that there are no edge crossings between the old
// and new focus locations; otherwise use drawTo.
func (t *tracker) moveTo(b Point) { t.b = b }
// drawTo moves the focus of the tracker to the given point. After this method is
// called, testEdge should be called with all edges that may cross the line
// segment between the old and new focus locations.
func (t *tracker) drawTo(b Point) {
t.a = t.b
t.b = b
// TODO: the edge crosser may need an in-place Init method if this gets expensive
t.crosser = NewEdgeCrosser(t.a, t.b)
}
// testEdge checks if the given edge crosses the current edge, and if so, then
// toggle the state of the given shapeID.
// This requires shape to have an interior.
func (t *tracker) testEdge(shapeID int32, edge Edge) {
if t.crosser.EdgeOrVertexCrossing(edge.V0, edge.V1) {
t.toggleShape(shapeID)
}
}
// setNextCellID is used to indicate that the last argument to moveTo or drawTo
// was the entry vertex of the given CellID, i.e. the tracker is positioned at the
// start of this cell. By using this method together with atCellID, the caller
// can avoid calling moveTo in cases where the exit vertex of the previous cell
// is the same as the entry vertex of the current cell.
func (t *tracker) setNextCellID(nextCellID CellID) {
t.nextCellID = nextCellID.RangeMin()
}
// atCellID reports if the focus is already at the entry vertex of the given
// CellID (provided that the caller calls setNextCellID as each cell is processed).
func (t *tracker) atCellID(cellid CellID) bool {
return cellid.RangeMin() == t.nextCellID
}
// toggleShape adds or removes the given shapeID from the set of IDs it is tracking.
func (t *tracker) toggleShape(shapeID int32) {
// Most shapeIDs slices are small, so special case the common steps.
// If there is nothing here, add it.
if len(t.shapeIDs) == 0 {
t.shapeIDs = append(t.shapeIDs, shapeID)
return
}
// If it's the first element, drop it from the slice.
if t.shapeIDs[0] == shapeID {
t.shapeIDs = t.shapeIDs[1:]
return
}
for i, s := range t.shapeIDs {
if s < shapeID {
continue
}
// If it's in the set, cut it out.
if s == shapeID {
copy(t.shapeIDs[i:], t.shapeIDs[i+1:]) // overwrite the ith element
t.shapeIDs = t.shapeIDs[:len(t.shapeIDs)-1]
return
}
// We've got to a point in the slice where we should be inserted.
// (the given shapeID is now less than the current positions id.)
t.shapeIDs = append(t.shapeIDs[0:i],
append([]int32{shapeID}, t.shapeIDs[i:len(t.shapeIDs)]...)...)
return
}
// We got to the end and didn't find it, so add it to the list.
t.shapeIDs = append(t.shapeIDs, shapeID)
}
// saveAndClearStateBefore makes an internal copy of the state for shape ids below
// the given limit, and then clear the state for those shapes. This is used during
// incremental updates to track the state of added and removed shapes separately.
func (t *tracker) saveAndClearStateBefore(limitShapeID int32) {
limit := t.lowerBound(limitShapeID)
t.savedIDs = append([]int32(nil), t.shapeIDs[:limit]...)
t.shapeIDs = t.shapeIDs[limit:]
}
// restoreStateBefore restores the state previously saved by saveAndClearStateBefore.
// This only affects the state for shapeIDs below "limitShapeID".
func (t *tracker) restoreStateBefore(limitShapeID int32) {
limit := t.lowerBound(limitShapeID)
t.shapeIDs = append(append([]int32(nil), t.savedIDs...), t.shapeIDs[limit:]...)
t.savedIDs = nil
}
// lowerBound returns the shapeID of the first entry x where x >= shapeID.
func (t *tracker) lowerBound(shapeID int32) int32 {
panic("not implemented")
}
// removedShape represents a set of edges from the given shape that is queued for removal.
type removedShape struct {
shapeID int32
hasInterior bool
containsTrackerOrigin bool
edges []Edge
}
// There are three basic states the index can be in.
const (
stale int32 = iota // There are pending updates.
updating // Updates are currently being applied.
fresh // There are no pending updates.
)
// ShapeIndex indexes a set of Shapes, where a Shape is some collection of edges
// that optionally defines an interior. It can be used to represent a set of
// points, a set of polylines, or a set of polygons. For Shapes that have
// interiors, the index makes it very fast to determine which Shape(s) contain
// a given point or region.
//
// The index can be updated incrementally by adding or removing shapes. It is
// designed to handle up to hundreds of millions of edges. All data structures
// are designed to be small, so the index is compact; generally it is smaller
// than the underlying data being indexed. The index is also fast to construct.
//
// Polygon, Loop, and Polyline implement Shape which allows these objects to
// be indexed easily. You can find useful query methods in CrossingEdgeQuery
// and ClosestEdgeQuery (Not yet implemented in Go).
//
// Example showing how to build an index of Polylines:
//
// index := NewShapeIndex()
// for _, polyline := range polylines {
// index.Add(polyline);
// }
// // Now you can use a CrossingEdgeQuery or ClosestEdgeQuery here.
//
type ShapeIndex struct {
// shapes is a map of shape ID to shape.
shapes map[int32]Shape
// The maximum number of edges per cell.
// TODO(roberts): Update the comments when the usage of this is implemented.
maxEdgesPerCell int
// nextID tracks the next ID to hand out. IDs are not reused when shapes
// are removed from the index.
nextID int32
// cellMap is a map from CellID to the set of clipped shapes that intersect that
// cell. The cell IDs cover a set of non-overlapping regions on the sphere.
// In C++, this is a BTree, so the cells are ordered naturally by the data structure.
cellMap map[CellID]*ShapeIndexCell
// Track the ordered list of cell IDs.
cells []CellID
// The current status of the index; accessed atomically.
status int32
// Additions and removals are queued and processed on the first subsequent
// query. There are several reasons to do this:
//
// - It is significantly more efficient to process updates in batches if
// the amount of entities added grows.
// - Often the index will never be queried, in which case we can save both
// the time and memory required to build it. Examples:
// + Loops that are created simply to pass to an Polygon. (We don't
// need the Loop index, because Polygon builds its own index.)
// + Applications that load a database of geometry and then query only
// a small fraction of it.
//
// The main drawback is that we need to go to some extra work to ensure that
// some methods are still thread-safe. Note that the goal is *not* to
// make this thread-safe in general, but simply to hide the fact that
// we defer some of the indexing work until query time.
//
// This mutex protects all of following fields in the index.
mu sync.RWMutex
// pendingAdditionsPos is the index of the first entry that has not been processed
// via applyUpdatesInternal.
pendingAdditionsPos int32
// The set of shapes that have been queued for removal but not processed yet by
// applyUpdatesInternal.
pendingRemovals []*removedShape
}
// NewShapeIndex creates a new ShapeIndex.
func NewShapeIndex() *ShapeIndex {
return &ShapeIndex{
maxEdgesPerCell: 10,
shapes: make(map[int32]Shape),
cellMap: make(map[CellID]*ShapeIndexCell),
cells: nil,
status: fresh,
}
}
// Iterator returns an iterator for this index.
func (s *ShapeIndex) Iterator() *ShapeIndexIterator {
s.maybeApplyUpdates()
return &ShapeIndexIterator{index: s}
}
// Begin positions the iterator at the first cell in the index.
func (s *ShapeIndex) Begin() *ShapeIndexIterator {
s.maybeApplyUpdates()
return &ShapeIndexIterator{index: s}
}
// End positions the iterator at the last cell in the index.
func (s *ShapeIndex) End() *ShapeIndexIterator {
// TODO(roberts): It's possible that updates could happen to the index between
// the time this is called and the time the iterators position is used and this
// will be invalid or not the end. For now, things will be undefined if this
// happens. See about referencing the IsFresh to guard for this in the future.
s.maybeApplyUpdates()
return &ShapeIndexIterator{
index: s,
position: len(s.cells),
}
}
// Len reports the number of Shapes in this index.
func (s *ShapeIndex) Len() int {
return len(s.shapes)
}
// Reset resets the index to its original state.
func (s *ShapeIndex) Reset() {
s.shapes = make(map[int32]Shape)
s.nextID = 0
s.cellMap = make(map[CellID]*ShapeIndexCell)
s.cells = nil
atomic.StoreInt32(&s.status, fresh)
}
// NumEdges returns the number of edges in this index.
func (s *ShapeIndex) NumEdges() int {
numEdges := 0
for _, shape := range s.shapes {
numEdges += shape.NumEdges()
}
return numEdges
}
// Shape returns the shape with the given ID, or nil if the shape has been removed from the index.
func (s *ShapeIndex) Shape(id int32) Shape { return s.shapes[id] }
// Add adds the given shape to the index.
func (s *ShapeIndex) Add(shape Shape) {
s.shapes[s.nextID] = shape
s.nextID++
atomic.StoreInt32(&s.status, stale)
}
// Remove removes the given shape from the index.
func (s *ShapeIndex) Remove(shape Shape) {
// The index updates itself lazily because it is much more efficient to
// process additions and removals in batches.
// Lookup the id of this shape in the index.
id := int32(-1)
for k, v := range s.shapes {
if v == shape {
id = k
}
}
// If the shape wasn't found, it's already been removed or was not in the index.
if s.shapes[id] == nil {
return
}
// Remove the shape from the shapes map.
delete(s.shapes, id)
// We are removing a shape that has not yet been added to the index,
// so there is nothing else to do.
if id >= s.pendingAdditionsPos {
return
}
numEdges := shape.NumEdges()
removed := &removedShape{
shapeID: id,
hasInterior: shape.HasInterior(),
containsTrackerOrigin: shape.ReferencePoint().Contained,
edges: make([]Edge, numEdges),
}
for e := 0; e < numEdges; e++ {
removed.edges[e] = shape.Edge(e)
}
s.pendingRemovals = append(s.pendingRemovals, removed)
atomic.StoreInt32(&s.status, stale)
}
// IsFresh reports if there are no pending updates that need to be applied.
// This can be useful to avoid building the index unnecessarily, or for
// choosing between two different algorithms depending on whether the index
// is available.
//
// The returned index status may be slightly out of date if the index was
// built in a different thread. This is fine for the intended use (as an
// efficiency hint), but it should not be used by internal methods.
func (s *ShapeIndex) IsFresh() bool {
return atomic.LoadInt32(&s.status) == fresh
}
// isFirstUpdate reports if this is the first update to the index.
func (s *ShapeIndex) isFirstUpdate() bool {
// Note that it is not sufficient to check whether cellMap is empty, since
// entries are added to it during the update process.
return s.pendingAdditionsPos == 0
}
// isShapeBeingRemoved reports if the shape with the given ID is currently slated for removal.
func (s *ShapeIndex) isShapeBeingRemoved(shapeID int32) bool {
// All shape ids being removed fall below the index position of shapes being added.
return shapeID < s.pendingAdditionsPos
}
// maybeApplyUpdates checks if the index pieces have changed, and if so, applies pending updates.
func (s *ShapeIndex) maybeApplyUpdates() {
// TODO(roberts): To avoid acquiring and releasing the mutex on every
// query, we should use atomic operations when testing whether the status
// is fresh and when updating the status to be fresh. This guarantees
// that any thread that sees a status of fresh will also see the
// corresponding index updates.
if atomic.LoadInt32(&s.status) != fresh {
s.mu.Lock()
s.applyUpdatesInternal()
atomic.StoreInt32(&s.status, fresh)
s.mu.Unlock()
}
}
// applyUpdatesInternal does the actual work of updating the index by applying all
// pending additions and removals. It does *not* update the indexes status.
func (s *ShapeIndex) applyUpdatesInternal() {
// TODO(roberts): Building the index can use up to 20x as much memory per
// edge as the final index memory size. If this causes issues, add in
// batched updating to limit the amount of items per batch to a
// configurable memory footprint overhead.
t := newTracker()
// allEdges maps a Face to a collection of faceEdges.
allEdges := make([][]faceEdge, 6)
for _, p := range s.pendingRemovals {
s.removeShapeInternal(p, allEdges, t)
}
for id := s.pendingAdditionsPos; id < int32(len(s.shapes)); id++ {
s.addShapeInternal(id, allEdges, t)
}
for face := 0; face < 6; face++ {
s.updateFaceEdges(face, allEdges[face], t)
}
s.pendingRemovals = s.pendingRemovals[:0]
s.pendingAdditionsPos = int32(len(s.shapes))
// It is the caller's responsibility to update the index status.
}
// addShapeInternal clips all edges of the given shape to the six cube faces,
// adds the clipped edges to the set of allEdges, and starts tracking its
// interior if necessary.
func (s *ShapeIndex) addShapeInternal(shapeID int32, allEdges [][]faceEdge, t *tracker) {
shape, ok := s.shapes[shapeID]
if !ok {
// This shape has already been removed.
return
}
faceEdge := faceEdge{
shapeID: shapeID,
hasInterior: shape.HasInterior(),
}
if faceEdge.hasInterior {
t.addShape(shapeID, containsBruteForce(shape, t.focus()))
}
numEdges := shape.NumEdges()
for e := 0; e < numEdges; e++ {
edge := shape.Edge(e)
faceEdge.edgeID = e
faceEdge.edge = edge
faceEdge.maxLevel = maxLevelForEdge(edge)
s.addFaceEdge(faceEdge, allEdges)
}
}
// addFaceEdge adds the given faceEdge into the collection of all edges.
func (s *ShapeIndex) addFaceEdge(fe faceEdge, allEdges [][]faceEdge) {
aFace := face(fe.edge.V0.Vector)
// See if both endpoints are on the same face, and are far enough from
// the edge of the face that they don't intersect any (padded) adjacent face.
if aFace == face(fe.edge.V1.Vector) {
x, y := validFaceXYZToUV(aFace, fe.edge.V0.Vector)
fe.a = r2.Point{x, y}
x, y = validFaceXYZToUV(aFace, fe.edge.V1.Vector)
fe.b = r2.Point{x, y}
maxUV := 1 - cellPadding
if math.Abs(fe.a.X) <= maxUV && math.Abs(fe.a.Y) <= maxUV &&
math.Abs(fe.b.X) <= maxUV && math.Abs(fe.b.Y) <= maxUV {
allEdges[aFace] = append(allEdges[aFace], fe)
return
}
}
// Otherwise, we simply clip the edge to all six faces.
for face := 0; face < 6; face++ {
if aClip, bClip, intersects := ClipToPaddedFace(fe.edge.V0, fe.edge.V1, face, cellPadding); intersects {
fe.a = aClip
fe.b = bClip
allEdges[face] = append(allEdges[face], fe)
}
}
return
}
// updateFaceEdges adds or removes the various edges from the index.
// An edge is added if shapes[id] is not nil, and removed otherwise.
func (s *ShapeIndex) updateFaceEdges(face int, faceEdges []faceEdge, t *tracker) {
numEdges := len(faceEdges)
if numEdges == 0 && len(t.shapeIDs) == 0 {
return
}
// Create the initial clippedEdge for each faceEdge. Additional clipped
// edges are created when edges are split between child cells. We create
// two arrays, one containing the edge data and another containing pointers
// to those edges, so that during the recursion we only need to copy
// pointers in order to propagate an edge to the correct child.
clippedEdges := make([]*clippedEdge, numEdges)
bound := r2.EmptyRect()
for e := 0; e < numEdges; e++ {
clipped := &clippedEdge{
faceEdge: &faceEdges[e],
}
clipped.bound = r2.RectFromPoints(faceEdges[e].a, faceEdges[e].b)
clippedEdges[e] = clipped
bound = bound.AddRect(clipped.bound)
}
// Construct the initial face cell containing all the edges, and then update
// all the edges in the index recursively.
faceID := CellIDFromFace(face)
pcell := PaddedCellFromCellID(faceID, cellPadding)