• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2006 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 
17 package com.google.common.geometry;
18 
19 import com.google.common.base.Preconditions;
20 import com.google.common.collect.Maps;
21 import com.google.common.geometry.S2EdgeIndex.DataEdgeIterator;
22 import com.google.common.geometry.S2EdgeUtil.EdgeCrosser;
23 
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.logging.Logger;
28 
29 /**
30  *
31  * An S2Loop represents a simple spherical polygon. It consists of a single
32  * chain of vertices where the first vertex is implicitly connected to the last.
33  * All loops are defined to have a CCW orientation, i.e. the interior of the
34  * polygon is on the left side of the edges. This implies that a clockwise loop
35  * enclosing a small area is interpreted to be a CCW loop enclosing a very large
36  * area.
37  *
38  *  Loops are not allowed to have any duplicate vertices (whether adjacent or
39  * not), and non-adjacent edges are not allowed to intersect. Loops must have at
40  * least 3 vertices. Although these restrictions are not enforced in optimized
41  * code, you may get unexpected results if they are violated.
42  *
43  *  Point containment is defined such that if the sphere is subdivided into
44  * faces (loops), every point is contained by exactly one face. This implies
45  * that loops do not necessarily contain all (or any) of their vertices An
46  * S2LatLngRect represents a latitude-longitude rectangle. It is capable of
47  * representing the empty and full rectangles as well as single points.
48  *
49  */
50 
51 public final strictfp class S2Loop implements S2Region, Comparable<S2Loop> {
52   private static final Logger log = Logger.getLogger(S2Loop.class.getCanonicalName());
53 
54   /**
55    * Max angle that intersections can be off by and yet still be considered
56    * colinear.
57    */
58   public static final double MAX_INTERSECTION_ERROR = 1e-15;
59 
60   /**
61    * Edge index used for performance-critical operations. For example,
62    * contains() can determine whether a point is inside a loop in nearly
63    * constant time, whereas without an edge index it is forced to compare the
64    * query point against every edge in the loop.
65    */
66   private S2EdgeIndex index;
67 
68   /** Maps each S2Point to its order in the loop, from 1 to numVertices. */
69   private Map<S2Point, Integer> vertexToIndex;
70 
71   private final S2Point[] vertices;
72   private final int numVertices;
73 
74   /**
75    * The index (into "vertices") of the vertex that comes first in the total
76    * ordering of all vertices in this loop.
77    */
78   private int firstLogicalVertex;
79 
80   private S2LatLngRect bound;
81   private boolean originInside;
82   private int depth;
83 
84   /**
85    * Initialize a loop connecting the given vertices. The last vertex is
86    * implicitly connected to the first. All points should be unit length. Loops
87    * must have at least 3 vertices.
88    *
89    * @param vertices
90    */
S2Loop(final List<S2Point> vertices)91   public S2Loop(final List<S2Point> vertices) {
92     this.numVertices = vertices.size();
93     this.vertices = new S2Point[numVertices];
94     this.bound = S2LatLngRect.full();
95     this.depth = 0;
96 
97     // if (debugMode) {
98     //  assert (isValid(vertices, DEFAULT_MAX_ADJACENT));
99     // }
100 
101     vertices.toArray(this.vertices);
102 
103     // initOrigin() must be called before InitBound() because the latter
104     // function expects Contains() to work properly.
105     initOrigin();
106     initBound();
107     initFirstLogicalVertex();
108   }
109 
110   /**
111    * Initialize a loop corresponding to the given cell.
112    */
S2Loop(S2Cell cell)113   public S2Loop(S2Cell cell) {
114     this(cell, cell.getRectBound());
115   }
116 
117   /**
118    * Like the constructor above, but assumes that the cell's bounding rectangle
119    * has been precomputed.
120    *
121    * @param cell
122    * @param bound
123    */
S2Loop(S2Cell cell, S2LatLngRect bound)124   public S2Loop(S2Cell cell, S2LatLngRect bound) {
125     this.bound = bound;
126     numVertices = 4;
127     vertices = new S2Point[numVertices];
128     vertexToIndex = null;
129     index = null;
130     depth = 0;
131     for (int i = 0; i < 4; ++i) {
132       vertices[i] = cell.getVertex(i);
133     }
134     initOrigin();
135     initFirstLogicalVertex();
136   }
137 
138   /**
139    * Copy constructor.
140    */
S2Loop(S2Loop src)141   public S2Loop(S2Loop src) {
142     this.numVertices = src.numVertices();
143     this.vertices = src.vertices.clone();
144     this.vertexToIndex = src.vertexToIndex;
145     this.index = src.index;
146     this.firstLogicalVertex = src.firstLogicalVertex;
147     this.bound = src.getRectBound();
148     this.originInside = src.originInside;
149     this.depth = src.depth();
150   }
151 
depth()152   public int depth() {
153     return depth;
154   }
155 
156   /**
157    * The depth of a loop is defined as its nesting level within its containing
158    * polygon. "Outer shell" loops have depth 0, holes within those loops have
159    * depth 1, shells within those holes have depth 2, etc. This field is only
160    * used by the S2Polygon implementation.
161    *
162    * @param depth
163    */
setDepth(int depth)164   public void setDepth(int depth) {
165     this.depth = depth;
166   }
167 
168   /**
169    * Return true if this loop represents a hole in its containing polygon.
170    */
isHole()171   public boolean isHole() {
172     return (depth & 1) != 0;
173   }
174 
175   /**
176    * The sign of a loop is -1 if the loop represents a hole in its containing
177    * polygon, and +1 otherwise.
178    */
sign()179   public int sign() {
180     return isHole() ? -1 : 1;
181   }
182 
numVertices()183   public int numVertices() {
184     return numVertices;
185   }
186 
187   /**
188    * For convenience, we make two entire copies of the vertex list available:
189    * vertex(n..2*n-1) is mapped to vertex(0..n-1), where n == numVertices().
190    */
vertex(int i)191   public S2Point vertex(int i) {
192     try {
193       return vertices[i >= vertices.length ? i - vertices.length : i];
194     } catch (ArrayIndexOutOfBoundsException e) {
195       throw new IllegalStateException("Invalid vertex index");
196     }
197   }
198 
199   /**
200    * Comparator (needed by Comparable interface)
201    */
202   @Override
compareTo(S2Loop other)203   public int compareTo(S2Loop other) {
204     if (numVertices() != other.numVertices()) {
205       return this.numVertices() - other.numVertices();
206     }
207     // Compare the two loops' vertices, starting with each loop's
208     // firstLogicalVertex. This allows us to always catch cases where logically
209     // identical loops have different vertex orderings (e.g. ABCD and BCDA).
210     int maxVertices = numVertices();
211     int iThis = firstLogicalVertex;
212     int iOther = other.firstLogicalVertex;
213     for (int i = 0; i < maxVertices; ++i, ++iThis, ++iOther) {
214       int compare = vertex(iThis).compareTo(other.vertex(iOther));
215       if (compare != 0) {
216         return compare;
217       }
218     }
219     return 0;
220   }
221 
222   /**
223    * Calculates firstLogicalVertex, the vertex in this loop that comes first in
224    * a total ordering of all vertices (by way of S2Point's compareTo function).
225    */
initFirstLogicalVertex()226   private void initFirstLogicalVertex() {
227     int first = 0;
228     for (int i = 1; i < numVertices; ++i) {
229       if (vertex(i).compareTo(vertex(first)) < 0) {
230         first = i;
231       }
232     }
233     firstLogicalVertex = first;
234   }
235 
236   /**
237    * Return true if the loop area is at most 2*Pi.
238    */
isNormalized()239   public boolean isNormalized() {
240     // We allow a bit of error so that exact hemispheres are
241     // considered normalized.
242     return getArea() <= 2 * S2.M_PI + 1e-14;
243   }
244 
245   /**
246    * Invert the loop if necessary so that the area enclosed by the loop is at
247    * most 2*Pi.
248    */
normalize()249   public void normalize() {
250     if (!isNormalized()) {
251       invert();
252     }
253   }
254 
255   /**
256    * Reverse the order of the loop vertices, effectively complementing the
257    * region represented by the loop.
258    */
invert()259   public void invert() {
260     int last = numVertices() - 1;
261     for (int i = (last - 1) / 2; i >= 0; --i) {
262       S2Point t = vertices[i];
263       vertices[i] = vertices[last - i];
264       vertices[last - i] = t;
265     }
266     vertexToIndex = null;
267     index = null;
268     originInside ^= true;
269     if (bound.lat().lo() > -S2.M_PI_2 && bound.lat().hi() < S2.M_PI_2) {
270       // The complement of this loop contains both poles.
271       bound = S2LatLngRect.full();
272     } else {
273       initBound();
274     }
275     initFirstLogicalVertex();
276   }
277 
278   /**
279    * Helper method to get area and optionally centroid.
280    */
getAreaCentroid(boolean doCentroid)281   private S2AreaCentroid getAreaCentroid(boolean doCentroid) {
282     S2Point centroid = null;
283     // Don't crash even if loop is not well-defined.
284     if (numVertices() < 3) {
285       return new S2AreaCentroid(0D, centroid);
286     }
287 
288     // The triangle area calculation becomes numerically unstable as the length
289     // of any edge approaches 180 degrees. However, a loop may contain vertices
290     // that are 180 degrees apart and still be valid, e.g. a loop that defines
291     // the northern hemisphere using four points. We handle this case by using
292     // triangles centered around an origin that is slightly displaced from the
293     // first vertex. The amount of displacement is enough to get plenty of
294     // accuracy for antipodal points, but small enough so that we still get
295     // accurate areas for very tiny triangles.
296     //
297     // Of course, if the loop contains a point that is exactly antipodal from
298     // our slightly displaced vertex, the area will still be unstable, but we
299     // expect this case to be very unlikely (i.e. a polygon with two vertices on
300     // opposite sides of the Earth with one of them displaced by about 2mm in
301     // exactly the right direction). Note that the approximate point resolution
302     // using the E7 or S2CellId representation is only about 1cm.
303 
304     S2Point origin = vertex(0);
305     int axis = (origin.largestAbsComponent() + 1) % 3;
306     double slightlyDisplaced = origin.get(axis) + S2.M_E * 1e-10;
307     origin =
308         new S2Point((axis == 0) ? slightlyDisplaced : origin.x,
309             (axis == 1) ? slightlyDisplaced : origin.y, (axis == 2) ? slightlyDisplaced : origin.z);
310     origin = S2Point.normalize(origin);
311 
312     double areaSum = 0;
313     S2Point centroidSum = new S2Point(0, 0, 0);
314     for (int i = 1; i <= numVertices(); ++i) {
315       areaSum += S2.signedArea(origin, vertex(i - 1), vertex(i));
316       if (doCentroid) {
317         // The true centroid is already premultiplied by the triangle area.
318         S2Point trueCentroid = S2.trueCentroid(origin, vertex(i - 1), vertex(i));
319         centroidSum = S2Point.add(centroidSum, trueCentroid);
320       }
321     }
322     // The calculated area at this point should be between -4*Pi and 4*Pi,
323     // although it may be slightly larger or smaller than this due to
324     // numerical errors.
325     // assert (Math.abs(areaSum) <= 4 * S2.M_PI + 1e-12);
326 
327     if (areaSum < 0) {
328       // If the area is negative, we have computed the area to the right of the
329       // loop. The area to the left is 4*Pi - (-area). Amazingly, the centroid
330       // does not need to be changed, since it is the negative of the integral
331       // of position over the region to the right of the loop. This is the same
332       // as the integral of position over the region to the left of the loop,
333       // since the integral of position over the entire sphere is (0, 0, 0).
334       areaSum += 4 * S2.M_PI;
335     }
336     // The loop's sign() does not affect the return result and should be taken
337     // into account by the caller.
338     if (doCentroid) {
339       centroid = centroidSum;
340     }
341     return new S2AreaCentroid(areaSum, centroid);
342   }
343 
344   /**
345    * Return the area of the loop interior, i.e. the region on the left side of
346    * the loop. The return value is between 0 and 4*Pi and the true centroid of
347    * the loop multiplied by the area of the loop (see S2.java for details on
348    * centroids). Note that the centroid may not be contained by the loop.
349    */
getAreaAndCentroid()350   public S2AreaCentroid getAreaAndCentroid() {
351     return getAreaCentroid(true);
352   }
353 
354   /**
355    * Return the area of the polygon interior, i.e. the region on the left side
356    * of an odd number of loops. The return value is between 0 and 4*Pi.
357    */
getArea()358   public double getArea() {
359     return getAreaCentroid(false).getArea();
360   }
361 
362   /**
363    * Return the true centroid of the polygon multiplied by the area of the
364    * polygon (see {@link S2} for details on centroids). Note that the centroid
365    * may not be contained by the polygon.
366    */
getCentroid()367   public S2Point getCentroid() {
368     return getAreaCentroid(true).getCentroid();
369   }
370 
371   // The following are the possible relationships between two loops A and B:
372   //
373   // (1) A and B do not intersect.
374   // (2) A contains B.
375   // (3) B contains A.
376   // (4) The boundaries of A and B cross (i.e. the boundary of A
377   // intersects the interior and exterior of B and vice versa).
378   // (5) (A union B) is the entire sphere (i.e. A contains the
379   // complement of B and vice versa).
380   //
381   // More than one of these may be true at the same time, for example if
382   // A == B or A == Complement(B).
383 
384   /**
385    * Return true if the region contained by this loop is a superset of the
386    * region contained by the given other loop.
387    */
contains(S2Loop b)388   public boolean contains(S2Loop b) {
389     // For this loop A to contains the given loop B, all of the following must
390     // be true:
391     //
392     // (1) There are no edge crossings between A and B except at vertices.
393     //
394     // (2) At every vertex that is shared between A and B, the local edge
395     // ordering implies that A contains B.
396     //
397     // (3) If there are no shared vertices, then A must contain a vertex of B
398     // and B must not contain a vertex of A. (An arbitrary vertex may be
399     // chosen in each case.)
400     //
401     // The second part of (3) is necessary to detect the case of two loops whose
402     // union is the entire sphere, i.e. two loops that contains each other's
403     // boundaries but not each other's interiors.
404 
405     if (!bound.contains(b.getRectBound())) {
406       return false;
407     }
408 
409     // Unless there are shared vertices, we need to check whether A contains a
410     // vertex of B. Since shared vertices are rare, it is more efficient to do
411     // this test up front as a quick rejection test.
412     if (!contains(b.vertex(0)) && findVertex(b.vertex(0)) < 0) {
413       return false;
414     }
415 
416     // Now check whether there are any edge crossings, and also check the loop
417     // relationship at any shared vertices.
418     if (checkEdgeCrossings(b, new S2EdgeUtil.WedgeContains()) <= 0) {
419       return false;
420     }
421 
422     // At this point we know that the boundaries of A and B do not intersect,
423     // and that A contains a vertex of B. However we still need to check for
424     // the case mentioned above, where (A union B) is the entire sphere.
425     // Normally this check is very cheap due to the bounding box precondition.
426     if (bound.union(b.getRectBound()).isFull()) {
427       if (b.contains(vertex(0)) && b.findVertex(vertex(0)) < 0) {
428         return false;
429       }
430     }
431     return true;
432   }
433 
434   /**
435    * Return true if the region contained by this loop intersects the region
436    * contained by the given other loop.
437    */
intersects(S2Loop b)438   public boolean intersects(S2Loop b) {
439     // a->Intersects(b) if and only if !a->Complement()->Contains(b).
440     // This code is similar to Contains(), but is optimized for the case
441     // where both loops enclose less than half of the sphere.
442 
443     if (!bound.intersects(b.getRectBound())) {
444       return false;
445     }
446 
447     // Normalize the arguments so that B has a smaller longitude span than A.
448     // This makes intersection tests much more efficient in the case where
449     // longitude pruning is used (see CheckEdgeCrossings).
450     if (b.getRectBound().lng().getLength() > bound.lng().getLength()) {
451       return b.intersects(this);
452     }
453 
454     // Unless there are shared vertices, we need to check whether A contains a
455     // vertex of B. Since shared vertices are rare, it is more efficient to do
456     // this test up front as a quick acceptance test.
457     if (contains(b.vertex(0)) && findVertex(b.vertex(0)) < 0) {
458       return true;
459     }
460 
461     // Now check whether there are any edge crossings, and also check the loop
462     // relationship at any shared vertices.
463     if (checkEdgeCrossings(b, new S2EdgeUtil.WedgeIntersects()) < 0) {
464       return true;
465     }
466 
467     // We know that A does not contain a vertex of B, and that there are no edge
468     // crossings. Therefore the only way that A can intersect B is if B
469     // entirely contains A. We can check this by testing whether B contains an
470     // arbitrary non-shared vertex of A. Note that this check is cheap because
471     // of the bounding box precondition and the fact that we normalized the
472     // arguments so that A's longitude span is at least as long as B's.
473     if (b.getRectBound().contains(bound)) {
474       if (b.contains(vertex(0)) && b.findVertex(vertex(0)) < 0) {
475         return true;
476       }
477     }
478 
479     return false;
480   }
481 
482   /**
483    * Given two loops of a polygon, return true if A contains B. This version of
484    * contains() is much cheaper since it does not need to check whether the
485    * boundaries of the two loops cross.
486    */
containsNested(S2Loop b)487   public boolean containsNested(S2Loop b) {
488     if (!bound.contains(b.getRectBound())) {
489       return false;
490     }
491 
492     // We are given that A and B do not share any edges, and that either one
493     // loop contains the other or they do not intersect.
494     int m = findVertex(b.vertex(1));
495     if (m < 0) {
496       // Since b->vertex(1) is not shared, we can check whether A contains it.
497       return contains(b.vertex(1));
498     }
499     // Check whether the edge order around b->vertex(1) is compatible with
500     // A containin B.
501     return (new S2EdgeUtil.WedgeContains()).test(
502         vertex(m - 1), vertex(m), vertex(m + 1), b.vertex(0), b.vertex(2)) > 0;
503   }
504 
505   /**
506    * Return +1 if A contains B (i.e. the interior of B is a subset of the
507    * interior of A), -1 if the boundaries of A and B cross, and 0 otherwise.
508    * Requires that A does not properly contain the complement of B, i.e. A and B
509    * do not contain each other's boundaries. This method is used for testing
510    * whether multi-loop polygons contain each other.
511    */
containsOrCrosses(S2Loop b)512   public int containsOrCrosses(S2Loop b) {
513     // There can be containment or crossing only if the bounds intersect.
514     if (!bound.intersects(b.getRectBound())) {
515       return 0;
516     }
517 
518     // Now check whether there are any edge crossings, and also check the loop
519     // relationship at any shared vertices. Note that unlike Contains() or
520     // Intersects(), we can't do a point containment test as a shortcut because
521     // we need to detect whether there are any edge crossings.
522     int result = checkEdgeCrossings(b, new S2EdgeUtil.WedgeContainsOrCrosses());
523 
524     // If there was an edge crossing or a shared vertex, we know the result
525     // already. (This is true even if the result is 1, but since we don't
526     // bother keeping track of whether a shared vertex was seen, we handle this
527     // case below.)
528     if (result <= 0) {
529       return result;
530     }
531 
532     // At this point we know that the boundaries do not intersect, and we are
533     // given that (A union B) is a proper subset of the sphere. Furthermore
534     // either A contains B, or there are no shared vertices (due to the check
535     // above). So now we just need to distinguish the case where A contains B
536     // from the case where B contains A or the two loops are disjoint.
537     if (!bound.contains(b.getRectBound())) {
538       return 0;
539     }
540     if (!contains(b.vertex(0)) && findVertex(b.vertex(0)) < 0) {
541       return 0;
542     }
543 
544     return 1;
545   }
546 
547   /**
548    * Returns true if two loops have the same boundary except for vertex
549    * perturbations. More precisely, the vertices in the two loops must be in the
550    * same cyclic order, and corresponding vertex pairs must be separated by no
551    * more than maxError. Note: This method mostly useful only for testing
552    * purposes.
553    */
boundaryApproxEquals(S2Loop b, double maxError)554   boolean boundaryApproxEquals(S2Loop b, double maxError) {
555     if (numVertices() != b.numVertices()) {
556       return false;
557     }
558     int maxVertices = numVertices();
559     int iThis = firstLogicalVertex;
560     int iOther = b.firstLogicalVertex;
561     for (int i = 0; i < maxVertices; ++i, ++iThis, ++iOther) {
562       if (!S2.approxEquals(vertex(iThis), b.vertex(iOther), maxError)) {
563         return false;
564       }
565     }
566     return true;
567   }
568 
569   // S2Region interface (see {@code S2Region} for details):
570 
571   /** Return a bounding spherical cap. */
572   @Override
getCapBound()573   public S2Cap getCapBound() {
574     return bound.getCapBound();
575   }
576 
577 
578   /** Return a bounding latitude-longitude rectangle. */
579   @Override
getRectBound()580   public S2LatLngRect getRectBound() {
581     return bound;
582   }
583 
584   /**
585    * If this method returns true, the region completely contains the given cell.
586    * Otherwise, either the region does not contain the cell or the containment
587    * relationship could not be determined.
588    */
589   @Override
contains(S2Cell cell)590   public boolean contains(S2Cell cell) {
591     // It is faster to construct a bounding rectangle for an S2Cell than for
592     // a general polygon. A future optimization could also take advantage of
593     // the fact than an S2Cell is convex.
594 
595     S2LatLngRect cellBound = cell.getRectBound();
596     if (!bound.contains(cellBound)) {
597       return false;
598     }
599     S2Loop cellLoop = new S2Loop(cell, cellBound);
600     return contains(cellLoop);
601   }
602 
603   /**
604    * If this method returns false, the region does not intersect the given cell.
605    * Otherwise, either region intersects the cell, or the intersection
606    * relationship could not be determined.
607    */
608   @Override
mayIntersect(S2Cell cell)609   public boolean mayIntersect(S2Cell cell) {
610     // It is faster to construct a bounding rectangle for an S2Cell than for
611     // a general polygon. A future optimization could also take advantage of
612     // the fact than an S2Cell is convex.
613 
614     S2LatLngRect cellBound = cell.getRectBound();
615     if (!bound.intersects(cellBound)) {
616       return false;
617     }
618     return new S2Loop(cell, cellBound).intersects(this);
619   }
620 
621   /**
622    * The point 'p' does not need to be normalized.
623    */
contains(S2Point p)624   public boolean contains(S2Point p) {
625     if (!bound.contains(p)) {
626       return false;
627     }
628 
629     boolean inside = originInside;
630     S2Point origin = S2.origin();
631     S2EdgeUtil.EdgeCrosser crosser = new S2EdgeUtil.EdgeCrosser(origin, p,
632         vertices[numVertices - 1]);
633 
634     // The s2edgeindex library is not optimized yet for long edges,
635     // so the tradeoff to using it comes with larger loops.
636     if (numVertices < 2000) {
637       for (int i = 0; i < numVertices; i++) {
638         inside ^= crosser.edgeOrVertexCrossing(vertices[i]);
639       }
640     } else {
641       DataEdgeIterator it = getEdgeIterator(numVertices);
642       int previousIndex = -2;
643       for (it.getCandidates(origin, p); it.hasNext(); it.next()) {
644         int ai = it.index();
645         if (previousIndex != ai - 1) {
646           crosser.restartAt(vertices[ai]);
647         }
648         previousIndex = ai;
649         inside ^= crosser.edgeOrVertexCrossing(vertex(ai + 1));
650       }
651     }
652 
653     return inside;
654   }
655 
656   /**
657    * Returns the shortest distance from a point P to this loop, given as the
658    * angle formed between P, the origin and the nearest point on the loop to P.
659    * This angle in radians is equivalent to the arclength along the unit sphere.
660    */
getDistance(S2Point p)661   public S1Angle getDistance(S2Point p) {
662     S2Point normalized = S2Point.normalize(p);
663 
664     // The furthest point from p on the sphere is its antipode, which is an
665     // angle of PI radians. This is an upper bound on the angle.
666     S1Angle minDistance = S1Angle.radians(Math.PI);
667     for (int i = 0; i < numVertices(); i++) {
668       minDistance =
669           S1Angle.min(minDistance, S2EdgeUtil.getDistance(normalized, vertex(i), vertex(i + 1)));
670     }
671     return minDistance;
672   }
673 
674   /**
675    * Creates an edge index over the vertices, which by itself takes no time.
676    * Then the expected number of queries is used to determine whether brute
677    * force lookups are likely to be slower than really creating an index, and if
678    * so, we do so. Finally an iterator is returned that can be used to perform
679    * edge lookups.
680    */
getEdgeIterator(int expectedQueries)681   private final DataEdgeIterator getEdgeIterator(int expectedQueries) {
682     if (index == null) {
683       index = new S2EdgeIndex() {
684         @Override
685         protected int getNumEdges() {
686           return numVertices;
687         }
688 
689         @Override
690         protected S2Point edgeFrom(int index) {
691           return vertex(index);
692         }
693 
694         @Override
695         protected S2Point edgeTo(int index) {
696           return vertex(index + 1);
697         }
698       };
699     }
700     index.predictAdditionalCalls(expectedQueries);
701     return new S2EdgeIndex.DataEdgeIterator(index);
702   }
703 
704   /** Return true if this loop is valid. */
isValid()705   public boolean isValid() {
706     if (numVertices < 3) {
707       log.info("Degenerate loop");
708       return false;
709     }
710 
711     // All vertices must be unit length.
712     for (int i = 0; i < numVertices; ++i) {
713       if (!S2.isUnitLength(vertex(i))) {
714         log.info("Vertex " + i + " is not unit length");
715         return false;
716       }
717     }
718 
719     // Loops are not allowed to have any duplicate vertices.
720     HashMap<S2Point, Integer> vmap = Maps.newHashMap();
721     for (int i = 0; i < numVertices; ++i) {
722       Integer previousVertexIndex = vmap.put(vertex(i), i);
723       if (previousVertexIndex != null) {
724         log.info("Duplicate vertices: " + previousVertexIndex + " and " + i);
725         return false;
726       }
727     }
728 
729     // Non-adjacent edges are not allowed to intersect.
730     boolean crosses = false;
731     DataEdgeIterator it = getEdgeIterator(numVertices);
732     for (int a1 = 0; a1 < numVertices; a1++) {
733       int a2 = (a1 + 1) % numVertices;
734       EdgeCrosser crosser = new EdgeCrosser(vertex(a1), vertex(a2), vertex(0));
735       int previousIndex = -2;
736       for (it.getCandidates(vertex(a1), vertex(a2)); it.hasNext(); it.next()) {
737         int b1 = it.index();
738         int b2 = (b1 + 1) % numVertices;
739         // If either 'a' index equals either 'b' index, then these two edges
740         // share a vertex. If a1==b1 then it must be the case that a2==b2, e.g.
741         // the two edges are the same. In that case, we skip the test, since we
742         // don't want to test an edge against itself. If a1==b2 or b1==a2 then
743         // we have one edge ending at the start of the other, or in other words,
744         // the edges share a vertex -- and in S2 space, where edges are always
745         // great circle segments on a sphere, edges can only intersect at most
746         // once, so we don't need to do further checks in that case either.
747         if (a1 != b2 && a2 != b1 && a1 != b1) {
748           // WORKAROUND(shakusa, ericv): S2.robustCCW() currently
749           // requires arbitrary-precision arithmetic to be truly robust. That
750           // means it can give the wrong answers in cases where we are trying
751           // to determine edge intersections. The workaround is to ignore
752           // intersections between edge pairs where all four points are
753           // nearly colinear.
754           double abc = S2.angle(vertex(a1), vertex(a2), vertex(b1));
755           boolean abcNearlyLinear = S2.approxEquals(abc, 0D, MAX_INTERSECTION_ERROR) ||
756               S2.approxEquals(abc, S2.M_PI, MAX_INTERSECTION_ERROR);
757           double abd = S2.angle(vertex(a1), vertex(a2), vertex(b2));
758           boolean abdNearlyLinear = S2.approxEquals(abd, 0D, MAX_INTERSECTION_ERROR) ||
759               S2.approxEquals(abd, S2.M_PI, MAX_INTERSECTION_ERROR);
760           if (abcNearlyLinear && abdNearlyLinear) {
761             continue;
762           }
763 
764           if (previousIndex != b1) {
765             crosser.restartAt(vertex(b1));
766           }
767 
768           // Beware, this may return the loop is valid if there is a
769           // "vertex crossing".
770           // TODO(user): Fix that.
771           crosses = crosser.robustCrossing(vertex(b2)) > 0;
772           previousIndex = b2;
773           if (crosses ) {
774             log.info("Edges " + a1 + " and " + b1 + " cross");
775             log.info(String.format("Edge locations in degrees: " + "%s-%s and %s-%s",
776                 new S2LatLng(vertex(a1)).toStringDegrees(),
777                 new S2LatLng(vertex(a2)).toStringDegrees(),
778                 new S2LatLng(vertex(b1)).toStringDegrees(),
779                 new S2LatLng(vertex(b2)).toStringDegrees()));
780             return false;
781           }
782         }
783       }
784     }
785 
786     return true;
787   }
788 
789   /**
790    * Static version of isValid(), to be used only when an S2Loop instance is not
791    * available, but validity of the points must be checked.
792    *
793    * @return true if the given loop is valid. Creates an instance of S2Loop and
794    *         defers this call to {@link #isValid()}.
795    */
isValid(List<S2Point> vertices)796   public static boolean isValid(List<S2Point> vertices) {
797     return new S2Loop(vertices).isValid();
798   }
799 
800   @Override
toString()801   public String toString() {
802     StringBuilder builder = new StringBuilder("S2Loop, ");
803 
804     builder.append(vertices.length).append(" points. [");
805 
806     for (S2Point v : vertices) {
807       builder.append(v.toString()).append(" ");
808     }
809     builder.append("]");
810 
811     return builder.toString();
812   }
813 
initOrigin()814   private void initOrigin() {
815     // The bounding box does not need to be correct before calling this
816     // function, but it must at least contain vertex(1) since we need to
817     // do a Contains() test on this point below.
818     Preconditions.checkState(bound.contains(vertex(1)));
819 
820     // To ensure that every point is contained in exactly one face of a
821     // subdivision of the sphere, all containment tests are done by counting the
822     // edge crossings starting at a fixed point on the sphere (S2::Origin()).
823     // We need to know whether this point is inside or outside of the loop.
824     // We do this by first guessing that it is outside, and then seeing whether
825     // we get the correct containment result for vertex 1. If the result is
826     // incorrect, the origin must be inside the loop.
827     //
828     // A loop with consecutive vertices A,B,C contains vertex B if and only if
829     // the fixed vector R = S2::Ortho(B) is on the left side of the wedge ABC.
830     // The test below is written so that B is inside if C=R but not if A=R.
831 
832     originInside = false; // Initialize before calling Contains().
833     boolean v1Inside = S2.orderedCCW(S2.ortho(vertex(1)), vertex(0), vertex(2), vertex(1));
834     if (v1Inside != contains(vertex(1))) {
835       originInside = true;
836     }
837   }
838 
initBound()839   private void initBound() {
840     // The bounding rectangle of a loop is not necessarily the same as the
841     // bounding rectangle of its vertices. First, the loop may wrap entirely
842     // around the sphere (e.g. a loop that defines two revolutions of a
843     // candy-cane stripe). Second, the loop may include one or both poles.
844     // Note that a small clockwise loop near the equator contains both poles.
845 
846     S2EdgeUtil.RectBounder bounder = new S2EdgeUtil.RectBounder();
847     for (int i = 0; i <= numVertices(); ++i) {
848       bounder.addPoint(vertex(i));
849     }
850     S2LatLngRect b = bounder.getBound();
851     // Note that we need to initialize bound with a temporary value since
852     // contains() does a bounding rectangle check before doing anything else.
853     bound = S2LatLngRect.full();
854     if (contains(new S2Point(0, 0, 1))) {
855       b = new S2LatLngRect(new R1Interval(b.lat().lo(), S2.M_PI_2), S1Interval.full());
856     }
857     // If a loop contains the south pole, then either it wraps entirely
858     // around the sphere (full longitude range), or it also contains the
859     // north pole in which case b.lng().isFull() due to the test above.
860 
861     if (b.lng().isFull() && contains(new S2Point(0, 0, -1))) {
862       b = new S2LatLngRect(new R1Interval(-S2.M_PI_2, b.lat().hi()), b.lng());
863     }
864     bound = b;
865   }
866 
867   /**
868    * Return the index of a vertex at point "p", or -1 if not found. The return
869    * value is in the range 1..num_vertices_ if found.
870    */
findVertex(S2Point p)871   private int findVertex(S2Point p) {
872     if (vertexToIndex == null) {
873       vertexToIndex = new HashMap<S2Point, Integer>();
874       for (int i = 1; i <= numVertices; i++) {
875         vertexToIndex.put(vertex(i), i);
876       }
877     }
878     Integer index = vertexToIndex.get(p);
879     if (index == null) {
880       return -1;
881     } else {
882       return index;
883     }
884   }
885 
886   /**
887    * This method encapsulates the common code for loop containment and
888    * intersection tests. It is used in three slightly different variations to
889    * implement contains(), intersects(), and containsOrCrosses().
890    *
891    *  In a nutshell, this method checks all the edges of this loop (A) for
892    * intersection with all the edges of B. It returns -1 immediately if any edge
893    * intersections are found. Otherwise, if there are any shared vertices, it
894    * returns the minimum value of the given WedgeRelation for all such vertices
895    * (returning immediately if any wedge returns -1). Returns +1 if there are no
896    * intersections and no shared vertices.
897    */
checkEdgeCrossings(S2Loop b, S2EdgeUtil.WedgeRelation relation)898   private int checkEdgeCrossings(S2Loop b, S2EdgeUtil.WedgeRelation relation) {
899     DataEdgeIterator it = getEdgeIterator(b.numVertices);
900     int result = 1;
901     // since 'this' usually has many more vertices than 'b', use the index on
902     // 'this' and loop over 'b'
903     for (int j = 0; j < b.numVertices(); ++j) {
904       S2EdgeUtil.EdgeCrosser crosser =
905         new S2EdgeUtil.EdgeCrosser(b.vertex(j), b.vertex(j + 1), vertex(0));
906       int previousIndex = -2;
907       for (it.getCandidates(b.vertex(j), b.vertex(j + 1)); it.hasNext(); it.next()) {
908         int i = it.index();
909         if (previousIndex != i - 1) {
910           crosser.restartAt(vertex(i));
911         }
912         previousIndex = i;
913         int crossing = crosser.robustCrossing(vertex(i + 1));
914         if (crossing < 0) {
915           continue;
916         }
917         if (crossing > 0) {
918           return -1; // There is a proper edge crossing.
919         }
920         if (vertex(i + 1).equals(b.vertex(j + 1))) {
921           result = Math.min(result, relation.test(
922               vertex(i), vertex(i + 1), vertex(i + 2), b.vertex(j), b.vertex(j + 2)));
923           if (result < 0) {
924             return result;
925           }
926         }
927       }
928     }
929     return result;
930   }
931 }
932