1 // Copyright 2005 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.s2cell_union;
20 
21 import s2.logger;
22 import s2.s1angle;
23 import s2.s2cap;
24 import s2.s2cell;
25 import s2.s2cell_id;
26 import s2.s2latlng_rect;
27 import s2.s2point;
28 import s2.s2region;
29 import s2.util.coding.coder;
30 
31 import std.algorithm : countUntil, min, max, isSorted, sort;
32 import std.exception : enforce;
33 import std.range;
34 
35 // The maximum number of cells allowed by S2CellUnion.Decode
36 enum int S2CELL_UNION_DECODE_MAX_NUM_CELLS = 1_000_000;
37 enum ubyte CURRENT_LOSSLESS_ENCODING_VERSION_NUMBER = 1;
38 
39 /**
40  * An S2CellUnion is a region consisting of cells of various sizes.  Typically
41  * a cell union is used to approximate some other shape.  There is a tradeoff
42  * between the accuracy of the approximation and how many cells are used.
43  * Unlike polygons, cells have a fixed hierarchical structure.  This makes
44  * them more suitable for optimizations based on preprocessing.
45  *
46  * An S2CellUnion is represented as a vector of sorted, non-overlapping
47  * S2CellIds.  By default the vector is also "normalized", meaning that groups
48  * of 4 child cells have been replaced by their parent cell whenever possible.
49  * S2CellUnions are not required to be normalized, but certain operations will
50  * return different results if they are not (e.g., Contains(S2CellUnion).)
51  *
52  * S2CellUnion is movable and copyable.
53  */
54 final class S2CellUnion : S2Region {
55 public:
56   /// Creates an empty cell union.
57   this() {}
58 
59   /**
60    * Constructs a cell union with the given S2CellIds, then calls Normalize()
61    * to sort them, remove duplicates, and merge cells when possible.  (See
62    * FromNormalized if your vector is already normalized.)
63    *
64    * The argument is passed by value, so if you are passing a named variable
65    * and have no further use for it, consider using std::move().
66    *
67    * A cell union containing a single S2CellId may be constructed like this:
68    *
69    *     S2CellUnion example({cell_id});
70    */
71   this(in S2CellId[] cell_ids) {
72     _cellIds = cell_ids.dup;
73     normalize();
74   }
75 
76   // Convenience constructor that accepts a vector of uint64.  Note that
77   // unlike the constructor above, this one makes a copy of "cell_ids".
78   this(in ulong[] cell_ids) {
79     _cellIds = toS2CellIds(cell_ids.dup);
80     normalize();
81   }
82 
83   /**
84    * Constructs a cell union from S2CellIds that have already been normalized
85    * (typically because they were extracted from another S2CellUnion).
86    *
87    * The argument is passed by value, so if you are passing a named variable
88    * and have no further use for it, consider using std::move().
89    *
90    * REQUIRES: "cell_ids" satisfies the requirements of IsNormalized().
91    */
92   static S2CellUnion fromNormalized(S2CellId[] cell_ids) {
93     auto result = new S2CellUnion(cell_ids, VerbatimFlag.VERBATIM);
94     enforce(result.isNormalized());
95     return result;
96   }
97 
98   /**
99    * Constructs a cell union from a vector of sorted, non-overlapping
100    * S2CellIds.  Unlike the other constructors, FromVerbatim does not require
101    * that groups of 4 child cells have been replaced by their parent cell.  In
102    * other words, "cell_ids" must satisfy the requirements of IsValid() but
103    * not necessarily IsNormalized().
104    *
105    * Note that if the cell union is not normalized, certain operations may
106    * return different results (e.g., Contains(S2CellUnion)).
107    *
108    * REQUIRES: "cell_ids" satisfies the requirements of IsValid().
109    */
110   static S2CellUnion fromVerbatim(S2CellId[] cell_ids) {
111     auto result = new S2CellUnion(cell_ids, VerbatimFlag.VERBATIM);
112     enforce(result.isValid());
113     return result;
114   }
115 
116   /**
117    * Constructs a cell union that corresponds to a continuous range of cell
118    * ids.  The output is a normalized collection of cell ids that covers the
119    * leaf cells between "min_id" and "max_id" inclusive.
120    *
121    * REQUIRES: min_id.is_leaf(), max_id.is_leaf(), min_id <= max_id.
122    */
123   static S2CellUnion fromMinMax(S2CellId min_id, S2CellId max_id) {
124     auto result = new S2CellUnion();
125     result.initFromMinMax(min_id, max_id);
126     return result;
127   }
128 
129   /**
130    * Like FromMinMax() except that the union covers the range of leaf cells
131    * from "begin" (inclusive) to "end" (exclusive), as with Python ranges or
132    * STL iterator ranges.  If (begin == end) the result is empty.
133    *
134    * REQUIRES: begin.is_leaf(), end.is_leaf(), begin <= end.
135    */
136   static S2CellUnion fromBeginEnd(S2CellId begin, S2CellId end) {
137     auto result = new S2CellUnion();
138     result.initFromBeginEnd(begin, end);
139     return result;
140   }
141 
142   /**
143    * initialize() methods corresponding to the constructors/factory methods above.
144    * TODO(ericv): Consider deprecating these methods in favor of using the
145    * constructors and move assignment operator.
146    */
147   void initialize(S2CellId[] cell_ids) {
148     _cellIds = cell_ids;
149     normalize();
150   }
151 
152   void initialize(ulong[] cell_ids) {
153     _cellIds = toS2CellIds(cell_ids);
154     normalize();
155   }
156 
157   void initFromMinMax(S2CellId min_id, S2CellId max_id)
158   in {
159     assert(max_id.isValid());
160   } do {
161     initFromBeginEnd(min_id, max_id.next());
162   }
163 
164   void initFromBeginEnd(S2CellId begin, S2CellId end)
165   in {
166     assert(begin.isLeaf());
167     assert(end.isLeaf());
168     assert(begin <= end);
169   } out {
170     // The output is already normalized.
171     assert(isNormalized());
172   } do {
173     // We repeatedly add the largest cell we can.
174     _cellIds.length = 0;
175     for (S2CellId id = begin.maximumTile(end);
176          id != end; id = id.next().maximumTile(end)) {
177       _cellIds ~= id;
178     }
179   }
180 
181   // Returns true if the given four cells have a common parent.
182   // REQUIRES: The four cells are distinct.
183   static bool areSiblings(S2CellId a, S2CellId b, S2CellId c, S2CellId d) {
184     // A necessary (but not sufficient) condition is that the XOR of the
185     // four cells must be zero.  This is also very fast to test.
186     if ((a.id() ^ b.id() ^ c.id()) != d.id()) return false;
187 
188     // Now we do a slightly more expensive but exact test.  First, compute a
189     // mask that blocks out the two bits that encode the child position of
190     // "id" with respect to its parent, then check that the other three
191     // children all agree with "mask".
192     ulong mask = d.lsb() << 1;
193     mask = ~(mask + (mask << 1));
194     ulong id_masked = (d.id() & mask);
195     return ((a.id() & mask) == id_masked &&
196         (b.id() & mask) == id_masked &&
197         (c.id() & mask) == id_masked &&
198         !d.isFace());
199   }
200 
201   /// Clears the contents of the cell union and minimizes memory usage.
202   void clear() {
203     _cellIds.length = 0;
204   }
205 
206   /**
207    * Gives ownership of the vector data to the client without copying, and
208    * clears the content of the cell union.  The original data in cell_ids
209    * is lost if there was any.
210    */
211   S2CellId[] release() {
212     // vector's rvalue reference constructor does not necessarily leave
213     // moved-from value in empty state, so swap instead.
214     S2CellId[] cell_ids = _cellIds;
215     _cellIds.length = 0;
216     return cell_ids;
217   }
218 
219   /// Convenience methods for accessing the individual cell ids.
220   int numCells() const {
221     return cast(int)(_cellIds.length);
222   }
223 
224   S2CellId cellId(int i) const {
225     return _cellIds[i];
226   }
227 
228   /// Vector-like methods for accessing the individual cell ids.
229   size_t size() const {
230     return _cellIds.length;
231   }
232 
233   bool empty() const {
234     return _cellIds.empty();
235   }
236 
237   S2CellId opIndex(size_t i) const {
238     return _cellIds[i];
239   }
240 
241   struct Iterator {
242     const(S2CellId)[] cellIds;
243     size_t pos;
244 
245     inout(S2CellId) getValue() inout {
246       return cellIds[pos];
247     }
248 
249     void opUnary(string op)()
250     if (op == "++") {
251       if (pos < cellIds.length) {
252         pos++;
253       }
254     }
255 
256     void opUnary(string op)()
257     if (op == "--") {
258       if (pos > 0) {
259         pos--;
260       }
261     }
262   }
263 
264   /**
265    * Standard begin/end methods, to allow range-based for loops:
266    *
267    *  for (S2CellId id : cell_union) { ... }
268    */
269   Iterator begin() const {
270     return Iterator(_cellIds, 0);
271   }
272 
273   Iterator end() const {
274     return Iterator(_cellIds, _cellIds.length);
275   }
276 
277   /// Direct access to the underlying vector for STL algorithms.
278   inout(S2CellId[]) cellIds() inout {
279     return _cellIds;
280   }
281 
282   /**
283    * Returns true if the cell union is valid, meaning that the S2CellIds are
284    * valid, non-overlapping, and sorted in increasing order.
285    */
286   bool isValid() const {
287     if (numCells() > 0 && !cellId(0).isValid()) {
288       return false;
289     }
290     for (int i = 1; i < numCells(); ++i) {
291       if (!cellId(i).isValid()) {
292         return false;
293       }
294       if (cellId(i - 1).rangeMax() >= cellId(i).rangeMin()) {
295         return false;
296       }
297     }
298     return true;
299   }
300 
301   /**
302    * Returns true if the cell union is normalized, meaning that it is
303    * satisfies IsValid() and that no four cells have a common parent.
304    * Certain operations such as Contains(S2CellUnion) will return a different
305    * result if the cell union is not normalized.
306    */
307   bool isNormalized() const {
308     if (numCells() > 0 && !cellId(0).isValid()) {
309       return false;
310     }
311     for (int i = 1; i < numCells(); ++i) {
312       if (!cellId(i).isValid()) {
313         return false;
314       }
315       if (cellId(i - 1).rangeMax() >= cellId(i).rangeMin()) {
316         return false;
317       }
318       if (i >= 3 && areSiblings(cellId(i - 3), cellId(i - 2), cellId(i - 1), cellId(i))) {
319         return false;
320       }
321     }
322     return true;
323   }
324 
325   /**
326    * Normalizes the cell union by discarding cells that are contained by other
327    * cells, replacing groups of 4 child cells by their parent cell whenever
328    * possible, and sorting all the cell ids in increasing order.
329    *
330    * Returns true if the number of cells was reduced.
331    * TODO(ericv): Change this method to return void.
332    */
333   bool normalize() {
334     return normalize(_cellIds);
335   }
336 
337   /**
338    * Replaces "output" with an expanded version of the cell union where any
339    * cells whose level is less than "min_level" or where (level - min_level)
340    * is not a multiple of "level_mod" are replaced by their children, until
341    * either both of these conditions are satisfied or the maximum level is
342    * reached.
343    *
344    * This method allows a covering generated by S2RegionCoverer using
345    * min_level() or level_mod() constraints to be stored as a normalized cell
346    * union (which allows various geometric computations to be done) and then
347    * converted back to the original list of cell ids that satisfies the
348    * desired constraints.
349    */
350   void denormalize(int min_level, int level_mod, ref S2CellId[] output) const {
351     denormalize(_cellIds, min_level, level_mod, output);
352   }
353 
354   /**
355    * If there are more than "excess" elements of the cell_ids() vector that
356    * are allocated but unused, reallocates the array to eliminate the excess
357    * space.  This reduces memory usage when many cell unions need to be held
358    * in memory at once.
359    */
360   void pack(int excess = 0) {
361     // TODO: For now, let the D runtime manage this.
362     return;
363   }
364 
365   /**
366    * Returns true if the cell union contains the given cell id.  Containment
367    * is defined with respect to regions, e.g. a cell contains its 4 children.
368    * This is a fast operation (logarithmic in the size of the cell union).
369    *
370    * CAVEAT: If you have constructed a non-normalized S2CellUnion using
371    * FromVerbatim, note that groups of 4 child cells are *not* considered to
372    * contain their parent cell.  To get this behavior you must use one of the
373    * other constructors or call Normalize() explicitly.
374    */
375   bool contains(S2CellId id) const {
376     // This is an exact test.  Each cell occupies a linear span of the S2
377     // space-filling curve, and the cell id is simply the position at the center
378     // of this span.  The cell union ids are sorted in increasing order along
379     // the space-filling curve.  So we simply find the pair of cell ids that
380     // surround the given cell id (using binary search).  There is containment
381     // if and only if one of these two cell ids contains this cell.
382 
383     // Split the cellIds into a ranges [0] = less, [1] = equal, [2] = greater
384     auto trisectRanges = assumeSorted(_cellIds[]).trisect(id);
385     return !trisectRanges[1].empty
386         || !trisectRanges[2].empty && trisectRanges[2].front.rangeMin() <= id
387         || !trisectRanges[0].empty && trisectRanges[0].back.rangeMax() >= id;
388   }
389 
390   /**
391    * Returns true if the cell union intersects the given cell id.
392    * This is a fast operation (logarithmic in the size of the cell union).
393    */
394   bool intersects(S2CellId id) const {
395     // This is an exact test; see the comments for Contains() above.
396     auto trisectRanges = assumeSorted(_cellIds[]).trisect(id);
397     return !trisectRanges[1].empty
398         || !trisectRanges[2].empty && trisectRanges[2].front.rangeMin() <= id.rangeMax()
399         || !trisectRanges[0].empty && trisectRanges[0].back.rangeMax() >= id.rangeMin();
400   }
401 
402   // Returns true if this cell union contains the given other cell union.
403   //
404   // CAVEAT: If you have constructed a non-normalized S2CellUnion using
405   // FromVerbatim, note that groups of 4 child cells are *not* considered to
406   // contain their parent cell.  To get this behavior you must use one of the
407   // other constructors or call Normalize() explicitly.
408   bool contains(in S2CellUnion y) const {
409     // TODO(ericv): A divide-and-conquer or alternating-skip-search
410     // approach may be sigificantly faster in both the average and worst case.
411 
412     foreach (S2CellId y_id; y._cellIds) {
413       if (!contains(y_id)) return false;
414     }
415     return true;
416   }
417 
418   // Returns true if this cell union intersects the given other cell union.
419   bool intersects(in S2CellUnion y) const {
420     // TODO(ericv): A divide-and-conquer or alternating-skip-search
421     // approach may be sigificantly faster in both the average and worst case.
422 
423     foreach (S2CellId y_id; y._cellIds) {
424       if (intersects(y_id)) return true;
425     }
426     return false;
427   }
428 
429   // Returns the union of the two given cell unions.
430   S2CellUnion unite(in S2CellUnion y) const {
431     return new S2CellUnion(array(chain(_cellIds, y._cellIds)));
432   }
433 
434   // Returns the intersection of the two given cell unions.
435   S2CellUnion intersect(in S2CellUnion y) const
436   out (result) {
437     // The output is normalized as long as at least one input is normalized.
438     assert(result.isNormalized() || (!isNormalized() && !y.isNormalized()));
439   } do {
440     auto result = new S2CellUnion();
441     getIntersection(_cellIds, y._cellIds, result._cellIds);
442     return result;
443   }
444 
445   // Specialized version of GetIntersection() that returns the intersection of
446   // a cell union with an S2CellId.  This can be useful for splitting a cell
447   // union into pieces.
448   S2CellUnion intersect(S2CellId id) const
449   out (result) {
450     assert(result.isNormalized() || !isNormalized());
451   } do {
452     auto result = new S2CellUnion();
453     if (contains(id)) {
454       result._cellIds ~= id;
455     } else {
456       auto ranges = assumeSorted(_cellIds).trisect(id.rangeMin());
457       // Get the range of values >= id.rangeMin().
458       auto geCells = chain(ranges[1], ranges[2]);
459       S2CellId id_max = id.rangeMax();
460       while (!geCells.empty() && geCells.front() <= id_max) {
461         result._cellIds ~= geCells.front();
462         geCells.popFront();
463       }
464     }
465     return result;
466   }
467 
468   /// Returns the difference of the two given cell unions.
469   S2CellUnion difference(in S2CellUnion y) const
470   out (result) {
471     // The output is normalized as long as the first argument is normalized.
472     assert(result.isNormalized() || !isNormalized());
473   } do {
474     // TODO(ericv): this is approximately O(N*log(N)), but could probably
475     // use similar techniques as GetIntersection() to be more efficient.
476 
477     auto result = new S2CellUnion();
478     for (auto iter = begin(); iter != end(); iter++) {
479       getDifferenceInternal(iter.getValue(), y, result._cellIds);
480     }
481     return result;
482   }
483 
484   private static void getDifferenceInternal(
485       S2CellId cell, in S2CellUnion y, ref S2CellId[] cell_ids) {
486     // Add the difference between cell and y to cell_ids.
487     // If they intersect but the difference is non-empty, divide and conquer.
488     if (!y.intersects(cell)) {
489       cell_ids ~= cell;
490     } else if (!y.contains(cell)) {
491       S2CellId child = cell.childBegin();
492       for (int i = 0; ; ++i) {
493         getDifferenceInternal(child, y, cell_ids);
494         if (i == 3) break;  // Avoid unnecessary next() computation.
495         child = child.next();
496       }
497     }
498   }
499 
500   /**
501    * Expands the cell union by adding a buffer of cells at "expand_level"
502    * around the union boundary.
503    *
504    * For each cell "c" in the union, we add all neighboring cells at level
505    * "expand_level" that are adjacent to "c".  Note that there can be many
506    * such cells if "c" is large compared to "expand_level".  If "c" is smaller
507    * than "expand_level", we first add the parent of "c" at "expand_level" and
508    * then add all the neighbors of that cell.
509    *
510    * Note that the size of the output is exponential in "expand_level".  For
511    * example, if expand_level == 20 and the input has a cell at level 10,
512    * there will be on the order of 4000 adjacent cells in the output.  For
513    * most applications the Expand(min_radius, max_level_diff) method below is
514    * easier to use.
515    */
516   void expand(int expand_level) {
517     S2CellId[] output;
518     ulong level_lsb = S2CellId.lsbForLevel(expand_level);
519     for (int i = numCells(); --i >= 0; ) {
520       S2CellId id = cellId(i);
521       if (id.lsb() < level_lsb) {
522         id = id.parent(expand_level);
523         // Optimization: skip over any cells contained by this one.  This is
524         // especially important when very small regions are being expanded.
525         while (i > 0 && id.contains(cellId(i - 1))) --i;
526       }
527       output ~= id;
528       id.appendAllNeighbors(expand_level, output);
529     }
530     initialize(output);
531   }
532 
533   /**
534    * Expands the cell union such that it contains all points whose distance to
535    * the cell union is at most "min_radius", but do not use cells that are
536    * more than "max_level_diff" levels higher than the largest cell in the
537    * input.  The second parameter controls the tradeoff between accuracy and
538    * output size when a large region is being expanded by a small amount
539    * (e.g. expanding Canada by 1km).  For example, if max_level_diff == 4 the
540    * region will always be expanded by approximately 1/16 the width of its
541    * largest cell.  Note that in the worst case, the number of cells in the
542    * output can be up to 4 * (1 + 2 ** max_level_diff) times larger than the
543    * number of cells in the input.
544    */
545   void expand(S1Angle min_radius, int max_level_diff) {
546     import s2.s2metrics : MIN_WIDTH;
547 
548     int min_level = S2CellId.MAX_LEVEL;
549     foreach (S2CellId id; _cellIds) {
550       min_level = min(min_level, id.level());
551     }
552     // Find the maximum level such that all cells are at least "min_radius" wide.
553     int radius_level = MIN_WIDTH.getLevelForMinValue(min_radius.radians());
554     if (radius_level == 0 && min_radius.radians() > MIN_WIDTH.getValue(0)) {
555       // The requested expansion is greater than the width of a face cell.
556       // The easiest way to handle this is to expand twice.
557       expand(0);
558     }
559     expand(min(min_level + max_level_diff, radius_level));
560   }
561 
562   /**
563    * The number of leaf cells covered by the union.
564    * This will be no more than 6*2^60 for the whole sphere.
565    */
566   ulong leafCellsCovered() const {
567     ulong num_leaves = 0;
568     foreach (S2CellId id; _cellIds) {
569       int inverted_level = S2CellId.MAX_LEVEL - id.level();
570       num_leaves += (1uL << (inverted_level << 1));
571     }
572     return num_leaves;
573   }
574 
575   /**
576    * Approximates this cell union's area in steradians by summing the average
577    * area of each contained cell's average area, using the AverageArea method
578    * from the S2Cell class.  This is equivalent to the number of leaves covered,
579    * multiplied by the average area of a leaf.  Note that AverageArea does not
580    * take into account distortion of cell, and thus may be off by up to a
581    * factor of up to 1.7.
582    *
583    * NOTE: Since this is proportional to LeafCellsCovered(), it is
584    * always better to use that function if all you care about is
585    * the relative average area between objects.
586    */
587   double averageBasedArea() const {
588     return S2Cell.averageArea(S2CellId.MAX_LEVEL) * leafCellsCovered();
589   }
590 
591   // Calculates this cell union's area in steradians by summing the approximate
592   // area for each contained cell, using the ApproxArea method from the S2Cell
593   // class.
594   double approxArea() const {
595     double area = 0;
596     foreach (S2CellId id; _cellIds) {
597       area += (new S2Cell(id)).approxArea();
598     }
599     return area;
600   }
601 
602   /**
603    * Calculates this cell union's area in steradians by summing the exact area
604    * for each contained cell, using the Exact method from the S2Cell class.
605    */
606   double ExactArea() const {
607     double area = 0;
608     foreach (S2CellId id; _cellIds) {
609       area += (new S2Cell(id)).exactArea();
610     }
611     return area;
612   }
613 
614   /// Return true if two cell unions are identical.
615   override
616   bool opEquals(Object o) {
617     S2CellUnion y = cast(S2CellUnion) o;
618     if (y is null) return false;
619     return cellIds() == y.cellIds();
620   }
621 
622   ////////////////////////////////////////////////////////////////////////
623   // S2Region interface (see s2region.h for details):
624 
625   override
626   S2Region clone() {
627     return new S2CellUnion(_cellIds, VerbatimFlag.VERBATIM);
628   }
629 
630   override
631   S2Cap getCapBound() {
632     // Compute the approximate centroid of the region.  This won't produce the
633     // bounding cap of minimal area, but it should be close enough.
634     if (_cellIds.length == 0) {
635       return S2Cap.empty();
636     }
637 
638     auto centroid = S2Point(0, 0, 0);
639     foreach (S2CellId id; _cellIds) {
640       double area = S2Cell.averageArea(id.level());
641       centroid += area * id.toS2Point();
642     }
643     if (centroid == S2Point(0, 0, 0)) {
644       centroid = S2Point(1, 0, 0);
645     } else {
646       centroid = centroid.normalize();
647     }
648 
649     // Use the centroid as the cap axis, and expand the cap angle so that it
650     // contains the bounding caps of all the individual cells.  Note that it is
651     // *not* sufficient to just bound all the cell vertices because the bounding
652     // cap may be concave (i.e. cover more than one hemisphere).
653     S2Cap cap = S2Cap.fromPoint(centroid);
654     for (Iterator iter = begin(); iter != end(); iter++) {
655       S2CellId id = iter.getValue();
656       cap.addCap((new S2Cell(id)).getCapBound());
657     }
658     return cap;
659   }
660 
661   override
662   S2LatLngRect getRectBound() {
663     S2LatLngRect bound = S2LatLngRect.empty();
664     for (Iterator iter = begin(); iter != end(); iter++) {
665       S2CellId id = iter.getValue();
666       bound = bound.unite((new S2Cell(id)).getRectBound());
667     }
668     return bound;
669   }
670 
671   override
672   void getCellUnionBound(out S2CellId[] cellIds) {
673     cellIds = _cellIds.dup;
674   }
675 
676   /// This is a fast operation (logarithmic in the size of the cell union).
677   override
678   bool contains(in S2Cell cell) const {
679     return contains(cell.id());
680   }
681 
682   /// This is a fast operation (logarithmic in the size of the cell union).
683   override
684   bool mayIntersect(in S2Cell cell) const {
685     return intersects(cell.id());
686   }
687 
688   /**
689    * The point 'p' does not need to be normalized.
690    * This is a fast operation (logarithmic in the size of the cell union).
691    */
692   bool contains(in S2Point p) const {
693     return contains(S2CellId(p));
694   }
695 
696   /**
697    * Appends a serialized representation of the S2CellUnion to "encoder".
698    *
699    * REQUIRES: "encoder" uses the default constructor, so that its buffer
700    *           can be enlarged as necessary by calling Ensure(int).
701    */
702   void encode(ORangeT)(Encoder!ORangeT encoder) const {
703     // Unsigned char for version number, and N+1 uint64's for N cell_ids
704     // (1 for vector length, N for the ids).
705     encoder.ensure(ubyte.sizeof + ulong.sizeof * (1 + _cellIds.length));
706 
707     encoder.put8(CURRENT_LOSSLESS_ENCODING_VERSION_NUMBER);
708     encoder.put64(_cellIds.length);
709     foreach (cell_id; _cellIds) {
710       cell_id.encode(encoder);
711     }
712   }
713 
714   /// Decodes an S2CellUnion encoded with Encode().  Returns true on success.
715   bool decode(IRangeT)(Decoder!IRangeT decoder) {
716     // Should contain at least version and vector length.
717     if (decoder.avail() < ubyte.sizeof + ulong.sizeof) return false;
718     ubyte versionNum = decoder.get8();
719     if (versionNum > CURRENT_LOSSLESS_ENCODING_VERSION_NUMBER) return false;
720 
721     ulong num_cells = decoder.get64();
722     if (num_cells > S2CELL_UNION_DECODE_MAX_NUM_CELLS) {
723       return false;
724     }
725 
726     auto temp_cell_ids = new S2CellId[num_cells];
727     for (int i = 0; i < num_cells; ++i) {
728       if (!temp_cell_ids[i].decode(decoder)) return false;
729     }
730     _cellIds = temp_cell_ids;
731     return true;
732   }
733 
734   ////////////////////////////////////////////////////////////////////////
735   // Static methods intended for high-performance clients that prefer to
736   // manage their own storage.
737 
738   /**
739    * Like Normalize(), but works with a vector of S2CellIds.
740    * Equivalent to:
741    *   *cell_ids = S2CellUnion(std::move(*cell_ids)).Release();
742    */
743   static bool normalize(ref S2CellId[] ids) {
744     // Optimize the representation by discarding cells contained by other cells,
745     // and looking for cases where all subcells of a parent cell are present.
746     ids = array(sort(ids));
747     int output = 0;
748     foreach (id; ids) {
749       // Check whether this cell is contained by the previous cell.
750       if (output > 0 && ids[output - 1].contains(id)) continue;
751 
752       // Discard any previous cells contained by this cell.
753       while (output > 0 && id.contains(ids[output - 1])) --output;
754 
755       // Check whether the last 3 elements plus "id" can be collapsed into a
756       // single parent cell.
757       while (output >= 3 && areSiblings(ids[output - 3], ids[output - 2], ids[output - 1], id)) {
758         // Replace four children by their parent cell.
759         id = id.parent();
760         output -= 3;
761       }
762       ids[output++] = id;
763     }
764     if (ids.length == output) return false;
765     ids.length = output;
766     return true;
767   }
768 
769 
770   // Like Denormalize(), but works with a vector of S2CellIds.
771   // REQUIRES: out != &in
772   static void denormalize(
773       in S2CellId[] input, int min_level, int level_mod, out S2CellId[] output)
774   in {
775     assert(min_level >= 0);
776     assert(min_level <=  S2CellId.MAX_LEVEL);
777     assert(level_mod >= 1);
778     assert(level_mod <= 3);
779     assert(input.length == 0 || output !is input);
780   } do {
781     output.reserve(input.length);
782     foreach (S2CellId id; input) {
783       int level = id.level();
784       int new_level = max(min_level, level);
785       if (level_mod > 1) {
786         // Round up so that (new_level - min_level) is a multiple of level_mod.
787         // (Note that S2CellId::kMaxLevel is a multiple of 1, 2, and 3.)
788         new_level += (S2CellId.MAX_LEVEL - (new_level - min_level)) % level_mod;
789         new_level = min(S2CellId.MAX_LEVEL, new_level);
790       }
791       if (new_level == level) {
792         output ~= id;
793       } else {
794         S2CellId end = id.childEnd(new_level);
795         for (id = id.childBegin(new_level); id != end; id = id.next()) {
796           output ~= id;
797         }
798       }
799     }
800   }
801 
802   /**
803    * Like GetIntersection(), but works directly with vectors of S2CellIds,
804    * Equivalent to:
805    *
806    *    *out = S2CellUnion(x).Intersection(S2CellUnion(y)).Release()
807    *
808    * except that this method has slightly more relaxed normalization
809    * requirements: the input vectors may contain groups of 4 child cells that
810    * all have the same parent.  (In a normalized S2CellUnion, such groups are
811    * always replaced by the parent cell.)
812    */
813   static void getIntersection(in S2CellId[] x, in S2CellId[] y, out S2CellId[] output)
814   in {
815     import std.conv : to;
816     assert(output.length == 0 || output !is x);
817     assert(output.length == 0 || output !is y);
818     assert(isSorted(x), "x is unsorted: x=" ~ to!string(x));
819     assert(isSorted(y), "y is unsorted: y=" ~ to!string(y));
820   } out {
821     // The output is generated in sorted order.
822     assert(isSorted(output));
823   } do {
824     // This is a fairly efficient calculation that uses binary search to skip
825     // over sections of both input vectors.  It takes logarithmic time if all the
826     // cells of "x" come before or after all the cells of "y" in S2CellId order.
827     size_t xPos = 0;
828     size_t yPos = 0;
829     while (xPos < x.length && yPos < y.length) {
830       S2CellId xPosMin = x[xPos].rangeMin();
831       S2CellId yPosMin = y[yPos].rangeMin();
832       if (xPosMin > yPosMin) {
833         // Either x[xPos].contains(*i) or the two cells are disjoint.
834         if (x[xPos] <= y[yPos].rangeMax()) {
835           output ~= x[xPos++];
836         } else {
837           // Advance "j" to the first cell possibly contained by *i.
838           yPos++;
839           long skipAmount = countUntil!(a => a >= xPosMin)(y[yPos .. $]);
840           if (skipAmount == -1) {
841             yPos = y.length;
842           } else {
843             yPos += skipAmount;
844           }
845           // The previous cell *(j-1) may now contain *i.
846           if (x[xPos] <= y[yPos - 1].rangeMax()) {
847             yPos--;
848           }
849         }
850       } else if (yPosMin > xPosMin) {
851         // Identical to the code above with "i" and "j" reversed.
852         if (y[yPos] <= x[xPos].rangeMax()) {
853           output ~= y[yPos++];
854         } else {
855           xPos++;
856           long skipAmount = countUntil!(a => a >= yPosMin)(x[xPos .. $]);
857           if (skipAmount == -1) {
858             xPos = x.length;
859           } else {
860             xPos += skipAmount;
861           }
862           if (y[yPos] <= x[xPos - 1].rangeMax()) {
863             --xPos;
864           }
865         }
866       } else {
867         // "i" and "j" have the same range_min(), so one contains the other.
868         if (x[xPos] < y[yPos])
869           output ~= x[xPos++];
870         else
871           output ~= y[yPos++];
872       }
873     }
874   }
875 
876 package:
877   // Internal constructor that does not check "cell_ids" for validity.
878   this(in S2CellId[] cell_ids, VerbatimFlag verbatim) {
879     _cellIds = cell_ids.dup;
880   }
881 
882   // Internal constructor that does not check "cell_ids" for validity.
883   enum VerbatimFlag {
884     VERBATIM
885   }
886 
887 private:
888   // Converts a vector of uint64 to a vector of S2CellIds.
889   static S2CellId[] toS2CellIds(in ulong[] ids) {
890     S2CellId[] cell_ids;
891     cell_ids.reserve(ids.length);
892     foreach (id; ids) cell_ids ~= S2CellId(id);
893     return cell_ids;
894   }
895 
896   S2CellId[] _cellIds;
897 }