Coverage Report

Created: 2025-11-15 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s2point_index.h
Line
Count
Source
1
// Copyright 2015 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_S2POINT_INDEX_H_
19
#define S2_S2POINT_INDEX_H_
20
21
#include <cstddef>
22
#include <tuple>
23
#include <type_traits>
24
#include <utility>
25
26
#include "absl/container/btree_map.h"
27
#include "absl/log/absl_check.h"
28
29
#include "s2/_fp_contract_off.h"  // IWYU pragma: keep
30
#include "s2/s2cell_id.h"
31
#include "s2/s2cell_iterator.h"
32
#include "s2/s2point.h"
33
34
namespace s2internal {
35
// Hack to expose bytes_used.
36
template <typename Key, typename Value>
37
class BTreeMultimap : public absl::btree_multimap<Key, Value> {
38
 public:
39
0
  size_t bytes_used() const { return this->tree_.bytes_used(); }
40
};
41
}  // namespace s2internal
42
43
44
// S2PointIndex maintains an index of points sorted by leaf S2CellId.  Each
45
// point can optionally store auxiliary data such as an integer or pointer.
46
// This can be used to map results back to client data structures.
47
//
48
// The class supports adding or removing points dynamically, and provides a
49
// seekable iterator interface for navigating the index.
50
//
51
// You can use this class in conjunction with S2ClosestPointQuery to find the
52
// closest index points to a given query point.  For example:
53
//
54
// void Test(const vector<S2Point>& index_points,
55
//           const vector<S2Point>& target_points) {
56
//   // The template argument allows auxiliary data to be attached to each
57
//   // point (in this case, the array index).
58
//   S2PointIndex<int> index;
59
//   for (int i = 0; i < index_points.size(); ++i) {
60
//     index.Add(index_points[i], i);
61
//   }
62
//   S2ClosestPointQuery<int> query(&index);
63
//   query.mutable_options()->set_max_results(5);
64
//   for (const S2Point& target_point : target_points) {
65
//     S2ClosestPointQueryPointTarget target(target_point);
66
//     for (const auto& result : query.FindClosestPoints(&target)) {
67
//       // The Result class contains the following methods:
68
//       //   distance() is the distance to the target.
69
//       //   point() is the indexed point.
70
//       //   data() is the auxiliary data.
71
//       DoSomething(target_point, result);
72
//     }
73
//   }
74
// }
75
//
76
// The Data argument defaults to an empty class, which uses no additional
77
// space beyond the S2Point itself.  In this case the Data argument is
78
// required.  For example:
79
//
80
//   S2PointIndex<> index;
81
//   index.Add(point);
82
//
83
// Points can be added or removed from the index at any time by calling Add()
84
// or Remove().  However when the index is modified, you must call Init() on
85
// each iterator before using it again (or simply create a new iterator).
86
//
87
//   index.Add(new_point, 123456);
88
//   it.Init(&index);
89
//   it.Seek(target.range_min());
90
//
91
// You can also access the index directly using the iterator interface.  For
92
// example, here is how to iterate through all the points in a given S2CellId
93
// "target_id":
94
//
95
//   S2PointIndex<int>::Iterator it(&index);
96
//   it.Seek(target_id.range_min());
97
//   for (; !it.done() && it.id() <= target_id.range_max(); it.Next()) {
98
//     DoSomething(it.id(), it.point(), it.data());
99
//   }
100
//
101
// TODO(ericv): Consider adding an S2PointIndexRegion class, which could be
102
// used to efficiently compute coverings of a collection of S2Points.
103
//
104
// REQUIRES: "Data" has default and copy constructors.
105
// REQUIRES: "Data" has operator== and operator<.
106
template <class Data = std::tuple<> /*empty class*/>
107
class S2PointIndex {
108
 public:
109
  // PointData is essentially std::pair with named fields.  It stores an
110
  // S2Point and its associated data, taking advantage of the "empty base
111
  // optimization" to ensure that no extra space is used when Data is empty.
112
  class PointData {
113
   public:
114
    PointData() = default;  // Needed by STL
115
0
    PointData(const S2Point& point, const Data& data) : tuple_(point, data) {}
116
117
0
    const S2Point& point() const { return std::get<0>(tuple_); }
118
0
    const Data& data() const { return std::get<1>(tuple_); }
119
120
    friend bool operator==(const PointData& x, const PointData& y) {
121
      return x.tuple_ == y.tuple_;
122
    }
123
    friend bool operator<(const PointData& x, const PointData& y) {
124
      return x.tuple_ < y.tuple_;
125
    }
126
127
   private:
128
    // Note that std::tuple has special template magic to ensure that Data
129
    // doesn't take up any space when it is empty.  (This is not true if you
130
    // simply declare a member of type Data.)
131
    std::tuple<S2Point, Data> tuple_;
132
  };
133
134
  // Default constructor.
135
  // TODO(b/252809194): Move definition back to .cc file.
136
0
  S2PointIndex() = default;
137
138
  // Returns the number of points in the index.
139
  int num_points() const;
140
141
  // Adds the given point to the index.  Invalidates all iterators.
142
  void Add(const S2Point& point, const Data& data);
143
  void Add(const PointData& point_data);
144
145
  // Convenience function for the case when Data is an empty class.
146
  void Add(const S2Point& point);
147
148
  // Removes the given point from the index.  Both the "point" and "data"
149
  // fields must match the point to be removed.  Returns false if the given
150
  // point was not present.  Invalidates all iterators.
151
  bool Remove(const S2Point& point, const Data& data);
152
  bool Remove(const PointData& point_data);
153
154
  // Convenience function for the case when Data is an empty class.
155
  void Remove(const S2Point& point);
156
157
  // Resets the index to its original empty state.  Invalidates all iterators.
158
  void Clear();
159
160
  // Returns the number of bytes currently occupied by the index.
161
  size_t SpaceUsed() const;
162
163
 private:
164
  // Defined here because the Iterator class below uses it.
165
  using Map = s2internal::BTreeMultimap<S2CellId, PointData>;
166
167
 public:
168
  class Iterator final : public S2CellIterator {
169
   public:
170
    // Default constructor; must be followed by a call to Init().
171
    Iterator();
172
173
    // Convenience constructor that calls Init().
174
    explicit Iterator(const S2PointIndex* index);
175
176
    // Initializes an iterator for the given S2PointIndex.  If the index is
177
    // non-empty, the iterator is positioned at the first cell.
178
    //
179
    // This method may be called multiple times, e.g. to make an iterator
180
    // valid again after the index is modified.
181
    void Init(const S2PointIndex* index);
182
183
    // The S2CellId for the current index entry.
184
    // REQUIRES: !done()
185
    S2CellId id() const override;
186
187
    // The point associated with the current index entry.
188
    // REQUIRES: !done()
189
    const S2Point& point() const;
190
191
    // The client-supplied data associated with the current index entry.
192
    // REQUIRES: !done()
193
    const Data& data() const;
194
195
    // The (S2Point, data) pair associated with the current index entry.
196
    const PointData& point_data() const;
197
198
    // Returns true if the iterator is positioned past the last index entry.
199
    bool done() const override;
200
201
    // Positions the iterator at the first index entry (if any).
202
    void Begin() override;
203
204
    // Positions the iterator so that done() is true.
205
    void Finish() override;
206
207
    // Advances the iterator to the next index entry.
208
    // REQUIRES: !done()
209
    void Next() override;
210
211
    // If the iterator is already positioned at the beginning, returns false.
212
    // Otherwise positions the iterator at the previous entry and returns true.
213
    bool Prev() override;
214
215
    // Positions the iterator at the first entry with id() >= target, or at the
216
    // end of the index if no such entry exists.
217
    void Seek(S2CellId target) override;
218
219
    // Positions the iterator at the cell containing target and returns true. If
220
    // no such cell exists, return false and leave the iterator in an undefined
221
    // (but valid) state.
222
0
    bool Locate(const S2Point& target) override {
223
0
      return LocateImpl(*this, target);
224
0
    }
225
226
    // Let T be the target S2CellId.  If T is contained by some index cell I
227
    // (including equality), this method positions the iterator at I and returns
228
    // INDEXED.  Otherwise if T contains one or more (smaller) index cells, it
229
    // positions the iterator at the first such cell I and returns SUBDIVIDED.
230
    // Otherwise it returns DISJOINT and leaves the iterator in an undefined
231
    // (but valid) state.
232
0
    S2CellRelation Locate(S2CellId target) override {
233
0
      return LocateImpl(*this, target);
234
0
    }
235
236
   private:
237
    const Map* map_ = nullptr;
238
    typename Map::const_iterator iter_, end_;
239
  };
240
241
 private:
242
  friend class Iterator;
243
  Map map_;
244
245
  S2PointIndex(const S2PointIndex&) = delete;
246
  void operator=(const S2PointIndex&) = delete;
247
};
248
249
250
//////////////////   Implementation details follow   ////////////////////
251
252
template <class Data>
253
0
inline int S2PointIndex<Data>::num_points() const {
254
0
  return map_.size();
255
0
}
256
257
template <class Data>
258
0
void S2PointIndex<Data>::Add(const PointData& point_data) {
259
0
  S2CellId id(point_data.point());
260
0
  map_.insert(std::make_pair(id, point_data));
261
0
}
262
263
template <class Data>
264
0
void S2PointIndex<Data>::Add(const S2Point& point, const Data& data) {
265
0
  Add(PointData(point, data));
266
0
}
267
268
template <class Data>
269
void S2PointIndex<Data>::Add(const S2Point& point) {
270
  static_assert(std::is_empty<Data>::value, "Data must be empty");
271
  Add(point, {});
272
}
273
274
template <class Data>
275
bool S2PointIndex<Data>::Remove(const PointData& point_data) {
276
  S2CellId id(point_data.point());
277
  for (typename Map::iterator it = map_.lower_bound(id), end = map_.end();
278
       it != end && it->first == id; ++it) {
279
    if (it->second == point_data) {
280
      map_.erase(it);
281
      return true;
282
    }
283
  }
284
  return false;
285
}
286
287
template <class Data>
288
bool S2PointIndex<Data>::Remove(const S2Point& point, const Data& data) {
289
  return Remove(PointData(point, data));
290
}
291
292
template <class Data>
293
void S2PointIndex<Data>::Remove(const S2Point& point) {
294
  static_assert(std::is_empty<Data>::value, "Data must be empty");
295
  Remove(point, {});
296
}
297
298
template <class Data>
299
void S2PointIndex<Data>::Clear() {
300
  map_.clear();
301
}
302
303
template <class Data>
304
0
size_t S2PointIndex<Data>::SpaceUsed() const {
305
0
  return sizeof(*this) - sizeof(map_) + map_.bytes_used();
306
0
}
307
308
template <class Data>
309
0
inline S2PointIndex<Data>::Iterator::Iterator() = default;
310
311
template <class Data>
312
inline S2PointIndex<Data>::Iterator::Iterator(
313
    const S2PointIndex<Data>* index) {
314
  Init(index);
315
}
316
317
template <class Data>
318
inline void S2PointIndex<Data>::Iterator::Init(
319
0
    const S2PointIndex<Data>* index) {
320
0
  map_ = &index->map_;
321
0
  iter_ = map_->begin();
322
0
  end_ = map_->end();
323
0
}
324
325
template <class Data>
326
0
inline S2CellId S2PointIndex<Data>::Iterator::id() const {
327
0
  ABSL_DCHECK(!done());
328
0
  return iter_->first;
329
0
}
330
331
template <class Data>
332
inline const S2Point& S2PointIndex<Data>::Iterator::point() const {
333
  ABSL_DCHECK(!done());
334
  return iter_->second.point();
335
}
336
337
template <class Data>
338
inline const Data& S2PointIndex<Data>::Iterator::data() const {
339
  ABSL_DCHECK(!done());
340
  return iter_->second.data();
341
}
342
343
template <class Data>
344
inline const typename S2PointIndex<Data>::PointData&
345
0
S2PointIndex<Data>::Iterator::point_data() const {
346
0
  ABSL_DCHECK(!done());
347
0
  return iter_->second;
348
0
}
349
350
template <class Data>
351
0
inline bool S2PointIndex<Data>::Iterator::done() const {
352
0
  return iter_ == end_;
353
0
}
354
355
template <class Data>
356
0
inline void S2PointIndex<Data>::Iterator::Begin() {
357
0
  iter_ = map_->begin();
358
0
}
359
360
template <class Data>
361
0
inline void S2PointIndex<Data>::Iterator::Finish() {
362
0
  iter_ = end_;
363
0
}
364
365
template <class Data>
366
0
inline void S2PointIndex<Data>::Iterator::Next() {
367
0
  ABSL_DCHECK(!done());
368
0
  ++iter_;
369
0
}
370
371
template <class Data>
372
0
inline bool S2PointIndex<Data>::Iterator::Prev() {
373
0
  if (iter_ == map_->begin()) return false;
374
0
  --iter_;
375
0
  return true;
376
0
}
377
378
template <class Data>
379
0
inline void S2PointIndex<Data>::Iterator::Seek(S2CellId target) {
380
0
  iter_ = map_->lower_bound(target);
381
0
}
382
383
#endif  // S2_S2POINT_INDEX_H_