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 // Original Author: ericv@google.com (Eric Veach) 17 // Converted to D: madric@gmail.com (Vijay Nayar) 18 19 module s2.s2point_index; 20 21 import s2.s2cell_id; 22 import s2.s2point; 23 import s2.util.container.btree_map; 24 25 26 /** 27 * S2PointIndex maintains an index of points sorted by leaf S2CellId. Each 28 * point has some associated client-supplied data, such as an integer or 29 * pointer. This can be used to map results back to client data structures. 30 * 31 * The class supports adding or removing points dynamically, and provides a 32 * seekable iterator interface for navigating the index. 33 * 34 * You can use this class in conjuction with S2ClosestPointQuery to find the 35 * closest index points to a given query point. For example: 36 * 37 * void Test(const vector<S2Point>& points, const S2Point& target) { 38 * // The template argument allows auxiliary data to be attached to each 39 * // point (in this case, the array index). 40 * S2PointIndex<int> index; 41 * for (int i = 0; i < points.size(); ++i) { 42 * index.Add(points[i], i); 43 * } 44 * S2ClosestPointQuery<int> query(&index); 45 * query.FindClosestPoint(target); 46 * if (query.num_points() > 0) { 47 * // query.point(0) is the closest point (result 0). 48 * // query.distance(0) is the distance to the target. 49 * // query.data(0) is the auxiliary data (the array index set above). 50 * DoSomething(query.point(0), query.data(0), query.distance(0)); 51 * } 52 * } 53 * 54 * Alternatively, you can access the index directly using the iterator 55 * interface. For example, here is how to iterate through all the points in a 56 * given S2CellId "target_id": 57 * 58 * S2PointIndex<int>::Iterator it(&index); 59 * it.Seek(target_id.range_min()); 60 * for (; !it.done() && it.id() <= target_id.range_max(); it.Next()) { 61 * DoSomething(it.id(), it.point(), it.data()); 62 * } 63 * 64 * Points can be added or removed from the index at any time by calling Add() 65 * or Remove(). However when the index is modified, you must call Init() on 66 * each iterator before using it again (or simply create a new iterator). 67 * 68 * index.Add(new_point, 123456); 69 * it.Init(&index); 70 * it.Seek(target.range_min()); 71 * 72 * TODO(ericv): Make this a subtype of S2Region, so that it can also be used 73 * to efficiently compute coverings of a collection of S2Points. 74 * 75 * REQUIRES: "Data" has default and copy constructors. 76 * REQUIRES: "Data" has operator== and operator<. 77 */ 78 class S2PointIndex(DataT) { 79 public: 80 // PointData is essentially std::pair with named fields. It stores an 81 // S2Point and its associated client data. 82 static class PointData { 83 public: 84 85 this(in S2Point point, in DataT data) { 86 _point = point; 87 _data = data; 88 } 89 90 S2Point point() const { 91 return _point; 92 } 93 94 const(DataT) data() const { 95 return _data; 96 } 97 98 bool opEquals(in PointData other) const { 99 return _point == other._point && _data == other._data; 100 } 101 102 // Not required by S2PointIndex but useful for tests. 103 int opCmp(in PointData other) const { 104 if (_point < other._point) return -1; 105 if (_point != other._point) return 1; 106 if (_data < other._data) return -1; 107 if (_data != other._data) return 1; 108 return 0; 109 } 110 111 override 112 string toString() const { 113 import std.conv; 114 return "PointData[point=" ~ point.toString ~ ", data=" ~ to!string(data) ~ "]"; 115 } 116 117 private: 118 S2Point _point; 119 DataT _data; 120 } 121 122 // Default constructor. 123 this() { 124 _map = new Map(); 125 } 126 127 // Returns the number of points in the index. 128 int numPoints() const { 129 return cast(int) _map.length; 130 } 131 132 // Adds the given point to the index. Invalidates all iterators. 133 void add(in S2Point point, in DataT data) { 134 add(new PointData(point, data)); 135 } 136 137 void add(PointData point_data) { 138 auto id = S2CellId(point_data.point()); 139 _map.insert(id, point_data); 140 } 141 142 // Removes the given point from the index. Both the "point" and "data" 143 // fields must match the point to be removed. Returns false if the given 144 // point was not present. Invalidates all iterators. 145 bool remove(in S2Point point, in DataT data) { 146 return remove(new PointData(point, data)); 147 } 148 149 bool remove(in PointData point_data) { 150 auto id = S2CellId(point_data.point()); 151 foreach (Map.Pair entry; _map.equalRange(id)) { 152 if (entry.value == point_data) { 153 _map.remove(entry.key); 154 return true; 155 } 156 } 157 return false; 158 } 159 160 // Resets the index to its original empty state. Invalidates all iterators. 161 void clear() { 162 _map.clear(); 163 } 164 165 166 private: 167 // Defined here because the Iterator class below uses it. 168 alias Map = BTreeMap!(S2CellId, PointData); 169 170 public: 171 static struct Iterator { 172 public: 173 // Default constructor; must be followed by a call to Init(). 174 //this() { } 175 176 // Convenience constructor that calls Init(). 177 this(S2PointIndex index) { 178 initialize(index); 179 } 180 181 // Initializes an iterator for the given S2PointIndex. If the index is 182 // non-empty, the iterator is positioned at the first cell. 183 // 184 // This method may be called multiple times, e.g. to make an iterator 185 // valid again after the index is modified. 186 void initialize(S2PointIndex index) { 187 _map = index._map; 188 _iter = _map.begin(); 189 _end = _map.end(); 190 } 191 192 // The S2CellId for the current index entry. 193 // REQUIRES: !done() 194 S2CellId id() const 195 in { 196 assert(!done()); 197 } do { 198 return _iter.getValue().key; 199 } 200 201 // The point associated with the current index entry. 202 // REQUIRES: !done() 203 const(S2Point) point() const 204 in { 205 assert(!done()); 206 } do { 207 return _iter.getValue().value.point(); 208 } 209 210 // The client-supplied data associated with the current index entry. 211 // REQUIRES: !done() 212 inout(DataT) data() inout 213 in { 214 assert(!done()); 215 } do { 216 return _iter.getValue().value.data(); 217 } 218 219 220 // The (S2Point, data) pair associated with the current index entry. 221 inout(PointData) pointData() inout 222 in { 223 assert(!done()); 224 } do { 225 return _iter.getValue().value; 226 } 227 228 // Returns true if the iterator is positioned past the last index entry. 229 bool done() const { 230 return _iter == _end; 231 } 232 233 // Positions the iterator at the first index entry (if any). 234 void begin() { 235 _iter = _map.begin(); 236 } 237 238 // Positions the iterator so that done() is true. 239 void finish() { 240 _iter = _end; 241 } 242 243 // Advances the iterator to the next index entry. 244 // REQUIRES: !done() 245 void next() 246 in { 247 assert(!done()); 248 } do { 249 ++_iter; 250 } 251 252 // If the iterator is already positioned at the beginning, returns false. 253 // Otherwise positions the iterator at the previous entry and returns true. 254 bool prev() { 255 if (_iter == _map.begin()) return false; 256 --_iter; 257 return true; 258 } 259 260 // Positions the iterator at the first entry with id() >= target, or at the 261 // end of the index if no such entry exists. 262 void seek(S2CellId target) { 263 _iter = _map.equalRange(target).toIterator(); 264 } 265 266 private: 267 Map _map; 268 Map.Iterator _iter, _end; 269 } 270 271 private: 272 Map _map; 273 }