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 }