@@ -147,17 +147,11 @@ func (e EdgeQueryResult) IsEmpty() bool {
147
147
// Less reports if this results is less that the other first by distance,
148
148
// then by (shapeID, edgeID). This is used for sorting.
149
149
func (e EdgeQueryResult ) Less (other EdgeQueryResult ) bool {
150
- if e .distance .less ( other .distance ) {
151
- return true
150
+ if e .distance .chordAngle () != other .distance . chordAngle ( ) {
151
+ return e . distance . less ( other . distance )
152
152
}
153
- if other .distance .less (e .distance ) {
154
- return false
155
- }
156
- if e .shapeID < other .shapeID {
157
- return true
158
- }
159
- if other .shapeID < e .shapeID {
160
- return false
153
+ if e .shapeID != other .shapeID {
154
+ return e .shapeID < other .shapeID
161
155
}
162
156
return e .edgeID < other .edgeID
163
157
}
@@ -219,6 +213,22 @@ type EdgeQuery struct {
219
213
220
214
// testedEdges tracks the set of shape and edges that have already been tested.
221
215
testedEdges map [ShapeEdgeID ]uint32
216
+
217
+ // For the optimized algorihm we precompute the top-level CellIDs that
218
+ // will be added to the priority queue. There can be at most 6 of these
219
+ // cells. Essentially this is just a covering of the indexed edges, except
220
+ // that we also store pointers to the corresponding ShapeIndexCells to
221
+ // reduce the number of index seeks required.
222
+ indexCovering []CellID
223
+ indexCells []* ShapeIndexCell
224
+
225
+ // The algorithm maintains a priority queue of unprocessed CellIDs, sorted
226
+ // in increasing order of distance from the target.
227
+ queue * queryQueue
228
+
229
+ iter * ShapeIndexIterator
230
+ maxDistanceCovering []CellID
231
+ initialCells []CellID
222
232
}
223
233
224
234
// NewClosestEdgeQuery returns an EdgeQuery that is used for finding the
@@ -245,11 +255,14 @@ func NewClosestEdgeQuery(index *ShapeIndex, opts *EdgeQueryOptions) *EdgeQuery {
245
255
if opts == nil {
246
256
opts = NewClosestEdgeQueryOptions ()
247
257
}
248
- return & EdgeQuery {
258
+ e := & EdgeQuery {
249
259
testedEdges : make (map [ShapeEdgeID ]uint32 ),
250
260
index : index ,
251
261
opts : opts .common ,
262
+ queue : newQueryQueue (),
252
263
}
264
+
265
+ return e
253
266
}
254
267
255
268
// NewFurthestEdgeQuery returns an EdgeQuery that is used for finding the
@@ -264,11 +277,22 @@ func NewFurthestEdgeQuery(index *ShapeIndex, opts *EdgeQueryOptions) *EdgeQuery
264
277
if opts == nil {
265
278
opts = NewFurthestEdgeQueryOptions ()
266
279
}
267
- return & EdgeQuery {
280
+ e := & EdgeQuery {
268
281
testedEdges : make (map [ShapeEdgeID ]uint32 ),
269
282
index : index ,
270
283
opts : opts .common ,
284
+ queue : newQueryQueue (),
271
285
}
286
+
287
+ return e
288
+ }
289
+
290
+ // Reset resets the state of this EdgeQuery.
291
+ func (e * EdgeQuery ) Reset () {
292
+ e .indexNumEdges = 0
293
+ e .indexNumEdgesLimit = 0
294
+ e .indexCovering = nil
295
+ e .indexCells = nil
272
296
}
273
297
274
298
// FindEdges returns the edges for the given target that satisfy the current options.
@@ -462,10 +486,7 @@ func (e *EdgeQuery) findEdgesInternal(target distanceTarget, opts *queryOptions)
462
486
// If the target takes advantage of maxError then we need to avoid
463
487
// duplicate edges explicitly. (Otherwise it happens automatically.)
464
488
e .avoidDuplicates = targetUsesMaxError && opts .maxResults > 1
465
-
466
- // TODO(roberts): Uncomment when optimized is completed.
467
- e .findEdgesBruteForce ()
468
- //e.findEdgesOptimized()
489
+ e .findEdgesOptimized ()
469
490
}
470
491
}
471
492
@@ -505,8 +526,278 @@ func (e *EdgeQuery) findEdgesBruteForce() {
505
526
}
506
527
}
507
528
529
+ func (e * EdgeQuery ) findEdgesOptimized () {
530
+ e .initQueue ()
531
+ // Repeatedly find the closest Cell to "target" and either split it into
532
+ // its four children or process all of its edges.
533
+ for e .queue .size () > 0 {
534
+ // We need to copy the top entry before removing it, and we need to
535
+ // remove it before adding any new entries to the queue.
536
+ entry := e .queue .pop ()
537
+
538
+ if ! entry .distance .less (e .distanceLimit ) {
539
+ e .queue .reset () // Clear any remaining entries.
540
+ break
541
+ }
542
+ // If this is already known to be an index cell, just process it.
543
+ if entry .indexCell != nil {
544
+ e .processEdges (entry )
545
+ continue
546
+ }
547
+ // Otherwise split the cell into its four children. Before adding a
548
+ // child back to the queue, we first check whether it is empty. We do
549
+ // this in two seek operations rather than four by seeking to the key
550
+ // between children 0 and 1 and to the key between children 2 and 3.
551
+ id := entry .id
552
+ ch := id .Children ()
553
+ e .iter .seek (ch [1 ].RangeMin ())
554
+
555
+ if ! e .iter .Done () && e .iter .CellID () <= ch [1 ].RangeMax () {
556
+ e .processOrEnqueueCell (ch [1 ])
557
+ }
558
+ if e .iter .Prev () && e .iter .CellID () >= id .RangeMin () {
559
+ e .processOrEnqueueCell (ch [0 ])
560
+ }
561
+
562
+ e .iter .seek (ch [3 ].RangeMin ())
563
+ if ! e .iter .Done () && e .iter .CellID () <= id .RangeMax () {
564
+ e .processOrEnqueueCell (ch [3 ])
565
+ }
566
+ if e .iter .Prev () && e .iter .CellID () >= ch [2 ].RangeMin () {
567
+ e .processOrEnqueueCell (ch [2 ])
568
+ }
569
+ }
570
+ }
571
+
572
+ func (e * EdgeQuery ) processOrEnqueueCell (id CellID ) {
573
+ if e .iter .CellID () == id {
574
+ e .processOrEnqueue (id , e .iter .IndexCell ())
575
+ } else {
576
+ e .processOrEnqueue (id , nil )
577
+ }
578
+ }
579
+
580
+ func (e * EdgeQuery ) initQueue () {
581
+ if len (e .indexCovering ) == 0 {
582
+ // We delay iterator initialization until now to make queries on very
583
+ // small indexes a bit faster (i.e., where brute force is used).
584
+ e .iter = NewShapeIndexIterator (e .index )
585
+ }
586
+
587
+ // Optimization: if the user is searching for just the closest edge, and the
588
+ // center of the target's bounding cap happens to intersect an index cell,
589
+ // then we try to limit the search region to a small disc by first
590
+ // processing the edges in that cell. This sets distance_limit_ based on
591
+ // the closest edge in that cell, which we can then use to limit the search
592
+ // area. This means that the cell containing "target" will be processed
593
+ // twice, but in general this is still faster.
594
+ //
595
+ // TODO(roberts): Even if the cap center is not contained, we could still
596
+ // process one or both of the adjacent index cells in CellID order,
597
+ // provided that those cells are closer than distanceLimit.
598
+ cb := e .target .capBound ()
599
+ if cb .IsEmpty () {
600
+ return // Empty target.
601
+ }
602
+
603
+ if e .opts .maxResults == 1 && e .iter .LocatePoint (cb .Center ()) {
604
+ e .processEdges (& queryQueueEntry {
605
+ distance : e .target .distance ().zero (),
606
+ id : e .iter .CellID (),
607
+ indexCell : e .iter .IndexCell (),
608
+ })
609
+ // Skip the rest of the algorithm if we found an intersecting edge.
610
+ if e .distanceLimit == e .target .distance ().zero () {
611
+ return
612
+ }
613
+ }
614
+ if len (e .indexCovering ) == 0 {
615
+ e .initCovering ()
616
+ }
617
+ if e .distanceLimit == e .target .distance ().infinity () {
618
+ // Start with the precomputed index covering.
619
+ for i := range e .indexCovering {
620
+ e .processOrEnqueue (e .indexCovering [i ], e .indexCells [i ])
621
+ }
622
+ } else {
623
+ // Compute a covering of the search disc and intersect it with the
624
+ // precomputed index covering.
625
+ coverer := & RegionCoverer {MaxCells : 4 , LevelMod : 1 , MaxLevel : maxLevel }
626
+
627
+ radius := cb .Radius () + e .distanceLimit .chordAngleBound ().Angle ()
628
+ searchCB := CapFromCenterAngle (cb .Center (), radius )
629
+ maxDistCover := coverer .FastCovering (searchCB )
630
+ e .initialCells = CellUnionFromIntersection (e .indexCovering , maxDistCover )
631
+
632
+ // Now we need to clean up the initial cells to ensure that they all
633
+ // contain at least one cell of the ShapeIndex. (Some may not intersect
634
+ // the index at all, while other may be descendants of an index cell.)
635
+ i , j := 0 , 0
636
+ for i < len (e .initialCells ) {
637
+ idI := e .initialCells [i ]
638
+ // Find the top-level cell that contains this initial cell.
639
+ for e .indexCovering [j ].RangeMax () < idI {
640
+ j ++
641
+ }
642
+
643
+ idJ := e .indexCovering [j ]
644
+ if idI == idJ {
645
+ // This initial cell is one of the top-level cells. Use the
646
+ // precomputed ShapeIndexCell pointer to avoid an index seek.
647
+ e .processOrEnqueue (idJ , e .indexCells [j ])
648
+ i ++
649
+ j ++
650
+ } else {
651
+ // This initial cell is a proper descendant of a top-level cell.
652
+ // Check how it is related to the cells of the ShapeIndex.
653
+ r := e .iter .LocateCellID (idI )
654
+ if r == Indexed {
655
+ // This cell is a descendant of an index cell.
656
+ // Enqueue it and skip any other initial cells
657
+ // that are also descendants of this cell.
658
+ e .processOrEnqueue (e .iter .CellID (), e .iter .IndexCell ())
659
+ lastID := e .iter .CellID ().RangeMax ()
660
+ for i < len (e .initialCells ) && e .initialCells [i ] <= lastID {
661
+ i ++
662
+ }
663
+ } else {
664
+ // Enqueue the cell only if it contains at least one index cell.
665
+ if r == Subdivided {
666
+ e .processOrEnqueue (idI , nil )
667
+ }
668
+ i ++
669
+ }
670
+ }
671
+ }
672
+ }
673
+ }
674
+
675
+ func (e * EdgeQuery ) initCovering () {
676
+ // Find the range of Cells spanned by the index and choose a level such
677
+ // that the entire index can be covered with just a few cells. These are
678
+ // the "top-level" cells. There are two cases:
679
+ //
680
+ // - If the index spans more than one face, then there is one top-level cell
681
+ // per spanned face, just big enough to cover the index cells on that face.
682
+ //
683
+ // - If the index spans only one face, then we find the smallest cell "C"
684
+ // that covers the index cells on that face (just like the case above).
685
+ // Then for each of the 4 children of "C", if the child contains any index
686
+ // cells then we create a top-level cell that is big enough to just fit
687
+ // those index cells (i.e., shrinking the child as much as possible to fit
688
+ // its contents). This essentially replicates what would happen if we
689
+ // started with "C" as the top-level cell, since "C" would immediately be
690
+ // split, except that we take the time to prune the children further since
691
+ // this will save work on every subsequent query.
692
+ e .indexCovering = make ([]CellID , 0 , 6 )
693
+
694
+ // TODO(roberts): Use a single iterator below and save position
695
+ // information using pair {CellID, ShapeIndexCell}.
696
+ next := NewShapeIndexIterator (e .index , IteratorBegin )
697
+ last := NewShapeIndexIterator (e .index , IteratorEnd )
698
+ last .Prev ()
699
+ if next .CellID () != last .CellID () {
700
+ // The index has at least two cells. Choose a level such that the entire
701
+ // index can be spanned with at most 6 cells (if the index spans multiple
702
+ // faces) or 4 cells (it the index spans a single face).
703
+ level , ok := next .CellID ().CommonAncestorLevel (last .CellID ())
704
+ if ! ok {
705
+ level = 0
706
+ } else {
707
+ level ++
708
+ }
709
+
710
+ // Visit each potential top-level cell except the last (handled below).
711
+ lastID := last .CellID ().Parent (level )
712
+ for id := next .CellID ().Parent (level ); id != lastID ; id = id .Next () {
713
+ // Skip any top-level cells that don't contain any index cells.
714
+ if id .RangeMax () < next .CellID () {
715
+ continue
716
+ }
717
+
718
+ // Find the range of index cells contained by this top-level cell and
719
+ // then shrink the cell if necessary so that it just covers them.
720
+ cellFirst := next .clone ()
721
+ next .seek (id .RangeMax ().Next ())
722
+ cellLast := next .clone ()
723
+ cellLast .Prev ()
724
+ e .addInitialRange (cellFirst , cellLast )
725
+ break
726
+ }
727
+
728
+ }
729
+ e .addInitialRange (next , last )
730
+ }
731
+
732
+ // addInitialRange adds an entry to the indexCovering and indexCells that covers the given
733
+ // inclusive range of cells.
734
+ //
735
+ // This requires that first and last cells have a common ancestor.
736
+ func (e * EdgeQuery ) addInitialRange (first , last * ShapeIndexIterator ) {
737
+ if first .CellID () == last .CellID () {
738
+ // The range consists of a single index cell.
739
+ e .indexCovering = append (e .indexCovering , first .CellID ())
740
+ e .indexCells = append (e .indexCells , first .IndexCell ())
741
+ } else {
742
+ // Add the lowest common ancestor of the given range.
743
+ level , _ := first .CellID ().CommonAncestorLevel (last .CellID ())
744
+ e .indexCovering = append (e .indexCovering , first .CellID ().Parent (level ))
745
+ e .indexCells = append (e .indexCells , nil )
746
+ }
747
+ }
748
+
749
+ // processEdges processes all the edges of the given index cell.
750
+ func (e * EdgeQuery ) processEdges (entry * queryQueueEntry ) {
751
+ for _ , clipped := range entry .indexCell .shapes {
752
+ shape := e .index .Shape (clipped .shapeID )
753
+ for j := 0 ; j < clipped .numEdges (); j ++ {
754
+ e .maybeAddResult (shape , int32 (clipped .edges [j ]))
755
+ }
756
+ }
757
+ }
758
+
759
+ // processOrEnqueue the given cell id and indexCell.
760
+ func (e * EdgeQuery ) processOrEnqueue (id CellID , indexCell * ShapeIndexCell ) {
761
+ if indexCell != nil {
762
+ // If this index cell has only a few edges, then it is faster to check
763
+ // them directly rather than computing the minimum distance to the Cell
764
+ // and inserting it into the queue.
765
+ const minEdgesToEnqueue = 10
766
+ numEdges := indexCell .numEdges ()
767
+ if numEdges == 0 {
768
+ return
769
+ }
770
+ if numEdges < minEdgesToEnqueue {
771
+ // Set "distance" to zero to avoid the expense of computing it.
772
+ e .processEdges (& queryQueueEntry {
773
+ distance : e .target .distance ().zero (),
774
+ id : id ,
775
+ indexCell : indexCell ,
776
+ })
777
+ return
778
+ }
779
+ }
780
+
781
+ // Otherwise compute the minimum distance to any point in the cell and add
782
+ // it to the priority queue.
783
+ cell := CellFromCellID (id )
784
+ dist := e .distanceLimit
785
+ var ok bool
786
+ if dist , ok = e .target .updateDistanceToCell (cell , dist ); ! ok {
787
+ return
788
+ }
789
+ if e .useConservativeCellDistance {
790
+ // Ensure that "distance" is a lower bound on the true distance to the cell.
791
+ dist = dist .sub (e .target .distance ().fromChordAngle (e .opts .maxError ))
792
+ }
793
+
794
+ e .queue .push (& queryQueueEntry {
795
+ distance : dist ,
796
+ id : id ,
797
+ indexCell : indexCell ,
798
+ })
799
+ }
800
+
508
801
// TODO(roberts): Remaining pieces
509
- // Add clear/reset/re-init method to empty out the state of the query.
510
- // findEdgesOptimized and related methods.
511
802
// GetEdge
512
803
// Project
0 commit comments