Coverage Report

Created: 2026-06-13 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s2closest_edge_query_base.h
Line
Count
Source
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
// Author: ericv@google.com (Eric Veach)
17
18
#ifndef S2_S2CLOSEST_EDGE_QUERY_BASE_H_
19
#define S2_S2CLOSEST_EDGE_QUERY_BASE_H_
20
21
#include <algorithm>
22
#include <cstdint>
23
#include <cstdlib>
24
#include <iterator>
25
#include <limits>
26
#include <memory>
27
#include <optional>
28
#include <queue>
29
#include <type_traits>
30
#include <utility>
31
#include <vector>
32
33
#include "absl/container/btree_set.h"
34
#include "absl/container/flat_hash_set.h"
35
#include "absl/container/inlined_vector.h"
36
#include "absl/functional/function_ref.h"
37
#include "absl/log/absl_check.h"
38
#include "absl/log/absl_log.h"
39
#include "s2/_fp_contract_off.h"  // IWYU pragma: keep
40
#include "s2/s1angle.h"
41
#include "s2/s1chord_angle.h"
42
#include "s2/s2cap.h"
43
#include "s2/s2cell.h"
44
#include "s2/s2cell_id.h"
45
#include "s2/s2cell_union.h"
46
#include "s2/s2distance_target.h"
47
#include "s2/s2point.h"
48
#include "s2/s2region_coverer.h"
49
#include "s2/s2shape.h"
50
#include "s2/s2shape_index.h"
51
#include "s2/s2shapeutil_count_edges.h"
52
#include "s2/s2shapeutil_shape_edge_id.h"
53
54
// S2ClosestEdgeQueryBase is a templatized class for finding the closest
55
// edge(s) between two geometries.  It is not intended to be used directly,
56
// but rather to serve as the implementation of various specialized classes
57
// with more convenient APIs (such as S2ClosestEdgeQuery).  It is flexible
58
// enough so that it can be adapted to compute maximum distances and even
59
// potentially Hausdorff distances.
60
//
61
// By using the appropriate options, this class can answer questions such as:
62
//
63
//  - Find the minimum distance between two geometries A and B.
64
//  - Find all edges of geometry A that are within a distance D of geometry B.
65
//  - Find the k edges of geometry A that are closest to a given point P.
66
//
67
// You can also specify whether polygons should include their interiors (i.e.,
68
// if a point is contained by a polygon, should the distance be zero or should
69
// it be measured to the polygon boundary?)
70
//
71
// The input geometries may consist of any number of points, polylines, and
72
// polygons (collectively referred to as "shapes").  Shapes do not need to be
73
// disjoint; they may overlap or intersect arbitrarily.  The implementation is
74
// designed to be fast for both simple and complex geometries.
75
//
76
// The Distance template argument is used to represent distances.  Usually it
77
// is a thin wrapper around S1ChordAngle, but another distance type may be
78
// used as long as it implements the Distance concept described in
79
// s2distance_target.h.  For example this can be used to measure maximum
80
// distances, to get more accuracy, or to measure non-spheroidal distances.
81
template <class Distance>
82
class S2ClosestEdgeQueryBase {
83
 public:
84
  // Each "Result" object represents a closest edge.  Note the following
85
  // special cases:
86
  //
87
  //  - (shape_id() >= 0) && (edge_id() < 0) represents the interior of a shape.
88
  //    Such results may be returned when options.include_interiors() is true.
89
  //    Such results can be identified using the is_interior() method.
90
  //
91
  //  - (shape_id() < 0) && (edge_id() < 0) is returned by FindClosestEdge()
92
  //    to indicate that no edge satisfies the given query options.  Such
93
  //    results can be identified using is_empty() method.
94
  class Result {
95
   public:
96
    // The default constructor yields an empty result, with a distance() of
97
    // Infinity() and shape_id == edge_id == -1.
98
0
    Result() : distance_(Distance::Infinity()), shape_id_(-1), edge_id_(-1) {}
99
100
    // Constructs a Result object for the given arguments.
101
    Result(Distance distance, int32_t shape_id, int32_t edge_id)
102
0
        : distance_(distance), shape_id_(shape_id), edge_id_(edge_id) {}
103
104
    // The distance from the target to this edge.
105
0
    Distance distance() const { return distance_; }
106
107
    // Identifies an indexed shape.
108
0
    int32_t shape_id() const { return shape_id_; }
109
110
    // Identifies an edge within the shape.
111
0
    int32_t edge_id() const { return edge_id_; }
112
113
    // Returns true if this Result object represents the interior of a shape.
114
    // (Such results may be returned when options.include_interiors() is true.)
115
    bool is_interior() const { return shape_id_ >= 0 && edge_id_ < 0; }
116
117
    // Returns true if this Result object indicates that no edge satisfies the
118
    // given query options.  (This result is only returned in one special
119
    // case, namely when FindClosestEdge() does not find any suitable edges.
120
    // It is never returned by methods that return a vector of results.)
121
0
    bool is_empty() const { return shape_id_ < 0; }
122
123
    // Returns true if two Result objects are identical.
124
0
    friend bool operator==(const Result& x, const Result& y) {
125
0
      return (x.distance_ == y.distance_ && x.shape_id_ == y.shape_id_ &&
126
0
              x.edge_id_ == y.edge_id_);
127
0
    }
128
129
    // Compares edges first by distance, then by (shape_id, edge_id).
130
0
    friend bool operator<(const Result& x, const Result& y) {
131
0
      if (x.distance_ < y.distance_) return true;
132
0
      if (y.distance_ < x.distance_) return false;
133
0
      if (x.shape_id_ < y.shape_id_) return true;
134
0
      if (y.shape_id_ < x.shape_id_) return false;
135
0
      return x.edge_id_ < y.edge_id_;
136
0
    }
137
138
    // Indicates that linear rather than binary search should be used when this
139
    // type is used as the key in gtl::btree data structures.
140
    using absl_btree_prefer_linear_node_search = std::true_type;
141
142
   private:
143
    Distance distance_;  // The distance from the target to this edge.
144
    int32_t shape_id_;   // Identifies an indexed shape.
145
    int32_t edge_id_;    // Identifies an edge within the shape.
146
  };
147
148
  using Delta = typename Distance::Delta;
149
  using ShapeFilter = std::optional<absl::FunctionRef<bool(int)>>;
150
  using ResultVisitor = absl::FunctionRef<bool(const Result&)>;
151
152
  // Options that control the set of edges returned.  Note that by default
153
  // *all* edges are returned, so you will always want to set either the
154
  // max_results() option or the max_distance() option (or both).
155
  class Options {
156
   public:
157
    Options();
158
159
    // Specifies that at most "max_results" edges should be returned.
160
    //
161
    // REQUIRES: max_results >= 1
162
    // DEFAULT: kMaxMaxResults
163
    int max_results() const;
164
    void set_max_results(int max_results);
165
    static constexpr int kMaxMaxResults = std::numeric_limits<int>::max();
166
167
    // Specifies that only edges whose distance to the target is less than
168
    // "max_distance" should be returned.
169
    //
170
    // Note that edges whose distance is exactly equal to "max_distance" are
171
    // not returned.  In most cases this doesn't matter (since distances are
172
    // not computed exactly in the first place), but if such edges are needed
173
    // then you can retrieve them by specifying "max_distance" as the next
174
    // largest representable Distance.  For example, if Distance is an
175
    // S1ChordAngle then you can specify max_distance.Successor().
176
    //
177
    // DEFAULT: Distance::Infinity()
178
    Distance max_distance() const;
179
    void set_max_distance(Distance max_distance);
180
181
    // Specifies that edges up to max_error() further away than the true
182
    // closest edges may be substituted in the result set, as long as such
183
    // edges satisfy all the remaining search criteria (such as max_distance).
184
    // This option only has an effect if max_results() is also specified;
185
    // otherwise all edges closer than max_distance() will always be returned.
186
    //
187
    // Note that this does not affect how the distance between edges is
188
    // computed; it simply gives the algorithm permission to stop the search
189
    // early as soon as the best possible improvement drops below max_error().
190
    //
191
    // This can be used to implement distance predicates efficiently.  For
192
    // example, to determine whether the minimum distance is less than D, set
193
    // max_results() == 1 and max_distance() == max_error() == D.  This causes
194
    // the algorithm to terminate as soon as it finds any edge whose distance
195
    // is less than D, rather than continuing to search for an edge that is
196
    // even closer.
197
    //
198
    // DEFAULT: Distance::Delta::Zero()
199
    Delta max_error() const;
200
    void set_max_error(Delta max_error);
201
202
    // Specifies that polygon interiors should be included when measuring
203
    // distances.  In other words, polygons that contain the target should
204
    // have a distance of zero.  (For targets consisting of multiple connected
205
    // components, the distance is zero if any component is contained.)  This
206
    // is indicated in the results by returning a (shape_id, edge_id) pair
207
    // with edge_id == -1, i.e. this value denotes the polygons's interior.
208
    //
209
    // Note that for efficiency, any polygon that intersects the target may or
210
    // may not have an (edge_id == -1) result.  Such results are optional
211
    // because in that case the distance to the polygon is already zero.
212
    //
213
    // DEFAULT: true
214
    bool include_interiors() const;
215
    void set_include_interiors(bool include_interiors);
216
217
    // Specifies that distances should be computed by examining every edge
218
    // rather than using the S2ShapeIndex.  This is useful for testing,
219
    // benchmarking, and debugging.
220
    //
221
    // DEFAULT: false
222
    bool use_brute_force() const;
223
    void set_use_brute_force(bool use_brute_force);
224
225
   private:
226
    Distance max_distance_ = Distance::Infinity();
227
    Delta max_error_ = Delta::Zero();
228
    int max_results_ = kMaxMaxResults;
229
    bool include_interiors_ = true;
230
    bool use_brute_force_ = false;
231
  };
232
233
  // The Target class represents the geometry to which the distance is
234
  // measured.  For example, there can be subtypes for measuring the distance
235
  // to a point, an edge, or to an S2ShapeIndex (an arbitrary collection of
236
  // geometry).
237
  //
238
  // Implementations do *not* need to be thread-safe.  They may cache data or
239
  // allocate temporary data structures in order to improve performance.
240
  using Target = S2DistanceTarget<Distance>;
241
242
  // Default constructor; requires Init() to be called.
243
  S2ClosestEdgeQueryBase();
244
  ~S2ClosestEdgeQueryBase();
245
246
  // Convenience constructor that calls Init().
247
  explicit S2ClosestEdgeQueryBase(const S2ShapeIndex* index);
248
249
  // S2ClosestEdgeQueryBase is not copyable.
250
  S2ClosestEdgeQueryBase(const S2ClosestEdgeQueryBase&) = delete;
251
  void operator=(const S2ClosestEdgeQueryBase&) = delete;
252
253
  // Initializes the query.
254
  // REQUIRES: ReInit() must be called if "index" is modified.
255
  void Init(const S2ShapeIndex* index);
256
257
  // Reinitializes the query.  This method must be called whenever the
258
  // underlying index is modified.
259
  void ReInit();
260
261
  // Returns a reference to the underlying S2ShapeIndex.
262
  const S2ShapeIndex& index() const;
263
264
  // Returns the closest edges to the given target that satisfy the given
265
  // options.  This method may be called multiple times.
266
  //
267
  // Note that if options().include_interiors() is true, the result vector may
268
  // include some entries with edge_id == -1.  This indicates that the target
269
  // intersects the indexed polygon with the given shape_id.
270
  std::vector<Result> FindClosestEdges(Target* target, const Options& options,
271
                                       ShapeFilter filter = {});
272
273
  // This version can be more efficient when this method is called many times,
274
  // since it does not require allocating a new vector on each call.
275
  void FindClosestEdges(Target* target, const Options& options,
276
                        std::vector<Result>* results, ShapeFilter filter = {});
277
278
  // Calls a callback with the closest edges to the given target that satisfy
279
  // the given options.  Edges are reported in order of increasing distance.
280
  //
281
  // Updating the state that the ShapeFilter accesses while visiting is allowed
282
  // and can be used to disable reporting of results on the fly.
283
  void VisitClosestEdges(Target* target, Options options, ResultVisitor visitor,
284
                         ShapeFilter filter = {});
285
286
  // Convenience method that returns exactly one edge.  If no edges satisfy
287
  // the given search criteria, then a Result with distance == Infinity() and
288
  // shape_id == edge_id == -1 is returned.
289
  //
290
  // Note that if options.include_interiors() is true, edge_id == -1 is also
291
  // used to indicate that the target intersects an indexed polygon (but in
292
  // that case distance == Zero() and shape_id >= 0).
293
  //
294
  // REQUIRES: options.max_results() == 1
295
  Result FindClosestEdge(Target* target, const Options& options,
296
                         ShapeFilter filter = {});
297
298
 private:
299
  struct QueueEntry;
300
301
0
  const Options& options() const { return *options_; }
302
  void FindClosestEdgesInternal(Target* target, const Options& options,
303
                                std::optional<ResultVisitor> visitor = {});
304
  void FindClosestEdgesBruteForce(std::optional<ResultVisitor> visitor = {});
305
  void FindClosestEdgesOptimized(std::optional<ResultVisitor> visitor = {});
306
  void InitQueue();
307
  void InitCovering();
308
  void AddInitialRange(const S2ShapeIndex::Iterator& first,
309
                       const S2ShapeIndex::Iterator& last);
310
  void MaybeAddResult(const S2Shape& shape, int shape_id, int edge_id);
311
  void AddResult(const Result& result);
312
  void ProcessEdges(const QueueEntry& entry);
313
  void ProcessOrEnqueue(S2CellId id);
314
  void ProcessOrEnqueue(S2CellId id, const S2ShapeIndexCell* index_cell);
315
316
  // An optional call back for filtering shapes out as we scan the index.  This
317
  // is only set temporarily while a query is running.
318
  ShapeFilter shape_filter_;
319
320
  // Reports results that are less than cell_distance to the visitor.
321
  //
322
  // Returns true if result generation should continue, false otherwise.
323
0
  bool ReportResults(ResultVisitor visitor, const Distance& cell_distance) {
324
0
    auto iter = result_set_.begin();
325
0
    while (iter != result_set_.end() && iter->distance() < cell_distance) {
326
      // Re-check the shape filtering in case the user is updating it as we go.
327
0
      if (!shape_filter_ || (*shape_filter_)(iter->shape_id())) {
328
0
        if (!visitor(*iter)) {
329
0
          return false;
330
0
        }
331
0
      }
332
0
      iter = result_set_.erase(iter);
333
0
    }
334
0
    return true;
335
0
  }
336
337
  const S2ShapeIndex* index_;
338
  const Options* options_;
339
  Target* target_;
340
341
  // Indicates we're sending results to a visitor instead of accumulating them
342
  // entirely in internal storage.
343
  //
344
  // Placed here in padding intentionally to avoid increasing class size.
345
  bool visiting_ = false;
346
347
  // True if max_error() must be subtracted from priority queue cell distances
348
  // in order to ensure that such distances are measured conservatively.  This
349
  // is true only if the target takes advantage of max_error() in order to
350
  // return faster results, and 0 < max_error() < distance_limit_.
351
  bool use_conservative_cell_distance_;
352
353
  // For the optimized algorithm we precompute the top-level S2CellIds that
354
  // will be added to the priority queue.  There can be at most 6 of these
355
  // cells.  Essentially this is just a covering of the indexed edges, except
356
  // that we also store pointers to the corresponding S2ShapeIndexCells to
357
  // reduce the number of index seeks required.
358
  //
359
  // The covering needs to be stored in a std::vector so that we can use
360
  // S2CellUnion::GetIntersection().
361
  std::vector<S2CellId> index_covering_;
362
  absl::InlinedVector<const S2ShapeIndexCell*, 6> index_cells_;
363
364
  // The decision about whether to use the brute force algorithm is based on
365
  // counting the total number of edges in the index.  However if the index
366
  // contains a large number of shapes, this in itself might take too long.
367
  // So instead we only count edges up to (max_brute_force_index_size() + 1)
368
  // for the current target type (stored as index_num_edges_limit_).
369
  int index_num_edges_;
370
  int index_num_edges_limit_;
371
372
  // The distance beyond which we can safely ignore further candidate edges.
373
  // (Candidates that are exactly at the limit are ignored; this is more
374
  // efficient for UpdateMinDistance() and should not affect clients since
375
  // distance measurements have a small amount of error anyway.)
376
  //
377
  // Initially this is the same as the maximum distance specified by the user,
378
  // but it can also be updated by the algorithm (see MaybeAddResult).
379
  Distance distance_limit_;
380
381
  // The current result set is stored in one of three ways:
382
  //
383
  //  - If max_results() == 1, the best result is kept in result_singleton_.
384
  //
385
  //  - If max_results() == "infinity", results are appended to result_vector_
386
  //    and sorted/uniqued at the end.
387
  //
388
  //  - Otherwise results are kept in a btree_set so that we can progressively
389
  //    reduce the distance limit once max_results() results have been found.
390
  //    (A priority queue is not sufficient because we need to be able to
391
  //    check whether a candidate edge is already in the result set.)
392
  //
393
  // TODO(ericv): Check whether it would be faster to use avoid_duplicates_
394
  // when result_set_ is used so that we could use a priority queue instead.
395
  Result result_singleton_;
396
  std::vector<Result> result_vector_;
397
  absl::btree_set<Result> result_set_;
398
399
  // When the result edges are stored in a btree_set (see above), usually
400
  // duplicates can be removed simply by inserting candidate edges in the
401
  // current set.  However this is not true if Options::max_error() > 0 and
402
  // the Target subtype takes advantage of this by returning suboptimal
403
  // distances.  This is because when UpdateMinDistance() is called with
404
  // different "min_dist" parameters (i.e., the distance to beat), the
405
  // implementation may return a different distance for the same edge.  Since
406
  // the btree_set is keyed by (distance, shape_id, edge_id) this can create
407
  // duplicate edges in the results.
408
  //
409
  // The flag below is true when duplicates must be avoided explicitly.  This
410
  // is achieved by maintaining a separate set keyed by (shape_id, edge_id)
411
  // only, and checking whether each edge is in that set before computing the
412
  // distance to it.
413
  //
414
  // TODO(ericv): Check whether it is faster to avoid duplicates by default
415
  // (even when Options::max_results() == 1), rather than just when we need to.
416
  bool avoid_duplicates_;
417
  using ShapeEdgeId = s2shapeutil::ShapeEdgeId;
418
  absl::flat_hash_set<ShapeEdgeId> tested_edges_;
419
420
  // The algorithm maintains a priority queue of unprocessed S2CellIds, sorted
421
  // in increasing order of distance from the target.
422
  struct QueueEntry {
423
    // A lower bound on the distance from the target to "id".  This is the key
424
    // of the priority queue.
425
    Distance distance;
426
427
    // The cell being queued.
428
    S2CellId id;
429
430
    // If "id" belongs to the index, this field stores the corresponding
431
    // S2ShapeIndexCell.  Otherwise "id" is a proper ancestor of one or more
432
    // S2ShapeIndexCells and this field stores nullptr.  The purpose of this
433
    // field is to avoid an extra Seek() when the queue entry is processed.
434
    const S2ShapeIndexCell* index_cell;
435
436
    QueueEntry(Distance _distance, S2CellId _id,
437
               const S2ShapeIndexCell* _index_cell)
438
0
        : distance(_distance), id(_id), index_cell(_index_cell) {
439
0
    }
440
0
    bool operator<(const QueueEntry& other) const {
441
      // The priority queue returns the largest elements first, so we want the
442
      // "largest" entry to have the smallest distance.
443
0
      return other.distance < distance;
444
0
    }
445
  };
446
  using CellQueue =
447
      std::priority_queue<QueueEntry, absl::InlinedVector<QueueEntry, 16>>;
448
  CellQueue queue_;
449
450
  // Temporaries, defined here to avoid multiple allocations / initializations.
451
452
  S2ShapeIndex::Iterator iter_;
453
  std::vector<S2CellId> max_distance_covering_;
454
  std::vector<S2CellId> initial_cells_;
455
};
456
457
458
//////////////////   Implementation details follow   ////////////////////
459
460
template <class Distance>
461
0
inline S2ClosestEdgeQueryBase<Distance>::Options::Options() = default;
462
463
template <class Distance>
464
0
inline int S2ClosestEdgeQueryBase<Distance>::Options::max_results() const {
465
0
  return max_results_;
466
0
}
467
468
template <class Distance>
469
inline void S2ClosestEdgeQueryBase<Distance>::Options::set_max_results(
470
0
    int max_results) {
471
0
  ABSL_DCHECK_GE(max_results, 1);
472
0
  max_results_ = max_results;
473
0
}
474
475
template <class Distance>
476
inline Distance S2ClosestEdgeQueryBase<Distance>::Options::max_distance()
477
0
    const {
478
0
  return max_distance_;
479
0
}
480
481
template <class Distance>
482
inline void S2ClosestEdgeQueryBase<Distance>::Options::set_max_distance(
483
0
    Distance max_distance) {
484
0
  max_distance_ = max_distance;
485
0
}
486
487
template <class Distance>
488
inline typename Distance::Delta
489
0
S2ClosestEdgeQueryBase<Distance>::Options::max_error() const {
490
0
  return max_error_;
491
0
}
492
493
template <class Distance>
494
inline void S2ClosestEdgeQueryBase<Distance>::Options::set_max_error(
495
0
    Delta max_error) {
496
0
  max_error_ = max_error;
497
0
}
498
499
template <class Distance>
500
inline bool S2ClosestEdgeQueryBase<Distance>::Options::include_interiors()
501
0
    const {
502
0
  return include_interiors_;
503
0
}
504
505
template <class Distance>
506
inline void S2ClosestEdgeQueryBase<Distance>::Options::set_include_interiors(
507
0
    bool include_interiors) {
508
0
  include_interiors_ = include_interiors;
509
0
}
510
511
template <class Distance>
512
0
inline bool S2ClosestEdgeQueryBase<Distance>::Options::use_brute_force() const {
513
0
  return use_brute_force_;
514
0
}
515
516
template <class Distance>
517
inline void S2ClosestEdgeQueryBase<Distance>::Options::set_use_brute_force(
518
0
    bool use_brute_force) {
519
0
  use_brute_force_ = use_brute_force;
520
0
}
521
522
template <class Distance>
523
S2ClosestEdgeQueryBase<Distance>::S2ClosestEdgeQueryBase()
524
0
    : tested_edges_(/*bucket_count=*/1) {}
525
526
template <class Distance>
527
0
S2ClosestEdgeQueryBase<Distance>::~S2ClosestEdgeQueryBase() {
528
  // Prevent inline destructor bloat by providing a definition.
529
0
}
530
531
template <class Distance>
532
inline S2ClosestEdgeQueryBase<Distance>::S2ClosestEdgeQueryBase(
533
    const S2ShapeIndex* index) : S2ClosestEdgeQueryBase() {
534
  Init(index);
535
}
536
537
template <class Distance>
538
0
void S2ClosestEdgeQueryBase<Distance>::Init(const S2ShapeIndex* index) {
539
0
  index_ = index;
540
0
  ReInit();
541
0
}
542
543
template <class Distance>
544
0
void S2ClosestEdgeQueryBase<Distance>::ReInit() {
545
0
  index_num_edges_ = 0;
546
0
  index_num_edges_limit_ = 0;
547
0
  index_covering_.clear();
548
0
  index_cells_.clear();
549
  // We don't initialize iter_ here to make queries on small indexes a bit
550
  // faster (i.e., where brute force is used).
551
0
}
552
553
template <class Distance>
554
0
inline const S2ShapeIndex& S2ClosestEdgeQueryBase<Distance>::index() const {
555
0
  return *index_;
556
0
}
557
558
template <class Distance>
559
inline std::vector<typename S2ClosestEdgeQueryBase<Distance>::Result>
560
S2ClosestEdgeQueryBase<Distance>::FindClosestEdges(Target* target,
561
                                                   const Options& options,
562
0
                                                   ShapeFilter filter) {
563
0
  std::vector<Result> results;
564
0
  if (filter) {
565
0
    shape_filter_.emplace(*filter);
566
0
  }
567
0
  FindClosestEdges(target, options, &results);
568
0
  shape_filter_.reset();
569
0
  return results;
570
0
}
571
572
template <class Distance>
573
inline void S2ClosestEdgeQueryBase<Distance>::VisitClosestEdges(
574
    Target* target, Options options, ResultVisitor visitor,
575
0
    ShapeFilter filter) {
576
  // Temporarily set the visitor and shape filter, if we have one.
577
0
  if (filter) {
578
0
    shape_filter_.emplace(*filter);
579
0
  }
580
581
0
  result_set_.clear();
582
583
0
  visiting_ = true;
584
585
0
  int num_results_ = 0;
586
0
  const int max_results = options.max_results();
587
0
  FindClosestEdgesInternal(target, options, [&](const Result& result) {
588
0
    return (++num_results_ <= max_results) && visitor(result);
589
0
  });
590
591
0
  visiting_ = false;
592
593
0
  shape_filter_.reset();
594
0
}
595
596
template <class Distance>
597
typename S2ClosestEdgeQueryBase<Distance>::Result
598
S2ClosestEdgeQueryBase<Distance>::FindClosestEdge(Target* target,
599
                                                  const Options& options,
600
0
                                                  ShapeFilter filter) {
601
0
  ABSL_DCHECK_EQ(options.max_results(), 1);
602
0
  if (filter) {
603
0
    shape_filter_.emplace(*filter);
604
0
  }
605
0
  FindClosestEdgesInternal(target, options);
606
0
  shape_filter_.reset();
607
0
  return result_singleton_;
608
0
}
609
610
template <class Distance>
611
void S2ClosestEdgeQueryBase<Distance>::FindClosestEdges(
612
    Target* target, const Options& options, std::vector<Result>* results,
613
0
    ShapeFilter filter) {
614
0
  if (filter) {
615
0
    shape_filter_.emplace(*filter);
616
0
  }
617
0
  FindClosestEdgesInternal(target, options);
618
0
  shape_filter_.reset();
619
620
0
  results->clear();
621
0
  if (options.max_results() == 1) {
622
0
    if (result_singleton_.shape_id() >= 0) {
623
0
      results->push_back(result_singleton_);
624
0
    }
625
0
  } else if (options.max_results() == Options::kMaxMaxResults) {
626
0
    std::sort(result_vector_.begin(), result_vector_.end());
627
0
    std::unique_copy(result_vector_.begin(), result_vector_.end(),
628
0
                     std::back_inserter(*results));
629
0
    result_vector_.clear();
630
0
  } else {
631
0
    results->assign(result_set_.begin(), result_set_.end());
632
0
    result_set_.clear();
633
0
  }
634
0
}
635
636
template <class Distance>
637
void S2ClosestEdgeQueryBase<Distance>::FindClosestEdgesInternal(
638
    Target* target, const Options& options,
639
0
    std::optional<ResultVisitor> visitor) {
640
0
  target_ = target;
641
0
  options_ = &options;
642
643
0
  tested_edges_.clear();
644
0
  distance_limit_ = options.max_distance();
645
0
  result_singleton_ = Result();
646
0
  ABSL_DCHECK(result_vector_.empty());
647
0
  ABSL_DCHECK(result_set_.empty());
648
0
  ABSL_DCHECK_GE(target->max_brute_force_index_size(), 0);
649
0
  if (distance_limit_ == Distance::Zero()) return;
650
651
0
  if (options.max_results() == Options::kMaxMaxResults &&
652
0
      options.max_distance() == Distance::Infinity()) {
653
0
    ABSL_LOG(WARNING)
654
0
        << "Returning all edges (max_results/max_distance not set)";
655
0
  }
656
657
0
  if (options.include_interiors()) {
658
0
    absl::btree_set<int32_t> shape_ids;
659
660
0
    const size_t max_results = static_cast<size_t>(options.max_results());
661
0
    if (!shape_filter_) {
662
      // By default just insert shape ids into the output set.
663
0
      (void)target->VisitContainingShapeIds(
664
0
          *index_, [&](int id, const S2Point&) {
665
0
            shape_ids.insert(id);
666
0
            return shape_ids.size() < max_results;
667
0
          });
668
0
    } else {
669
      // If we have a shape filter, then filter shape ids before storing them.
670
0
      (void)target->VisitContainingShapeIds(
671
0
          *index_, [&](int id, const S2Point&) {
672
0
            if ((*shape_filter_)(id)) {
673
0
              shape_ids.insert(id);
674
0
            }
675
0
            return shape_ids.size() < max_results;
676
0
          });
677
0
    }
678
679
0
    const Distance kZero = Distance::Zero();
680
0
    for (int shape_id : shape_ids) {
681
0
      AddResult(Result(kZero, shape_id, -1));
682
0
    }
683
0
    if (distance_limit_ == kZero) return;
684
0
  }
685
686
  // If max_error() > 0 and the target takes advantage of this, then we may
687
  // need to adjust the distance estimates to the priority queue cells to
688
  // ensure that they are always a lower bound on the true distance.  For
689
  // example, suppose max_distance == 100, max_error == 30, and we compute the
690
  // distance to the target from some cell C0 as d(C0) == 80.  Then because
691
  // the target takes advantage of max_error(), the true distance could be as
692
  // low as 50.  In order not to miss edges contained by such cells, we need
693
  // to subtract max_error() from the distance estimates.  This behavior is
694
  // controlled by the use_conservative_cell_distance_ flag.
695
  //
696
  // However there is one important case where this adjustment is not
697
  // necessary, namely when max_distance() < max_error().  This is because
698
  // max_error() only affects the algorithm once at least max_results() edges
699
  // have been found that satisfy the given distance limit.  At that point,
700
  // max_error() is subtracted from distance_limit_ in order to ensure that
701
  // any further matches are closer by at least that amount.  But when
702
  // max_distance() < max_error(), this reduces the distance limit to 0,
703
  // i.e. all remaining candidate cells and edges can safely be discarded.
704
  // (Note that this is how IsDistanceLess() and friends are implemented.)
705
  //
706
  // Note that Distance::Delta only supports operator==.
707
0
  bool target_uses_max_error = (!(options.max_error() == Delta::Zero()) &&
708
0
                                target_->set_max_error(options.max_error()));
709
710
  // Note that we can't compare max_error() and distance_limit_ directly
711
  // because one is a Delta and one is a Distance.  Instead we subtract them.
712
0
  use_conservative_cell_distance_ = target_uses_max_error &&
713
0
      (distance_limit_ == Distance::Infinity() ||
714
0
       Distance::Zero() < distance_limit_ - options.max_error());
715
716
  // Use the brute force algorithm if the index is small enough.  To avoid
717
  // spending too much time counting edges when there are many shapes, we stop
718
  // counting once there are too many edges.  We may need to recount the edges
719
  // if we later see a target with a larger brute force edge threshold.
720
0
  int min_optimized_edges = target_->max_brute_force_index_size() + 1;
721
0
  if (min_optimized_edges > index_num_edges_limit_ &&
722
0
      index_num_edges_ >= index_num_edges_limit_) {
723
0
    index_num_edges_ = s2shapeutil::CountEdgesUpTo(*index_,
724
0
                                                   min_optimized_edges);
725
0
    index_num_edges_limit_ = min_optimized_edges;
726
0
  }
727
728
0
  if (options.use_brute_force() || index_num_edges_ < min_optimized_edges) {
729
    // The brute force algorithm considers each edge exactly once.
730
0
    avoid_duplicates_ = false;
731
0
    FindClosestEdgesBruteForce(visitor);
732
0
  } else {
733
    // If we're passing results to a visitor or the target takes advantage of
734
    // max_error() then we need to avoid duplicate edges explicitly.  (Otherwise
735
    // it happens automatically.)
736
0
    avoid_duplicates_ =
737
0
        (visitor || (target_uses_max_error && options.max_results() > 1));
738
0
    FindClosestEdgesOptimized(visitor);
739
0
  }
740
0
}
741
742
template <class Distance>
743
void S2ClosestEdgeQueryBase<Distance>::FindClosestEdgesBruteForce(
744
0
    std::optional<ResultVisitor> visitor) {
745
0
  for (int shape_id = 0; shape_id < index_->num_shape_ids(); ++shape_id) {
746
0
    const S2Shape* shape = index_->shape(shape_id);
747
0
    if (shape == nullptr) {
748
0
      continue;
749
0
    }
750
751
0
    if (!shape_filter_ || (*shape_filter_)(shape_id)) {
752
0
      int num_edges = shape->num_edges();
753
0
      for (int e = 0; e < num_edges; ++e) {
754
0
        MaybeAddResult(*shape, shape_id, e);
755
0
      }
756
0
    }
757
0
  }
758
759
  // Flush results to the visitor if we have one.
760
0
  if (visitor) {
761
0
    ReportResults(*visitor, Distance::Infinity());
762
0
  }
763
0
}
764
765
template <class Distance>
766
void S2ClosestEdgeQueryBase<Distance>::FindClosestEdgesOptimized(
767
0
    std::optional<ResultVisitor> visitor) {
768
0
  Distance last_cell_distance = Distance::Zero();
769
770
0
  InitQueue();
771
  // Repeatedly find the closest S2Cell to "target" and either split it into
772
  // its four children or process all of its edges.
773
0
  while (!queue_.empty()) {
774
    // We need to copy the top entry before removing it, and we need to
775
    // remove it before adding any new entries to the queue.
776
0
    QueueEntry entry = queue_.top();
777
0
    queue_.pop();
778
0
    if (!(entry.distance < distance_limit_)) {
779
0
      queue_ = CellQueue();  // Clear any remaining entries.
780
0
      break;
781
0
    }
782
783
    // If the cell distance has increased since we last reported results, then
784
    // we can try to report more results to the visitor.
785
0
    if (visitor && (last_cell_distance < entry.distance)) {
786
0
      last_cell_distance = entry.distance;
787
0
      if (!ReportResults(*visitor, last_cell_distance)) {
788
0
        return;
789
0
      }
790
0
    }
791
792
    // If this is already known to be an index cell, just process it.
793
0
    if (entry.index_cell != nullptr) {
794
0
      ProcessEdges(entry);
795
0
      continue;
796
0
    }
797
    // Otherwise split the cell into its four children.  Before adding a
798
    // child back to the queue, we first check whether it is empty.  We do
799
    // this in two seek operations rather than four by seeking to the key
800
    // between children 0 and 1 and to the key between children 2 and 3.
801
0
    S2CellId id = entry.id;
802
0
    iter_.Seek(id.child(1).range_min());
803
0
    if (!iter_.done() && iter_.id() <= id.child(1).range_max()) {
804
0
      ProcessOrEnqueue(id.child(1));
805
0
    }
806
0
    if (iter_.Prev() && iter_.id() >= id.range_min()) {
807
0
      ProcessOrEnqueue(id.child(0));
808
0
    }
809
0
    iter_.Seek(id.child(3).range_min());
810
0
    if (!iter_.done() && iter_.id() <= id.range_max()) {
811
0
      ProcessOrEnqueue(id.child(3));
812
0
    }
813
0
    if (iter_.Prev() && iter_.id() >= id.child(2).range_min()) {
814
0
      ProcessOrEnqueue(id.child(2));
815
0
    }
816
0
  }
817
818
  // Flush results to the visitor if we have one.
819
0
  if (visitor) {
820
0
    ReportResults(*visitor, Distance::Infinity());
821
0
  }
822
0
}
823
824
template <class Distance>
825
0
void S2ClosestEdgeQueryBase<Distance>::InitQueue() {
826
0
  ABSL_DCHECK(queue_.empty());
827
0
  if (index_covering_.empty()) {
828
    // We delay iterator initialization until now to make queries on very
829
    // small indexes a bit faster (i.e., where brute force is used).
830
0
    iter_.Init(index_, S2ShapeIndex::UNPOSITIONED);
831
0
  }
832
833
  // Optimization: if the user is searching for just the closest edge, and the
834
  // center of the target's bounding cap happens to intersect an index cell,
835
  // then we try to limit the search region to a small disc by first
836
  // processing the edges in that cell.  This sets distance_limit_ based on
837
  // the closest edge in that cell, which we can then use to limit the search
838
  // area.  This means that the cell containing "target" will be processed
839
  // twice, but in general this is still faster.
840
  //
841
  // TODO(ericv): Even if the cap center is not contained, we could still
842
  // process one or both of the adjacent index cells in S2CellId order,
843
  // provided that those cells are closer than distance_limit_.
844
0
  S2Cap cap = target_->GetCapBound();
845
0
  if (cap.is_empty()) return;  // Empty target.
846
0
  if (options().max_results() == 1 && iter_.Locate(cap.center())) {
847
0
    ProcessEdges(QueueEntry(Distance::Zero(), iter_.id(), &iter_.cell()));
848
    // Skip the rest of the algorithm if we found an intersecting edge.
849
0
    if (distance_limit_ == Distance::Zero()) return;
850
0
  }
851
0
  if (index_covering_.empty()) InitCovering();
852
0
  if (distance_limit_ == Distance::Infinity()) {
853
    // Start with the precomputed index covering.
854
0
    for (size_t i = 0; i < index_covering_.size(); ++i) {
855
0
      ProcessOrEnqueue(index_covering_[i], index_cells_[i]);
856
0
    }
857
0
  } else {
858
    // Compute a covering of the search disc and intersect it with the
859
    // precomputed index covering.
860
0
    S2RegionCoverer coverer;
861
0
    coverer.mutable_options()->set_max_cells(4);
862
0
    S1ChordAngle radius = cap.radius() + distance_limit_.GetChordAngleBound();
863
0
    S2Cap search_cap(cap.center(), radius);
864
0
    coverer.GetFastCovering(search_cap, &max_distance_covering_);
865
0
    S2CellUnion::GetIntersection(index_covering_, max_distance_covering_,
866
0
                                 &initial_cells_);
867
868
    // Now we need to clean up the initial cells to ensure that they all
869
    // contain at least one cell of the S2ShapeIndex.  (Some may not intersect
870
    // the index at all, while other may be descendants of an index cell.)
871
0
    for (size_t i = 0, j = 0; i < initial_cells_.size();) {
872
0
      S2CellId id_i = initial_cells_[i];
873
      // Find the top-level cell that contains this initial cell.
874
0
      while (index_covering_[j].range_max() < id_i) ++j;
875
0
      S2CellId id_j = index_covering_[j];
876
0
      if (id_i == id_j) {
877
        // This initial cell is one of the top-level cells.  Use the
878
        // precomputed S2ShapeIndexCell pointer to avoid an index seek.
879
0
        ProcessOrEnqueue(id_j, index_cells_[j]);
880
0
        ++i, ++j;
881
0
      } else {
882
        // This initial cell is a proper descendant of a top-level cell.
883
        // Check how it is related to the cells of the S2ShapeIndex.
884
0
        S2CellRelation r = iter_.Locate(id_i);
885
0
        if (r == S2CellRelation::INDEXED) {
886
          // This cell is a descendant of an index cell.  Enqueue it and skip
887
          // any other initial cells that are also descendants of this cell.
888
0
          ProcessOrEnqueue(iter_.id(), &iter_.cell());
889
0
          const S2CellId last_id = iter_.id().range_max();
890
0
          while (++i < initial_cells_.size() && initial_cells_[i] <= last_id)
891
0
            continue;
892
0
        } else {
893
          // Enqueue the cell only if it contains at least one index cell.
894
0
          if (r == S2CellRelation::SUBDIVIDED) ProcessOrEnqueue(id_i, nullptr);
895
0
          ++i;
896
0
        }
897
0
      }
898
0
    }
899
0
  }
900
0
}
901
902
template <class Distance>
903
0
void S2ClosestEdgeQueryBase<Distance>::InitCovering() {
904
  // Find the range of S2Cells spanned by the index and choose a level such
905
  // that the entire index can be covered with just a few cells.  These are
906
  // the "top-level" cells.  There are two cases:
907
  //
908
  //  - If the index spans more than one face, then there is one top-level cell
909
  // per spanned face, just big enough to cover the index cells on that face.
910
  //
911
  //  - If the index spans only one face, then we find the smallest cell "C"
912
  // that covers the index cells on that face (just like the case above).
913
  // Then for each of the 4 children of "C", if the child contains any index
914
  // cells then we create a top-level cell that is big enough to just fit
915
  // those index cells (i.e., shrinking the child as much as possible to fit
916
  // its contents).  This essentially replicates what would happen if we
917
  // started with "C" as the top-level cell, since "C" would immediately be
918
  // split, except that we take the time to prune the children further since
919
  // this will save work on every subsequent query.
920
921
  // Don't need to reserve index_cells_ since it is an InlinedVector.
922
0
  index_covering_.reserve(6);
923
924
  // TODO(ericv): Use a single iterator (iter_) below and save position
925
  // information using pair<S2CellId, const S2ShapeIndexCell*> type.
926
0
  S2ShapeIndex::Iterator next(index_, S2ShapeIndex::BEGIN);
927
0
  S2ShapeIndex::Iterator last(index_, S2ShapeIndex::END);
928
0
  last.Prev();
929
0
  if (next.id() != last.id()) {
930
    // The index has at least two cells.  Choose a level such that the entire
931
    // index can be spanned with at most 6 cells (if the index spans multiple
932
    // faces) or 4 cells (if the index spans a single face).
933
0
    int level = next.id().GetCommonAncestorLevel(last.id()) + 1;
934
935
    // Visit each potential top-level cell except the last (handled below).
936
0
    S2CellId last_id = last.id().parent(level);
937
0
    for (S2CellId id = next.id().parent(level); id != last_id; id = id.next()) {
938
      // Skip any top-level cells that don't contain any index cells.
939
0
      if (id.range_max() < next.id()) continue;
940
941
      // Find the range of index cells contained by this top-level cell and
942
      // then shrink the cell if necessary so that it just covers them.
943
0
      S2ShapeIndex::Iterator cell_first = next;
944
0
      next.Seek(id.range_max().next());
945
0
      S2ShapeIndex::Iterator cell_last = next;
946
0
      cell_last.Prev();
947
0
      AddInitialRange(cell_first, cell_last);
948
0
    }
949
0
  }
950
0
  AddInitialRange(next, last);
951
0
}
952
953
// Add an entry to index_covering_ and index_cells_ that covers the given
954
// inclusive range of cells.
955
//
956
// REQUIRES: "first" and "last" have a common ancestor.
957
template <class Distance>
958
void S2ClosestEdgeQueryBase<Distance>::AddInitialRange(
959
    const S2ShapeIndex::Iterator& first,
960
0
    const S2ShapeIndex::Iterator& last) {
961
0
  if (first.id() == last.id()) {
962
    // The range consists of a single index cell.
963
0
    index_covering_.push_back(first.id());
964
0
    index_cells_.push_back(&first.cell());
965
0
  } else {
966
    // Add the lowest common ancestor of the given range.
967
0
    int level = first.id().GetCommonAncestorLevel(last.id());
968
0
    ABSL_DCHECK_GE(level, 0);
969
0
    index_covering_.push_back(first.id().parent(level));
970
0
    index_cells_.push_back(nullptr);
971
0
  }
972
0
}
973
974
template <class Distance>
975
void S2ClosestEdgeQueryBase<Distance>::MaybeAddResult(const S2Shape& shape,
976
                                                      int shape_id,
977
0
                                                      int edge_id) {
978
0
  if (avoid_duplicates_ &&
979
0
      !tested_edges_.insert(ShapeEdgeId(shape_id, edge_id)).second) {
980
0
    return;
981
0
  }
982
983
0
  auto edge = shape.edge(edge_id);
984
0
  Distance distance = distance_limit_;
985
0
  if (target_->UpdateMinDistance(edge.v0, edge.v1, &distance)) {
986
0
    AddResult(Result(distance, shape_id, edge_id));
987
0
  }
988
0
}
989
990
template <class Distance>
991
0
void S2ClosestEdgeQueryBase<Distance>::AddResult(const Result& result) {
992
0
  if (!visiting_) {
993
0
    if (options().max_results() == 1) {
994
      // Optimization for the common case where only the closest edge is wanted.
995
0
      result_singleton_ = result;
996
0
      distance_limit_ = result.distance() - options().max_error();
997
0
      return;
998
0
    } else if (options().max_results() == Options::kMaxMaxResults) {
999
0
      result_vector_.push_back(result);  // Sort/unique at end.
1000
0
      return;
1001
0
    }
1002
0
  }
1003
1004
  // Add this edge to result_set_.  Note that even if we already have enough
1005
  // edges, we can't erase an element before insertion because the "new"
1006
  // edge might in fact be a duplicate.
1007
0
  result_set_.insert(result);
1008
0
  int size = result_set_.size();
1009
0
  if (size >= options().max_results()) {
1010
0
    if (size > options().max_results()) {
1011
0
      result_set_.erase(--result_set_.end());
1012
0
    }
1013
0
    distance_limit_ = (--result_set_.end())->distance() - options().max_error();
1014
0
  }
1015
0
}
1016
1017
// Return the number of edges in the given index cell.
1018
0
inline static int CountEdges(const S2ShapeIndexCell* cell) {
1019
0
  int count = 0;
1020
0
  for (int s = 0; s < cell->num_clipped(); ++s) {
1021
0
    count += cell->clipped(s).num_edges();
1022
0
  }
1023
0
  return count;
1024
0
}
Unexecuted instantiation: s2loop.cc:CountEdges(S2ShapeIndexCell const*)
Unexecuted instantiation: s2min_distance_targets.cc:CountEdges(S2ShapeIndexCell const*)
Unexecuted instantiation: s2polygon.cc:CountEdges(S2ShapeIndexCell const*)
Unexecuted instantiation: s2builder.cc:CountEdges(S2ShapeIndexCell const*)
Unexecuted instantiation: s2closest_edge_query.cc:CountEdges(S2ShapeIndexCell const*)
1025
1026
// Process all the edges of the given index cell.
1027
template <class Distance>
1028
0
void S2ClosestEdgeQueryBase<Distance>::ProcessEdges(const QueueEntry& entry) {
1029
0
  const S2ShapeIndexCell* index_cell = entry.index_cell;
1030
1031
0
  for (int s = 0; s < index_cell->num_clipped(); ++s) {
1032
0
    const S2ClippedShape& clipped = index_cell->clipped(s);
1033
1034
0
    int shape_id = clipped.shape_id();
1035
0
    if (!shape_filter_ || (*shape_filter_)(shape_id)) {
1036
0
      const S2Shape* shape = index_->shape(shape_id);
1037
0
      for (int j = 0; j < clipped.num_edges(); ++j) {
1038
0
        MaybeAddResult(*shape, shape_id, clipped.edge(j));
1039
0
      }
1040
0
    }
1041
0
  }
1042
0
}
1043
1044
// Enqueue the given cell id.
1045
// REQUIRES: iter_ is positioned at a cell contained by "id".
1046
template <class Distance>
1047
inline void S2ClosestEdgeQueryBase<Distance>::ProcessOrEnqueue(
1048
0
    S2CellId id) {
1049
0
  ABSL_DCHECK(id.contains(iter_.id()));
1050
0
  if (iter_.id() == id) {
1051
0
    ProcessOrEnqueue(id, &iter_.cell());
1052
0
  } else {
1053
0
    ProcessOrEnqueue(id, nullptr);
1054
0
  }
1055
0
}
1056
1057
// Add the given cell id to the queue.  "index_cell" is the corresponding
1058
// S2ShapeIndexCell, or nullptr if "id" is not an index cell.
1059
//
1060
// This version is called directly only by InitQueue().
1061
template <class Distance>
1062
void S2ClosestEdgeQueryBase<Distance>::ProcessOrEnqueue(
1063
0
    S2CellId id, const S2ShapeIndexCell* index_cell) {
1064
0
  if (index_cell) {
1065
    // If this index cell has only a few edges, then it is faster to check
1066
    // them directly rather than computing the minimum distance to the S2Cell
1067
    // and inserting it into the queue.
1068
0
    static const int kMinEdgesToEnqueue = 10;
1069
0
    int num_edges = CountEdges(index_cell);
1070
0
    if (num_edges == 0) return;
1071
0
    if (num_edges < kMinEdgesToEnqueue) {
1072
      // Set "distance" to zero to avoid the expense of computing it.
1073
0
      ProcessEdges(QueueEntry(Distance::Zero(), id, index_cell));
1074
0
      return;
1075
0
    }
1076
1077
    // If we have a shape filter, we can skip cells where all the shapes would
1078
    // be filtered out.
1079
0
    if (shape_filter_) {
1080
0
      bool skip_cell = true;
1081
0
      for (const S2ClippedShape& clipped : index_cell->clipped_shapes()) {
1082
0
        if ((*shape_filter_)(clipped.shape_id())) {
1083
0
          skip_cell = false;
1084
0
          break;
1085
0
        }
1086
0
      }
1087
1088
0
      if (skip_cell) {
1089
0
        return;
1090
0
      }
1091
0
    }
1092
0
  }
1093
1094
  // Otherwise compute the minimum distance to any point in the cell and add
1095
  // it to the priority queue.
1096
0
  S2Cell cell(id);
1097
0
  Distance distance = distance_limit_;
1098
0
  if (!target_->UpdateMinDistance(cell, &distance)) return;
1099
0
  if (use_conservative_cell_distance_) {
1100
    // Ensure that "distance" is a lower bound on the true distance to the cell.
1101
0
    distance = distance - options().max_error();  // operator-=() not defined.
1102
0
  }
1103
0
  queue_.push(QueueEntry(distance, id, index_cell));
1104
0
}
1105
1106
#endif  // S2_S2CLOSEST_EDGE_QUERY_BASE_H_