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