• 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 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