1 /** 2 S2Cap represents a disc-shaped region defined by a center and radius. 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.s2cap; 22 23 import algorithm = std.algorithm; 24 import format = std.format; 25 import math = std.math; 26 import s2.r1interval; 27 import s2.s1angle; 28 import s2.s1chord_angle; 29 import s2.s1interval; 30 import s2.s2cell; 31 import s2.s2cell_id; 32 import s2.s2debug; 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 s2edge_distances = s2.s2edge_distances; 40 import s2metrics = s2.s2metrics; 41 import s2pointutil = s2.s2pointutil; 42 43 import std.exception : enforce; 44 45 /** 46 S2Cap represents a disc-shaped region defined by a center and radius. 47 Technically this shape is called a "spherical cap" (rather than disc) 48 because it is not planar; the cap represents a portion of the sphere that 49 has been cut off by a plane. The boundary of the cap is the circle defined 50 by the intersection of the sphere and the plane. For containment purposes, 51 the cap is a closed set, i.e. it contains its boundary. 52 53 For the most part, you can use a spherical cap wherever you would use a 54 disc in planar geometry. The radius of the cap is measured along the 55 surface of the sphere (rather than the straight-line distance through the 56 interior). Thus a cap of radius Pi/2 is a hemisphere, and a cap of radius 57 Pi covers the entire sphere. 58 59 A cap can also be defined by its center point and height. The height 60 is simply the distance from the center point to the cutoff plane. There is 61 also support for "empty" and "full" caps, which contain no points and all 62 points respectively. 63 64 This class is intended to be copied by value as desired. It uses the 65 default copy constructor and assignment operator, however it is not a 66 "plain old datatype" (POD) because it has virtual functions. 67 */ 68 class S2Cap : S2Region { 69 private: 70 // Here are some useful relationships between the cap height (h), the cap 71 // radius (r), the maximum chord length from the cap's center (d), and the 72 // radius of cap's base (a). 73 // 74 // h = 1 - cos(r) 75 // = 2 * sin^2(r/2) 76 // d^2 = 2 * h 77 // = a^2 + h^2 78 79 /// Returns true if the cap intersects "cell", given that the cap does contain 80 /// any of the cell vertices (supplied in "vertices", an array of length 4). 81 bool intersects(in S2Cell cell, in S2Point[] vertices) const { 82 // Return true if this cap intersects any point of 'cell' excluding its 83 // vertices (which are assumed to already have been checked). 84 85 // If the cap is a hemisphere or larger, the cell and the complement of the 86 // cap are both convex. Therefore since no vertex of the cell is contained, 87 // no other interior point of the cell is contained either. 88 if (_radius >= S1ChordAngle.right()) { 89 return false; 90 } 91 92 // We need to check for empty caps due to the center check just below. 93 if (isEmpty()) { 94 return false; 95 } 96 97 // Optimization: return true if the cell contains the cap center. (This 98 // allows half of the edge checks below to be skipped.) 99 if (cell.contains(_center)) { 100 return true; 101 } 102 103 // At this point we know that the cell does not contain the cap center, 104 // and the cap does not contain any cell vertex. The only way that they 105 // can intersect is if the cap intersects the interior of some edge. 106 107 double sin2_angle = _radius.sin2(); 108 for (int k = 0; k < 4; ++k) { 109 S2Point edge = cell.getEdgeRaw(k); 110 double dot = _center.dotProd(edge); 111 if (dot > 0) { 112 // The center is in the interior half-space defined by the edge. We don't 113 // need to consider these edges, since if the cap intersects this edge 114 // then it also intersects the edge on the opposite side of the cell 115 // (because we know the center is not contained with the cell). 116 continue; 117 } 118 // The Norm2() factor is necessary because "edge" is not normalized. 119 if (dot * dot > sin2_angle * edge.norm2()) { 120 return false; // Entire cap is on the exterior side of this edge. 121 } 122 // Otherwise, the great circle containing this edge intersects 123 // the interior of the cap. We just need to check whether the point 124 // of closest approach occurs between the two edge endpoints. 125 Vector3_d dir = edge.crossProd(_center); 126 if (dir.dotProd(vertices[k]) < 0 && dir.dotProd(vertices[(k + 1) & 3]) > 0) 127 return true; 128 } 129 return false; 130 } 131 132 S2Point _center; 133 S1ChordAngle _radius; 134 135 public: 136 /// The default constructor returns an empty S2Cap. 137 this() { 138 _center = S2Point(1, 0, 0); 139 _radius = S1ChordAngle.negative(); 140 } 141 142 this(in S2Cap o) { 143 _center = o._center; 144 _radius = o._radius; 145 } 146 147 /** 148 Constructs a cap with the given center and radius. A negative radius 149 yields an empty cap; a radius of 180 degrees or more yields a full cap 150 (containing the entire sphere). "center" should be unit length. 151 */ 152 this(in S2Point center, S1Angle radius) 153 out { 154 assert(isValid()); 155 } do { 156 _center = center; 157 // The "min" calculation is necessary to handle S1Angle::Infinity(). 158 _radius = algorithm.min(radius, S1Angle.fromRadians(M_PI)); 159 } 160 161 /// Constructs a cap where the angle is expressed as an S1ChordAngle. This 162 /// constructor is more efficient than the one above. 163 this(in S2Point center, S1ChordAngle radius) 164 out { 165 assert(isValid()); 166 } do { 167 _center = center; 168 _radius = radius; 169 } 170 171 ~this() {} 172 173 /// Convenience function that creates a cap containing a single point. This 174 /// method is more efficient that the S2Cap(center, radius) constructor. 175 static S2Cap fromPoint(in S2Point center) { 176 return new S2Cap(center, S1ChordAngle.zero()); 177 } 178 179 /** 180 Returns a cap with the given center and height (see comments above). A 181 negative height yields an empty cap; a height of 2 or more yields a full 182 cap. "center" should be unit length. 183 */ 184 static S2Cap fromCenterHeight(in S2Point center, double height) { 185 return new S2Cap(center, S1ChordAngle.fromLength2(2 * height)); 186 } 187 188 /** 189 Returns a cap with the given center and surface area. Note that the area 190 can also be interpreted as the solid angle subtended by the cap (because 191 the sphere has unit radius). A negative area yields an empty cap; an 192 area of 4*Pi or more yields a full cap. "center" should be unit length. 193 */ 194 static S2Cap fromCenterArea(in S2Point center, double area) { 195 return new S2Cap(center, S1ChordAngle.fromLength2(area / M_PI)); 196 } 197 198 /// Returns an empty cap, i.e. a cap that contains no points. 199 static S2Cap empty() { 200 return new S2Cap(); 201 } 202 203 /// Returns a full cap, i.e. a cap that contains all points. 204 static S2Cap full() { 205 return new S2Cap(S2Point(1, 0, 0), S1ChordAngle.straight()); 206 } 207 208 // Accessor methods. 209 @property 210 S2Point center() const { 211 return _center; 212 } 213 214 @property 215 S1ChordAngle radius() const { 216 return _radius; 217 } 218 219 /// Returns the height of the cap, i.e. the distance from the center point to the cutoff plane. 220 double height() const { 221 return 0.5 * _radius.length2(); 222 } 223 224 /** 225 Returns the cap radius as an S1Angle. (Note that the cap angle is stored 226 internally as an S1ChordAngle, so this method requires a trigonometric 227 operation and may yield a slightly different result than the value passed 228 to the (S2Point, S1Angle) constructor.) 229 */ 230 S1Angle getRadius() const { 231 return _radius.toS1Angle(); 232 } 233 234 /// Returns the area of the cap. 235 double getArea() const { 236 return 2 * M_PI * algorithm.max(0.0, height()); 237 } 238 239 /** 240 Returns the true centroid of the cap multiplied by its surface area (see 241 s2centroids.h for details on centroids). The result lies on the ray from 242 the origin through the cap's center, but it is not unit length. Note that 243 if you just want the "surface centroid", i.e. the normalized result, then 244 it is much simpler just to call center(). 245 246 The reason for multiplying the result by the cap area is to make it 247 easier to compute the centroid of more complicated shapes. The centroid 248 of a union of disjoint regions can be computed simply by adding their 249 GetCentroid() results. Caveat: for caps that contain a single point 250 (i.e., zero radius), this method always returns the origin (0, 0, 0). 251 This is because shapes with no area don't affect the centroid of a 252 union whose total area is positive. 253 */ 254 S2Point getCentroid() const { 255 // From symmetry, the centroid of the cap must be somewhere on the line 256 // from the origin to the center of the cap on the surface of the sphere. 257 // When a sphere is divided into slices of constant thickness by a set of 258 // parallel planes, all slices have the same surface area. This implies 259 // that the radial component of the centroid is simply the midpoint of the 260 // range of radial distances spanned by the cap. That is easily computed 261 // from the cap height. 262 if (isEmpty()) { 263 return S2Point(); 264 } 265 double r = 1.0 - 0.5 * height(); 266 return r * getArea() * _center; 267 } 268 269 270 /** 271 We allow negative heights (to represent empty caps) but heights are 272 normalized so that they do not exceed 2. 273 */ 274 bool isValid() const { 275 return s2pointutil.isUnitLength(_center) && _radius.length2() <= 4; 276 } 277 278 /// Returns true if the cap is empty, i.e. it contains no points. 279 bool isEmpty() const { 280 return _radius.isNegative(); 281 } 282 283 /// Returns true if the cap is full, i.e. it contains all points. 284 bool isFull() const { 285 return _radius.length2() == 4; 286 } 287 288 /** 289 Returns the complement of the interior of the cap. A cap and its 290 complement have the same boundary but do not share any interior points. 291 The complement operator is not a bijection because the complement of a 292 singleton cap (containing a single point) is the same as the complement 293 of an empty cap. 294 */ 295 S2Cap complement() const { 296 // The complement of a full cap is an empty cap, not a singleton. 297 // Also make sure that the complement of an empty cap is full. 298 if (isFull()) { 299 return empty(); 300 } 301 if (isEmpty()) { 302 return full(); 303 } 304 return new S2Cap(-_center, S1ChordAngle.fromLength2(4 - _radius.length2())); 305 } 306 307 /// Returns true if and only if this cap contains the given other cap 308 /// (in a set containment sense, e.g. every cap contains the empty cap). 309 bool contains(in S2Cap other) const { 310 if (isFull() || other.isEmpty()) { 311 return true; 312 } 313 return _radius >= S1ChordAngle(_center, other._center) + other._radius; 314 } 315 316 /// Returns true if and only if this cap intersects the given other cap, 317 /// i.e. whether they have any points in common. 318 bool intersects(in S2Cap other) const { 319 if (isEmpty() || other.isEmpty()) { 320 return false; 321 } 322 return _radius + other._radius >= S1ChordAngle(_center, other._center); 323 } 324 325 /** 326 Returns true if and only if the interior of this cap intersects the 327 given other cap. (This relationship is not symmetric, since only 328 the interior of this cap is used.) 329 */ 330 bool interiorIntersects(in S2Cap other) const { 331 // Make sure this cap has an interior and the other cap is non-empty. 332 if (_radius.length2() <= 0 || other.isEmpty()) { 333 return false; 334 } 335 return _radius + other._radius > S1ChordAngle(_center, other._center); 336 } 337 338 /** 339 Returns true if and only if the given point is contained in the interior 340 of the cap (i.e. the cap excluding its boundary). "p" should be be a 341 unit-length vector. 342 */ 343 bool interiorContains(in S2Point p) const 344 in { 345 assert(s2pointutil.isUnitLength(p)); 346 } do { 347 return isFull() || S1ChordAngle(_center, p) < _radius; 348 } 349 350 /** 351 Increase the cap height if necessary to include the given point. If the 352 cap is empty then the center is set to the given point, but otherwise the 353 center is not changed. "p" should be a unit-length vector. 354 */ 355 void addPoint(in S2Point p) 356 in { 357 // Compute the squared chord length, then convert it into a height. 358 assert(s2pointutil.isUnitLength(p)); 359 } do { 360 if (isEmpty()) { 361 _center = p; 362 _radius = S1ChordAngle.zero(); 363 } else { 364 // After calling cap.AddPoint(p), cap.Contains(p) must be true. However 365 // we don't need to do anything special to achieve this because Contains() 366 // does exactly the same distance calculation that we do here. 367 _radius = algorithm.max(_radius, S1ChordAngle(_center, p)); 368 } 369 } 370 371 /// Increases the cap height if necessary to include "other". If the current 372 /// cap is empty it is set to the given other cap. 373 void addCap(in S2Cap other) { 374 if (isEmpty()) { 375 _center = other._center; 376 _radius = other._radius; 377 } else { 378 // We round up the distance to ensure that the cap is actually contained. 379 // TODO(ericv): Do some error analysis in order to guarantee this. 380 S1ChordAngle dist = S1ChordAngle(_center, other._center) + other._radius; 381 _radius = algorithm.max(_radius, dist.plusError(double.epsilon * dist.length2())); 382 } 383 } 384 385 /// Returns a cap that contains all points within a given distance of this 386 /// cap. Note that any expansion of the empty cap is still empty. 387 S2Cap expanded(S1Angle distance) const 388 in { 389 assert(distance.radians() >= 0); 390 } do { 391 if (isEmpty()) { 392 return empty(); 393 } 394 return new S2Cap(_center, _radius + S1ChordAngle(distance)); 395 } 396 397 // Return the smallest cap which encloses this cap and "other". 398 S2Cap unite(in S2Cap other) const { 399 if (_radius < other._radius) { 400 return other.unite(this); 401 } 402 if (isFull() || other.isEmpty()) { 403 return new S2Cap(this); 404 } 405 // This calculation would be more efficient using S1ChordAngles. 406 S1Angle this_radius = getRadius(); 407 S1Angle other_radius = other.getRadius(); 408 S1Angle distance = S1Angle(center(), other.center()); 409 if (this_radius >= distance + other_radius) { 410 return new S2Cap(this); 411 } else { 412 S1Angle result_radius = 0.5 * (distance + this_radius + other_radius); 413 S2Point result_center = s2edge_distances.interpolateAtDistance( 414 0.5 * (distance - this_radius + other_radius), 415 center(), 416 other.center()); 417 return new S2Cap(result_center, result_radius); 418 } 419 } 420 421 //////////////////////////////////////////////////////////////////////// 422 // S2Region interface (see s2region.d for details): 423 424 override 425 S2Cap clone() const { 426 return new S2Cap(this); 427 } 428 429 override 430 S2Cap getCapBound() const { 431 return new S2Cap(this); 432 } 433 434 override 435 S2LatLngRect getRectBound() const { 436 if (isEmpty()) { 437 return S2LatLngRect.empty(); 438 } 439 440 // Convert the center to a (lat,lng) pair, and compute the cap angle. 441 S2LatLng center_ll = S2LatLng(_center); 442 double cap_angle = getRadius().radians(); 443 444 bool all_longitudes = false; 445 double[2] lat; 446 double[2] lng; 447 lng[0] = -M_PI; 448 lng[1] = M_PI; 449 450 // Check whether cap includes the south pole. 451 lat[0] = center_ll.lat().radians() - cap_angle; 452 if (lat[0] <= -math.PI_2) { 453 lat[0] = -math.PI_2; 454 all_longitudes = true; 455 } 456 // Check whether cap includes the north pole. 457 lat[1] = center_ll.lat().radians() + cap_angle; 458 if (lat[1] >= math.PI_2) { 459 lat[1] = math.PI_2; 460 all_longitudes = true; 461 } 462 if (!all_longitudes) { 463 // Compute the range of longitudes covered by the cap. We use the law 464 // of sines for spherical triangles. Consider the triangle ABC where 465 // A is the north pole, B is the center of the cap, and C is the point 466 // of tangency between the cap boundary and a line of longitude. Then 467 // C is a right angle, and letting a,b,c denote the sides opposite A,B,C, 468 // we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c). 469 // Here "a" is the cap angle, and "c" is the colatitude (90 degrees 470 // minus the latitude). This formula also works for negative latitudes. 471 // 472 // The formula for sin(a) follows from the relationship h = 1 - cos(a). 473 474 double sin_a = _radius.sin(); 475 double sin_c = math.cos(center_ll.lat().radians()); 476 if (sin_a <= sin_c) { 477 double angle_A = math.asin(sin_a / sin_c); 478 lng[0] = math.remainder(center_ll.lng().radians() - angle_A, 2 * M_PI); 479 lng[1] = math.remainder(center_ll.lng().radians() + angle_A, 2 * M_PI); 480 } 481 } 482 return new S2LatLngRect(R1Interval(lat[0], lat[1]), S1Interval(lng[0], lng[1])); 483 } 484 485 /** 486 Computes a covering of the S2Cap. In general the covering consists of at 487 most 4 cells except for very large caps, which may need up to 6 cells. 488 The output is not sorted. 489 */ 490 override 491 void getCellUnionBound(out S2CellId[] cell_ids) { 492 // TODO(ericv): The covering could be made quite a bit tighter by mapping 493 // the cap to a rectangle in (i,j)-space and finding a covering for that. 494 495 // Find the maximum level such that the cap contains at most one cell vertex 496 // and such that S2CellId::AppendVertexNeighbors() can be called. 497 int level = s2metrics.MIN_WIDTH.getLevelForMinValue(getRadius().radians()) - 1; 498 499 // If level < 0, then more than three face cells are required. 500 if (level < 0) { 501 cell_ids.reserve(6); 502 for (int face = 0; face < 6; ++face) { 503 cell_ids ~= S2CellId.fromFace(face); 504 } 505 } else { 506 // The covering consists of the 4 cells at the given level that share the 507 // cell vertex that is closest to the cap center. 508 cell_ids.reserve(4); 509 S2CellId(_center).appendVertexNeighbors(level, cell_ids); 510 } 511 } 512 513 override 514 bool contains(in S2Cell cell) const { 515 // If the cap does not contain all cell vertices, return false. 516 // We check the vertices before taking the Complement() because we can't 517 // accurately represent the complement of a very small cap (a height 518 // of 2-epsilon is rounded off to 2). 519 S2Point[4] vertices; 520 for (int k = 0; k < 4; ++k) { 521 vertices[k] = cell.getVertex(k); 522 if (!contains(vertices[k])) return false; 523 } 524 // Otherwise, return true if the complement of the cap does not intersect 525 // the cell. (This test is slightly conservative, because technically we 526 // want Complement().InteriorIntersects() here.) 527 return !complement().intersects(cell, vertices); 528 } 529 530 override 531 bool mayIntersect(in S2Cell cell) const { 532 // If the cap contains any cell vertex, return true. 533 S2Point[4] vertices; 534 for (int k = 0; k < 4; ++k) { 535 vertices[k] = cell.getVertex(k); 536 if (contains(vertices[k])) return true; 537 } 538 return intersects(cell, vertices); 539 } 540 541 542 /// The point "p" should be a unit-length vector. 543 override 544 bool contains(in S2Point p) const 545 in { 546 assert(s2pointutil.isUnitLength(p)); 547 } do { 548 return S1ChordAngle(_center, p) <= _radius; 549 } 550 551 /** 552 Appends a serialized representation of the S2Cap to "encoder". 553 554 REQUIRES: "encoder" uses the default constructor, so that its buffer 555 can be enlarged as necessary by calling Ensure(int). 556 */ 557 void encode(ORangeT)(Encoder!ORangeT encoder) const 558 out (; encoder.avail() >= 0) { 559 encoder.ensure(4 * double.sizeof); 560 561 encoder.putDouble(_center.x()); 562 encoder.putDouble(_center.y()); 563 encoder.putDouble(_center.z()); 564 encoder.putDouble(_radius.length2()); 565 } 566 567 /// Decodes an S2Cap encoded with Encode(). Returns true on success. 568 bool decode(IRangeT)(Decoder!IRangeT decoder) { 569 if (decoder.avail() < 4 * double.sizeof) return false; 570 571 double x = decoder.getDouble(); 572 double y = decoder.getDouble(); 573 double z = decoder.getDouble(); 574 _center = S2Point(x, y, z); 575 _radius = S1ChordAngle.fromLength2(decoder.getDouble()); 576 577 if (flagsS2Debug) { 578 enforce(isValid(), "Invalid S2Cap: " ~ toString()); 579 } 580 return true; 581 } 582 583 /////////////////////////////////////////////////////////////////////// 584 // The following static methods are convenience functions for assertions 585 // and testing purposes only. 586 587 /// Returns true if two caps are identical. 588 override 589 bool opEquals(in Object o) const { 590 S2Cap other = cast(S2Cap) o; 591 if (other !is null) { 592 return (_center == other._center && _radius == other._radius) 593 || (isEmpty() && other.isEmpty()) 594 || (isFull() && other.isFull()); 595 } 596 return false; 597 } 598 599 /// Returns true if the cap center and height differ by at most "max_error" 600 /// from the given cap "other". 601 bool approxEquals(in S2Cap other, S1Angle max_error_angle = S1Angle.fromRadians(1e-14)) const { 602 const double max_error = max_error_angle.radians(); 603 const double r2 = _radius.length2(); 604 const double other_r2 = other._radius.length2(); 605 return (s2pointutil.approxEquals(_center, other._center, max_error_angle) && 606 math.fabs(r2 - other_r2) <= max_error) || 607 (isEmpty() && other_r2 <= max_error) || 608 (other.isEmpty() && r2 <= max_error) || 609 (isFull() && other_r2 >= 2 - max_error) || 610 (other.isFull() && r2 >= 2 - max_error); 611 } 612 613 override 614 string toString() const { 615 return format.format( 616 "[_center=%s, _radius=%s]", center().toString(), getRadius().toString()); 617 } 618 }