1 /* 2 * Copyright 2005 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.common.geometry; 17 18 import com.google.common.collect.Lists; 19 20 import java.util.ArrayList; 21 import java.util.Collections; 22 import java.util.Iterator; 23 import java.util.List; 24 25 /** 26 * An S2CellUnion is a region consisting of cells of various sizes. Typically a 27 * cell union is used to approximate some other shape. There is a tradeoff 28 * between the accuracy of the approximation and how many cells are used. Unlike 29 * polygons, cells have a fixed hierarchical structure. This makes them more 30 * suitable for optimizations based on preprocessing. 31 * 32 */ 33 public strictfp class S2CellUnion implements S2Region, Iterable<S2CellId> { 34 35 /** The CellIds that form the Union */ 36 private ArrayList<S2CellId> cellIds = new ArrayList<S2CellId>(); 37 S2CellUnion()38 public S2CellUnion() { 39 } 40 initFromCellIds(ArrayList<S2CellId> cellIds)41 public void initFromCellIds(ArrayList<S2CellId> cellIds) { 42 initRawCellIds(cellIds); 43 normalize(); 44 } 45 46 /** 47 * Populates a cell union with the given S2CellIds or 64-bit cells ids, and 48 * then calls Normalize(). The InitSwap() version takes ownership of the 49 * vector data without copying and clears the given vector. These methods may 50 * be called multiple times. 51 */ initFromIds(ArrayList<Long> cellIds)52 public void initFromIds(ArrayList<Long> cellIds) { 53 initRawIds(cellIds); 54 normalize(); 55 } 56 initSwap(ArrayList<S2CellId> cellIds)57 public void initSwap(ArrayList<S2CellId> cellIds) { 58 initRawSwap(cellIds); 59 normalize(); 60 } 61 initRawCellIds(ArrayList<S2CellId> cellIds)62 public void initRawCellIds(ArrayList<S2CellId> cellIds) { 63 this.cellIds = cellIds; 64 } 65 initRawIds(ArrayList<Long> cellIds)66 public void initRawIds(ArrayList<Long> cellIds) { 67 int size = cellIds.size(); 68 this.cellIds = new ArrayList<S2CellId>(size); 69 for (Long id : cellIds) { 70 this.cellIds.add(new S2CellId(id)); 71 } 72 } 73 74 /** 75 * Like Init(), but does not call Normalize(). The cell union *must* be 76 * normalized before doing any calculations with it, so it is the caller's 77 * responsibility to make sure that the input is normalized. This method is 78 * useful when converting cell unions to another representation and back. 79 * These methods may be called multiple times. 80 */ initRawSwap(ArrayList<S2CellId> cellIds)81 public void initRawSwap(ArrayList<S2CellId> cellIds) { 82 this.cellIds = new ArrayList<S2CellId>(cellIds); 83 cellIds.clear(); 84 } 85 size()86 public int size() { 87 return cellIds.size(); 88 } 89 90 /** Convenience methods for accessing the individual cell ids. */ cellId(int i)91 public S2CellId cellId(int i) { 92 return cellIds.get(i); 93 } 94 95 /** Enable iteration over the union's cells. */ 96 @Override iterator()97 public Iterator<S2CellId> iterator() { 98 return cellIds.iterator(); 99 } 100 101 /** Direct access to the underlying vector for iteration . */ cellIds()102 public ArrayList<S2CellId> cellIds() { 103 return cellIds; 104 } 105 106 /** 107 * Replaces "output" with an expanded version of the cell union where any 108 * cells whose level is less than "min_level" or where (level - min_level) is 109 * not a multiple of "level_mod" are replaced by their children, until either 110 * both of these conditions are satisfied or the maximum level is reached. 111 * 112 * This method allows a covering generated by S2RegionCoverer using 113 * min_level() or level_mod() constraints to be stored as a normalized cell 114 * union (which allows various geometric computations to be done) and then 115 * converted back to the original list of cell ids that satisfies the desired 116 * constraints. 117 */ denormalize(int minLevel, int levelMod, ArrayList<S2CellId> output)118 public void denormalize(int minLevel, int levelMod, ArrayList<S2CellId> output) { 119 // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL); 120 // assert (levelMod >= 1 && levelMod <= 3); 121 122 output.clear(); 123 output.ensureCapacity(size()); 124 for (S2CellId id : this) { 125 int level = id.level(); 126 int newLevel = Math.max(minLevel, level); 127 if (levelMod > 1) { 128 // Round up so that (new_level - min_level) is a multiple of level_mod. 129 // (Note that S2CellId::kMaxLevel is a multiple of 1, 2, and 3.) 130 newLevel += (S2CellId.MAX_LEVEL - (newLevel - minLevel)) % levelMod; 131 newLevel = Math.min(S2CellId.MAX_LEVEL, newLevel); 132 } 133 if (newLevel == level) { 134 output.add(id); 135 } else { 136 S2CellId end = id.childEnd(newLevel); 137 for (id = id.childBegin(newLevel); !id.equals(end); id = id.next()) { 138 output.add(id); 139 } 140 } 141 } 142 } 143 144 /** 145 * If there are more than "excess" elements of the cell_ids() vector that are 146 * allocated but unused, reallocate the array to eliminate the excess space. 147 * This reduces memory usage when many cell unions need to be held in memory 148 * at once. 149 */ pack()150 public void pack() { 151 cellIds.trimToSize(); 152 } 153 154 155 /** 156 * Return true if the cell union contains the given cell id. Containment is 157 * defined with respect to regions, e.g. a cell contains its 4 children. This 158 * is a fast operation (logarithmic in the size of the cell union). 159 */ contains(S2CellId id)160 public boolean contains(S2CellId id) { 161 // This function requires that Normalize has been called first. 162 // 163 // This is an exact test. Each cell occupies a linear span of the S2 164 // space-filling curve, and the cell id is simply the position at the center 165 // of this span. The cell union ids are sorted in increasing order along 166 // the space-filling curve. So we simply find the pair of cell ids that 167 // surround the given cell id (using binary search). There is containment 168 // if and only if one of these two cell ids contains this cell. 169 170 int pos = Collections.binarySearch(cellIds, id); 171 if (pos < 0) { 172 pos = -pos - 1; 173 } 174 if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id)) { 175 return true; 176 } 177 return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id); 178 } 179 180 /** 181 * Return true if the cell union intersects the given cell id. This is a fast 182 * operation (logarithmic in the size of the cell union). 183 */ intersects(S2CellId id)184 public boolean intersects(S2CellId id) { 185 // This function requires that Normalize has been called first. 186 // This is an exact test; see the comments for Contains() above. 187 int pos = Collections.binarySearch(cellIds, id); 188 189 if (pos < 0) { 190 pos = -pos - 1; 191 } 192 193 194 if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id.rangeMax())) { 195 return true; 196 } 197 return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id.rangeMin()); 198 } 199 contains(S2CellUnion that)200 public boolean contains(S2CellUnion that) { 201 // TODO(kirilll?): A divide-and-conquer or alternating-skip-search approach 202 // may be significantly faster in both the average and worst case. 203 for (S2CellId id : that) { 204 if (!this.contains(id)) { 205 return false; 206 } 207 } 208 return true; 209 } 210 211 /** This is a fast operation (logarithmic in the size of the cell union). */ 212 @Override contains(S2Cell cell)213 public boolean contains(S2Cell cell) { 214 return contains(cell.id()); 215 } 216 217 /** 218 * Return true if this cell union contain/intersects the given other cell 219 * union. 220 */ intersects(S2CellUnion union)221 public boolean intersects(S2CellUnion union) { 222 // TODO(kirilll?): A divide-and-conquer or alternating-skip-search approach 223 // may be significantly faster in both the average and worst case. 224 for (S2CellId id : union) { 225 if (intersects(id)) { 226 return true; 227 } 228 } 229 return false; 230 } 231 getUnion(S2CellUnion x, S2CellUnion y)232 public void getUnion(S2CellUnion x, S2CellUnion y) { 233 // assert (x != this && y != this); 234 cellIds.clear(); 235 cellIds.ensureCapacity(x.size() + y.size()); 236 cellIds.addAll(x.cellIds); 237 cellIds.addAll(y.cellIds); 238 normalize(); 239 } 240 241 /** 242 * Specialized version of GetIntersection() that gets the intersection of a 243 * cell union with the given cell id. This can be useful for "splitting" a 244 * cell union into chunks. 245 */ getIntersection(S2CellUnion x, S2CellId id)246 public void getIntersection(S2CellUnion x, S2CellId id) { 247 // assert (x != this); 248 cellIds.clear(); 249 if (x.contains(id)) { 250 cellIds.add(id); 251 } else { 252 int pos = Collections.binarySearch(x.cellIds, id.rangeMin()); 253 254 if (pos < 0) { 255 pos = -pos - 1; 256 } 257 258 S2CellId idmax = id.rangeMax(); 259 int size = x.cellIds.size(); 260 while (pos < size && x.cellIds.get(pos).lessOrEquals(idmax)) { 261 cellIds.add(x.cellIds.get(pos++)); 262 } 263 } 264 } 265 266 /** 267 * Initialize this cell union to the union or intersection of the two given 268 * cell unions. Requires: x != this and y != this. 269 */ getIntersection(S2CellUnion x, S2CellUnion y)270 public void getIntersection(S2CellUnion x, S2CellUnion y) { 271 // assert (x != this && y != this); 272 273 // This is a fairly efficient calculation that uses binary search to skip 274 // over sections of both input vectors. It takes constant time if all the 275 // cells of "x" come before or after all the cells of "y" in S2CellId order. 276 277 cellIds.clear(); 278 279 int i = 0; 280 int j = 0; 281 282 while (i < x.cellIds.size() && j < y.cellIds.size()) { 283 S2CellId imin = x.cellId(i).rangeMin(); 284 S2CellId jmin = y.cellId(j).rangeMin(); 285 if (imin.greaterThan(jmin)) { 286 // Either j->contains(*i) or the two cells are disjoint. 287 if (x.cellId(i).lessOrEquals(y.cellId(j).rangeMax())) { 288 cellIds.add(x.cellId(i++)); 289 } else { 290 // Advance "j" to the first cell possibly contained by *i. 291 j = indexedBinarySearch(y.cellIds, imin, j + 1); 292 // The previous cell *(j-1) may now contain *i. 293 if (x.cellId(i).lessOrEquals(y.cellId(j - 1).rangeMax())) { 294 --j; 295 } 296 } 297 } else if (jmin.greaterThan(imin)) { 298 // Identical to the code above with "i" and "j" reversed. 299 if (y.cellId(j).lessOrEquals(x.cellId(i).rangeMax())) { 300 cellIds.add(y.cellId(j++)); 301 } else { 302 i = indexedBinarySearch(x.cellIds, jmin, i + 1); 303 if (y.cellId(j).lessOrEquals(x.cellId(i - 1).rangeMax())) { 304 --i; 305 } 306 } 307 } else { 308 // "i" and "j" have the same range_min(), so one contains the other. 309 if (x.cellId(i).lessThan(y.cellId(j))) { 310 cellIds.add(x.cellId(i++)); 311 } else { 312 cellIds.add(y.cellId(j++)); 313 } 314 } 315 } 316 // The output is generated in sorted order, and there should not be any 317 // cells that can be merged (provided that both inputs were normalized). 318 // assert (!normalize()); 319 } 320 321 /** 322 * Just as normal binary search, except that it allows specifying the starting 323 * value for the lower bound. 324 * 325 * @return The position of the searched element in the list (if found), or the 326 * position where the element could be inserted without violating the 327 * order. 328 */ indexedBinarySearch(List<S2CellId> l, S2CellId key, int low)329 private int indexedBinarySearch(List<S2CellId> l, S2CellId key, int low) { 330 int high = l.size() - 1; 331 332 while (low <= high) { 333 int mid = (low + high) >> 1; 334 S2CellId midVal = l.get(mid); 335 int cmp = midVal.compareTo(key); 336 337 if (cmp < 0) { 338 low = mid + 1; 339 } else if (cmp > 0) { 340 high = mid - 1; 341 } else { 342 return mid; // key found 343 } 344 } 345 return low; // key not found 346 } 347 348 /** 349 * Expands the cell union such that it contains all cells of the given level 350 * that are adjacent to any cell of the original union. Two cells are defined 351 * as adjacent if their boundaries have any points in common, i.e. most cells 352 * have 8 adjacent cells (not counting the cell itself). 353 * 354 * Note that the size of the output is exponential in "level". For example, 355 * if level == 20 and the input has a cell at level 10, there will be on the 356 * order of 4000 adjacent cells in the output. For most applications the 357 * Expand(min_fraction, min_distance) method below is easier to use. 358 */ expand(int level)359 public void expand(int level) { 360 ArrayList<S2CellId> output = new ArrayList<S2CellId>(); 361 long levelLsb = S2CellId.lowestOnBitForLevel(level); 362 int i = size() - 1; 363 do { 364 S2CellId id = cellId(i); 365 if (id.lowestOnBit() < levelLsb) { 366 id = id.parent(level); 367 // Optimization: skip over any cells contained by this one. This is 368 // especially important when very small regions are being expanded. 369 while (i > 0 && id.contains(cellId(i - 1))) { 370 --i; 371 } 372 } 373 output.add(id); 374 id.getAllNeighbors(level, output); 375 } while (--i >= 0); 376 initSwap(output); 377 } 378 379 /** 380 * Expand the cell union such that it contains all points whose distance to 381 * the cell union is at most minRadius, but do not use cells that are more 382 * than maxLevelDiff levels higher than the largest cell in the input. The 383 * second parameter controls the tradeoff between accuracy and output size 384 * when a large region is being expanded by a small amount (e.g. expanding 385 * Canada by 1km). 386 * 387 * For example, if maxLevelDiff == 4, the region will always be expanded by 388 * approximately 1/16 the width of its largest cell. Note that in the worst 389 * case, the number of cells in the output can be up to 4 * (1 + 2 ** 390 * maxLevelDiff) times larger than the number of cells in the input. 391 */ expand(S1Angle minRadius, int maxLevelDiff)392 public void expand(S1Angle minRadius, int maxLevelDiff) { 393 int minLevel = S2CellId.MAX_LEVEL; 394 for (S2CellId id : this) { 395 minLevel = Math.min(minLevel, id.level()); 396 } 397 // Find the maximum level such that all cells are at least "min_radius" 398 // wide. 399 int radiusLevel = S2Projections.MIN_WIDTH.getMaxLevel(minRadius.radians()); 400 if (radiusLevel == 0 && minRadius.radians() > S2Projections.MIN_WIDTH.getValue(0)) { 401 // The requested expansion is greater than the width of a face cell. 402 // The easiest way to handle this is to expand twice. 403 expand(0); 404 } 405 expand(Math.min(minLevel + maxLevelDiff, radiusLevel)); 406 } 407 408 @Override clone()409 public S2Region clone() { 410 S2CellUnion copy = new S2CellUnion(); 411 copy.initRawCellIds(Lists.newArrayList(cellIds)); 412 return copy; 413 } 414 415 @Override getCapBound()416 public S2Cap getCapBound() { 417 // Compute the approximate centroid of the region. This won't produce the 418 // bounding cap of minimal area, but it should be close enough. 419 if (cellIds.isEmpty()) { 420 return S2Cap.empty(); 421 } 422 S2Point centroid = new S2Point(0, 0, 0); 423 for (S2CellId id : this) { 424 double area = S2Cell.averageArea(id.level()); 425 centroid = S2Point.add(centroid, S2Point.mul(id.toPoint(), area)); 426 } 427 if (centroid.equals(new S2Point(0, 0, 0))) { 428 centroid = new S2Point(1, 0, 0); 429 } else { 430 centroid = S2Point.normalize(centroid); 431 } 432 433 // Use the centroid as the cap axis, and expand the cap angle so that it 434 // contains the bounding caps of all the individual cells. Note that it is 435 // *not* sufficient to just bound all the cell vertices because the bounding 436 // cap may be concave (i.e. cover more than one hemisphere). 437 S2Cap cap = S2Cap.fromAxisHeight(centroid, 0); 438 for (S2CellId id : this) { 439 cap = cap.addCap(new S2Cell(id).getCapBound()); 440 } 441 return cap; 442 } 443 444 @Override getRectBound()445 public S2LatLngRect getRectBound() { 446 S2LatLngRect bound = S2LatLngRect.empty(); 447 for (S2CellId id : this) { 448 bound = bound.union(new S2Cell(id).getRectBound()); 449 } 450 return bound; 451 } 452 453 454 /** This is a fast operation (logarithmic in the size of the cell union). */ 455 @Override mayIntersect(S2Cell cell)456 public boolean mayIntersect(S2Cell cell) { 457 return intersects(cell.id()); 458 } 459 460 /** 461 * The point 'p' does not need to be normalized. This is a fast operation 462 * (logarithmic in the size of the cell union). 463 */ contains(S2Point p)464 public boolean contains(S2Point p) { 465 return contains(S2CellId.fromPoint(p)); 466 467 } 468 469 /** 470 * The number of leaf cells covered by the union. 471 * This will be no more than 6*2^60 for the whole sphere. 472 * 473 * @return the number of leaf cells covered by the union 474 */ leafCellsCovered()475 public long leafCellsCovered() { 476 long numLeaves = 0; 477 for (S2CellId cellId : cellIds) { 478 int invertedLevel = S2CellId.MAX_LEVEL - cellId.level(); 479 numLeaves += (1L << (invertedLevel << 1)); 480 } 481 return numLeaves; 482 } 483 484 485 /** 486 * Approximate this cell union's area by summing the average area of 487 * each contained cell's average area, using {@link S2Cell#averageArea()}. 488 * This is equivalent to the number of leaves covered, multiplied by 489 * the average area of a leaf. 490 * Note that {@link S2Cell#averageArea()} does not take into account 491 * distortion of cell, and thus may be off by up to a factor of 1.7. 492 * NOTE: Since this is proportional to LeafCellsCovered(), it is 493 * always better to use the other function if all you care about is 494 * the relative average area between objects. 495 * 496 * @return the sum of the average area of each contained cell's average area 497 */ averageBasedArea()498 public double averageBasedArea() { 499 return S2Cell.averageArea(S2CellId.MAX_LEVEL) * leafCellsCovered(); 500 } 501 502 /** 503 * Calculates this cell union's area by summing the approximate area for each 504 * contained cell, using {@link S2Cell#approxArea()}. 505 * 506 * @return approximate area of the cell union 507 */ approxArea()508 public double approxArea() { 509 double area = 0; 510 for (S2CellId cellId : cellIds) { 511 area += new S2Cell(cellId).approxArea(); 512 } 513 return area; 514 } 515 516 /** 517 * Calculates this cell union's area by summing the exact area for each 518 * contained cell, using the {@link S2Cell#exactArea()}. 519 * 520 * @return the exact area of the cell union 521 */ exactArea()522 public double exactArea() { 523 double area = 0; 524 for (S2CellId cellId : cellIds) { 525 area += new S2Cell(cellId).exactArea(); 526 } 527 return area; 528 } 529 530 /** Return true if two cell unions are identical. */ 531 @Override equals(Object that)532 public boolean equals(Object that) { 533 if (!(that instanceof S2CellUnion)) { 534 return false; 535 } 536 S2CellUnion union = (S2CellUnion) that; 537 return this.cellIds.equals(union.cellIds); 538 } 539 540 @Override hashCode()541 public int hashCode() { 542 int value = 17; 543 for (S2CellId id : this) { 544 value = 37 * value + id.hashCode(); 545 } 546 return value; 547 } 548 549 /** 550 * Normalizes the cell union by discarding cells that are contained by other 551 * cells, replacing groups of 4 child cells by their parent cell whenever 552 * possible, and sorting all the cell ids in increasing order. Returns true if 553 * the number of cells was reduced. 554 * 555 * This method *must* be called before doing any calculations on the cell 556 * union, such as Intersects() or Contains(). 557 * 558 * @return true if the normalize operation had any effect on the cell union, 559 * false if the union was already normalized 560 */ normalize()561 public boolean normalize() { 562 // Optimize the representation by looking for cases where all subcells 563 // of a parent cell are present. 564 565 ArrayList<S2CellId> output = new ArrayList<S2CellId>(cellIds.size()); 566 output.ensureCapacity(cellIds.size()); 567 Collections.sort(cellIds); 568 569 for (S2CellId id : this) { 570 int size = output.size(); 571 // Check whether this cell is contained by the previous cell. 572 if (!output.isEmpty() && output.get(size - 1).contains(id)) { 573 continue; 574 } 575 576 // Discard any previous cells contained by this cell. 577 while (!output.isEmpty() && id.contains(output.get(output.size() - 1))) { 578 output.remove(output.size() - 1); 579 } 580 581 // Check whether the last 3 elements of "output" plus "id" can be 582 // collapsed into a single parent cell. 583 while (output.size() >= 3) { 584 size = output.size(); 585 // A necessary (but not sufficient) condition is that the XOR of the 586 // four cells must be zero. This is also very fast to test. 587 if ((output.get(size - 3).id() ^ output.get(size - 2).id() ^ output.get(size - 1).id()) 588 != id.id()) { 589 break; 590 } 591 592 // Now we do a slightly more expensive but exact test. First, compute a 593 // mask that blocks out the two bits that encode the child position of 594 // "id" with respect to its parent, then check that the other three 595 // children all agree with "mask. 596 long mask = id.lowestOnBit() << 1; 597 mask = ~(mask + (mask << 1)); 598 long idMasked = (id.id() & mask); 599 if ((output.get(size - 3).id() & mask) != idMasked 600 || (output.get(size - 2).id() & mask) != idMasked 601 || (output.get(size - 1).id() & mask) != idMasked || id.isFace()) { 602 break; 603 } 604 605 // Replace four children by their parent cell. 606 output.remove(size - 1); 607 output.remove(size - 2); 608 output.remove(size - 3); 609 id = id.parent(); 610 } 611 output.add(id); 612 } 613 if (output.size() < size()) { 614 initRawSwap(output); 615 return true; 616 } 617 return false; 618 } 619 } 620