• 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.HashMultiset;
21 import com.google.common.collect.Lists;
22 import com.google.common.collect.Maps;
23 import com.google.common.collect.Multiset;
24 import com.google.common.collect.TreeMultimap;
25 
26 import java.util.Collections;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.logging.Logger;
32 
33 /**
34  * An S2Polygon is an S2Region object that represents a polygon. A polygon
35  * consists of zero or more {@link S2Loop loops} representing "shells" and
36  * "holes". All loops should be oriented CCW, i.e. the shell or hole is on the
37  * left side of the loop. Loops may be specified in any order. A point is
38  * defined to be inside the polygon if it is contained by an odd number of
39  * loops.
40  *
41  *  Polygons have the following restrictions:
42  *
43  *  - Loops may not cross, i.e. the boundary of a loop may not intersect both
44  * the interior and exterior of any other loop.
45  *
46  *  - Loops may not share edges, i.e. if a loop contains an edge AB, then no
47  * other loop may contain AB or BA.
48  *
49  *  - No loop may cover more than half the area of the sphere. This ensures that
50  * no loop properly contains the complement of any other loop, even if the loops
51  * are from different polygons. (Loops that represent exact hemispheres are
52  * allowed.)
53  *
54  *  Loops may share vertices, however no vertex may appear twice in a single
55  * loop.
56  *
57  */
58 public final strictfp class S2Polygon implements S2Region, Comparable<S2Polygon> {
59   private static final Logger log = Logger.getLogger(S2Polygon.class.getCanonicalName());
60 
61   private List<S2Loop> loops;
62 
63   private S2LatLngRect bound;
64   private boolean hasHoles;
65   private int numVertices;
66 
67   /**
68    * Creates an empty polygon that should be initialized by calling Init().
69    */
S2Polygon()70   public S2Polygon() {
71     this.loops = Lists.newArrayList();
72     this.bound = S2LatLngRect.empty();
73     this.hasHoles = false;
74     this.numVertices = 0;
75   }
76 
77   /**
78    * Convenience constructor that calls Init() with the given loops. Clears the
79    * given list.
80    */
S2Polygon(List<S2Loop> loops)81   public S2Polygon(List<S2Loop> loops) {
82     this.loops = Lists.newArrayList();
83     this.bound = S2LatLngRect.empty();
84 
85     init(loops);
86   }
87 
88   /**
89    * Copy constructor.
90    */
S2Polygon(S2Loop loop)91   public S2Polygon(S2Loop loop) {
92     this.loops = Lists.newArrayList();
93     this.bound = loop.getRectBound();
94     this.hasHoles = false;
95     this.numVertices = loop.numVertices();
96 
97     loops.add(loop);
98   }
99 
100   /**
101    * Copy constructor.
102    */
S2Polygon(S2Polygon src)103   public S2Polygon(S2Polygon src) {
104     this.loops = Lists.newArrayList();
105     this.bound = src.getRectBound();
106     this.hasHoles = src.hasHoles;
107     this.numVertices = src.numVertices;
108 
109     for (int i = 0; i < src.numLoops(); ++i) {
110       loops.add(new S2Loop(src.loop(i)));
111     }
112   }
113 
114   /**
115    * Comparator (needed by Comparable interface). For two polygons to be
116    * compared as equal: - the must have the same number of loops; - the loops
117    * must be ordered in the same way (this is guaranteed by the total ordering
118    * imposed by sortValueLoops). - loops must be logically equivalent (even if
119    * ordered with a different starting point, e.g. ABCD and BCDA).
120    */
121   @Override
compareTo(S2Polygon other)122   public int compareTo(S2Polygon other) {
123     // If number of loops differ, use that.
124     if (this.numLoops() != other.numLoops()) {
125       return this.numLoops() - other.numLoops();
126     }
127     for (int i = 0; i < this.numLoops(); ++i) {
128       int compare = this.loops.get(i).compareTo(other.loops.get(i));
129       if (compare != 0) {
130         return compare;
131       }
132     }
133     return 0;
134   }
135 
136   /**
137    * Initialize a polygon by taking ownership of the given loops and clearing
138    * the given list. This method figures out the loop nesting hierarchy and then
139    * reorders the loops by following a preorder traversal. This implies that
140    * each loop is immediately followed by its descendants in the nesting
141    * hierarchy. (See also getParent and getLastDescendant.)
142    */
init(List<S2Loop> loops)143   public void init(List<S2Loop> loops) {
144     // assert isValid(loops);
145     // assert (this.loops.isEmpty());
146 
147     Map<S2Loop, List<S2Loop>> loopMap = Maps.newHashMap();
148     // Yes, a null key is valid. It is used here to refer to the root of the
149     // loopMap
150     loopMap.put(null, Lists.<S2Loop>newArrayList());
151 
152     for (S2Loop loop : loops) {
153       insertLoop(loop, null, loopMap);
154       this.numVertices += loop.numVertices();
155     }
156     loops.clear();
157 
158     // Sort all of the lists of loops; in this way we guarantee a total ordering
159     // on loops in the polygon. Loops will be sorted by their natural ordering,
160     // while also preserving the requirement that each loop is immediately
161     // followed by its descendants in the nesting hierarchy.
162     //
163     // TODO(andriy): as per kirilll in CL 18750833 code review comments:
164     // This should work for now, but I think it's possible to guarantee the
165     // correct order inside insertLoop by searching for the correct position in
166     // the children list before inserting.
167     sortValueLoops(loopMap);
168 
169     // Reorder the loops in depth-first traversal order.
170     // Starting at null == starting at the root
171     initLoop(null, -1, loopMap);
172 
173     // TODO(dbeaumont): Add tests or preconditions for these asserts (here and elesewhere).
174     // forall i != j : containsChild(loop(i), loop(j), loopMap) == loop(i).containsNested(loop(j)));
175 
176     // Compute the bounding rectangle of the entire polygon.
177     hasHoles = false;
178     bound = S2LatLngRect.empty();
179     for (int i = 0; i < numLoops(); ++i) {
180       if (loop(i).sign() < 0) {
181         hasHoles = true;
182       } else {
183         bound = bound.union(loop(i).getRectBound());
184       }
185     }
186   }
187 
188   /**
189    * Release ownership of the loops of this polygon by appending them to the
190    * given list. Resets the polygon to be empty.
191    */
release(List<S2Loop> loops)192   public void release(List<S2Loop> loops) {
193     loops.addAll(this.loops);
194     this.loops.clear();
195     bound = S2LatLngRect.empty();
196     hasHoles = false;
197     numVertices = 0;
198   }
199 
200   /**
201    * Return true if the given loops form a valid polygon. Assumes that that all
202    * of the given loops have already been validated.
203    */
isValid(final List<S2Loop> loops)204   public static boolean isValid(final List<S2Loop> loops) {
205     // If a loop contains an edge AB, then no other loop may contain AB or BA.
206     // We only need this test if there are at least two loops, assuming that
207     // each loop has already been validated.
208     if (loops.size() > 1) {
209       Map<UndirectedEdge, LoopVertexIndexPair> edges = Maps.newHashMap();
210       for (int i = 0; i < loops.size(); ++i) {
211         S2Loop lp = loops.get(i);
212         for (int j = 0; j < lp.numVertices(); ++j) {
213           UndirectedEdge key = new UndirectedEdge(lp.vertex(j), lp.vertex(j + 1));
214           LoopVertexIndexPair value = new LoopVertexIndexPair(i, j);
215           if (edges.containsKey(key)) {
216             LoopVertexIndexPair other = edges.get(key);
217             log.info(
218                 "Duplicate edge: loop " + i + ", edge " + j + " and loop " + other.getLoopIndex()
219                     + ", edge " + other.getVertexIndex());
220             return false;
221           } else {
222             edges.put(key, value);
223           }
224         }
225       }
226     }
227 
228     // Verify that no loop covers more than half of the sphere, and that no
229     // two loops cross.
230     for (int i = 0; i < loops.size(); ++i) {
231       if (!loops.get(i).isNormalized()) {
232         log.info("Loop " + i + " encloses more than half the sphere");
233         return false;
234       }
235       for (int j = i + 1; j < loops.size(); ++j) {
236         // This test not only checks for edge crossings, it also detects
237         // cases where the two boundaries cross at a shared vertex.
238         if (loops.get(i).containsOrCrosses(loops.get(j)) < 0) {
239           log.info("Loop " + i + " crosses loop " + j);
240           return false;
241         }
242       }
243     }
244     return true;
245   }
246 
numLoops()247   public int numLoops() {
248     return loops.size();
249   }
250 
loop(int k)251   public S2Loop loop(int k) {
252     return loops.get(k);
253   }
254 
255   /**
256    * Return the index of the parent of loop k, or -1 if it has no parent.
257    */
getParent(int k)258   public int getParent(int k) {
259     int depth = loop(k).depth();
260     if (depth == 0) {
261       return -1; // Optimization.
262     }
263     while (--k >= 0 && loop(k).depth() >= depth) {
264       // spin
265     }
266     return k;
267   }
268 
269   /**
270    * Return the index of the last loop that is contained within loop k. Returns
271    * num_loops() - 1 if k < 0. Note that loops are indexed according to a
272    * preorder traversal of the nesting hierarchy, so the immediate children of
273    * loop k can be found by iterating over loops (k+1)..getLastDescendant(k) and
274    * selecting those whose depth is equal to (loop(k).depth() + 1).
275    */
getLastDescendant(int k)276   public int getLastDescendant(int k) {
277     if (k < 0) {
278       return numLoops() - 1;
279     }
280     int depth = loop(k).depth();
281     while (++k < numLoops() && loop(k).depth() > depth) {
282       // spin
283     }
284     return k - 1;
285   }
286 
getAreaCentroid(boolean doCentroid)287   private S2AreaCentroid getAreaCentroid(boolean doCentroid) {
288     double areaSum = 0;
289     S2Point centroidSum = new S2Point(0, 0, 0);
290     for (int i = 0; i < numLoops(); ++i) {
291       S2AreaCentroid areaCentroid = doCentroid ? loop(i).getAreaAndCentroid() : null;
292       double loopArea = doCentroid ? areaCentroid.getArea() : loop(i).getArea();
293 
294       int loopSign = loop(i).sign();
295       areaSum += loopSign * loopArea;
296       if (doCentroid) {
297         S2Point currentCentroid = areaCentroid.getCentroid();
298         centroidSum =
299             new S2Point(centroidSum.x + loopSign * currentCentroid.x,
300                 centroidSum.y + loopSign * currentCentroid.y,
301                 centroidSum.z + loopSign * currentCentroid.z);
302       }
303     }
304 
305     return new S2AreaCentroid(areaSum, doCentroid ? centroidSum : null);
306   }
307 
308   /**
309    * Return the area of the polygon interior, i.e. the region on the left side
310    * of an odd number of loops (this value return value is between 0 and 4*Pi)
311    * and the true centroid of the polygon multiplied by the area of the polygon
312    * (see s2.h for details on centroids). Note that the centroid may not be
313    * contained by the polygon.
314    */
getAreaAndCentroid()315   public S2AreaCentroid getAreaAndCentroid() {
316     return getAreaCentroid(true);
317   }
318 
319   /**
320    * Return the area of the polygon interior, i.e. the region on the left side
321    * of an odd number of loops. The return value is between 0 and 4*Pi.
322    */
getArea()323   public double getArea() {
324     return getAreaCentroid(false).getArea();
325   }
326 
327   /**
328    * Return the true centroid of the polygon multiplied by the area of the
329    * polygon (see s2.h for details on centroids). Note that the centroid may not
330    * be contained by the polygon.
331    */
getCentroid()332   public S2Point getCentroid() {
333     return getAreaCentroid(true).getCentroid();
334   }
335 
336   /**
337    * Returns the shortest distance from a point P to this polygon, given as the
338    * angle formed between P, the origin and the nearest point on the polygon to
339    * P. This angle in radians is equivalent to the arclength along the unit
340    * sphere.
341    *
342    * If the point is contained inside the polygon, the distance returned is 0.
343    */
getDistance(S2Point p)344   public S1Angle getDistance(S2Point p) {
345     if (contains(p)) {
346       return S1Angle.radians(0);
347     }
348 
349     // The furthest point from p on the sphere is its antipode, which is an
350     // angle of PI radians. This is an upper bound on the angle.
351     S1Angle minDistance = S1Angle.radians(Math.PI);
352     for (int i = 0; i < numLoops(); i++) {
353       minDistance = S1Angle.min(minDistance, loop(i).getDistance(p));
354     }
355 
356     return minDistance;
357   }
358 
359 
360   /**
361    * Return true if this polygon contains the given other polygon, i.e. if
362    * polygon A contains all points contained by polygon B.
363    */
contains(S2Polygon b)364   public boolean contains(S2Polygon b) {
365     // If both polygons have one loop, use the more efficient S2Loop method.
366     // Note that S2Loop.contains does its own bounding rectangle check.
367     if (numLoops() == 1 && b.numLoops() == 1) {
368       return loop(0).contains(b.loop(0));
369     }
370 
371     // Otherwise if neither polygon has holes, we can still use the more
372     // efficient S2Loop::Contains method (rather than ContainsOrCrosses),
373     // but it's worthwhile to do our own bounds check first.
374     if (!bound.contains(b.getRectBound())) {
375       // If the union of the bounding boxes spans the full longitude range,
376       // it is still possible that polygon A contains B. (This is only
377       // possible if at least one polygon has multiple shells.)
378       if (!bound.lng().union(b.getRectBound().lng()).isFull()) {
379         return false;
380       }
381     }
382     if (!hasHoles && !b.hasHoles) {
383       for (int j = 0; j < b.numLoops(); ++j) {
384         if (!anyLoopContains(b.loop(j))) {
385           return false;
386         }
387       }
388       return true;
389     }
390 
391     // This could be implemented more efficiently for polygons with lots of
392     // holes by keeping a copy of the LoopMap computed during initialization.
393     // However, in practice most polygons are one loop, and multiloop polygons
394     // tend to consist of many shells rather than holes. In any case, the real
395     // way to get more efficiency is to implement a sub-quadratic algorithm
396     // such as building a trapezoidal map.
397 
398     // Every shell of B must be contained by an odd number of loops of A,
399     // and every hole of A must be contained by an even number of loops of B.
400     return containsAllShells(b) && b.excludesAllHoles(this);
401   }
402 
403   /**
404    * Return true if this polygon intersects the given other polygon, i.e. if
405    * there is a point that is contained by both polygons.
406    */
intersects(S2Polygon b)407   public boolean intersects(S2Polygon b) {
408     // A.intersects(B) if and only if !complement(A).contains(B). However,
409     // implementing a complement() operation is trickier than it sounds,
410     // and in any case it's more efficient to test for intersection directly.
411 
412     // If both polygons have one loop, use the more efficient S2Loop method.
413     // Note that S2Loop.intersects does its own bounding rectangle check.
414     if (numLoops() == 1 && b.numLoops() == 1) {
415       return loop(0).intersects(b.loop(0));
416     }
417 
418     // Otherwise if neither polygon has holes, we can still use the more
419     // efficient S2Loop.intersects method. The polygons intersect if and
420     // only if some pair of loop regions intersect.
421     if (!bound.intersects(b.getRectBound())) {
422       return false;
423     }
424     if (!hasHoles && !b.hasHoles) {
425       for (int i = 0; i < numLoops(); ++i) {
426         for (int j = 0; j < b.numLoops(); ++j) {
427           if (loop(i).intersects(b.loop(j))) {
428             return true;
429           }
430         }
431       }
432       return false;
433     }
434 
435     // Otherwise if any shell of B is contained by an odd number of loops of A,
436     // or any shell of A is contained by an odd number of loops of B, there is
437     // an intersection.
438     return intersectsAnyShell(b) || b.intersectsAnyShell(this);
439   }
440 
441   /**
442    *  Indexing structure to efficiently clipEdge() of a polygon. This is an
443    * abstract class because we need to use if for both polygons (for
444    * initToIntersection() and friends) and for sets of lists of points (for
445    * initToSimplified()).
446    *
447    *  Usage -- in your subclass, create an array of vertex counts for each loop
448    * in the loop sequence and pass it to this constructor. Overwrite
449    * edgeFromTo(), calling decodeIndex() and use the resulting two indices to
450    * access your accessing vertices.
451    */
452   private abstract static class S2LoopSequenceIndex extends S2EdgeIndex {
453     /** Map from the unidimensional edge index to the loop this edge belongs to. */
454     private final int[] indexToLoop;
455 
456     /**
457      * Reverse of indexToLoop: maps a loop index to the unidimensional index
458      * of the first edge in the loop.
459      */
460     private final int[] loopToFirstIndex;
461 
462     /**
463      * Must be called by each subclass with the array of vertices per loop. The
464      * length of the array is the number of loops, and the <code>i</code>
465      * <sup>th</sup> loop's vertex count is in the <code>i</code>
466      * <sup>th</sup> index of the array.
467      */
S2LoopSequenceIndex(int[] numVertices)468     public S2LoopSequenceIndex(int[] numVertices) {
469       int totalEdges = 0;
470       for (int edges : numVertices) {
471         totalEdges += edges;
472       }
473       indexToLoop = new int[totalEdges];
474       loopToFirstIndex = new int[numVertices.length];
475 
476       totalEdges = 0;
477       for (int j = 0; j < numVertices.length; j++) {
478         loopToFirstIndex[j] = totalEdges;
479         for (int i = 0; i < numVertices[j]; i++) {
480           indexToLoop[totalEdges] = j;
481           totalEdges++;
482         }
483       }
484     }
485 
decodeIndex(int index)486     public final LoopVertexIndexPair decodeIndex(int index) {
487       int loopIndex = indexToLoop[index];
488       int vertexInLoop = index - loopToFirstIndex[loopIndex];
489       return new LoopVertexIndexPair(loopIndex, vertexInLoop);
490     }
491 
492     // It is faster to return both vertices at once. It makes a difference
493     // for small polygons.
edgeFromTo(int index)494     public abstract S2Edge edgeFromTo(int index);
495 
496     @Override
getNumEdges()497     public final int getNumEdges() {
498       return indexToLoop.length;
499     }
500 
501     @Override
edgeFrom(int index)502     public S2Point edgeFrom(int index) {
503       S2Edge fromTo = edgeFromTo(index);
504       S2Point from = fromTo.getStart();
505       return from;
506     }
507 
508     @Override
edgeTo(int index)509     protected S2Point edgeTo(int index) {
510       S2Edge fromTo = edgeFromTo(index);
511       S2Point to = fromTo.getEnd();
512       return to;
513     }
514   }
515 
516   // Indexing structure for an S2Polygon.
517   private static final class S2PolygonIndex extends S2LoopSequenceIndex {
518     private final S2Polygon poly;
519     private final boolean reverse;
520 
getVertices(S2Polygon poly)521     private static int[] getVertices(S2Polygon poly) {
522       int[] vertices = new int[poly.numLoops()];
523       for (int i = 0; i < vertices.length; i++) {
524         vertices[i] = poly.loop(i).numVertices();
525       }
526       return vertices;
527     }
528 
S2PolygonIndex(S2Polygon poly, boolean reverse)529     public S2PolygonIndex(S2Polygon poly, boolean reverse) {
530       super(getVertices(poly));
531       this.poly = poly;
532       this.reverse = reverse;
533     }
534 
535     @Override
edgeFromTo(int index)536     public S2Edge edgeFromTo(int index) {
537       LoopVertexIndexPair indices = decodeIndex(index);
538       int loopIndex = indices.getLoopIndex();
539       int vertexInLoop = indices.getVertexIndex();
540       S2Loop loop = poly.loop(loopIndex);
541       int fromIndex;
542       int toIndex;
543       if (loop.isHole() ^ reverse) {
544         fromIndex = loop.numVertices() - 1 - vertexInLoop;
545         toIndex = 2 * loop.numVertices() - 2 - vertexInLoop;
546       } else {
547         fromIndex = vertexInLoop;
548         toIndex = vertexInLoop + 1;
549       }
550       S2Point from = loop.vertex(fromIndex);
551       S2Point to = loop.vertex(toIndex);
552       return new S2Edge(from, to);
553     }
554   }
555 
addIntersection(S2Point a0, S2Point a1, S2Point b0, S2Point b1, boolean addSharedEdges, int crossing, List<ParametrizedS2Point> intersections)556   private static void addIntersection(S2Point a0,
557       S2Point a1,
558       S2Point b0,
559       S2Point b1,
560       boolean addSharedEdges,
561       int crossing,
562       List<ParametrizedS2Point> intersections) {
563     if (crossing > 0) {
564       // There is a proper edge crossing.
565       S2Point x = S2EdgeUtil.getIntersection(a0, a1, b0, b1);
566       double t = S2EdgeUtil.getDistanceFraction(x, a0, a1);
567       intersections.add(new ParametrizedS2Point(t, x));
568     } else if (S2EdgeUtil.vertexCrossing(a0, a1, b0, b1)) {
569       // There is a crossing at one of the vertices. The basic rule is simple:
570       // if a0 equals one of the "b" vertices, the crossing occurs at t=0;
571       // otherwise, it occurs at t=1.
572       //
573       // This has the effect that when two symmetric edges are encountered (an
574       // edge an its reverse), neither one is included in the output. When two
575       // duplicate edges are encountered, both are included in the output. The
576       // "addSharedEdges" flag allows one of these two copies to be removed by
577       // changing its intersection parameter from 0 to 1.
578       double t = (a0 == b0 || a0 == b1) ? 0 : 1;
579       if (!addSharedEdges && a1 == b1) {
580         t = 1;
581       }
582       intersections.add(new ParametrizedS2Point(t, t == 0 ? a0 : a1));
583     }
584   }
585 
586   /**
587    * Find all points where the polygon B intersects the edge (a0,a1), and add
588    * the corresponding parameter values (in the range [0,1]) to "intersections".
589    */
clipEdge(final S2Point a0, final S2Point a1, S2LoopSequenceIndex bIndex, boolean addSharedEdges, List<ParametrizedS2Point> intersections)590   private static void clipEdge(final S2Point a0, final S2Point a1, S2LoopSequenceIndex bIndex,
591       boolean addSharedEdges, List<ParametrizedS2Point> intersections) {
592     S2LoopSequenceIndex.DataEdgeIterator it = new S2LoopSequenceIndex.DataEdgeIterator(bIndex);
593     it.getCandidates(a0, a1);
594     S2EdgeUtil.EdgeCrosser crosser = new S2EdgeUtil.EdgeCrosser(a0, a1, a0);
595     S2Point from = null;
596     S2Point to = null;
597     for (; it.hasNext(); it.next()) {
598       S2Point previousTo = to;
599       S2Edge fromTo = bIndex.edgeFromTo(it.index());
600       from = fromTo.getStart();
601       to = fromTo.getEnd();
602       if (previousTo != from) {
603         crosser.restartAt(from);
604       }
605       int crossing = crosser.robustCrossing(to);
606       if (crossing < 0) {
607         continue;
608       }
609       addIntersection(a0, a1, from, to, addSharedEdges, crossing, intersections);
610     }
611   }
612 
613   /**
614    * Clip the boundary of A to the interior of B, and add the resulting edges to
615    * "builder". Shells are directed CCW and holes are directed clockwise, unless
616    * "reverseA" or "reverseB" is true in which case these directions in the
617    * corresponding polygon are reversed. If "invertB" is true, the boundary of A
618    * is clipped to the exterior rather than the interior of B. If
619    * "adSharedEdges" is true, then the output will include any edges that are
620    * shared between A and B (both edges must be in the same direction after any
621    * edge reversals are taken into account).
622    */
clipBoundary(final S2Polygon a, boolean reverseA, final S2Polygon b, boolean reverseB, boolean invertB, boolean addSharedEdges, S2PolygonBuilder builder)623   private static void clipBoundary(final S2Polygon a,
624       boolean reverseA,
625       final S2Polygon b,
626       boolean reverseB,
627       boolean invertB,
628       boolean addSharedEdges,
629       S2PolygonBuilder builder) {
630     S2PolygonIndex bIndex = new S2PolygonIndex(b, reverseB);
631     bIndex.predictAdditionalCalls(a.getNumVertices());
632 
633     List<ParametrizedS2Point> intersections = Lists.newArrayList();
634     for (S2Loop aLoop : a.loops) {
635       int n = aLoop.numVertices();
636       int dir = (aLoop.isHole() ^ reverseA) ? -1 : 1;
637       boolean inside = b.contains(aLoop.vertex(0)) ^ invertB;
638       for (int j = (dir > 0) ? 0 : n; n > 0; --n, j += dir) {
639         S2Point a0 = aLoop.vertex(j);
640         S2Point a1 = aLoop.vertex(j + dir);
641         intersections.clear();
642         clipEdge(a0, a1, bIndex, addSharedEdges, intersections);
643 
644         if (inside) {
645           intersections.add(new ParametrizedS2Point(0.0, a0));
646         }
647         inside = ((intersections.size() & 0x1) == 0x1);
648         // assert ((b.contains(a1) ^ invertB) == inside);
649         if (inside) {
650           intersections.add(new ParametrizedS2Point(1.0, a1));
651         }
652 
653         // Remove duplicates and produce a list of unique intersections.
654         Collections.sort(intersections);
655         for (int size = intersections.size(), i = 1; i < size; i += 2) {
656           builder.addEdge(intersections.get(i - 1).getPoint(), intersections.get(i).getPoint());
657         }
658       }
659     }
660   }
661 
662   /**
663    * Returns total number of vertices in all loops.
664    */
getNumVertices()665   public int getNumVertices() {
666     return this.numVertices;
667   }
668 
669   /**
670    * Initialize this polygon to the intersection, union, or difference (A - B)
671    * of the given two polygons. The "vertexMergeRadius" determines how close two
672    * vertices must be to be merged together and how close a vertex must be to an
673    * edge in order to be spliced into it (see S2PolygonBuilder for details). By
674    * default, the merge radius is just large enough to compensate for errors
675    * that occur when computing intersection points between edges
676    * (S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE).
677    *
678    *  If you are going to convert the resulting polygon to a lower-precision
679    * format, it is necessary to increase the merge radius in order to get a
680    * valid result after rounding (i.e. no duplicate vertices, etc). For example,
681    * if you are going to convert them to geostore.PolygonProto format, then
682    * S1Angle.e7(1) is a good value for "vertex_merge_radius".
683    */
initToIntersection(final S2Polygon a, final S2Polygon b)684   public void initToIntersection(final S2Polygon a, final S2Polygon b) {
685     initToIntersectionSloppy(a, b, S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE);
686   }
687 
initToIntersectionSloppy( final S2Polygon a, final S2Polygon b, S1Angle vertexMergeRadius)688   public void initToIntersectionSloppy(
689       final S2Polygon a, final S2Polygon b, S1Angle vertexMergeRadius) {
690     Preconditions.checkState(numLoops() == 0);
691     if (!a.bound.intersects(b.bound)) {
692       return;
693     }
694 
695     // We want the boundary of A clipped to the interior of B,
696     // plus the boundary of B clipped to the interior of A,
697     // plus one copy of any directed edges that are in both boundaries.
698 
699     S2PolygonBuilder.Options options = S2PolygonBuilder.Options.DIRECTED_XOR;
700     options.setMergeDistance(vertexMergeRadius);
701     S2PolygonBuilder builder = new S2PolygonBuilder(options);
702     clipBoundary(a, false, b, false, false, true, builder);
703     clipBoundary(b, false, a, false, false, false, builder);
704     if (!builder.assemblePolygon(this, null)) {
705       // TODO (andriy): do something more meaningful here.
706       log.severe("Bad directed edges");
707     }
708   }
709 
initToUnion(final S2Polygon a, final S2Polygon b)710   public void initToUnion(final S2Polygon a, final S2Polygon b) {
711     initToUnionSloppy(a, b, S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE);
712   }
713 
initToUnionSloppy(final S2Polygon a, final S2Polygon b, S1Angle vertexMergeRadius)714   public void initToUnionSloppy(final S2Polygon a, final S2Polygon b, S1Angle vertexMergeRadius) {
715     Preconditions.checkState(numLoops() == 0);
716 
717     // We want the boundary of A clipped to the exterior of B,
718     // plus the boundary of B clipped to the exterior of A,
719     // plus one copy of any directed edges that are in both boundaries.
720 
721     S2PolygonBuilder.Options options = S2PolygonBuilder.Options.DIRECTED_XOR;
722     options.setMergeDistance(vertexMergeRadius);
723     S2PolygonBuilder builder = new S2PolygonBuilder(options);
724     clipBoundary(a, false, b, false, true, true, builder);
725     clipBoundary(b, false, a, false, true, false, builder);
726     if (!builder.assemblePolygon(this, null)) {
727       // TODO(andriy): do something more meaningful here.
728       log.severe("Bad directed edges");
729     }
730   }
731 
732   /**
733    * Return a polygon which is the union of the given polygons. Note: clears the
734    * List!
735    */
destructiveUnion(List<S2Polygon> polygons)736   public static S2Polygon destructiveUnion(List<S2Polygon> polygons) {
737     return destructiveUnionSloppy(polygons, S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE);
738   }
739 
740   /**
741    * Return a polygon which is the union of the given polygons; combines
742    * vertices that form edges that are almost identical, as defined by
743    * vertexMergeRadius. Note: clears the List!
744    */
destructiveUnionSloppy( List<S2Polygon> polygons, S1Angle vertexMergeRadius)745   public static S2Polygon destructiveUnionSloppy(
746       List<S2Polygon> polygons, S1Angle vertexMergeRadius) {
747     // Effectively create a priority queue of polygons in order of number of
748     // vertices. Repeatedly union the two smallest polygons and add the result
749     // to the queue until we have a single polygon to return.
750 
751     // map: # of vertices -> polygon
752     TreeMultimap<Integer, S2Polygon> queue = TreeMultimap.create();
753 
754     for (S2Polygon polygon : polygons) {
755       queue.put(polygon.getNumVertices(), polygon);
756     }
757     polygons.clear();
758 
759     Set<Map.Entry<Integer, S2Polygon>> queueSet = queue.entries();
760     while (queueSet.size() > 1) {
761       // Pop two simplest polygons from queue.
762       queueSet = queue.entries();
763       Iterator<Map.Entry<Integer, S2Polygon>> smallestIter = queueSet.iterator();
764 
765       Map.Entry<Integer, S2Polygon> smallest = smallestIter.next();
766       int aSize = smallest.getKey().intValue();
767       S2Polygon aPolygon = smallest.getValue();
768       smallestIter.remove();
769 
770       smallest = smallestIter.next();
771       int bSize = smallest.getKey().intValue();
772       S2Polygon bPolygon = smallest.getValue();
773       smallestIter.remove();
774 
775       // Union and add result back to queue.
776       S2Polygon unionPolygon = new S2Polygon();
777       unionPolygon.initToUnionSloppy(aPolygon, bPolygon, vertexMergeRadius);
778       int unionSize = aSize + bSize;
779       queue.put(unionSize, unionPolygon);
780       // We assume that the number of vertices in the union polygon is the
781       // sum of the number of vertices in the original polygons, which is not
782       // always true, but will almost always be a decent approximation, and
783       // faster than recomputing.
784     }
785 
786     if (queue.isEmpty()) {
787       return new S2Polygon();
788     } else {
789       return queue.get(queue.asMap().firstKey()).first();
790     }
791   }
792 
isNormalized()793   public boolean isNormalized() {
794     Multiset<S2Point> vertices = HashMultiset.<S2Point>create();
795     S2Loop lastParent = null;
796     for (int i = 0; i < numLoops(); ++i) {
797       S2Loop child = loop(i);
798       if (child.depth() == 0) {
799         continue;
800       }
801       S2Loop parent = loop(getParent(i));
802       if (parent != lastParent) {
803         vertices.clear();
804         for (int j = 0; j < parent.numVertices(); ++j) {
805           vertices.add(parent.vertex(j));
806         }
807         lastParent = parent;
808       }
809       int count = 0;
810       for (int j = 0; j < child.numVertices(); ++j) {
811         if (vertices.count(child.vertex(j)) > 0) {
812           ++count;
813         }
814       }
815       if (count > 1) {
816         return false;
817       }
818     }
819     return true;
820   }
821 
822   /**
823    * Return true if two polygons have the same boundary except for vertex
824    * perturbations. Both polygons must have loops with the same cyclic vertex
825    * order and the same nesting hierarchy, but the vertex locations are allowed
826    * to differ by up to "max_error". Note: This method mostly useful only for
827    * testing purposes.
828    */
boundaryApproxEquals(S2Polygon b, double maxError)829   boolean boundaryApproxEquals(S2Polygon b, double maxError) {
830     if (numLoops() != b.numLoops()) {
831       log.severe(
832           "!= loops: " + Integer.toString(numLoops()) + " vs. " + Integer.toString(b.numLoops()));
833       return false;
834     }
835 
836     // For now, we assume that there is at most one candidate match for each
837     // loop. (So far this method is just used for testing.)
838     for (int i = 0; i < numLoops(); ++i) {
839       S2Loop aLoop = loop(i);
840       boolean success = false;
841       for (int j = 0; j < numLoops(); ++j) {
842         S2Loop bLoop = b.loop(j);
843         if (bLoop.depth() == aLoop.depth() && bLoop.boundaryApproxEquals(aLoop, maxError)) {
844           success = true;
845           break;
846         }
847       }
848       if (!success) {
849         return false;
850       }
851     }
852     return true;
853   }
854 
855   // S2Region interface (see S2Region.java for details):
856 
857   /** Return a bounding spherical cap. */
858   @Override
getCapBound()859   public S2Cap getCapBound() {
860     return bound.getCapBound();
861   }
862 
863 
864   /** Return a bounding latitude-longitude rectangle. */
865   @Override
getRectBound()866   public S2LatLngRect getRectBound() {
867     return bound;
868   }
869 
870   /**
871    * If this method returns true, the region completely contains the given cell.
872    * Otherwise, either the region does not contain the cell or the containment
873    * relationship could not be determined.
874    */
875   @Override
contains(S2Cell cell)876   public boolean contains(S2Cell cell) {
877     if (numLoops() == 1) {
878       return loop(0).contains(cell);
879     }
880     S2LatLngRect cellBound = cell.getRectBound();
881     if (!bound.contains(cellBound)) {
882       return false;
883     }
884 
885     S2Loop cellLoop = new S2Loop(cell, cellBound);
886     S2Polygon cellPoly = new S2Polygon(cellLoop);
887     return contains(cellPoly);
888   }
889 
890   /**
891    * If this method returns false, the region does not intersect the given cell.
892    * Otherwise, either region intersects the cell, or the intersection
893    * relationship could not be determined.
894    */
895   @Override
mayIntersect(S2Cell cell)896   public boolean mayIntersect(S2Cell cell) {
897     if (numLoops() == 1) {
898       return loop(0).mayIntersect(cell);
899     }
900     S2LatLngRect cellBound = cell.getRectBound();
901     if (!bound.intersects(cellBound)) {
902       return false;
903     }
904 
905     S2Loop cellLoop = new S2Loop(cell, cellBound);
906     S2Polygon cellPoly = new S2Polygon(cellLoop);
907     return intersects(cellPoly);
908   }
909 
910   /**
911    * The point 'p' does not need to be normalized.
912    */
contains(S2Point p)913   public boolean contains(S2Point p) {
914     if (numLoops() == 1) {
915       return loop(0).contains(p); // Optimization.
916     }
917     if (!bound.contains(p)) {
918       return false;
919     }
920     boolean inside = false;
921     for (int i = 0; i < numLoops(); ++i) {
922       inside ^= loop(i).contains(p);
923       if (inside && !hasHoles) {
924         break; // Shells are disjoint.
925       }
926     }
927     return inside;
928   }
929 
930   // For each map entry, sorts the value list.
sortValueLoops(Map<S2Loop, List<S2Loop>> loopMap)931   private static void sortValueLoops(Map<S2Loop, List<S2Loop>> loopMap) {
932     for (S2Loop key : loopMap.keySet()) {
933       Collections.sort(loopMap.get(key));
934     }
935   }
936 
insertLoop(S2Loop newLoop, S2Loop parent, Map<S2Loop, List<S2Loop>> loopMap)937   private static void insertLoop(S2Loop newLoop, S2Loop parent, Map<S2Loop, List<S2Loop>> loopMap) {
938     List<S2Loop> children = loopMap.get(parent);
939 
940     if (children == null) {
941       children = Lists.newArrayList();
942       loopMap.put(parent, children);
943     }
944 
945     for (S2Loop child : children) {
946       if (child.containsNested(newLoop)) {
947         insertLoop(newLoop, child, loopMap);
948         return;
949       }
950     }
951 
952     // No loop may contain the complement of another loop. (Handling this case
953     // is significantly more complicated.)
954     // assert (parent == null || !newLoop.containsNested(parent));
955 
956     // Some of the children of the parent loop may now be children of
957     // the new loop.
958     List<S2Loop> newChildren = loopMap.get(newLoop);
959     for (int i = 0; i < children.size();) {
960       S2Loop child = children.get(i);
961       if (newLoop.containsNested(child)) {
962         if (newChildren == null) {
963           newChildren = Lists.newArrayList();
964           loopMap.put(newLoop, newChildren);
965         }
966         newChildren.add(child);
967         children.remove(i);
968       } else {
969         ++i;
970       }
971     }
972     children.add(newLoop);
973   }
974 
initLoop(S2Loop loop, int depth, Map<S2Loop, List<S2Loop>> loopMap)975   private void initLoop(S2Loop loop, int depth, Map<S2Loop, List<S2Loop>> loopMap) {
976     if (loop != null) {
977       loop.setDepth(depth);
978       loops.add(loop);
979     }
980     List<S2Loop> children = loopMap.get(loop);
981     if (children != null) {
982       for (S2Loop child : children) {
983         initLoop(child, depth + 1, loopMap);
984       }
985     }
986   }
987 
containsOrCrosses(S2Loop b)988   private int containsOrCrosses(S2Loop b) {
989     boolean inside = false;
990     for (int i = 0; i < numLoops(); ++i) {
991       int result = loop(i).containsOrCrosses(b);
992       if (result < 0) {
993         return -1; // The loop boundaries intersect.
994       }
995       if (result > 0) {
996         inside ^= true;
997       }
998     }
999     return inside ? 1 : 0; // True if loop B is contained by the polygon.
1000   }
1001 
1002   /** Return true if any loop contains the given loop. */
anyLoopContains(S2Loop b)1003   private boolean anyLoopContains(S2Loop b) {
1004     for (int i = 0; i < numLoops(); ++i) {
1005       if (loop(i).contains(b)) {
1006         return true;
1007       }
1008     }
1009     return false;
1010   }
1011 
1012   /** Return true if this polygon (A) contains all the shells of B. */
containsAllShells(S2Polygon b)1013   private boolean containsAllShells(S2Polygon b) {
1014     for (int j = 0; j < b.numLoops(); ++j) {
1015       if (b.loop(j).sign() < 0) {
1016         continue;
1017       }
1018       if (containsOrCrosses(b.loop(j)) <= 0) {
1019         // Shell of B is not contained by A, or the boundaries intersect.
1020         return false;
1021       }
1022     }
1023     return true;
1024   }
1025 
1026   /**
1027    * Return true if this polygon (A) excludes (i.e. does not intersect) all
1028    * holes of B.
1029    */
excludesAllHoles(S2Polygon b)1030   private boolean excludesAllHoles(S2Polygon b) {
1031     for (int j = 0; j < b.numLoops(); ++j) {
1032       if (b.loop(j).sign() > 0) {
1033         continue;
1034       }
1035       if (containsOrCrosses(b.loop(j)) != 0) {
1036         // Hole of B is contained by A, or the boundaries intersect.
1037         return false;
1038       }
1039     }
1040     return true;
1041   }
1042 
1043   /** Return true if this polygon (A) intersects any shell of B. */
intersectsAnyShell(S2Polygon b)1044   private boolean intersectsAnyShell(S2Polygon b) {
1045     for (int j = 0; j < b.numLoops(); ++j) {
1046       if (b.loop(j).sign() < 0) {
1047         continue;
1048       }
1049       if (containsOrCrosses(b.loop(j)) != 0) {
1050         // Shell of B is contained by A, or the boundaries intersect.
1051         return true;
1052       }
1053     }
1054     return false;
1055   }
1056 
1057   /**
1058    * A human readable representation of the polygon
1059    */
1060   @Override
toString()1061   public String toString() {
1062     StringBuilder sb = new StringBuilder();
1063     sb.append("Polygon: (").append(numLoops()).append(") loops:\n");
1064     for (int i = 0; i < numLoops(); ++i) {
1065       S2Loop s2Loop = loop(i);
1066       sb.append("loop <\n");
1067       for (int v = 0; v < s2Loop.numVertices(); ++v) {
1068         S2Point s2Point = s2Loop.vertex(v);
1069         sb.append(s2Point.toDegreesString());
1070         sb.append("\n"); // end of vertex
1071       }
1072       sb.append(">\n"); // end of loop
1073     }
1074     return sb.toString();
1075   }
1076 
1077   private static final class UndirectedEdge {
1078     // Note: An UndirectedEdge and an S2Edge can never be considered equal (in
1079     // terms of the equals() method) and hence they re not be related types.
1080     // If you need to convert between the types then separate conversion
1081     // methods should be introduced.
1082 
1083     private final S2Point a;
1084     private final S2Point b;
1085 
UndirectedEdge(S2Point start, S2Point end)1086     public UndirectedEdge(S2Point start, S2Point end) {
1087       this.a = start;
1088       this.b = end;
1089     }
1090 
getStart()1091     public S2Point getStart() {
1092       return a;
1093     }
1094 
getEnd()1095     public S2Point getEnd() {
1096       return b;
1097     }
1098 
1099     @Override
toString()1100     public String toString() {
1101       return String.format("Edge: (%s <-> %s)\n   or [%s <-> %s]",
1102           a.toDegreesString(), b.toDegreesString(), a, b);
1103     }
1104 
1105     @Override
equals(Object o)1106     public boolean equals(Object o) {
1107       if (o == null || !(o instanceof UndirectedEdge)) {
1108         return false;
1109       }
1110       UndirectedEdge other = (UndirectedEdge) o;
1111       return ((getStart().equals(other.getStart()) && getEnd().equals(other.getEnd()))
1112           || (getStart().equals(other.getEnd()) && getEnd().equals(other.getStart())));
1113     }
1114 
1115     @Override
hashCode()1116     public int hashCode() {
1117       return getStart().hashCode() + getEnd().hashCode();
1118     }
1119   }
1120 
1121   private static final class LoopVertexIndexPair {
1122     private final int loopIndex;
1123     private final int vertexIndex;
1124 
LoopVertexIndexPair(int loopIndex, int vertexIndex)1125     public LoopVertexIndexPair(int loopIndex, int vertexIndex) {
1126       this.loopIndex = loopIndex;
1127       this.vertexIndex = vertexIndex;
1128     }
1129 
getLoopIndex()1130     public int getLoopIndex() {
1131       return loopIndex;
1132     }
1133 
getVertexIndex()1134     public int getVertexIndex() {
1135       return vertexIndex;
1136     }
1137   }
1138 
1139   /**
1140    * An S2Point that also has a parameter associated with it, which corresponds
1141    * to a time-like order on the points.
1142    */
1143   private static final class ParametrizedS2Point implements Comparable<ParametrizedS2Point> {
1144     private final double time;
1145     private final S2Point point;
1146 
ParametrizedS2Point(double time, S2Point point)1147     public ParametrizedS2Point(double time, S2Point point) {
1148       this.time = time;
1149       this.point = point;
1150     }
1151 
getTime()1152     public double getTime() {
1153       return time;
1154     }
1155 
getPoint()1156     public S2Point getPoint() {
1157       return point;
1158     }
1159 
1160     @Override
compareTo(ParametrizedS2Point o)1161     public int compareTo(ParametrizedS2Point o) {
1162       int compareTime = Double.compare(time, o.time);
1163       if (compareTime != 0) {
1164         return compareTime;
1165       }
1166       return point.compareTo(o.point);
1167     }
1168   }
1169 }
1170