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