1 /**
2    An S2Cell is an S2Region object that represents a cell.
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;
22 
23 import algorithm = std.algorithm;
24 import math = std.math;
25 import s2.r1interval;
26 import s2.r2point;
27 import s2.r2rect;
28 import s2.s1chord_angle;
29 import s2.s1interval;
30 import s2.s2cap;
31 import s2.s2cell_id;
32 import s2.s2edge_crosser;
33 import s2.s2latlng;
34 import s2.s2latlng_rect;
35 import s2.s2point;
36 import s2.s2region;
37 import s2.util.coding.coder;
38 import s2.util.math.vector;
39 import s2coords = s2.s2coords;
40 import s2edge_distances = s2.s2edge_distances;
41 import s2measures = s2.s2measures;
42 import s2metrics = s2.s2metrics;
43 
44 import std.exception;
45 
46 /**
47  * An S2Cell is an S2Region object that represents a cell.
48 
49  * Unlike S2CellIds, it supports efficient containment and intersection tests.  However, it is also
50  * a more expensive representation (currently 48 bytes rather than 8).
51  *
52  * This class is intended to be copied by value as desired.  It uses the default copy constructor
53  * and assignment operator, however it is not a "plain old datatype" (POD) because it has virtual
54  * functions.
55  */
56 class S2Cell : S2Region {
57 public:
58   /// The default constructor is required in order to use freelists.
59   /// Cells should otherwise always be constructed explicitly.
60   this() {}
61 
62   /// An S2Cell always corresponds to a particular S2CellId.  The other
63   /// constructors are just convenience methods.
64   this(in S2CellId id) {
65     initFromS2CellId(id);
66   }
67 
68   this(in S2Cell cell) {
69     _face = cell._face;
70     _level = cell._level;
71     _orientation = cell._orientation;
72     _id = cell._id;
73     _uv = cell._uv;
74   }
75 
76   /// Convenience constructors.  The S2LatLng must be normalized.
77   this(in S2Point p) {
78     this(S2CellId(p));
79   }
80 
81   this(in S2LatLng ll) {
82     this(S2CellId(ll));
83   }
84 
85   /// Returns the cell corresponding to the given S2 cube face.
86   static S2Cell fromFace(int face) {
87     return new S2Cell(S2CellId.fromFace(face));
88   }
89 
90   /**
91    * Returns a cell given its face (range 0..5), Hilbert curve position within
92    * that face (an unsigned integer with S2CellId::kPosBits bits), and level
93    * (range 0..kMaxLevel).  The given position will be modified to correspond
94    * to the Hilbert curve position at the center of the returned cell.  This
95    * is a static function rather than a constructor in order to indicate what
96    * the arguments represent.
97    */
98   static S2Cell fromFacePosLevel(int face, ulong pos, int level) {
99     return new S2Cell(S2CellId.fromFacePosLevel(face, pos, level));
100   }
101 
102   void initFromS2CellId(in S2CellId id) {
103     _id = id;
104     int[2] ij;
105     int orientation;
106     _face = cast(byte) id.toFaceIJOrientation(ij[0], ij[1], orientation);
107     _orientation = cast(byte) orientation;  // Compress int to a byte.
108     _level = cast(byte) id.level();
109     _uv = S2CellId.IJLevelToBoundUV(ij, _level);
110   }
111 
112   S2CellId id() const {
113     return _id;
114   }
115 
116   int face() const {
117     return _face;
118   }
119 
120   int level() const {
121     return _level;
122   }
123 
124   int orientation() const {
125     return _orientation;
126   }
127 
128   bool isLeaf() const {
129     return _level == S2CellId.MAX_LEVEL;
130   }
131 
132   /// These are equivalent to the S2CellId methods, but have a more efficient
133   /// implementation since the level has been precomputed.
134   int getSizeIJ() const {
135     return S2CellId.getSizeIJ(level());
136   }
137 
138   double getSizeST() const {
139     return S2CellId.getSizeST(level());
140   }
141 
142   /**
143    * Returns the k-th vertex of the cell (k = 0,1,2,3).  Vertices are returned
144    * in CCW order (lower left, lower right, upper right, upper left in the UV
145    * plane).  The points returned by GetVertexRaw are not normalized.
146    * For convenience, the argument is reduced modulo 4 to the range [0..3].
147    */
148   S2Point getVertex(int k) const {
149     return getVertexRaw(k).normalize();
150   }
151 
152   S2Point getVertexRaw(int k) const {
153     return s2coords.FaceUVtoXYZ(_face, _uv.getVertex(k));
154   }
155 
156   /**
157    * Returns the inward-facing normal of the great circle passing through the
158    * edge from vertex k to vertex k+1 (mod 4).  The normals returned by
159    * GetEdgeRaw are not necessarily unit length.  For convenience, the
160    * argument is reduced modulo 4 to the range [0..3].
161    */
162   S2Point getEdge(int k) const {
163     return getEdgeRaw(k).normalize();
164   }
165 
166   S2Point getEdgeRaw(int k) const {
167     switch (k & 3) {
168       case 0:  return s2coords.GetVNorm(_face, _uv[1][0]);   // Bottom
169       case 1:  return s2coords.GetUNorm(_face, _uv[0][1]);   // Right
170       case 2:  return -s2coords.GetVNorm(_face, _uv[1][1]);  // Top
171       default: return -s2coords.GetUNorm(_face, _uv[0][0]);  // Left
172     }
173   }
174 
175   /**
176    * Divides the S2Cell into its four children.
177    *
178    * If this is not a leaf cell, sets children[0..3] to the four children of
179    * this cell (in traversal order) and return true.  Otherwise returns false.
180    * This method is equivalent to the following:
181    *
182    * for (pos=0, id=child_begin(); id != child_end(); id = id.next(), ++pos)
183    *   children[pos] = S2Cell(id);
184    *
185    * except that it is more than two times faster.
186    */
187   bool subdivide(out S2Cell[4] children) const {
188     // This function is equivalent to just iterating over the child cell ids
189     // and calling the S2Cell constructor, but it is about 2.5 times faster.
190 
191     if (_id.isLeaf()) {
192       return false;
193     }
194 
195     // Compute the cell midpoint in uv-space.
196     R2Point uv_mid = _id.getCenterUV();
197 
198     // Create four children with the appropriate bounds.
199     S2CellId id = _id.childBegin();
200     for (int pos = 0; pos < 4; ++pos, id = id.next()) {
201       S2Cell child = new S2Cell();
202       child._face = _face;
203       child._level = cast(byte)(_level + 1);
204       child._orientation = cast(byte)(_orientation ^ s2coords.POS_TO_ORIENTATION[pos]);
205       child._id = id;
206       // We want to split the cell in half in "u" and "v".  To decide which
207       // side to set equal to the midpoint value, we look at cell's (i,j)
208       // position within its parent.  The index for "i" is in bit 1 of ij.
209       int ij = s2coords.POS_TO_IJ[_orientation][pos];
210       int i = ij >> 1;
211       int j = ij & 1;
212       child._uv[0][i] = _uv[0][i];
213       child._uv[0][1-i] = uv_mid[0];
214       child._uv[1][j] = _uv[1][j];
215       child._uv[1][1-j] = uv_mid[1];
216       children[pos] = child;
217     }
218     return true;
219   }
220 
221   /**
222    * Returns the direction vector corresponding to the center in (s,t)-space of
223    * the given cell.  This is the point at which the cell is divided into four
224    * subcells; it is not necessarily the centroid of the cell in (u,v)-space
225    * or (x,y,z)-space.  The point returned by GetCenterRaw is not necessarily
226    * unit length.
227    */
228   S2Point getCenter() const {
229     return getCenterRaw().normalize();
230   }
231 
232   S2Point getCenterRaw() const {
233     return _id.toS2PointRaw();
234   }
235 
236   /// Returns the average area for cells at the given level.
237   static double averageArea(int level) {
238     return s2metrics.AVG_AREA.getValue(level);
239   }
240 
241   /**
242    * Returns the average area of cells at this level in steradians.  This is
243    * accurate to within a factor of 1.7 (for S2_QUADRATIC_PROJECTION) and is
244    * extremely cheap to compute.
245    */
246   double averageArea() const {
247     return averageArea(_level);
248   }
249 
250   /**
251    * Returns the approximate area of this cell in steradians.  This method is
252    * accurate to within 3% percent for all cell sizes and accurate to within
253    * 0.1% for cells at level 5 or higher (i.e. squares 350km to a side or
254    * smaller on the Earth's surface).  It is moderately cheap to compute.
255    */
256   double approxArea() const {
257     // All cells at the first two levels have the same area.
258     if (_level < 2) {
259       return averageArea(_level);
260     }
261 
262     // First, compute the approximate area of the cell when projected
263     // perpendicular to its normal.  The cross product of its diagonals gives
264     // the normal, and the length of the normal is twice the projected area.
265     double flat_area =
266         0.5 * (getVertex(2) - getVertex(0)).crossProd(getVertex(3) - getVertex(1)).norm();
267 
268     // Now, compensate for the curvature of the cell surface by pretending
269     // that the cell is shaped like a spherical cap.  The ratio of the
270     // area of a spherical cap to the area of its projected disc turns out
271     // to be 2 / (1 + sqrt(1 - r*r)) where "r" is the radius of the disc.
272     // For example, when r=0 the ratio is 1, and when r=1 the ratio is 2.
273     // Here we set Pi*r*r == flat_area to find the equivalent disc.
274     return flat_area * 2 / (1 + math.sqrt(1.0 - algorithm.min(math.M_1_PI * flat_area, 1.0)));
275   }
276 
277   /**
278    * Returns the area of this cell as accurately as possible.  This method is
279    * more expensive but it is accurate to 6 digits of precision even for leaf
280    * cells (whose area is approximately 1e-18).
281    */
282   double exactArea() const {
283     // There is a straightforward mathematical formula for the exact surface
284     // area (based on 4 calls to asin), but as the cell size gets small this
285     // formula has too much cancellation error.  So instead we compute the area
286     // as the sum of two triangles (which is very accurate at all cell levels).
287     S2Point v0 = getVertex(0);
288     S2Point v1 = getVertex(1);
289     S2Point v2 = getVertex(2);
290     S2Point v3 = getVertex(3);
291     return s2measures.area(v0, v1, v2) + s2measures.area(v0, v2, v3);
292   }
293 
294   /// Returns the bounds of this cell in (u,v)-space.
295   R2Rect getBoundUV() const {
296     return _uv;
297   }
298 
299   /// Returns the distance from the cell to the given point.  Returns zero if
300   /// the point is inside the cell.
301   S1ChordAngle getDistance(in S2Point target) const {
302     return getDistanceInternal(target, true /*to_interior*/);
303   }
304 
305   /// Returns the distance from the cell boundary to the given point.
306   S1ChordAngle getBoundaryDistance(in S2Point target) const {
307     return getDistanceInternal(target, false /*to_interior*/);
308   }
309 
310   /// Returns the maximum distance from the cell (including its interior) to the given point.
311   S1ChordAngle getMaxDistance(in S2Point target) const {
312     // First check the 4 cell vertices.  If all are within the hemisphere
313     // centered around target, the max distance will be to one of these vertices.
314     S2Point target_uvw = s2coords.FaceXYZtoUVW(_face, target);
315     S1ChordAngle max_dist = algorithm.max(
316         algorithm.max(vertexChordDist(target_uvw, 0, 0), vertexChordDist(target_uvw, 1, 0)),
317         algorithm.max(vertexChordDist(target_uvw, 0, 1), vertexChordDist(target_uvw, 1, 1)));
318 
319     if (max_dist <= S1ChordAngle.right()) {
320       return max_dist;
321     }
322 
323     // Otherwise, find the minimum distance d_min to the antipodal point and the
324     // maximum distance will be Pi - d_min.
325     return S1ChordAngle.straight() - getDistance(-target);
326   }
327 
328 
329   /// Returns the minimum distance from the cell to the given edge AB.  Returns
330   /// zero if the edge intersects the cell interior.
331   S1ChordAngle getDistance(in S2Point a, in S2Point b) const {
332     // Possible optimizations:
333     //  - Currently the (cell vertex, edge endpoint) distances are computed
334     //    twice each, and the length of AB is computed 4 times.
335     //  - To fix this, refactor GetDistance(target) so that it skips calculating
336     //    the distance to each cell vertex.  Instead, compute the cell vertices
337     //    and distances in this function, and add a low-level UpdateMinDistance
338     //    that allows the XA, XB, and AB distances to be passed in.
339     //  - It might also be more efficient to do all calculations in UVW-space,
340     //    since this would involve transforming 2 points rather than 4.
341 
342     // First, check the minimum distance to the edge endpoints A and B.
343     // (This also detects whether either endpoint is inside the cell.)
344     S1ChordAngle min_dist = algorithm.min(getDistance(a), getDistance(b));
345     if (min_dist == S1ChordAngle.zero()) {
346       return min_dist;
347     }
348 
349     // Otherwise, check whether the edge crosses the cell boundary.
350     // Note that S2EdgeCrosser needs pointers to vertices.
351     S2Point[4] v;
352     for (int i = 0; i < 4; ++i) {
353       v[i] = getVertex(i);
354     }
355     scope auto crosser = new S2EdgeCrosser(a, b, v[3]);
356     for (int i = 0; i < 4; ++i) {
357       if (crosser.crossingSign(v[i]) >= 0) {
358         return S1ChordAngle.zero();
359       }
360     }
361     // Finally, check whether the minimum distance occurs between a cell vertex
362     // and the interior of the edge AB.  (Some of this work is redundant, since
363     // it also checks the distance to the endpoints A and B again.)
364     //
365     // Note that we don't need to check the distance from the interior of AB to
366     // the interior of a cell edge, because the only way that this distance can
367     // be minimal is if the two edges cross (already checked above).
368     for (int i = 0; i < 4; ++i) {
369       s2edge_distances.updateMinDistance(v[i], a, b, min_dist);
370     }
371     return min_dist;
372   }
373 
374 
375   /// Returns the maximum distance from the cell (including its interior) to the
376   /// given edge AB.
377   S1ChordAngle getMaxDistance(in S2Point a, in S2Point b) const {
378     // If the maximum distance from both endpoints to the cell is less than Pi/2
379     // then the maximum distance from the edge to the cell is the maximum of the
380     // two endpoint distances.
381     S1ChordAngle max_dist = algorithm.max(getMaxDistance(a), getMaxDistance(b));
382     if (max_dist <= S1ChordAngle.right()) {
383       return max_dist;
384     }
385 
386     return S1ChordAngle.straight() - getDistance(-a, -b);
387   }
388 
389   /// Returns the distance from the cell to the given cell.  Returns zero if
390   /// one cell contains the other.
391   S1ChordAngle getDistance(in S2Cell target) const {
392     // If the cells intersect, the distance is zero.  We use the (u,v) ranges
393     // rather S2CellId::intersects() so that cells that share a partial edge or
394     // corner are considered to intersect.
395     if (_face == target._face && _uv.intersects(target._uv)) {
396       return S1ChordAngle.zero();
397     }
398 
399     // Otherwise, the minimum distance always occurs between a vertex of one
400     // cell and an edge of the other cell (including the edge endpoints).  This
401     // represents a total of 32 possible (vertex, edge) pairs.
402     //
403     // TODO(ericv): This could be optimized to be at least 5x faster by pruning
404     // the set of possible closest vertex/edge pairs using the faces and (u,v)
405     // ranges of both cells.
406     S2Point[4] va, vb;
407     for (int i = 0; i < 4; ++i) {
408       va[i] = getVertex(i);
409       vb[i] = target.getVertex(i);
410     }
411     S1ChordAngle min_dist = S1ChordAngle.infinity();
412     for (int i = 0; i < 4; ++i) {
413       for (int j = 0; j < 4; ++j) {
414         s2edge_distances.updateMinDistance(va[i], vb[j], vb[(j + 1) & 3], min_dist);
415         s2edge_distances.updateMinDistance(vb[i], va[j], va[(j + 1) & 3], min_dist);
416       }
417     }
418     return min_dist;
419   }
420 
421   /// Returns the maximum distance from the cell (including its interior) to the given target cell.
422   S1ChordAngle getMaxDistance(in S2Cell target) const {
423     // Need to check the antipodal target for intersection with the cell. If it
424     // intersects, the distance is S1ChordAngle::Straight().
425     if (_face == oppositeFace(target._face) && _uv.intersects(oppositeUV(target._uv))) {
426       return S1ChordAngle.straight();
427     }
428 
429     // Otherwise, the maximum distance always occurs between a vertex of one
430     // cell and an edge of the other cell (including the edge endpoints).  This
431     // represents a total of 32 possible (vertex, edge) pairs.
432     //
433     // TODO(user): When the maximum distance is at most Pi/2, the maximum is
434     // always attained between a pair of vertices, and this could be made much
435     // faster by testing each vertex pair once rather than the current 4 times.
436     S2Point[4] va, vb;
437     for (int i = 0; i < 4; ++i) {
438       va[i] = getVertex(i);
439       vb[i] = target.getVertex(i);
440     }
441     S1ChordAngle max_dist = S1ChordAngle.negative();
442     for (int i = 0; i < 4; ++i) {
443       for (int j = 0; j < 4; ++j) {
444         s2edge_distances.updateMaxDistance(va[i], vb[j], vb[(j + 1) & 3], max_dist);
445         s2edge_distances.updateMaxDistance(vb[i], va[j], va[(j + 1) & 3], max_dist);
446       }
447     }
448     return max_dist;
449   }
450 
451 
452   ////////////////////////////////////////////////////////////////////////
453   // Operator implementations:
454 
455   override
456   bool opEquals(in Object y) {
457     S2Cell other = cast(S2Cell) y;
458     if (other) {
459       return id() == other.id();
460     } else {
461       return false;
462     }
463   }
464 
465   override
466   int opCmp(in Object y) {
467     S2Cell other = cast(S2Cell) y;
468     enforce(other, "Cannot compare S2Cell with another type.");
469     return id > other.id() ? 1
470         : id == other.id() ? 0 : -1;
471   }
472 
473   ////////////////////////////////////////////////////////////////////////
474   // S2Region interface (see s2region.h for details):
475 
476   override
477   S2Region clone() const {
478     return new S2Cell(this);
479   }
480 
481   override
482   S2Cap getCapBound() const {
483     // Use the cell center in (u,v)-space as the cap axis.  This vector is
484     // very close to GetCenter() and faster to compute.  Neither one of these
485     // vectors yields the bounding cap with minimal surface area, but they
486     // are both pretty close.
487     //
488     // It's possible to show that the two vertices that are furthest from
489     // the (u,v)-origin never determine the maximum cap size (this is a
490     // possible future optimization).
491 
492     S2Point center = s2coords.FaceUVtoXYZ(_face, _uv.getCenter()).normalize();
493     S2Cap cap = S2Cap.fromPoint(center);
494     for (int k = 0; k < 4; ++k) {
495       cap.addPoint(getVertex(k));
496     }
497     return cap;
498   }
499 
500   override
501   S2LatLngRect getRectBound() const {
502     if (_level > 0) {
503       // Except for cells at level 0, the latitude and longitude extremes are
504       // attained at the vertices.  Furthermore, the latitude range is
505       // determined by one pair of diagonally opposite vertices and the
506       // longitude range is determined by the other pair.
507       //
508       // We first determine which corner (i,j) of the cell has the largest
509       // absolute latitude.  To maximize latitude, we want to find the point in
510       // the cell that has the largest absolute z-coordinate and the smallest
511       // absolute x- and y-coordinates.  To do this we look at each coordinate
512       // (u and v), and determine whether we want to minimize or maximize that
513       // coordinate based on the axis direction and the cell's (u,v) quadrant.
514       double u = _uv[0][0] + _uv[0][1];
515       double v = _uv[1][0] + _uv[1][1];
516       int i = s2coords.GetUAxis(_face)[2] == 0 ? (u < 0) : (u > 0);
517       int j = s2coords.GetVAxis(_face)[2] == 0 ? (v < 0) : (v > 0);
518       R1Interval lat = R1Interval.fromPointPair(getLatitude(i, j), getLatitude(1-i, 1-j));
519       S1Interval lng = S1Interval.fromPointPair(getLongitude(i, 1-j), getLongitude(1-i, j));
520 
521       // We grow the bounds slightly to make sure that the bounding rectangle
522       // contains S2LatLng(P) for any point P inside the loop L defined by the
523       // four *normalized* vertices.  Note that normalization of a vector can
524       // change its direction by up to 0.5 * DBL_EPSILON radians, and it is not
525       // enough just to add Normalize() calls to the code above because the
526       // latitude/longitude ranges are not necessarily determined by diagonally
527       // opposite vertex pairs after normalization.
528       //
529       // We would like to bound the amount by which the latitude/longitude of a
530       // contained point P can exceed the bounds computed above.  In the case of
531       // longitude, the normalization error can change the direction of rounding
532       // leading to a maximum difference in longitude of 2 * DBL_EPSILON.  In
533       // the case of latitude, the normalization error can shift the latitude by
534       // up to 0.5 * DBL_EPSILON and the other sources of error can cause the
535       // two latitudes to differ by up to another 1.5 * DBL_EPSILON, which also
536       // leads to a maximum difference of 2 * DBL_EPSILON.
537       return new S2LatLngRect(lat, lng)
538           .expanded(S2LatLng.fromRadians(2 * double.epsilon, 2 * double.epsilon))
539           .polarClosure();
540     }
541 
542     // The 4 cells around the equator extend to +/-45 degrees latitude at the
543     // midpoints of their top and bottom edges.  The two cells covering the
544     // poles extend down to +/-35.26 degrees at their vertices.  The maximum
545     // error in this calculation is 0.5 * DBL_EPSILON.
546     const double kPoleMinLat = math.asin(math.sqrt(1.0/3)) - 0.5 * double.epsilon;
547 
548     // The face centers are the +X, +Y, +Z, -X, -Y, -Z axes in that order.
549     enforce(((_face < 3) ? 1 : -1) == s2coords.GetNorm(_face)[_face % 3]);
550 
551     S2LatLngRect bound;
552     switch (_face) {
553       case 0:
554         bound = new S2LatLngRect(
555             R1Interval(-math.PI_4, math.PI_4), S1Interval(-math.PI_4, math.PI_4));
556         break;
557       case 1:
558         bound = new S2LatLngRect(
559             R1Interval(-math.PI_4, math.PI_4), S1Interval(math.PI_4, 3 * math.PI_4));
560         break;
561       case 2:
562         bound = new S2LatLngRect(R1Interval(kPoleMinLat, math.PI_2), S1Interval.full());
563         break;
564       case 3:
565         bound = new S2LatLngRect(
566             R1Interval(-math.PI_4, math.PI_4), S1Interval(3 * math.PI_4, -3 * math.PI_4));
567         break;
568       case 4:
569         bound = new S2LatLngRect(
570             R1Interval(-math.PI_4, math.PI_4), S1Interval(-3 * math.PI_4, -math.PI_4));
571         break;
572       default:
573         bound = new S2LatLngRect(R1Interval(-math.PI_2, -kPoleMinLat), S1Interval.full());
574         break;
575     }
576     // Finally, we expand the bound to account for the error when a point P is
577     // converted to an S2LatLng to test for containment.  (The bound should be
578     // large enough so that it contains the computed S2LatLng of any contained
579     // point, not just the infinite-precision version.)  We don't need to expand
580     // longitude because longitude is calculated via a single call to atan2(),
581     // which is guaranteed to be semi-monotonic.  (In fact the Gnu implementation
582     // is also correctly rounded, but we don't even need that here.)
583     return bound.expanded(S2LatLng.fromRadians(double.epsilon, 0));
584   }
585 
586   override
587   void getCellUnionBound(out S2CellId[] cellIds) {
588     cellIds ~= id();
589   }
590 
591   override
592   bool contains(in S2Cell cell) const {
593     return _id.contains(cell._id);
594   }
595 
596   override
597   bool mayIntersect(in S2Cell cell) const {
598     return _id.intersects(cell._id);
599   }
600 
601   /**
602    * Returns true if the cell contains the given point "p".  Note that unlike
603    * S2Loop/S2Polygon, S2Cells are considered to be closed sets.  This means
604    * that points along an S2Cell edge (or at a vertex) belong to the adjacent
605    * cell(s) as well.
606    *
607    * If instead you want every point to be contained by exactly one S2Cell,
608    * you will need to convert the S2Cells to S2Loops (which implement point
609    * containment this way).
610    *
611    * The point "p" does not need to be normalized.
612    */
613   override
614   bool contains(in S2Point p) const {
615     // We can't just call XYZtoFaceUV, because for points that lie on the
616     // boundary between two faces (i.e. u or v is +1/-1) we need to return
617     // true for both adjacent cells.
618     R2Point uv;
619     if (!s2coords.FaceXYZtoUV(_face, p, uv)) return false;
620 
621     // Expand the (u,v) bound to ensure that
622     //
623     //   S2Cell(S2CellId(p)).Contains(p)
624     //
625     // is always true.  To do this, we need to account for the error when
626     // converting from (u,v) coordinates to (s,t) coordinates.  At least in the
627     // case of S2_QUADRATIC_PROJECTION, the total error is at most DBL_EPSILON.
628     return _uv.expanded(double.epsilon).contains(uv);
629   }
630 
631   /**
632    * Appends a serialized representation of the S2Cell to "encoder".
633    *
634    * REQUIRES: "encoder" uses the default constructor, so that its buffer
635    *           can be enlarged as necessary by calling Ensure(int).
636    */
637   void encode(ORangeT)(Encoder!ORangeT encoder) const {
638     _id.encode(encoder);
639   }
640 
641   /// Decodes an S2Cell encoded with Encode().  Returns true on success.
642   bool decode(IRangeT)(Decoder!IRangeT decoder) {
643     S2CellId id;
644     if (!id.decode(decoder)) return false;
645     initFromS2CellId(id);
646     return true;
647   }
648 
649   override
650   string toString() const {
651     import std.format;
652     return format!("[face=%d, level=%d, orientation=%d, id=%s, uv=%s]")(
653         _face, _level, _orientation, _id, _uv);
654   }
655 
656  private:
657   // Returns the latitude or longitude of the cell vertex given by (i,j),
658   // where "i" and "j" are either 0 or 1.
659   double getLatitude(int i, int j) const {
660     S2Point p = s2coords.FaceUVtoXYZ(_face, _uv[0][i], _uv[1][j]);
661     return S2LatLng.latitude(p).radians();
662   }
663 
664   double getLongitude(int i, int j) const {
665     S2Point p = s2coords.FaceUVtoXYZ(_face, _uv[0][i], _uv[1][j]);
666     return S2LatLng.longitude(p).radians();
667   }
668 
669   S1ChordAngle vertexChordDist(in S2Point p, int i, int j) const {
670     S2Point vertex = S2Point(_uv[0][i], _uv[1][j], 1).normalize();
671     return S1ChordAngle(p, vertex);
672   }
673 
674   /**
675    * Given a point P and either the lower or upper edge of the S2Cell (specified
676    * by setting "v_end" to 0 or 1 respectively), return true if P is closer to
677    * the interior of that edge than it is to either endpoint.
678    */
679   bool UEdgeIsClosest(in S2Point p, int v_end) const {
680     double u0 = _uv[0][0], u1 = _uv[0][1], v = _uv[1][v_end];
681     // These are the normals to the planes that are perpendicular to the edge
682     // and pass through one of its two endpoints.
683     auto dir0 = S2Point(v * v + 1, -u0 * v, -u0);
684     auto dir1 = S2Point(v * v + 1, -u1 * v, -u1);
685     return p.dotProd(dir0) > 0 && p.dotProd(dir1) < 0;
686   }
687 
688   /**
689    * Given a point P and either the left or right edge of the S2Cell (specified
690    * by setting "u_end" to 0 or 1 respectively), return true if P is closer to
691    * the interior of that edge than it is to either endpoint.
692    */
693   bool VEdgeIsClosest(in S2Point p, int u_end) const {
694     double v0 = _uv[1][0], v1 = _uv[1][1], u = _uv[0][u_end];
695     // See comments above.
696     auto dir0 = S2Point(-u * v0, u * u + 1, -v0);
697     auto dir1 = S2Point(-u * v1, u * u + 1, -v1);
698     return p.dotProd(dir0) > 0 && p.dotProd(dir1) < 0;
699   }
700 
701   /// Returns the distance from the given point to the interior of the cell if
702   /// "to_interior" is true, and to the boundary of the cell otherwise.
703   S1ChordAngle getDistanceInternal(in S2Point target_xyz, bool to_interior) const {
704     // All calculations are done in the (u,v,w) coordinates of this cell's face.
705     S2Point target = s2coords.FaceXYZtoUVW(_face, target_xyz);
706 
707     // Compute dot products with all four upward or rightward-facing edge
708     // normals.  "dirIJ" is the dot product for the edge corresponding to axis
709     // I, endpoint J.  For example, dir01 is the right edge of the S2Cell
710     // (corresponding to the upper endpoint of the u-axis).
711     double dir00 = target[0] - target[2] * _uv[0][0];
712     double dir01 = target[0] - target[2] * _uv[0][1];
713     double dir10 = target[1] - target[2] * _uv[1][0];
714     double dir11 = target[1] - target[2] * _uv[1][1];
715     bool inside = true;
716     if (dir00 < 0) {
717       inside = false;  // Target is to the left of the cell
718       if (VEdgeIsClosest(target, 0)) return edgeDistance(-dir00, _uv[0][0]);
719     }
720     if (dir01 > 0) {
721       inside = false;  // Target is to the right of the cell
722       if (VEdgeIsClosest(target, 1)) return edgeDistance(dir01, _uv[0][1]);
723     }
724     if (dir10 < 0) {
725       inside = false;  // Target is below the cell
726       if (UEdgeIsClosest(target, 0)) return edgeDistance(-dir10, _uv[1][0]);
727     }
728     if (dir11 > 0) {
729       inside = false;  // Target is above the cell
730       if (UEdgeIsClosest(target, 1)) return edgeDistance(dir11, _uv[1][1]);
731     }
732     if (inside) {
733       if (to_interior) return S1ChordAngle.zero();
734       // Although you might think of S2Cells as rectangles, they are actually
735       // arbitrary quadrilaterals after they are projected onto the sphere.
736       // Therefore the simplest approach is just to find the minimum distance to
737       // any of the four edges.
738       return algorithm.min(
739           algorithm.min(
740               edgeDistance(-dir00, _uv[0][0]),
741               edgeDistance(dir01, _uv[0][1])),
742           algorithm.min(
743               edgeDistance(-dir10, _uv[1][0]),
744               edgeDistance(dir11, _uv[1][1])));
745     }
746     // Otherwise, the closest point is one of the four cell vertices.  Note that
747     // it is *not* trivial to narrow down the candidates based on the edge sign
748     // tests above, because (1) the edges don't meet at right angles and (2)
749     // there are points on the far side of the sphere that are both above *and*
750     // below the cell, etc.
751     return algorithm.min(
752         algorithm.min(
753             vertexChordDist(target, 0, 0),
754             vertexChordDist(target, 1, 0)),
755         algorithm.min(
756             vertexChordDist(target, 0, 1),
757             vertexChordDist(target, 1, 1)));
758   }
759 
760   /// Given the dot product of a point P with the normal of a u- or v-edge at the
761   /// given coordinate value, return the distance from P to that edge.
762   static S1ChordAngle edgeDistance(double dirIJ, double uv) {
763     // Let P by the target point and let R be the closest point on the given
764     // edge AB.  The desired distance PR can be expressed as PR^2 = PQ^2 + QR^2
765     // where Q is the point P projected onto the plane through the great circle
766     // through AB.  We can compute the distance PQ^2 perpendicular to the plane
767     // from "dirIJ" (the dot product of the target point P with the edge
768     // normal) and the squared length the edge normal (1 + uv**2).
769     double pq2 = (dirIJ * dirIJ) / (1 + uv * uv);
770 
771     // We can compute the distance QR as (1 - OQ) where O is the sphere origin,
772     // and we can compute OQ^2 = 1 - PQ^2 using the Pythagorean theorem.
773     // (This calculation loses accuracy as angle POQ approaches Pi/2.)
774     double qr = 1 - math.sqrt(1.0 - pq2);
775     return S1ChordAngle.fromLength2(pq2 + qr * qr);
776   }
777 
778   static int oppositeFace(int face) {
779     return face >= 3 ? face - 3 : face + 3;
780   }
781 
782   /// The antipodal UV is the transpose of the original UV, interpreted within the opposite face.
783   static R2Rect oppositeUV(in R2Rect uv) {
784     return R2Rect(uv[1], uv[0]);
785   }
786 
787   // This structure occupies 44 bytes plus one pointer for the vtable.
788   byte _face;
789   byte _level;
790   byte _orientation;
791   S2CellId _id;
792   R2Rect _uv;
793 }