1 /**
2    A 64-bit unsigned integer that uniquely identifies a cell in S2 cell decomposition.
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.s2cell_id;
22 
23 import s2.r1interval;
24 import s2.r2point;
25 import s2.r2rect;
26 import s2.s1angle;
27 import s2.s2latlng;
28 import s2.s2point;
29 import s2.util.coding.coder;
30 import s2coords = s2.s2coords;
31 
32 import algorithm = std.algorithm;
33 import array = std.array;
34 import bitop = core.bitop;
35 import conv = std.conv;
36 import format = std.format;
37 import math = std.math;
38 import range = std.range;
39 
40 /**
41  * An S2CellId is a 64-bit unsigned integer that uniquely identifies a
42  * cell in the S2 cell decomposition.
43  *
44  * It has the following format:
45  *
46  *   id = [face][face_pos]
47  *
48  *   face:     a 3-bit number (range 0..5) encoding the cube face.
49  *
50  *   face_pos: a 61-bit number encoding the position of the center of this
51  *             cell along the Hilbert curve over this face (see the Wiki
52  *             pages for details).
53  *
54  * Sequentially increasing cell ids follow a continuous space-filling curve
55  * over the entire sphere.  They have the following properties:
56  *
57  *  - The id of a cell at level k consists of a 3-bit face number followed
58  *    by k bit pairs that recursively select one of the four children of
59  *    each cell.  The next bit is always 1, and all other bits are 0.
60  *    Therefore, the level of a cell is determined by the position of its
61  *    lowest-numbered bit that is turned on (for a cell at level k, this
62  *    position is 2 * (MAX_LEVEL - k).)
63  *
64  *  - The id of a parent cell is at the midpoint of the range of ids spanned
65  *    by its children (or by its descendants at any level).
66  *
67  * Leaf cells are often used to represent points on the unit sphere, and
68  * this class provides methods for converting directly between these two
69  * representations.  For cells that represent 2D regions rather than
70  * discrete point, it is better to use the S2Cell class.
71  *
72  * This class is intended to be copied by value as desired.  It uses
73  * the default copy constructor and assignment operator.
74  */
75 struct S2CellId {
76 private:
77   // The default constructor returns an invalid cell id.
78   ulong _id = 0;
79 
80 public:
81   // The extra position bit (61 rather than 60) let us encode each cell as its
82   // Hilbert curve position at the cell center (which is halfway along the
83   // portion of the Hilbert curve that fills that cell).
84   static immutable int FACE_BITS = 3;
85   static immutable int NUM_FACES = 6;
86   static immutable int MAX_LEVEL = s2coords.MAX_CELL_LEVEL;  // Valid levels: 0..MAX_LEVEL
87   static immutable int POS_BITS = 2 * MAX_LEVEL + 1;
88   static immutable int MAX_SIZE = 1 << MAX_LEVEL;
89 
90   /// Construct a cell from a raw numerical S2 Cell ID.
91   this(ulong id) {
92     _id = id;
93   }
94 
95   /**
96    * Construct a leaf cell containing the given point "p".  Usually there is
97    * is exactly one such cell, but for points along the edge of a cell, any
98    * adjacent cell may be (deterministically) chosen.  This is because
99    * S2CellIds are considered to be closed sets.  The returned cell will
100    * always contain the given point, i.e.
101    *
102    *   S2Cell(S2CellId(p)).Contains(p)
103    *
104    * is always true.  The point "p" does not need to be normalized.
105    *
106    * If instead you want every point to be contained by exactly one S2Cell,
107    * you will need to convert the S2CellIds to S2Loops (which implement point
108    * containment this way).
109    */
110   this(in S2Point p) {
111     double u, v;
112     int face = s2coords.XYZtoFaceUV(p, u, v);
113     int i = s2coords.STtoIJ(s2coords.UVtoST(u));
114     int j = s2coords.STtoIJ(s2coords.UVtoST(v));
115     _id = fromFaceIJ(face, i, j).id();
116   }
117 
118   /// Constructs a cell from a latitude-longitude coordinate.
119   this(in S2LatLng ll) {
120     this(ll.toS2Point());
121   }
122 
123   /// Returns an representative empty cell smaller than any valid cell..
124   static S2CellId none() {
125     return S2CellId();
126   }
127 
128   /**
129    * Returns an invalid cell id guaranteed to be larger than any
130    * valid cell id.  Useful for creating indexes.
131    */
132   static S2CellId sentinel() {
133     return S2CellId(ulong.max);
134   }
135 
136   /// Return the cell corresponding to a given S2 cube face.
137   static S2CellId fromFace(int face) {
138     return S2CellId((cast(ulong) face << POS_BITS) + lsbForLevel(0));
139   }
140 
141   /**
142    * Returns a cell given its face \(range 0..5\), Hilbert curve position within
143    * that face \(an unsigned integer with S2CellId::POS_BITS bits\), and level
144    * \(range 0..MAX_LEVEL\).  The given position will be modified to correspond
145    * to the Hilbert curve position at the center of the returned cell.  This
146    * is a static function rather than a constructor in order to indicate what
147    * the arguments represent.
148    */
149   static S2CellId fromFacePosLevel(int face, ulong pos, int level) {
150     auto cell = S2CellId((cast(ulong) face << POS_BITS) + (pos | 1));
151     return cell.parent(level);
152   }
153 
154   /**
155    * Returns the direction vector corresponding to the center of the given
156    * cell.  The vector returned by ToPointRaw is not necessarily unit length.
157    * This method returns the same result as S2Cell.getCenter().
158    *
159    * The maximum directional error in ToPoint() (compared to the exact
160    * mathematical result) is 1.5 * double.epsilon radians, and the maximum length
161    * error is 2 * double.epsilon \(the same as Normalize\).
162    */
163   S2Point toS2Point() const {
164     return toS2PointRaw().normalize();
165   }
166 
167   S2Point toS2PointRaw() const {
168     int si, ti;
169     int face = getCenterSiTi(si, ti);
170     return s2coords.FaceSiTitoXYZ(face, si, ti);
171   }
172 
173   /// Returns the center of the cell in (s,t) coordinates (see s2coords.d).
174   R2Point getCenterST() const {
175     int si, ti;
176     getCenterSiTi(si, ti);
177     return R2Point(s2coords.SiTitoST(si), s2coords.SiTitoST(ti));
178   }
179 
180   /// Returns the edge length of this cell in (s,t)-space.
181   double getSizeST() const {
182     return getSizeST(level());
183   }
184 
185 
186   /// Returns the edge length in (s,t)-space of cells at the given level.
187   static double getSizeST(int level) {
188     return s2coords.IJtoSTMin(getSizeIJ(level));
189   }
190 
191   /// Returns the bounds of this cell in (s,t)-space.
192   R2Rect getBoundST() const {
193     double size = getSizeST();
194     return R2Rect.fromCenterSize(getCenterST(), R2Point(size, size));
195   }
196 
197   /**
198    * Returns the center of the cell in (u,v) coordinates (see s2coords.h).
199    * Note that the center of the cell is defined as the point at which it is
200    * recursively subdivided into four children; in general, it is not at the
201    * midpoint of the (u,v) rectangle covered by the cell.
202    */
203   R2Point getCenterUV() const {
204     int si, ti;
205     getCenterSiTi(si, ti);
206     return R2Point(s2coords.STtoUV(s2coords.SiTitoST(si)), s2coords.STtoUV(s2coords.SiTitoST(ti)));
207   }
208 
209   /// Returns the bounds of this cell in (u,v)-space.
210   R2Rect getBoundUV() const {
211     int[2] ij;
212     toFaceIJOrientation(ij[0], ij[1]);
213     return IJLevelToBoundUV(ij, level());
214   }
215 
216   /**
217    * Expands a rectangle in (u,v)-space so that it contains all points within
218    * the given distance of the boundary, and return the smallest such
219    * rectangle.  If the distance is negative, then instead shrink this
220    * rectangle so that it excludes all points within the given absolute
221    * distance of the boundary.
222    *
223    * Distances are measured *on the sphere*, not in (u,v)-space.  For example,
224    * you can use this method to expand the (u,v)-bound of an S2CellId so that
225    * it contains all points within 5km of the original cell.  You can then
226    * test whether a point lies within the expanded bounds like this:
227    *
228    *   R2Point uv;
229    *   if (S2::FaceXYZtoUV(face, point, &uv) && bound.Contains(uv)) { ... }
230    *
231    * Limitations:
232    *
233    *  - Because the rectangle is drawn on one of the six cube-face planes
234    *    (i.e., {x,y,z} = +/-1), it can cover at most one hemisphere.  This
235    *    limits the maximum amount that a rectangle can be expanded.  For
236    *    example, S2CellId bounds can be expanded safely by at most 45 degrees
237    *    (about 5000 km on the Earth's surface).
238    *
239    *  - The implementation is not exact for negative distances.  The resulting
240    *    rectangle will exclude all points within the given distance of the
241    *    boundary but may be slightly smaller than necessary.
242    */
243   static R2Rect expandedByDistanceUV(in R2Rect uv, in S1Angle distance) {
244     // Expand each of the four sides of the rectangle just enough to include all
245     // points within the given distance of that side.  (The rectangle may be
246     // expanded by a different amount in (u,v)-space on each side.)
247     double u0 = uv[0][0];
248     double u1 = uv[0][1];
249     double v0 = uv[1][0];
250     double v1 = uv[1][1];
251     double max_u = algorithm.max(math.abs(u0), math.abs(u1));
252     double max_v = algorithm.max(math.abs(v0), math.abs(v1));
253     double sin_dist = sin(distance);
254     return R2Rect(
255         R1Interval(expandEndpoint(u0, max_v, -sin_dist), expandEndpoint(u1, max_v, sin_dist)),
256         R1Interval(expandEndpoint(v0, max_u, -sin_dist), expandEndpoint(v1, max_u, sin_dist)));
257   }
258 
259   /**
260    * This is a helper function for expandedByDistanceUV().
261    *
262    * Given an edge of the form (u,v0)-(u,v1), let max_v = max(abs(v0), abs(v1)).
263    * This method returns a new u-coordinate u' such that the distance from the
264    * line u=u' to the given edge (u,v0)-(u,v1) is exactly the given distance
265    * (which is specified as the sine of the angle corresponding to the distance).
266    */
267   private static double expandEndpoint(double u, double max_v, double sinDist) {
268     // This is based on solving a spherical right triangle, similar to the
269     // calculation in S2Cap::GetRectBound.
270     double sin_u_shift = sinDist * math.sqrt((1 + u * u + max_v * max_v) / (1 + u * u));
271     double cos_u_shift = math.sqrt(1 - sin_u_shift * sin_u_shift);
272     // The following is an expansion of tan(atan(u) + asin(sin_u_shift)).
273     return (cos_u_shift * u + sin_u_shift) / (cos_u_shift - sin_u_shift * u);
274   }
275 
276   /**
277    * Returns the (face, si, ti) coordinates of the center of the cell.  Note
278    * that although (si,ti) coordinates span the range [0,2**31] in general,
279    * the cell center coordinates are always in the range [1,2**31-1] and
280    * therefore can be represented using a signed 32-bit integer.
281    */
282   int getCenterSiTi(out int psi, out int pti) const {
283     // First we compute the discrete (i,j) coordinates of a leaf cell contained
284     // within the given cell.  Given that cells are represented by the Hilbert
285     // curve position corresponding at their center, it turns out that the cell
286     // returned by ToFaceIJOrientation is always one of two leaf cells closest
287     // to the center of the cell (unless the given cell is a leaf cell itself,
288     // in which case there is only one possibility).
289     //
290     // Given a cell of size s >= 2 (i.e. not a leaf cell), and letting (imin,
291     // jmin) be the coordinates of its lower left-hand corner, the leaf cell
292     // returned by ToFaceIJOrientation() is either (imin + s/2, jmin + s/2)
293     // (imin + s/2 - 1, jmin + s/2 - 1).  The first case is the one we want.
294     // We can distinguish these two cases by looking at the low bit of "i" or
295     // "j".  In the second case the low bit is one, unless s == 2 (i.e. the
296     // level just above leaf cells) in which case the low bit is zero.
297     //
298     // In the code below, the expression ((i ^ (int(_id) >> 2)) & 1) is true
299     // if we are in the second case described above.
300     int i, j;
301     int face = toFaceIJOrientation(i, j);
302     int delta = isLeaf() ? 1 : ((i ^ (cast(int) _id >> 2)) & 1) ? 2 : 0;
303 
304     // Note that (2 * {i,j} + delta) will never overflow a 32-bit integer.
305     psi = 2 * i + delta;
306     pti = 2 * j + delta;
307     return face;
308   }
309 
310   /// Returns the S2LatLng corresponding to the center of the given cell.
311   S2LatLng toLatLng() const {
312     return S2LatLng(toS2PointRaw());
313   }
314 
315   /// The 64-bit unique identifier for this cell.
316   @property
317   ulong id() const {
318     return _id;
319   }
320 
321   /**
322    * Returns true if id() represents a valid cell.
323    *
324    * All methods require isValid() to be true unless otherwise specified
325    * (although not all methods enforce this).
326    */
327   bool isValid() const {
328     return (face() < NUM_FACES && (lsb() & 0x1555555555555555uL));
329   }
330 
331   /// Which cube face this cell belongs to, in the range 0..5.
332   int face() const {
333     return _id >> POS_BITS;
334   }
335 
336   /**
337    * The position of the cell center along the Hilbert curve over this face,
338    * in the range 0..(2**POS_BITS-1).
339    */
340   ulong pos() const {
341     return _id & (~0uL >> FACE_BITS);
342   }
343 
344   /// Returns the subdivision level of the cell (range 0..MAX_LEVEL).
345   int level() const
346   in {
347     // We can't just DCHECK(isValid()) because we want level() to be to be
348     // defined for end-iterators, i.e. S2CellId::End(kLevel).  However there is
349     // no good way to define S2CellId::None().level(), so we do prohibit that.
350     assert(_id != 0);
351   } do {
352     // A special case for leaf cells is not worthwhile.
353     return MAX_LEVEL - (bitop.bsf(_id) >> 1);
354   }
355 
356   /// Returns the edge length of this cell in (i,j)-space.
357   int getSizeIJ() const {
358     return getSizeIJ(level());
359   }
360 
361   /// Like getSizeIJ(), but returns the size of cells at the given level.
362   static int getSizeIJ(int level) {
363     return 1 << (MAX_LEVEL - level);
364   }
365 
366   /**
367    * Returns true if this is a leaf cell (more efficient than checking
368    * whether level() == MAX_LEVEL).
369    */
370   bool isLeaf() const {
371     return cast(int) _id & 1;
372   }
373 
374   /**
375    * Return true if this is a top-level face cell (more efficient than
376    * checking whether level() == 0).
377    */
378   bool isFace() const {
379     return (_id & (lsbForLevel(0) - 1)) == 0;
380   }
381 
382   /**
383    * Return the child position (0..3) of this cell within its parent.
384    * REQUIRES: level() >= 1.
385    */
386   int childPosition() const {
387     // No need for a special implementation; the compiler optimizes this well.
388     return childPosition(level());
389   }
390 
391   /**
392    * Return the child position (0..3) of this cell's ancestor at the given
393    * level within its parent.  For example, child_position(1) returns the
394    * position of this cell's level-1 ancestor within its top-level face cell.
395    * REQUIRES: 1 <= level <= this->level().
396    */
397   int childPosition(int level) const
398   in {
399     assert(isValid());
400     assert(level >=  1);
401     assert(level <= this.level());
402   } do {
403     return cast(int)(_id >> (2 * (MAX_LEVEL - level) + 1)) & 3;
404   }
405 
406   /**
407    * These methods return the range of cell ids that are contained within this
408    * cell \(including itself\).
409    *
410    * The range is *inclusive* \(i.e. test using >= and <=\) and the return values of both methods
411    * are valid leaf cell ids.  In other words, `a.contains(b)` if and only if
412    * ---
413    *     (b >= a.range_min() && b <= a.range_max())
414    * ---
415    * If you want to iterate through all the descendants of this cell at a
416    * particular level, use `childBegin(level)` and `childEnd(level)` instead.
417    * Also see `maximumTile()`, which can be used to iterate through a range of
418    * cells using S2CellIds at different levels that are as large as possible.
419    *
420    * If you need to convert the range to a semi-open interval \[min, limit\)
421    * \(e.g., in order to use a key-value store that only supports semi-open
422    * range queries\), do not attempt to define "limit" as `range_max.next()`.
423    * The problem is that leaf S2CellIds are 2 units apart, so the semi-open
424    * interval \[min, limit\) includes an additional value `(range_max.id() + 1)`
425    * which is happens to be a valid S2CellId about one-third of the time and
426    * is *never* contained by this cell.  \(It always correpsonds to a cell that
427    * is larger than this one.\)  You can define "limit" as `(range_max.id() + 1)`
428    * if necessary \(which is not always a valid S2CellId but can still be used
429    * with FromToken/ToToken\), or you can convert `rangeMax()` to the key space
430    * of your key-value store and define "limit" as `successor(key)`.
431    *
432    * Note that `Sentinel().rangeMin() == Sentinel.rangeMax() == Sentinel()`.
433    */
434   S2CellId rangeMin() const {
435     return S2CellId(_id - (lsb() - 1));
436   }
437 
438   S2CellId rangeMax() const {
439     return S2CellId(_id + (lsb() - 1));
440   }
441 
442   /// Returns true if the given cell is contained within this one.
443   bool contains(S2CellId other) const
444   in {
445     assert(isValid(), "Invalid cell: " ~ toString());
446     assert(other.isValid());
447   } do {
448     return other >= rangeMin() && other <= rangeMax();
449   }
450 
451 
452   /// Returns true if the given cell intersects this one.
453   bool intersects(S2CellId other) const
454   in {
455     assert(isValid());
456     assert(other.isValid());
457   } do {
458     return other.rangeMin() <= rangeMax() && other.rangeMax() >= rangeMin();
459   }
460 
461   /**
462    * Returns the cell at the previous level or at the given level (which must
463    * be less than or equal to the current level).
464    */
465   S2CellId parent() const
466   in {
467     assert(isValid());
468     assert(!isFace());
469   } do {
470     ulong new_lsb = lsb() << 2;
471     return S2CellId((_id & (~new_lsb + 1)) | new_lsb);
472   }
473 
474   S2CellId parent(int level) const
475   in {
476     assert(isValid(), format.format("Cannot get parent of invalid cell %s.\n  id=%x, face=%d, lsb=%d", toString(), id(), face(), lsb()));
477     assert(level >= 0);
478     assert(level <= this.level());
479   } do {
480     ulong new_lsb = lsbForLevel(level);
481     return S2CellId((_id & (~new_lsb + 1)) | new_lsb);
482   }
483 
484   /**
485    * Returns the immediate child of this cell at the given traversal order
486    * position (in the range 0 to 3).  This cell must not be a leaf cell.
487    */
488   S2CellId child(int position) const
489   in {
490     assert(isValid());
491     assert(!isLeaf());
492   } do {
493     // To change the level, we need to move the least-significant bit two
494     // positions downward.  We do this by subtracting (4 * new_lsb) and adding
495     // new_lsb.  Then to advance to the given child cell, we add
496     // (2 * position * new_lsb).
497     ulong new_lsb = lsb() >> 2;
498     return S2CellId(_id + (2 * position + 1 - 4) * new_lsb);
499   }
500 
501   /**
502    * Iterator-style methods for traversing the immediate children of a cell or
503    * all of the children at a given level (greater than or equal to the current
504    * level).  Note that the end value is exclusive, just like standard STL
505    * iterators, and may not even be a valid cell id.  You should iterate using
506    * code like this:
507    *
508    *   for(S2CellId c = id.child_begin(); c != id.child_end(); c = c.next())
509    *     ...
510    *
511    * The convention for advancing the iterator is "c = c.next()" rather
512    * than "++c" to avoid possible confusion with incrementing the
513    * underlying 64-bit cell id.
514    */
515   S2CellId childBegin() const
516   in {
517     assert(isValid());
518     assert(!isLeaf());
519   } do {
520     ulong old_lsb = lsb();
521     return S2CellId(_id - old_lsb + (old_lsb >> 2));
522   }
523 
524   S2CellId childBegin(int level) const
525   in {
526     assert(isValid());
527     assert(level >= this.level());
528     assert(level <= MAX_LEVEL);
529   } do {
530     return S2CellId(_id - lsb() + lsbForLevel(level));
531   }
532 
533   S2CellId childEnd() const
534   in {
535     assert(isValid());
536     assert(!isLeaf());
537   } do {
538     ulong old_lsb = lsb();
539     return S2CellId(_id + old_lsb + (old_lsb >> 2));
540   }
541 
542   S2CellId childEnd(int level) const
543   in {
544     assert(isValid());
545     assert(level >= this.level());
546     assert(level <= MAX_LEVEL);
547   } do {
548     return S2CellId(_id + lsb() + lsbForLevel(level));
549   }
550 
551   /**
552    * Returns the next/previous cell at the same level along the Hilbert curve.
553    * Works correctly when advancing from one face to the next, but
554    * does *not* wrap around from the last face to the first or vice versa.
555    */
556   S2CellId next() const {
557     return S2CellId(_id + (lsb() << 1));
558   }
559 
560   S2CellId prev() const {
561     return S2CellId(_id - (lsb() << 1));
562   }
563 
564   /**
565    * This method advances or retreats the indicated number of steps along the
566    * Hilbert curve at the current level, and returns the new position.  The
567    * position is never advanced past End() or before Begin().
568    */
569   S2CellId advance(long steps) const {
570     if (steps == 0) {
571       return this;
572     }
573 
574     // We clamp the number of steps if necessary to ensure that we do not
575     // advance past the End() or before the Begin() of this level.  Note that
576     // min_steps and max_steps always fit in a signed 64-bit integer.
577 
578     int step_shift = 2 * (MAX_LEVEL - level()) + 1;
579     if (steps < 0) {
580       long min_steps = -cast(long)(_id >> step_shift);
581       if (steps < min_steps) steps = min_steps;
582     } else {
583       long max_steps = (WRAP_OFFSET + lsb() - _id) >> step_shift;
584       if (steps > max_steps) steps = max_steps;
585     }
586     // If steps is negative, then shifting it left has undefined behavior.
587     // Cast to uint64 for a 2's complement answer.
588     return S2CellId(_id + (cast(ulong) steps << step_shift));
589   }
590 
591   /**
592    * Returns the number of steps that this cell is from Begin(level()). The
593    * return value is always non-negative.
594    */
595   long distanceFromBegin() const {
596     const int step_shift = 2 * (MAX_LEVEL - level()) + 1;
597     return _id >> step_shift;
598   }
599 
600   /**
601    * Like next() and prev(), but these methods wrap around from the last face
602    * to the first and vice versa.  They should *not* be used for iteration in
603    * conjunction with child_begin(), child_end(), Begin(), or End().  The
604    * input must be a valid cell id.
605    */
606   S2CellId nextWrap() const
607   in {
608     assert(isValid());
609   } do {
610     S2CellId n = next();
611     if (n._id < WRAP_OFFSET) return n;
612     return S2CellId(n._id - WRAP_OFFSET);
613   }
614 
615   S2CellId prevWrap() const
616   in {
617     assert(isValid());
618   } do {
619     S2CellId p = prev();
620     if (p._id < WRAP_OFFSET) return p;
621     return S2CellId(p._id + WRAP_OFFSET);
622   }
623 
624   /**
625    * This method advances or retreats the indicated number of steps along the
626    * Hilbert curve at the current level, and returns the new position.  The
627    * position wraps between the first and last faces as necessary.  The input
628    * must be a valid cell id.
629    */
630   S2CellId advanceWrap(long steps) const
631   in {
632     assert(isValid());
633   } do {
634     if (steps == 0) {
635       return this;
636     }
637 
638     int step_shift = 2 * (MAX_LEVEL - level()) + 1;
639     if (steps < 0) {
640       long min_steps = -cast(long)(_id >> step_shift);
641       if (steps < min_steps) {
642         long step_wrap = WRAP_OFFSET >> step_shift;
643         steps %= step_wrap;
644         if (steps < min_steps) steps += step_wrap;
645       }
646     } else {
647       // Unlike advance(), we don't want to return End(level).
648       long max_steps = (WRAP_OFFSET - _id) >> step_shift;
649       if (steps > max_steps) {
650         long step_wrap = WRAP_OFFSET >> step_shift;
651         steps %= step_wrap;
652         if (steps > max_steps) steps -= step_wrap;
653       }
654     }
655     return S2CellId(_id + (cast(ulong)(steps) << step_shift));
656   }
657 
658   /**
659    * Returns the largest cell with the same `range_min()` and such that
660    * `range_max() < limit.range_min()`.
661    *
662    * Returns "limit" if no such cell exists. This method can be used to generate a small set
663    * of S2CellIds that covers a given range \(a "tiling"\).  This example shows how to
664    * generate a tiling for a semi-open range of leaf cells \[start, limit\):
665    * ---
666    *   for (S2CellId id = start.maximum_tile(limit);
667    *        id != limit; id = id.next().maximum_tile(limit)) { ... }
668    * ---
669    * Note that in general the cells in the tiling will be of different sizes;
670    * they gradually get larger \(near the middle of the range\) and then
671    * gradually get smaller \(as "limit" is approached\).
672    */
673   S2CellId maximumTile(S2CellId limit) const {
674     S2CellId id = this;
675     S2CellId start = id.rangeMin();
676     if (start >= limit.rangeMin()) {
677       return limit;
678     }
679 
680     if (id.rangeMax() >= limit) {
681       // The cell is too large.  Shrink it.  Note that when generating coverings
682       // of S2CellId ranges, this loop usually executes only once.  Also because
683       // id.range_min() < limit.range_min(), we will always exit the loop by the
684       // time we reach a leaf cell.
685       do {
686         id = id.child(0);
687       } while (id.rangeMax() >= limit);
688       return id;
689     }
690     // The cell may be too small.  Grow it if necessary.  Note that generally
691     // this loop only iterates once.
692     while (!id.isFace()) {
693       S2CellId parent = id.parent();
694       if (parent.rangeMin() != start || parent.rangeMax() >= limit) {
695         break;
696       }
697       id = parent;
698     }
699     return id;
700   }
701 
702   /**
703    * Returns the level of the lowest common ancestor of this cell and "other",
704    * that is, the maximum level such that parent(level) == other.parent(level).
705    * Returns -1 if the two cells do not have any common ancestor (i.e., they
706    * are from different faces).
707    */
708   int getCommonAncestorLevel(S2CellId other) const {
709     // Basically we find the first bit position at which the two S2CellIds
710     // differ and convert that to a level.  The max() below is necessary for the
711     // case where one S2CellId is a descendant of the other.
712     ulong bits = algorithm.max(id() ^ other.id(), algorithm.max(lsb(), other.lsb()));
713     assert(bits != 0);  // Because lsb() is non-zero.
714 
715     // Compute the position of the most significant bit, and then map the bit
716     // position as follows:
717     // {0} -> 30, {1,2} -> 29, {3,4} -> 28, ... , {59,60} -> 0, {61,62,63} -> -1.
718     return algorithm.max(60 - bitop.bsr(bits), -1) >> 1;
719   }
720 
721   /**
722    * Iterator-style methods for traversing all the cells along the Hilbert
723    * curve at a given level (across all 6 faces of the cube).  Note that the
724    * end value is exclusive (just like standard STL iterators), and is not a
725    * valid cell id.
726    */
727   static S2CellId begin(int level) {
728     return fromFace(0).childBegin(level);
729   }
730 
731   static S2CellId end(int level) {
732     return fromFace(5).childEnd(level);
733   }
734 
735   /**
736    * Methods to encode and decode cell ids to compact text strings suitable
737    * for display or indexing.  Cells at lower levels (i.e. larger cells) are
738    * encoded into fewer characters.  The maximum token length is 16.
739    *
740    * Tokens preserve ordering, i.e. ToToken(x) < ToToken(y) iff x < y.
741    *
742    * ToToken() returns a string by value for convenience; the compiler
743    * does this without intermediate copying in most cases.
744    *
745    * These methods guarantee that FromToken(ToToken(x)) == x even when
746    * "x" is an invalid cell id.  All tokens are alphanumeric strings.
747    * FromToken() returns S2CellId::None() for malformed inputs.
748    */
749   string toToken() const {
750     // Simple implementation: print the id in hex without trailing zeros.
751     // Using hex has the advantage that the tokens are case-insensitive, all
752     // characters are alphanumeric, no characters require any special escaping
753     // in queries for most indexing systems, and it's easy to compare cell
754     // tokens against the feature ids of the corresponding features.
755     //
756     // Using base 64 would produce slightly shorter tokens, but for typical cell
757     // sizes used during indexing (up to level 15 or so) the average savings
758     // would be less than 2 bytes per cell which doesn't seem worth it.
759 
760     // "0" with trailing 0s stripped is the empty string, which is not a
761     // reasonable token.  Encode as "X".
762     if (_id == 0) {
763       return "X";
764     }
765     const size_t num_zero_digits = bitop.bsf(_id) / 4;
766     return hexFormatString(_id >> (4 * num_zero_digits), 16 - num_zero_digits);
767   }
768 
769   // Print the num_digits low order hex digits.
770   private static string hexFormatString(ulong val, size_t num_digits) {
771     char[] result = array.replicate([' '], num_digits);
772     for (; num_digits--; val >>= 4) {
773       result[num_digits] = "0123456789abcdef"[val & 0xF];
774     }
775     return result.idup;
776   }
777 
778   static S2CellId fromToken(string token) {
779     if (token.length > 16) {
780       return S2CellId.none();
781     }
782     ulong id = 0;
783     for (int i = 0, pos = 60; i < token.length; ++i, pos -= 4) {
784       ulong d;
785       if ('0' <= token[i] && token[i] <= '9') {
786         d = token[i] - '0';
787       } else if ('a' <= token[i] && token[i] <= 'f') {
788         d = token[i] - 'a' + 10;
789       } else if ('A' <= token[i] && token[i] <= 'F') {
790         d = token[i] - 'A' + 10;
791       } else {
792         return S2CellId.none();
793       }
794       id |= d << pos;
795     }
796     return S2CellId(id);
797   }
798 
799   /**
800    * Use encoder to generate a serialized representation of this cell id.
801    * Can also encode an invalid cell.
802    */
803   void encode(ORangeT)(Encoder!ORangeT encoder) const {
804     encoder.ensure(ulong.sizeof);  // A single uint64.
805     encoder.put64(_id);
806   }
807 
808   /// Decodes an S2CellId encoded by Encode(). Returns true on success.
809   bool decode(IRangeT)(Decoder!IRangeT decoder) {
810     if (decoder.avail() < ulong.sizeof) return false;
811     _id = decoder.get64();
812     return true;
813   }
814 
815   /**
816    * Creates a human readable debug string.  Used for << and available for
817    * direct usage as well.  The format is "f/dd..d" where "f" is a digit in
818    * the range [0-5] representing the S2CellId face, and "dd..d" is a string
819    * of digits in the range [0-3] representing each child's position with
820    * respect to its parent.  (Note that the latter string may be empty.)
821    *
822    * For example "4/" represents S2CellId::FromFace(4), and "3/02" represents
823    * S2CellId::FromFace(3).child(0).child(2).
824    */
825   string toString() const {
826     if (!isValid()) {
827       return "Invalid: " ~ format.format("%016x", id());
828     }
829     string s = conv.to!string(face()) ~ "/";
830     for (int current_level = 1; current_level <= level(); ++current_level) {
831       // Avoid dependencies of SimpleItoA, and slowness of StrAppend &
832       // std::to_string.
833       s ~= "0123"[childPosition(current_level)];
834     }
835     return s;
836   }
837 
838   /**
839    * Converts a string in the format returned by ToString() to an S2CellId.
840    * Returns S2CellId::None() if the string could not be parsed.
841    *
842    * The method name includes "Debug" in order to avoid possible confusion
843    * with FromToken() above.
844    */
845   static S2CellId fromDebugString(in string str) {
846     // This function is reasonably efficient, but is only intended for use in
847     // tests.
848     int level = cast(int)(str.length - 2);
849     if (level < 0 || level > MAX_LEVEL) {
850       return S2CellId.none();
851     }
852     int face = str[0] - '0';
853     if (face < 0 || face > 5 || str[1] != '/') {
854       return S2CellId.none();
855     }
856     S2CellId id = S2CellId.fromFace(face);
857     for (int i = 2; i < str.length; ++i) {
858       int child_pos = str[i] - '0';
859       if (child_pos < 0 || child_pos > 3) {
860         return S2CellId.none();
861       }
862       id = id.child(child_pos);
863     }
864     return id;
865   }
866 
867   /**
868    * Returns the four cells that are adjacent across the cell's four edges.
869    * Neighbors are returned in the order defined by S2Cell::GetEdge.  All
870    * neighbors are guaranteed to be distinct.
871    */
872   void getEdgeNeighbors(out S2CellId[4] neighbors) const {
873     int i, j;
874     int level = level();
875     int size = getSizeIJ(level);
876     int face = toFaceIJOrientation(i, j);
877 
878     // Edges 0, 1, 2, 3 are in the down, right, up, left directions.
879     neighbors[0] = fromFaceIJSame(face, i, j - size, j - size >= 0).parent(level);
880     neighbors[1] = fromFaceIJSame(face, i + size, j, i + size < MAX_SIZE).parent(level);
881     neighbors[2] = fromFaceIJSame(face, i, j + size, j + size < MAX_SIZE).parent(level);
882     neighbors[3] = fromFaceIJSame(face, i - size, j, i - size >= 0).parent(level);
883   }
884 
885   /**
886    * Returns the neighbors of closest vertex to this cell at the given level,
887    * by appending them to "output".  Normally there are four neighbors, but
888    * the closest vertex may only have three neighbors if it is one of the 8
889    * cube vertices.
890    *
891    * Requires: level < this->level(), so that we can determine which vertex is
892    * closest (in particular, level == MAX_LEVEL is not allowed).
893    */
894   void appendVertexNeighbors(RangeT)(int level, ref RangeT output) const
895   if (range.isOutputRange!(RangeT, S2CellId))
896   in {
897     // "level" must be strictly less than this cell's level so that we can
898     // determine which vertex this cell is closest to.
899     assert(level < this.level());
900   } do {
901     int i, j;
902     int face = toFaceIJOrientation(i, j);
903 
904     // Determine the i- and j-offsets to the closest neighboring cell in each
905     // direction.  This involves looking at the next bit of "i" and "j" to
906     // determine which quadrant of this->parent(level) this cell lies in.
907     int halfsize = getSizeIJ(level + 1);
908     int size = halfsize << 1;
909     bool isame, jsame;
910     int ioffset, joffset;
911     if (i & halfsize) {
912       ioffset = size;
913       isame = (i + size) < MAX_SIZE;
914     } else {
915       ioffset = -size;
916       isame = (i - size) >= 0;
917     }
918     if (j & halfsize) {
919       joffset = size;
920       jsame = (j + size) < MAX_SIZE;
921     } else {
922       joffset = -size;
923       jsame = (j - size) >= 0;
924     }
925 
926     output ~= parent(level);
927     output ~= fromFaceIJSame(face, i + ioffset, j, isame).parent(level);
928     output ~= fromFaceIJSame(face, i, j + joffset, jsame).parent(level);
929     // If i- and j- edge neighbors are *both* on a different face, then this
930     // vertex only has three neighbors (it is one of the 8 cube vertices).
931     if (isame || jsame) {
932       output ~= fromFaceIJSame(face, i + ioffset, j + joffset, isame && jsame).parent(level);
933     }
934   }
935 
936   /**
937    * Append all neighbors of this cell at the given level to "output".  Two
938    * cells X and Y are neighbors if their boundaries intersect but their
939    * interiors do not.  In particular, two cells that intersect at a single
940    * point are neighbors.  Note that for cells adjacent to a face vertex, the
941    * same neighbor may be appended more than once.
942    *
943    * REQUIRES: nbr_level >= this->level().
944    */
945   void appendAllNeighbors(ORangeT)(int nbr_level, ref ORangeT output) const
946   if (range.isOutputRange!(ORangeT, S2CellId))
947   in {
948     assert(nbr_level >= level());
949   } do {
950     int i, j;
951     int face = toFaceIJOrientation(i, j);
952 
953     // Find the coordinates of the lower left-hand leaf cell.  We need to
954     // normalize (i,j) to a known position within the cell because nbr_level
955     // may be larger than this cell's level.
956     int size = getSizeIJ();
957     i &= -size;
958     j &= -size;
959 
960     int nbr_size = getSizeIJ(nbr_level);
961     assert(nbr_size <= size);
962 
963     // We compute the top-bottom, left-right, and diagonal neighbors in one
964     // pass.  The loop test is at the end of the loop to avoid 32-bit overflow.
965     for (int k = -nbr_size; ; k += nbr_size) {
966       bool same_face;
967       if (k < 0) {
968         same_face = (j + k >= 0);
969       } else if (k >= size) {
970         same_face = (j + k < MAX_SIZE);
971       } else {
972         same_face = true;
973         // Top and bottom neighbors.
974         output ~= fromFaceIJSame(face, i + k, j - nbr_size, j - size >= 0).parent(nbr_level);
975         output ~= fromFaceIJSame(face, i + k, j + size, j + size < MAX_SIZE).parent(nbr_level);
976       }
977       // Left, right, and diagonal neighbors.
978       output ~= fromFaceIJSame(face, i - nbr_size, j + k, same_face && i - size >= 0)
979           .parent(nbr_level);
980       output ~= fromFaceIJSame(face, i + size, j + k, same_face && i + size < MAX_SIZE)
981           .parent(nbr_level);
982       if (k >= size) {
983         break;
984       }
985     }
986   }
987 
988   /////////////////////////////////////////////////////////////////////
989   // Low-level methods.
990 
991   /**
992    * Returns a leaf cell given its cube face (range 0..5) and
993    * i- and j-coordinates (see s2coords.h).
994    */
995   static S2CellId fromFaceIJ(int face, int i, int j) {
996     // Optimization notes:
997     //  - Non-overlapping bit fields can be combined with either "+" or "|".
998     //    Generally "+" seems to produce better code, but not always.
999 
1000     // Note that this value gets shifted one bit to the left at the end
1001     // of the function.
1002     ulong n = cast(ulong)(face) << (POS_BITS - 1);
1003 
1004     // Alternating faces have opposite Hilbert curve orientations; this
1005     // is necessary in order for all faces to have a right-handed
1006     // coordinate system.
1007     ulong bits = (face & s2coords.SWAP_MASK);
1008 
1009     // Each iteration maps 4 bits of "i" and "j" into 8 bits of the Hilbert
1010     // curve position.  The lookup table transforms a 10-bit key of the form
1011     // "iiiijjjjoo" to a 10-bit value of the form "ppppppppoo", where the
1012     // letters [ijpo] denote bits of "i", "j", Hilbert curve position, and
1013     // Hilbert curve orientation respectively.
1014     int mask;
1015     static foreach (k; range.iota(7, -1, -1)) {
1016       mask = (1 << LOOKUP_BITS) - 1;
1017       bits += ((i >> (k * LOOKUP_BITS)) & mask) << (LOOKUP_BITS + 2);
1018       bits += ((j >> (k * LOOKUP_BITS)) & mask) << 2;
1019       bits = LOOKUP_POS[bits];
1020       n |= (bits >> 2) << (k * 2 * LOOKUP_BITS);
1021       bits &= (s2coords.SWAP_MASK | s2coords.INVERT_MASK);
1022     }
1023 
1024     return S2CellId(n * 2 + 1);
1025   }
1026 
1027   /**
1028    * Return the (face, i, j) coordinates for the leaf cell corresponding to
1029    * this cell id.  Since cells are represented by the Hilbert curve position
1030    * at the center of the cell, the returned (i,j) for non-leaf cells will be
1031    * a leaf cell adjacent to the cell center.  If "orientation" is non-nullptr,
1032    * also return the Hilbert curve orientation for the current cell.
1033    */
1034   int toFaceIJOrientation(out int pi, out int pj) const {
1035     extractHilbertIJBits(pi, pj);
1036     return face();
1037   }
1038 
1039   int toFaceIJOrientation(out int pi, out int pj, out int orientation) const {
1040     int bits = extractHilbertIJBits(pi, pj);
1041 
1042     // The position of a non-leaf cell at level "n" consists of a prefix of
1043     // 2*n bits that identifies the cell, followed by a suffix of
1044     // 2*(kMaxLevel-n)+1 bits of the form 10*.  If n==kMaxLevel, the suffix is
1045     // just "1" and has no effect.  Otherwise, it consists of "10", followed
1046     // by (kMaxLevel-n-1) repetitions of "00", followed by "0".  The "10" has
1047     // no effect, while each occurrence of "00" has the effect of reversing
1048     // the kSwapMask bit.
1049     assert(0 == s2coords.POS_TO_ORIENTATION[2]);
1050     assert(s2coords.SWAP_MASK == s2coords.POS_TO_ORIENTATION[0]);
1051     if (lsb() & 0x1111111111111110uL) {
1052       bits ^= s2coords.SWAP_MASK;
1053     }
1054     orientation = bits;
1055 
1056     return face();
1057   }
1058 
1059   private int extractHilbertIJBits(out int pi, out int pj) const {
1060     int i = 0, j = 0;
1061     int bits = (face() & s2coords.SWAP_MASK);
1062 
1063     // Each iteration maps 8 bits of the Hilbert curve position into
1064     // 4 bits of "i" and "j".  The lookup table transforms a key of the
1065     // form "ppppppppoo" to a value of the form "iiiijjjjoo", where the
1066     // letters [ijpo] represents bits of "i", "j", the Hilbert curve
1067     // position, and the Hilbert curve orientation respectively.
1068     //
1069     // On the first iteration we need to be careful to clear out the bits
1070     // representing the cube face.
1071     int nbits;
1072     static foreach (k; range.iota(7, -1, -1)) {
1073       nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
1074       bits += (cast(int)(_id >> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits)) - 1)) << 2;
1075       bits = LOOKUP_IJ[bits];
1076       i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
1077       j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
1078       bits &= (s2coords.SWAP_MASK | s2coords.INVERT_MASK);
1079     }
1080 
1081     pi = i;
1082     pj = j;
1083     return bits;
1084   }
1085 
1086   /**
1087    * Returns the lowest-numbered bit that is on for this cell id, which is
1088    * equal to (uint64(1) << (2 * (MAX_LEVEL - level))).  So for example,
1089    * a.lsb() <= b.lsb() if and only if a.level() >= b.level(), but the
1090    * first test is more efficient.
1091    */
1092   ulong lsb() const {
1093     return _id & (~_id + 1);
1094   }
1095 
1096   /// Returns the lowest-numbered bit that is on for cells at the given level.
1097   static ulong lsbForLevel(int level) {
1098     return 1uL << (2 * (MAX_LEVEL - level));
1099   }
1100 
1101   /**
1102    * Returns the bound in (u,v)-space for the cell at the given level containing
1103    * the leaf cell with the given (i,j)-coordinates.
1104    */
1105   static R2Rect IJLevelToBoundUV(int[2] ij, int level) {
1106     R2Rect bound;
1107     int cell_size = getSizeIJ(level);
1108     for (int d = 0; d < 2; ++d) {
1109       int ij_lo = ij[d] & -cell_size;
1110       int ij_hi = ij_lo + cell_size;
1111 
1112       bound[d][0] = s2coords.STtoUV(s2coords.IJtoSTMin(ij_lo));
1113       bound[d][1] = s2coords.STtoUV(s2coords.IJtoSTMin(ij_hi));
1114     }
1115     return bound;
1116   }
1117 
1118   /// Supports the == and != operators.
1119   bool opEquals(in S2CellId v) const {
1120     return id() == v.id();
1121   }
1122 
1123   /// Supports <, <=, >, and >= operators.
1124   int opCmp(in S2CellId v) const {
1125     if (id() == v.id()) {
1126       return 0;
1127     }
1128     return id() > v.id() ? 1 : -1;
1129   }
1130 
1131   /// The ID serves as a good hash for associative arrays.
1132   size_t toHash() const @safe pure nothrow {
1133     return cast(size_t) _id;
1134   }
1135 
1136  private:
1137   // This is the offset required to wrap around from the beginning of the
1138   // Hilbert curve to the end or vice versa; see next_wrap() and prev_wrap().
1139   static immutable ulong WRAP_OFFSET = cast(ulong) NUM_FACES << POS_BITS;
1140   static immutable int LOOKUP_BITS = 4;
1141   static immutable int LOOKUP_ARRAY_SIZE = 1 << (2 * LOOKUP_BITS + 2);
1142   static immutable ushort[LOOKUP_ARRAY_SIZE] LOOKUP_POS;
1143   static immutable ushort[LOOKUP_ARRAY_SIZE] LOOKUP_IJ;
1144 
1145   shared static this() {
1146     ushort[LOOKUP_ARRAY_SIZE] lookupPos;
1147     ushort[LOOKUP_ARRAY_SIZE] lookupIJ;
1148     initLookupCell(lookupPos, lookupIJ, 0, 0, 0, 0, 0, 0);
1149     initLookupCell(lookupPos, lookupIJ, 0, 0, 0, s2coords.SWAP_MASK, 0, s2coords.SWAP_MASK);
1150     initLookupCell(lookupPos, lookupIJ, 0, 0, 0, s2coords.INVERT_MASK, 0, s2coords.INVERT_MASK);
1151     initLookupCell(lookupPos, lookupIJ, 0, 0, 0, s2coords.SWAP_MASK | s2coords.INVERT_MASK, 0,
1152         s2coords.SWAP_MASK | s2coords.INVERT_MASK);
1153     LOOKUP_POS = lookupPos;
1154     LOOKUP_IJ = lookupIJ;
1155   }
1156 
1157   /**
1158    * Given a face and a point (i,j) where either i or j is outside the valid
1159    * range [0..MAX_SIZE-1], this function first determines which neighboring
1160    * face "contains" (i,j), and then returns the leaf cell on that face which
1161    * is adjacent to the given face and whose distance from (i,j) is minimal.
1162    */
1163   static S2CellId fromFaceIJWrap(int face, int i, int j) {
1164     // Convert i and j to the coordinates of a leaf cell just beyond the
1165     // boundary of this face.  This prevents 32-bit overflow in the case
1166     // of finding the neighbors of a face cell.
1167     i = algorithm.max(-1, algorithm.min(MAX_SIZE, i));
1168     j = algorithm.max(-1, algorithm.min(MAX_SIZE, j));
1169 
1170     // We want to wrap these coordinates onto the appropriate adjacent face.
1171     // The easiest way to do this is to convert the (i,j) coordinates to (x,y,z)
1172     // (which yields a point outside the normal face boundary), and then call
1173     // S2::XYZtoFaceUV() to project back onto the correct face.
1174     //
1175     // The code below converts (i,j) to (si,ti), and then (si,ti) to (u,v) using
1176     // the linear projection (u=2*s-1 and v=2*t-1).  (The code further below
1177     // converts back using the inverse projection, s=0.5*(u+1) and t=0.5*(v+1).
1178     // Any projection would work here, so we use the simplest.)  We also clamp
1179     // the (u,v) coordinates so that the point is barely outside the
1180     // [-1,1]x[-1,1] face rectangle, since otherwise the reprojection step
1181     // (which divides by the new z coordinate) might change the other
1182     // coordinates enough so that we end up in the wrong leaf cell.
1183     static const double SCALE = 1.0 / MAX_SIZE;
1184     static const double LIMIT = 1.0 + double.epsilon;
1185     // The arithmetic below is designed to avoid 32-bit integer overflows.
1186     assert(0 == MAX_SIZE % 2);
1187     double u = algorithm.max(-LIMIT, algorithm.min(LIMIT, SCALE * (2 * (i - MAX_SIZE / 2) + 1)));
1188     double v = algorithm.max(-LIMIT, algorithm.min(LIMIT, SCALE * (2 * (j - MAX_SIZE / 2) + 1)));
1189 
1190     // Find the leaf cell coordinates on the adjacent face, and convert
1191     // them to a cell id at the appropriate level.
1192     face = s2coords.XYZtoFaceUV(s2coords.FaceUVtoXYZ(face, u, v), u, v);
1193     return fromFaceIJ(face, s2coords.STtoIJ(0.5*(u+1)), s2coords.STtoIJ(0.5*(v+1)));
1194   }
1195 
1196   /**
1197    * Inline helper function that calls fromFaceIJ if "same_face" is true,
1198    * or fromFaceIJWrap if "same_face" is false.
1199    */
1200   static S2CellId fromFaceIJSame(int face, int i, int j, bool same_face) {
1201     if (same_face)
1202       return S2CellId.fromFaceIJ(face, i, j);
1203     else
1204       return S2CellId.fromFaceIJWrap(face, i, j);
1205   }
1206 
1207   static void initLookupCell(
1208       ref ushort[LOOKUP_ARRAY_SIZE] lookupPos, ref ushort[LOOKUP_ARRAY_SIZE] lookupIJ,
1209       int level, int i, int j, int orig_orientation, int pos, int orientation) {
1210     if (level == LOOKUP_BITS) {
1211       int ij = (i << LOOKUP_BITS) + j;
1212       lookupPos[(ij << 2) + orig_orientation] = cast(ushort)((pos << 2) + orientation);
1213       lookupIJ[(pos << 2) + orig_orientation] = cast(ushort)((ij << 2) + orientation);
1214     } else {
1215       level++;
1216       i <<= 1;
1217       j <<= 1;
1218       pos <<= 2;
1219       const int[4] r = s2coords.POS_TO_IJ[orientation];
1220       initLookupCell(
1221           lookupPos, lookupIJ,
1222           level, i + (r[0] >> 1), j + (r[0] & 1), orig_orientation,
1223           pos, orientation ^ s2coords.POS_TO_ORIENTATION[0]);
1224       initLookupCell(
1225           lookupPos, lookupIJ,
1226           level, i + (r[1] >> 1), j + (r[1] & 1), orig_orientation,
1227           pos + 1, orientation ^ s2coords.POS_TO_ORIENTATION[1]);
1228       initLookupCell(
1229           lookupPos, lookupIJ,
1230           level, i + (r[2] >> 1), j + (r[2] & 1), orig_orientation,
1231           pos + 2, orientation ^ s2coords.POS_TO_ORIENTATION[2]);
1232       initLookupCell(
1233           lookupPos, lookupIJ,
1234           level, i + (r[3] >> 1), j + (r[3] & 1), orig_orientation,
1235           pos + 3, orientation ^ s2coords.POS_TO_ORIENTATION[3]);
1236     }
1237   }
1238 }