1 /**
2    Allows arbitrary regions to be approximated as unions of cells (S2CellUnion).
3 
4    Copyright: 2005 Google Inc. All Rights Reserved.
5 
6    License:
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10 
11    $(LINK http://www.apache.org/licenses/LICENSE-2.0)
12 
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS-IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 
19    Authors: ericv@google.com (Eric Veach), madric@gmail.com (Vijay Nayar)
20 */
21 module s2.s2region_coverer;
22 
23 import s2.logger;
24 import s2.s2cell;
25 import s2.s2cell_id;
26 import s2.s2cell_union;
27 import s2.s2point;
28 import s2.s2region;
29 
30 import std.algorithm : isSorted, min, max;
31 import std.array : array;
32 import std.container : BinaryHeap, heapify;
33 import std.range : assumeSorted, chain, SortedRange, takeOne;
34 
35 /**
36    An S2RegionCoverer is a class that allows arbitrary regions to be
37    approximated as unions of cells (S2CellUnion).  This is useful for
38    implementing various sorts of search and precomputation operations.
39 
40    Typical usage:
41    ---
42    auto options = new S2RegionCoverer.Options();
43    options.setMaxCells(5);
44    auto coverer = new S2RegionCoverer(options);
45    auto cap = new S2Cap(center, radius);
46    S2CellUnion covering = coverer.getCovering(cap);
47    ---
48 
49    This yields a vector of at most 5 cells that is guaranteed to cover the
50    given cap (a disc-shaped region on the sphere).
51 
52    The approximation algorithm is not optimal but does a pretty good job in
53    practice.  The output does not always use the maximum number of cells
54    allowed, both because this would not always yield a better approximation,
55    and because max_cells() is a limit on how much work is done exploring the
56    possible covering as well as a limit on the final output size.
57 
58    Because it is an approximation algorithm, one should not rely on the
59    stability of the output.  In particular, the output of the covering algorithm
60    may change across different versions of the library.
61 
62    One can also generate interior coverings, which are sets of cells which
63    are entirely contained within a region.  Interior coverings can be
64    empty, even for non-empty regions, if there are no cells that satisfy
65    the provided constraints and are contained by the region.  Note that for
66    performance reasons, it is wise to specify a max_level when computing
67    interior coverings - otherwise for regions with small or zero area, the
68    algorithm may spend a lot of time subdividing cells all the way to leaf
69    level to try to find contained cells.
70 */
71 class S2RegionCoverer {
72 public:
73 
74   /// Options for the S2RegionCoverer.
75   static class Options {
76   public:
77     enum int DEFAULT_MAX_CELLS = 8;
78 
79     /**
80        Sets the desired maximum number of cells in the approximation.  Note
81        the following:
82 
83         - For any setting of max_cells(), up to 6 cells may be returned if
84           that is the minimum number required (e.g. if the region intersects
85           all six cube faces).  Even for very tiny regions, up to 3 cells may
86           be returned if they happen to be located at the intersection of
87           three cube faces.
88 
89         - min_level() takes priority over max_cells(), i.e. cells below the
90           given level will never be used even if this causes a large number of
91           cells to be returned.
92 
93         - If max_cells() is less than 4, the area of the covering may be
94           arbitrarily large compared to the area of the original region even
95           if the region is convex (e.g. an S2Cap or S2LatLngRect).
96 
97        Accuracy is measured by dividing the area of the covering by the area
98        of the original region.  The following table shows the median and worst
99        case values for this area ratio on a test case consisting of 100,000
100        spherical caps of random size (generated using s2region_coverer_test):
101 
102        ---
103          max_cells:        3      4     5     6     8    12    20   100   1000
104          median ratio:  5.33   3.32  2.73  2.34  1.98  1.66  1.42  1.11   1.01
105          worst case:  215518  14.41  9.72  5.26  3.91  2.75  1.92  1.20   1.02
106        ---
107 
108        The default value of 8 gives a reasonable tradeoff between the number
109        of cells used and the accuracy of the approximation.
110 
111        DEFAULT: DEFAULT_MAX_CELLS
112     */
113     int maxCells() const {
114       return _maxCells;
115     }
116 
117     void setMaxCells(int max_cells) {
118         _maxCells = max_cells;
119     }
120 
121     /**
122        Sets the minimum and maximum cell levels to be used.  The default is to
123        use all cell levels.
124 
125        To find the cell level corresponding to a given physical distance, use
126        the S2Cell metrics defined in s2metrics.h.  For example, to find the
127        cell level that corresponds to an average edge length of 10km, use:
128 
129        ---
130          int level =
131              AVG_EDGE.getClosestLevel(S2Earth.kmToRadians(length_km));
132        ---
133 
134        Note that min_level() takes priority over max_cells(), i.e. cells below
135        the given level will never be used even if this causes a large number
136        of cells to be returned.  (This doesn't apply to interior coverings,
137        since interior coverings make no completeness guarantees -- the result
138        is simply a set of cells that covers as much of the interior as
139        possible while satisfying the given restrictions.)
140 
141        Requires: max_level() >= min_level()
142        Default: 0
143     */
144     int minLevel() const {
145       return _minLevel;
146     }
147 
148     void setMinLevel(int min_level)
149     in {
150       assert(min_level >=  0);
151       assert(min_level <= S2CellId.MAX_LEVEL);
152     } do {
153       _minLevel = max(0, min(S2CellId.MAX_LEVEL, min_level));
154     }
155 
156     /// Default: `S2CellId.MAX_LEVEL`
157     int maxLevel() const {
158       return _maxLevel;
159     }
160 
161     void setMaxLevel(int max_level)
162     in {
163       assert(max_level >= 0);
164       assert(max_level <= S2CellId.MAX_LEVEL);
165     } do {
166       _maxLevel = max(0, min(S2CellId.MAX_LEVEL, max_level));
167     }
168 
169     /// Convenience function that sets both the maximum and minimum cell levels.
170     void setFixedLevel(int level) {
171       setMinLevel(level);
172       setMaxLevel(level);
173     }
174 
175     /**
176        If specified, then only cells where (level - min_level) is a multiple
177        of "level_mod" will be used (default 1).  This effectively allows the
178        branching factor of the S2CellId hierarchy to be increased.  Currently
179        the only parameter values allowed are 1, 2, or 3, corresponding to
180        branching factors of 4, 16, and 64 respectively.
181 
182        Default: 1
183     */
184     int levelMod() const {
185       return _levelMod;
186     }
187 
188     void setLevelMod(int level_mod)
189     in {
190       assert(level_mod >= 1);
191       assert(level_mod <= 3);
192     } do {
193       _levelMod = max(1, min(3, level_mod));
194     }
195 
196     /**
197        Convenience function that returns the maximum level such that
198 
199        ---
200        (level <= max_level()) && (level - min_level()) % level_mod() == 0.
201        ---
202 
203        This is the maximum level that will actually be used in coverings.
204     */
205     int trueMaxLevel() const {
206       if (_levelMod == 1) return _maxLevel;
207       return _maxLevel - (_maxLevel - _minLevel) % _levelMod;
208     }
209 
210     override
211     string toString() const {
212       import std.format : format;
213       return format(
214           "[maxCells=%d, minLevel=%d, maxLevel=%d, levelMod=%d, trueMaxLevel=%d]",
215           _maxCells, _minLevel, _maxLevel, _levelMod, trueMaxLevel());
216     }
217 
218   protected:
219     int _maxCells = DEFAULT_MAX_CELLS;
220     int _minLevel = 0;
221     int _maxLevel = S2CellId.MAX_LEVEL;
222     int _levelMod = 1;
223   }
224 
225   this() {
226     QueueEntry[] queueEntries;
227     _pq = heapify(queueEntries);
228     _options = new Options();
229   }
230 
231   /// Constructs an S2RegionCoverer with the given options.
232   this(Options options) {
233     this();
234     _options = options;
235   }
236 
237   /// Returns the current options.  Options can be modifed between calls.
238   const(Options) options() const {
239     return _options;
240   }
241 
242   Options mutableOptions() {
243     return _options;
244   }
245 
246   /**
247      Returns an S2CellUnion that covers (GetCovering) or is contained within
248      (GetInteriorCovering) the given region and satisfies the current options.
249 
250      Note that if options().min_level() > 0 or options().level_mod() > 1, the
251      by definition the S2CellUnion may not be normalized, i.e. there may be
252      groups of four child cells that can be replaced by their parent cell.
253   */
254   S2CellUnion getCovering(S2Region region) {
255     _interiorCovering = false;
256     getCoveringInternal(region);
257     auto r = S2CellUnion.fromVerbatim(_result);
258     _result = null;
259     return r;
260   }
261 
262   S2CellUnion getInteriorCovering(S2Region region) {
263     _interiorCovering = true;
264     getCoveringInternal(region);
265     auto r = S2CellUnion.fromVerbatim(_result);
266     _result = null;
267     return r;
268   }
269 
270   /**
271      Like the methods above, but works directly with a vector of S2CellIds.
272      This version can be more efficient when this method is called many times,
273      since it does not require allocating a new vector on each call.
274   */
275   void getCovering(S2Region region, ref S2CellId[] covering) {
276     _interiorCovering = false;
277     getCoveringInternal(region);
278     covering = _result;
279     _result = null;
280   }
281 
282   void getInteriorCovering(S2Region region, ref S2CellId[] interior) {
283     _interiorCovering = true;
284     getCoveringInternal(region);
285     interior = _result;
286     _result = null;
287   }
288 
289   /**
290      Like GetCovering(), except that this method is much faster and the
291      coverings are not as tight.  All of the usual parameters are respected
292      (max_cells, min_level, max_level, and level_mod), except that the
293      implementation makes no attempt to take advantage of large values of
294      max_cells().  (A small number of cells will always be returned.)
295 
296      This function is useful as a starting point for algorithms that
297      recursively subdivide cells.
298   */
299   void getFastCovering(S2Region region, ref S2CellId[] covering) {
300     region.getCellUnionBound(covering);
301     canonicalizeCovering(covering);
302   }
303 
304   /**
305      Given a connected region and a starting point, return a set of cells at
306      the given level that cover the region.
307 
308      Note that this method is *not* faster than the regular GetCovering()
309      method for most region types, such as S2Cap or S2Polygon, and in fact it
310      can be much slower when the output consists of a large number of cells.
311      Currently it can be faster at generating coverings of long narrow regions
312      such as polylines, but this may change in the future, in which case this
313      method will most likely be removed.
314   */
315   static void getSimpleCovering(
316       S2Region region, in S2Point start, int level, ref S2CellId[] output) {
317     return floodFill(region, S2CellId(start).parent(level), output);
318   }
319 
320   /**
321      Given a region and a starting cell, returns the set of all the
322      edge-connected cells at the same level that intersect "region".
323      The output cells are returned in arbitrary order.
324   */
325   static void floodFill(S2Region region, S2CellId start, out S2CellId[] output) {
326     bool[S2CellId] all;
327     S2CellId[] frontier;
328     all[start] = true;
329     frontier ~= start;
330     while (frontier.length != 0) {
331       S2CellId id = frontier[$-1];
332       frontier.length--;
333       if (!region.mayIntersect(new S2Cell(id))) continue;
334       output ~= id;
335 
336       S2CellId[4] neighbors;
337       id.getEdgeNeighbors(neighbors);
338       for (int edge = 0; edge < 4; ++edge) {
339         S2CellId nbr = neighbors[edge];
340         if (nbr !in all) {
341           all[nbr] = true;
342           frontier ~= nbr;
343         }
344       }
345     }
346   }
347 
348   /**
349      Returns true if the given S2CellId vector represents a valid covering
350      that conforms to the current covering parameters.  In particular:
351 
352      - All S2CellIds must be valid.
353 
354      - S2CellIds must be sorted and non-overlapping.
355 
356      - S2CellId levels must satisfy min_level(), max_level(), and level_mod().
357 
358      - If covering.size() > max_cells(), there must be no two cells with
359        a common ancestor at min_level() or higher.
360 
361      - There must be no sequence of cells that could be replaced by an
362        ancestor (i.e. with level_mod() == 1, the 4 child cells of a parent).
363   */
364   bool isCanonical(in S2CellUnion covering) const {
365     return isCanonical(covering.cellIds());
366   }
367 
368   bool isCanonical(in S2CellId[] covering) const {
369     const int min_level = _options.minLevel();
370     const int max_level = _options.trueMaxLevel();
371     const int level_mod = _options.levelMod();
372     const bool too_many_cells = covering.length > _options.maxCells();
373     int same_parent_count = 1;
374     S2CellId prev_id = S2CellId.none();
375     foreach (const S2CellId id; covering) {
376       if (!id.isValid()) {
377         return false;
378       }
379 
380       // Check that the S2CellId level is acceptable.
381       const int level = id.level();
382       if (level < min_level || level > max_level) {
383         return false;
384       }
385       if (level_mod > 1 && (level - min_level) % level_mod != 0) {
386         return false;
387       }
388 
389       if (prev_id != S2CellId.none()) {
390         // Check that cells are sorted and non-overlapping.
391         if (prev_id.rangeMax() >= id.rangeMin()) {
392           return false;
393         }
394 
395         // If there are too many cells, check that no pair of adjacent cells
396         // could be replaced by an ancestor.
397         if (too_many_cells && id.getCommonAncestorLevel(prev_id) >= min_level) {
398           return false;
399         }
400 
401         // Check that there are no sequences of (4 ** level_mod) cells that all
402         // have the same parent (considering only multiples of "level_mod").
403         int plevel = level - level_mod;
404         if (plevel < min_level || level != prev_id.level()
405             || id.parent(plevel) != prev_id.parent(plevel)) {
406           same_parent_count = 1;
407         } else if (++same_parent_count == (1 << (2 * level_mod))) {
408           return false;
409         }
410       }
411       prev_id = id;
412     }
413     return true;
414   }
415 
416   /**
417      Modify "covering" if necessary so that it conforms to the current
418      covering parameters (max_cells, min_level, max_level, and level_mod).
419      There are no restrictions on the input S2CellIds (they may be unsorted,
420      overlapping, etc).
421   */
422   S2CellUnion canonicalizeCovering(in S2CellUnion covering) {
423     auto ids = covering.cellIds().dup;
424     canonicalizeCovering(ids);
425     return new S2CellUnion(ids);
426   }
427 
428   void canonicalizeCovering(ref S2CellId[] covering)
429   out {
430     assert(isCanonical(covering));
431   } do {
432     // Note that when the covering parameters have their default values, almost
433     // all of the code in this function is skipped.
434 
435     // If any cells are too small, or don't satisfy level_mod(), then replace
436     // them with ancestors.
437     if (_options.maxLevel() < S2CellId.MAX_LEVEL || _options.levelMod() > 1) {
438       for (int i = 0; i < covering.length; ++i) {
439         S2CellId id = covering[i];
440         int level = id.level();
441         int new_level = adjustLevel(min(level, _options.maxLevel()));
442         if (new_level != level) {
443           covering[i] = id.parent(new_level);
444         }
445       }
446     }
447 
448     // Sort the cells and simplify them.
449     S2CellUnion.normalize(covering);
450 
451     // Make sure that the covering satisfies min_level() and level_mod(),
452     // possibly at the expense of satisfying max_cells().
453     if (_options.minLevel() > 0 || _options.levelMod() > 1) {
454       S2CellUnion.denormalize(covering, _options.minLevel(), _options.levelMod(), _result);
455       covering = _result;
456       _result = null;
457     }
458 
459     // If there are too many cells and the covering is very large, use the
460     // S2RegionCoverer to compute a new covering.  (This avoids possible O(n^2)
461     // behavior of the simpler algorithm below.)
462     long excess = covering.length - _options.maxCells();
463     if (excess <= 0 || isCanonical(covering)) {
464       return;
465     }
466     if (excess * covering.length > 10000) {
467       getCovering(new S2CellUnion(covering), covering);
468     } else {
469       // Repeatedly replace two adjacent cells in S2CellId order by their lowest
470       // common ancestor until the number of cells is acceptable.
471       while (covering.length > _options.maxCells()) {
472         int best_index = -1, best_level = -1;
473         for (int i = 0; i + 1 < covering.length; ++i) {
474           int level = covering[i].getCommonAncestorLevel(covering[i+1]);
475           level = adjustLevel(level);
476           if (level > best_level) {
477             best_level = level;
478             best_index = i;
479           }
480         }
481         if (best_level < _options.minLevel()) break;
482 
483         // Replace all cells contained by the new ancestor cell.
484         S2CellId id = covering[best_index].parent(best_level);
485         replaceCellsWithAncestor(covering, id);
486 
487         // Now repeatedly check whether all children of the parent cell are
488         // present, in which case we can replace those cells with their parent.
489         while (best_level > _options.minLevel()) {
490           best_level -= _options.levelMod();
491           id = id.parent(best_level);
492           if (!containsAllChildren(covering, id)) break;
493           replaceCellsWithAncestor(covering, id);
494         }
495       }
496     }
497   }
498 
499 
500  private:
501   class Candidate {
502     S2Cell cell;
503     bool isTerminal;        // Cell should not be expanded further.
504     int numChildren;        // Number of children that intersect the region.
505     Candidate[] children;  // Actual size may be 0, 4, 16, or 64 elements.
506 
507     override
508     string toString() const {
509       import std.format : format;
510       return format("Candidate[cell=%s, isTerminal=%s, numChildren=%d]",
511           cell, isTerminal, numChildren);
512     }
513   }
514 
515   /**
516      If the cell intersects the given region, return a new candidate with no
517      children, otherwise return nullptr.  Also marks the candidate as "terminal"
518      if it should not be expanded further.
519   */
520   Candidate newCandidate(S2Cell cell) {
521     if (!_region.mayIntersect(cell)) return null;
522 
523     bool is_terminal = false;
524     if (cell.level() >= _options.minLevel()) {
525       if (_interiorCovering) {
526         if (_region.contains(cell)) {
527           is_terminal = true;
528         } else if (cell.level() + _options.levelMod() > _options.maxLevel()) {
529           return null;
530         }
531       } else {
532         if (cell.level() + _options.levelMod() > _options.maxLevel() || _region.contains(cell)) {
533           is_terminal = true;
534         }
535       }
536     }
537     size_t children_size = 0;
538     if (!is_terminal) {
539       children_size = Candidate.sizeof << maxChildrenShift();
540     }
541     Candidate candidate = new Candidate();
542     candidate.cell = cell;
543     candidate.isTerminal = is_terminal;
544     candidate.numChildren = 0;
545     ++_candidatesCreatedCounter;
546     return candidate;
547   }
548 
549   /// Return the log base 2 of the maximum number of children of a candidate.
550   int maxChildrenShift() const {
551     return 2 * options().levelMod();
552   }
553 
554   /**
555      Process a candidate by either adding it to the result_ vector or
556      expanding its children and inserting it into the priority queue.
557      Passing an argument of nullptr does nothing.
558   */
559   void addCandidate(Candidate candidate) {
560     if (candidate is null) return;
561 
562     if (candidate.isTerminal) {
563       _result ~= candidate.cell.id();
564       return;
565     }
566     assert(candidate.numChildren == 0);
567 
568     // Expand one level at a time until we hit min_level() to ensure that we
569     // don't skip over it.
570     int num_levels = (candidate.cell.level() < _options.minLevel()) ? 1 : _options.levelMod();
571     int num_terminals = expandChildren(candidate, candidate.cell, num_levels);
572 
573     if (candidate.numChildren != 0 && !_interiorCovering
574         && num_terminals == 1 << maxChildrenShift()
575         && candidate.cell.level() >= _options.minLevel()) {
576       // Optimization: add the parent cell rather than all of its children.
577       // We can't do this for interior coverings, since the children just
578       // intersect the region, but may not be contained by it - we need to
579       // subdivide them further.
580       candidate.isTerminal = true;
581       addCandidate(candidate);
582     } else {
583       // We negate the priority so that smaller absolute priorities are returned
584       // first.  The heuristic is designed to refine the largest cells first,
585       // since those are where we have the largest potential gain.  Among cells
586       // of the same size, we prefer the cells with the fewest children.
587       // Finally, among cells with equal numbers of children we prefer those
588       // with the smallest number of children that cannot be refined further.
589       int priority = -((((candidate.cell.level() << maxChildrenShift())
590                   + candidate.numChildren) << maxChildrenShift())
591           + num_terminals);
592       _pq.insert(QueueEntry(priority, candidate));
593       logger.logDebug("Push: ", candidate.cell.id(), " (", priority, ") ");
594     }
595   }
596 
597   /**
598      Populate the children of "candidate" by expanding the given number of
599      levels from the given cell.  Returns the number of children that were
600      marked "terminal".
601   */
602   int expandChildren(Candidate candidate, const S2Cell cell, int num_levels) {
603     num_levels--;
604     S2Cell[4] child_cells;
605     cell.subdivide(child_cells);
606     int num_terminals = 0;
607     foreach (int i; 0 .. 4) {
608       if (num_levels > 0) {
609         if (_region.mayIntersect(child_cells[i])) {
610           num_terminals += expandChildren(candidate, child_cells[i], num_levels);
611         }
612         continue;
613       }
614       Candidate child = newCandidate(child_cells[i]);
615       if (child) {
616         candidate.children ~= child;
617         candidate.numChildren++;
618         if (child.isTerminal) ++num_terminals;
619       }
620     }
621     return num_terminals;
622   }
623 
624   /// Computes a set of initial candidates that cover the given region.
625   void getInitialCandidates() {
626     // Optimization: start with a small (usually 4 cell) covering of the
627     // region's bounding cap.
628     auto tmp_coverer = new S2RegionCoverer();
629     tmp_coverer.mutableOptions().setMaxCells(min(4, _options.maxCells()));
630     tmp_coverer.mutableOptions().setMaxLevel(_options.maxLevel());
631     S2CellId[] cells;
632     tmp_coverer.getFastCovering(_region, cells);
633     adjustCellLevels(cells);
634     foreach (S2CellId cell_id; cells) {
635       addCandidate(newCandidate(new S2Cell(cell_id)));
636     }
637   }
638 
639   /// Generates a covering and stores it in result_.
640   void getCoveringInternal(S2Region region)
641   in {
642     assert(_pq.length == 0);
643     assert(_result.length == 0);
644   } out {
645     assert(isCanonical(_result));
646   } do {
647     // Strategy: Start with the 6 faces of the cube.  Discard any
648     // that do not intersect the shape.  Then repeatedly choose the
649     // largest cell that intersects the shape and subdivide it.
650     //
651     // result_ contains the cells that will be part of the output, while pq_
652     // contains cells that we may still subdivide further.  Cells that are
653     // entirely contained within the region are immediately added to the output,
654     // while cells that do not intersect the region are immediately discarded.
655     // Therefore pq_ only contains cells that partially intersect the region.
656     // Candidates are prioritized first according to cell size (larger cells
657     // first), then by the number of intersecting children they have (fewest
658     // children first), and then by the number of fully contained children
659     // (fewest children first).
660 
661     _region = region;
662     _candidatesCreatedCounter = 0;
663 
664     getInitialCandidates();
665     while (!_pq.empty()
666         && (!_interiorCovering || _result.length < _options.maxCells())) {
667       Candidate candidate = _pq.front().candidate;
668       _pq.popFront();
669       logger.logDebug("Pop: ", candidate.cell.id());
670       // For interior coverings we keep subdividing no matter how many children
671       // the candidate has.  If we reach max_cells() before expanding all
672       // children, we will just use some of them.  For exterior coverings we
673       // cannot do this, because the result has to cover the whole region, so
674       // all children have to be used.  The (candidate->num_children == 1) case
675       // takes care of the situation when we already have more than max_cells()
676       // in results (min_level is too high).  Subdividing the candidate with one
677       // child does no harm in this case.
678       if (_interiorCovering
679           || candidate.cell.level() < _options.minLevel()
680           || candidate.numChildren == 1
681           || (_result.length + _pq.length + candidate.numChildren <= _options.maxCells())) {
682         // Expand this candidate into its children.
683         for (int i = 0; i < candidate.numChildren; ++i) {
684           if (!_interiorCovering || _result.length < _options.maxCells()) {
685             addCandidate(candidate.children[i]);
686           }
687         }
688       } else {
689         candidate.isTerminal = true;
690         addCandidate(candidate);
691       }
692     }
693     logger.logDebug("Created ", _result.length, " cells, ",
694         _candidatesCreatedCounter, " candidates created, ",
695         _pq.length, " left");
696     while (!_pq.empty()) {
697       _pq.popFront();
698     }
699     _region = null;
700 
701     // Rather than just returning the raw list of cell ids, we construct a cell
702     // union and then denormalize it.  This has the effect of replacing four
703     // child cells with their parent whenever this does not violate the covering
704     // parameters specified (min_level, level_mod, etc).  This significantly
705     // reduces the number of cells returned in many cases, and it is cheap
706     // compared to computing the covering in the first place.
707     S2CellUnion.normalize(_result);
708     if (_options.minLevel() > 0 || _options.levelMod() > 1) {
709       auto result_copy = _result;
710       S2CellUnion.denormalize(result_copy, _options.minLevel(), _options.levelMod(), _result);
711     }
712   }
713 
714   /**
715      If level > min_level(), then reduce "level" if necessary so that it also
716      satisfies level_mod().  Levels smaller than min_level() are not affected
717      (since cells at these levels are eventually expanded).
718   */
719   int adjustLevel(int level) const {
720     if (_options.levelMod() > 1 && level > _options.minLevel()) {
721       level -= (level - _options.minLevel()) % _options.levelMod();
722     }
723     return level;
724   }
725 
726   /**
727      Ensure that all cells with level > min_level() also satisfy level_mod(),
728      by replacing them with an ancestor if necessary.  Cell levels smaller
729      than min_level() are not modified (see AdjustLevel).  The output is
730      then normalized to ensure that no redundant cells are present.
731   */
732   void adjustCellLevels(ref S2CellId[] cells) const
733   in {
734     assert(isSorted(cells));
735   } do {
736     if (_options.levelMod() == 1) return;
737 
738     int output = 0;
739     foreach (S2CellId id; cells) {
740       int level = id.level();
741       int new_level = adjustLevel(level);
742       if (new_level != level) id = id.parent(new_level);
743       if (output > 0 && cells[output-1].contains(id)) continue;
744       while (output > 0 && id.contains(cells[output-1])) --output;
745       cells[output++] = id;
746     }
747     cells.length = output;
748   }
749 
750   /**
751      Returns true if "covering" contains all children of "id" at level
752      (id.level() + options_.level_mod()).
753   */
754   bool containsAllChildren(in S2CellId[] covering, S2CellId id) const {
755     auto ranges = assumeSorted(covering).trisect(id.rangeMin());
756     auto geRange = chain(ranges[1], ranges[2]);
757     int level = id.level() + _options.levelMod();
758     for (S2CellId child = id.childBegin(level);
759          child != id.childEnd(level);
760          geRange.popFront(), child = child.next()) {
761       if (geRange.empty() || geRange.front != child) return false;
762     }
763     return true;
764   }
765 
766   /// Replaces all descendants of "id" in "covering" with "id".
767   /// REQUIRES: "covering" contains at least one descendant of "id".
768   void replaceCellsWithAncestor(ref S2CellId[] covering, S2CellId id) const {
769     auto ltRange = assumeSorted(covering).lowerBound(id.rangeMin());
770     auto gtRange = assumeSorted(covering).upperBound(id.rangeMax());
771     auto cutRange = chain(ltRange, [id], gtRange);
772     covering = array(cutRange);
773   }
774 
775   Options _options;
776 
777   // We save a temporary copy of the pointer passed to GetCovering() in order
778   // to avoid passing this parameter around internally.  It is only used (and
779   // only valid) for the duration of a single GetCovering() call.
780   S2Region _region = null;
781 
782   // The set of S2CellIds that have been added to the covering so far.
783   S2CellId[] _result;
784 
785   // We keep the candidates in a priority queue.  We specify a vector to hold
786   // the queue entries since for some reason priority_queue<> uses a deque by
787   // default.  We define our own own comparison function on QueueEntries in
788   // order to make the results deterministic.  (Using the default
789   // less<QueueEntry>, entries of equal priority would be sorted according to
790   // the memory address of the candidate.)
791 
792   struct QueueEntry {
793     int priority;
794     Candidate candidate;
795 
796     int opCmp(ref in QueueEntry b) {
797       return priority - b.priority;
798     }
799   }
800   alias CandidateQueue = BinaryHeap!(QueueEntry[]);
801   CandidateQueue _pq;
802 
803   // True if we're computing an interior covering.
804   bool _interiorCovering;
805 
806   // Counter of number of candidates created, for performance evaluation.
807   int _candidatesCreatedCounter;
808 }