1 /** 2 Allows arbitrary regions to be approximated as unions of cells (S2CellUnion). 3 4 Copyright: 2005 Google Inc. All Rights Reserved. 5 6 License: 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 $(LINK http://www.apache.org/licenses/LICENSE-2.0) 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS-IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 19 Authors: ericv@google.com (Eric Veach), madric@gmail.com (Vijay Nayar) 20 */ 21 module s2.s2region_coverer; 22 23 import s2.logger; 24 import s2.s2cell; 25 import s2.s2cell_id; 26 import s2.s2cell_union; 27 import s2.s2point; 28 import s2.s2region; 29 30 import std.algorithm : isSorted, min, max; 31 import std.array : array; 32 import std.container : BinaryHeap, heapify; 33 import std.range : assumeSorted, chain, SortedRange, takeOne; 34 35 /** 36 An S2RegionCoverer is a class that allows arbitrary regions to be 37 approximated as unions of cells (S2CellUnion). This is useful for 38 implementing various sorts of search and precomputation operations. 39 40 Typical usage: 41 --- 42 auto options = new S2RegionCoverer.Options(); 43 options.setMaxCells(5); 44 auto coverer = new S2RegionCoverer(options); 45 auto cap = new S2Cap(center, radius); 46 S2CellUnion covering = coverer.getCovering(cap); 47 --- 48 49 This yields a vector of at most 5 cells that is guaranteed to cover the 50 given cap (a disc-shaped region on the sphere). 51 52 The approximation algorithm is not optimal but does a pretty good job in 53 practice. The output does not always use the maximum number of cells 54 allowed, both because this would not always yield a better approximation, 55 and because max_cells() is a limit on how much work is done exploring the 56 possible covering as well as a limit on the final output size. 57 58 Because it is an approximation algorithm, one should not rely on the 59 stability of the output. In particular, the output of the covering algorithm 60 may change across different versions of the library. 61 62 One can also generate interior coverings, which are sets of cells which 63 are entirely contained within a region. Interior coverings can be 64 empty, even for non-empty regions, if there are no cells that satisfy 65 the provided constraints and are contained by the region. Note that for 66 performance reasons, it is wise to specify a max_level when computing 67 interior coverings - otherwise for regions with small or zero area, the 68 algorithm may spend a lot of time subdividing cells all the way to leaf 69 level to try to find contained cells. 70 */ 71 class S2RegionCoverer { 72 public: 73 74 /// Options for the S2RegionCoverer. 75 static class Options { 76 public: 77 enum int DEFAULT_MAX_CELLS = 8; 78 79 /** 80 Sets the desired maximum number of cells in the approximation. Note 81 the following: 82 83 - For any setting of max_cells(), up to 6 cells may be returned if 84 that is the minimum number required (e.g. if the region intersects 85 all six cube faces). Even for very tiny regions, up to 3 cells may 86 be returned if they happen to be located at the intersection of 87 three cube faces. 88 89 - min_level() takes priority over max_cells(), i.e. cells below the 90 given level will never be used even if this causes a large number of 91 cells to be returned. 92 93 - If max_cells() is less than 4, the area of the covering may be 94 arbitrarily large compared to the area of the original region even 95 if the region is convex (e.g. an S2Cap or S2LatLngRect). 96 97 Accuracy is measured by dividing the area of the covering by the area 98 of the original region. The following table shows the median and worst 99 case values for this area ratio on a test case consisting of 100,000 100 spherical caps of random size (generated using s2region_coverer_test): 101 102 --- 103 max_cells: 3 4 5 6 8 12 20 100 1000 104 median ratio: 5.33 3.32 2.73 2.34 1.98 1.66 1.42 1.11 1.01 105 worst case: 215518 14.41 9.72 5.26 3.91 2.75 1.92 1.20 1.02 106 --- 107 108 The default value of 8 gives a reasonable tradeoff between the number 109 of cells used and the accuracy of the approximation. 110 111 DEFAULT: DEFAULT_MAX_CELLS 112 */ 113 int maxCells() const { 114 return _maxCells; 115 } 116 117 void setMaxCells(int max_cells) { 118 _maxCells = max_cells; 119 } 120 121 /** 122 Sets the minimum and maximum cell levels to be used. The default is to 123 use all cell levels. 124 125 To find the cell level corresponding to a given physical distance, use 126 the S2Cell metrics defined in s2metrics.h. For example, to find the 127 cell level that corresponds to an average edge length of 10km, use: 128 129 --- 130 int level = 131 AVG_EDGE.getClosestLevel(S2Earth.kmToRadians(length_km)); 132 --- 133 134 Note that min_level() takes priority over max_cells(), i.e. cells below 135 the given level will never be used even if this causes a large number 136 of cells to be returned. (This doesn't apply to interior coverings, 137 since interior coverings make no completeness guarantees -- the result 138 is simply a set of cells that covers as much of the interior as 139 possible while satisfying the given restrictions.) 140 141 Requires: max_level() >= min_level() 142 Default: 0 143 */ 144 int minLevel() const { 145 return _minLevel; 146 } 147 148 void setMinLevel(int min_level) 149 in { 150 assert(min_level >= 0); 151 assert(min_level <= S2CellId.MAX_LEVEL); 152 } do { 153 _minLevel = max(0, min(S2CellId.MAX_LEVEL, min_level)); 154 } 155 156 /// Default: `S2CellId.MAX_LEVEL` 157 int maxLevel() const { 158 return _maxLevel; 159 } 160 161 void setMaxLevel(int max_level) 162 in { 163 assert(max_level >= 0); 164 assert(max_level <= S2CellId.MAX_LEVEL); 165 } do { 166 _maxLevel = max(0, min(S2CellId.MAX_LEVEL, max_level)); 167 } 168 169 /// Convenience function that sets both the maximum and minimum cell levels. 170 void setFixedLevel(int level) { 171 setMinLevel(level); 172 setMaxLevel(level); 173 } 174 175 /** 176 If specified, then only cells where (level - min_level) is a multiple 177 of "level_mod" will be used (default 1). This effectively allows the 178 branching factor of the S2CellId hierarchy to be increased. Currently 179 the only parameter values allowed are 1, 2, or 3, corresponding to 180 branching factors of 4, 16, and 64 respectively. 181 182 Default: 1 183 */ 184 int levelMod() const { 185 return _levelMod; 186 } 187 188 void setLevelMod(int level_mod) 189 in { 190 assert(level_mod >= 1); 191 assert(level_mod <= 3); 192 } do { 193 _levelMod = max(1, min(3, level_mod)); 194 } 195 196 /** 197 Convenience function that returns the maximum level such that 198 199 --- 200 (level <= max_level()) && (level - min_level()) % level_mod() == 0. 201 --- 202 203 This is the maximum level that will actually be used in coverings. 204 */ 205 int trueMaxLevel() const { 206 if (_levelMod == 1) return _maxLevel; 207 return _maxLevel - (_maxLevel - _minLevel) % _levelMod; 208 } 209 210 override 211 string toString() const { 212 import std.format : format; 213 return format( 214 "[maxCells=%d, minLevel=%d, maxLevel=%d, levelMod=%d, trueMaxLevel=%d]", 215 _maxCells, _minLevel, _maxLevel, _levelMod, trueMaxLevel()); 216 } 217 218 protected: 219 int _maxCells = DEFAULT_MAX_CELLS; 220 int _minLevel = 0; 221 int _maxLevel = S2CellId.MAX_LEVEL; 222 int _levelMod = 1; 223 } 224 225 this() { 226 QueueEntry[] queueEntries; 227 _pq = heapify(queueEntries); 228 _options = new Options(); 229 } 230 231 /// Constructs an S2RegionCoverer with the given options. 232 this(Options options) { 233 this(); 234 _options = options; 235 } 236 237 /// Returns the current options. Options can be modifed between calls. 238 const(Options) options() const { 239 return _options; 240 } 241 242 Options mutableOptions() { 243 return _options; 244 } 245 246 /** 247 Returns an S2CellUnion that covers (GetCovering) or is contained within 248 (GetInteriorCovering) the given region and satisfies the current options. 249 250 Note that if options().min_level() > 0 or options().level_mod() > 1, the 251 by definition the S2CellUnion may not be normalized, i.e. there may be 252 groups of four child cells that can be replaced by their parent cell. 253 */ 254 S2CellUnion getCovering(S2Region region) { 255 _interiorCovering = false; 256 getCoveringInternal(region); 257 auto r = S2CellUnion.fromVerbatim(_result); 258 _result = null; 259 return r; 260 } 261 262 S2CellUnion getInteriorCovering(S2Region region) { 263 _interiorCovering = true; 264 getCoveringInternal(region); 265 auto r = S2CellUnion.fromVerbatim(_result); 266 _result = null; 267 return r; 268 } 269 270 /** 271 Like the methods above, but works directly with a vector of S2CellIds. 272 This version can be more efficient when this method is called many times, 273 since it does not require allocating a new vector on each call. 274 */ 275 void getCovering(S2Region region, ref S2CellId[] covering) { 276 _interiorCovering = false; 277 getCoveringInternal(region); 278 covering = _result; 279 _result = null; 280 } 281 282 void getInteriorCovering(S2Region region, ref S2CellId[] interior) { 283 _interiorCovering = true; 284 getCoveringInternal(region); 285 interior = _result; 286 _result = null; 287 } 288 289 /** 290 Like GetCovering(), except that this method is much faster and the 291 coverings are not as tight. All of the usual parameters are respected 292 (max_cells, min_level, max_level, and level_mod), except that the 293 implementation makes no attempt to take advantage of large values of 294 max_cells(). (A small number of cells will always be returned.) 295 296 This function is useful as a starting point for algorithms that 297 recursively subdivide cells. 298 */ 299 void getFastCovering(S2Region region, ref S2CellId[] covering) { 300 region.getCellUnionBound(covering); 301 canonicalizeCovering(covering); 302 } 303 304 /** 305 Given a connected region and a starting point, return a set of cells at 306 the given level that cover the region. 307 308 Note that this method is *not* faster than the regular GetCovering() 309 method for most region types, such as S2Cap or S2Polygon, and in fact it 310 can be much slower when the output consists of a large number of cells. 311 Currently it can be faster at generating coverings of long narrow regions 312 such as polylines, but this may change in the future, in which case this 313 method will most likely be removed. 314 */ 315 static void getSimpleCovering( 316 S2Region region, in S2Point start, int level, ref S2CellId[] output) { 317 return floodFill(region, S2CellId(start).parent(level), output); 318 } 319 320 /** 321 Given a region and a starting cell, returns the set of all the 322 edge-connected cells at the same level that intersect "region". 323 The output cells are returned in arbitrary order. 324 */ 325 static void floodFill(S2Region region, S2CellId start, out S2CellId[] output) { 326 bool[S2CellId] all; 327 S2CellId[] frontier; 328 all[start] = true; 329 frontier ~= start; 330 while (frontier.length != 0) { 331 S2CellId id = frontier[$-1]; 332 frontier.length--; 333 if (!region.mayIntersect(new S2Cell(id))) continue; 334 output ~= id; 335 336 S2CellId[4] neighbors; 337 id.getEdgeNeighbors(neighbors); 338 for (int edge = 0; edge < 4; ++edge) { 339 S2CellId nbr = neighbors[edge]; 340 if (nbr !in all) { 341 all[nbr] = true; 342 frontier ~= nbr; 343 } 344 } 345 } 346 } 347 348 /** 349 Returns true if the given S2CellId vector represents a valid covering 350 that conforms to the current covering parameters. In particular: 351 352 - All S2CellIds must be valid. 353 354 - S2CellIds must be sorted and non-overlapping. 355 356 - S2CellId levels must satisfy min_level(), max_level(), and level_mod(). 357 358 - If covering.size() > max_cells(), there must be no two cells with 359 a common ancestor at min_level() or higher. 360 361 - There must be no sequence of cells that could be replaced by an 362 ancestor (i.e. with level_mod() == 1, the 4 child cells of a parent). 363 */ 364 bool isCanonical(in S2CellUnion covering) const { 365 return isCanonical(covering.cellIds()); 366 } 367 368 bool isCanonical(in S2CellId[] covering) const { 369 const int min_level = _options.minLevel(); 370 const int max_level = _options.trueMaxLevel(); 371 const int level_mod = _options.levelMod(); 372 const bool too_many_cells = covering.length > _options.maxCells(); 373 int same_parent_count = 1; 374 S2CellId prev_id = S2CellId.none(); 375 foreach (const S2CellId id; covering) { 376 if (!id.isValid()) { 377 return false; 378 } 379 380 // Check that the S2CellId level is acceptable. 381 const int level = id.level(); 382 if (level < min_level || level > max_level) { 383 return false; 384 } 385 if (level_mod > 1 && (level - min_level) % level_mod != 0) { 386 return false; 387 } 388 389 if (prev_id != S2CellId.none()) { 390 // Check that cells are sorted and non-overlapping. 391 if (prev_id.rangeMax() >= id.rangeMin()) { 392 return false; 393 } 394 395 // If there are too many cells, check that no pair of adjacent cells 396 // could be replaced by an ancestor. 397 if (too_many_cells && id.getCommonAncestorLevel(prev_id) >= min_level) { 398 return false; 399 } 400 401 // Check that there are no sequences of (4 ** level_mod) cells that all 402 // have the same parent (considering only multiples of "level_mod"). 403 int plevel = level - level_mod; 404 if (plevel < min_level || level != prev_id.level() 405 || id.parent(plevel) != prev_id.parent(plevel)) { 406 same_parent_count = 1; 407 } else if (++same_parent_count == (1 << (2 * level_mod))) { 408 return false; 409 } 410 } 411 prev_id = id; 412 } 413 return true; 414 } 415 416 /** 417 Modify "covering" if necessary so that it conforms to the current 418 covering parameters (max_cells, min_level, max_level, and level_mod). 419 There are no restrictions on the input S2CellIds (they may be unsorted, 420 overlapping, etc). 421 */ 422 S2CellUnion canonicalizeCovering(in S2CellUnion covering) { 423 auto ids = covering.cellIds().dup; 424 canonicalizeCovering(ids); 425 return new S2CellUnion(ids); 426 } 427 428 void canonicalizeCovering(ref S2CellId[] covering) 429 out { 430 assert(isCanonical(covering)); 431 } do { 432 // Note that when the covering parameters have their default values, almost 433 // all of the code in this function is skipped. 434 435 // If any cells are too small, or don't satisfy level_mod(), then replace 436 // them with ancestors. 437 if (_options.maxLevel() < S2CellId.MAX_LEVEL || _options.levelMod() > 1) { 438 for (int i = 0; i < covering.length; ++i) { 439 S2CellId id = covering[i]; 440 int level = id.level(); 441 int new_level = adjustLevel(min(level, _options.maxLevel())); 442 if (new_level != level) { 443 covering[i] = id.parent(new_level); 444 } 445 } 446 } 447 448 // Sort the cells and simplify them. 449 S2CellUnion.normalize(covering); 450 451 // Make sure that the covering satisfies min_level() and level_mod(), 452 // possibly at the expense of satisfying max_cells(). 453 if (_options.minLevel() > 0 || _options.levelMod() > 1) { 454 S2CellUnion.denormalize(covering, _options.minLevel(), _options.levelMod(), _result); 455 covering = _result; 456 _result = null; 457 } 458 459 // If there are too many cells and the covering is very large, use the 460 // S2RegionCoverer to compute a new covering. (This avoids possible O(n^2) 461 // behavior of the simpler algorithm below.) 462 long excess = covering.length - _options.maxCells(); 463 if (excess <= 0 || isCanonical(covering)) { 464 return; 465 } 466 if (excess * covering.length > 10000) { 467 getCovering(new S2CellUnion(covering), covering); 468 } else { 469 // Repeatedly replace two adjacent cells in S2CellId order by their lowest 470 // common ancestor until the number of cells is acceptable. 471 while (covering.length > _options.maxCells()) { 472 int best_index = -1, best_level = -1; 473 for (int i = 0; i + 1 < covering.length; ++i) { 474 int level = covering[i].getCommonAncestorLevel(covering[i+1]); 475 level = adjustLevel(level); 476 if (level > best_level) { 477 best_level = level; 478 best_index = i; 479 } 480 } 481 if (best_level < _options.minLevel()) break; 482 483 // Replace all cells contained by the new ancestor cell. 484 S2CellId id = covering[best_index].parent(best_level); 485 replaceCellsWithAncestor(covering, id); 486 487 // Now repeatedly check whether all children of the parent cell are 488 // present, in which case we can replace those cells with their parent. 489 while (best_level > _options.minLevel()) { 490 best_level -= _options.levelMod(); 491 id = id.parent(best_level); 492 if (!containsAllChildren(covering, id)) break; 493 replaceCellsWithAncestor(covering, id); 494 } 495 } 496 } 497 } 498 499 500 private: 501 class Candidate { 502 S2Cell cell; 503 bool isTerminal; // Cell should not be expanded further. 504 int numChildren; // Number of children that intersect the region. 505 Candidate[] children; // Actual size may be 0, 4, 16, or 64 elements. 506 507 override 508 string toString() const { 509 import std.format : format; 510 return format("Candidate[cell=%s, isTerminal=%s, numChildren=%d]", 511 cell, isTerminal, numChildren); 512 } 513 } 514 515 /** 516 If the cell intersects the given region, return a new candidate with no 517 children, otherwise return nullptr. Also marks the candidate as "terminal" 518 if it should not be expanded further. 519 */ 520 Candidate newCandidate(S2Cell cell) { 521 if (!_region.mayIntersect(cell)) return null; 522 523 bool is_terminal = false; 524 if (cell.level() >= _options.minLevel()) { 525 if (_interiorCovering) { 526 if (_region.contains(cell)) { 527 is_terminal = true; 528 } else if (cell.level() + _options.levelMod() > _options.maxLevel()) { 529 return null; 530 } 531 } else { 532 if (cell.level() + _options.levelMod() > _options.maxLevel() || _region.contains(cell)) { 533 is_terminal = true; 534 } 535 } 536 } 537 size_t children_size = 0; 538 if (!is_terminal) { 539 children_size = Candidate.sizeof << maxChildrenShift(); 540 } 541 Candidate candidate = new Candidate(); 542 candidate.cell = cell; 543 candidate.isTerminal = is_terminal; 544 candidate.numChildren = 0; 545 ++_candidatesCreatedCounter; 546 return candidate; 547 } 548 549 /// Return the log base 2 of the maximum number of children of a candidate. 550 int maxChildrenShift() const { 551 return 2 * options().levelMod(); 552 } 553 554 /** 555 Process a candidate by either adding it to the result_ vector or 556 expanding its children and inserting it into the priority queue. 557 Passing an argument of nullptr does nothing. 558 */ 559 void addCandidate(Candidate candidate) { 560 if (candidate is null) return; 561 562 if (candidate.isTerminal) { 563 _result ~= candidate.cell.id(); 564 return; 565 } 566 assert(candidate.numChildren == 0); 567 568 // Expand one level at a time until we hit min_level() to ensure that we 569 // don't skip over it. 570 int num_levels = (candidate.cell.level() < _options.minLevel()) ? 1 : _options.levelMod(); 571 int num_terminals = expandChildren(candidate, candidate.cell, num_levels); 572 573 if (candidate.numChildren != 0 && !_interiorCovering 574 && num_terminals == 1 << maxChildrenShift() 575 && candidate.cell.level() >= _options.minLevel()) { 576 // Optimization: add the parent cell rather than all of its children. 577 // We can't do this for interior coverings, since the children just 578 // intersect the region, but may not be contained by it - we need to 579 // subdivide them further. 580 candidate.isTerminal = true; 581 addCandidate(candidate); 582 } else { 583 // We negate the priority so that smaller absolute priorities are returned 584 // first. The heuristic is designed to refine the largest cells first, 585 // since those are where we have the largest potential gain. Among cells 586 // of the same size, we prefer the cells with the fewest children. 587 // Finally, among cells with equal numbers of children we prefer those 588 // with the smallest number of children that cannot be refined further. 589 int priority = -((((candidate.cell.level() << maxChildrenShift()) 590 + candidate.numChildren) << maxChildrenShift()) 591 + num_terminals); 592 _pq.insert(QueueEntry(priority, candidate)); 593 logger.logDebug("Push: ", candidate.cell.id(), " (", priority, ") "); 594 } 595 } 596 597 /** 598 Populate the children of "candidate" by expanding the given number of 599 levels from the given cell. Returns the number of children that were 600 marked "terminal". 601 */ 602 int expandChildren(Candidate candidate, const S2Cell cell, int num_levels) { 603 num_levels--; 604 S2Cell[4] child_cells; 605 cell.subdivide(child_cells); 606 int num_terminals = 0; 607 foreach (int i; 0 .. 4) { 608 if (num_levels > 0) { 609 if (_region.mayIntersect(child_cells[i])) { 610 num_terminals += expandChildren(candidate, child_cells[i], num_levels); 611 } 612 continue; 613 } 614 Candidate child = newCandidate(child_cells[i]); 615 if (child) { 616 candidate.children ~= child; 617 candidate.numChildren++; 618 if (child.isTerminal) ++num_terminals; 619 } 620 } 621 return num_terminals; 622 } 623 624 /// Computes a set of initial candidates that cover the given region. 625 void getInitialCandidates() { 626 // Optimization: start with a small (usually 4 cell) covering of the 627 // region's bounding cap. 628 auto tmp_coverer = new S2RegionCoverer(); 629 tmp_coverer.mutableOptions().setMaxCells(min(4, _options.maxCells())); 630 tmp_coverer.mutableOptions().setMaxLevel(_options.maxLevel()); 631 S2CellId[] cells; 632 tmp_coverer.getFastCovering(_region, cells); 633 adjustCellLevels(cells); 634 foreach (S2CellId cell_id; cells) { 635 addCandidate(newCandidate(new S2Cell(cell_id))); 636 } 637 } 638 639 /// Generates a covering and stores it in result_. 640 void getCoveringInternal(S2Region region) 641 in { 642 assert(_pq.length == 0); 643 assert(_result.length == 0); 644 } out { 645 assert(isCanonical(_result)); 646 } do { 647 // Strategy: Start with the 6 faces of the cube. Discard any 648 // that do not intersect the shape. Then repeatedly choose the 649 // largest cell that intersects the shape and subdivide it. 650 // 651 // result_ contains the cells that will be part of the output, while pq_ 652 // contains cells that we may still subdivide further. Cells that are 653 // entirely contained within the region are immediately added to the output, 654 // while cells that do not intersect the region are immediately discarded. 655 // Therefore pq_ only contains cells that partially intersect the region. 656 // Candidates are prioritized first according to cell size (larger cells 657 // first), then by the number of intersecting children they have (fewest 658 // children first), and then by the number of fully contained children 659 // (fewest children first). 660 661 _region = region; 662 _candidatesCreatedCounter = 0; 663 664 getInitialCandidates(); 665 while (!_pq.empty() 666 && (!_interiorCovering || _result.length < _options.maxCells())) { 667 Candidate candidate = _pq.front().candidate; 668 _pq.popFront(); 669 logger.logDebug("Pop: ", candidate.cell.id()); 670 // For interior coverings we keep subdividing no matter how many children 671 // the candidate has. If we reach max_cells() before expanding all 672 // children, we will just use some of them. For exterior coverings we 673 // cannot do this, because the result has to cover the whole region, so 674 // all children have to be used. The (candidate->num_children == 1) case 675 // takes care of the situation when we already have more than max_cells() 676 // in results (min_level is too high). Subdividing the candidate with one 677 // child does no harm in this case. 678 if (_interiorCovering 679 || candidate.cell.level() < _options.minLevel() 680 || candidate.numChildren == 1 681 || (_result.length + _pq.length + candidate.numChildren <= _options.maxCells())) { 682 // Expand this candidate into its children. 683 for (int i = 0; i < candidate.numChildren; ++i) { 684 if (!_interiorCovering || _result.length < _options.maxCells()) { 685 addCandidate(candidate.children[i]); 686 } 687 } 688 } else { 689 candidate.isTerminal = true; 690 addCandidate(candidate); 691 } 692 } 693 logger.logDebug("Created ", _result.length, " cells, ", 694 _candidatesCreatedCounter, " candidates created, ", 695 _pq.length, " left"); 696 while (!_pq.empty()) { 697 _pq.popFront(); 698 } 699 _region = null; 700 701 // Rather than just returning the raw list of cell ids, we construct a cell 702 // union and then denormalize it. This has the effect of replacing four 703 // child cells with their parent whenever this does not violate the covering 704 // parameters specified (min_level, level_mod, etc). This significantly 705 // reduces the number of cells returned in many cases, and it is cheap 706 // compared to computing the covering in the first place. 707 S2CellUnion.normalize(_result); 708 if (_options.minLevel() > 0 || _options.levelMod() > 1) { 709 auto result_copy = _result; 710 S2CellUnion.denormalize(result_copy, _options.minLevel(), _options.levelMod(), _result); 711 } 712 } 713 714 /** 715 If level > min_level(), then reduce "level" if necessary so that it also 716 satisfies level_mod(). Levels smaller than min_level() are not affected 717 (since cells at these levels are eventually expanded). 718 */ 719 int adjustLevel(int level) const { 720 if (_options.levelMod() > 1 && level > _options.minLevel()) { 721 level -= (level - _options.minLevel()) % _options.levelMod(); 722 } 723 return level; 724 } 725 726 /** 727 Ensure that all cells with level > min_level() also satisfy level_mod(), 728 by replacing them with an ancestor if necessary. Cell levels smaller 729 than min_level() are not modified (see AdjustLevel). The output is 730 then normalized to ensure that no redundant cells are present. 731 */ 732 void adjustCellLevels(ref S2CellId[] cells) const 733 in { 734 assert(isSorted(cells)); 735 } do { 736 if (_options.levelMod() == 1) return; 737 738 int output = 0; 739 foreach (S2CellId id; cells) { 740 int level = id.level(); 741 int new_level = adjustLevel(level); 742 if (new_level != level) id = id.parent(new_level); 743 if (output > 0 && cells[output-1].contains(id)) continue; 744 while (output > 0 && id.contains(cells[output-1])) --output; 745 cells[output++] = id; 746 } 747 cells.length = output; 748 } 749 750 /** 751 Returns true if "covering" contains all children of "id" at level 752 (id.level() + options_.level_mod()). 753 */ 754 bool containsAllChildren(in S2CellId[] covering, S2CellId id) const { 755 auto ranges = assumeSorted(covering).trisect(id.rangeMin()); 756 auto geRange = chain(ranges[1], ranges[2]); 757 int level = id.level() + _options.levelMod(); 758 for (S2CellId child = id.childBegin(level); 759 child != id.childEnd(level); 760 geRange.popFront(), child = child.next()) { 761 if (geRange.empty() || geRange.front != child) return false; 762 } 763 return true; 764 } 765 766 /// Replaces all descendants of "id" in "covering" with "id". 767 /// REQUIRES: "covering" contains at least one descendant of "id". 768 void replaceCellsWithAncestor(ref S2CellId[] covering, S2CellId id) const { 769 auto ltRange = assumeSorted(covering).lowerBound(id.rangeMin()); 770 auto gtRange = assumeSorted(covering).upperBound(id.rangeMax()); 771 auto cutRange = chain(ltRange, [id], gtRange); 772 covering = array(cutRange); 773 } 774 775 Options _options; 776 777 // We save a temporary copy of the pointer passed to GetCovering() in order 778 // to avoid passing this parameter around internally. It is only used (and 779 // only valid) for the duration of a single GetCovering() call. 780 S2Region _region = null; 781 782 // The set of S2CellIds that have been added to the covering so far. 783 S2CellId[] _result; 784 785 // We keep the candidates in a priority queue. We specify a vector to hold 786 // the queue entries since for some reason priority_queue<> uses a deque by 787 // default. We define our own own comparison function on QueueEntries in 788 // order to make the results deterministic. (Using the default 789 // less<QueueEntry>, entries of equal priority would be sorted according to 790 // the memory address of the candidate.) 791 792 struct QueueEntry { 793 int priority; 794 Candidate candidate; 795 796 int opCmp(ref in QueueEntry b) { 797 return priority - b.priority; 798 } 799 } 800 alias CandidateQueue = BinaryHeap!(QueueEntry[]); 801 CandidateQueue _pq; 802 803 // True if we're computing an interior covering. 804 bool _interiorCovering; 805 806 // Counter of number of candidates created, for performance evaluation. 807 int _candidatesCreatedCounter; 808 }