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