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 }