1 // Copyright 2013 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.s2closest_edge_query; 20 21 import s2.s1angle; 22 import s2.s1chord_angle; 23 import s2.s2cell; 24 import s2.s2cell_id; 25 import s2.s2closest_edge_query_base; 26 import s2.s2edge_distances : getUpdateMinDistanceMaxError, project; 27 import s2.s2min_distance_targets; 28 import s2.s2point; 29 import s2.s2shape; 30 import s2.s2shape_index; 31 32 33 /** 34 * S2ClosestEdgeQuery is a helper class for finding the closest edge(s) to a 35 * given point, edge, S2Cell, or geometry collection. For example, given a 36 * set of polylines, the following code efficiently finds the closest 5 edges 37 * to a query point: 38 * 39 * void Test(const vector<S2Polyline*>& polylines, const S2Point& point) { 40 * MutableS2ShapeIndex index; 41 * for (S2Polyline* polyline : polylines) { 42 * index.Add(new S2Polyline::Shape(polyline)); 43 * } 44 * S2ClosestEdgeQuery query(&index); 45 * query.mutable_options()->set_max_edges(5); 46 * S2ClosestEdgeQuery::PointTarget target(point); 47 * for (const auto& result : query.FindClosestEdges(&target)) { 48 * // The Result struct contains the following fields: 49 * // "distance" is the distance to the edge. 50 * // "shape_id" identifies the S2Shape containing the edge. 51 * // "edge_id" identifies the edge with the given shape. 52 * // The following convenience methods may also be useful: 53 * // query.GetEdge(result) returns the endpoints of the edge. 54 * // query.Project(point, result) computes the closest point on the 55 * // result edge to the given target point. 56 * int polyline_index = result.shape_id; 57 * int edge_index = result.edge_id; 58 * S1ChordAngle distance = result.distance; // Use ToAngle() for S1Angle. 59 * S2Shape::Edge edge = query.GetEdge(result); 60 * S2Point closest_point = query.Project(point, result); 61 * } 62 * } 63 * 64 * You can find either the k closest edges, or all edges within a given 65 * radius, or both (i.e., the k closest edges up to a given maximum radius). 66 * E.g. to find all the edges within 5 kilometers, call 67 * 68 * query.mutable_options()->set_max_distance( 69 * S2Earth::ToAngle(util::units::Kilometers(5))); 70 * 71 * By default *all* edges are returned, so you should always specify either 72 * max_edges() or max_distance() or both. There is also a FindClosestEdge() 73 * convenience method that returns only the closest edge. 74 * 75 * Note that by default, distances are measured to the boundary and interior 76 * of polygons. For example, if a point is inside a polygon then its distance 77 * is zero. To change this behavior, call set_include_interiors(false). 78 * 79 * If you only need to test whether the distance is above or below a given 80 * threshold (e.g., 10 km), you can use the IsDistanceLess() method. This is 81 * much faster than actually calculating the distance with FindClosestEdge(), 82 * since the implementation can stop as soon as it can prove that the minimum 83 * distance is either above or below the threshold. 84 * 85 * To find the closest edges to a query edge rather than a point, use: 86 * 87 * S2ClosestEdgeQuery::EdgeTarget target(v0, v1); 88 * query.FindClosestEdges(&target); 89 * 90 * Similarly you can find the closest edges to an S2Cell by using an 91 * S2ClosestEdgeQuery::CellTarget, and you can find the closest edges to an 92 * arbitrary collection of points, polylines, and polygons by using an 93 * S2ClosestEdgeQuery::ShapeIndexTarget. 94 * 95 * The implementation is designed to be fast for both simple and complex 96 * geometric objects. 97 */ 98 class S2ClosestEdgeQuery { 99 public: 100 // See S2ClosestEdgeQueryBase for full documentation. 101 102 // S2MinDistance is a thin wrapper around S1ChordAngle that implements the 103 // Distance concept required by S2ClosestPointQueryBase. 104 alias Distance = S2MinDistance; 105 alias Base = S2ClosestEdgeQueryBase!Distance; 106 107 // Each "Result" object represents a closest edge. It has the following 108 // fields: 109 // 110 // S1ChordAngle distance; // The distance from the target to this edge. 111 // int32 shape_id; // Identifies an indexed shape. 112 // int32 edge_id; // Identifies an edge within the shape. 113 alias Result = Base.Result; 114 115 /** 116 * Options that control the set of edges returned. Note that by default 117 * *all* edges are returned, so you will always want to set either the 118 * max_edges() option or the max_distance() option (or both). 119 */ 120 final static class Options : Base.Options { 121 public: 122 // See S2ClosestEdgeQueryBase::Options for the full set of options. 123 124 this() { } 125 126 this(Options options) { 127 super(options); 128 } 129 130 /** 131 * Specifies that only edges whose distance to the target is less than 132 * "max_distance" should be returned. 133 * 134 * Note that edges whose distance is exactly equal to "max_distance" are 135 * not returned. Normally this doesn't matter, because distances are not 136 * computed exactly in the first place, but if such edges are needed then 137 * see set_inclusive_max_distance() below. 138 * 139 * DEFAULT: Distance::Infinity() 140 */ 141 void setMaxDistance(S1ChordAngle max_distance) { 142 super.setMaxDistance(Distance(max_distance)); 143 } 144 145 /** 146 * Like set_max_distance(), except that edges whose distance is exactly 147 * equal to "max_distance" are also returned. Equivalent to calling 148 * set_max_distance(max_distance.Successor()). 149 */ 150 void setInclusiveMaxDistance(S1ChordAngle max_distance) { 151 setMaxDistance(max_distance.successor()); 152 } 153 154 // Like set_inclusive_max_distance(), except that "max_distance" is also 155 // increased by the maximum error in the distance calculation. This 156 // ensures that all edges whose true distance is less than or equal to 157 // "max_distance" will be returned (along with some edges whose true 158 // distance is slightly greater). 159 // 160 // Algorithms that need to do exact distance comparisons can use this 161 // option to find a set of candidate edges that can then be filtered 162 // further (e.g., using s2pred::CompareDistance). 163 void setConservativeMaxDistance(S1ChordAngle max_distance) { 164 setMaxDistance( 165 Distance( 166 max_distance.plusError( 167 getUpdateMinDistanceMaxError(max_distance)) 168 .successor())); 169 170 } 171 172 // Versions of set_max_distance that take an S1Angle argument. (Note that 173 // these functions require a conversion, and that the S1ChordAngle versions 174 // are preferred.) 175 void setMaxDistance(S1Angle max_distance) { 176 super.setMaxDistance(Distance(S1ChordAngle(max_distance))); 177 } 178 179 void setInclusiveMaxDistance(S1Angle max_distance) { 180 setInclusiveMaxDistance(S1ChordAngle(max_distance)); 181 } 182 183 void setConservativeMaxDistance(S1Angle max_distance) { 184 setConservativeMaxDistance(S1ChordAngle(max_distance)); 185 } 186 187 // See S2ClosestEdgeQueryBase::Options for documentation. 188 void setMaxError(S1Angle max_error) { 189 super.setMaxError(S1ChordAngle(max_error)); 190 } 191 192 } 193 194 // "Target" represents the geometry to which the distance is measured. 195 // There are subtypes for measuring the distance to a point, an edge, an 196 // S2Cell, or an S2ShapeIndex (an arbitrary collection of geometry). 197 alias Target = S2MinDistanceTarget; 198 199 /// Target subtype that computes the closest distance to a point. 200 final static class PointTarget : S2MinDistancePointTarget { 201 public: 202 this(in S2Point point) { 203 super(point); 204 } 205 206 override 207 int maxBruteForceIndexSize() const { 208 // Using BM_FindClosest (which finds the single closest edge), the 209 // break-even points are approximately 80, 100, and 250 edges for point 210 // cloud, fractal, and regular loop geometry respectively. 211 return 120; 212 } 213 } 214 215 // Target subtype that computes the closest distance to an edge. 216 final static class EdgeTarget : S2MinDistanceEdgeTarget { 217 public: 218 this(in S2Point a, in S2Point b) { 219 super(a, b); 220 } 221 222 override 223 int maxBruteForceIndexSize() const { 224 // Using BM_FindClosestToEdge (which finds the single closest edge), the 225 // break-even points are approximately 40, 50, and 100 edges for point 226 // cloud, fractal, and regular loop geometry respectively. 227 return 60; 228 } 229 } 230 231 // Target subtype that computes the closest distance to an S2Cell 232 // (including the interior of the cell). 233 final static class CellTarget : S2MinDistanceCellTarget { 234 public: 235 this(in S2Cell cell) { 236 super(cell); 237 } 238 239 override 240 int maxBruteForceIndexSize() const { 241 // Using BM_FindClosestToCell (which finds the single closest edge), the 242 // break-even points are approximately 20, 25, and 40 edges for point cloud, 243 // fractal, and regular loop geometry respectively. 244 return 30; 245 } 246 } 247 248 // Target subtype that computes the closest distance to an S2ShapeIndex 249 // (an arbitrary collection of points, polylines, and/or polygons). 250 // 251 // By default, distances are measured to the boundary and interior of 252 // polygons in the S2ShapeIndex rather than to polygon boundaries only. 253 // If you wish to change this behavior, you may call 254 // 255 // target.set_include_interiors(false); 256 // 257 // (see S2MinDistanceShapeIndexTarget for details). 258 final static class ShapeIndexTarget : S2MinDistanceShapeIndexTarget { 259 public: 260 this(S2ShapeIndex index) { 261 super(index); 262 } 263 264 override 265 int maxBruteForceIndexSize() const { 266 // For BM_FindClosestToSameSizeAbuttingIndex (which uses two nearby indexes 267 // with similar edge counts), the break-even points are approximately 20, 268 // 30, and 40 edges for point cloud, fractal, and regular loop geometry 269 // respectively. 270 return 25; 271 } 272 } 273 274 // Convenience constructor that calls Init(). Options may be specified here 275 // or changed at any time using the mutable_options() accessor method. 276 this(S2ShapeIndex index, Options options = null) { 277 this(); 278 if (options is null) 279 options = new Options(); 280 initialize(index, options); 281 } 282 283 // Default constructor; requires Init() to be called. 284 this() { 285 _base = new Base(); 286 } 287 288 // Initializes the query. Options may be specified here or changed at any 289 // time using the mutable_options() accessor method. 290 // 291 // REQUIRES: "index" must persist for the lifetime of this object. 292 // REQUIRES: ReInit() must be called if "index" is modified. 293 void initialize(S2ShapeIndex index, Options options) { 294 _options = options is null ? new Options() : options; 295 _base.initialize(index); 296 } 297 298 // Reinitializes the query. This method must be called whenever the 299 // underlying S2ShapeIndex is modified. 300 void reInititialize() { 301 _base.reInitialize(); 302 } 303 304 // Returns a reference to the underlying S2ShapeIndex. 305 const(S2ShapeIndex) index() const { 306 return _base.index(); 307 } 308 309 // Returns the query options. Options can be modifed between queries. 310 const(Options) options() const { 311 return _options; 312 } 313 314 Options mutableOptions() { 315 return _options; 316 } 317 318 // Returns the closest edges to the given target that satisfy the given 319 // options. This method may be called multiple times. 320 // 321 // Note that if options().include_interiors() is true, the result vector may 322 // include some entries with edge_id == -1. This indicates that the target 323 // intersects the indexed polygon with the given shape_id. 324 Result[] findClosestEdges(Target target) { 325 return _base.findClosestEdges(target, _options); 326 } 327 328 // This version can be more efficient when this method is called many times, 329 // since it does not require allocating a new vector on each call. 330 void findClosestEdges(Target target, out Result[] results) { 331 _base.findClosestEdges(target, _options, results); 332 } 333 334 //////////////////////// Convenience Methods //////////////////////// 335 336 // Returns the closest edge to the target. If no edge satisfies the search 337 // criteria, then the Result object will have distance == Infinity() and 338 // shape_id == edge_id == -1. 339 // 340 // Note that if options.include_interiors() is true, edge_id == -1 is also 341 // used to indicate that the target intersects an indexed polygon (but in 342 // that case distance == Zero() and shape_id >= 0). 343 Result findClosestEdge(Target target) { 344 import std.conv; 345 static assert( 346 __traits(classInstanceSize, Options) <= 48, 347 "Consider not copying Options (" ~ to!string(__traits(classInstanceSize, Options)) 348 ~ " bytes) here"); 349 auto tmp_options = new Options(_options); 350 tmp_options.setMaxEdges(1); 351 return _base.findClosestEdge(target, tmp_options); 352 } 353 354 // Returns the minimum distance to the target. If the index or target is 355 // empty, returns S1ChordAngle::Infinity(). 356 // 357 // Use IsDistanceLess() if you only want to compare the distance against a 358 // threshold value, since it is often much faster. 359 S1ChordAngle getDistance(Target target) { 360 return findClosestEdge(target).distance; 361 } 362 363 // Returns true if the distance to "target" is less than "limit". 364 // 365 // This method is usually much faster than GetDistance(), since it is much 366 // less work to determine whether the minimum distance is above or below a 367 // threshold than it is to calculate the actual minimum distance. 368 bool isDistanceLess(Target target, S1ChordAngle limit) { 369 import std.conv; 370 static assert( 371 __traits(classInstanceSize, Options) <= 48, 372 "Consider not copying Options (" ~ to!string(__traits(classInstanceSize, Options)) 373 ~ " bytes) here"); 374 Options tmp_options = new Options(_options); 375 tmp_options.setMaxEdges(1); 376 tmp_options.setMaxDistance(limit); 377 tmp_options.setMaxError(S1ChordAngle.straight().toS1Angle()); 378 return _base.findClosestEdge(target, tmp_options).shapeId >= 0; 379 } 380 381 // Like IsDistanceLess(), but also returns true if the distance to "target" 382 // is exactly equal to "limit". 383 bool isDistanceLessOrEqual(Target target, S1ChordAngle limit) { 384 import std.conv; 385 static assert( 386 __traits(classInstanceSize, Options) <= 48, 387 "Consider not copying Options (" ~ to!string(__traits(classInstanceSize, Options)) 388 ~ " bytes) here"); 389 Options tmp_options = new Options(_options); 390 tmp_options.setMaxEdges(1); 391 tmp_options.setInclusiveMaxDistance(limit); 392 tmp_options.setMaxError(S1ChordAngle.straight().toS1Angle()); 393 return _base.findClosestEdge(target, tmp_options).shapeId >= 0; 394 } 395 396 // Like IsDistanceLessOrEqual(), except that "limit" is increased by the 397 // maximum error in the distance calculation. This ensures that this 398 // function returns true whenever the true, exact distance is less than 399 // or equal to "limit". 400 // 401 // For example, suppose that we want to test whether two geometries might 402 // intersect each other after they are snapped together using S2Builder 403 // (using the IdentitySnapFunction with a given "snap_radius"). Since 404 // S2Builder uses exact distance predicates (s2predicates.h), we need to 405 // measure the distance between the two geometries conservatively. If the 406 // distance is definitely greater than "snap_radius", then the geometries 407 // are guaranteed to not intersect after snapping. 408 bool isConservativeDistanceLessOrEqual(Target target, S1ChordAngle limit) { 409 import std.conv; 410 static assert( 411 __traits(classInstanceSize, Options) <= 48, 412 "Consider not copying Options (" ~ to!string(__traits(classInstanceSize, Options)) 413 ~ " bytes) here"); 414 Options tmp_options = _options; 415 tmp_options.setMaxEdges(1); 416 tmp_options.setConservativeMaxDistance(limit); 417 tmp_options.setMaxError(S1ChordAngle.straight().toS1Angle()); 418 return _base.findClosestEdge(target, tmp_options).shapeId >= 0; 419 } 420 421 // Returns the endpoints of the given result edge. 422 // 423 // CAVEAT: If options().include_interiors() is true, then clients must not 424 // pass this method any Result objects that correspond to shape interiors, 425 // i.e. those where result.edge_id < 0. 426 // 427 // REQUIRES: result.edge_id >= 0 428 S2Shape.Edge getEdge(in Result result) const { 429 return index().shape(result.shapeId).edge(result.edgeId); 430 } 431 432 // Returns the point on given result edge that is closest to "point". 433 S2Point project(in S2Point point, in Result result) const { 434 if (result.edgeId < 0) return point; 435 auto edge = getEdge(result); 436 return .project(point, edge.v0, edge.v1); 437 } 438 439 private: 440 Options _options; 441 Base _base; 442 }