Coverage Report

Created: 2026-06-13 06:25

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