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 }