1 // Copyright 2005 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 // Original author: ericv@google.com (Eric Veach)
16 // Converted to D:  madric@gmail.com (Vijay Nayar)
17 
18 module s2.s2latlng_rect_bounder;
19 
20 import algorithm = std.algorithm;
21 import math = std.math;
22 import s2.r1interval;
23 import s2.s1chord_angle;
24 import s2.s1interval;
25 import s2.s2latlng;
26 import s2.s2latlng_rect;
27 import s2.s2point;
28 import s2.s2pointutil;
29 import s2.util.math.vector;
30 
31 /**
32  * This class computes a bounding rectangle that contains all edges defined
33  * by a vertex chain v0, v1, v2, ...  All vertices must be unit length.
34  * Note that the bounding rectangle of an edge can be larger than the
35  * bounding rectangle of its endpoints, e.g. consider an edge that passes
36  * through the north pole.
37  *
38  * The bounds are calculated conservatively to account for numerical errors
39  * when S2Points are converted to S2LatLngs.  More precisely, this class
40  * guarantees the following.  Let L be a closed edge chain (loop) such that
41  * the interior of the loop does not contain either pole.  Now if P is any
42  * point such that L.Contains(P), then RectBound(L).Contains(S2LatLng(P)).
43  */
44 class S2LatLngRectBounder {
45 private:
46   // Common back end for AddPoint() and AddLatLng().  b and b_latlng
47   // must refer to the same vertex.
48   void addInternal(in S2Point b, in S2LatLng b_latlng)
49   in {
50     // Simple consistency check to verify that b and b_latlng are alternate
51     // representations of the same vertex.
52     assert(approxEquals(b, b_latlng.toS2Point()));
53   } do {
54     if (_bound.isEmpty()) {
55       _bound.addPoint(b_latlng);
56     } else {
57       // First compute the cross product N = A x B robustly.  This is the normal
58       // to the great circle through A and B.  We don't use S2::RobustCrossProd()
59       // since that method returns an arbitrary vector orthogonal to A if the two
60       // vectors are proportional, and we want the zero vector in that case.
61       Vector3_d n = (_a - b).crossProd(_a + b);  // N = 2 * (A x B)
62 
63       // The relative error in N gets large as its norm gets very small (i.e.,
64       // when the two points are nearly identical or antipodal).  We handle this
65       // by choosing a maximum allowable error, and if the error is greater than
66       // this we fall back to a different technique.  Since it turns out that
67       // the other sources of error in converting the normal to a maximum
68       // latitude add up to at most 1.16 * DBL_EPSILON (see below), and it is
69       // desirable to have the total error be a multiple of DBL_EPSILON, we have
70       // chosen to limit the maximum error in the normal to 3.84 * DBL_EPSILON.
71       // It is possible to show that the error is less than this when
72       //
73       //   n.Norm() >= 8 * sqrt(3) / (3.84 - 0.5 - sqrt(3)) * DBL_EPSILON
74       //            = 1.91346e-15 (about 8.618 * DBL_EPSILON)
75       double n_norm = n.norm();
76       if (n_norm < 1.91346e-15) {
77         // A and B are either nearly identical or nearly antipodal (to within
78         // 4.309 * DBL_EPSILON, or about 6 nanometers on the earth's surface).
79         if (_a.dotProd(b) < 0) {
80           // The two points are nearly antipodal.  The easiest solution is to
81           // assume that the edge between A and B could go in any direction
82           // around the sphere.
83           _bound = S2LatLngRect.full();
84         } else {
85           // The two points are nearly identical (to within 4.309 * DBL_EPSILON).
86           // In this case we can just use the bounding rectangle of the points,
87           // since after the expansion done by GetBound() this rectangle is
88           // guaranteed to include the (lat,lng) values of all points along AB.
89           _bound = _bound.unite(S2LatLngRect.fromPointPair(_aLatLng, b_latlng));
90         }
91       } else {
92         // Compute the longitude range spanned by AB.
93         S1Interval lng_ab = S1Interval.fromPointPair(
94             _aLatLng.lng().radians(), b_latlng.lng().radians());
95         if (lng_ab.getLength() >= M_PI - 2 * double.epsilon) {
96           // The points lie on nearly opposite lines of longitude to within the
97           // maximum error of the calculation.  (Note that this test relies on
98           // the fact that M_PI is slightly less than the true value of Pi, and
99           // that representable values near M_PI are 2 * DBL_EPSILON apart.)
100           // The easiest solution is to assume that AB could go on either side
101           // of the pole.
102           lng_ab = S1Interval.full();
103         }
104 
105         // Next we compute the latitude range spanned by the edge AB.  We start
106         // with the range spanning the two endpoints of the edge:
107         R1Interval lat_ab = R1Interval.fromPointPair(
108             _aLatLng.lat().radians(), b_latlng.lat().radians());
109 
110         // This is the desired range unless the edge AB crosses the plane
111         // through N and the Z-axis (which is where the great circle through A
112         // and B attains its minimum and maximum latitudes).  To test whether AB
113         // crosses this plane, we compute a vector M perpendicular to this
114         // plane and then project A and B onto it.
115         Vector3_d m = n.crossProd(S2Point(0, 0, 1));
116         double m_a = m.dotProd(_a);
117         double m_b = m.dotProd(b);
118 
119         // We want to test the signs of "m_a" and "m_b", so we need to bound
120         // the error in these calculations.  It is possible to show that the
121         // total error is bounded by
122         //
123         //  (1 + sqrt(3)) * DBL_EPSILON * n_norm + 8 * sqrt(3) * (DBL_EPSILON**2)
124         //    = 6.06638e-16 * n_norm + 6.83174e-31
125 
126         double m_error = 6.06638e-16 * n_norm + 6.83174e-31;
127         if (m_a * m_b < 0 || math.fabs(m_a) <= m_error || math.fabs(m_b) <= m_error) {
128           // Minimum/maximum latitude *may* occur in the edge interior.
129           //
130           // The maximum latitude is 90 degrees minus the latitude of N.  We
131           // compute this directly using atan2 in order to get maximum accuracy
132           // near the poles.
133           //
134           // Our goal is compute a bound that contains the computed latitudes of
135           // all S2Points P that pass the point-in-polygon containment test.
136           // There are three sources of error we need to consider:
137           //  - the directional error in N (at most 3.84 * DBL_EPSILON)
138           //  - converting N to a maximum latitude
139           //  - computing the latitude of the test point P
140           // The latter two sources of error are at most 0.955 * DBL_EPSILON
141           // individually, but it is possible to show by a more complex analysis
142           // that together they can add up to at most 1.16 * DBL_EPSILON, for a
143           // total error of 5 * DBL_EPSILON.
144           //
145           // We add 3 * DBL_EPSILON to the bound here, and GetBound() will pad
146           // the bound by another 2 * DBL_EPSILON.
147           double max_lat = algorithm.min(
148               math.atan2(math.sqrt(n[0]*n[0] + n[1]*n[1]), math.fabs(n[2])) + 3 * double.epsilon,
149               math.PI_2);
150 
151           // In order to get tight bounds when the two points are close together,
152           // we also bound the min/max latitude relative to the latitudes of the
153           // endpoints A and B.  First we compute the distance between A and B,
154           // and then we compute the maximum change in latitude between any two
155           // points along the great circle that are separated by this distance.
156           // This gives us a latitude change "budget".  Some of this budget must
157           // be spent getting from A to B; the remainder bounds the round-trip
158           // distance (in latitude) from A or B to the min or max latitude
159           // attained along the edge AB.
160           double lat_budget = 2 * math.asin(0.5 * (_a - b).norm() * math.sin(max_lat));
161           double max_delta = 0.5 * (lat_budget - lat_ab.getLength()) + double.epsilon;
162 
163           // Test whether AB passes through the point of maximum latitude or
164           // minimum latitude.  If the dot product(s) are small enough then the
165           // result may be ambiguous.
166           if (m_a <= m_error && m_b >= -m_error) {
167             lat_ab.setHi(algorithm.min(max_lat, lat_ab.hi() + max_delta));
168           }
169           if (m_b <= m_error && m_a >= -m_error) {
170             lat_ab.setLo(algorithm.max(-max_lat, lat_ab.lo() - max_delta));
171           }
172         }
173         _bound = _bound.unite(new S2LatLngRect(lat_ab, lng_ab));
174       }
175     }
176     _a = b;
177     _aLatLng = b_latlng;
178   }
179 
180   S2Point _a;             // The previous vertex in the chain.
181   S2LatLng _aLatLng;     // The corresponding latitude-longitude.
182   S2LatLngRect _bound;    // The current bounding rectangle.
183 
184 public:
185   this() {
186     _bound = S2LatLngRect.empty();
187   }
188 
189   /**
190    * This method is called to add a vertex to the chain when the vertex is
191    * represented as an S2Point.  Requires that 'b' has unit length.  Repeated
192    * vertices are ignored.
193    */
194   void addPoint(in S2Point b)
195   in {
196     assert(isUnitLength(b));
197   } do {
198     addInternal(b, S2LatLng(b));
199   }
200 
201   /**
202    * This method is called to add a vertex to the chain when the vertex is
203    * represented as an S2LatLng.  Repeated vertices are ignored.
204    */
205   void addLatLng(in S2LatLng b_latlng) {
206     addInternal(b_latlng.toS2Point(), b_latlng);
207   }
208 
209   /**
210    * Returns the bounding rectangle of the edge chain that connects the
211    * vertices defined so far.  This bound satisfies the guarantee made
212    * above, i.e. if the edge chain defines a loop, then the bound contains
213    * the S2LatLng coordinates of all S2Points contained by the loop.
214    */
215   S2LatLngRect getBound() const {
216     // To save time, we ignore numerical errors in the computed S2LatLngs while
217     // accumulating the bounds and then account for them here.
218     //
219     // S2LatLng(S2Point) has a maximum error of 0.955 * DBL_EPSILON in latitude.
220     // In the worst case, we might have rounded "inwards" when computing the
221     // bound and "outwards" when computing the latitude of a contained point P,
222     // therefore we expand the latitude bounds by 2 * DBL_EPSILON in each
223     // direction.  (A more complex analysis shows that 1.5 * DBL_EPSILON is
224     // enough, but the expansion amount should be a multiple of DBL_EPSILON in
225     // order to avoid rounding errors during the expansion itself.)
226     //
227     // S2LatLng(S2Point) has a maximum error of DBL_EPSILON in longitude, which
228     // is simply the maximum rounding error for results in the range [-Pi, Pi].
229     // This is true because the Gnu implementation of atan2() comes from the IBM
230     // Accurate Mathematical Library, which implements correct rounding for this
231     // instrinsic (i.e., it returns the infinite precision result rounded to the
232     // nearest representable value, with ties rounded to even values).  This
233     // implies that we don't need to expand the longitude bounds at all, since
234     // we only guarantee that the bound contains the *rounded* latitudes of
235     // contained points.  The *true* latitudes of contained points may lie up to
236     // DBL_EPSILON outside of the returned bound.
237 
238     const S2LatLng kExpansion = S2LatLng.fromRadians(2 * double.epsilon, 0);
239     return _bound.expanded(kExpansion).polarClosure();
240   }
241 
242   /**
243    * Expands a bound returned by GetBound() so that it is guaranteed to
244    * contain the bounds of any subregion whose bounds are computed using
245    * this class.  For example, consider a loop L that defines a square.
246    * GetBound() ensures that if a point P is contained by this square, then
247    * S2LatLng(P) is contained by the bound.  But now consider a diamond
248    * shaped loop S contained by L.  It is possible that GetBound() returns a
249    * *larger* bound for S than it does for L, due to rounding errors.  This
250    * method expands the bound for L so that it is guaranteed to contain the
251    * bounds of any subregion S.
252    *
253    * More precisely, if L is a loop that does not contain either pole, and S
254    * is a loop such that L.Contains(S), then
255    *
256    *   ExpandForSubregions(RectBound(L)).Contains(RectBound(S)).
257    */
258   static S2LatLngRect expandForSubregions(in S2LatLngRect bound) {
259     // Empty bounds don't need expansion.
260     if (bound.isEmpty()) return new S2LatLngRect(bound);
261 
262     // First we need to check whether the bound B contains any nearly-antipodal
263     // points (to within 4.309 * DBL_EPSILON).  If so then we need to return
264     // S2LatLngRect::Full(), since the subregion might have an edge between two
265     // such points, and AddPoint() returns Full() for such edges.  Note that
266     // this can happen even if B is not Full(); for example, consider a loop
267     // that defines a 10km strip straddling the equator extending from
268     // longitudes -100 to +100 degrees.
269     //
270     // It is easy to check whether B contains any antipodal points, but checking
271     // for nearly-antipodal points is trickier.  Essentially we consider the
272     // original bound B and its reflection through the origin B', and then test
273     // whether the minimum distance between B and B' is less than 4.309 *
274     // DBL_EPSILON.
275 
276     // "lng_gap" is a lower bound on the longitudinal distance between B and its
277     // reflection B'.  (2.5 * DBL_EPSILON is the maximum combined error of the
278     // endpoint longitude calculations and the GetLength() call.)
279     double lng_gap = algorithm.max(0.0, M_PI - bound.lng().getLength() - 2.5 * double.epsilon);
280 
281     // "min_abs_lat" is the minimum distance from B to the equator (if zero or
282     // negative, then B straddles the equator).
283     double min_abs_lat = algorithm.max(bound.lat().lo(), -bound.lat().hi());
284 
285     // "lat_gap1" and "lat_gap2" measure the minimum distance from B to the
286     // south and north poles respectively.
287     double lat_gap1 = math.PI_2 + bound.lat().lo();
288     double lat_gap2 = math.PI_2 - bound.lat().hi();
289 
290     if (min_abs_lat >= 0) {
291       // The bound B does not straddle the equator.  In this case the minimum
292       // distance is between one endpoint of the latitude edge in B closest to
293       // the equator and the other endpoint of that edge in B'.  The latitude
294       // distance between these two points is 2*min_abs_lat, and the longitude
295       // distance is lng_gap.  We could compute the distance exactly using the
296       // Haversine formula, but then we would need to bound the errors in that
297       // calculation.  Since we only need accuracy when the distance is very
298       // small (close to 4.309 * DBL_EPSILON), we substitute the Euclidean
299       // distance instead.  This gives us a right triangle XYZ with two edges of
300       // length x = 2*min_abs_lat and y ~= lng_gap.  The desired distance is the
301       // length of the third edge "z", and we have
302       //
303       //         z  ~=  sqrt(x^2 + y^2)  >=  (x + y) / sqrt(2)
304       //
305       // Therefore the region may contain nearly antipodal points only if
306       //
307       //  2*min_abs_lat + lng_gap  <  sqrt(2) * 4.309 * DBL_EPSILON
308       //                           ~= 1.354e-15
309       //
310       // Note that because the given bound B is conservative, "min_abs_lat" and
311       // "lng_gap" are both lower bounds on their true values so we do not need
312       // to make any adjustments for their errors.
313       if (2 * min_abs_lat + lng_gap < 1.354e-15) {
314         return S2LatLngRect.full();
315       }
316     } else if (lng_gap >= math.PI_2) {
317       // B spans at most Pi/2 in longitude.  The minimum distance is always
318       // between one corner of B and the diagonally opposite corner of B'.  We
319       // use the same distance approximation that we used above; in this case
320       // we have an obtuse triangle XYZ with two edges of length x = lat_gap1
321       // and y = lat_gap2, and angle Z >= Pi/2 between them.  We then have
322       //
323       //         z  >=  sqrt(x^2 + y^2)  >=  (x + y) / sqrt(2)
324       //
325       // Unlike the case above, "lat_gap1" and "lat_gap2" are not lower bounds
326       // (because of the extra addition operation, and because M_PI_2 is not
327       // exactly equal to Pi/2); they can exceed their true values by up to
328       // 0.75 * DBL_EPSILON.  Putting this all together, the region may
329       // contain nearly antipodal points only if
330       //
331       //   lat_gap1 + lat_gap2  <  (sqrt(2) * 4.309 + 1.5) * DBL_EPSILON
332       //                        ~= 1.687e-15
333       if (lat_gap1 + lat_gap2 < 1.687e-15) {
334         return S2LatLngRect.full();
335       }
336     } else {
337       // Otherwise we know that (1) the bound straddles the equator and (2) its
338       // width in longitude is at least Pi/2.  In this case the minimum
339       // distance can occur either between a corner of B and the diagonally
340       // opposite corner of B' (as in the case above), or between a corner of B
341       // and the opposite longitudinal edge reflected in B'.  It is sufficient
342       // to only consider the corner-edge case, since this distance is also a
343       // lower bound on the corner-corner distance when that case applies.
344 
345       // Consider the spherical triangle XYZ where X is a corner of B with
346       // minimum absolute latitude, Y is the closest pole to X, and Z is the
347       // point closest to X on the opposite longitudinal edge of B'.  This is a
348       // right triangle (Z = Pi/2), and from the spherical law of sines we have
349       //
350       //     sin(z) / sin(Z)  =  sin(y) / sin(Y)
351       //     sin(max_lat_gap) / 1  =  sin(d_min) / sin(lng_gap)
352       //     sin(d_min)  =  sin(max_lat_gap) * sin(lng_gap)
353       //
354       // where "max_lat_gap" = max(lat_gap1, lat_gap2) and "d_min" is the
355       // desired minimum distance.  Now using the facts that sin(t) >= (2/Pi)*t
356       // for 0 <= t <= Pi/2, that we only need an accurate approximation when
357       // at least one of "max_lat_gap" or "lng_gap" is extremely small (in
358       // which case sin(t) ~= t), and recalling that "max_lat_gap" has an error
359       // of up to 0.75 * DBL_EPSILON, we want to test whether
360       //
361       //   max_lat_gap * lng_gap  <  (4.309 + 0.75) * (Pi/2) * DBL_EPSILON
362       //                          ~= 1.765e-15
363       if (algorithm.max(lat_gap1, lat_gap2) * lng_gap < 1.765e-15) {
364         return S2LatLngRect.full();
365       }
366     }
367     // Next we need to check whether the subregion might contain any edges that
368     // span (M_PI - 2 * DBL_EPSILON) radians or more in longitude, since AddPoint
369     // sets the longitude bound to Full() in that case.  This corresponds to
370     // testing whether (lng_gap <= 0) in "lng_expansion" below.
371 
372     // Otherwise, the maximum latitude error in AddPoint is 4.8 * DBL_EPSILON.
373     // In the worst case, the errors when computing the latitude bound for a
374     // subregion could go in the opposite direction as the errors when computing
375     // the bound for the original region, so we need to double this value.
376     // (More analysis shows that it's okay to round down to a multiple of
377     // DBL_EPSILON.)
378     //
379     // For longitude, we rely on the fact that atan2 is correctly rounded and
380     // therefore no additional bounds expansion is necessary.
381 
382     double lat_expansion = 9 * double.epsilon;
383     double lng_expansion = (lng_gap <= 0) ? M_PI : 0;
384     return bound.expanded(S2LatLng.fromRadians(lat_expansion, lng_expansion)).polarClosure();
385   }
386 
387   // Returns the maximum error in GetBound() provided that the result does
388   // not include either pole.  It is only to be used for testing purposes
389   // (e.g., by passing it to S2LatLngRect::ApproxEquals).
390   static S2LatLng maxErrorForTests() {
391     // The maximum error in the latitude calculation is
392     //    3.84 * DBL_EPSILON   for the RobustCrossProd calculation
393     //    0.96 * DBL_EPSILON   for the Latitude() calculation
394     //    5    * DBL_EPSILON   added by AddPoint/GetBound to compensate for error
395     //    ------------------
396     //    9.80 * DBL_EPSILON   maximum error in result
397     //
398     // The maximum error in the longitude calculation is DBL_EPSILON.  GetBound
399     // does not do any expansion because this isn't necessary in order to
400     // bound the *rounded* longitudes of contained points.
401     return S2LatLng.fromRadians(10 * double.epsilon, 1 * double.epsilon);
402   }
403 }