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 }