1 // Copyright 2016 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.builder.util.snap_functions;
20 
21 import s2.s1angle;
22 import s2.s2builder;
23 import s2.s2builder;
24 import s2.s2cell_id;
25 import s2.s2latlng;
26 import s2.s2metrics;
27 import s2.s2point;
28 
29 import std.algorithm;
30 import std.math;
31 
32 /**
33  * A SnapFunction that snaps every vertex to itself.  It should be used when
34  * vertices do not need to be snapped to a discrete set of locations (such as
35  * E7 lat/lngs), or when maximum accuracy is desired.
36  *
37  * If the given "snap_radius" is zero, then all input vertices are preserved
38  * exactly.  Otherwise, S2Builder merges nearby vertices to ensure that no
39  * vertex pair is closer than "snap_radius".  Furthermore, vertices are
40  * separated from non-incident edges by at least "min_edge_vertex_separation",
41  * equal to (0.5 * snap_radius).  For example, if the snap_radius is 1km, then
42  * vertices will be separated from non-incident edges by at least 500m.
43  */
44 class IdentitySnapFunction : S2Builder.SnapFunction {
45 public:
46   /// The default constructor uses a snap_radius of zero (i.e., no snapping).
47   this() {
48     _snapRadius = S1Angle.zero();
49   }
50 
51   /// Convenience constructor that calls set_snap_radius().
52   this(S1Angle snap_radius) {
53     setSnapRadius(snap_radius);
54   }
55 
56   this(in IdentitySnapFunction other) {
57     _snapRadius = other._snapRadius;
58   }
59 
60   /// REQUIRES: snap_radius <= SnapFunction::kMaxSnapRadius()
61   void setSnapRadius(S1Angle snap_radius)
62   in {
63     assert(snap_radius <= kMaxSnapRadius());
64   } do {
65     _snapRadius = snap_radius;
66   }
67 
68   override
69   S1Angle snapRadius() const {
70     return _snapRadius;
71   }
72 
73   /// For the identity snap function, all vertex pairs are separated by at
74   /// least snap_radius().
75   override
76   S1Angle minVertexSeparation() const {
77     // Since SnapFunction does not move the input point, output vertices are
78     // separated by the full snap_radius().
79     return _snapRadius;
80   }
81 
82   // For the identity snap function, edges are separated from all non-incident
83   // vertices by at least 0.5 * snap_radius().
84   override
85   S1Angle minEdgeVertexSeparation() const {
86     // In the worst case configuration, the edge separation is half of the
87     // vertex separation.
88     return 0.5 * _snapRadius;
89   }
90 
91   override
92   S2Point snapPoint(in S2Point point) const {
93     return point;
94   }
95 
96   override
97   S2Builder.SnapFunction clone() const {
98     return new IdentitySnapFunction(this);
99   }
100 
101   override
102   string toString() const {
103     import std.conv;
104     return "IdentitySnapFunction[_snapRadius=" ~ _snapRadius.to!string ~ "]";
105   }
106 
107 private:
108   // Copying and assignment are allowed.
109   S1Angle _snapRadius;
110 }
111 
112 /**
113  * A SnapFunction that snaps vertices to S2CellId centers.  This can be useful
114  * if you want to encode your geometry compactly using S2Polygon::Encode(),
115  * for example.  You can snap to the centers of cells at any level.
116  *
117  * Every snap level has a corresponding minimum snap radius, which is simply
118  * the maximum distance that a vertex can move when snapped.  It is
119  * approximately equal to half of the maximum diagonal length for cells at the
120  * chosen level.  You can also set the snap radius to a larger value; for
121  * example, you could snap to the centers of leaf cells (1cm resolution) but
122  * set the snap_radius() to 10m.  This would result in significant extra
123  * simplification, without moving vertices unnecessarily (i.e., vertices that
124  * are at least 10m away from all other vertices will move by less than 1cm).
125  */
126 class S2CellIdSnapFunction : S2Builder.SnapFunction {
127 public:
128   /// The default constructor snaps to S2CellId::kMaxLevel (i.e., the centers
129   /// of leaf cells), and uses the minimum allowable snap radius at that level.
130   this() {
131     setLevel(S2CellId.MAX_LEVEL);
132   }
133 
134   /// Convenience constructor equivalent to calling set_level(level).
135   this(int level) {
136     setLevel(level);
137   }
138 
139   this(in S2CellIdSnapFunction other) {
140     _level = other._level;
141     _snapRadius = other._snapRadius;
142   }
143 
144   /**
145    * Snaps vertices to S2Cell centers at the given level.  As a side effect,
146    * this method also resets "snap_radius" to the minimum value allowed at
147    * this level:
148    *
149    *   set_snap_radius(MinSnapRadiusForLevel(level))
150    *
151    * This means that if you want to use a larger snap radius than the minimum,
152    * you must call set_snap_radius() *after* calling set_level().
153    */
154   void setLevel(int level)
155   in {
156     assert(level >= 0);
157     assert(level <= S2CellId.MAX_LEVEL);
158   } do {
159     _level = level;
160     setSnapRadius(minSnapRadiusForLevel(level));
161   }
162 
163   int level() const {
164     return _level;
165   }
166 
167   /**
168    * Defines the snap radius to be used (see s2builder.h).  The snap radius
169    * must be at least the minimum value for the current level(), but larger
170    * values can also be used (e.g., to simplify the geometry).
171    *
172    * REQUIRES: snap_radius >= MinSnapRadiusForLevel(level())
173    * REQUIRES: snap_radius <= SnapFunction::kMaxSnapRadius()
174    */
175   void setSnapRadius(S1Angle snap_radius)
176   in {
177     assert(snap_radius >= minSnapRadiusForLevel(level()));
178     assert(snap_radius <= kMaxSnapRadius());
179   } do {
180     _snapRadius = snap_radius;
181   }
182 
183   override
184   S1Angle snapRadius() const {
185       return _snapRadius;
186   }
187 
188   /// Returns the minimum allowable snap radius for the given S2Cell level
189   /// (approximately equal to half of the maximum cell diagonal length).
190   static S1Angle minSnapRadiusForLevel(int level) {
191     // snap_radius() needs to be an upper bound on the true distance that a
192     // point can move when snapped, taking into account numerical errors.
193     //
194     // The maximum error when converting from an S2Point to an S2CellId is
195     // S2::kMaxDiag.deriv() * DBL_EPSILON.  The maximum error when converting an
196     // S2CellId center back to an S2Point is 1.5 * DBL_EPSILON.  These add up to
197     // just slightly less than 4 * DBL_EPSILON.
198     return S1Angle.fromRadians(0.5 * MAX_DIAG.getValue(level) + 4 * double.epsilon);
199   }
200 
201   /**
202    * Returns the minimum S2Cell level (i.e., largest S2Cells) such that
203    * vertices will not move by more than "snap_radius".  This can be useful
204    * when choosing an appropriate level to snap to.  The return value is
205    * always a valid level (out of range values are silently clamped).
206    *
207    * If you want to choose the snap level based on a distance, and then use
208    * the minimum possible snap radius for the chosen level, do this:
209    *
210    *   S2CellIdSnapFunction f(
211    *       S2CellIdSnapFunction::LevelForMaxSnapRadius(distance));
212    */
213   static int levelForMaxSnapRadius(S1Angle snap_radius) {
214     // When choosing a level, we need to acount for the error bound of
215     // 4 * DBL_EPSILON that is added by MinSnapRadiusForLevel().
216     return MAX_DIAG.getLevelForMaxValue(2 * (snap_radius.radians() - 4 * double.epsilon));
217   }
218 
219   /**
220    * For S2CellId snapping, the minimum separation between vertices depends on
221    * level() and snap_radius().  It can vary between 0.5 * snap_radius()
222    * and snap_radius().
223    */
224   override
225   S1Angle minVertexSeparation() const {
226     // We have three different bounds for the minimum vertex separation: one is
227     // a constant bound, one is proportional to snap_radius, and one is equal to
228     // snap_radius minus a constant.  These bounds give the best results for
229     // small, medium, and large snap radii respectively.  We return the maximum
230     // of the three bounds.
231     //
232     // 1. Constant bound: Vertices are always separated by at least
233     //    kMinEdge(level), the minimum edge length for the chosen snap level.
234     //
235     // 2. Proportional bound: It can be shown that in the plane, the worst-case
236     //    configuration has a vertex separation of 2 / sqrt(13) * snap_radius.
237     //    This is verified in the unit test, except that on the sphere the ratio
238     //    is slightly smaller at cell level 2 (0.54849 vs. 0.55470).  We reduce
239     //    that value a bit more below to be conservative.
240     //
241     // 3. Best asymptotic bound: This bound bound is derived by observing we
242     //    only select a new site when it is at least snap_radius() away from all
243     //    existing sites, and the site can move by at most 0.5 * kMaxDiag(level)
244     //    when snapped.
245     S1Angle min_edge = S1Angle.fromRadians(MIN_EDGE.getValue(_level));
246     S1Angle max_diag = S1Angle.fromRadians(MAX_DIAG.getValue(_level));
247     return max(min_edge,
248         max(0.548 * _snapRadius,  // 2 / sqrt(13) in the plane
249             _snapRadius - 0.5 * max_diag));
250   }
251 
252   /**
253    * For S2CellId snapping, the minimum separation between edges and
254    * non-incident vertices depends on level() and snap_radius().  It can
255    * be as low as 0.219 * snap_radius(), but is typically 0.5 * snap_radius()
256    * or more.
257    */
258   override
259   S1Angle minEdgeVertexSeparation() const {
260     // Similar to min_vertex_separation(), in this case we have four bounds: a
261     // constant bound that holds only at the minimum snap radius, a constant
262     // bound that holds for any snap radius, a bound that is proportional to
263     // snap_radius, and a bound that is equal to snap_radius minus a constant.
264     //
265     // 1. Constant bounds:
266     //
267     //    (a) At the minimum snap radius for a given level, it can be shown that
268     //    vertices are separated from edges by at least 0.5 * kMinDiag(level) in
269     //    the plane.  The unit test verifies this, except that on the sphere the
270     //    worst case is slightly better: 0.5652980068 * kMinDiag(level).
271     //
272     //    (b) Otherwise, for arbitrary snap radii the worst-case configuration
273     //    in the plane has an edge-vertex separation of sqrt(3/19) *
274     //    kMinDiag(level), where sqrt(3/19) is about 0.3973597071.  The unit
275     //    test verifies that the bound is slighty better on the sphere:
276     //    0.3973595687 * kMinDiag(level).
277     //
278     // 2. Proportional bound: In the plane, the worst-case configuration has an
279     //    edge-vertex separation of 2 * sqrt(3/247) * snap_radius, which is
280     //    about 0.2204155075.  The unit test verifies this, except that on the
281     //    sphere the bound is slightly worse for certain large S2Cells: the
282     //    minimum ratio occurs at cell level 6, and is about 0.2196666953.
283     //
284     // 3. Best asymptotic bound: If snap_radius() is large compared to the
285     //    minimum snap radius, then the best bound is achieved by 3 sites on a
286     //    circular arc of radius "snap_radius", spaced "min_vertex_separation"
287     //    apart.  An input edge passing just to one side of the center of the
288     //    circle intersects the Voronoi regions of the two end sites but not the
289     //    Voronoi region of the center site, and gives an edge separation of
290     //    (min_vertex_separation ** 2) / (2 * snap_radius).  This bound
291     //    approaches 0.5 * snap_radius for large snap radii, i.e.  the minimum
292     //    edge-vertex separation approaches half of the minimum vertex
293     //    separation as the snap radius becomes large compared to the cell size.
294 
295     S1Angle min_diag = S1Angle.fromRadians(MIN_DIAG.getValue(_level));
296     if (snapRadius() == minSnapRadiusForLevel(_level)) {
297       // This bound only holds when the minimum snap radius is being used.
298       return 0.565 * min_diag;            // 0.500 in the plane
299     }
300     // Otherwise, these bounds hold for any snap_radius().
301     S1Angle vertex_sep = minVertexSeparation();
302     return max(0.397 * min_diag,          // sqrt(3 / 19) in the plane
303         max(0.219 * _snapRadius,  // 2 * sqrt(3 / 247) in the plane
304             0.5 * (vertex_sep / _snapRadius) * vertex_sep));
305   }
306 
307   override
308   S2Point snapPoint(in S2Point point) const {
309     return S2CellId(point).parent(_level).toS2Point();
310   }
311 
312   override
313   S2Builder.SnapFunction clone() const {
314     return new S2CellIdSnapFunction(this);
315   }
316 
317 private:
318   // Copying and assignment are allowed.
319   int _level;
320   S1Angle _snapRadius;
321 }
322 
323 /**
324  * A SnapFunction that snaps vertices to S2LatLng E5, E6, or E7 coordinates.
325  * These coordinates are expressed in degrees multiplied by a power of 10 and
326  * then rounded to the nearest integer.  For example, in E6 coordinates the
327  * point (23.12345651, -45.65432149) would become (23123457, -45654321).
328  *
329  * The main argument of the SnapFunction is the exponent for the power of 10
330  * that coordinates should be multipled by before rounding.  For example,
331  * IntLatLngSnapFunction(7) is a function that snaps to E7 coordinates.  The
332  * exponent can range from 0 to 10.
333  *
334  * Each exponent has a corresponding minimum snap radius, which is simply the
335  * maximum distance that a vertex can move when snapped.  It is approximately
336  * equal to 1/sqrt(2) times the nominal point spacing; for example, for
337  * snapping to E7 the minimum snap radius is (1e-7 / sqrt(2)) degrees.
338  * You can also set the snap radius to any value larger than this; this can
339  * result in significant extra simplification (similar to using a larger
340  * exponent) but does not move vertices unnecessarily.
341  */
342 class IntLatLngSnapFunction : S2Builder.SnapFunction {
343 public:
344   /// The default constructor yields an invalid snap function.  You must set
345   /// the exponent explicitly before using it.
346   this() {
347     _exponent = -1;
348     _snapRadius = S1Angle();
349     _fromDegrees = 0;
350     _toDegrees = 0;
351   }
352 
353   /// Convenience constructor equivalent to calling set_exponent(exponent).
354   this(int exponent) {
355       setExponent(exponent);
356   }
357 
358   this(in IntLatLngSnapFunction other) {
359     _exponent = other._exponent;
360     _snapRadius = other._snapRadius;
361     _fromDegrees = other._fromDegrees;
362     _toDegrees = other._toDegrees;
363   }
364 
365   /**
366    * Snaps vertices to points whose (lat, lng) coordinates are integers after
367    * converting to degrees and multiplying by 10 raised to the given exponent.
368    * For example, (exponent == 7) yields E7 coordinates.  As a side effect,
369    * this method also resets "snap_radius" to the minimum value allowed for
370    * this exponent:
371    *
372    *   set_snap_radius(MinSnapRadiusForExponent(exponent))
373    *
374    * This means that if you want to use a larger snap radius than the minimum,
375    * you must call set_snap_radius() *after* calling set_exponent().
376    *
377    * REQUIRES: kMinExponent <= exponent <= kMaxExponent
378    */
379   void setExponent(int exponent)
380   in {
381     assert(exponent >= MIN_EXPONENT);
382     assert(exponent <= MAX_EXPONENT);
383   } do {
384     _exponent = exponent;
385     setSnapRadius(minSnapRadiusForExponent(exponent));
386 
387     // Precompute the scale factors needed for snapping.  Note that these
388     // calculations need to exactly match the ones in s1angle.h to ensure
389     // that the same S2Points are generated.
390     double power = 1;
391     for (int i = 0; i < exponent; ++i) power *= 10;
392     _fromDegrees = power;
393     _toDegrees = 1 / power;
394   }
395 
396   int exponent() const {
397     return _exponent;
398   }
399 
400   /// The minum exponent supported for snapping.
401   enum int MIN_EXPONENT = 0;
402 
403   /// The maximum exponent supported for snapping.
404   enum int MAX_EXPONENT = 10;
405 
406   /**
407    * Defines the snap radius to be used (see s2builder.h).  The snap radius
408    * must be at least the minimum value for the current exponent(), but larger
409    * values can also be used (e.g., to simplify the geometry).
410    *
411    * REQUIRES: snap_radius >= MinSnapRadiusForExponent(exponent())
412    * REQUIRES: snap_radius <= SnapFunction::kMaxSnapRadius()
413    */
414   void setSnapRadius(S1Angle snap_radius)
415   in {
416     assert(snap_radius >= minSnapRadiusForExponent(exponent()));
417     assert(snap_radius <= kMaxSnapRadius());
418   } do {
419     _snapRadius = snap_radius;
420   }
421 
422   override
423   S1Angle snapRadius() const {
424     return _snapRadius;
425   }
426 
427   // Returns the minimum allowable snap radius for the given exponent
428   // (approximately equal to (pow(10, -exponent) / sqrt(2)) degrees).
429   static S1Angle minSnapRadiusForExponent(int exponent) {
430     // snap_radius() needs to be an upper bound on the true distance that a
431     // point can move when snapped, taking into account numerical errors.
432     //
433     // The maximum errors in latitude and longitude can be bounded as
434     // follows (as absolute errors in terms of DBL_EPSILON):
435     //
436     //                                      Latitude      Longitude
437     // Convert to S2LatLng:                    1.000          1.000
438     // Convert to degrees:                     1.032          2.063
439     // Scale by 10**exp:                       0.786          1.571
440     // Round to integer: 0.5 * S1Angle::Degrees(to_degrees_)
441     // Scale by 10**(-exp):                    1.375          2.749
442     // Convert to radians:                     1.252          1.503
443     // ------------------------------------------------------------
444     // Total (except for rounding)             5.445          8.886
445     //
446     // The maximum error when converting the S2LatLng back to an S2Point is
447     //
448     //   sqrt(2) * (maximum error in latitude or longitude) + 1.5 * DBL_EPSILON
449     //
450     // which works out to (9 * sqrt(2) + 1.5) * DBL_EPSILON radians.  Finally
451     // we need to consider the effect of rounding to integer coordinates
452     // (much larger than the errors above), which can change the position by
453     // up to (sqrt(2) * 0.5 * to_degrees_) radians.
454     double power = 1;
455     for (int i = 0; i < exponent; ++i) power *= 10;
456     return (S1Angle.fromDegrees(M_SQRT1_2 / power) +
457         S1Angle.fromRadians((9 * M_SQRT2 + 1.5) * double.epsilon));
458   }
459 
460   /**
461    * Returns the minimum exponent such that vertices will not move by more
462    * than "snap_radius".  This can be useful when choosing an appropriate
463    * exponent for snapping.  The return value is always a valid exponent
464    * (out of range values are silently clamped).
465    *
466    * If you want to choose the exponent based on a distance, and then use
467    * the minimum possible snap radius for that exponent, do this:
468    *
469    *   IntLatLngSnapFunction f(
470    *       IntLatLngSnapFunction::ExponentForMaxSnapRadius(distance));
471    */
472   static int exponentForMaxSnapRadius(S1Angle snap_radius) {
473     // When choosing an exponent, we need to acount for the error bound of
474     // (9 * sqrt(2) + 1.5) * DBL_EPSILON added by MinSnapRadiusForExponent().
475     snap_radius -= S1Angle.fromRadians((9 * M_SQRT2 + 1.5) * double.epsilon);
476     snap_radius = max(snap_radius, S1Angle.fromRadians(1e-30));
477     double exponent = log10(M_SQRT1_2 / snap_radius.degrees());
478 
479     // There can be small errors in the calculation above, so to ensure that
480     // this function is the inverse of MinSnapRadiusForExponent() we subtract a
481     // small error tolerance.
482     return max(MIN_EXPONENT,
483         min(MAX_EXPONENT, cast(int) ceil(exponent - 2 * double.epsilon)));
484   }
485 
486   /**
487    * For IntLatLng snapping, the minimum separation between vertices depends on
488    * exponent() and snap_radius().  It can vary between snap_radius()
489    * and snap_radius().
490    */
491   override
492   S1Angle minVertexSeparation() const {
493     // We have two bounds for the minimum vertex separation: one is proportional
494     // to snap_radius, and one is equal to snap_radius minus a constant.  These
495     // bounds give the best results for small and large snap radii respectively.
496     // We return the maximum of the two bounds.
497     //
498     // 1. Proportional bound: It can be shown that in the plane, the worst-case
499     //    configuration has a vertex separation of (sqrt(2) / 3) * snap_radius.
500     //    This is verified in the unit test, except that on the sphere the ratio
501     //    is slightly smaller (0.471337 vs. 0.471404).  We reduce that value a
502     //    bit more below to be conservative.
503     //
504     // 2. Best asymptotic bound: This bound bound is derived by observing we
505     //    only select a new site when it is at least snap_radius() away from all
506     //    existing sites, and snapping a vertex can move it by up to
507     //    ((1 / sqrt(2)) * to_degrees_) degrees.
508     return max(0.471 * _snapRadius,        // sqrt(2) / 3 in the plane
509         _snapRadius - S1Angle.fromDegrees(M_SQRT1_2 * _toDegrees));
510   }
511 
512   /**
513    * For IntLatLng snapping, the minimum separation between edges and
514    * non-incident vertices depends on level() and snap_radius().  It can
515    * be as low as 0.222 * snap_radius(), but is typically 0.39 * snap_radius()
516    * or more.
517    */
518   override
519   S1Angle minEdgeVertexSeparation() const {
520     // Similar to min_vertex_separation(), in this case we have three bounds:
521     // one is a constant bound, one is proportional to snap_radius, and one is
522     // equal to snap_radius minus a constant.
523     //
524     // 1. Constant bound: In the plane, the worst-case configuration has an
525     //    edge-vertex separation of ((1 / sqrt(13)) * to_degrees_) degrees.
526     //    The unit test verifies this, except that on the sphere the ratio is
527     //    slightly lower when small exponents such as E1 are used
528     //    (0.2772589 vs 0.2773501).
529     //
530     // 2. Proportional bound: In the plane, the worst-case configuration has an
531     //    edge-vertex separation of (2 / 9) * snap_radius (0.222222222222).  The
532     //    unit test verifies this, except that on the sphere the bound can be
533     //    slightly worse with large exponents (e.g., E9) due to small numerical
534     //    errors (0.222222126756717).
535     //
536     // 3. Best asymptotic bound: If snap_radius() is large compared to the
537     //    minimum snap radius, then the best bound is achieved by 3 sites on a
538     //    circular arc of radius "snap_radius", spaced "min_vertex_separation"
539     //    apart (see S2CellIdSnapFunction::min_edge_vertex_separation).  This
540     //    bound approaches 0.5 * snap_radius as the snap radius becomes large
541     //    relative to the grid spacing.
542 
543     S1Angle vertex_sep = minVertexSeparation();
544     return max(0.277 * S1Angle.fromDegrees(_toDegrees),  // 1/sqrt(13) in the plane
545         max(0.222 * _snapRadius,               // 2/9 in the plane
546             0.5 * (vertex_sep / _snapRadius) * vertex_sep));
547   }
548 
549   override
550   S2Point snapPoint(in S2Point point) const
551   in {
552     assert(_exponent >= 0);  // Make sure the snap function was initialized.
553   } do {
554     auto input = S2LatLng(point);
555     long lat = lround(input.lat().degrees() * _fromDegrees);
556     long lng = lround(input.lng().degrees() * _fromDegrees);
557     return S2LatLng.fromDegrees(lat * _toDegrees, lng * _toDegrees).toS2Point();
558   }
559 
560   override
561   S2Builder.SnapFunction clone() const {
562     return new IntLatLngSnapFunction(this);
563   }
564 
565 private:
566   // Copying and assignment are allowed.
567   int _exponent;
568   S1Angle _snapRadius;
569   double _fromDegrees;
570   double _toDegrees;
571 }