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 }