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 module s2.s2closest_edge_query_base;
20 
21 import s2.logger;
22 import s2.s1angle;
23 import s2.s1chord_angle;
24 import s2.s2cap;
25 import s2.s2cell;
26 import s2.s2cell_id;
27 import s2.s2cell_union;
28 import s2.s2distance_target;
29 import s2.s2point;
30 import s2.s2region_coverer;
31 import s2.s2shape;
32 import s2.s2shape_index;
33 import s2.shapeutil.count_edges : countEdgesUpTo;
34 import s2.shapeutil.shape_edge_id;
35 import s2.util.container.btree;
36 
37 import std.algorithm;
38 import std.array;
39 import std.container.binaryheap;
40 import std.exception : enforce;
41 import std.range : retro;
42 
43 // S2ClosestEdgeQueryBase is a templatized class for finding the closest
44 // edge(s) between two geometries.  It is not intended to be used directly,
45 // but rather to serve as the implementation of various specialized classes
46 // with more convenient APIs (such as S2ClosestEdgeQuery).  It is flexible
47 // enough so that it can be adapted to compute maximum distances and even
48 // potentially Hausdorff distances.
49 //
50 // By using the appropriate options, this class can answer questions such as:
51 //
52 //  - Find the minimum distance between two geometries A and B.
53 //  - Find all edges of geometry A that are within a distance D of geometry B.
54 //  - Find the k edges of geometry A that are closest to a given point P.
55 //
56 // You can also specify whether polygons should include their interiors (i.e.,
57 // if a point is contained by a polygon, should the distance be zero or should
58 // it be measured to the polygon boundary?)
59 //
60 // The input geometries may consist of any number of points, polylines, and
61 // polygons (collectively referred to as "shapes").  Shapes do not need to be
62 // disjoint; they may overlap or intersect arbitrarily.  The implementation is
63 // designed to be fast for both simple and complex geometries.
64 //
65 // The Distance template argument is used to represent distances.  Usually
66 // this type is a thin wrapper around S1ChordAngle, but another distance type
67 // may be substituted as long as it implements the API below.  This can be
68 // used to change the comparison function (e.g., to find the furthest edges
69 // from the target), or to get more accuracy if desired.
70 //
71 // The Distance concept is as follows:
72 //
73 // class Distance {
74 //  public:
75 //   // Default and copy constructors, assignment operator:
76 //   Distance();
77 //   Distance(const Distance&);
78 //   Distance& operator=(const Distance&);
79 //
80 //   // Factory methods:
81 //   static Distance Zero();      // Returns a zero distance.
82 //   static Distance Infinity();  // Larger than any valid distance.
83 //   static Distance Negative();  // Smaller than any valid distance.
84 //
85 //   // Comparison operators:
86 //   friend bool operator==(Distance x, Distance y);
87 //   friend bool operator<(Distance x, Distance y);
88 //
89 //   // Delta represents the positive difference between two distances.
90 //   // It is used together with operator-() to implement Options::max_error().
91 //   // Typically Distance::Delta is simply S1ChordAngle.
92 //   class Delta {
93 //    public:
94 //     Delta();
95 //     Delta(const Delta&);
96 //     Delta& operator=(const Delta&);
97 //     friend bool operator==(Delta x, Delta y);
98 //     static Delta Zero();
99 //   };
100 //
101 //   // Subtraction operator.  Note that the second argument represents a
102 //   // delta between two distances.  This distinction is important for
103 //   // classes that compute maximum distances (e.g., S2FurthestEdgeQuery).
104 //   friend Distance operator-(Distance x, Delta delta);
105 //
106 //   // Method that returns an upper bound on the S1ChordAngle corresponding
107 //   // to this Distance (needed to implement Options::max_distance
108 //   // efficiently).  For example, if Distance measures WGS84 ellipsoid
109 //   // distance then the corresponding angle needs to be 0.56% larger.
110 //   S1ChordAngle GetChordAngleBound() const;
111 // };
112 class S2ClosestEdgeQueryBase(DistanceT) {
113 public:
114   alias Delta = DistanceT.Delta;
115 
116   // Options that control the set of edges returned.  Note that by default
117   // *all* edges are returned, so you will always want to set either the
118   // max_edges() option or the max_distance() option (or both).
119   static class Options {
120   public:
121     this() { }
122 
123     this(Options options) {
124       _maxDistance = options._maxDistance;
125       _maxError = options._maxError;
126       _maxEdges = options._maxEdges;
127       _includeInteriors = options._includeInteriors;
128       _useBruteForce = options._useBruteForce;
129     }
130 
131     // Specifies that at most "max_edges" edges should be returned.
132     //
133     // REQUIRES: max_edges >= 1
134     // DEFAULT: numeric_limits<int>::max()
135     int maxEdges() const {
136       return _maxEdges;
137     }
138 
139     void setMaxEdges(int max_edges)
140     in {
141       assert(max_edges >= 1);
142     } do {
143       _maxEdges = max_edges;
144     }
145 
146     enum int MAX_MAX_EDGES = int.max;
147 
148     // Specifies that only edges whose distance to the target is less than
149     // "max_distance" should be returned.
150     //
151     // Note that edges whose distance is exactly equal to "max_distance" are
152     // not returned.  In most cases this doesn't matter (since distances are
153     // not computed exactly in the first place), but if such edges are needed
154     // then you can retrieve them by specifying "max_distance" as the next
155     // largest representable DistanceT.  For example, if DistanceT is an
156     // S1ChordAngle then you can specify max_distance.Successor().
157     //
158     // DEFAULT: DistanceT::Infinity()
159     DistanceT maxDistance() const {
160       return _maxDistance;
161     }
162 
163     void setMaxDistance(DistanceT max_distance) {
164       _maxDistance = max_distance;
165     }
166 
167     // Specifies that edges up to max_error() further away than the true
168     // closest edges may be substituted in the result set, as long as such
169     // edges satisfy all the remaining search criteria (such as max_distance).
170     // This option only has an effect if max_edges() is also specified;
171     // otherwise all edges closer than max_distance() will always be returned.
172     //
173     // Note that this does not affect how the distance between edges is
174     // computed; it simply gives the algorithm permission to stop the search
175     // early as soon as the best possible improvement drops below max_error().
176     //
177     // This can be used to implement distance predicates efficiently.  For
178     // example, to determine whether the minimum distance is less than D, set
179     // max_edges() == 1 and max_distance() == max_error() == D.  This causes
180     // the algorithm to terminate as soon as it finds any edge whose distance
181     // is less than D, rather than continuing to search for an edge that is
182     // even closer.
183     //
184     // DEFAULT: DistanceT::Delta::Zero()
185     Delta maxError() const {
186       return _maxError;
187     }
188 
189     void setMaxError(Delta max_error) {
190       _maxError = max_error;
191     }
192 
193     // Specifies that polygon interiors should be included when measuring
194     // distances.  In other words, polygons that contain the target should
195     // have a distance of zero.  (For targets consisting of multiple connected
196     // components, the distance is zero if any component is contained.)  This
197     // is indicated in the results by returning a (shape_id, edge_id) pair
198     // with edge_id == -1, i.e. this value denotes the polygons's interior.
199     //
200     // Note that for efficiency, any polygon that intersects the target may or
201     // may not have an (edge_id == -1) result.  Such results are optional
202     // because in that case the distance to the polygon is already zero.
203     //
204     // DEFAULT: true
205     bool includeInteriors() const {
206       return _includeInteriors;
207     }
208 
209     void setIncludeInteriors(bool include_interiors) {
210       _includeInteriors = include_interiors;
211     }
212 
213     // Specifies that distances should be computed by examining every edge
214     // rather than using the S2ShapeIndex.  This is useful for testing,
215     // benchmarking, and debugging.
216     //
217     // DEFAULT: false
218     bool useBruteForce() const {
219       return _useBruteForce;
220     }
221 
222     void setUseBruteForce(bool use_brute_force) {
223       _useBruteForce = use_brute_force;
224     }
225 
226   private:
227     DistanceT _maxDistance = DistanceT.infinity;
228     Delta _maxError = Delta.zero;
229     int _maxEdges = MAX_MAX_EDGES;
230     bool _includeInteriors = true;
231     bool _useBruteForce = false;
232   }
233 
234   // The Target class represents the geometry to which the distance is
235   // measured.  For example, there can be subtypes for measuring the distance
236   // to a point, an edge, or to an S2ShapeIndex (an arbitrary collection of
237   // geometry).
238   //
239   // Implementations do *not* need to be thread-safe.  They may cache data or
240   // allocate temporary data structures in order to improve performance.
241   alias Target = S2DistanceTarget!DistanceT;
242 
243   // Each "Result" object represents a closest edge.  Note the following
244   // special cases:
245   //
246   //  - (shape_id >= 0) && (edge_id < 0) represents the interior of a shape.
247   //    Such results may be returned when options.include_interiors() is true.
248   //
249   //  - (shape_id < 0) && (edge_id < 0) is returned by `FindClosestEdge` to
250   //    indicate that no edge satisfies the requested query options.
251   //
252   // TODO(ericv): Convert to a class with accessor methods.
253   struct Result {
254     DistanceT distance = DistanceT.infinity;  // The distance from the target to this edge.
255     int shapeId = -1;     // Identifies an indexed shape.
256     int edgeId = -1;      // Identifies an edge within the shape.
257 
258     // Compares edges first by distance, then by (shape_id, edge_id).
259     int opCmp(in Result o) const {
260       if (distance != o.distance) return distance.opCmp(o.distance);
261       if (shapeId != o.shapeId) return shapeId - o.shapeId;
262       if (edgeId != o.edgeId) return edgeId - o.edgeId;
263       return 0;
264     }
265   }
266 
267   // Default constructor; requires Init() to be called.
268   this() {
269     _iter = new S2ShapeIndex.Iterator();
270     _resultSet = new BTree!Result();
271     _queue = BinaryHeap!(QueueEntry[])([]);
272   }
273 
274   // Convenience constructor that calls Init().
275   this(S2ShapeIndex index) {
276     this();
277     initialize(index);
278   }
279 
280   // Initializes the query.
281   // REQUIRES: ReInit() must be called if "index" is modified.
282   void initialize(S2ShapeIndex index) {
283     _index = index;
284     reInitialize();
285   }
286 
287   // Reinitializes the query.  This method must be called whenever the
288   // underlying index is modified.
289   void reInitialize() {
290     _indexNumEdges = 0;
291     _indexNumEdgesLimit = 0;
292     _indexCovering.length = 0;
293     _indexCells.length = 0;
294     // We don't initialize iter_ here to make queries on small indexes a bit
295     // faster (i.e., where brute force is used).
296   }
297 
298   // Returns a reference to the underlying S2ShapeIndex.
299   const(S2ShapeIndex) index() const {
300     return _index;
301   }
302 
303   // Returns the closest edges to the given target that satisfy the given
304   // options.  This method may be called multiple times.
305   //
306   // Note that if options().include_interiors() is true, the result vector may
307   // include some entries with edge_id == -1.  This indicates that the target
308   // intersects the indexed polygon with the given shape_id.
309   Result[] findClosestEdges(Target target, Options options) {
310     import std.stdio;
311     Result[] results;
312     findClosestEdges(target, options, results);
313     return results;
314   }
315 
316   // This version can be more efficient when this method is called many times,
317   // since it does not require allocating a new vector on each call.
318   void findClosestEdges(Target target, Options options, out Result[] results) {
319     findClosestEdgesInternal(target, options);
320     if (options.maxEdges() == 1) {
321       if (_resultSingleton.shapeId >= 0) {
322         results ~= _resultSingleton;
323       }
324     } else if (options.maxEdges() == Options.MAX_MAX_EDGES) {
325       results ~= _resultVector;
326       _resultVector.length = 0;
327     } else {
328       results = _resultSet[].array;
329       _resultSet.clear();
330     }
331   }
332 
333   // Convenience method that returns exactly one edge.  If no edges satisfy
334   // the given search criteria, then a Result with distance == Infinity() and
335   // shape_id == edge_id == -1 is returned.
336   //
337   // Note that if options.include_interiors() is true, edge_id == -1 is also
338   // used to indicate that the target intersects an indexed polygon (but in
339   // that case distance == Zero() and shape_id >= 0).
340   //
341   // REQUIRES: options.max_edges() == 1
342   Result findClosestEdge(Target target, Options options)
343   in {
344     assert(options.maxEdges() == 1);
345   } do {
346     findClosestEdgesInternal(target, options);
347     return _resultSingleton;
348   }
349 
350  private:
351 
352   const(Options) options() const {
353     return _options;
354   }
355 
356   void findClosestEdgesInternal(Target target, Options options)
357   in {
358     assert(_resultVector.empty());
359     assert(_resultSet.empty());
360     assert(target.maxBruteForceIndexSize() >= 0);
361   } do {
362     _target = target;
363     _options = options;
364 
365     _testedEdges.clear();
366     _distanceLimit = options.maxDistance();
367     _resultSingleton = Result();
368 
369     if (_distanceLimit == DistanceT.zero()) return;
370 
371     if (options.maxEdges() == Options.MAX_MAX_EDGES
372         && options.maxDistance() == DistanceT.infinity()) {
373       logger.logWarn("Returning all edges (max_edges/max_distance not set)");
374     }
375 
376     if (options.includeInteriors()) {
377       auto shape_ids = new BTree!int();
378       target.visitContainingShapes(
379           _index,
380           (in S2Shape containing_shape, in S2Point target_point) {
381             shape_ids.insert(containing_shape.id());
382             return shape_ids.length < options.maxEdges();
383           });
384       foreach (int shape_id; shape_ids) {
385         addResult(Result(DistanceT.zero(), shape_id, -1));
386       }
387       if (_distanceLimit == DistanceT.zero()) return;
388     }
389 
390     // If max_error() > 0 and the target takes advantage of this, then we may
391     // need to adjust the distance estimates to S2ShapeIndex cells to ensure
392     // that they are always a lower bound on the true distance.  For example,
393     // suppose max_distance == 100, max_error == 30, and we compute the distance
394     // to the target from some cell C0 as d(C0) == 80.  Then because the target
395     // takes advantage of max_error(), the true distance could be as low as 50.
396     // In order not to miss edges contained by such cells, we need to subtract
397     // max_error() from the distance estimates.  This behavior is controlled by
398     // the use_conservative_cell_distance_ flag.
399     //
400     // However there is one important case where this adjustment is not
401     // necessary, namely when max_distance() < max_error().  This is because
402     // max_error() only affects the algorithm once at least max_edges() edges
403     // have been found that satisfy the given distance limit.  At that point,
404     // max_error() is subtracted from distance_limit_ in order to ensure that
405     // any further matches are closer by at least that amount.  But when
406     // max_distance() < max_error(), this reduces the distance limit to 0,
407     // i.e. all remaining candidate cells and edges can safely be discarded.
408     // (Note that this is how IsDistanceLess() and friends are implemented.)
409     //
410     // Note that DistanceT::Delta only supports operator==.
411     bool target_uses_max_error = (!(options.maxError() == Delta.zero())
412         && _target.setMaxError(options.maxError()));
413 
414     // Note that we can't compare max_error() and distance_limit_ directly
415     // because one is a Delta and one is a DistanceT.  Instead we subtract them.
416     _useConservativeCellDistance = target_uses_max_error
417         && (_distanceLimit == DistanceT.infinity()
418             || DistanceT.zero() < _distanceLimit - options.maxError());
419 
420     // Use the brute force algorithm if the index is small enough.  To avoid
421     // spending too much time counting edges when there are many shapes, we stop
422     // counting once there are too many edges.  We may need to recount the edges
423     // if we later see a target with a larger brute force edge threshold.
424     int min_optimized_edges = _target.maxBruteForceIndexSize() + 1;
425     if (min_optimized_edges > _indexNumEdgesLimit && _indexNumEdges >= _indexNumEdgesLimit) {
426       _indexNumEdges = countEdgesUpTo(_index, min_optimized_edges);
427       _indexNumEdgesLimit = min_optimized_edges;
428     }
429 
430     if (options.useBruteForce() || _indexNumEdges < min_optimized_edges) {
431       // The brute force algorithm considers each edge exactly once.
432       _avoidDuplicates = false;
433       findClosestEdgesBruteForce();
434     } else {
435       // If the target takes advantage of max_error() then we need to avoid
436       // duplicate edges explicitly.  (Otherwise it happens automatically.)
437       _avoidDuplicates = (target_uses_max_error && options.maxEdges() > 1);
438       findClosestEdgesOptimized();
439     }
440   }
441 
442   void findClosestEdgesBruteForce() {
443     int num_shape_ids = _index.numShapeIds();
444     for (int id = 0; id < num_shape_ids; ++id) {
445       const(S2Shape) shape = _index.shape(id);
446       if (shape is null) continue;
447       int num_edges = shape.numEdges();
448       for (int e = 0; e < num_edges; ++e) {
449         maybeAddResult(shape, e);
450       }
451     }
452   }
453 
454   void findClosestEdgesOptimized() {
455     initQueue();
456     // Repeatedly find the closest S2Cell to "target" and either split it into
457     // its four children or process all of its edges.
458     while (!_queue.empty()) {
459       // We need to copy the top entry before removing it, and we need to
460       // remove it before adding any new entries to the queue.
461       QueueEntry entry = _queue.front();
462       _queue.popFront();
463       // Work around weird parse error in gcc 4.9 by using a local variable for
464       // entry.distance.
465       DistanceT distance = entry.distance;
466       if (!(distance < _distanceLimit)) {
467         _queue.clear();  // Clear any remaining entries.
468         break;
469       }
470       // If this is already known to be an index cell, just process it.
471       if (entry.indexCell !is null) {
472         processEdges(entry);
473         continue;
474       }
475       // Otherwise split the cell into its four children.  Before adding a
476       // child back to the queue, we first check whether it is empty.  We do
477       // this in two seek operations rather than four by seeking to the key
478       // between children 0 and 1 and to the key between children 2 and 3.
479       S2CellId id = entry.id;
480       _iter.seek(id.child(1).rangeMin());
481       if (!_iter.done() && _iter.id() <= id.child(1).rangeMax()) {
482         enqueueCurrentCell(id.child(1));
483       }
484       if (_iter.prev() && _iter.id() >= id.rangeMin()) {
485         enqueueCurrentCell(id.child(0));
486       }
487       _iter.seek(id.child(3).rangeMin());
488       if (!_iter.done() && _iter.id() <= id.rangeMax()) {
489         enqueueCurrentCell(id.child(3));
490       }
491       if (_iter.prev() && _iter.id() >= id.child(2).rangeMin()) {
492         enqueueCurrentCell(id.child(2));
493       }
494     }
495   }
496 
497   void initQueue()
498   in {
499     assert(_queue.empty());
500   } do {
501     if (_indexCovering.empty()) {
502       // We delay iterator initialization until now to make queries on very
503       // small indexes a bit faster (i.e., where brute force is used).
504       _iter.initialize(_index, S2ShapeIndex.InitialPosition.UNPOSITIONED);
505     }
506 
507     // Optimization: if the user is searching for just the closest edge, and the
508     // target happens to intersect an index cell, then we try to limit the search
509     // region to a small disc by first processing the edges in that cell.  This
510     // sets distance_limit_ based on the closest edge in that cell, which we can
511     // then use to limit the search area.  This means that the cell containing
512     // "target" will be processed twice, but in general this is still faster.
513     S2Cap cap = _target.getCapBound();
514     if (options().maxEdges() == 1 && _iter.locate(cap.center())) {
515       processEdges(new QueueEntry(DistanceT.zero(), _iter.id(), _iter.cell()));
516       // Skip the rest of the algorithm if we found an intersecting edge.
517       if (_distanceLimit == DistanceT.zero()) return;
518     }
519     if (_indexCovering.empty()) {
520       initCovering();
521     }
522     if (_distanceLimit == DistanceT.infinity()) {
523       // Start with the precomputed index covering.
524       for (int i = 0; i < _indexCovering.length; ++i) {
525         enqueueCell(_indexCovering[i], _indexCells[i]);
526       }
527     } else {
528       // Compute a covering of the search disc and intersect it with the
529       // precomputed index covering.
530       auto coverer = new S2RegionCoverer();
531       coverer.mutableOptions().setMaxCells(4);
532       S1ChordAngle radius = cap.radius() + _distanceLimit.getChordAngleBound();
533       auto search_cap = new S2Cap(cap.center(), radius);
534       coverer.getFastCovering(search_cap, _maxDistanceCovering);
535       S2CellUnion.getIntersection(_indexCovering, _maxDistanceCovering, _initialCells);
536 
537       // Now we need to clean up the initial cells to ensure that they all
538       // contain at least one cell of the S2ShapeIndex.  (Some may not intersect
539       // the index at all, while other may be descendants of an index cell.)
540       for (int i = 0, j = 0; i < _initialCells.length; ) {
541         S2CellId id_i = _initialCells[i];
542         // Find the top-level cell that contains this initial cell.
543         while (_indexCovering[j].rangeMax() < id_i) ++j;
544         S2CellId id_j = _indexCovering[j];
545         if (id_i == id_j) {
546           // This initial cell is one of the top-level cells.  Use the
547           // precomputed S2ShapeIndexCell pointer to avoid an index seek.
548           enqueueCell(id_j, _indexCells[j]);
549           ++i, ++j;
550         } else {
551           // This initial cell is a proper descendant of a top-level cell.
552           // Check how it is related to the cells of the S2ShapeIndex.
553           S2ShapeIndex.CellRelation r = _iter.locate(id_i);
554           if (r == S2ShapeIndex.CellRelation.INDEXED) {
555             // This cell is a descendant of an index cell.  Enqueue it and skip
556             // any other initial cells that are also descendants of this cell.
557             enqueueCell(_iter.id(), _iter.cell());
558             const S2CellId last_id = _iter.id().rangeMax();
559             while (++i < _initialCells.length && _initialCells[i] <= last_id)
560               continue;
561           } else {
562             // Enqueue the cell only if it contains at least one index cell.
563             if (r == S2ShapeIndex.CellRelation.SUBDIVIDED) enqueueCell(id_i, null);
564             ++i;
565           }
566         }
567       }
568     }
569   }
570 
571   void initCovering() {
572     // Find the range of S2Cells spanned by the index and choose a level such
573     // that the entire index can be covered with just a few cells.  These are
574     // the "top-level" cells.  There are two cases:
575     //
576     //  - If the index spans more than one face, then there is one top-level cell
577     // per spanned face, just big enough to cover the index cells on that face.
578     //
579     //  - If the index spans only one face, then we find the smallest cell "C"
580     // that covers the index cells on that face (just like the case above).
581     // Then for each of the 4 children of "C", if the child contains any index
582     // cells then we create a top-level cell that is big enough to just fit
583     // those index cells (i.e., shrinking the child as much as possible to fit
584     // its contents).  This essentially replicates what would happen if we
585     // started with "C" as the top-level cell, since "C" would immediately be
586     // split, except that we take the time to prune the children further since
587     // this will save work on every subsequent query.
588 
589     // Don't need to reserve index_cells_ since it is an InlinedVector.
590     _indexCovering.reserve(6);
591 
592     // TODO(ericv): Use a single iterator (iter_) below and save position
593     // information using pair<S2CellId, const S2ShapeIndexCell*> type.
594     auto next = new S2ShapeIndex.Iterator(_index, S2ShapeIndex.InitialPosition.BEGIN);
595     auto last = new S2ShapeIndex.Iterator(_index, S2ShapeIndex.InitialPosition.END);
596     last.prev();
597     if (next.id() != last.id()) {
598       // The index has at least two cells.  Choose a level such that the entire
599       // index can be spanned with at most 6 cells (if the index spans multiple
600       // faces) or 4 cells (it the index spans a single face).
601       int level = next.id().getCommonAncestorLevel(last.id()) + 1;
602       // Visit each potential top-level cell except the last (handled below).
603       S2CellId last_id = last.id().parent(level);
604       for (S2CellId id = next.id().parent(level); id != last_id; id = id.next()) {
605         // Skip any top-level cells that don't contain any index cells.
606         if (id.rangeMax() < next.id()) continue;
607 
608         // Find the range of index cells contained by this top-level cell and
609         // then shrink the cell if necessary so that it just covers them.
610         S2ShapeIndex.Iterator cell_first = new S2ShapeIndex.Iterator();
611         cell_first.copy(next);
612         next.seek(id.rangeMax().next());
613         S2ShapeIndex.Iterator cell_last = new S2ShapeIndex.Iterator();
614         cell_last.copy(next);
615         cell_last.prev();
616         addInitialRange(cell_first, cell_last);
617       }
618     }
619     addInitialRange(next, last);
620   }
621 
622   /**
623    * Add an entry to index_covering_ and index_cells_ that covers the given
624    * inclusive range of cells.
625    *
626    * REQUIRES: "first" and "last" have a common ancestor.
627    */
628   void addInitialRange(S2ShapeIndex.Iterator first, in S2ShapeIndex.Iterator last) {
629     if (first.id() == last.id()) {
630       // The range consists of a single index cell.
631       _indexCovering ~= first.id();
632       _indexCells ~= first.cell();
633     } else {
634       // Add the lowest common ancestor of the given range.
635       int level = first.id().getCommonAncestorLevel(last.id());
636       debug enforce(level >= 0);
637       _indexCovering ~= first.id().parent(level);
638       _indexCells ~= null;
639     }
640   }
641 
642   void maybeAddResult(in S2Shape shape, int edge_id) {
643     auto testEdge = ShapeEdgeId(shape.id(), edge_id);
644     if (_avoidDuplicates && testEdge !in _testedEdges) {
645       return;
646     }
647     _testedEdges[testEdge] = true;
648     auto edge = shape.edge(edge_id);
649     DistanceT distance = _distanceLimit;
650     if (_target.updateMinDistance(edge.v0, edge.v1, distance)) {
651       addResult(Result(distance, shape.id(), edge_id));
652     }
653   }
654 
655   void addResult(in Result result) {
656     if (options().maxEdges() == 1) {
657       // Optimization for the common case where only the closest edge is wanted.
658       _resultSingleton = result;
659       _distanceLimit = result.distance - options().maxError();
660     } else if (options().maxEdges() == Options.MAX_MAX_EDGES) {
661       _resultVector ~= result;  // Sort/unique at end.
662     } else {
663       // Add this edge to result_set_.  Note that even if we already have enough
664       // edges, we can't erase an element before insertion because the "new"
665       // edge might in fact be a duplicate.
666       _resultSet.insert(result);
667       int size = cast(int) _resultSet.length;
668       if (size >= options().maxEdges()) {
669         if (size > options().maxEdges()) {
670           _resultSet.remove(_resultSet.end().getValue());
671         }
672         _distanceLimit = _resultSet.end().getValue().distance - options().maxError();
673       }
674     }
675   }
676 
677   // Process all the edges of the given index cell.
678   void processEdges(in QueueEntry entry) {
679     const(S2ShapeIndexCell) index_cell = entry.indexCell;
680     for (int s = 0; s < index_cell.numClipped(); ++s) {
681       const(S2ClippedShape) clipped = index_cell.clipped(s);
682       S2Shape shape = _index.shape(clipped.shapeId());
683       for (int j = 0; j < clipped.numEdges(); ++j) {
684         maybeAddResult(shape, clipped.edge(j));
685       }
686     }
687   }
688 
689   // Add the given cell id to the queue.  "index_cell" is the corresponding
690   // S2ShapeIndexCell, or nullptr if "id" is not an index cell.
691   void enqueueCell(in S2CellId id, in S2ShapeIndexCell index_cell) {
692     if (index_cell) {
693       // If this index cell has only a few edges, then it is faster to check
694       // them directly rather than computing the minimum distance to the S2Cell
695       // and inserting it into the queue.
696       enum int kMinEdgesToEnqueue = 10;
697       int num_edges = countEdges(index_cell);
698       if (num_edges == 0) return;
699       if (num_edges < kMinEdgesToEnqueue) {
700         // Set "distance" to zero to avoid the expense of computing it.
701         processEdges(new QueueEntry(DistanceT.zero(), id, index_cell));
702         return;
703       }
704     }
705     // Otherwise compute the minimum distance to any point in the cell and add
706     // it to the priority queue.
707     auto cell = new S2Cell(id);
708     DistanceT distance = _distanceLimit;
709     if (!_target.updateMinDistance(cell, distance)) return;
710     if (_useConservativeCellDistance) {
711       // Ensure that "distance" is a lower bound on the true distance to the cell.
712       distance = distance - options().maxError();  // operator-=() not defined.
713     }
714     _queue.insert(new QueueEntry(distance, id, index_cell));
715   }
716 
717   // Enqueue the given cell id.
718   // REQUIRES: iter_ is positioned at a cell contained by "id".
719   void enqueueCurrentCell(S2CellId id)
720   in {
721     assert(id.contains(_iter.id()));
722   } do {
723     if (_iter.id() == id) {
724       enqueueCell(id, _iter.cell());
725     } else {
726       enqueueCell(id, null);
727     }
728   }
729 
730   // Return the number of edges in the given index cell.
731   static int countEdges(in S2ShapeIndexCell cell) {
732     int count = 0;
733     for (int s = 0; s < cell.numClipped(); ++s) {
734       count += cell.clipped(s).numEdges();
735     }
736     return count;
737   }
738 
739   S2ShapeIndex _index;
740   Options _options;
741   Target _target;
742 
743   // True if max_error() must be subtracted from S2ShapeIndex cell distances
744   // in order to ensure that such distances are measured conservatively.  This
745   // is true only if the target takes advantage of max_error() in order to
746   // return faster results, and 0 < max_error() < distance_limit_.
747   bool _useConservativeCellDistance;
748 
749   // For the optimized algorihm we precompute the top-level S2CellIds that
750   // will be added to the priority queue.  There can be at most 6 of these
751   // cells.  Essentially this is just a covering of the indexed edges, except
752   // that we also store pointers to the corresponding S2ShapeIndexCells to
753   // reduce the number of index seeks required.
754   //
755   // The covering needs to be stored in a std::vector so that we can use
756   // S2CellUnion::GetIntersection().
757   S2CellId[] _indexCovering;
758   S2ShapeIndexCell[] _indexCells;
759 
760   // The decision about whether to use the brute force algorithm is based on
761   // counting the total number of edges in the index.  However if the index
762   // contains a large number of shapes, this in itself might take too long.
763   // So instead we only count edges up to (max_brute_force_index_size() + 1)
764   // for the current target type (stored as index_num_edges_limit_).
765   int _indexNumEdges;
766   int _indexNumEdgesLimit;
767 
768   // The distance beyond which we can safely ignore further candidate edges.
769   // (Candidates that are exactly at the limit are ignored; this is more
770   // efficient for UpdateMinDistance() and should not affect clients since
771   // distance measurements have a small amount of error anyway.)
772   //
773   // Initially this is the same as the maximum distance specified by the user,
774   // but it can also be updated by the algorithm (see MaybeAddResult).
775   DistanceT _distanceLimit;
776 
777   // The current result set is stored in one of three ways:
778   //
779   //  - If max_edges() == 1, the best result is kept in result_singleton_.
780   //
781   //  - If max_edges() == "infinity", results are appended to result_vector_
782   //    and sorted/uniqued at the end.
783   //
784   //  - Otherwise results are kept in a btree_set so that we can progressively
785   //    reduce the distance limit once max_edges() results have been found.
786   //    (A priority queue is not sufficient because we need to be able to
787   //    check whether a candidate edge is already in the result set.)
788   Result _resultSingleton;
789   Result[] _resultVector;
790   BTree!Result _resultSet;
791 
792   // When the result edges are stored in a btree_set (see above), usually
793   // duplicates can be removed simply by inserting candidate edges in the
794   // current set.  However this is not true if Options::max_error() > 0 and
795   // the Target subtype takes advantage of this by returning suboptimal
796   // distances.  This is because when UpdateMinDistance() is called with
797   // different "min_dist" parameters (i.e., the distance to beat), the
798   // implementation may return a different distance for the same edge.  Since
799   // the btree_set is keyed by (distance, shape_id, edge_id) this can create
800   // duplicate edges in the results.
801   //
802   // The flag below is true when duplicates must be avoided explicitly.  This
803   // is achieved by maintaining a separate set keyed by (shape_id, edge_id)
804   // only, and checking whether each edge is in that set before computing the
805   // distance to it.
806   bool _avoidDuplicates;
807   bool[ShapeEdgeId] _testedEdges;
808 
809   // The algorithm maintains a priority queue of unprocessed S2CellIds, sorted
810   // in increasing order of distance from the target point.
811   class QueueEntry {
812     this(in DistanceT distance, in S2CellId id, in S2ShapeIndexCell indexCell) {
813       this.distance = distance;
814       this.id = id;
815       this.indexCell = indexCell;
816     }
817 
818     // A lower bound on the distance from the target point to any edge point
819     // within "id".  This is the key of the priority queue.
820     const(DistanceT) distance;
821 
822     // The cell being queued.
823     const(S2CellId) id;
824 
825     // If "id" belongs to the index, this field stores the corresponding
826     // S2ShapeIndexCell.  Otherwise "id" is a proper ancestor of one or more
827     // S2ShapeIndexCells and this field stores nullptr.  The purpose of this
828     // field is to avoid an extra Seek() when the queue entry is processed.
829     const(S2ShapeIndexCell) indexCell;
830 
831     int opCmp(in QueueEntry other) const {
832       // The priority queue returns the largest elements first, so we want the
833       // "largest" entry to have the smallest distance.
834       return other.distance.opCmp(distance);
835     }
836   }
837 
838   alias CellQueue = BinaryHeap!(QueueEntry[]);
839   CellQueue _queue;
840 
841   // Temporaries, defined here to avoid multiple allocations / initializations.
842 
843   S2ShapeIndex.Iterator _iter;
844   S2CellId[] _maxDistanceCovering;
845   S2CellId[] _initialCells;
846 }