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 // This file defines a collection of classes that are useful for computing
20 // minimum distances on the sphere.  Their purpose is to allow code to be
21 // shared among the various query classes that find nearby geometry, such as
22 // S2ClosestPointQuery and S2ClosestEdgeQuery.
23 
24 module s2.s2min_distance_targets;
25 
26 import s2.s1angle;
27 import s2.s1chord_angle;
28 import s2.s2cap;
29 import s2.s2cell;
30 import s2.s2closest_edge_query;
31 import s2.s2contains_point_query;
32 import s2.s2distance_target;
33 import s2.s2edge_distances : updateMinDistance, updateEdgePairMinDistance;
34 import s2.s2point;
35 import s2.s2shape;
36 import s2.s2shape_index;
37 import s2.s2shape_index_region : makeS2ShapeIndexRegion;
38 
39 import std.math : sqrt;
40 
41 /**
42  * S2MinDistance is a thin wrapper around S1ChordAngle that is used by classes
43  * such as S2ClosestEdgeQuery to compute minimum distances on the sphere (as
44  * opposed to maximum distances, ellipsoidal distances, etc).
45  *
46  * It implements the Distance concept defined by S2DistanceTarget, which is
47  * used by query classes such as S2ClosestPointQuery and S2ClosestEdgeQuery.
48  * (See s2distance_target.h for details.)
49  */
50 struct S2MinDistance {
51 public:
52   S1ChordAngle s1ChordAngle;
53 
54   alias s1ChordAngle this;
55 
56   alias Delta = S1ChordAngle;
57 
58   static S2MinDistance zero() {
59     return S2MinDistance(S1ChordAngle.zero());
60   }
61 
62   static S2MinDistance infinity() {
63     return S2MinDistance(S1ChordAngle.infinity());
64   }
65 
66   static S2MinDistance negative() {
67     return S2MinDistance(S1ChordAngle.negative());
68   }
69 
70   S1ChordAngle getChordAngleBound() const {
71     return plusError(getS1AngleConstructorMaxError());
72   }
73 
74   // If (dist < *this), updates *this and returns true (used internally).
75   bool updateMin(in S2MinDistance dist) {
76     if (dist < this) {
77       this = dist;
78       return true;
79     }
80     return false;
81   }
82 
83   int opCmp(in S2MinDistance other) const {
84     return s1ChordAngle.opCmp(other.s1ChordAngle);
85   }
86 }
87 
88 // S2MinDistanceTarget represents a geometric object to which distances are
89 // measured.  Specifically, it is used to compute minimum distances on the
90 // sphere (as opposed to maximum distances, ellipsoidal distances, etc).
91 //
92 // Subtypes are defined below for measuring the distance to a point, an edge,
93 // an S2Cell, or an S2ShapeIndex (an arbitrary collection of geometry).
94 alias S2MinDistanceTarget = S2DistanceTarget!S2MinDistance;
95 
96 // An S2DistanceTarget subtype for computing the minimum distance to a point.
97 class S2MinDistancePointTarget : S2MinDistanceTarget {
98 public:
99   this(in S2Point point) {
100     _point = point;
101   }
102 
103   final override
104   S2Cap getCapBound() {
105     return new S2Cap(_point, S1ChordAngle.zero());
106   }
107 
108   final override
109   bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) {
110     return min_dist.updateMin(S2MinDistance(S1ChordAngle(p, _point)));
111   }
112 
113   final override
114   bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) {
115     return .updateMinDistance(_point, v0, v1, min_dist);
116   }
117 
118   final override
119   bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) {
120     return min_dist.updateMin(S2MinDistance(cell.getDistance(_point)));
121   }
122 
123   final override
124   bool visitContainingShapes(S2ShapeIndex index, ShapeVisitor visitor) {
125     return makeS2ContainsPointQuery(index).visitContainingShapes(
126         _point, (in S2Shape shape) {
127           return visitor(shape, _point);
128         });
129   }
130 
131 private:
132   S2Point _point;
133 }
134 
135 // An S2DistanceTarget subtype for computing the minimum distance to a edge.
136 class S2MinDistanceEdgeTarget : S2MinDistanceTarget {
137 public:
138   this(in S2Point a, in S2Point b) {
139     _a = a;
140     _b = b;
141   }
142 
143   final override
144   S2Cap getCapBound() {
145     // The following computes a radius equal to half the edge length in an
146     // efficient and numerically stable way.
147     double d2 = S1ChordAngle(_a, _b).length2();
148     double r2 = (0.5 * d2) / (1 + sqrt(1 - 0.25 * d2));
149     return new S2Cap((_a + _b).normalize(), S1ChordAngle.fromLength2(r2));
150   }
151 
152   final override
153   bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) {
154     return .updateMinDistance(p, _a, _b, min_dist);
155   }
156 
157   final override
158   bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) {
159     return updateEdgePairMinDistance(_a, _b, v0, v1, min_dist);
160   }
161 
162   final override
163   bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) {
164     return min_dist.updateMin(S2MinDistance(cell.getDistance(_a, _b)));
165   }
166 
167   final override
168   bool visitContainingShapes(S2ShapeIndex index, ShapeVisitor visitor) {
169     // We test the center of the edge in order to ensure that edge targets AB
170     // and BA yield identical results (which is not guaranteed by the API but
171     // users might expect).  Other options would be to test both endpoints, or
172     // return different results for AB and BA in some cases.
173     scope target = new S2MinDistancePointTarget((_a + _b).normalize());
174     return target.visitContainingShapes(index, visitor);
175   }
176 
177 private:
178   S2Point _a;
179   S2Point _b;
180 }
181 
182 // An S2DistanceTarget subtype for computing the minimum distance to an S2Cell
183 // (including the interior of the cell).
184 class S2MinDistanceCellTarget : S2MinDistanceTarget {
185 public:
186   this(in S2Cell cell) {
187     _cell = cell;
188   }
189 
190   final override
191   S2Cap getCapBound() {
192     return _cell.getCapBound();
193   }
194 
195   final override
196   bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) {
197     return min_dist.updateMin(S2MinDistance(_cell.getDistance(p)));
198   }
199 
200   final override
201   bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) {
202     return min_dist.updateMin(S2MinDistance(_cell.getDistance(v0, v1)));
203   }
204 
205   final override
206   bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) {
207     return min_dist.updateMin(S2MinDistance(_cell.getDistance(cell)));
208   }
209 
210   final override
211   bool visitContainingShapes(S2ShapeIndex index, ShapeVisitor visitor) {
212     // The simplest approach is simply to return the polygons that contain the
213     // cell center.  Alternatively, if the index cell is smaller than the target
214     // cell then we could return all polygons that are present in the
215     // S2ShapeIndexCell, but since the index is built conservatively this may
216     // include some polygons that don't quite intersect the cell.  So we would
217     // either need to recheck for intersection more accurately, or weaken the
218     // VisitContainingShapes contract so that it only guarantees approximate
219     // intersection, neither of which seems like a good tradeoff.
220     scope target = new S2MinDistancePointTarget(_cell.getCenter());
221     return target.visitContainingShapes(index, visitor);
222   }
223 
224 private:
225   const(S2Cell) _cell;
226 }
227 
228 /**
229  * An S2DistanceTarget subtype for computing the minimum distance to an
230  * S2ShapeIndex \(a collection of points, polylines, and/or polygons\).
231  *
232  * Note that ShapeIndexTarget has its own options:
233  *
234  *   include_interiors()
235  *     - specifies that distances are measured to the boundary and interior
236  *       of polygons in the S2ShapeIndex.  \(If set to false, distance is
237  *       measured to the polygon boundary only.\)
238  *       DEFAULT: true.
239  *
240  *   brute_force()
241  *     - specifies that the distances should be computed by examining every
242  *       edge in the S2ShapeIndex \(for testing and debugging purposes\).
243  *       DEFAULT: false.
244  *
245  * These options are specified independently of the corresponding
246  * S2ClosestEdgeQuery options.  For example, if include_interiors is true for
247  * an ShapeIndexTarget but false for the S2ClosestEdgeQuery where the target
248  * is used, then distances will be measured from the boundary of one
249  * S2ShapeIndex to the boundary and interior of the other.
250  *
251  * Note that when the distance to a ShapeIndexTarget is zero because the
252  * target intersects the interior of the query index, you can find a point
253  * that achieves this zero distance by calling the `visitContainingShapes()`
254  * method directly.  For example:
255  *
256  * ---
257  *   auto target = new S2ClosestEdgeQuery.ShapeIndexTarget(target_index);
258  *   target.visitContainingShapes(
259  *       query_index, (S2Shape containing_shape, const S2Point& target_point) {
260  *         ... do something with "target_point" ...
261  *         return false;  // Terminate search
262  *       });
263  * ---
264  */
265 class S2MinDistanceShapeIndexTarget : S2MinDistanceTarget {
266 public:
267   this(S2ShapeIndex index) {
268     _index = index;
269     _query = new S2ClosestEdgeQuery(index);
270   }
271 
272   // Specifies that distance will be measured to the boundary and interior
273   // of polygons in the S2ShapeIndex rather than to polygon boundaries only.
274   //
275   // DEFAULT: true
276   bool includeInteriors() const {
277     return _query.options().includeInteriors();
278   }
279 
280   void setIncludeInteriors(bool include_interiors) {
281     _query.mutableOptions().setIncludeInteriors(include_interiors);
282   }
283 
284   // Specifies that the distances should be computed by examining every edge
285   // in the S2ShapeIndex (for testing and debugging purposes).
286   //
287   // DEFAULT: false
288   bool useBruteForce() const {
289     return _query.options().useBruteForce();
290   }
291 
292   void setUseBruteForce(bool use_brute_force) {
293     _query.mutableOptions().setUseBruteForce(use_brute_force);
294   }
295 
296   override
297   bool setMaxError(in S1ChordAngle max_error) {
298     _query.mutableOptions().setMaxError(max_error.toS1Angle());
299     return true;  // Indicates that we may return suboptimal results.
300   }
301 
302   final override
303   S2Cap getCapBound() {
304     return makeS2ShapeIndexRegion(_index).getCapBound();
305   }
306 
307   final override
308   bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) {
309     _query.mutableOptions().setMaxDistance(min_dist);
310     auto target = new S2ClosestEdgeQuery.PointTarget(p);
311     S2ClosestEdgeQuery.Result r = _query.findClosestEdge(target);
312     if (r.shapeId < 0) return false;
313     min_dist = r.distance;
314     return true;
315   }
316 
317   final override
318   bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) {
319     _query.mutableOptions().setMaxDistance(min_dist);
320     auto target = new S2ClosestEdgeQuery.EdgeTarget(v0, v1);
321     S2ClosestEdgeQuery.Result r = _query.findClosestEdge(target);
322     if (r.shapeId < 0) return false;
323     min_dist = r.distance;
324     return true;
325   }
326 
327   final override
328   bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) {
329     _query.mutableOptions().setMaxDistance(min_dist);
330     auto target = new S2ClosestEdgeQuery.CellTarget(cell);
331     S2ClosestEdgeQuery.Result r = _query.findClosestEdge(target);
332     if (r.shapeId < 0) return false;
333     min_dist = r.distance;
334     return true;
335   }
336 
337   final override
338   bool visitContainingShapes(S2ShapeIndex query_index, in ShapeVisitor visitor) {
339     // It is sufficient to find the set of chain starts in the target index
340     // (i.e., one vertex per connected component of edges) that are contained by
341     // the query index, except for one special case to handle full polygons.
342     //
343     // TODO(ericv): Do this by merge-joining the two S2ShapeIndexes, and share
344     // the code with S2BooleanOperation.
345 
346     int num_shape_ids = _index.numShapeIds();
347     for (int s = 0; s < num_shape_ids; ++s) {
348       const(S2Shape) shape = _index.shape(s);
349       if (shape is null) continue;
350       int num_chains = shape.numChains();
351       // Shapes that don't have any edges require a special case (below).
352       bool tested_point = false;
353       for (int c = 0; c < num_chains; ++c) {
354         S2Shape.Chain chain = shape.chain(c);
355         if (chain.length == 0) continue;
356         tested_point = true;
357         S2Point v0 = shape.chainEdge(c, 0).v0;
358         auto target = new S2MinDistancePointTarget(v0);
359         if (!target.visitContainingShapes(query_index, visitor)) {
360           return false;
361         }
362       }
363       if (!tested_point) {
364         // Special case to handle full polygons.
365         S2Shape.ReferencePoint refPoint = shape.getReferencePoint();
366         if (!refPoint.contained) continue;
367         auto target = new S2MinDistancePointTarget(refPoint.point);
368         if (!target.visitContainingShapes(query_index, visitor)) {
369           return false;
370         }
371       }
372     }
373     return true;
374   }
375 
376 private:
377   S2ShapeIndex _index;
378   S2ClosestEdgeQuery _query;
379 }