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 import com.google.common.base.Preconditions; 19 20 /** 21 * An S2LatLngRect represents a latitude-longitude rectangle. It is capable of 22 * representing the empty and full rectangles as well as single points. 23 * 24 */ 25 26 public strictfp class S2LatLngRect implements S2Region { 27 28 private final R1Interval lat; 29 private final S1Interval lng; 30 31 /** 32 * Construct a rectangle from minimum and maximum latitudes and longitudes. If 33 * lo.lng() > hi.lng(), the rectangle spans the 180 degree longitude line. 34 */ S2LatLngRect(final S2LatLng lo, final S2LatLng hi)35 public S2LatLngRect(final S2LatLng lo, final S2LatLng hi) { 36 lat = new R1Interval(lo.lat().radians(), hi.lat().radians()); 37 lng = new S1Interval(lo.lng().radians(), hi.lng().radians()); 38 // assert (isValid()); 39 } 40 41 /** Construct a rectangle from latitude and longitude intervals. */ S2LatLngRect(R1Interval lat, S1Interval lng)42 public S2LatLngRect(R1Interval lat, S1Interval lng) { 43 this.lat = lat; 44 this.lng = lng; 45 // assert (isValid()); 46 } 47 48 /** The canonical empty rectangle */ empty()49 public static S2LatLngRect empty() { 50 return new S2LatLngRect(R1Interval.empty(), S1Interval.empty()); 51 } 52 53 /** The canonical full rectangle. */ full()54 public static S2LatLngRect full() { 55 return new S2LatLngRect(fullLat(), fullLng()); 56 } 57 58 /** The full allowable range of latitudes. */ fullLat()59 public static R1Interval fullLat() { 60 return new R1Interval(-S2.M_PI_2, S2.M_PI_2); 61 } 62 63 /** 64 * The full allowable range of longitudes. 65 */ fullLng()66 public static S1Interval fullLng() { 67 return S1Interval.full(); 68 } 69 70 /** 71 * Construct a rectangle from a center point (in lat-lng space) and size in 72 * each dimension. If size.lng() is greater than 360 degrees it is clamped, 73 * and latitudes greater than +/- 90 degrees are also clamped. So for example, 74 * FromCenterSize((80,170),(20,20)) -> (lo=(60,150),hi=(90,-170)). 75 */ fromCenterSize(S2LatLng center, S2LatLng size)76 public static S2LatLngRect fromCenterSize(S2LatLng center, S2LatLng size) { 77 return fromPoint(center).expanded(size.mul(0.5)); 78 } 79 80 /** Convenience method to construct a rectangle containing a single point. */ fromPoint(S2LatLng p)81 public static S2LatLngRect fromPoint(S2LatLng p) { 82 // assert (p.isValid()); 83 return new S2LatLngRect(p, p); 84 } 85 86 /** 87 * Convenience method to construct the minimal bounding rectangle containing 88 * the two given points. This is equivalent to starting with an empty 89 * rectangle and calling AddPoint() twice. Note that it is different than the 90 * S2LatLngRect(lo, hi) constructor, where the first point is always used as 91 * the lower-left corner of the resulting rectangle. 92 */ fromPointPair(S2LatLng p1, S2LatLng p2)93 public static S2LatLngRect fromPointPair(S2LatLng p1, S2LatLng p2) { 94 // assert (p1.isValid() && p2.isValid()); 95 return new S2LatLngRect(R1Interval.fromPointPair(p1.lat().radians(), p2 96 .lat().radians()), S1Interval.fromPointPair(p1.lng().radians(), p2.lng() 97 .radians())); 98 } 99 100 /** 101 * Return a latitude-longitude rectangle that contains the edge from "a" to 102 * "b". Both points must be unit-length. Note that the bounding rectangle of 103 * an edge can be larger than the bounding rectangle of its endpoints. 104 */ fromEdge(S2Point a, S2Point b)105 public static S2LatLngRect fromEdge(S2Point a, S2Point b) { 106 // assert (S2.isUnitLength(a) && S2.isUnitLength(b)); 107 S2LatLngRect r = fromPointPair(new S2LatLng(a), new S2LatLng(b)); 108 109 // Check whether the min/max latitude occurs in the edge interior. 110 // We find the normal to the plane containing AB, and then a vector "dir" in 111 // this plane that also passes through the equator. We use RobustCrossProd 112 // to ensure that the edge normal is accurate even when the two points are 113 // very close together. 114 S2Point ab = S2.robustCrossProd(a, b); 115 S2Point dir = S2Point.crossProd(ab, new S2Point(0, 0, 1)); 116 double da = dir.dotProd(a); 117 double db = dir.dotProd(b); 118 if (da * db >= 0) { 119 // Minimum and maximum latitude are attained at the vertices. 120 return r; 121 } 122 // Minimum/maximum latitude occurs in the edge interior. This affects the 123 // latitude bounds but not the longitude bounds. 124 double absLat = Math.acos(Math.abs(ab.z / ab.norm())); 125 if (da < 0) { 126 return new S2LatLngRect(new R1Interval(r.lat().lo(), absLat), r.lng()); 127 } else { 128 return new S2LatLngRect(new R1Interval(-absLat, r.lat().hi()), r.lng()); 129 } 130 } 131 132 /** 133 * Return true if the rectangle is valid, which essentially just means that 134 * the latitude bounds do not exceed Pi/2 in absolute value and the longitude 135 * bounds do not exceed Pi in absolute value. 136 * 137 */ isValid()138 public boolean isValid() { 139 // The lat/lng ranges must either be both empty or both non-empty. 140 return (Math.abs(lat.lo()) <= S2.M_PI_2 && Math.abs(lat.hi()) <= S2.M_PI_2 141 && lng.isValid() && lat.isEmpty() == lng.isEmpty()); 142 } 143 144 // Accessor methods. latLo()145 public S1Angle latLo() { 146 return S1Angle.radians(lat.lo()); 147 } 148 latHi()149 public S1Angle latHi() { 150 return S1Angle.radians(lat.hi()); 151 } 152 lngLo()153 public S1Angle lngLo() { 154 return S1Angle.radians(lng.lo()); 155 } 156 lngHi()157 public S1Angle lngHi() { 158 return S1Angle.radians(lng.hi()); 159 } 160 lat()161 public R1Interval lat() { 162 return lat; 163 } 164 lng()165 public S1Interval lng() { 166 return lng; 167 } 168 lo()169 public S2LatLng lo() { 170 return new S2LatLng(latLo(), lngLo()); 171 } 172 hi()173 public S2LatLng hi() { 174 return new S2LatLng(latHi(), lngHi()); 175 } 176 177 /** 178 * Return true if the rectangle is empty, i.e. it contains no points at all. 179 */ isEmpty()180 public boolean isEmpty() { 181 return lat.isEmpty(); 182 } 183 184 // Return true if the rectangle is full, i.e. it contains all points. isFull()185 public boolean isFull() { 186 return lat.equals(fullLat()) && lng.isFull(); 187 } 188 189 /** 190 * Return true if lng_.lo() > lng_.hi(), i.e. the rectangle crosses the 180 191 * degree latitude line. 192 */ isInverted()193 public boolean isInverted() { 194 return lng.isInverted(); 195 } 196 197 /** Return the k-th vertex of the rectangle (k = 0,1,2,3) in CCW order. */ getVertex(int k)198 public S2LatLng getVertex(int k) { 199 // Return the points in CCW order (SW, SE, NE, NW). 200 switch (k) { 201 case 0: 202 return S2LatLng.fromRadians(lat.lo(), lng.lo()); 203 case 1: 204 return S2LatLng.fromRadians(lat.lo(), lng.hi()); 205 case 2: 206 return S2LatLng.fromRadians(lat.hi(), lng.hi()); 207 case 3: 208 return S2LatLng.fromRadians(lat.hi(), lng.lo()); 209 default: 210 throw new IllegalArgumentException("Invalid vertex index."); 211 } 212 } 213 214 /** 215 * Return the center of the rectangle in latitude-longitude space (in general 216 * this is not the center of the region on the sphere). 217 */ getCenter()218 public S2LatLng getCenter() { 219 return S2LatLng.fromRadians(lat.getCenter(), lng.getCenter()); 220 } 221 222 /** 223 * Return the minimum distance (measured along the surface of the sphere) 224 * from a given point to the rectangle (both its boundary and its interior). 225 * The latLng must be valid. 226 */ getDistance(S2LatLng p)227 public S1Angle getDistance(S2LatLng p) { 228 // The algorithm here is the same as in getDistance(S2LagLngRect), only 229 // with simplified calculations. 230 S2LatLngRect a = this; 231 232 Preconditions.checkState(!a.isEmpty()); 233 Preconditions.checkArgument(p.isValid()); 234 235 if (a.lng().contains(p.lng().radians())) { 236 return S1Angle.radians(Math.max(0.0, Math.max(p.lat().radians() - a.lat().hi(), 237 a.lat().lo() - p.lat().radians()))); 238 } 239 240 S1Interval interval = new S1Interval(a.lng().hi(), a.lng().complement().getCenter()); 241 double aLng = a.lng().lo(); 242 if (interval.contains(p.lng().radians())) { 243 aLng = a.lng().hi(); 244 } 245 246 S2Point lo = S2LatLng.fromRadians(a.lat().lo(), aLng).toPoint(); 247 S2Point hi = S2LatLng.fromRadians(a.lat().hi(), aLng).toPoint(); 248 S2Point loCrossHi = 249 S2LatLng.fromRadians(0, aLng - S2.M_PI_2).normalized().toPoint(); 250 return S2EdgeUtil.getDistance(p.toPoint(), lo, hi, loCrossHi); 251 } 252 253 /** 254 * Return the minimum distance (measured along the surface of the sphere) to 255 * the given S2LatLngRect. Both S2LatLngRects must be non-empty. 256 */ getDistance(S2LatLngRect other)257 public S1Angle getDistance(S2LatLngRect other) { 258 S2LatLngRect a = this; 259 S2LatLngRect b = other; 260 261 Preconditions.checkState(!a.isEmpty()); 262 Preconditions.checkArgument(!b.isEmpty()); 263 264 // First, handle the trivial cases where the longitude intervals overlap. 265 if (a.lng().intersects(b.lng())) { 266 if (a.lat().intersects(b.lat())) { 267 return S1Angle.radians(0); // Intersection between a and b. 268 } 269 270 // We found an overlap in the longitude interval, but not in the latitude 271 // interval. This means the shortest path travels along some line of 272 // longitude connecting the high-latitude of the lower rect with the 273 // low-latitude of the higher rect. 274 S1Angle lo, hi; 275 if (a.lat().lo() > b.lat().hi()) { 276 lo = b.latHi(); 277 hi = a.latLo(); 278 } else { 279 lo = a.latHi(); 280 hi = b.latLo(); 281 } 282 return S1Angle.radians(hi.radians() - lo.radians()); 283 } 284 285 // The longitude intervals don't overlap. In this case, the closest points 286 // occur somewhere on the pair of longitudinal edges which are nearest in 287 // longitude-space. 288 S1Angle aLng, bLng; 289 S1Interval loHi = S1Interval.fromPointPair(a.lng().lo(), b.lng().hi()); 290 S1Interval hiLo = S1Interval.fromPointPair(a.lng().hi(), b.lng().lo()); 291 if (loHi.getLength() < hiLo.getLength()) { 292 aLng = a.lngLo(); 293 bLng = b.lngHi(); 294 } else { 295 aLng = a.lngHi(); 296 bLng = b.lngLo(); 297 } 298 299 // The shortest distance between the two longitudinal segments will include 300 // at least one segment endpoint. We could probably narrow this down further 301 // to a single point-edge distance by comparing the relative latitudes of the 302 // endpoints, but for the sake of clarity, we'll do all four point-edge 303 // distance tests. 304 S2Point aLo = new S2LatLng(a.latLo(), aLng).toPoint(); 305 S2Point aHi = new S2LatLng(a.latHi(), aLng).toPoint(); 306 S2Point aLoCrossHi = 307 S2LatLng.fromRadians(0, aLng.radians() - S2.M_PI_2).normalized().toPoint(); 308 S2Point bLo = new S2LatLng(b.latLo(), bLng).toPoint(); 309 S2Point bHi = new S2LatLng(b.latHi(), bLng).toPoint(); 310 S2Point bLoCrossHi = 311 S2LatLng.fromRadians(0, bLng.radians() - S2.M_PI_2).normalized().toPoint(); 312 313 return S1Angle.min(S2EdgeUtil.getDistance(aLo, bLo, bHi, bLoCrossHi), 314 S1Angle.min(S2EdgeUtil.getDistance(aHi, bLo, bHi, bLoCrossHi), 315 S1Angle.min(S2EdgeUtil.getDistance(bLo, aLo, aHi, aLoCrossHi), 316 S2EdgeUtil.getDistance(bHi, aLo, aHi, aLoCrossHi)))); 317 } 318 319 /** 320 * Return the width and height of this rectangle in latitude-longitude space. 321 * Empty rectangles have a negative width and height. 322 */ getSize()323 public S2LatLng getSize() { 324 return S2LatLng.fromRadians(lat.getLength(), lng.getLength()); 325 } 326 327 /** 328 * More efficient version of Contains() that accepts a S2LatLng rather than an 329 * S2Point. 330 */ contains(S2LatLng ll)331 public boolean contains(S2LatLng ll) { 332 // assert (ll.isValid()); 333 return (lat.contains(ll.lat().radians()) && lng.contains(ll.lng() 334 .radians())); 335 336 } 337 338 /** 339 * Return true if and only if the given point is contained in the interior of 340 * the region (i.e. the region excluding its boundary). The point 'p' does not 341 * need to be normalized. 342 */ interiorContains(S2Point p)343 public boolean interiorContains(S2Point p) { 344 return interiorContains(new S2LatLng(p)); 345 } 346 347 /** 348 * More efficient version of InteriorContains() that accepts a S2LatLng rather 349 * than an S2Point. 350 */ interiorContains(S2LatLng ll)351 public boolean interiorContains(S2LatLng ll) { 352 // assert (ll.isValid()); 353 return (lat.interiorContains(ll.lat().radians()) && lng 354 .interiorContains(ll.lng().radians())); 355 } 356 357 /** 358 * Return true if and only if the rectangle contains the given other 359 * rectangle. 360 */ contains(S2LatLngRect other)361 public boolean contains(S2LatLngRect other) { 362 return lat.contains(other.lat) && lng.contains(other.lng); 363 } 364 365 /** 366 * Return true if and only if the interior of this rectangle contains all 367 * points of the given other rectangle (including its boundary). 368 */ interiorContains(S2LatLngRect other)369 public boolean interiorContains(S2LatLngRect other) { 370 return (lat.interiorContains(other.lat) && lng 371 .interiorContains(other.lng)); 372 } 373 374 /** Return true if this rectangle and the given other rectangle have any 375 points in common. */ intersects(S2LatLngRect other)376 public boolean intersects(S2LatLngRect other) { 377 return lat.intersects(other.lat) && lng.intersects(other.lng); 378 } 379 380 /** 381 * Returns true if this rectangle intersects the given cell. (This is an exact 382 * test and may be fairly expensive, see also MayIntersect below.) 383 */ intersects(S2Cell cell)384 public boolean intersects(S2Cell cell) { 385 // First we eliminate the cases where one region completely contains the 386 // other. Once these are disposed of, then the regions will intersect 387 // if and only if their boundaries intersect. 388 389 if (isEmpty()) { 390 return false; 391 } 392 if (contains(cell.getCenter())) { 393 return true; 394 } 395 if (cell.contains(getCenter().toPoint())) { 396 return true; 397 } 398 399 // Quick rejection test (not required for correctness). 400 if (!intersects(cell.getRectBound())) { 401 return false; 402 } 403 404 // Now check whether the boundaries intersect. Unfortunately, a 405 // latitude-longitude rectangle does not have straight edges -- two edges 406 // are curved, and at least one of them is concave. 407 408 // Precompute the cell vertices as points and latitude-longitudes. 409 S2Point[] cellV = new S2Point[4]; 410 S2LatLng[] cellLl = new S2LatLng[4]; 411 for (int i = 0; i < 4; ++i) { 412 cellV[i] = cell.getVertex(i); // Must be normalized. 413 cellLl[i] = new S2LatLng(cellV[i]); 414 if (contains(cellLl[i])) { 415 return true; // Quick acceptance test. 416 } 417 } 418 419 for (int i = 0; i < 4; ++i) { 420 S1Interval edgeLng = S1Interval.fromPointPair( 421 cellLl[i].lng().radians(), cellLl[(i + 1) & 3].lng().radians()); 422 if (!lng.intersects(edgeLng)) { 423 continue; 424 } 425 426 final S2Point a = cellV[i]; 427 final S2Point b = cellV[(i + 1) & 3]; 428 if (edgeLng.contains(lng.lo())) { 429 if (intersectsLngEdge(a, b, lat, lng.lo())) { 430 return true; 431 } 432 } 433 if (edgeLng.contains(lng.hi())) { 434 if (intersectsLngEdge(a, b, lat, lng.hi())) { 435 return true; 436 } 437 } 438 if (intersectsLatEdge(a, b, lat.lo(), lng)) { 439 return true; 440 } 441 if (intersectsLatEdge(a, b, lat.hi(), lng)) { 442 return true; 443 } 444 } 445 return false; 446 } 447 448 /** 449 * Return true if and only if the interior of this rectangle intersects any 450 * point (including the boundary) of the given other rectangle. 451 */ interiorIntersects(S2LatLngRect other)452 public boolean interiorIntersects(S2LatLngRect other) { 453 return (lat.interiorIntersects(other.lat) && lng 454 .interiorIntersects(other.lng)); 455 } 456 addPoint(S2Point p)457 public S2LatLngRect addPoint(S2Point p) { 458 return addPoint(new S2LatLng(p)); 459 } 460 461 // Increase the size of the bounding rectangle to include the given point. 462 // The rectangle is expanded by the minimum amount possible. addPoint(S2LatLng ll)463 public S2LatLngRect addPoint(S2LatLng ll) { 464 // assert (ll.isValid()); 465 R1Interval newLat = lat.addPoint(ll.lat().radians()); 466 S1Interval newLng = lng.addPoint(ll.lng().radians()); 467 return new S2LatLngRect(newLat, newLng); 468 } 469 470 /** 471 * Return a rectangle that contains all points whose latitude distance from 472 * this rectangle is at most margin.lat(), and whose longitude distance from 473 * this rectangle is at most margin.lng(). In particular, latitudes are 474 * clamped while longitudes are wrapped. Note that any expansion of an empty 475 * interval remains empty, and both components of the given margin must be 476 * non-negative. 477 * 478 * NOTE: If you are trying to grow a rectangle by a certain *distance* on the 479 * sphere (e.g. 5km), use the ConvolveWithCap() method instead. 480 */ expanded(S2LatLng margin)481 public S2LatLngRect expanded(S2LatLng margin) { 482 // assert (margin.lat().radians() >= 0 && margin.lng().radians() >= 0); 483 if (isEmpty()) { 484 return this; 485 } 486 return new S2LatLngRect(lat.expanded(margin.lat().radians()).intersection( 487 fullLat()), lng.expanded(margin.lng().radians())); 488 } 489 490 /** 491 * Return the smallest rectangle containing the union of this rectangle and 492 * the given rectangle. 493 */ union(S2LatLngRect other)494 public S2LatLngRect union(S2LatLngRect other) { 495 return new S2LatLngRect(lat.union(other.lat), lng.union(other.lng)); 496 } 497 498 /** 499 * Return the smallest rectangle containing the intersection of this rectangle 500 * and the given rectangle. Note that the region of intersection may consist 501 * of two disjoint rectangles, in which case a single rectangle spanning both 502 * of them is returned. 503 */ intersection(S2LatLngRect other)504 public S2LatLngRect intersection(S2LatLngRect other) { 505 R1Interval intersectLat = lat.intersection(other.lat); 506 S1Interval intersectLng = lng.intersection(other.lng); 507 if (intersectLat.isEmpty() || intersectLng.isEmpty()) { 508 // The lat/lng ranges must either be both empty or both non-empty. 509 return empty(); 510 } 511 return new S2LatLngRect(intersectLat, intersectLng); 512 } 513 514 /** 515 * Return a rectangle that contains the convolution of this rectangle with a 516 * cap of the given angle. This expands the rectangle by a fixed distance (as 517 * opposed to growing the rectangle in latitude-longitude space). The returned 518 * rectangle includes all points whose minimum distance to the original 519 * rectangle is at most the given angle. 520 */ convolveWithCap(S1Angle angle)521 public S2LatLngRect convolveWithCap(S1Angle angle) { 522 // The most straightforward approach is to build a cap centered on each 523 // vertex and take the union of all the bounding rectangles (including the 524 // original rectangle; this is necessary for very large rectangles). 525 526 // Optimization: convert the angle to a height exactly once. 527 S2Cap cap = S2Cap.fromAxisAngle(new S2Point(1, 0, 0), angle); 528 529 S2LatLngRect r = this; 530 for (int k = 0; k < 4; ++k) { 531 S2Cap vertexCap = S2Cap.fromAxisHeight(getVertex(k).toPoint(), cap 532 .height()); 533 r = r.union(vertexCap.getRectBound()); 534 } 535 return r; 536 } 537 538 /** Return the surface area of this rectangle on the unit sphere. */ area()539 public double area() { 540 if (isEmpty()) { 541 return 0; 542 } 543 544 // This is the size difference of the two spherical caps, multiplied by 545 // the longitude ratio. 546 return lng().getLength() * Math.abs(Math.sin(latHi().radians()) - Math.sin(latLo().radians())); 547 } 548 549 /** Return true if two rectangles contains the same set of points. */ 550 @Override equals(Object that)551 public boolean equals(Object that) { 552 if (!(that instanceof S2LatLngRect)) { 553 return false; 554 } 555 S2LatLngRect otherRect = (S2LatLngRect) that; 556 return lat().equals(otherRect.lat()) && lng().equals(otherRect.lng()); 557 } 558 559 /** 560 * Return true if the latitude and longitude intervals of the two rectangles 561 * are the same up to the given tolerance (see r1interval.h and s1interval.h 562 * for details). 563 */ approxEquals(S2LatLngRect other, double maxError)564 public boolean approxEquals(S2LatLngRect other, double maxError) { 565 return (lat.approxEquals(other.lat, maxError) && lng.approxEquals( 566 other.lng, maxError)); 567 } 568 approxEquals(S2LatLngRect other)569 public boolean approxEquals(S2LatLngRect other) { 570 return approxEquals(other, 1e-15); 571 } 572 573 @Override hashCode()574 public int hashCode() { 575 int value = 17; 576 value = 37 * value + lat.hashCode(); 577 return (37 * value + lng.hashCode()); 578 } 579 580 // ////////////////////////////////////////////////////////////////////// 581 // S2Region interface (see {@code S2Region} for details): 582 583 @Override clone()584 public S2Region clone() { 585 return new S2LatLngRect(this.lo(), this.hi()); 586 } 587 588 @Override getCapBound()589 public S2Cap getCapBound() { 590 // We consider two possible bounding caps, one whose axis passes 591 // through the center of the lat-long rectangle and one whose axis 592 // is the north or south pole. We return the smaller of the two caps. 593 594 if (isEmpty()) { 595 return S2Cap.empty(); 596 } 597 598 double poleZ, poleAngle; 599 if (lat.lo() + lat.hi() < 0) { 600 // South pole axis yields smaller cap. 601 poleZ = -1; 602 poleAngle = S2.M_PI_2 + lat.hi(); 603 } else { 604 poleZ = 1; 605 poleAngle = S2.M_PI_2 - lat.lo(); 606 } 607 S2Cap poleCap = S2Cap.fromAxisAngle(new S2Point(0, 0, poleZ), S1Angle 608 .radians(poleAngle)); 609 610 // For bounding rectangles that span 180 degrees or less in longitude, the 611 // maximum cap size is achieved at one of the rectangle vertices. For 612 // rectangles that are larger than 180 degrees, we punt and always return a 613 // bounding cap centered at one of the two poles. 614 double lngSpan = lng.hi() - lng.lo(); 615 if (Math.IEEEremainder(lngSpan, 2 * S2.M_PI) >= 0) { 616 if (lngSpan < 2 * S2.M_PI) { 617 S2Cap midCap = S2Cap.fromAxisAngle(getCenter().toPoint(), S1Angle 618 .radians(0)); 619 for (int k = 0; k < 4; ++k) { 620 midCap = midCap.addPoint(getVertex(k).toPoint()); 621 } 622 if (midCap.height() < poleCap.height()) { 623 return midCap; 624 } 625 } 626 } 627 return poleCap; 628 } 629 630 @Override getRectBound()631 public S2LatLngRect getRectBound() { 632 return this; 633 } 634 635 @Override contains(S2Cell cell)636 public boolean contains(S2Cell cell) { 637 // A latitude-longitude rectangle contains a cell if and only if it contains 638 // the cell's bounding rectangle. (This is an exact test.) 639 return contains(cell.getRectBound()); 640 } 641 642 /** 643 * This test is cheap but is NOT exact. Use Intersects() if you want a more 644 * accurate and more expensive test. Note that when this method is used by an 645 * S2RegionCoverer, the accuracy isn't all that important since if a cell may 646 * intersect the region then it is subdivided, and the accuracy of this method 647 * goes up as the cells get smaller. 648 */ 649 @Override mayIntersect(S2Cell cell)650 public boolean mayIntersect(S2Cell cell) { 651 // This test is cheap but is NOT exact (see s2latlngrect.h). 652 return intersects(cell.getRectBound()); 653 } 654 655 /** The point 'p' does not need to be normalized. */ contains(S2Point p)656 public boolean contains(S2Point p) { 657 return contains(new S2LatLng(p)); 658 } 659 660 /** 661 * Return true if the edge AB intersects the given edge of constant longitude. 662 */ intersectsLngEdge(S2Point a, S2Point b, R1Interval lat, double lng)663 private static boolean intersectsLngEdge(S2Point a, S2Point b, 664 R1Interval lat, double lng) { 665 // Return true if the segment AB intersects the given edge of constant 666 // longitude. The nice thing about edges of constant longitude is that 667 // they are straight lines on the sphere (geodesics). 668 669 return S2.simpleCrossing(a, b, S2LatLng.fromRadians(lat.lo(), lng) 670 .toPoint(), S2LatLng.fromRadians(lat.hi(), lng).toPoint()); 671 } 672 673 /** 674 * Return true if the edge AB intersects the given edge of constant latitude. 675 */ intersectsLatEdge(S2Point a, S2Point b, double lat, S1Interval lng)676 private static boolean intersectsLatEdge(S2Point a, S2Point b, double lat, 677 S1Interval lng) { 678 // Return true if the segment AB intersects the given edge of constant 679 // latitude. Unfortunately, lines of constant latitude are curves on 680 // the sphere. They can intersect a straight edge in 0, 1, or 2 points. 681 // assert (S2.isUnitLength(a) && S2.isUnitLength(b)); 682 683 // First, compute the normal to the plane AB that points vaguely north. 684 S2Point z = S2Point.normalize(S2.robustCrossProd(a, b)); 685 if (z.z < 0) { 686 z = S2Point.neg(z); 687 } 688 689 // Extend this to an orthonormal frame (x,y,z) where x is the direction 690 // where the great circle through AB achieves its maximium latitude. 691 S2Point y = S2Point.normalize(S2.robustCrossProd(z, new S2Point(0, 0, 1))); 692 S2Point x = S2Point.crossProd(y, z); 693 // assert (S2.isUnitLength(x) && x.z >= 0); 694 695 // Compute the angle "theta" from the x-axis (in the x-y plane defined 696 // above) where the great circle intersects the given line of latitude. 697 double sinLat = Math.sin(lat); 698 if (Math.abs(sinLat) >= x.z) { 699 return false; // The great circle does not reach the given latitude. 700 } 701 // assert (x.z > 0); 702 double cosTheta = sinLat / x.z; 703 double sinTheta = Math.sqrt(1 - cosTheta * cosTheta); 704 double theta = Math.atan2(sinTheta, cosTheta); 705 706 // The candidate intersection points are located +/- theta in the x-y 707 // plane. For an intersection to be valid, we need to check that the 708 // intersection point is contained in the interior of the edge AB and 709 // also that it is contained within the given longitude interval "lng". 710 711 // Compute the range of theta values spanned by the edge AB. 712 S1Interval abTheta = S1Interval.fromPointPair(Math.atan2( 713 a.dotProd(y), a.dotProd(x)), Math.atan2(b.dotProd(y), b.dotProd(x))); 714 715 if (abTheta.contains(theta)) { 716 // Check if the intersection point is also in the given "lng" interval. 717 S2Point isect = S2Point.add(S2Point.mul(x, cosTheta), S2Point.mul(y, 718 sinTheta)); 719 if (lng.contains(Math.atan2(isect.y, isect.x))) { 720 return true; 721 } 722 } 723 if (abTheta.contains(-theta)) { 724 // Check if the intersection point is also in the given "lng" interval. 725 S2Point intersection = S2Point.sub(S2Point.mul(x, cosTheta), S2Point.mul(y, sinTheta)); 726 if (lng.contains(Math.atan2(intersection.y, intersection.x))) { 727 return true; 728 } 729 } 730 return false; 731 732 } 733 734 @Override toString()735 public String toString() { 736 return "[Lo=" + lo() + ", Hi=" + hi() + "]"; 737 } 738 } 739