1 // Copyright 2017 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS-IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 
16 // Original Author: ericv@google.com (Eric Veach)
17 // Converted to D:  madric@gmail.com (Vijay Nayar)
18 //
19 // See S2ClosestPointQueryBase (defined below) for an overview.
20 
21 module s2.s2closest_point_query_base;
22 
23 import s2.logger;
24 import s2.s1chord_angle;
25 import s2.s2cap;
26 import s2.s2cell;
27 import s2.s2cell_id;
28 import s2.s2cell_union;
29 import s2.s2distance_target;
30 import s2.s2edge_distances;
31 import s2.s2point;
32 import s2.s2point_index;
33 import s2.s2region;
34 import s2.s2region_coverer;
35 
36 import std.algorithm : isSorted, reverse, sort, uniq;
37 import std.container.binaryheap;
38 import std.exception;
39 import std.range;
40 import std.typecons : Rebindable;
41 
42 
43 /**
44  * Options that control the set of points returned.  Note that by default
45  * *all* points are returned, so you will always want to set either the
46  * max_points() option or the max_distance() option (or both).
47  *
48  * This class is also available as S2ClosestPointQueryBase<Data>::Options.
49  * (It is defined here to avoid depending on the "Data" template argument.)
50  *
51  * The Distance template argument is described below.
52  */
53 class S2ClosestPointQueryBaseOptions(Distance) {
54 public:
55   alias Delta = Distance.Delta;
56 
57   this() {}
58 
59   /**
60    * Specifies that at most "max_points" points should be returned.
61    *
62    * REQUIRES: max_points >= 1
63    * DEFAULT: numeric_limits<int>::max()
64    */
65   int maxPoints() const {
66     return _maxPoints;
67   }
68 
69   void setMaxPoints(int max_points)
70   in {
71     assert(max_points >= 1);
72   } do {
73     _maxPoints = max_points;
74   }
75 
76   enum int MAX_MAX_POINTS = int.max;
77 
78   /**
79    * Specifies that only points whose distance to the target is less than
80    * "max_distance" should be returned.
81    *
82    * Note that points whose distance is exactly equal to "max_distance" are
83    * not returned.  In most cases this doesn't matter (since distances are
84    * not computed exactly in the first place), but if such points are needed
85    * then you can retrieve them by specifying "max_distance" as the next
86    * largest representable Distance.  For example, if Distance is an
87    * S1ChordAngle then you can specify max_distance.Successor().
88    *
89    * DEFAULT: Distance::Infinity()
90    */
91   Distance maxDistance() const {
92     return _maxDistance;
93   }
94 
95   void setMaxDistance(Distance max_distance) {
96     _maxDistance = max_distance;
97   }
98 
99   /**
100    * Specifies that points up to max_error() further away than the true
101    * closest points may be substituted in the result set, as long as such
102    * points satisfy all the remaining search criteria (such as max_distance).
103    * This option only has an effect if max_points() is also specified;
104    * otherwise all points closer than max_distance() will always be returned.
105    *
106    * Note that this does not affect how the distance between points is
107    * computed; it simply gives the algorithm permission to stop the search
108    * early as soon as the best possible improvement drops below max_error().
109    *
110    * This can be used to implement distance predicates efficiently.  For
111    * example, to determine whether the minimum distance is less than D, the
112    * IsDistanceLess() method sets max_points() == 1 and max_distance() ==
113    * max_error() == D.  This causes the algorithm to terminate as soon as it
114    * finds any point whose distance is less than D, rather than continuing to
115    * search for a point that is even closer.
116    *
117    * DEFAULT: Distance::Delta::Zero()
118    */
119   Delta maxError() const {
120     return _maxError;
121   }
122 
123   void setMaxError(Delta max_error) {
124     _maxError = max_error;
125   }
126 
127   /**
128    * Specifies that points must be contained by the given S2Region.  "region"
129    * is owned by the caller and must persist during the lifetime of this
130    * object.  The value may be changed between calls to FindClosestPoints(),
131    * or reset by calling set_region(nullptr).
132    *
133    * Note that if you want to set the region to a disc around the target
134    * point, it is faster to use set_max_distance() instead.  You can also call
135    * both methods, e.g. to set a maximum distance and also require that points
136    * lie within a given rectangle.
137    */
138   inout(S2Region) region() inout {
139     return _region;
140   }
141 
142   void setRegion(S2Region region) {
143     _region = region;
144   }
145 
146   /**
147    * Specifies that distances should be computed by examining every point
148    * rather than using the S2ShapeIndex.  This is useful for testing,
149    * benchmarking, and debugging.
150    *
151    * DEFAULT: false
152    */
153   bool useBruteForce() const {
154     return _useBruteForce;
155   }
156 
157   void setUseBruteForce(bool use_brute_force) {
158     _useBruteForce = use_brute_force;
159   }
160 
161   ThisT dup(this ThisT)() {
162     ThisT d = new ThisT();
163     d._maxDistance = _maxDistance;
164     d._maxError = _maxError;
165     d._region = _region;
166     d._maxPoints = _maxPoints;
167     d._useBruteForce = _useBruteForce;
168     return d;
169   }
170 
171   override
172   string toString() const {
173     import std.conv;
174     return "S2ClosestPointQueryBaseOptions"
175         ~ "[ maxDistance=" ~ _maxDistance.to!string
176         ~ ", maxError=" ~ _maxError.to!string
177         ~ ", region=" ~ _region.to!string
178         ~ ", maxPoints=" ~ _maxPoints.to!string
179         ~ ", useBruteForce=" ~ _useBruteForce.to!string ~ " ]";
180   }
181 
182 protected:
183   Distance _maxDistance = Distance.infinity();
184   Delta _maxError = Delta.zero();
185   S2Region _region = null;
186   int _maxPoints = MAX_MAX_POINTS;
187   bool _useBruteForce = false;
188 }
189 
190 /**
191  * S2ClosestPointQueryBase is a templatized class for finding the closest
192  * point(s) to a given target.  It is not intended to be used directly, but
193  * rather to serve as the implementation of various specialized classes with
194  * more convenient APIs (such as S2ClosestPointQuery).  It is flexible enough
195  * so that it can be adapted to compute maximum distances and even potentially
196  * Hausdorff distances.
197  *
198  * By using the appropriate options, this class can answer questions such as:
199  *
200  *  - Find the minimum distance between a point collection A and a target B.
201  *  - Find all points in collection A that are within a distance D of target B.
202  *  - Find the k points of collection A that are closest to a given point P.
203  *
204  * The target is any class that implements the S2DistanceTarget interface.
205  * There are predefined targets for points, edges, S2Cells, and S2ShapeIndexes
206  * (arbitrary collctions of points, polylines, and polygons).
207  *
208  * The Distance template argument is used to represent distances.  Usually
209  * this type is a thin wrapper around S1ChordAngle, but another distance type
210  * may be substituted as long as it implements the API below.  This can be
211  * used to change the comparison function (e.g., to find the furthest edges
212  * from the target), or to get more accuracy if desired.
213  *
214  * The Distance concept is as follows:
215  *
216  * class Distance {
217  *  public:
218  *   // Default and copy constructors, assignment operator:
219  *   Distance();
220  *   Distance(const Distance&);
221  *   Distance& operator=(const Distance&);
222  *
223  *   // Factory methods:
224  *   static Distance Zero();      // Returns a zero distance.
225  *   static Distance Infinity();  // Larger than any valid distance.
226  *   static Distance Negative();  // Smaller than any valid distance.
227  *
228  *   // Comparison operators:
229  *   friend bool operator==(Distance x, Distance y);
230  *   friend bool operator<(Distance x, Distance y);
231  *
232  *   // Delta represents the positive difference between two distances.
233  *   // It is used together with operator-() to implement Options::max_error().
234  *   // Typically Distance::Delta is simply S1ChordAngle.
235  *   class Delta {
236  *    public:
237  *     Delta();
238  *     Delta(const Delta&);
239  *     Delta& operator=(const Delta&);
240  *     friend bool operator==(Delta x, Delta y);
241  *     static Delta Zero();
242  *   };
243  *
244  *   // Subtraction operator.  Note that the second argument represents a
245  *   // delta between two distances.  This distinction is important for
246  *   // classes that compute maximum distances (e.g., S2FurthestEdgeQuery).
247  *   friend Distance operator-(Distance x, Delta delta);
248  *
249  *   // Method that returns an upper bound on the S1ChordAngle corresponding
250  *   // to this Distance (needed to implement Options::max_distance
251  *   // efficiently).  For example, if Distance measures WGS84 ellipsoid
252  *   // distance then the corresponding angle needs to be 0.56% larger.
253  *   S1ChordAngle GetChordAngleBound() const;
254  * };
255  */
256 class S2ClosestPointQueryBase(Distance, Data) {
257 public:
258   alias Delta = Distance.Delta;
259   alias Index = S2PointIndex!Data;
260   alias PointData = Index.PointData;
261   alias Options = S2ClosestPointQueryBaseOptions!Distance;
262 
263   /**
264    * The Target class represents the geometry to which the distance is
265    * measured.  For example, there can be subtypes for measuring the distance
266    * to a point, an edge, or to an S2ShapeIndex (an arbitrary collection of
267    * geometry).
268    *
269    * Implementations do *not* need to be thread-safe.  They may cache data or
270    * allocate temporary data structures in order to improve performance.
271    */
272   alias Target = S2DistanceTarget!Distance;
273 
274   /// Each "Result" object represents a closest point.
275   struct Result {
276   public:
277     /// Constructs a Result object for the given point.
278     this(Distance distance, PointData point_data) {
279       _distance = distance;
280       _pointData = point_data;
281     }
282 
283     /**
284      * Returns true if this Result object does not refer to any data point.
285      * (The only case where an empty Result is returned is when the
286      * FindClosestPoint() method does not find any points that meet the
287      * specified criteria.)
288      */
289     bool isEmpty() const {
290       return _pointData is null;
291     }
292 
293     /// The distance from the target to this point.
294     Distance distance() const {
295       return _distance;
296     }
297 
298     /// The point itself.
299     const(S2Point) point() const {
300       return _pointData.point();
301     }
302 
303     /// The client-specified data associated with this point.
304     const(Data) data() const {
305       return _pointData.data();
306     }
307 
308     /// Returns true if two Result objects are identical.
309     bool opEquals(in Result y) const {
310       return _distance == y._distance && _pointData == y._pointData;
311     }
312 
313     /// Compares two Result objects first by distance, then by point_data().
314     int opCmp(in Result y) const {
315       if (_distance < y._distance) return -1;
316       if (_distance > y._distance) return 1;
317       if (_pointData < y._pointData) return -1;
318       if (_pointData > y._pointData) return 1;
319       return 0;
320     }
321 
322   private:
323     Distance _distance = Distance.infinity();
324     PointData _pointData = null;
325   }
326 
327   /**
328    * The minimum number of points that a cell must contain to enqueue it
329    * rather than processing its contents immediately.
330    */
331   enum int MIN_POINTS_TO_ENQUEUE = 13;
332 
333   /// Default constructor; requires initialize() to be called.
334   this() {
335     _queue = CellQueue(new QueueEntry[0]);
336     _resultSet = BinaryHeap!(Result[])(new Result[0]);
337   }
338 
339   /// Convenience constructor that calls Init().
340   this(Index index) {
341     this();
342     initialize(index);
343   }
344 
345   /**
346    * Initializes the query.
347    * REQUIRES: ReInit() must be called if "index" is modified.
348    */
349   void initialize(S2PointIndex!Data index) {
350     _index = index;
351     reInitialize();
352   }
353 
354   /**
355    * Reinitializes the query.  This method must be called whenever the
356    * underlying index is modified.
357    */
358   void reInitialize() {
359     _iter.initialize(_index);
360     _indexCovering.length = 0;
361   }
362 
363   /// Return a reference to the underlying S2PointIndex.
364   const(Index) index() const {
365     return _index;
366   }
367 
368   /**
369    * Returns the closest points to the given target that satisfy the given
370    * options.  This method may be called multiple times.
371    */
372   Result[] findClosestPoints(Target target, Options options) {
373     Result[] results;
374     findClosestPoints(target, options, results);
375     return results;
376   }
377 
378   /**
379    * This version can be more efficient when this method is called many times,
380    * since it does not require allocating a new vector on each call.
381    */
382   void findClosestPoints(Target target, Options options, ref Result[] results) {
383     findClosestPointsInternal(target, options);
384     results.length = 0;
385     if (options.maxPoints() == 1) {
386       if (!_resultSingleton.isEmpty()) {
387         results ~= _resultSingleton;
388       }
389     } else if (options.maxPoints() == Options.MAX_MAX_POINTS) {
390       sort(_resultVector);
391       results = _resultVector.uniq.array;
392       _resultVector.length = 0;
393     } else {
394       results.reserve(_resultSet.length());
395       for (; !_resultSet.empty(); _resultSet.popFront()) {
396         results ~= _resultSet.front();  // The highest-priority result.
397       }
398       // The priority queue returns the largest elements first.
399       reverse(results);
400       enforce(isSorted(results));
401     }
402   }
403 
404   /**
405    * Convenience method that returns exactly one point.  If no points satisfy
406    * the given search criteria, then a Result with distance() == Infinity()
407    * and is_empty() == true is returned.
408    *
409    * REQUIRES: options.max_points() == 1
410    */
411   Result findClosestPoint(Target target, Options options)
412   in {
413     assert(options.maxPoints() == 1);
414   } do {
415     findClosestPointsInternal(target, options);
416     return _resultSingleton;
417   }
418 
419 private:
420   alias Iterator = Index.Iterator;
421 
422   inout(Options) options() inout {
423     return _options;
424   }
425 
426   void findClosestPointsInternal(Target target, Options options) {
427     _target = target;
428     _options = options;
429 
430     _distanceLimit = options.maxDistance();
431     _resultSingleton = Result();
432     enforce(_resultVector.empty());
433     enforce(_resultSet.empty());
434     enforce(target.maxBruteForceIndexSize() >= 0);
435     if (_distanceLimit == Distance.zero()) return;
436 
437     if (options.maxPoints() == Options.MAX_MAX_POINTS
438         && options.maxDistance() == Distance.infinity()
439         && options.region() is null) {
440       logger.logWarn("Returning all points (max_points/max_distance/region not set)");
441     }
442 
443     // Note that given point is processed only once (unlike S2ClosestEdgeQuery),
444     // and therefore we don't need to worry about the possibility of having
445     // duplicate points in the results.
446     if (options.maxError() != Delta.zero()) {
447       _target.setMaxError(options.maxError());
448     }
449     if (options.useBruteForce() || _index.numPoints() <= _target.maxBruteForceIndexSize()) {
450       findClosestPointsBruteForce();
451     } else {
452       findClosestPointsOptimized();
453     }
454   }
455 
456   void findClosestPointsBruteForce() {
457     for (_iter.begin(); !_iter.done(); _iter.next()) {
458       maybeAddResult(_iter.pointData());
459     }
460   }
461 
462   void findClosestPointsOptimized() {
463     initQueue();
464     while (!_queue.empty()) {
465       // We need to copy the top entry before removing it, and we need to remove
466       // it before adding any new entries to the queue.
467       QueueEntry entry = _queue.front();
468       _queue.popFront();
469       // Work around weird parse error in gcc 4.9 by using a local variable for
470       // entry.distance.
471       Distance distance = entry.distance;
472       if (!(distance < _distanceLimit)) {
473         _queue.clear();  // Clear any remaining entries.
474         break;
475       }
476       S2CellId child = entry.id.childBegin();
477       // We already know that it has too many points, so process its children.
478       // Each child may either be processed directly or enqueued again.  The
479       // loop is optimized so that we don't seek unnecessarily.
480       bool seek = true;
481       for (int i = 0; i < 4; ++i, child = child.next()) {
482         seek = enqueueCell(child, _iter, seek);
483       }
484     }
485   }
486 
487   void initQueue()
488   in {
489     assert(_queue.empty());
490   } do {
491     // Optimization: rather than starting with the entire index, see if we can
492     // limit the search region to a small disc.  Then we can find a covering for
493     // that disc and intersect it with the covering for the index.  This can
494     // save a lot of work when the search region is small.
495     S2Cap cap = _target.getCapBound();
496     if (options().maxPoints() == 1) {
497       // If the user is searching for just the closest point, we can compute an
498       // upper bound on search radius by seeking to the target point in the
499       // index and looking at the adjacent index points (in S2CellId order).
500       // The minimum distance to either of these points is an upper bound on the
501       // search radius.
502       //
503       // TODO(ericv): The same strategy would also work for small values of
504       // max_points() > 1, e.g. max_points() == 20, except that we would need to
505       // examine more neighbors (at least 20, and preferably 20 in each
506       // direction).  It's not clear whether this is a common case, though, and
507       // also this would require extending MaybeAddResult() so that it can
508       // remove duplicate entries.  (The points added here may be re-added by
509       // EnqueueCell(), but this is okay when max_points() == 1.)
510       _iter.seek(S2CellId(cap.center()));
511       if (!_iter.done()) {
512         maybeAddResult(_iter.pointData());
513       }
514       if (_iter.prev()) {
515         maybeAddResult(_iter.pointData());
516       }
517       // Skip the rest of the algorithm if we found a matching point.
518       if (_distanceLimit == Distance.zero()) return;
519     }
520     // We start with a covering of the set of indexed points, then intersect it
521     // with the given region (if any) and maximum search radius disc (if any).
522     if (_indexCovering.empty()) initCovering();
523     const(S2CellId)[] initial_cells = _indexCovering;
524     if (options().region()) {
525       auto coverer = new S2RegionCoverer();
526       coverer.mutableOptions().setMaxCells(4);
527       coverer.getCovering(options().region(), _regionCovering);
528       S2CellUnion.getIntersection(_indexCovering, _regionCovering, _intersectionWithRegion);
529       initial_cells = _intersectionWithRegion;
530     }
531     if (_distanceLimit < Distance.infinity()) {
532       auto coverer = new S2RegionCoverer();
533       coverer.mutableOptions().setMaxCells(4);
534       S1ChordAngle radius = cap.radius() + _distanceLimit.getChordAngleBound();
535       auto search_cap = new S2Cap(cap.center(), radius);
536       coverer.getFastCovering(search_cap, _maxDistanceCovering);
537       S2CellUnion.getIntersection(
538           initial_cells, _maxDistanceCovering, _intersectionWithMaxDistance);
539       initial_cells = _intersectionWithMaxDistance;
540     }
541     _iter.begin();
542     for (int i = 0; i < initial_cells.length && !_iter.done(); ++i) {
543       S2CellId id = initial_cells[i];
544       enqueueCell(id, _iter, id.rangeMin() > _iter.id() /*seek*/);
545     }
546   }
547 
548   void initCovering() {
549     // Compute the "index covering", which is a small number of S2CellIds that
550     // cover the indexed points.  There are two cases:
551     //
552     //  - If the index spans more than one face, then there is one covering cell
553     // per spanned face, just big enough to cover the index cells on that face.
554     //
555     //  - If the index spans only one face, then we find the smallest cell "C"
556     // that covers the index cells on that face (just like the case above).
557     // Then for each of the 4 children of "C", if the child contains any index
558     // cells then we create a covering cell that is big enough to just fit
559     // those index cells (i.e., shrinking the child as much as possible to fit
560     // its contents).  This essentially replicates what would happen if we
561     // started with "C" as the covering cell, since "C" would immediately be
562     // split, except that we take the time to prune the children further since
563     // this will save work on every subsequent query.
564     _indexCovering.reserve(6);
565     _iter.finish();
566     if (!_iter.prev()) return;  // Empty index.
567     S2CellId index_last_id = _iter.id();
568     _iter.begin();
569     if (_iter.id() != index_last_id) {
570       // The index has at least two cells.  Choose a level such that the entire
571       // index can be spanned with at most 6 cells (if the index spans multiple
572       // faces) or 4 cells (it the index spans a single face).
573       int level = _iter.id().getCommonAncestorLevel(index_last_id) + 1;
574 
575       // Visit each potential covering cell except the last (handled below).
576       S2CellId last_id = index_last_id.parent(level);
577       for (S2CellId id = _iter.id().parent(level); id != last_id; id = id.next()) {
578         // Skip any covering cells that don't contain any index cells.
579         if (id.rangeMax() < _iter.id()) continue;
580 
581         // Find the range of index cells contained by this covering cell and
582         // then shrink the cell if necessary so that it just covers them.
583         S2CellId cell_first_id = _iter.id();
584         _iter.seek(id.rangeMax().next());
585         _iter.prev();
586         S2CellId cell_last_id = _iter.id();
587         _iter.next();
588         addInitialRange(cell_first_id, cell_last_id);
589       }
590     }
591     addInitialRange(_iter.id(), index_last_id);
592   }
593 
594   /**
595    * Adds a cell to index_covering_ that covers the given inclusive range.
596    *
597    * REQUIRES: "first" and "last" have a common ancestor.
598    */
599   void addInitialRange(S2CellId first_id, S2CellId last_id) {
600     // Add the lowest common ancestor of the given range.
601     int level = first_id.getCommonAncestorLevel(last_id);
602     debug enforce(level >= 0);
603     _indexCovering ~= first_id.parent(level);
604   }
605 
606   void maybeAddResult(PointData point_data) {
607     Distance distance = _distanceLimit;
608     if (!_target.updateMinDistance(point_data.point(), distance)) return;
609     S2Region region = options().region();
610     if (region && !region.contains(point_data.point())) return;
611 
612     auto result = Result(distance, point_data);
613     if (options().maxPoints() == 1) {
614       // Optimization for the common case where only the closest point is wanted.
615       _resultSingleton = result;
616       _distanceLimit = result.distance() - options().maxError();
617     } else if (options().maxPoints() == Options.MAX_MAX_POINTS) {
618       _resultVector ~= result;  // Sort/unique at end.
619     } else {
620       // Add this point to result_set_.  Note that with the current algorithm
621       // each candidate point is considered at most once (except for one special
622       // case where max_points() == 1, see InitQueue for details), so we don't
623       // need to worry about possibly adding a duplicate entry here.
624       if (_resultSet.length() >= options().maxPoints()) {
625         _resultSet.popFront();  // Replace the furthest result point.
626       }
627       _resultSet.insert(result);
628       if (_resultSet.length() >= options().maxPoints()) {
629         _distanceLimit = _resultSet.front().distance() - options().maxError();
630       }
631     }
632   }
633 
634   /**
635    * Either process the contents of the given cell immediately, or add it to the
636    * queue to be subdivided.  If "seek" is false, then "iter" must already be
637    * positioned at the first indexed point within this cell.
638    *
639    * Returns "true" if the cell was added to the queue, and "false" if it was
640    * processed immediately, in which case "iter" is left positioned at the next
641    * cell in S2CellId order.
642    */
643   bool enqueueCell(S2CellId id, ref Iterator iter, bool seek) {
644     if (seek) iter.seek(id.rangeMin());
645     if (id.isLeaf()) {
646       // Leaf cells can't be subdivided.
647       for (; !iter.done() && iter.id() == id; iter.next()) {
648         maybeAddResult(iter.pointData());
649       }
650       return false;  // No need to seek to next child.
651     }
652     S2CellId last = id.rangeMax();
653     int num_points = 0;
654     for (; !iter.done() && iter.id() <= last; iter.next()) {
655       if (num_points == MIN_POINTS_TO_ENQUEUE - 1) {
656         // This cell has too many points (including this one), so enqueue it.
657         auto cell = new S2Cell(id);
658         Distance distance = _distanceLimit;
659         // We check "region_" second because it may be relatively expensive.
660         if (_target.updateMinDistance(cell, distance)
661             && (!options().region() || options().region().mayIntersect(cell))) {
662           _queue.insert(QueueEntry(distance, id));
663         }
664         return true;  // Seek to next child.
665       }
666       _tmpPointData[num_points++] = iter.pointData();
667     }
668     // There were few enough points that we might as well process them now.
669     for (int i = 0; i < num_points; ++i) {
670       maybeAddResult(_tmpPointData[i]);
671     }
672     return false;  // No need to seek to next child.
673   }
674 
675   /// The maximum number of points to process without subdividing further.
676   enum int MAX_LEAF_POINTS = 12;
677 
678   Index _index;
679   Options _options;
680   Target _target;
681 
682   /**
683    * For the optimized algorihm we precompute the top-level S2CellIds that
684    * will be added to the priority queue.  There can be at most 6 of these
685    * cells.  Essentially this is just a covering of the indexed points.
686    */
687   S2CellId[] _indexCovering;
688 
689   /**
690    * The distance beyond which we can safely ignore further candidate points.
691    * (Candidates that are exactly at the limit are ignored; this is more
692    * efficient for UpdateMinDistance() and should not affect clients since
693    * distance measurements have a small amount of error anyway.)
694    *
695    * Initially this is the same as the maximum distance specified by the user,
696    * but it can also be updated by the algorithm (see MaybeAddResult).
697    */
698   Distance _distanceLimit;
699 
700   /**
701    * The current result set is stored in one of three ways:
702    *
703    *  - If max_points() == 1, the best result is kept in result_singleton_.
704    *
705    *  - If max_points() == "infinity", results are appended to result_vector_
706    *    and sorted/uniqued at the end.
707    *
708    *  - Otherwise results are kept in a priority queue so that we can
709    *    progressively reduce the distance limit once max_points() results have
710    *    been found.
711    */
712   Result _resultSingleton;
713   Result[] _resultVector;
714 
715   /// Used as a priority queue for the results.
716   BinaryHeap!(Result[]) _resultSet;
717 
718   /**
719    * The algorithm maintains a priority queue of unprocessed S2CellIds, sorted
720    * in increasing order of distance from the target point.
721    */
722   struct QueueEntry {
723     /// A lower bound on the distance from the target point to any point
724     /// within "id".  This is the key of the priority queue.
725     Distance distance;
726 
727     /// The cell being queued.
728     S2CellId id;
729 
730     /// The priority queue returns the largest elements first, so we want the
731     /// "largest" entry to have the smallest distance.
732     int opCmp(in QueueEntry other) const {
733       if (distance > other.distance)
734         return -1;
735       else if (distance < other.distance)
736         return 1;
737       return 0;
738     }
739   }
740 
741   alias CellQueue = BinaryHeap!(QueueEntry[]);
742   CellQueue _queue;
743 
744   // Temporaries, defined here to avoid multiple allocations / initializations.
745   Iterator _iter;
746   S2CellId[] _regionCovering;
747   S2CellId[] _maxDistanceCovering;
748   S2CellId[] _intersectionWithRegion;
749   S2CellId[] _intersectionWithMaxDistance;
750   PointData[MIN_POINTS_TO_ENQUEUE - 1] _tmpPointData;
751 }