1 // Copyright 2017 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS-IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 16 // Original Author: ericv@google.com (Eric Veach) 17 // Converted to D: madric@gmail.com (Vijay Nayar) 18 19 module s2.s2shape_index_region; 20 21 import s2.r2point; 22 import s2.r2rect; 23 import s2.s2cap; 24 import s2.s2cell; 25 import s2.s2cell_id; 26 import s2.s2cell_union; 27 import s2.s2contains_point_query; 28 import s2.s2edge_clipping 29 : clipToPaddedFace, intersectsRect, FACE_CLIP_ERROR_UV_COORD, INTERSECTS_RECT_ERROR_UV_DIST; 30 import s2.s2edge_crosser; 31 import s2.s2latlng_rect; 32 import s2.s2point; 33 import s2.s2region; 34 import s2.s2shape; 35 import s2.s2shape_index; 36 37 import std.exception : enforce; 38 39 /** 40 * This class wraps an S2ShapeIndex object with the additional methods needed 41 * to implement the S2Region API, in order to allow S2RegionCoverer to compute 42 * S2CellId coverings of arbitrary collections of geometry. 43 * 44 * These methods could conceivably be made part of S2ShapeIndex itself, but 45 * there are several advantages to having a separate class: 46 * 47 * - The class can be templated in order to avoid virtual calls and memory 48 * allocation (for iterators) when the concrete S2ShapeIndex type is known. 49 * 50 * - Implementing these methods efficiently requires an S2ShapeIndex iterator, 51 * and this design allows a single iterator to be allocated and reused. 52 * 53 * - S2Region::Clone() is not a good fit for the S2ShapeIndex API because 54 * it can't be implemented for some subtypes (e.g., EncodedS2ShapeIndex). 55 * 56 * Example usage: 57 * 58 * S2CellUnion GetCovering(const S2ShapeIndex& index) { 59 * S2RegionCoverer coverer; 60 * coverer.mutable_options()->set_max_cells(20); 61 * S2CellUnion covering; 62 * coverer.GetCovering(MakeS2ShapeIndexRegion(&index), &covering); 63 * return covering; 64 * } 65 * 66 * This class is not thread-safe. To use it in parallel, each thread should 67 * construct its own instance (this is not expensive). 68 */ 69 final class S2ShapeIndexRegion(IndexT) : S2Region { 70 public: 71 // Rather than calling this constructor, which requires specifying the 72 // S2ShapeIndex type explicitly, the preferred idiom is to call 73 // MakeS2ShapeIndexRegion(&index) instead. For example: 74 // 75 // coverer.GetCovering(MakeS2ShapeIndexRegion(&index), &covering); 76 this(IndexT index) { 77 _containsQuery = new S2ContainsPointQuery!IndexT(index); 78 _iter = _containsQuery.mutableIter(); 79 } 80 81 inout(IndexT) index() inout { 82 return _containsQuery.index(); 83 } 84 85 //////////////////////////////////////////////////////////////////////// 86 // S2Region interface (see s2region.h for details): 87 88 // Clone() returns a *shallow* copy; it does not make a copy of the 89 // underlying S2ShapeIndex. 90 override 91 S2ShapeIndexRegion!IndexT clone() { 92 return new S2ShapeIndexRegion!IndexT(index()); 93 } 94 95 override 96 S2Cap getCapBound() { 97 S2CellId[] covering; 98 getCellUnionBound(covering); 99 return new S2CellUnion(covering).getCapBound(); 100 } 101 102 override 103 S2LatLngRect getRectBound() { 104 S2CellId[] covering; 105 getCellUnionBound(covering); 106 return new S2CellUnion(covering).getRectBound(); 107 } 108 109 // This method currently returns at most 4 cells, unless the index spans 110 // multiple faces in which case it may return up to 6 cells. 111 override 112 void getCellUnionBound(out S2CellId[] cell_ids) { 113 // We find the range of S2Cells spanned by the index and choose a level such 114 // that the entire index can be covered with just a few cells. There are 115 // two cases: 116 // 117 // - If the index intersects two or more faces, then for each intersected 118 // face we add one cell to the covering. Rather than adding the entire 119 // face, instead we add the smallest S2Cell that covers the S2ShapeIndex 120 // cells within that face. 121 // 122 // - If the index intersects only one face, then we first find the smallest 123 // cell S that contains the index cells (just like the case above). 124 // However rather than using the cell S itself, instead we repeat this 125 // process for each of its child cells. In other words, for each 126 // child cell C we add the smallest S2Cell C' that covers the index cells 127 // within C. This extra step is relatively cheap and produces much 128 // tighter coverings when the S2ShapeIndex consists of a small region 129 // near the center of a large S2Cell. 130 // 131 // The following code uses only a single Iterator object because creating an 132 // Iterator may be relatively expensive for some S2ShapeIndex types (e.g., 133 // it may involve memory allocation). 134 cell_ids.length = 0; 135 cell_ids.reserve(6); 136 137 // Find the last S2CellId in the index. 138 _iter.finish(); 139 if (!_iter.prev()) return; // Empty index. 140 const(S2CellId) last_index_id = _iter.id(); 141 _iter.begin(); 142 if (_iter.id() != last_index_id) { 143 // The index has at least two cells. Choose an S2CellId level such that 144 // the entire index can be spanned with at most 6 cells (if the index 145 // spans multiple faces) or 4 cells (it the index spans a single face). 146 int level = _iter.id().getCommonAncestorLevel(last_index_id) + 1; 147 148 // For each cell C at the chosen level, we compute the smallest S2Cell 149 // that covers the S2ShapeIndex cells within C. 150 const(S2CellId) last_id = last_index_id.parent(level); 151 for (auto id = _iter.id().parent(level); id != last_id; id = id.next()) { 152 // If the cell C does not contain any index cells, then skip it. 153 if (id.rangeMax() < _iter.id()) continue; 154 155 // Find the range of index cells contained by C and then shrink C so 156 // that it just covers those cells. 157 S2CellId first = _iter.id(); 158 _iter.seek(id.rangeMax().next()); 159 _iter.prev(); 160 coverRange(first, _iter.id(), cell_ids); 161 _iter.next(); 162 } 163 } 164 coverRange(_iter.id(), last_index_id, cell_ids); 165 } 166 167 168 // Returns true if "target" is contained by any single shape. If the cell 169 // is covered by a union of different shapes then it may return false. 170 // 171 // The implementation is conservative but not exact; if a shape just barely 172 // contains the given cell then it may return false. The maximum error is 173 // less than 10 * DBL_EPSILON radians (or about 15 nanometers). 174 override 175 bool contains(in S2Cell target) { 176 S2ShapeIndex.CellRelation relation = _iter.locate(target.id()); 177 178 // If the relation is DISJOINT, then "target" is not contained. Similarly if 179 // the relation is SUBDIVIDED then "target" is not contained, since index 180 // cells are subdivided only if they (nearly) intersect too many edges. 181 if (relation != S2ShapeIndex.CellRelation.INDEXED) return false; 182 183 // Otherwise, the iterator points to an index cell containing "target". 184 // If any shape contains the target cell, we return true. 185 enforce(_iter.id().contains(target.id())); 186 const(S2ShapeIndexCell) cell = _iter.cell(); 187 for (int s = 0; s < cell.numClipped(); ++s) { 188 const(S2ClippedShape) clipped = cell.clipped(s); 189 // The shape contains the target cell iff the shape contains the cell 190 // center and none of its edges intersects the (padded) cell interior. 191 if (_iter.id() == target.id()) { 192 if (clipped.numEdges() == 0 && clipped.containsCenter()) return true; 193 } else { 194 // It is faster to call AnyEdgeIntersects() before Contains(). 195 if (index().shape(clipped.shapeId()).hasInterior() 196 && !anyEdgeIntersects(clipped, target) 197 && _containsQuery.shapeContains(_iter, clipped, target.getCenter())) { 198 return true; 199 } 200 } 201 } 202 return false; 203 } 204 205 // Returns true if any shape intersects "target". 206 // 207 // The implementation is conservative but not exact; if a shape is just 208 // barely disjoint from the given cell then it may return true. The maximum 209 // error is less than 10 * DBL_EPSILON radians (or about 15 nanometers). 210 override 211 bool mayIntersect(in S2Cell target) { 212 S2ShapeIndex.CellRelation relation = _iter.locate(target.id()); 213 214 // If "target" does not overlap any index cell, there is no intersection. 215 if (relation == S2ShapeIndex.CellRelation.DISJOINT) return false; 216 217 // If "target" is subdivided into one or more index cells, then there is an 218 // intersection to within the S2ShapeIndex error bound. 219 if (relation == S2ShapeIndex.CellRelation.SUBDIVIDED) return true; 220 221 // Otherwise, the iterator points to an index cell containing "target". 222 // 223 // If "target" is an index cell itself, there is an intersection because index 224 // cells are created only if they have at least one edge or they are 225 // entirely contained by the loop. 226 enforce(_iter.id().contains(target.id())); 227 if (_iter.id() == target.id()) return true; 228 229 // Test whether any shape intersects the target cell or contains its center. 230 const(S2ShapeIndexCell) cell = _iter.cell(); 231 for (int s = 0; s < cell.numClipped(); ++s) { 232 const(S2ClippedShape) clipped = cell.clipped(s); 233 if (anyEdgeIntersects(clipped, target)) return true; 234 if (_containsQuery.shapeContains(_iter, clipped, target.getCenter())) { 235 return true; 236 } 237 } 238 return false; 239 } 240 241 // Returns true if the given point is contained by any two-dimensional shape 242 // (i.e., polygon). Boundaries are treated as being semi-open (i.e., the 243 // same rules as S2Polygon). Zero and one-dimensional shapes are ignored by 244 // this method (if you need more flexibility, see S2BooleanOperation). 245 override 246 bool contains(in S2Point p) { 247 if (_iter.locate(p)) { 248 const(S2ShapeIndexCell) cell = _iter.cell(); 249 for (int s = 0; s < cell.numClipped(); ++s) { 250 if (_containsQuery.shapeContains(_iter, cell.clipped(s), p)) { 251 return true; 252 } 253 } 254 } 255 return false; 256 } 257 258 private: 259 alias Iterator = IndexT.Iterator; 260 261 // Computes the smallest S2Cell that covers the S2Cell range (first, last) and 262 // adds this cell to "cell_ids". 263 // 264 // REQUIRES: "first" and "last" have a common ancestor. 265 static void coverRange(S2CellId first, S2CellId last, ref S2CellId[] cell_ids) { 266 if (first == last) { 267 // The range consists of a single index cell. 268 cell_ids ~= first; 269 } else { 270 // Add the lowest common ancestor of the given range. 271 int level = first.getCommonAncestorLevel(last); 272 enforce(level >= 0); 273 cell_ids ~= first.parent(level); 274 } 275 } 276 277 // Returns true if the indexed shape "clipped" in the indexed cell "id" 278 // contains the point "p". 279 // 280 // REQUIRES: id.contains(S2CellId(p)) 281 //bool Contains(S2CellId id, const S2ClippedShape& clipped, const S2Point& p) const; 282 283 // Returns true if any edge of the indexed shape "clipped" intersects the 284 // cell "target". It may also return true if an edge is very close to 285 // "target"; the maximum error is less than 10 * DBL_EPSILON radians (about 286 // 15 nanometers). 287 bool anyEdgeIntersects(in S2ClippedShape clipped, in S2Cell target) const { 288 enum double kMaxError = FACE_CLIP_ERROR_UV_COORD + INTERSECTS_RECT_ERROR_UV_DIST; 289 const R2Rect bound = target.getBoundUV().expanded(kMaxError); 290 const int face = target.face(); 291 const S2Shape shape = index().shape(clipped.shapeId()); 292 const int num_edges = clipped.numEdges(); 293 for (int i = 0; i < num_edges; ++i) { 294 const auto edge = shape.edge(clipped.edge(i)); 295 R2Point p0, p1; 296 if (clipToPaddedFace(edge.v0, edge.v1, face, kMaxError, p0, p1) 297 && intersectsRect(p0, p1, bound)) { 298 return true; 299 } 300 } 301 return false; 302 } 303 304 // This class is not thread-safe! 305 S2ContainsPointQuery!IndexT _containsQuery; 306 307 // Optimization: rather than declaring our own iterator, instead we reuse 308 // the iterator declared by S2ContainsPointQuery. (This improves benchmark 309 // times significantly for classes that create a new S2ShapeIndexRegion 310 // object on every call to Contains/MayIntersect(S2Cell). 311 Iterator _iter; 312 } 313 314 // Returns an S2ShapeIndexRegion that wraps the given S2ShapeIndex. Note that 315 // it is efficient to return S2ShapeIndexRegion objects by value. 316 S2ShapeIndexRegion!IndexT makeS2ShapeIndexRegion(IndexT)(IndexT index) { 317 return new S2ShapeIndexRegion!IndexT(index); 318 }