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 }