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 // See S2ClosestPointQueryBase (defined below) for an overview. 20 21 module s2.s2closest_point_query_base; 22 23 import s2.logger; 24 import s2.s1chord_angle; 25 import s2.s2cap; 26 import s2.s2cell; 27 import s2.s2cell_id; 28 import s2.s2cell_union; 29 import s2.s2distance_target; 30 import s2.s2edge_distances; 31 import s2.s2point; 32 import s2.s2point_index; 33 import s2.s2region; 34 import s2.s2region_coverer; 35 36 import std.algorithm : isSorted, reverse, sort, uniq; 37 import std.container.binaryheap; 38 import std.exception; 39 import std.range; 40 import std.typecons : Rebindable; 41 42 43 /** 44 * Options that control the set of points returned. Note that by default 45 * *all* points are returned, so you will always want to set either the 46 * max_points() option or the max_distance() option (or both). 47 * 48 * This class is also available as S2ClosestPointQueryBase<Data>::Options. 49 * (It is defined here to avoid depending on the "Data" template argument.) 50 * 51 * The Distance template argument is described below. 52 */ 53 class S2ClosestPointQueryBaseOptions(Distance) { 54 public: 55 alias Delta = Distance.Delta; 56 57 this() {} 58 59 /** 60 * Specifies that at most "max_points" points should be returned. 61 * 62 * REQUIRES: max_points >= 1 63 * DEFAULT: numeric_limits<int>::max() 64 */ 65 int maxPoints() const { 66 return _maxPoints; 67 } 68 69 void setMaxPoints(int max_points) 70 in { 71 assert(max_points >= 1); 72 } do { 73 _maxPoints = max_points; 74 } 75 76 enum int MAX_MAX_POINTS = int.max; 77 78 /** 79 * Specifies that only points whose distance to the target is less than 80 * "max_distance" should be returned. 81 * 82 * Note that points whose distance is exactly equal to "max_distance" are 83 * not returned. In most cases this doesn't matter (since distances are 84 * not computed exactly in the first place), but if such points are needed 85 * then you can retrieve them by specifying "max_distance" as the next 86 * largest representable Distance. For example, if Distance is an 87 * S1ChordAngle then you can specify max_distance.Successor(). 88 * 89 * DEFAULT: Distance::Infinity() 90 */ 91 Distance maxDistance() const { 92 return _maxDistance; 93 } 94 95 void setMaxDistance(Distance max_distance) { 96 _maxDistance = max_distance; 97 } 98 99 /** 100 * Specifies that points up to max_error() further away than the true 101 * closest points may be substituted in the result set, as long as such 102 * points satisfy all the remaining search criteria (such as max_distance). 103 * This option only has an effect if max_points() is also specified; 104 * otherwise all points closer than max_distance() will always be returned. 105 * 106 * Note that this does not affect how the distance between points is 107 * computed; it simply gives the algorithm permission to stop the search 108 * early as soon as the best possible improvement drops below max_error(). 109 * 110 * This can be used to implement distance predicates efficiently. For 111 * example, to determine whether the minimum distance is less than D, the 112 * IsDistanceLess() method sets max_points() == 1 and max_distance() == 113 * max_error() == D. This causes the algorithm to terminate as soon as it 114 * finds any point whose distance is less than D, rather than continuing to 115 * search for a point that is even closer. 116 * 117 * DEFAULT: Distance::Delta::Zero() 118 */ 119 Delta maxError() const { 120 return _maxError; 121 } 122 123 void setMaxError(Delta max_error) { 124 _maxError = max_error; 125 } 126 127 /** 128 * Specifies that points must be contained by the given S2Region. "region" 129 * is owned by the caller and must persist during the lifetime of this 130 * object. The value may be changed between calls to FindClosestPoints(), 131 * or reset by calling set_region(nullptr). 132 * 133 * Note that if you want to set the region to a disc around the target 134 * point, it is faster to use set_max_distance() instead. You can also call 135 * both methods, e.g. to set a maximum distance and also require that points 136 * lie within a given rectangle. 137 */ 138 inout(S2Region) region() inout { 139 return _region; 140 } 141 142 void setRegion(S2Region region) { 143 _region = region; 144 } 145 146 /** 147 * Specifies that distances should be computed by examining every point 148 * rather than using the S2ShapeIndex. This is useful for testing, 149 * benchmarking, and debugging. 150 * 151 * DEFAULT: false 152 */ 153 bool useBruteForce() const { 154 return _useBruteForce; 155 } 156 157 void setUseBruteForce(bool use_brute_force) { 158 _useBruteForce = use_brute_force; 159 } 160 161 ThisT dup(this ThisT)() { 162 ThisT d = new ThisT(); 163 d._maxDistance = _maxDistance; 164 d._maxError = _maxError; 165 d._region = _region; 166 d._maxPoints = _maxPoints; 167 d._useBruteForce = _useBruteForce; 168 return d; 169 } 170 171 override 172 string toString() const { 173 import std.conv; 174 return "S2ClosestPointQueryBaseOptions" 175 ~ "[ maxDistance=" ~ _maxDistance.to!string 176 ~ ", maxError=" ~ _maxError.to!string 177 ~ ", region=" ~ _region.to!string 178 ~ ", maxPoints=" ~ _maxPoints.to!string 179 ~ ", useBruteForce=" ~ _useBruteForce.to!string ~ " ]"; 180 } 181 182 protected: 183 Distance _maxDistance = Distance.infinity(); 184 Delta _maxError = Delta.zero(); 185 S2Region _region = null; 186 int _maxPoints = MAX_MAX_POINTS; 187 bool _useBruteForce = false; 188 } 189 190 /** 191 * S2ClosestPointQueryBase is a templatized class for finding the closest 192 * point(s) to a given target. It is not intended to be used directly, but 193 * rather to serve as the implementation of various specialized classes with 194 * more convenient APIs (such as S2ClosestPointQuery). It is flexible enough 195 * so that it can be adapted to compute maximum distances and even potentially 196 * Hausdorff distances. 197 * 198 * By using the appropriate options, this class can answer questions such as: 199 * 200 * - Find the minimum distance between a point collection A and a target B. 201 * - Find all points in collection A that are within a distance D of target B. 202 * - Find the k points of collection A that are closest to a given point P. 203 * 204 * The target is any class that implements the S2DistanceTarget interface. 205 * There are predefined targets for points, edges, S2Cells, and S2ShapeIndexes 206 * (arbitrary collctions of points, polylines, and polygons). 207 * 208 * The Distance template argument is used to represent distances. Usually 209 * this type is a thin wrapper around S1ChordAngle, but another distance type 210 * may be substituted as long as it implements the API below. This can be 211 * used to change the comparison function (e.g., to find the furthest edges 212 * from the target), or to get more accuracy if desired. 213 * 214 * The Distance concept is as follows: 215 * 216 * class Distance { 217 * public: 218 * // Default and copy constructors, assignment operator: 219 * Distance(); 220 * Distance(const Distance&); 221 * Distance& operator=(const Distance&); 222 * 223 * // Factory methods: 224 * static Distance Zero(); // Returns a zero distance. 225 * static Distance Infinity(); // Larger than any valid distance. 226 * static Distance Negative(); // Smaller than any valid distance. 227 * 228 * // Comparison operators: 229 * friend bool operator==(Distance x, Distance y); 230 * friend bool operator<(Distance x, Distance y); 231 * 232 * // Delta represents the positive difference between two distances. 233 * // It is used together with operator-() to implement Options::max_error(). 234 * // Typically Distance::Delta is simply S1ChordAngle. 235 * class Delta { 236 * public: 237 * Delta(); 238 * Delta(const Delta&); 239 * Delta& operator=(const Delta&); 240 * friend bool operator==(Delta x, Delta y); 241 * static Delta Zero(); 242 * }; 243 * 244 * // Subtraction operator. Note that the second argument represents a 245 * // delta between two distances. This distinction is important for 246 * // classes that compute maximum distances (e.g., S2FurthestEdgeQuery). 247 * friend Distance operator-(Distance x, Delta delta); 248 * 249 * // Method that returns an upper bound on the S1ChordAngle corresponding 250 * // to this Distance (needed to implement Options::max_distance 251 * // efficiently). For example, if Distance measures WGS84 ellipsoid 252 * // distance then the corresponding angle needs to be 0.56% larger. 253 * S1ChordAngle GetChordAngleBound() const; 254 * }; 255 */ 256 class S2ClosestPointQueryBase(Distance, Data) { 257 public: 258 alias Delta = Distance.Delta; 259 alias Index = S2PointIndex!Data; 260 alias PointData = Index.PointData; 261 alias Options = S2ClosestPointQueryBaseOptions!Distance; 262 263 /** 264 * The Target class represents the geometry to which the distance is 265 * measured. For example, there can be subtypes for measuring the distance 266 * to a point, an edge, or to an S2ShapeIndex (an arbitrary collection of 267 * geometry). 268 * 269 * Implementations do *not* need to be thread-safe. They may cache data or 270 * allocate temporary data structures in order to improve performance. 271 */ 272 alias Target = S2DistanceTarget!Distance; 273 274 /// Each "Result" object represents a closest point. 275 struct Result { 276 public: 277 /// Constructs a Result object for the given point. 278 this(Distance distance, PointData point_data) { 279 _distance = distance; 280 _pointData = point_data; 281 } 282 283 /** 284 * Returns true if this Result object does not refer to any data point. 285 * (The only case where an empty Result is returned is when the 286 * FindClosestPoint() method does not find any points that meet the 287 * specified criteria.) 288 */ 289 bool isEmpty() const { 290 return _pointData is null; 291 } 292 293 /// The distance from the target to this point. 294 Distance distance() const { 295 return _distance; 296 } 297 298 /// The point itself. 299 const(S2Point) point() const { 300 return _pointData.point(); 301 } 302 303 /// The client-specified data associated with this point. 304 const(Data) data() const { 305 return _pointData.data(); 306 } 307 308 /// Returns true if two Result objects are identical. 309 bool opEquals(in Result y) const { 310 return _distance == y._distance && _pointData == y._pointData; 311 } 312 313 /// Compares two Result objects first by distance, then by point_data(). 314 int opCmp(in Result y) const { 315 if (_distance < y._distance) return -1; 316 if (_distance > y._distance) return 1; 317 if (_pointData < y._pointData) return -1; 318 if (_pointData > y._pointData) return 1; 319 return 0; 320 } 321 322 private: 323 Distance _distance = Distance.infinity(); 324 PointData _pointData = null; 325 } 326 327 /** 328 * The minimum number of points that a cell must contain to enqueue it 329 * rather than processing its contents immediately. 330 */ 331 enum int MIN_POINTS_TO_ENQUEUE = 13; 332 333 /// Default constructor; requires initialize() to be called. 334 this() { 335 _queue = CellQueue(new QueueEntry[0]); 336 _resultSet = BinaryHeap!(Result[])(new Result[0]); 337 } 338 339 /// Convenience constructor that calls Init(). 340 this(Index index) { 341 this(); 342 initialize(index); 343 } 344 345 /** 346 * Initializes the query. 347 * REQUIRES: ReInit() must be called if "index" is modified. 348 */ 349 void initialize(S2PointIndex!Data index) { 350 _index = index; 351 reInitialize(); 352 } 353 354 /** 355 * Reinitializes the query. This method must be called whenever the 356 * underlying index is modified. 357 */ 358 void reInitialize() { 359 _iter.initialize(_index); 360 _indexCovering.length = 0; 361 } 362 363 /// Return a reference to the underlying S2PointIndex. 364 const(Index) index() const { 365 return _index; 366 } 367 368 /** 369 * Returns the closest points to the given target that satisfy the given 370 * options. This method may be called multiple times. 371 */ 372 Result[] findClosestPoints(Target target, Options options) { 373 Result[] results; 374 findClosestPoints(target, options, results); 375 return results; 376 } 377 378 /** 379 * This version can be more efficient when this method is called many times, 380 * since it does not require allocating a new vector on each call. 381 */ 382 void findClosestPoints(Target target, Options options, ref Result[] results) { 383 findClosestPointsInternal(target, options); 384 results.length = 0; 385 if (options.maxPoints() == 1) { 386 if (!_resultSingleton.isEmpty()) { 387 results ~= _resultSingleton; 388 } 389 } else if (options.maxPoints() == Options.MAX_MAX_POINTS) { 390 sort(_resultVector); 391 results = _resultVector.uniq.array; 392 _resultVector.length = 0; 393 } else { 394 results.reserve(_resultSet.length()); 395 for (; !_resultSet.empty(); _resultSet.popFront()) { 396 results ~= _resultSet.front(); // The highest-priority result. 397 } 398 // The priority queue returns the largest elements first. 399 reverse(results); 400 enforce(isSorted(results)); 401 } 402 } 403 404 /** 405 * Convenience method that returns exactly one point. If no points satisfy 406 * the given search criteria, then a Result with distance() == Infinity() 407 * and is_empty() == true is returned. 408 * 409 * REQUIRES: options.max_points() == 1 410 */ 411 Result findClosestPoint(Target target, Options options) 412 in { 413 assert(options.maxPoints() == 1); 414 } do { 415 findClosestPointsInternal(target, options); 416 return _resultSingleton; 417 } 418 419 private: 420 alias Iterator = Index.Iterator; 421 422 inout(Options) options() inout { 423 return _options; 424 } 425 426 void findClosestPointsInternal(Target target, Options options) { 427 _target = target; 428 _options = options; 429 430 _distanceLimit = options.maxDistance(); 431 _resultSingleton = Result(); 432 enforce(_resultVector.empty()); 433 enforce(_resultSet.empty()); 434 enforce(target.maxBruteForceIndexSize() >= 0); 435 if (_distanceLimit == Distance.zero()) return; 436 437 if (options.maxPoints() == Options.MAX_MAX_POINTS 438 && options.maxDistance() == Distance.infinity() 439 && options.region() is null) { 440 logger.logWarn("Returning all points (max_points/max_distance/region not set)"); 441 } 442 443 // Note that given point is processed only once (unlike S2ClosestEdgeQuery), 444 // and therefore we don't need to worry about the possibility of having 445 // duplicate points in the results. 446 if (options.maxError() != Delta.zero()) { 447 _target.setMaxError(options.maxError()); 448 } 449 if (options.useBruteForce() || _index.numPoints() <= _target.maxBruteForceIndexSize()) { 450 findClosestPointsBruteForce(); 451 } else { 452 findClosestPointsOptimized(); 453 } 454 } 455 456 void findClosestPointsBruteForce() { 457 for (_iter.begin(); !_iter.done(); _iter.next()) { 458 maybeAddResult(_iter.pointData()); 459 } 460 } 461 462 void findClosestPointsOptimized() { 463 initQueue(); 464 while (!_queue.empty()) { 465 // We need to copy the top entry before removing it, and we need to remove 466 // it before adding any new entries to the queue. 467 QueueEntry entry = _queue.front(); 468 _queue.popFront(); 469 // Work around weird parse error in gcc 4.9 by using a local variable for 470 // entry.distance. 471 Distance distance = entry.distance; 472 if (!(distance < _distanceLimit)) { 473 _queue.clear(); // Clear any remaining entries. 474 break; 475 } 476 S2CellId child = entry.id.childBegin(); 477 // We already know that it has too many points, so process its children. 478 // Each child may either be processed directly or enqueued again. The 479 // loop is optimized so that we don't seek unnecessarily. 480 bool seek = true; 481 for (int i = 0; i < 4; ++i, child = child.next()) { 482 seek = enqueueCell(child, _iter, seek); 483 } 484 } 485 } 486 487 void initQueue() 488 in { 489 assert(_queue.empty()); 490 } do { 491 // Optimization: rather than starting with the entire index, see if we can 492 // limit the search region to a small disc. Then we can find a covering for 493 // that disc and intersect it with the covering for the index. This can 494 // save a lot of work when the search region is small. 495 S2Cap cap = _target.getCapBound(); 496 if (options().maxPoints() == 1) { 497 // If the user is searching for just the closest point, we can compute an 498 // upper bound on search radius by seeking to the target point in the 499 // index and looking at the adjacent index points (in S2CellId order). 500 // The minimum distance to either of these points is an upper bound on the 501 // search radius. 502 // 503 // TODO(ericv): The same strategy would also work for small values of 504 // max_points() > 1, e.g. max_points() == 20, except that we would need to 505 // examine more neighbors (at least 20, and preferably 20 in each 506 // direction). It's not clear whether this is a common case, though, and 507 // also this would require extending MaybeAddResult() so that it can 508 // remove duplicate entries. (The points added here may be re-added by 509 // EnqueueCell(), but this is okay when max_points() == 1.) 510 _iter.seek(S2CellId(cap.center())); 511 if (!_iter.done()) { 512 maybeAddResult(_iter.pointData()); 513 } 514 if (_iter.prev()) { 515 maybeAddResult(_iter.pointData()); 516 } 517 // Skip the rest of the algorithm if we found a matching point. 518 if (_distanceLimit == Distance.zero()) return; 519 } 520 // We start with a covering of the set of indexed points, then intersect it 521 // with the given region (if any) and maximum search radius disc (if any). 522 if (_indexCovering.empty()) initCovering(); 523 const(S2CellId)[] initial_cells = _indexCovering; 524 if (options().region()) { 525 auto coverer = new S2RegionCoverer(); 526 coverer.mutableOptions().setMaxCells(4); 527 coverer.getCovering(options().region(), _regionCovering); 528 S2CellUnion.getIntersection(_indexCovering, _regionCovering, _intersectionWithRegion); 529 initial_cells = _intersectionWithRegion; 530 } 531 if (_distanceLimit < Distance.infinity()) { 532 auto coverer = new S2RegionCoverer(); 533 coverer.mutableOptions().setMaxCells(4); 534 S1ChordAngle radius = cap.radius() + _distanceLimit.getChordAngleBound(); 535 auto search_cap = new S2Cap(cap.center(), radius); 536 coverer.getFastCovering(search_cap, _maxDistanceCovering); 537 S2CellUnion.getIntersection( 538 initial_cells, _maxDistanceCovering, _intersectionWithMaxDistance); 539 initial_cells = _intersectionWithMaxDistance; 540 } 541 _iter.begin(); 542 for (int i = 0; i < initial_cells.length && !_iter.done(); ++i) { 543 S2CellId id = initial_cells[i]; 544 enqueueCell(id, _iter, id.rangeMin() > _iter.id() /*seek*/); 545 } 546 } 547 548 void initCovering() { 549 // Compute the "index covering", which is a small number of S2CellIds that 550 // cover the indexed points. There are two cases: 551 // 552 // - If the index spans more than one face, then there is one covering cell 553 // per spanned face, just big enough to cover the index cells on that face. 554 // 555 // - If the index spans only one face, then we find the smallest cell "C" 556 // that covers the index cells on that face (just like the case above). 557 // Then for each of the 4 children of "C", if the child contains any index 558 // cells then we create a covering cell that is big enough to just fit 559 // those index cells (i.e., shrinking the child as much as possible to fit 560 // its contents). This essentially replicates what would happen if we 561 // started with "C" as the covering cell, since "C" would immediately be 562 // split, except that we take the time to prune the children further since 563 // this will save work on every subsequent query. 564 _indexCovering.reserve(6); 565 _iter.finish(); 566 if (!_iter.prev()) return; // Empty index. 567 S2CellId index_last_id = _iter.id(); 568 _iter.begin(); 569 if (_iter.id() != index_last_id) { 570 // The index has at least two cells. Choose a level such that the entire 571 // index can be spanned with at most 6 cells (if the index spans multiple 572 // faces) or 4 cells (it the index spans a single face). 573 int level = _iter.id().getCommonAncestorLevel(index_last_id) + 1; 574 575 // Visit each potential covering cell except the last (handled below). 576 S2CellId last_id = index_last_id.parent(level); 577 for (S2CellId id = _iter.id().parent(level); id != last_id; id = id.next()) { 578 // Skip any covering cells that don't contain any index cells. 579 if (id.rangeMax() < _iter.id()) continue; 580 581 // Find the range of index cells contained by this covering cell and 582 // then shrink the cell if necessary so that it just covers them. 583 S2CellId cell_first_id = _iter.id(); 584 _iter.seek(id.rangeMax().next()); 585 _iter.prev(); 586 S2CellId cell_last_id = _iter.id(); 587 _iter.next(); 588 addInitialRange(cell_first_id, cell_last_id); 589 } 590 } 591 addInitialRange(_iter.id(), index_last_id); 592 } 593 594 /** 595 * Adds a cell to index_covering_ that covers the given inclusive range. 596 * 597 * REQUIRES: "first" and "last" have a common ancestor. 598 */ 599 void addInitialRange(S2CellId first_id, S2CellId last_id) { 600 // Add the lowest common ancestor of the given range. 601 int level = first_id.getCommonAncestorLevel(last_id); 602 debug enforce(level >= 0); 603 _indexCovering ~= first_id.parent(level); 604 } 605 606 void maybeAddResult(PointData point_data) { 607 Distance distance = _distanceLimit; 608 if (!_target.updateMinDistance(point_data.point(), distance)) return; 609 S2Region region = options().region(); 610 if (region && !region.contains(point_data.point())) return; 611 612 auto result = Result(distance, point_data); 613 if (options().maxPoints() == 1) { 614 // Optimization for the common case where only the closest point is wanted. 615 _resultSingleton = result; 616 _distanceLimit = result.distance() - options().maxError(); 617 } else if (options().maxPoints() == Options.MAX_MAX_POINTS) { 618 _resultVector ~= result; // Sort/unique at end. 619 } else { 620 // Add this point to result_set_. Note that with the current algorithm 621 // each candidate point is considered at most once (except for one special 622 // case where max_points() == 1, see InitQueue for details), so we don't 623 // need to worry about possibly adding a duplicate entry here. 624 if (_resultSet.length() >= options().maxPoints()) { 625 _resultSet.popFront(); // Replace the furthest result point. 626 } 627 _resultSet.insert(result); 628 if (_resultSet.length() >= options().maxPoints()) { 629 _distanceLimit = _resultSet.front().distance() - options().maxError(); 630 } 631 } 632 } 633 634 /** 635 * Either process the contents of the given cell immediately, or add it to the 636 * queue to be subdivided. If "seek" is false, then "iter" must already be 637 * positioned at the first indexed point within this cell. 638 * 639 * Returns "true" if the cell was added to the queue, and "false" if it was 640 * processed immediately, in which case "iter" is left positioned at the next 641 * cell in S2CellId order. 642 */ 643 bool enqueueCell(S2CellId id, ref Iterator iter, bool seek) { 644 if (seek) iter.seek(id.rangeMin()); 645 if (id.isLeaf()) { 646 // Leaf cells can't be subdivided. 647 for (; !iter.done() && iter.id() == id; iter.next()) { 648 maybeAddResult(iter.pointData()); 649 } 650 return false; // No need to seek to next child. 651 } 652 S2CellId last = id.rangeMax(); 653 int num_points = 0; 654 for (; !iter.done() && iter.id() <= last; iter.next()) { 655 if (num_points == MIN_POINTS_TO_ENQUEUE - 1) { 656 // This cell has too many points (including this one), so enqueue it. 657 auto cell = new S2Cell(id); 658 Distance distance = _distanceLimit; 659 // We check "region_" second because it may be relatively expensive. 660 if (_target.updateMinDistance(cell, distance) 661 && (!options().region() || options().region().mayIntersect(cell))) { 662 _queue.insert(QueueEntry(distance, id)); 663 } 664 return true; // Seek to next child. 665 } 666 _tmpPointData[num_points++] = iter.pointData(); 667 } 668 // There were few enough points that we might as well process them now. 669 for (int i = 0; i < num_points; ++i) { 670 maybeAddResult(_tmpPointData[i]); 671 } 672 return false; // No need to seek to next child. 673 } 674 675 /// The maximum number of points to process without subdividing further. 676 enum int MAX_LEAF_POINTS = 12; 677 678 Index _index; 679 Options _options; 680 Target _target; 681 682 /** 683 * For the optimized algorihm we precompute the top-level S2CellIds that 684 * will be added to the priority queue. There can be at most 6 of these 685 * cells. Essentially this is just a covering of the indexed points. 686 */ 687 S2CellId[] _indexCovering; 688 689 /** 690 * The distance beyond which we can safely ignore further candidate points. 691 * (Candidates that are exactly at the limit are ignored; this is more 692 * efficient for UpdateMinDistance() and should not affect clients since 693 * distance measurements have a small amount of error anyway.) 694 * 695 * Initially this is the same as the maximum distance specified by the user, 696 * but it can also be updated by the algorithm (see MaybeAddResult). 697 */ 698 Distance _distanceLimit; 699 700 /** 701 * The current result set is stored in one of three ways: 702 * 703 * - If max_points() == 1, the best result is kept in result_singleton_. 704 * 705 * - If max_points() == "infinity", results are appended to result_vector_ 706 * and sorted/uniqued at the end. 707 * 708 * - Otherwise results are kept in a priority queue so that we can 709 * progressively reduce the distance limit once max_points() results have 710 * been found. 711 */ 712 Result _resultSingleton; 713 Result[] _resultVector; 714 715 /// Used as a priority queue for the results. 716 BinaryHeap!(Result[]) _resultSet; 717 718 /** 719 * The algorithm maintains a priority queue of unprocessed S2CellIds, sorted 720 * in increasing order of distance from the target point. 721 */ 722 struct QueueEntry { 723 /// A lower bound on the distance from the target point to any point 724 /// within "id". This is the key of the priority queue. 725 Distance distance; 726 727 /// The cell being queued. 728 S2CellId id; 729 730 /// The priority queue returns the largest elements first, so we want the 731 /// "largest" entry to have the smallest distance. 732 int opCmp(in QueueEntry other) const { 733 if (distance > other.distance) 734 return -1; 735 else if (distance < other.distance) 736 return 1; 737 return 0; 738 } 739 } 740 741 alias CellQueue = BinaryHeap!(QueueEntry[]); 742 CellQueue _queue; 743 744 // Temporaries, defined here to avoid multiple allocations / initializations. 745 Iterator _iter; 746 S2CellId[] _regionCovering; 747 S2CellId[] _maxDistanceCovering; 748 S2CellId[] _intersectionWithRegion; 749 S2CellId[] _intersectionWithMaxDistance; 750 PointData[MIN_POINTS_TO_ENQUEUE - 1] _tmpPointData; 751 }