1 // Copyright 2017 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.s2closest_edge_query_base; 20 21 import s2.logger; 22 import s2.s1angle; 23 import s2.s1chord_angle; 24 import s2.s2cap; 25 import s2.s2cell; 26 import s2.s2cell_id; 27 import s2.s2cell_union; 28 import s2.s2distance_target; 29 import s2.s2point; 30 import s2.s2region_coverer; 31 import s2.s2shape; 32 import s2.s2shape_index; 33 import s2.shapeutil.count_edges : countEdgesUpTo; 34 import s2.shapeutil.shape_edge_id; 35 import s2.util.container.btree; 36 37 import std.algorithm; 38 import std.array; 39 import std.container.binaryheap; 40 import std.exception : enforce; 41 import std.range : retro; 42 43 // S2ClosestEdgeQueryBase is a templatized class for finding the closest 44 // edge(s) between two geometries. It is not intended to be used directly, 45 // but rather to serve as the implementation of various specialized classes 46 // with more convenient APIs (such as S2ClosestEdgeQuery). It is flexible 47 // enough so that it can be adapted to compute maximum distances and even 48 // potentially Hausdorff distances. 49 // 50 // By using the appropriate options, this class can answer questions such as: 51 // 52 // - Find the minimum distance between two geometries A and B. 53 // - Find all edges of geometry A that are within a distance D of geometry B. 54 // - Find the k edges of geometry A that are closest to a given point P. 55 // 56 // You can also specify whether polygons should include their interiors (i.e., 57 // if a point is contained by a polygon, should the distance be zero or should 58 // it be measured to the polygon boundary?) 59 // 60 // The input geometries may consist of any number of points, polylines, and 61 // polygons (collectively referred to as "shapes"). Shapes do not need to be 62 // disjoint; they may overlap or intersect arbitrarily. The implementation is 63 // designed to be fast for both simple and complex geometries. 64 // 65 // The Distance template argument is used to represent distances. Usually 66 // this type is a thin wrapper around S1ChordAngle, but another distance type 67 // may be substituted as long as it implements the API below. This can be 68 // used to change the comparison function (e.g., to find the furthest edges 69 // from the target), or to get more accuracy if desired. 70 // 71 // The Distance concept is as follows: 72 // 73 // class Distance { 74 // public: 75 // // Default and copy constructors, assignment operator: 76 // Distance(); 77 // Distance(const Distance&); 78 // Distance& operator=(const Distance&); 79 // 80 // // Factory methods: 81 // static Distance Zero(); // Returns a zero distance. 82 // static Distance Infinity(); // Larger than any valid distance. 83 // static Distance Negative(); // Smaller than any valid distance. 84 // 85 // // Comparison operators: 86 // friend bool operator==(Distance x, Distance y); 87 // friend bool operator<(Distance x, Distance y); 88 // 89 // // Delta represents the positive difference between two distances. 90 // // It is used together with operator-() to implement Options::max_error(). 91 // // Typically Distance::Delta is simply S1ChordAngle. 92 // class Delta { 93 // public: 94 // Delta(); 95 // Delta(const Delta&); 96 // Delta& operator=(const Delta&); 97 // friend bool operator==(Delta x, Delta y); 98 // static Delta Zero(); 99 // }; 100 // 101 // // Subtraction operator. Note that the second argument represents a 102 // // delta between two distances. This distinction is important for 103 // // classes that compute maximum distances (e.g., S2FurthestEdgeQuery). 104 // friend Distance operator-(Distance x, Delta delta); 105 // 106 // // Method that returns an upper bound on the S1ChordAngle corresponding 107 // // to this Distance (needed to implement Options::max_distance 108 // // efficiently). For example, if Distance measures WGS84 ellipsoid 109 // // distance then the corresponding angle needs to be 0.56% larger. 110 // S1ChordAngle GetChordAngleBound() const; 111 // }; 112 class S2ClosestEdgeQueryBase(DistanceT) { 113 public: 114 alias Delta = DistanceT.Delta; 115 116 // Options that control the set of edges returned. Note that by default 117 // *all* edges are returned, so you will always want to set either the 118 // max_edges() option or the max_distance() option (or both). 119 static class Options { 120 public: 121 this() { } 122 123 this(Options options) { 124 _maxDistance = options._maxDistance; 125 _maxError = options._maxError; 126 _maxEdges = options._maxEdges; 127 _includeInteriors = options._includeInteriors; 128 _useBruteForce = options._useBruteForce; 129 } 130 131 // Specifies that at most "max_edges" edges should be returned. 132 // 133 // REQUIRES: max_edges >= 1 134 // DEFAULT: numeric_limits<int>::max() 135 int maxEdges() const { 136 return _maxEdges; 137 } 138 139 void setMaxEdges(int max_edges) 140 in { 141 assert(max_edges >= 1); 142 } do { 143 _maxEdges = max_edges; 144 } 145 146 enum int MAX_MAX_EDGES = int.max; 147 148 // Specifies that only edges whose distance to the target is less than 149 // "max_distance" should be returned. 150 // 151 // Note that edges whose distance is exactly equal to "max_distance" are 152 // not returned. In most cases this doesn't matter (since distances are 153 // not computed exactly in the first place), but if such edges are needed 154 // then you can retrieve them by specifying "max_distance" as the next 155 // largest representable DistanceT. For example, if DistanceT is an 156 // S1ChordAngle then you can specify max_distance.Successor(). 157 // 158 // DEFAULT: DistanceT::Infinity() 159 DistanceT maxDistance() const { 160 return _maxDistance; 161 } 162 163 void setMaxDistance(DistanceT max_distance) { 164 _maxDistance = max_distance; 165 } 166 167 // Specifies that edges up to max_error() further away than the true 168 // closest edges may be substituted in the result set, as long as such 169 // edges satisfy all the remaining search criteria (such as max_distance). 170 // This option only has an effect if max_edges() is also specified; 171 // otherwise all edges closer than max_distance() will always be returned. 172 // 173 // Note that this does not affect how the distance between edges is 174 // computed; it simply gives the algorithm permission to stop the search 175 // early as soon as the best possible improvement drops below max_error(). 176 // 177 // This can be used to implement distance predicates efficiently. For 178 // example, to determine whether the minimum distance is less than D, set 179 // max_edges() == 1 and max_distance() == max_error() == D. This causes 180 // the algorithm to terminate as soon as it finds any edge whose distance 181 // is less than D, rather than continuing to search for an edge that is 182 // even closer. 183 // 184 // DEFAULT: DistanceT::Delta::Zero() 185 Delta maxError() const { 186 return _maxError; 187 } 188 189 void setMaxError(Delta max_error) { 190 _maxError = max_error; 191 } 192 193 // Specifies that polygon interiors should be included when measuring 194 // distances. In other words, polygons that contain the target should 195 // have a distance of zero. (For targets consisting of multiple connected 196 // components, the distance is zero if any component is contained.) This 197 // is indicated in the results by returning a (shape_id, edge_id) pair 198 // with edge_id == -1, i.e. this value denotes the polygons's interior. 199 // 200 // Note that for efficiency, any polygon that intersects the target may or 201 // may not have an (edge_id == -1) result. Such results are optional 202 // because in that case the distance to the polygon is already zero. 203 // 204 // DEFAULT: true 205 bool includeInteriors() const { 206 return _includeInteriors; 207 } 208 209 void setIncludeInteriors(bool include_interiors) { 210 _includeInteriors = include_interiors; 211 } 212 213 // Specifies that distances should be computed by examining every edge 214 // rather than using the S2ShapeIndex. This is useful for testing, 215 // benchmarking, and debugging. 216 // 217 // DEFAULT: false 218 bool useBruteForce() const { 219 return _useBruteForce; 220 } 221 222 void setUseBruteForce(bool use_brute_force) { 223 _useBruteForce = use_brute_force; 224 } 225 226 private: 227 DistanceT _maxDistance = DistanceT.infinity; 228 Delta _maxError = Delta.zero; 229 int _maxEdges = MAX_MAX_EDGES; 230 bool _includeInteriors = true; 231 bool _useBruteForce = false; 232 } 233 234 // The Target class represents the geometry to which the distance is 235 // measured. For example, there can be subtypes for measuring the distance 236 // to a point, an edge, or to an S2ShapeIndex (an arbitrary collection of 237 // geometry). 238 // 239 // Implementations do *not* need to be thread-safe. They may cache data or 240 // allocate temporary data structures in order to improve performance. 241 alias Target = S2DistanceTarget!DistanceT; 242 243 // Each "Result" object represents a closest edge. Note the following 244 // special cases: 245 // 246 // - (shape_id >= 0) && (edge_id < 0) represents the interior of a shape. 247 // Such results may be returned when options.include_interiors() is true. 248 // 249 // - (shape_id < 0) && (edge_id < 0) is returned by `FindClosestEdge` to 250 // indicate that no edge satisfies the requested query options. 251 // 252 // TODO(ericv): Convert to a class with accessor methods. 253 struct Result { 254 DistanceT distance = DistanceT.infinity; // The distance from the target to this edge. 255 int shapeId = -1; // Identifies an indexed shape. 256 int edgeId = -1; // Identifies an edge within the shape. 257 258 // Compares edges first by distance, then by (shape_id, edge_id). 259 int opCmp(in Result o) const { 260 if (distance != o.distance) return distance.opCmp(o.distance); 261 if (shapeId != o.shapeId) return shapeId - o.shapeId; 262 if (edgeId != o.edgeId) return edgeId - o.edgeId; 263 return 0; 264 } 265 } 266 267 // Default constructor; requires Init() to be called. 268 this() { 269 _iter = new S2ShapeIndex.Iterator(); 270 _resultSet = new BTree!Result(); 271 _queue = BinaryHeap!(QueueEntry[])([]); 272 } 273 274 // Convenience constructor that calls Init(). 275 this(S2ShapeIndex index) { 276 this(); 277 initialize(index); 278 } 279 280 // Initializes the query. 281 // REQUIRES: ReInit() must be called if "index" is modified. 282 void initialize(S2ShapeIndex index) { 283 _index = index; 284 reInitialize(); 285 } 286 287 // Reinitializes the query. This method must be called whenever the 288 // underlying index is modified. 289 void reInitialize() { 290 _indexNumEdges = 0; 291 _indexNumEdgesLimit = 0; 292 _indexCovering.length = 0; 293 _indexCells.length = 0; 294 // We don't initialize iter_ here to make queries on small indexes a bit 295 // faster (i.e., where brute force is used). 296 } 297 298 // Returns a reference to the underlying S2ShapeIndex. 299 const(S2ShapeIndex) index() const { 300 return _index; 301 } 302 303 // Returns the closest edges to the given target that satisfy the given 304 // options. This method may be called multiple times. 305 // 306 // Note that if options().include_interiors() is true, the result vector may 307 // include some entries with edge_id == -1. This indicates that the target 308 // intersects the indexed polygon with the given shape_id. 309 Result[] findClosestEdges(Target target, Options options) { 310 import std.stdio; 311 Result[] results; 312 findClosestEdges(target, options, results); 313 return results; 314 } 315 316 // This version can be more efficient when this method is called many times, 317 // since it does not require allocating a new vector on each call. 318 void findClosestEdges(Target target, Options options, out Result[] results) { 319 findClosestEdgesInternal(target, options); 320 if (options.maxEdges() == 1) { 321 if (_resultSingleton.shapeId >= 0) { 322 results ~= _resultSingleton; 323 } 324 } else if (options.maxEdges() == Options.MAX_MAX_EDGES) { 325 results ~= _resultVector; 326 _resultVector.length = 0; 327 } else { 328 results = _resultSet[].array; 329 _resultSet.clear(); 330 } 331 } 332 333 // Convenience method that returns exactly one edge. If no edges satisfy 334 // the given search criteria, then a Result with distance == Infinity() and 335 // shape_id == edge_id == -1 is returned. 336 // 337 // Note that if options.include_interiors() is true, edge_id == -1 is also 338 // used to indicate that the target intersects an indexed polygon (but in 339 // that case distance == Zero() and shape_id >= 0). 340 // 341 // REQUIRES: options.max_edges() == 1 342 Result findClosestEdge(Target target, Options options) 343 in { 344 assert(options.maxEdges() == 1); 345 } do { 346 findClosestEdgesInternal(target, options); 347 return _resultSingleton; 348 } 349 350 private: 351 352 const(Options) options() const { 353 return _options; 354 } 355 356 void findClosestEdgesInternal(Target target, Options options) 357 in { 358 assert(_resultVector.empty()); 359 assert(_resultSet.empty()); 360 assert(target.maxBruteForceIndexSize() >= 0); 361 } do { 362 _target = target; 363 _options = options; 364 365 _testedEdges.clear(); 366 _distanceLimit = options.maxDistance(); 367 _resultSingleton = Result(); 368 369 if (_distanceLimit == DistanceT.zero()) return; 370 371 if (options.maxEdges() == Options.MAX_MAX_EDGES 372 && options.maxDistance() == DistanceT.infinity()) { 373 logger.logWarn("Returning all edges (max_edges/max_distance not set)"); 374 } 375 376 if (options.includeInteriors()) { 377 auto shape_ids = new BTree!int(); 378 target.visitContainingShapes( 379 _index, 380 (in S2Shape containing_shape, in S2Point target_point) { 381 shape_ids.insert(containing_shape.id()); 382 return shape_ids.length < options.maxEdges(); 383 }); 384 foreach (int shape_id; shape_ids) { 385 addResult(Result(DistanceT.zero(), shape_id, -1)); 386 } 387 if (_distanceLimit == DistanceT.zero()) return; 388 } 389 390 // If max_error() > 0 and the target takes advantage of this, then we may 391 // need to adjust the distance estimates to S2ShapeIndex cells to ensure 392 // that they are always a lower bound on the true distance. For example, 393 // suppose max_distance == 100, max_error == 30, and we compute the distance 394 // to the target from some cell C0 as d(C0) == 80. Then because the target 395 // takes advantage of max_error(), the true distance could be as low as 50. 396 // In order not to miss edges contained by such cells, we need to subtract 397 // max_error() from the distance estimates. This behavior is controlled by 398 // the use_conservative_cell_distance_ flag. 399 // 400 // However there is one important case where this adjustment is not 401 // necessary, namely when max_distance() < max_error(). This is because 402 // max_error() only affects the algorithm once at least max_edges() edges 403 // have been found that satisfy the given distance limit. At that point, 404 // max_error() is subtracted from distance_limit_ in order to ensure that 405 // any further matches are closer by at least that amount. But when 406 // max_distance() < max_error(), this reduces the distance limit to 0, 407 // i.e. all remaining candidate cells and edges can safely be discarded. 408 // (Note that this is how IsDistanceLess() and friends are implemented.) 409 // 410 // Note that DistanceT::Delta only supports operator==. 411 bool target_uses_max_error = (!(options.maxError() == Delta.zero()) 412 && _target.setMaxError(options.maxError())); 413 414 // Note that we can't compare max_error() and distance_limit_ directly 415 // because one is a Delta and one is a DistanceT. Instead we subtract them. 416 _useConservativeCellDistance = target_uses_max_error 417 && (_distanceLimit == DistanceT.infinity() 418 || DistanceT.zero() < _distanceLimit - options.maxError()); 419 420 // Use the brute force algorithm if the index is small enough. To avoid 421 // spending too much time counting edges when there are many shapes, we stop 422 // counting once there are too many edges. We may need to recount the edges 423 // if we later see a target with a larger brute force edge threshold. 424 int min_optimized_edges = _target.maxBruteForceIndexSize() + 1; 425 if (min_optimized_edges > _indexNumEdgesLimit && _indexNumEdges >= _indexNumEdgesLimit) { 426 _indexNumEdges = countEdgesUpTo(_index, min_optimized_edges); 427 _indexNumEdgesLimit = min_optimized_edges; 428 } 429 430 if (options.useBruteForce() || _indexNumEdges < min_optimized_edges) { 431 // The brute force algorithm considers each edge exactly once. 432 _avoidDuplicates = false; 433 findClosestEdgesBruteForce(); 434 } else { 435 // If the target takes advantage of max_error() then we need to avoid 436 // duplicate edges explicitly. (Otherwise it happens automatically.) 437 _avoidDuplicates = (target_uses_max_error && options.maxEdges() > 1); 438 findClosestEdgesOptimized(); 439 } 440 } 441 442 void findClosestEdgesBruteForce() { 443 int num_shape_ids = _index.numShapeIds(); 444 for (int id = 0; id < num_shape_ids; ++id) { 445 const(S2Shape) shape = _index.shape(id); 446 if (shape is null) continue; 447 int num_edges = shape.numEdges(); 448 for (int e = 0; e < num_edges; ++e) { 449 maybeAddResult(shape, e); 450 } 451 } 452 } 453 454 void findClosestEdgesOptimized() { 455 initQueue(); 456 // Repeatedly find the closest S2Cell to "target" and either split it into 457 // its four children or process all of its edges. 458 while (!_queue.empty()) { 459 // We need to copy the top entry before removing it, and we need to 460 // remove it before adding any new entries to the queue. 461 QueueEntry entry = _queue.front(); 462 _queue.popFront(); 463 // Work around weird parse error in gcc 4.9 by using a local variable for 464 // entry.distance. 465 DistanceT distance = entry.distance; 466 if (!(distance < _distanceLimit)) { 467 _queue.clear(); // Clear any remaining entries. 468 break; 469 } 470 // If this is already known to be an index cell, just process it. 471 if (entry.indexCell !is null) { 472 processEdges(entry); 473 continue; 474 } 475 // Otherwise split the cell into its four children. Before adding a 476 // child back to the queue, we first check whether it is empty. We do 477 // this in two seek operations rather than four by seeking to the key 478 // between children 0 and 1 and to the key between children 2 and 3. 479 S2CellId id = entry.id; 480 _iter.seek(id.child(1).rangeMin()); 481 if (!_iter.done() && _iter.id() <= id.child(1).rangeMax()) { 482 enqueueCurrentCell(id.child(1)); 483 } 484 if (_iter.prev() && _iter.id() >= id.rangeMin()) { 485 enqueueCurrentCell(id.child(0)); 486 } 487 _iter.seek(id.child(3).rangeMin()); 488 if (!_iter.done() && _iter.id() <= id.rangeMax()) { 489 enqueueCurrentCell(id.child(3)); 490 } 491 if (_iter.prev() && _iter.id() >= id.child(2).rangeMin()) { 492 enqueueCurrentCell(id.child(2)); 493 } 494 } 495 } 496 497 void initQueue() 498 in { 499 assert(_queue.empty()); 500 } do { 501 if (_indexCovering.empty()) { 502 // We delay iterator initialization until now to make queries on very 503 // small indexes a bit faster (i.e., where brute force is used). 504 _iter.initialize(_index, S2ShapeIndex.InitialPosition.UNPOSITIONED); 505 } 506 507 // Optimization: if the user is searching for just the closest edge, and the 508 // target happens to intersect an index cell, then we try to limit the search 509 // region to a small disc by first processing the edges in that cell. This 510 // sets distance_limit_ based on the closest edge in that cell, which we can 511 // then use to limit the search area. This means that the cell containing 512 // "target" will be processed twice, but in general this is still faster. 513 S2Cap cap = _target.getCapBound(); 514 if (options().maxEdges() == 1 && _iter.locate(cap.center())) { 515 processEdges(new QueueEntry(DistanceT.zero(), _iter.id(), _iter.cell())); 516 // Skip the rest of the algorithm if we found an intersecting edge. 517 if (_distanceLimit == DistanceT.zero()) return; 518 } 519 if (_indexCovering.empty()) { 520 initCovering(); 521 } 522 if (_distanceLimit == DistanceT.infinity()) { 523 // Start with the precomputed index covering. 524 for (int i = 0; i < _indexCovering.length; ++i) { 525 enqueueCell(_indexCovering[i], _indexCells[i]); 526 } 527 } else { 528 // Compute a covering of the search disc and intersect it with the 529 // precomputed index covering. 530 auto coverer = new S2RegionCoverer(); 531 coverer.mutableOptions().setMaxCells(4); 532 S1ChordAngle radius = cap.radius() + _distanceLimit.getChordAngleBound(); 533 auto search_cap = new S2Cap(cap.center(), radius); 534 coverer.getFastCovering(search_cap, _maxDistanceCovering); 535 S2CellUnion.getIntersection(_indexCovering, _maxDistanceCovering, _initialCells); 536 537 // Now we need to clean up the initial cells to ensure that they all 538 // contain at least one cell of the S2ShapeIndex. (Some may not intersect 539 // the index at all, while other may be descendants of an index cell.) 540 for (int i = 0, j = 0; i < _initialCells.length; ) { 541 S2CellId id_i = _initialCells[i]; 542 // Find the top-level cell that contains this initial cell. 543 while (_indexCovering[j].rangeMax() < id_i) ++j; 544 S2CellId id_j = _indexCovering[j]; 545 if (id_i == id_j) { 546 // This initial cell is one of the top-level cells. Use the 547 // precomputed S2ShapeIndexCell pointer to avoid an index seek. 548 enqueueCell(id_j, _indexCells[j]); 549 ++i, ++j; 550 } else { 551 // This initial cell is a proper descendant of a top-level cell. 552 // Check how it is related to the cells of the S2ShapeIndex. 553 S2ShapeIndex.CellRelation r = _iter.locate(id_i); 554 if (r == S2ShapeIndex.CellRelation.INDEXED) { 555 // This cell is a descendant of an index cell. Enqueue it and skip 556 // any other initial cells that are also descendants of this cell. 557 enqueueCell(_iter.id(), _iter.cell()); 558 const S2CellId last_id = _iter.id().rangeMax(); 559 while (++i < _initialCells.length && _initialCells[i] <= last_id) 560 continue; 561 } else { 562 // Enqueue the cell only if it contains at least one index cell. 563 if (r == S2ShapeIndex.CellRelation.SUBDIVIDED) enqueueCell(id_i, null); 564 ++i; 565 } 566 } 567 } 568 } 569 } 570 571 void initCovering() { 572 // Find the range of S2Cells spanned by the index and choose a level such 573 // that the entire index can be covered with just a few cells. These are 574 // the "top-level" cells. There are two cases: 575 // 576 // - If the index spans more than one face, then there is one top-level cell 577 // per spanned face, just big enough to cover the index cells on that face. 578 // 579 // - If the index spans only one face, then we find the smallest cell "C" 580 // that covers the index cells on that face (just like the case above). 581 // Then for each of the 4 children of "C", if the child contains any index 582 // cells then we create a top-level cell that is big enough to just fit 583 // those index cells (i.e., shrinking the child as much as possible to fit 584 // its contents). This essentially replicates what would happen if we 585 // started with "C" as the top-level cell, since "C" would immediately be 586 // split, except that we take the time to prune the children further since 587 // this will save work on every subsequent query. 588 589 // Don't need to reserve index_cells_ since it is an InlinedVector. 590 _indexCovering.reserve(6); 591 592 // TODO(ericv): Use a single iterator (iter_) below and save position 593 // information using pair<S2CellId, const S2ShapeIndexCell*> type. 594 auto next = new S2ShapeIndex.Iterator(_index, S2ShapeIndex.InitialPosition.BEGIN); 595 auto last = new S2ShapeIndex.Iterator(_index, S2ShapeIndex.InitialPosition.END); 596 last.prev(); 597 if (next.id() != last.id()) { 598 // The index has at least two cells. Choose a level such that the entire 599 // index can be spanned with at most 6 cells (if the index spans multiple 600 // faces) or 4 cells (it the index spans a single face). 601 int level = next.id().getCommonAncestorLevel(last.id()) + 1; 602 // Visit each potential top-level cell except the last (handled below). 603 S2CellId last_id = last.id().parent(level); 604 for (S2CellId id = next.id().parent(level); id != last_id; id = id.next()) { 605 // Skip any top-level cells that don't contain any index cells. 606 if (id.rangeMax() < next.id()) continue; 607 608 // Find the range of index cells contained by this top-level cell and 609 // then shrink the cell if necessary so that it just covers them. 610 S2ShapeIndex.Iterator cell_first = new S2ShapeIndex.Iterator(); 611 cell_first.copy(next); 612 next.seek(id.rangeMax().next()); 613 S2ShapeIndex.Iterator cell_last = new S2ShapeIndex.Iterator(); 614 cell_last.copy(next); 615 cell_last.prev(); 616 addInitialRange(cell_first, cell_last); 617 } 618 } 619 addInitialRange(next, last); 620 } 621 622 /** 623 * Add an entry to index_covering_ and index_cells_ that covers the given 624 * inclusive range of cells. 625 * 626 * REQUIRES: "first" and "last" have a common ancestor. 627 */ 628 void addInitialRange(S2ShapeIndex.Iterator first, in S2ShapeIndex.Iterator last) { 629 if (first.id() == last.id()) { 630 // The range consists of a single index cell. 631 _indexCovering ~= first.id(); 632 _indexCells ~= first.cell(); 633 } else { 634 // Add the lowest common ancestor of the given range. 635 int level = first.id().getCommonAncestorLevel(last.id()); 636 debug enforce(level >= 0); 637 _indexCovering ~= first.id().parent(level); 638 _indexCells ~= null; 639 } 640 } 641 642 void maybeAddResult(in S2Shape shape, int edge_id) { 643 auto testEdge = ShapeEdgeId(shape.id(), edge_id); 644 if (_avoidDuplicates && testEdge !in _testedEdges) { 645 return; 646 } 647 _testedEdges[testEdge] = true; 648 auto edge = shape.edge(edge_id); 649 DistanceT distance = _distanceLimit; 650 if (_target.updateMinDistance(edge.v0, edge.v1, distance)) { 651 addResult(Result(distance, shape.id(), edge_id)); 652 } 653 } 654 655 void addResult(in Result result) { 656 if (options().maxEdges() == 1) { 657 // Optimization for the common case where only the closest edge is wanted. 658 _resultSingleton = result; 659 _distanceLimit = result.distance - options().maxError(); 660 } else if (options().maxEdges() == Options.MAX_MAX_EDGES) { 661 _resultVector ~= result; // Sort/unique at end. 662 } else { 663 // Add this edge to result_set_. Note that even if we already have enough 664 // edges, we can't erase an element before insertion because the "new" 665 // edge might in fact be a duplicate. 666 _resultSet.insert(result); 667 int size = cast(int) _resultSet.length; 668 if (size >= options().maxEdges()) { 669 if (size > options().maxEdges()) { 670 _resultSet.remove(_resultSet.end().getValue()); 671 } 672 _distanceLimit = _resultSet.end().getValue().distance - options().maxError(); 673 } 674 } 675 } 676 677 // Process all the edges of the given index cell. 678 void processEdges(in QueueEntry entry) { 679 const(S2ShapeIndexCell) index_cell = entry.indexCell; 680 for (int s = 0; s < index_cell.numClipped(); ++s) { 681 const(S2ClippedShape) clipped = index_cell.clipped(s); 682 S2Shape shape = _index.shape(clipped.shapeId()); 683 for (int j = 0; j < clipped.numEdges(); ++j) { 684 maybeAddResult(shape, clipped.edge(j)); 685 } 686 } 687 } 688 689 // Add the given cell id to the queue. "index_cell" is the corresponding 690 // S2ShapeIndexCell, or nullptr if "id" is not an index cell. 691 void enqueueCell(in S2CellId id, in S2ShapeIndexCell index_cell) { 692 if (index_cell) { 693 // If this index cell has only a few edges, then it is faster to check 694 // them directly rather than computing the minimum distance to the S2Cell 695 // and inserting it into the queue. 696 enum int kMinEdgesToEnqueue = 10; 697 int num_edges = countEdges(index_cell); 698 if (num_edges == 0) return; 699 if (num_edges < kMinEdgesToEnqueue) { 700 // Set "distance" to zero to avoid the expense of computing it. 701 processEdges(new QueueEntry(DistanceT.zero(), id, index_cell)); 702 return; 703 } 704 } 705 // Otherwise compute the minimum distance to any point in the cell and add 706 // it to the priority queue. 707 auto cell = new S2Cell(id); 708 DistanceT distance = _distanceLimit; 709 if (!_target.updateMinDistance(cell, distance)) return; 710 if (_useConservativeCellDistance) { 711 // Ensure that "distance" is a lower bound on the true distance to the cell. 712 distance = distance - options().maxError(); // operator-=() not defined. 713 } 714 _queue.insert(new QueueEntry(distance, id, index_cell)); 715 } 716 717 // Enqueue the given cell id. 718 // REQUIRES: iter_ is positioned at a cell contained by "id". 719 void enqueueCurrentCell(S2CellId id) 720 in { 721 assert(id.contains(_iter.id())); 722 } do { 723 if (_iter.id() == id) { 724 enqueueCell(id, _iter.cell()); 725 } else { 726 enqueueCell(id, null); 727 } 728 } 729 730 // Return the number of edges in the given index cell. 731 static int countEdges(in S2ShapeIndexCell cell) { 732 int count = 0; 733 for (int s = 0; s < cell.numClipped(); ++s) { 734 count += cell.clipped(s).numEdges(); 735 } 736 return count; 737 } 738 739 S2ShapeIndex _index; 740 Options _options; 741 Target _target; 742 743 // True if max_error() must be subtracted from S2ShapeIndex cell distances 744 // in order to ensure that such distances are measured conservatively. This 745 // is true only if the target takes advantage of max_error() in order to 746 // return faster results, and 0 < max_error() < distance_limit_. 747 bool _useConservativeCellDistance; 748 749 // For the optimized algorihm we precompute the top-level S2CellIds that 750 // will be added to the priority queue. There can be at most 6 of these 751 // cells. Essentially this is just a covering of the indexed edges, except 752 // that we also store pointers to the corresponding S2ShapeIndexCells to 753 // reduce the number of index seeks required. 754 // 755 // The covering needs to be stored in a std::vector so that we can use 756 // S2CellUnion::GetIntersection(). 757 S2CellId[] _indexCovering; 758 S2ShapeIndexCell[] _indexCells; 759 760 // The decision about whether to use the brute force algorithm is based on 761 // counting the total number of edges in the index. However if the index 762 // contains a large number of shapes, this in itself might take too long. 763 // So instead we only count edges up to (max_brute_force_index_size() + 1) 764 // for the current target type (stored as index_num_edges_limit_). 765 int _indexNumEdges; 766 int _indexNumEdgesLimit; 767 768 // The distance beyond which we can safely ignore further candidate edges. 769 // (Candidates that are exactly at the limit are ignored; this is more 770 // efficient for UpdateMinDistance() and should not affect clients since 771 // distance measurements have a small amount of error anyway.) 772 // 773 // Initially this is the same as the maximum distance specified by the user, 774 // but it can also be updated by the algorithm (see MaybeAddResult). 775 DistanceT _distanceLimit; 776 777 // The current result set is stored in one of three ways: 778 // 779 // - If max_edges() == 1, the best result is kept in result_singleton_. 780 // 781 // - If max_edges() == "infinity", results are appended to result_vector_ 782 // and sorted/uniqued at the end. 783 // 784 // - Otherwise results are kept in a btree_set so that we can progressively 785 // reduce the distance limit once max_edges() results have been found. 786 // (A priority queue is not sufficient because we need to be able to 787 // check whether a candidate edge is already in the result set.) 788 Result _resultSingleton; 789 Result[] _resultVector; 790 BTree!Result _resultSet; 791 792 // When the result edges are stored in a btree_set (see above), usually 793 // duplicates can be removed simply by inserting candidate edges in the 794 // current set. However this is not true if Options::max_error() > 0 and 795 // the Target subtype takes advantage of this by returning suboptimal 796 // distances. This is because when UpdateMinDistance() is called with 797 // different "min_dist" parameters (i.e., the distance to beat), the 798 // implementation may return a different distance for the same edge. Since 799 // the btree_set is keyed by (distance, shape_id, edge_id) this can create 800 // duplicate edges in the results. 801 // 802 // The flag below is true when duplicates must be avoided explicitly. This 803 // is achieved by maintaining a separate set keyed by (shape_id, edge_id) 804 // only, and checking whether each edge is in that set before computing the 805 // distance to it. 806 bool _avoidDuplicates; 807 bool[ShapeEdgeId] _testedEdges; 808 809 // The algorithm maintains a priority queue of unprocessed S2CellIds, sorted 810 // in increasing order of distance from the target point. 811 class QueueEntry { 812 this(in DistanceT distance, in S2CellId id, in S2ShapeIndexCell indexCell) { 813 this.distance = distance; 814 this.id = id; 815 this.indexCell = indexCell; 816 } 817 818 // A lower bound on the distance from the target point to any edge point 819 // within "id". This is the key of the priority queue. 820 const(DistanceT) distance; 821 822 // The cell being queued. 823 const(S2CellId) id; 824 825 // If "id" belongs to the index, this field stores the corresponding 826 // S2ShapeIndexCell. Otherwise "id" is a proper ancestor of one or more 827 // S2ShapeIndexCells and this field stores nullptr. The purpose of this 828 // field is to avoid an extra Seek() when the queue entry is processed. 829 const(S2ShapeIndexCell) indexCell; 830 831 int opCmp(in QueueEntry other) const { 832 // The priority queue returns the largest elements first, so we want the 833 // "largest" entry to have the smallest distance. 834 return other.distance.opCmp(distance); 835 } 836 } 837 838 alias CellQueue = BinaryHeap!(QueueEntry[]); 839 CellQueue _queue; 840 841 // Temporaries, defined here to avoid multiple allocations / initializations. 842 843 S2ShapeIndex.Iterator _iter; 844 S2CellId[] _maxDistanceCovering; 845 S2CellId[] _initialCells; 846 }