1 // Copyright 2005 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS-IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 16 // Original author: ericv@google.com (Eric Veach) 17 // Converted to D: madric@gmail.com (Vijay Nayar) 18 19 module s2.s2cell_union; 20 21 import s2.logger; 22 import s2.s1angle; 23 import s2.s2cap; 24 import s2.s2cell; 25 import s2.s2cell_id; 26 import s2.s2latlng_rect; 27 import s2.s2point; 28 import s2.s2region; 29 import s2.util.coding.coder; 30 31 import std.algorithm : countUntil, min, max, isSorted, sort; 32 import std.exception : enforce; 33 import std.range; 34 35 // The maximum number of cells allowed by S2CellUnion.Decode 36 enum int S2CELL_UNION_DECODE_MAX_NUM_CELLS = 1_000_000; 37 enum ubyte CURRENT_LOSSLESS_ENCODING_VERSION_NUMBER = 1; 38 39 /** 40 * An S2CellUnion is a region consisting of cells of various sizes. Typically 41 * a cell union is used to approximate some other shape. There is a tradeoff 42 * between the accuracy of the approximation and how many cells are used. 43 * Unlike polygons, cells have a fixed hierarchical structure. This makes 44 * them more suitable for optimizations based on preprocessing. 45 * 46 * An S2CellUnion is represented as a vector of sorted, non-overlapping 47 * S2CellIds. By default the vector is also "normalized", meaning that groups 48 * of 4 child cells have been replaced by their parent cell whenever possible. 49 * S2CellUnions are not required to be normalized, but certain operations will 50 * return different results if they are not (e.g., Contains(S2CellUnion).) 51 * 52 * S2CellUnion is movable and copyable. 53 */ 54 final class S2CellUnion : S2Region { 55 public: 56 /// Creates an empty cell union. 57 this() {} 58 59 /** 60 * Constructs a cell union with the given S2CellIds, then calls Normalize() 61 * to sort them, remove duplicates, and merge cells when possible. (See 62 * FromNormalized if your vector is already normalized.) 63 * 64 * The argument is passed by value, so if you are passing a named variable 65 * and have no further use for it, consider using std::move(). 66 * 67 * A cell union containing a single S2CellId may be constructed like this: 68 * 69 * S2CellUnion example({cell_id}); 70 */ 71 this(in S2CellId[] cell_ids) { 72 _cellIds = cell_ids.dup; 73 normalize(); 74 } 75 76 // Convenience constructor that accepts a vector of uint64. Note that 77 // unlike the constructor above, this one makes a copy of "cell_ids". 78 this(in ulong[] cell_ids) { 79 _cellIds = toS2CellIds(cell_ids.dup); 80 normalize(); 81 } 82 83 /** 84 * Constructs a cell union from S2CellIds that have already been normalized 85 * (typically because they were extracted from another S2CellUnion). 86 * 87 * The argument is passed by value, so if you are passing a named variable 88 * and have no further use for it, consider using std::move(). 89 * 90 * REQUIRES: "cell_ids" satisfies the requirements of IsNormalized(). 91 */ 92 static S2CellUnion fromNormalized(S2CellId[] cell_ids) { 93 auto result = new S2CellUnion(cell_ids, VerbatimFlag.VERBATIM); 94 enforce(result.isNormalized()); 95 return result; 96 } 97 98 /** 99 * Constructs a cell union from a vector of sorted, non-overlapping 100 * S2CellIds. Unlike the other constructors, FromVerbatim does not require 101 * that groups of 4 child cells have been replaced by their parent cell. In 102 * other words, "cell_ids" must satisfy the requirements of IsValid() but 103 * not necessarily IsNormalized(). 104 * 105 * Note that if the cell union is not normalized, certain operations may 106 * return different results (e.g., Contains(S2CellUnion)). 107 * 108 * REQUIRES: "cell_ids" satisfies the requirements of IsValid(). 109 */ 110 static S2CellUnion fromVerbatim(S2CellId[] cell_ids) { 111 auto result = new S2CellUnion(cell_ids, VerbatimFlag.VERBATIM); 112 enforce(result.isValid()); 113 return result; 114 } 115 116 /** 117 * Constructs a cell union that corresponds to a continuous range of cell 118 * ids. The output is a normalized collection of cell ids that covers the 119 * leaf cells between "min_id" and "max_id" inclusive. 120 * 121 * REQUIRES: min_id.is_leaf(), max_id.is_leaf(), min_id <= max_id. 122 */ 123 static S2CellUnion fromMinMax(S2CellId min_id, S2CellId max_id) { 124 auto result = new S2CellUnion(); 125 result.initFromMinMax(min_id, max_id); 126 return result; 127 } 128 129 /** 130 * Like FromMinMax() except that the union covers the range of leaf cells 131 * from "begin" (inclusive) to "end" (exclusive), as with Python ranges or 132 * STL iterator ranges. If (begin == end) the result is empty. 133 * 134 * REQUIRES: begin.is_leaf(), end.is_leaf(), begin <= end. 135 */ 136 static S2CellUnion fromBeginEnd(S2CellId begin, S2CellId end) { 137 auto result = new S2CellUnion(); 138 result.initFromBeginEnd(begin, end); 139 return result; 140 } 141 142 /** 143 * initialize() methods corresponding to the constructors/factory methods above. 144 * TODO(ericv): Consider deprecating these methods in favor of using the 145 * constructors and move assignment operator. 146 */ 147 void initialize(S2CellId[] cell_ids) { 148 _cellIds = cell_ids; 149 normalize(); 150 } 151 152 void initialize(ulong[] cell_ids) { 153 _cellIds = toS2CellIds(cell_ids); 154 normalize(); 155 } 156 157 void initFromMinMax(S2CellId min_id, S2CellId max_id) 158 in { 159 assert(max_id.isValid()); 160 } do { 161 initFromBeginEnd(min_id, max_id.next()); 162 } 163 164 void initFromBeginEnd(S2CellId begin, S2CellId end) 165 in { 166 assert(begin.isLeaf()); 167 assert(end.isLeaf()); 168 assert(begin <= end); 169 } out { 170 // The output is already normalized. 171 assert(isNormalized()); 172 } do { 173 // We repeatedly add the largest cell we can. 174 _cellIds.length = 0; 175 for (S2CellId id = begin.maximumTile(end); 176 id != end; id = id.next().maximumTile(end)) { 177 _cellIds ~= id; 178 } 179 } 180 181 // Returns true if the given four cells have a common parent. 182 // REQUIRES: The four cells are distinct. 183 static bool areSiblings(S2CellId a, S2CellId b, S2CellId c, S2CellId d) { 184 // A necessary (but not sufficient) condition is that the XOR of the 185 // four cells must be zero. This is also very fast to test. 186 if ((a.id() ^ b.id() ^ c.id()) != d.id()) return false; 187 188 // Now we do a slightly more expensive but exact test. First, compute a 189 // mask that blocks out the two bits that encode the child position of 190 // "id" with respect to its parent, then check that the other three 191 // children all agree with "mask". 192 ulong mask = d.lsb() << 1; 193 mask = ~(mask + (mask << 1)); 194 ulong id_masked = (d.id() & mask); 195 return ((a.id() & mask) == id_masked && 196 (b.id() & mask) == id_masked && 197 (c.id() & mask) == id_masked && 198 !d.isFace()); 199 } 200 201 /// Clears the contents of the cell union and minimizes memory usage. 202 void clear() { 203 _cellIds.length = 0; 204 } 205 206 /** 207 * Gives ownership of the vector data to the client without copying, and 208 * clears the content of the cell union. The original data in cell_ids 209 * is lost if there was any. 210 */ 211 S2CellId[] release() { 212 // vector's rvalue reference constructor does not necessarily leave 213 // moved-from value in empty state, so swap instead. 214 S2CellId[] cell_ids = _cellIds; 215 _cellIds.length = 0; 216 return cell_ids; 217 } 218 219 /// Convenience methods for accessing the individual cell ids. 220 int numCells() const { 221 return cast(int)(_cellIds.length); 222 } 223 224 S2CellId cellId(int i) const { 225 return _cellIds[i]; 226 } 227 228 /// Vector-like methods for accessing the individual cell ids. 229 size_t size() const { 230 return _cellIds.length; 231 } 232 233 bool empty() const { 234 return _cellIds.empty(); 235 } 236 237 S2CellId opIndex(size_t i) const { 238 return _cellIds[i]; 239 } 240 241 struct Iterator { 242 const(S2CellId)[] cellIds; 243 size_t pos; 244 245 inout(S2CellId) getValue() inout { 246 return cellIds[pos]; 247 } 248 249 void opUnary(string op)() 250 if (op == "++") { 251 if (pos < cellIds.length) { 252 pos++; 253 } 254 } 255 256 void opUnary(string op)() 257 if (op == "--") { 258 if (pos > 0) { 259 pos--; 260 } 261 } 262 } 263 264 /** 265 * Standard begin/end methods, to allow range-based for loops: 266 * 267 * for (S2CellId id : cell_union) { ... } 268 */ 269 Iterator begin() const { 270 return Iterator(_cellIds, 0); 271 } 272 273 Iterator end() const { 274 return Iterator(_cellIds, _cellIds.length); 275 } 276 277 /// Direct access to the underlying vector for STL algorithms. 278 inout(S2CellId[]) cellIds() inout { 279 return _cellIds; 280 } 281 282 /** 283 * Returns true if the cell union is valid, meaning that the S2CellIds are 284 * valid, non-overlapping, and sorted in increasing order. 285 */ 286 bool isValid() const { 287 if (numCells() > 0 && !cellId(0).isValid()) { 288 return false; 289 } 290 for (int i = 1; i < numCells(); ++i) { 291 if (!cellId(i).isValid()) { 292 return false; 293 } 294 if (cellId(i - 1).rangeMax() >= cellId(i).rangeMin()) { 295 return false; 296 } 297 } 298 return true; 299 } 300 301 /** 302 * Returns true if the cell union is normalized, meaning that it is 303 * satisfies IsValid() and that no four cells have a common parent. 304 * Certain operations such as Contains(S2CellUnion) will return a different 305 * result if the cell union is not normalized. 306 */ 307 bool isNormalized() const { 308 if (numCells() > 0 && !cellId(0).isValid()) { 309 return false; 310 } 311 for (int i = 1; i < numCells(); ++i) { 312 if (!cellId(i).isValid()) { 313 return false; 314 } 315 if (cellId(i - 1).rangeMax() >= cellId(i).rangeMin()) { 316 return false; 317 } 318 if (i >= 3 && areSiblings(cellId(i - 3), cellId(i - 2), cellId(i - 1), cellId(i))) { 319 return false; 320 } 321 } 322 return true; 323 } 324 325 /** 326 * Normalizes the cell union by discarding cells that are contained by other 327 * cells, replacing groups of 4 child cells by their parent cell whenever 328 * possible, and sorting all the cell ids in increasing order. 329 * 330 * Returns true if the number of cells was reduced. 331 * TODO(ericv): Change this method to return void. 332 */ 333 bool normalize() { 334 return normalize(_cellIds); 335 } 336 337 /** 338 * Replaces "output" with an expanded version of the cell union where any 339 * cells whose level is less than "min_level" or where (level - min_level) 340 * is not a multiple of "level_mod" are replaced by their children, until 341 * either both of these conditions are satisfied or the maximum level is 342 * reached. 343 * 344 * This method allows a covering generated by S2RegionCoverer using 345 * min_level() or level_mod() constraints to be stored as a normalized cell 346 * union (which allows various geometric computations to be done) and then 347 * converted back to the original list of cell ids that satisfies the 348 * desired constraints. 349 */ 350 void denormalize(int min_level, int level_mod, ref S2CellId[] output) const { 351 denormalize(_cellIds, min_level, level_mod, output); 352 } 353 354 /** 355 * If there are more than "excess" elements of the cell_ids() vector that 356 * are allocated but unused, reallocates the array to eliminate the excess 357 * space. This reduces memory usage when many cell unions need to be held 358 * in memory at once. 359 */ 360 void pack(int excess = 0) { 361 // TODO: For now, let the D runtime manage this. 362 return; 363 } 364 365 /** 366 * Returns true if the cell union contains the given cell id. Containment 367 * is defined with respect to regions, e.g. a cell contains its 4 children. 368 * This is a fast operation (logarithmic in the size of the cell union). 369 * 370 * CAVEAT: If you have constructed a non-normalized S2CellUnion using 371 * FromVerbatim, note that groups of 4 child cells are *not* considered to 372 * contain their parent cell. To get this behavior you must use one of the 373 * other constructors or call Normalize() explicitly. 374 */ 375 bool contains(S2CellId id) const { 376 // This is an exact test. Each cell occupies a linear span of the S2 377 // space-filling curve, and the cell id is simply the position at the center 378 // of this span. The cell union ids are sorted in increasing order along 379 // the space-filling curve. So we simply find the pair of cell ids that 380 // surround the given cell id (using binary search). There is containment 381 // if and only if one of these two cell ids contains this cell. 382 383 // Split the cellIds into a ranges [0] = less, [1] = equal, [2] = greater 384 auto trisectRanges = assumeSorted(_cellIds[]).trisect(id); 385 return !trisectRanges[1].empty 386 || !trisectRanges[2].empty && trisectRanges[2].front.rangeMin() <= id 387 || !trisectRanges[0].empty && trisectRanges[0].back.rangeMax() >= id; 388 } 389 390 /** 391 * Returns true if the cell union intersects the given cell id. 392 * This is a fast operation (logarithmic in the size of the cell union). 393 */ 394 bool intersects(S2CellId id) const { 395 // This is an exact test; see the comments for Contains() above. 396 auto trisectRanges = assumeSorted(_cellIds[]).trisect(id); 397 return !trisectRanges[1].empty 398 || !trisectRanges[2].empty && trisectRanges[2].front.rangeMin() <= id.rangeMax() 399 || !trisectRanges[0].empty && trisectRanges[0].back.rangeMax() >= id.rangeMin(); 400 } 401 402 // Returns true if this cell union contains the given other cell union. 403 // 404 // CAVEAT: If you have constructed a non-normalized S2CellUnion using 405 // FromVerbatim, note that groups of 4 child cells are *not* considered to 406 // contain their parent cell. To get this behavior you must use one of the 407 // other constructors or call Normalize() explicitly. 408 bool contains(in S2CellUnion y) const { 409 // TODO(ericv): A divide-and-conquer or alternating-skip-search 410 // approach may be sigificantly faster in both the average and worst case. 411 412 foreach (S2CellId y_id; y._cellIds) { 413 if (!contains(y_id)) return false; 414 } 415 return true; 416 } 417 418 // Returns true if this cell union intersects the given other cell union. 419 bool intersects(in S2CellUnion y) const { 420 // TODO(ericv): A divide-and-conquer or alternating-skip-search 421 // approach may be sigificantly faster in both the average and worst case. 422 423 foreach (S2CellId y_id; y._cellIds) { 424 if (intersects(y_id)) return true; 425 } 426 return false; 427 } 428 429 // Returns the union of the two given cell unions. 430 S2CellUnion unite(in S2CellUnion y) const { 431 return new S2CellUnion(array(chain(_cellIds, y._cellIds))); 432 } 433 434 // Returns the intersection of the two given cell unions. 435 S2CellUnion intersect(in S2CellUnion y) const 436 out (result) { 437 // The output is normalized as long as at least one input is normalized. 438 assert(result.isNormalized() || (!isNormalized() && !y.isNormalized())); 439 } do { 440 auto result = new S2CellUnion(); 441 getIntersection(_cellIds, y._cellIds, result._cellIds); 442 return result; 443 } 444 445 // Specialized version of GetIntersection() that returns the intersection of 446 // a cell union with an S2CellId. This can be useful for splitting a cell 447 // union into pieces. 448 S2CellUnion intersect(S2CellId id) const 449 out (result) { 450 assert(result.isNormalized() || !isNormalized()); 451 } do { 452 auto result = new S2CellUnion(); 453 if (contains(id)) { 454 result._cellIds ~= id; 455 } else { 456 auto ranges = assumeSorted(_cellIds).trisect(id.rangeMin()); 457 // Get the range of values >= id.rangeMin(). 458 auto geCells = chain(ranges[1], ranges[2]); 459 S2CellId id_max = id.rangeMax(); 460 while (!geCells.empty() && geCells.front() <= id_max) { 461 result._cellIds ~= geCells.front(); 462 geCells.popFront(); 463 } 464 } 465 return result; 466 } 467 468 /// Returns the difference of the two given cell unions. 469 S2CellUnion difference(in S2CellUnion y) const 470 out (result) { 471 // The output is normalized as long as the first argument is normalized. 472 assert(result.isNormalized() || !isNormalized()); 473 } do { 474 // TODO(ericv): this is approximately O(N*log(N)), but could probably 475 // use similar techniques as GetIntersection() to be more efficient. 476 477 auto result = new S2CellUnion(); 478 for (auto iter = begin(); iter != end(); iter++) { 479 getDifferenceInternal(iter.getValue(), y, result._cellIds); 480 } 481 return result; 482 } 483 484 private static void getDifferenceInternal( 485 S2CellId cell, in S2CellUnion y, ref S2CellId[] cell_ids) { 486 // Add the difference between cell and y to cell_ids. 487 // If they intersect but the difference is non-empty, divide and conquer. 488 if (!y.intersects(cell)) { 489 cell_ids ~= cell; 490 } else if (!y.contains(cell)) { 491 S2CellId child = cell.childBegin(); 492 for (int i = 0; ; ++i) { 493 getDifferenceInternal(child, y, cell_ids); 494 if (i == 3) break; // Avoid unnecessary next() computation. 495 child = child.next(); 496 } 497 } 498 } 499 500 /** 501 * Expands the cell union by adding a buffer of cells at "expand_level" 502 * around the union boundary. 503 * 504 * For each cell "c" in the union, we add all neighboring cells at level 505 * "expand_level" that are adjacent to "c". Note that there can be many 506 * such cells if "c" is large compared to "expand_level". If "c" is smaller 507 * than "expand_level", we first add the parent of "c" at "expand_level" and 508 * then add all the neighbors of that cell. 509 * 510 * Note that the size of the output is exponential in "expand_level". For 511 * example, if expand_level == 20 and the input has a cell at level 10, 512 * there will be on the order of 4000 adjacent cells in the output. For 513 * most applications the Expand(min_radius, max_level_diff) method below is 514 * easier to use. 515 */ 516 void expand(int expand_level) { 517 S2CellId[] output; 518 ulong level_lsb = S2CellId.lsbForLevel(expand_level); 519 for (int i = numCells(); --i >= 0; ) { 520 S2CellId id = cellId(i); 521 if (id.lsb() < level_lsb) { 522 id = id.parent(expand_level); 523 // Optimization: skip over any cells contained by this one. This is 524 // especially important when very small regions are being expanded. 525 while (i > 0 && id.contains(cellId(i - 1))) --i; 526 } 527 output ~= id; 528 id.appendAllNeighbors(expand_level, output); 529 } 530 initialize(output); 531 } 532 533 /** 534 * Expands the cell union such that it contains all points whose distance to 535 * the cell union is at most "min_radius", but do not use cells that are 536 * more than "max_level_diff" levels higher than the largest cell in the 537 * input. The second parameter controls the tradeoff between accuracy and 538 * output size when a large region is being expanded by a small amount 539 * (e.g. expanding Canada by 1km). For example, if max_level_diff == 4 the 540 * region will always be expanded by approximately 1/16 the width of its 541 * largest cell. Note that in the worst case, the number of cells in the 542 * output can be up to 4 * (1 + 2 ** max_level_diff) times larger than the 543 * number of cells in the input. 544 */ 545 void expand(S1Angle min_radius, int max_level_diff) { 546 import s2.s2metrics : MIN_WIDTH; 547 548 int min_level = S2CellId.MAX_LEVEL; 549 foreach (S2CellId id; _cellIds) { 550 min_level = min(min_level, id.level()); 551 } 552 // Find the maximum level such that all cells are at least "min_radius" wide. 553 int radius_level = MIN_WIDTH.getLevelForMinValue(min_radius.radians()); 554 if (radius_level == 0 && min_radius.radians() > MIN_WIDTH.getValue(0)) { 555 // The requested expansion is greater than the width of a face cell. 556 // The easiest way to handle this is to expand twice. 557 expand(0); 558 } 559 expand(min(min_level + max_level_diff, radius_level)); 560 } 561 562 /** 563 * The number of leaf cells covered by the union. 564 * This will be no more than 6*2^60 for the whole sphere. 565 */ 566 ulong leafCellsCovered() const { 567 ulong num_leaves = 0; 568 foreach (S2CellId id; _cellIds) { 569 int inverted_level = S2CellId.MAX_LEVEL - id.level(); 570 num_leaves += (1uL << (inverted_level << 1)); 571 } 572 return num_leaves; 573 } 574 575 /** 576 * Approximates this cell union's area in steradians by summing the average 577 * area of each contained cell's average area, using the AverageArea method 578 * from the S2Cell class. This is equivalent to the number of leaves covered, 579 * multiplied by the average area of a leaf. Note that AverageArea does not 580 * take into account distortion of cell, and thus may be off by up to a 581 * factor of up to 1.7. 582 * 583 * NOTE: Since this is proportional to LeafCellsCovered(), it is 584 * always better to use that function if all you care about is 585 * the relative average area between objects. 586 */ 587 double averageBasedArea() const { 588 return S2Cell.averageArea(S2CellId.MAX_LEVEL) * leafCellsCovered(); 589 } 590 591 // Calculates this cell union's area in steradians by summing the approximate 592 // area for each contained cell, using the ApproxArea method from the S2Cell 593 // class. 594 double approxArea() const { 595 double area = 0; 596 foreach (S2CellId id; _cellIds) { 597 area += (new S2Cell(id)).approxArea(); 598 } 599 return area; 600 } 601 602 /** 603 * Calculates this cell union's area in steradians by summing the exact area 604 * for each contained cell, using the Exact method from the S2Cell class. 605 */ 606 double ExactArea() const { 607 double area = 0; 608 foreach (S2CellId id; _cellIds) { 609 area += (new S2Cell(id)).exactArea(); 610 } 611 return area; 612 } 613 614 /// Return true if two cell unions are identical. 615 override 616 bool opEquals(Object o) { 617 S2CellUnion y = cast(S2CellUnion) o; 618 if (y is null) return false; 619 return cellIds() == y.cellIds(); 620 } 621 622 //////////////////////////////////////////////////////////////////////// 623 // S2Region interface (see s2region.h for details): 624 625 override 626 S2Region clone() { 627 return new S2CellUnion(_cellIds, VerbatimFlag.VERBATIM); 628 } 629 630 override 631 S2Cap getCapBound() { 632 // Compute the approximate centroid of the region. This won't produce the 633 // bounding cap of minimal area, but it should be close enough. 634 if (_cellIds.length == 0) { 635 return S2Cap.empty(); 636 } 637 638 auto centroid = S2Point(0, 0, 0); 639 foreach (S2CellId id; _cellIds) { 640 double area = S2Cell.averageArea(id.level()); 641 centroid += area * id.toS2Point(); 642 } 643 if (centroid == S2Point(0, 0, 0)) { 644 centroid = S2Point(1, 0, 0); 645 } else { 646 centroid = centroid.normalize(); 647 } 648 649 // Use the centroid as the cap axis, and expand the cap angle so that it 650 // contains the bounding caps of all the individual cells. Note that it is 651 // *not* sufficient to just bound all the cell vertices because the bounding 652 // cap may be concave (i.e. cover more than one hemisphere). 653 S2Cap cap = S2Cap.fromPoint(centroid); 654 for (Iterator iter = begin(); iter != end(); iter++) { 655 S2CellId id = iter.getValue(); 656 cap.addCap((new S2Cell(id)).getCapBound()); 657 } 658 return cap; 659 } 660 661 override 662 S2LatLngRect getRectBound() { 663 S2LatLngRect bound = S2LatLngRect.empty(); 664 for (Iterator iter = begin(); iter != end(); iter++) { 665 S2CellId id = iter.getValue(); 666 bound = bound.unite((new S2Cell(id)).getRectBound()); 667 } 668 return bound; 669 } 670 671 override 672 void getCellUnionBound(out S2CellId[] cellIds) { 673 cellIds = _cellIds.dup; 674 } 675 676 /// This is a fast operation (logarithmic in the size of the cell union). 677 override 678 bool contains(in S2Cell cell) const { 679 return contains(cell.id()); 680 } 681 682 /// This is a fast operation (logarithmic in the size of the cell union). 683 override 684 bool mayIntersect(in S2Cell cell) const { 685 return intersects(cell.id()); 686 } 687 688 /** 689 * The point 'p' does not need to be normalized. 690 * This is a fast operation (logarithmic in the size of the cell union). 691 */ 692 bool contains(in S2Point p) const { 693 return contains(S2CellId(p)); 694 } 695 696 /** 697 * Appends a serialized representation of the S2CellUnion to "encoder". 698 * 699 * REQUIRES: "encoder" uses the default constructor, so that its buffer 700 * can be enlarged as necessary by calling Ensure(int). 701 */ 702 void encode(ORangeT)(Encoder!ORangeT encoder) const { 703 // Unsigned char for version number, and N+1 uint64's for N cell_ids 704 // (1 for vector length, N for the ids). 705 encoder.ensure(ubyte.sizeof + ulong.sizeof * (1 + _cellIds.length)); 706 707 encoder.put8(CURRENT_LOSSLESS_ENCODING_VERSION_NUMBER); 708 encoder.put64(_cellIds.length); 709 foreach (cell_id; _cellIds) { 710 cell_id.encode(encoder); 711 } 712 } 713 714 /// Decodes an S2CellUnion encoded with Encode(). Returns true on success. 715 bool decode(IRangeT)(Decoder!IRangeT decoder) { 716 // Should contain at least version and vector length. 717 if (decoder.avail() < ubyte.sizeof + ulong.sizeof) return false; 718 ubyte versionNum = decoder.get8(); 719 if (versionNum > CURRENT_LOSSLESS_ENCODING_VERSION_NUMBER) return false; 720 721 ulong num_cells = decoder.get64(); 722 if (num_cells > S2CELL_UNION_DECODE_MAX_NUM_CELLS) { 723 return false; 724 } 725 726 auto temp_cell_ids = new S2CellId[num_cells]; 727 for (int i = 0; i < num_cells; ++i) { 728 if (!temp_cell_ids[i].decode(decoder)) return false; 729 } 730 _cellIds = temp_cell_ids; 731 return true; 732 } 733 734 //////////////////////////////////////////////////////////////////////// 735 // Static methods intended for high-performance clients that prefer to 736 // manage their own storage. 737 738 /** 739 * Like Normalize(), but works with a vector of S2CellIds. 740 * Equivalent to: 741 * *cell_ids = S2CellUnion(std::move(*cell_ids)).Release(); 742 */ 743 static bool normalize(ref S2CellId[] ids) { 744 // Optimize the representation by discarding cells contained by other cells, 745 // and looking for cases where all subcells of a parent cell are present. 746 ids = array(sort(ids)); 747 int output = 0; 748 foreach (id; ids) { 749 // Check whether this cell is contained by the previous cell. 750 if (output > 0 && ids[output - 1].contains(id)) continue; 751 752 // Discard any previous cells contained by this cell. 753 while (output > 0 && id.contains(ids[output - 1])) --output; 754 755 // Check whether the last 3 elements plus "id" can be collapsed into a 756 // single parent cell. 757 while (output >= 3 && areSiblings(ids[output - 3], ids[output - 2], ids[output - 1], id)) { 758 // Replace four children by their parent cell. 759 id = id.parent(); 760 output -= 3; 761 } 762 ids[output++] = id; 763 } 764 if (ids.length == output) return false; 765 ids.length = output; 766 return true; 767 } 768 769 770 // Like Denormalize(), but works with a vector of S2CellIds. 771 // REQUIRES: out != &in 772 static void denormalize( 773 in S2CellId[] input, int min_level, int level_mod, out S2CellId[] output) 774 in { 775 assert(min_level >= 0); 776 assert(min_level <= S2CellId.MAX_LEVEL); 777 assert(level_mod >= 1); 778 assert(level_mod <= 3); 779 assert(input.length == 0 || output !is input); 780 } do { 781 output.reserve(input.length); 782 foreach (S2CellId id; input) { 783 int level = id.level(); 784 int new_level = max(min_level, level); 785 if (level_mod > 1) { 786 // Round up so that (new_level - min_level) is a multiple of level_mod. 787 // (Note that S2CellId::kMaxLevel is a multiple of 1, 2, and 3.) 788 new_level += (S2CellId.MAX_LEVEL - (new_level - min_level)) % level_mod; 789 new_level = min(S2CellId.MAX_LEVEL, new_level); 790 } 791 if (new_level == level) { 792 output ~= id; 793 } else { 794 S2CellId end = id.childEnd(new_level); 795 for (id = id.childBegin(new_level); id != end; id = id.next()) { 796 output ~= id; 797 } 798 } 799 } 800 } 801 802 /** 803 * Like GetIntersection(), but works directly with vectors of S2CellIds, 804 * Equivalent to: 805 * 806 * *out = S2CellUnion(x).Intersection(S2CellUnion(y)).Release() 807 * 808 * except that this method has slightly more relaxed normalization 809 * requirements: the input vectors may contain groups of 4 child cells that 810 * all have the same parent. (In a normalized S2CellUnion, such groups are 811 * always replaced by the parent cell.) 812 */ 813 static void getIntersection(in S2CellId[] x, in S2CellId[] y, out S2CellId[] output) 814 in { 815 import std.conv : to; 816 assert(output.length == 0 || output !is x); 817 assert(output.length == 0 || output !is y); 818 assert(isSorted(x), "x is unsorted: x=" ~ to!string(x)); 819 assert(isSorted(y), "y is unsorted: y=" ~ to!string(y)); 820 } out { 821 // The output is generated in sorted order. 822 assert(isSorted(output)); 823 } do { 824 // This is a fairly efficient calculation that uses binary search to skip 825 // over sections of both input vectors. It takes logarithmic time if all the 826 // cells of "x" come before or after all the cells of "y" in S2CellId order. 827 size_t xPos = 0; 828 size_t yPos = 0; 829 while (xPos < x.length && yPos < y.length) { 830 S2CellId xPosMin = x[xPos].rangeMin(); 831 S2CellId yPosMin = y[yPos].rangeMin(); 832 if (xPosMin > yPosMin) { 833 // Either x[xPos].contains(*i) or the two cells are disjoint. 834 if (x[xPos] <= y[yPos].rangeMax()) { 835 output ~= x[xPos++]; 836 } else { 837 // Advance "j" to the first cell possibly contained by *i. 838 yPos++; 839 long skipAmount = countUntil!(a => a >= xPosMin)(y[yPos .. $]); 840 if (skipAmount == -1) { 841 yPos = y.length; 842 } else { 843 yPos += skipAmount; 844 } 845 // The previous cell *(j-1) may now contain *i. 846 if (x[xPos] <= y[yPos - 1].rangeMax()) { 847 yPos--; 848 } 849 } 850 } else if (yPosMin > xPosMin) { 851 // Identical to the code above with "i" and "j" reversed. 852 if (y[yPos] <= x[xPos].rangeMax()) { 853 output ~= y[yPos++]; 854 } else { 855 xPos++; 856 long skipAmount = countUntil!(a => a >= yPosMin)(x[xPos .. $]); 857 if (skipAmount == -1) { 858 xPos = x.length; 859 } else { 860 xPos += skipAmount; 861 } 862 if (y[yPos] <= x[xPos - 1].rangeMax()) { 863 --xPos; 864 } 865 } 866 } else { 867 // "i" and "j" have the same range_min(), so one contains the other. 868 if (x[xPos] < y[yPos]) 869 output ~= x[xPos++]; 870 else 871 output ~= y[yPos++]; 872 } 873 } 874 } 875 876 package: 877 // Internal constructor that does not check "cell_ids" for validity. 878 this(in S2CellId[] cell_ids, VerbatimFlag verbatim) { 879 _cellIds = cell_ids.dup; 880 } 881 882 // Internal constructor that does not check "cell_ids" for validity. 883 enum VerbatimFlag { 884 VERBATIM 885 } 886 887 private: 888 // Converts a vector of uint64 to a vector of S2CellIds. 889 static S2CellId[] toS2CellIds(in ulong[] ids) { 890 S2CellId[] cell_ids; 891 cell_ids.reserve(ids.length); 892 foreach (id; ids) cell_ids ~= S2CellId(id); 893 return cell_ids; 894 } 895 896 S2CellId[] _cellIds; 897 }