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.s2contains_point_query;
20 
21 import s2.s2edge_crosser;
22 import s2.s2edge_crossings : vertexCrossing;
23 import s2.s2point;
24 import s2.s2shape;
25 import s2.s2shape_index;
26 import s2.shapeutil.shape_edge;
27 
28 import std.typecons : Rebindable;
29 
30 /**
31  * Defines whether shapes are considered to contain their vertices.  Note that
32  * these definitions differ from the ones used by S2BooleanOperation.
33  *
34  *  - In the OPEN model, no shapes contain their vertices (not even points).
35  *    Therefore Contains(S2Point) returns true if and only if the point is
36  *    in the interior of some polygon.
37  *
38  *  - In the SEMI_OPEN model, polygon point containment is defined such that
39  *    if several polygons tile the region around a vertex, then exactly one of
40  *    those polygons contains that vertex.  Points and polylines still do not
41  *    contain any vertices.
42  *
43  *  - In the CLOSED model, all shapes contain their vertices (including points
44  *    and polylines).
45  *
46  * Note that points other than vertices are never contained by polylines.
47  * If you want need this behavior, use S2ClosestEdgeQuery::IsDistanceLess()
48  * with a suitable distance threshold instead.
49  */
50 enum S2VertexModel { OPEN, SEMI_OPEN, CLOSED }
51 
52 /// This class defines the options supported by S2ContainsPointQuery.
53 class S2ContainsPointQueryOptions {
54 public:
55   this() {}
56 
57   /// Convenience constructor that sets the vertex_model() option.
58   this(S2VertexModel vertex_model) {
59     _vertexModel = vertex_model;
60   }
61 
62   /**
63    * Controls whether shapes are considered to contain their vertices (see
64    * definitions above).  By default the SEMI_OPEN model is used.
65    *
66    * DEFAULT: S2VertexModel::SEMI_OPEN
67    */
68   S2VertexModel vertexModel() const {
69     return _vertexModel;
70   }
71 
72   void setVertexModel(S2VertexModel model) {
73     _vertexModel = model;
74   }
75 
76 private:
77   S2VertexModel _vertexModel = S2VertexModel.SEMI_OPEN;
78 }
79 
80 // S2ContainsPointQuery determines whether one or more shapes in an
81 // S2ShapeIndex contain a given S2Point.  The S2ShapeIndex may contain any
82 // number of points, polylines, and/or polygons (possibly overlapping).
83 // Shape boundaries may be modeled as OPEN, SEMI_OPEN, or CLOSED (this affects
84 // whether or not shapes are considered to contain their vertices).
85 //
86 // Example usage:
87 //   auto query = MakeS2ContainsPointQuery(&index, S2VertexModel::CLOSED);
88 //   return query.Contains(point);
89 //
90 // This class is not thread-safe.  To use it in parallel, each thread should
91 // construct its own instance (this is not expensive).
92 //
93 // However, note that if you need to do a large number of point containment
94 // tests, it is more efficient to re-use the S2ContainsPointQuery object
95 // rather than constructing a new one each time.
96 class S2ContainsPointQuery(IndexT) {
97 private:
98   alias Iterator = IndexT.Iterator;
99 
100 public:
101   /// Default constructor; requires Init() to be called.
102   this() {
103     _index = null;
104     _it = new Iterator();
105   }
106 
107   /**
108    * Rather than calling this constructor, which requires specifying the
109    * IndexT template argument explicitly, the preferred idiom is to call
110    * MakeS2ContainsPointQuery() instead.  For example:
111    *
112    *   return MakeS2ContainsPointQuery(&index).Contains(p);
113    */
114   alias Options = S2ContainsPointQueryOptions;
115 
116   this(IndexT index, Options options = null) {
117     _index = index;
118     _options = options !is null ? options : new Options();
119     _it = new Iterator(_index);
120   }
121 
122 
123   // Convenience constructor that accepts the S2VertexModel directly.
124   this(IndexT index, S2VertexModel vertex_model) {
125     this(index, new Options(vertex_model));
126   }
127 
128   inout(IndexT) index() inout {
129     return _index;
130   }
131 
132   const(Options) options() const {
133     return _options;
134   }
135 
136   /// Equivalent to the two-argument constructor above.
137   void initialize(IndexT index, Options options = null) {
138     _index = index;
139     _options = options !is null ? options : new Options();
140     _it.initialize(index);
141   }
142 
143   /**
144    * Returns true if any shape in the given index() contains the point "p"
145    * under the vertex model specified (OPEN, SEMI_OPEN, or CLOSED).
146    */
147   bool contains(in S2Point p) {
148     if (!_it.locate(p)) return false;
149 
150     const S2ShapeIndexCell cell = _it.cell();
151     int num_clipped = cell.numClipped();
152     for (int s = 0; s < num_clipped; ++s) {
153       if (shapeContains(_it, cell.clipped(s), p)) return true;
154     }
155     return false;
156   }
157 
158   /**
159    * Returns true if the given shape contains the point "p" under the vertex
160    * model specified (OPEN, SEMI_OPEN, or CLOSED).
161    *
162    * REQUIRES: "shape" belongs to index().
163    */
164   bool shapeContains(in S2Shape shape, in S2Point p) {
165     if (!_it.locate(p)) return false;
166     const(S2ClippedShape)* clipped = _it.cell().findClipped(shape.id());
167     if (clipped is null) return false;
168     return shapeContains(_it, *clipped, p);
169   }
170 
171   // Visits all shapes in the given index() that contain the given point "p",
172   // terminating early if the given ShapeVisitor function returns false (in
173   // which case VisitContainingShapes returns false as well).  Each shape is
174   // visited at most once.
175   //
176   // Note that the API allows non-const access to the visited shapes.
177   alias ShapeVisitor = bool delegate(S2Shape);
178 
179   bool visitContainingShapes(in S2Point p, ShapeVisitor visitor) {
180     // This function returns "false" only if the algorithm terminates early
181     // because the "visitor" function returned false.
182     if (!_it.locate(p)) return true;
183 
184     const(S2ShapeIndexCell) cell = _it.cell();
185     int num_clipped = cell.numClipped();
186     for (int s = 0; s < num_clipped; ++s) {
187       const(S2ClippedShape) clipped = cell.clipped(s);
188       if (shapeContains(_it, clipped, p) && !visitor(_index.shape(clipped.shapeId()))) {
189         return false;
190       }
191     }
192     return true;
193   }
194 
195   /// Convenience function that returns all the shapes that contain the given point "p".
196   S2Shape[] getContainingShapes(in S2Point p) {
197     S2Shape[] results;
198     visitContainingShapes(p, (S2Shape shape) {
199           results ~= shape;
200           return true;
201         });
202     return results;
203   }
204 
205   // Visits all edges in the given index() that are incident to the point "p"
206   // (i.e., "p" is one of the edge endpoints), terminating early if the given
207   // EdgeVisitor function returns false (in which case VisitIncidentEdges
208   // returns false as well).  Each edge is visited at most once.
209   alias EdgeVisitor = bool delegate(in ShapeEdge);
210 
211   bool visitIncidentEdges(in S2Point p, in EdgeVisitor visitor) {
212     // This function returns "false" only if the algorithm terminates early
213     // because the "visitor" function returned false.
214     if (!_it.locate(p)) return true;
215 
216     const S2ShapeIndexCell cell = _it.cell();
217     int num_clipped = cell.numClipped();
218     for (int s = 0; s < num_clipped; ++s) {
219       const S2ClippedShape clipped = cell.clipped(s);
220       int num_edges = clipped.numEdges();
221       if (num_edges == 0) continue;
222       const S2Shape shape = _index.shape(clipped.shapeId());
223       for (int i = 0; i < num_edges; ++i) {
224         int edge_id = clipped.edge(i);
225         auto edge = shape.edge(edge_id);
226         if ((edge.v0 == p || edge.v1 == p) && !visitor(new ShapeEdge(shape.id(), edge_id, edge))) {
227           return false;
228         }
229       }
230     }
231     return true;
232   }
233 
234   /////////////////////////// Low-Level Methods ////////////////////////////
235   //
236   // Most clients will not need the following methods.  They can be slightly
237   // more efficient but are harder to use.
238 
239   // Returns a pointer to the iterator used internally by this class, in order
240   // to avoid the need for clients to create their own iterator.  Clients are
241   // allowed to reposition this iterator arbitrarily between method calls.
242   Iterator mutableIter() {
243     return _it;
244   }
245 
246   // Low-level helper method that returns true if the given S2ClippedShape
247   // referred to by an S2ShapeIndex::Iterator contains the point "p".
248   bool shapeContains(in Iterator it, in S2ClippedShape clipped, in S2Point p) const {
249     bool inside = clipped.containsCenter();
250     int num_edges = clipped.numEdges();
251     if (num_edges > 0) {
252       // Points and polylines can be ignored unless the vertex model is CLOSED.
253       const S2Shape shape = _index.shape(clipped.shapeId());
254       if (!shape.hasInterior() && _options.vertexModel() != S2VertexModel.CLOSED) {
255         return false;
256       }
257       // Test containment by drawing a line segment from the cell center to the
258       // given point and counting edge crossings.
259       auto crosser = new S2CopyingEdgeCrosser(it.center(), p);
260       for (int i = 0; i < num_edges; ++i) {
261         auto edge = shape.edge(clipped.edge(i));
262         int sign = crosser.crossingSign(edge.v0, edge.v1);
263         if (sign < 0) {
264           continue;
265         }
266         if (sign == 0) {
267           // For the OPEN and CLOSED models, check whether "p" is a vertex.
268           if (_options.vertexModel() != S2VertexModel.SEMI_OPEN
269               && (edge.v0 == p || edge.v1 == p)) {
270             return _options.vertexModel() == S2VertexModel.CLOSED;
271           }
272           sign = vertexCrossing(crosser.a(), crosser.b(), edge.v0, edge.v1);
273         }
274         inside ^= cast(bool) sign;
275       }
276     }
277     return inside;
278   }
279 
280  private:
281   IndexT _index;
282   Options _options;
283   Iterator _it;
284 }
285 
286 // Returns an S2ContainsPointQuery for the given S2ShapeIndex.  Note that
287 // it is efficient to return S2ContainsPointQuery objects by value.
288 S2ContainsPointQuery!IndexT makeS2ContainsPointQuery(IndexT)(
289     IndexT index, S2ContainsPointQueryOptions options = null) {
290   return new S2ContainsPointQuery!IndexT(
291       index, options !is null ? options : new S2ContainsPointQueryOptions());
292 }