• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2005 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.common.geometry;
17 
18 
19 /**
20  * This class represents a spherical cap, i.e. a portion of a sphere cut off by
21  * a plane. The cap is defined by its axis and height. This representation has
22  * good numerical accuracy for very small caps (unlike the (axis,
23  * min-distance-from-origin) representation), and is also efficient for
24  * containment tests (unlike the (axis, angle) representation).
25  *
26  * Here are some useful relationships between the cap height (h), the cap
27  * opening angle (theta), the maximum chord length from the cap's center (d),
28  * and the radius of cap's base (a). All formulas assume a unit radius.
29  *
30  * h = 1 - cos(theta) = 2 sin^2(theta/2) d^2 = 2 h = a^2 + h^2
31  *
32  */
33 public final strictfp class S2Cap implements S2Region {
34 
35   /**
36    * Multiply a positive number by this constant to ensure that the result of a
37    * floating point operation is at least as large as the true
38    * infinite-precision result.
39    */
40   private static final double ROUND_UP = 1.0 + 1.0 / (1L << 52);
41 
42   private final S2Point axis;
43   private final double height;
44 
45   // Caps may be constructed from either an axis and a height, or an axis and
46   // an angle. To avoid ambiguity, there are no public constructors
S2Cap()47   private S2Cap() {
48     axis = new S2Point();
49     height = 0;
50   }
51 
S2Cap(S2Point axis, double height)52   private S2Cap(S2Point axis, double height) {
53     this.axis = axis;
54     this.height = height;
55     // assert (isValid());
56   }
57 
58   /**
59    * Create a cap given its axis and the cap height, i.e. the maximum projected
60    * distance along the cap axis from the cap center. 'axis' should be a
61    * unit-length vector.
62    */
fromAxisHeight(S2Point axis, double height)63   public static S2Cap fromAxisHeight(S2Point axis, double height) {
64     // assert (S2.isUnitLength(axis));
65     return new S2Cap(axis, height);
66   }
67 
68   /**
69    * Create a cap given its axis and the cap opening angle, i.e. maximum angle
70    * between the axis and a point on the cap. 'axis' should be a unit-length
71    * vector, and 'angle' should be between 0 and 180 degrees.
72    */
fromAxisAngle(S2Point axis, S1Angle angle)73   public static S2Cap fromAxisAngle(S2Point axis, S1Angle angle) {
74     // The height of the cap can be computed as 1-cos(angle), but this isn't
75     // very accurate for angles close to zero (where cos(angle) is almost 1).
76     // Computing it as 2*(sin(angle/2)**2) gives much better precision.
77 
78     // assert (S2.isUnitLength(axis));
79     double d = Math.sin(0.5 * angle.radians());
80     return new S2Cap(axis, 2 * d * d);
81 
82   }
83 
84   /**
85    * Create a cap given its axis and its area in steradians. 'axis' should be a
86    * unit-length vector, and 'area' should be between 0 and 4 * M_PI.
87    */
fromAxisArea(S2Point axis, double area)88   public static S2Cap fromAxisArea(S2Point axis, double area) {
89     // assert (S2.isUnitLength(axis));
90     return new S2Cap(axis, area / (2 * S2.M_PI));
91   }
92 
93   /** Return an empty cap, i.e. a cap that contains no points. */
empty()94   public static S2Cap empty() {
95     return new S2Cap(new S2Point(1, 0, 0), -1);
96   }
97 
98   /** Return a full cap, i.e. a cap that contains all points. */
full()99   public static S2Cap full() {
100     return new S2Cap(new S2Point(1, 0, 0), 2);
101   }
102 
103 
104   // Accessor methods.
axis()105   public S2Point axis() {
106     return axis;
107   }
108 
height()109   public double height() {
110     return height;
111   }
112 
area()113   public double area() {
114     return 2 * S2.M_PI * Math.max(0.0, height);
115   }
116 
117   /**
118    * Return the cap opening angle in radians, or a negative number for empty
119    * caps.
120    */
angle()121   public S1Angle angle() {
122     // This could also be computed as acos(1 - height_), but the following
123     // formula is much more accurate when the cap height is small. It
124     // follows from the relationship h = 1 - cos(theta) = 2 sin^2(theta/2).
125     if (isEmpty()) {
126       return S1Angle.radians(-1);
127     }
128     return S1Angle.radians(2 * Math.asin(Math.sqrt(0.5 * height)));
129   }
130 
131   /**
132    * We allow negative heights (to represent empty caps) but not heights greater
133    * than 2.
134    */
isValid()135   public boolean isValid() {
136     return S2.isUnitLength(axis) && height <= 2;
137   }
138 
139   /** Return true if the cap is empty, i.e. it contains no points. */
isEmpty()140   public boolean isEmpty() {
141     return height < 0;
142   }
143 
144   /** Return true if the cap is full, i.e. it contains all points. */
isFull()145   public boolean isFull() {
146     return height >= 2;
147   }
148 
149   /**
150    * Return the complement of the interior of the cap. A cap and its complement
151    * have the same boundary but do not share any interior points. The complement
152    * operator is not a bijection, since the complement of a singleton cap
153    * (containing a single point) is the same as the complement of an empty cap.
154    */
complement()155   public S2Cap complement() {
156     // The complement of a full cap is an empty cap, not a singleton.
157     // Also make sure that the complement of an empty cap has height 2.
158     double cHeight = isFull() ? -1 : 2 - Math.max(height, 0.0);
159     return S2Cap.fromAxisHeight(S2Point.neg(axis), cHeight);
160   }
161 
162   /**
163    * Return true if and only if this cap contains the given other cap (in a set
164    * containment sense, e.g. every cap contains the empty cap).
165    */
contains(S2Cap other)166   public boolean contains(S2Cap other) {
167     if (isFull() || other.isEmpty()) {
168       return true;
169     }
170     return angle().radians() >= axis.angle(other.axis)
171       + other.angle().radians();
172   }
173 
174   /**
175    * Return true if and only if the interior of this cap intersects the given
176    * other cap. (This relationship is not symmetric, since only the interior of
177    * this cap is used.)
178    */
interiorIntersects(S2Cap other)179   public boolean interiorIntersects(S2Cap other) {
180     // Interior(X) intersects Y if and only if Complement(Interior(X))
181     // does not contain Y.
182     return !complement().contains(other);
183   }
184 
185   /**
186    * Return true if and only if the given point is contained in the interior of
187    * the region (i.e. the region excluding its boundary). 'p' should be a
188    * unit-length vector.
189    */
interiorContains(S2Point p)190   public boolean interiorContains(S2Point p) {
191     // assert (S2.isUnitLength(p));
192     return isFull() || S2Point.sub(axis, p).norm2() < 2 * height;
193   }
194 
195   /**
196    * Increase the cap height if necessary to include the given point. If the cap
197    * is empty the axis is set to the given point, but otherwise it is left
198    * unchanged. 'p' should be a unit-length vector.
199    */
addPoint(S2Point p)200   public S2Cap addPoint(S2Point p) {
201     // Compute the squared chord length, then convert it into a height.
202     // assert (S2.isUnitLength(p));
203     if (isEmpty()) {
204       return new S2Cap(p, 0);
205     } else {
206       // To make sure that the resulting cap actually includes this point,
207       // we need to round up the distance calculation. That is, after
208       // calling cap.AddPoint(p), cap.Contains(p) should be true.
209       double dist2 = S2Point.sub(axis, p).norm2();
210       double newHeight = Math.max(height, ROUND_UP * 0.5 * dist2);
211       return new S2Cap(axis, newHeight);
212     }
213   }
214 
215   // Increase the cap height if necessary to include "other". If the current
216   // cap is empty it is set to the given other cap.
addCap(S2Cap other)217   public S2Cap addCap(S2Cap other) {
218     if (isEmpty()) {
219       return new S2Cap(other.axis, other.height);
220     } else {
221       // See comments for FromAxisAngle() and AddPoint(). This could be
222       // optimized by doing the calculation in terms of cap heights rather
223       // than cap opening angles.
224       double angle = axis.angle(other.axis) + other.angle().radians();
225       if (angle >= S2.M_PI) {
226         return new S2Cap(axis, 2); //Full cap
227       } else {
228         double d = Math.sin(0.5 * angle);
229         double newHeight = Math.max(height, ROUND_UP * 2 * d * d);
230         return new S2Cap(axis, newHeight);
231       }
232     }
233   }
234 
235   // //////////////////////////////////////////////////////////////////////
236   // S2Region interface (see {@code S2Region} for details):
237   @Override
getCapBound()238   public S2Cap getCapBound() {
239     return this;
240   }
241 
242   @Override
getRectBound()243   public S2LatLngRect getRectBound() {
244     if (isEmpty()) {
245       return S2LatLngRect.empty();
246     }
247 
248     // Convert the axis to a (lat,lng) pair, and compute the cap angle.
249     S2LatLng axisLatLng = new S2LatLng(axis);
250     double capAngle = angle().radians();
251 
252     boolean allLongitudes = false;
253     double[] lat = new double[2], lng = new double[2];
254     lng[0] = -S2.M_PI;
255     lng[1] = S2.M_PI;
256 
257     // Check whether cap includes the south pole.
258     lat[0] = axisLatLng.lat().radians() - capAngle;
259     if (lat[0] <= -S2.M_PI_2) {
260       lat[0] = -S2.M_PI_2;
261       allLongitudes = true;
262     }
263     // Check whether cap includes the north pole.
264     lat[1] = axisLatLng.lat().radians() + capAngle;
265     if (lat[1] >= S2.M_PI_2) {
266       lat[1] = S2.M_PI_2;
267       allLongitudes = true;
268     }
269     if (!allLongitudes) {
270       // Compute the range of longitudes covered by the cap. We use the law
271       // of sines for spherical triangles. Consider the triangle ABC where
272       // A is the north pole, B is the center of the cap, and C is the point
273       // of tangency between the cap boundary and a line of longitude. Then
274       // C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
275       // we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
276       // Here "a" is the cap angle, and "c" is the colatitude (90 degrees
277       // minus the latitude). This formula also works for negative latitudes.
278       //
279       // The formula for sin(a) follows from the relationship h = 1 - cos(a).
280 
281       double sinA = Math.sqrt(height * (2 - height));
282       double sinC = Math.cos(axisLatLng.lat().radians());
283       if (sinA <= sinC) {
284         double angleA = Math.asin(sinA / sinC);
285         lng[0] = Math.IEEEremainder(axisLatLng.lng().radians() - angleA,
286           2 * S2.M_PI);
287         lng[1] = Math.IEEEremainder(axisLatLng.lng().radians() + angleA,
288           2 * S2.M_PI);
289       }
290     }
291     return new S2LatLngRect(new R1Interval(lat[0], lat[1]), new S1Interval(
292       lng[0], lng[1]));
293   }
294 
295   @Override
contains(S2Cell cell)296   public boolean contains(S2Cell cell) {
297     // If the cap does not contain all cell vertices, return false.
298     // We check the vertices before taking the Complement() because we can't
299     // accurately represent the complement of a very small cap (a height
300     // of 2-epsilon is rounded off to 2).
301     S2Point[] vertices = new S2Point[4];
302     for (int k = 0; k < 4; ++k) {
303       vertices[k] = cell.getVertex(k);
304       if (!contains(vertices[k])) {
305         return false;
306       }
307     }
308     // Otherwise, return true if the complement of the cap does not intersect
309     // the cell. (This test is slightly conservative, because technically we
310     // want Complement().InteriorIntersects() here.)
311     return !complement().intersects(cell, vertices);
312   }
313 
314   @Override
mayIntersect(S2Cell cell)315   public boolean mayIntersect(S2Cell cell) {
316     // If the cap contains any cell vertex, return true.
317     S2Point[] vertices = new S2Point[4];
318     for (int k = 0; k < 4; ++k) {
319       vertices[k] = cell.getVertex(k);
320       if (contains(vertices[k])) {
321         return true;
322       }
323     }
324     return intersects(cell, vertices);
325   }
326 
327   /**
328    * Return true if the cap intersects 'cell', given that the cap vertices have
329    * alrady been checked.
330    */
intersects(S2Cell cell, S2Point[] vertices)331   public boolean intersects(S2Cell cell, S2Point[] vertices) {
332     // Return true if this cap intersects any point of 'cell' excluding its
333     // vertices (which are assumed to already have been checked).
334 
335     // If the cap is a hemisphere or larger, the cell and the complement of the
336     // cap are both convex. Therefore since no vertex of the cell is contained,
337     // no other interior point of the cell is contained either.
338     if (height >= 1) {
339       return false;
340     }
341 
342     // We need to check for empty caps due to the axis check just below.
343     if (isEmpty()) {
344       return false;
345     }
346 
347     // Optimization: return true if the cell contains the cap axis. (This
348     // allows half of the edge checks below to be skipped.)
349     if (cell.contains(axis)) {
350       return true;
351     }
352 
353     // At this point we know that the cell does not contain the cap axis,
354     // and the cap does not contain any cell vertex. The only way that they
355     // can intersect is if the cap intersects the interior of some edge.
356 
357     double sin2Angle = height * (2 - height); // sin^2(capAngle)
358     for (int k = 0; k < 4; ++k) {
359       S2Point edge = cell.getEdgeRaw(k);
360       double dot = axis.dotProd(edge);
361       if (dot > 0) {
362         // The axis is in the interior half-space defined by the edge. We don't
363         // need to consider these edges, since if the cap intersects this edge
364         // then it also intersects the edge on the opposite side of the cell
365         // (because we know the axis is not contained with the cell).
366         continue;
367       }
368       // The Norm2() factor is necessary because "edge" is not normalized.
369       if (dot * dot > sin2Angle * edge.norm2()) {
370         return false; // Entire cap is on the exterior side of this edge.
371       }
372       // Otherwise, the great circle containing this edge intersects
373       // the interior of the cap. We just need to check whether the point
374       // of closest approach occurs between the two edge endpoints.
375       S2Point dir = S2Point.crossProd(edge, axis);
376       if (dir.dotProd(vertices[k]) < 0
377           && dir.dotProd(vertices[(k + 1) & 3]) > 0) {
378         return true;
379       }
380     }
381     return false;
382   }
383 
contains(S2Point p)384   public boolean contains(S2Point p) {
385     // The point 'p' should be a unit-length vector.
386     // assert (S2.isUnitLength(p));
387     return S2Point.sub(axis, p).norm2() <= 2 * height;
388 
389   }
390 
391 
392   /** Return true if two caps are identical. */
393   @Override
equals(Object that)394   public boolean equals(Object that) {
395 
396     if (!(that instanceof S2Cap)) {
397       return false;
398     }
399 
400     S2Cap other = (S2Cap) that;
401     return (axis.equals(other.axis) && height == other.height)
402         || (isEmpty() && other.isEmpty()) || (isFull() && other.isFull());
403 
404   }
405 
406   @Override
hashCode()407   public int hashCode() {
408     if (isFull()) {
409       return 17;
410     } else if (isEmpty()) {
411       return 37;
412     }
413     int result = 17;
414     result = 37 * result + axis.hashCode();
415     long heightBits = Double.doubleToLongBits(height);
416     result = 37 * result + (int) ((heightBits >>> 32) ^ heightBits);
417     return result;
418   }
419 
420   // /////////////////////////////////////////////////////////////////////
421   // The following static methods are convenience functions for assertions
422   // and testing purposes only.
423 
424   /**
425    * Return true if the cap axis and height differ by at most "max_error" from
426    * the given cap "other".
427    */
approxEquals(S2Cap other, double maxError)428   boolean approxEquals(S2Cap other, double maxError) {
429     return (axis.aequal(other.axis, maxError) && Math.abs(height - other.height) <= maxError)
430       || (isEmpty() && other.height <= maxError)
431       || (other.isEmpty() && height <= maxError)
432       || (isFull() && other.height >= 2 - maxError)
433       || (other.isFull() && height >= 2 - maxError);
434   }
435 
approxEquals(S2Cap other)436   boolean approxEquals(S2Cap other) {
437     return approxEquals(other, 1e-14);
438   }
439 
440   @Override
toString()441   public String toString() {
442     return "[Point = " + axis.toString() + " Height = " + height + "]";
443   }
444 }
445