Coverage Report

Created: 2025-10-28 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s2latlng_rect.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.h"
19
20
#include <algorithm>
21
#include <cfloat>
22
#include <cmath>
23
#include <ostream>
24
25
#include "absl/flags/flag.h"
26
#include "absl/log/absl_check.h"
27
#include "absl/log/absl_log.h"
28
#include "s2/util/coding/coder.h"
29
#include "s2/r1interval.h"
30
#include "s2/s1angle.h"
31
#include "s2/s1chord_angle.h"
32
#include "s2/s1interval.h"
33
#include "s2/s2cap.h"
34
#include "s2/s2cell.h"
35
#include "s2/s2debug.h"
36
#include "s2/s2edge_crossings.h"
37
#include "s2/s2edge_distances.h"
38
#include "s2/s2latlng.h"
39
#include "s2/s2point.h"
40
#include "s2/s2pointutil.h"
41
42
using std::fabs;
43
using std::max;
44
using std::min;
45
using std::vector;
46
47
static const unsigned char kCurrentLosslessEncodingVersionNumber = 1;
48
49
S2LatLngRect S2LatLngRect::FromCenterSize(const S2LatLng& center,
50
0
                                          const S2LatLng& size) {
51
0
  return FromPoint(center).Expanded(0.5 * size);
52
0
}
53
54
0
S2LatLngRect S2LatLngRect::FromPoint(const S2LatLng& p) {
55
0
  ABSL_DLOG_IF(ERROR, !p.is_valid())
56
0
      << "Invalid S2LatLng in S2LatLngRect::GetDistance: " << p;
57
58
0
  return S2LatLngRect(p, p);
59
0
}
60
61
S2LatLngRect S2LatLngRect::FromPointPair(const S2LatLng& p1,
62
0
                                         const S2LatLng& p2) {
63
0
  ABSL_DLOG_IF(ERROR, !p1.is_valid())
64
0
      << "Invalid S2LatLng in S2LatLngRect::FromPointPair: " << p1;
65
0
  ABSL_DLOG_IF(ERROR, !p2.is_valid())
66
0
      << "Invalid S2LatLng in S2LatLngRect::FromPointPair: " << p2;
67
68
0
  return S2LatLngRect(R1Interval::FromPointPair(p1.lat().radians(),
69
0
                                                p2.lat().radians()),
70
0
                      S1Interval::FromPointPair(p1.lng().radians(),
71
0
                                                p2.lng().radians()));
72
0
}
73
74
0
S2LatLngRect* S2LatLngRect::Clone() const {
75
0
  return new S2LatLngRect(*this);
76
0
}
77
78
0
S2LatLng S2LatLngRect::GetVertex(int k) const {
79
  // Twiddle bits to return the points in CCW order (lower left, lower right,
80
  // upper right, upper left).
81
0
  int i = (k >> 1) & 1;
82
0
  return S2LatLng::FromRadians(lat_[i], lng_[i ^ (k & 1)]);
83
0
}
84
85
0
S2LatLng S2LatLngRect::GetCenter() const {
86
0
  return S2LatLng::FromRadians(lat_.GetCenter(), lng_.GetCenter());
87
0
}
88
89
0
S2LatLng S2LatLngRect::GetSize() const {
90
0
  return S2LatLng::FromRadians(lat_.GetLength(), lng_.GetLength());
91
0
}
92
93
0
double S2LatLngRect::Area() const {
94
0
  if (is_empty()) return 0.0;
95
  // This is the size difference of the two spherical caps, multiplied by
96
  // the longitude ratio.
97
0
  return lng().GetLength() * (sin(lat_hi()) - sin(lat_lo()));
98
0
}
99
100
0
S2Point S2LatLngRect::GetCentroid() const {
101
  // When a sphere is divided into slices of constant thickness by a set of
102
  // parallel planes, all slices have the same surface area.  This implies
103
  // that the z-component of the centroid is simply the midpoint of the
104
  // z-interval spanned by the S2LatLngRect.
105
  //
106
  // Similarly, it is easy to see that the (x,y) of the centroid lies in the
107
  // plane through the midpoint of the rectangle's longitude interval.  We
108
  // only need to determine the distance "d" of this point from the z-axis.
109
  //
110
  // Let's restrict our attention to a particular z-value.  In this z-plane,
111
  // the S2LatLngRect is a circular arc.  The centroid of this arc lies on a
112
  // radial line through the midpoint of the arc, and at a distance from the
113
  // z-axis of
114
  //
115
  //     r * (sin(alpha) / alpha)
116
  //
117
  // where r = sqrt(1-z^2) is the radius of the arc, and "alpha" is half of
118
  // the arc length (i.e., the arc covers longitudes [-alpha, alpha]).
119
  //
120
  // To find the centroid distance from the z-axis for the entire rectangle,
121
  // we just need to integrate over the z-interval.  This gives
122
  //
123
  //    d = Integrate[sqrt(1-z^2)*sin(alpha)/alpha, z1..z2] / (z2 - z1)
124
  //
125
  // where [z1, z2] is the range of z-values covered by the rectangle.  This
126
  // simplifies to
127
  //
128
  //    d = sin(alpha)/(2*alpha*(z2-z1))*(z2*r2 - z1*r1 + theta2 - theta1)
129
  //
130
  // where [theta1, theta2] is the latitude interval, z1=sin(theta1),
131
  // z2=sin(theta2), r1=cos(theta1), and r2=cos(theta2).
132
  //
133
  // Finally, we want to return not the centroid itself, but the centroid
134
  // scaled by the area of the rectangle.  The area of the rectangle is
135
  //
136
  //    A = 2 * alpha * (z2 - z1)
137
  //
138
  // which fortunately appears in the denominator of "d".
139
140
0
  if (is_empty()) return S2Point();
141
0
  const S1Angle::SinCosPair lo = lat_lo().SinCos();
142
0
  const double z1 = lo.sin;
143
0
  const double r1 = lo.cos;
144
0
  const S1Angle::SinCosPair hi = lat_hi().SinCos();
145
0
  const double z2 = hi.sin;
146
0
  const double r2 = hi.cos;
147
0
  const double alpha = 0.5 * lng_.GetLength();
148
0
  const double r = sin(alpha) * (r2 * z2 - r1 * z1 + lat_.GetLength());
149
0
  const S1Angle lng = S1Angle::Radians(lng_.GetCenter());
150
0
  const double z = alpha * (z2 + z1) * (z2 - z1);  // scaled by the area
151
0
  const S1Angle::SinCosPair center = lng.SinCos();
152
0
  return S2Point(r * center.cos, r * center.sin, z);
153
0
}
154
155
0
bool S2LatLngRect::Contains(const S2LatLng& ll) const {
156
0
  ABSL_DLOG_IF(ERROR, !ll.is_valid())
157
0
      << "Invalid S2LatLng in S2LatLngRect::Contains: " << ll;
158
159
0
  return (lat_.Contains(ll.lat().radians()) &&
160
0
          lng_.Contains(ll.lng().radians()));
161
0
}
162
163
0
bool S2LatLngRect::InteriorContains(const S2Point& p) const {
164
0
  return InteriorContains(S2LatLng(p));
165
0
}
166
167
0
bool S2LatLngRect::InteriorContains(const S2LatLng& ll) const {
168
0
  ABSL_DLOG_IF(ERROR, !ll.is_valid())
169
0
      << "Invalid S2LatLng in S2LatLngRect::InteriorContains: " << ll;
170
171
0
  return (lat_.InteriorContains(ll.lat().radians()) &&
172
0
          lng_.InteriorContains(ll.lng().radians()));
173
0
}
174
175
0
bool S2LatLngRect::Contains(const S2LatLngRect& other) const {
176
0
  return lat_.Contains(other.lat_) && lng_.Contains(other.lng_);
177
0
}
178
179
0
bool S2LatLngRect::InteriorContains(const S2LatLngRect& other) const {
180
0
  return (lat_.InteriorContains(other.lat_) &&
181
0
          lng_.InteriorContains(other.lng_));
182
0
}
183
184
0
bool S2LatLngRect::Intersects(const S2LatLngRect& other) const {
185
0
  return lat_.Intersects(other.lat_) && lng_.Intersects(other.lng_);
186
0
}
187
188
0
bool S2LatLngRect::InteriorIntersects(const S2LatLngRect& other) const {
189
0
  return (lat_.InteriorIntersects(other.lat_) &&
190
0
          lng_.InteriorIntersects(other.lng_));
191
0
}
192
193
bool S2LatLngRect::BoundaryIntersects(const S2Point& v0,
194
0
                                      const S2Point& v1) const {
195
0
  if (is_empty()) return false;
196
0
  if (!lng_.is_full()) {
197
0
    if (IntersectsLngEdge(v0, v1, lat_, lng_.lo())) return true;
198
0
    if (IntersectsLngEdge(v0, v1, lat_, lng_.hi())) return true;
199
0
  }
200
0
  if (lat_.lo() != -M_PI_2 && IntersectsLatEdge(v0, v1, lat_.lo(), lng_)) {
201
0
    return true;
202
0
  }
203
0
  if (lat_.hi() != M_PI_2 && IntersectsLatEdge(v0, v1, lat_.hi(), lng_)) {
204
0
    return true;
205
0
  }
206
0
  return false;
207
0
}
208
209
0
void S2LatLngRect::AddPoint(const S2Point& p) {
210
0
  AddPoint(S2LatLng(p));
211
0
}
212
213
0
void S2LatLngRect::AddPoint(const S2LatLng& ll) {
214
0
  ABSL_DLOG_IF(ERROR, !ll.is_valid())
215
0
      << "Invalid S2LatLng in S2LatLngRect::AddPoint: " << ll;
216
217
0
  lat_.AddPoint(ll.lat().radians());
218
0
  lng_.AddPoint(ll.lng().radians());
219
0
}
220
221
0
S2LatLngRect S2LatLngRect::Expanded(const S2LatLng& margin) const {
222
0
  R1Interval lat = lat_.Expanded(margin.lat().radians());
223
0
  S1Interval lng = lng_.Expanded(margin.lng().radians());
224
0
  if (lat.is_empty() || lng.is_empty()) return Empty();
225
0
  return S2LatLngRect(lat.Intersection(FullLat()), lng);
226
0
}
227
228
0
S2LatLngRect S2LatLngRect::PolarClosure() const {
229
0
  if (lat_.lo() == -M_PI_2 || lat_.hi() == M_PI_2) {
230
0
    return S2LatLngRect(lat_, S1Interval::Full());
231
0
  }
232
0
  return *this;
233
0
}
234
235
0
S2LatLngRect S2LatLngRect::Union(const S2LatLngRect& other) const {
236
0
  return S2LatLngRect(lat_.Union(other.lat_),
237
0
                      lng_.Union(other.lng_));
238
0
}
239
240
0
S2LatLngRect S2LatLngRect::Intersection(const S2LatLngRect& other) const {
241
0
  R1Interval lat = lat_.Intersection(other.lat_);
242
0
  S1Interval lng = lng_.Intersection(other.lng_);
243
0
  if (lat.is_empty() || lng.is_empty()) {
244
    // The lat/lng ranges must either be both empty or both non-empty.
245
0
    return Empty();
246
0
  }
247
0
  return S2LatLngRect(lat, lng);
248
0
}
249
250
0
S2LatLngRect S2LatLngRect::ExpandedByDistance(S1Angle distance) const {
251
0
  if (distance >= S1Angle::Zero()) {
252
    // The most straightforward approach is to build a cap centered on each
253
    // vertex and take the union of all the bounding rectangles (including the
254
    // original rectangle; this is necessary for very large rectangles).
255
256
    // TODO(ericv): Update this code to use an algorithm like the one below.
257
0
    S1ChordAngle radius(distance);
258
0
    S2LatLngRect r = *this;
259
0
    for (int k = 0; k < 4; ++k) {
260
0
      r = r.Union(S2Cap(GetVertex(k).ToPoint(), radius).GetRectBound());
261
0
    }
262
0
    return r;
263
0
  } else {
264
    // Shrink the latitude interval unless the latitude interval contains a pole
265
    // and the longitude interval is full, in which case the rectangle has no
266
    // boundary at that pole.
267
0
    R1Interval lat_result(
268
0
        lat().lo() <= FullLat().lo() && lng().is_full() ?
269
0
            FullLat().lo() : lat().lo() - distance.radians(),
270
0
        lat().hi() >= FullLat().hi() && lng().is_full() ?
271
0
            FullLat().hi() : lat().hi() + distance.radians());
272
0
    if (lat_result.is_empty()) {
273
0
      return S2LatLngRect::Empty();
274
0
    }
275
276
    // Maximum absolute value of a latitude in lat_result. At this latitude,
277
    // the cap occupies the largest longitude interval.
278
0
    double max_abs_lat = max(-lat_result.lo(), lat_result.hi());
279
280
    // Compute the largest longitude interval that the cap occupies. We use the
281
    // law of sines for spherical triangles. For the details, see the comment in
282
    // S2Cap::GetRectBound().
283
    //
284
    // When sin_a >= sin_c, the cap covers all the latitude.
285
0
    double sin_a = sin(-distance.radians());
286
0
    double sin_c = cos(max_abs_lat);
287
0
    double max_lng_margin = sin_a < sin_c ? asin(sin_a / sin_c) : M_PI_2;
288
289
0
    S1Interval lng_result = lng().Expanded(-max_lng_margin);
290
0
    if (lng_result.is_empty()) {
291
0
      return S2LatLngRect::Empty();
292
0
    }
293
0
    return S2LatLngRect(lat_result, lng_result);
294
0
  }
295
0
}
296
297
0
S2Cap S2LatLngRect::GetCapBound() const {
298
  // We consider two possible bounding caps, one whose axis passes
299
  // through the center of the lat-long rectangle and one whose axis
300
  // is the north or south pole.  We return the smaller of the two caps.
301
302
0
  if (is_empty()) return S2Cap::Empty();
303
304
0
  double pole_z, pole_angle;
305
0
  if (lat_.lo() + lat_.hi() < 0) {
306
    // South pole axis yields smaller cap.
307
0
    pole_z = -1;
308
0
    pole_angle = M_PI_2 + lat_.hi();
309
0
  } else {
310
0
    pole_z = 1;
311
0
    pole_angle = M_PI_2 - lat_.lo();
312
0
  }
313
  // Ensure that the bounding cap is conservative taking into account errors
314
  // in the arithmetic above and the S1Angle/S1ChordAngle conversion.
315
0
  S2Cap pole_cap(S2Point(0, 0, pole_z),
316
0
                 S1Angle::Radians((1 + 2 * DBL_EPSILON) * pole_angle));
317
318
  // For bounding rectangles that span 180 degrees or less in longitude, the
319
  // maximum cap size is achieved at one of the rectangle vertices.  For
320
  // rectangles that are larger than 180 degrees, we punt and always return a
321
  // bounding cap centered at one of the two poles.
322
0
  if (lng_.GetLength() < 2 * M_PI) {
323
0
    S2Cap mid_cap(GetCenter().ToPoint(), S1Angle::Zero());
324
0
    for (int k = 0; k < 4; ++k) {
325
0
      mid_cap.AddPoint(GetVertex(k).ToPoint());
326
0
    }
327
0
    if (mid_cap.height() < pole_cap.height())
328
0
      return mid_cap;
329
0
  }
330
0
  return pole_cap;
331
0
}
332
333
0
S2LatLngRect S2LatLngRect::GetRectBound() const {
334
0
  return *this;
335
0
}
336
337
0
void S2LatLngRect::GetCellUnionBound(vector<S2CellId>* cell_ids) const {
338
  // TODO(user): Is there a tighter bound?
339
0
  GetCapBound().GetCellUnionBound(cell_ids);
340
0
}
341
342
0
bool S2LatLngRect::Contains(const S2Cell& cell) const {
343
  // A latitude-longitude rectangle contains a cell if and only if it contains
344
  // the cell's bounding rectangle.  This test is exact from a mathematical
345
  // point of view, assuming that the bounds returned by S2Cell::GetRectBound()
346
  // are tight.  However, note that there can be a loss of precision when
347
  // converting between representations -- for example, if an S2Cell is
348
  // converted to a polygon, the polygon's bounding rectangle may not contain
349
  // the cell's bounding rectangle.  This has some slightly unexpected side
350
  // effects; for instance, if one creates an S2Polygon from an S2Cell, the
351
  // polygon will contain the cell, but the polygon's bounding box will not.
352
0
  return Contains(cell.GetRectBound());
353
0
}
354
355
0
bool S2LatLngRect::MayIntersect(const S2Cell& cell) const {
356
  // This test is cheap but is NOT exact (see s2latlng_rect.h).
357
0
  return Intersects(cell.GetRectBound());
358
0
}
359
360
0
void S2LatLngRect::Encode(Encoder* encoder) const {
361
0
  encoder->Ensure(40);  // sufficient
362
363
0
  encoder->put8(kCurrentLosslessEncodingVersionNumber);
364
0
  encoder->putdouble(lat_.lo());
365
0
  encoder->putdouble(lat_.hi());
366
0
  encoder->putdouble(lng_.lo());
367
0
  encoder->putdouble(lng_.hi());
368
369
0
  ABSL_DCHECK_GE(encoder->avail(), 0);
370
0
}
371
372
0
bool S2LatLngRect::Decode(Decoder* decoder) {
373
0
  if (decoder->avail() < sizeof(unsigned char) + 4 * sizeof(double))
374
0
    return false;
375
0
  unsigned char version = decoder->get8();
376
0
  if (version > kCurrentLosslessEncodingVersionNumber) return false;
377
378
0
  double lat_lo = decoder->getdouble();
379
0
  double lat_hi = decoder->getdouble();
380
0
  lat_ = R1Interval(lat_lo, lat_hi);
381
0
  double lng_lo = decoder->getdouble();
382
0
  double lng_hi = decoder->getdouble();
383
0
  lng_ = S1Interval(lng_lo, lng_hi);
384
385
0
  if (!is_valid()) {
386
0
    ABSL_DLOG_IF(ERROR, absl::GetFlag(FLAGS_s2debug))
387
0
        << "Invalid result in S2LatLngRect::Decode: " << *this;
388
0
    return false;
389
0
  }
390
391
0
  return true;
392
0
}
393
394
bool S2LatLngRect::IntersectsLngEdge(const S2Point& a, const S2Point& b,
395
0
                                     const R1Interval& lat, double lng) {
396
  // Return true if the segment AB intersects the given edge of constant
397
  // longitude.  The nice thing about edges of constant longitude is that
398
  // they are straight lines on the sphere (geodesics).
399
400
0
  return S2::CrossingSign(
401
0
      a, b, S2LatLng::FromRadians(lat.lo(), lng).ToPoint(),
402
0
      S2LatLng::FromRadians(lat.hi(), lng).ToPoint()) > 0;
403
0
}
404
405
bool S2LatLngRect::IntersectsLatEdge(const S2Point& a, const S2Point& b,
406
0
                                     double lat, const S1Interval& lng) {
407
  // Return true if the segment AB intersects the given edge of constant
408
  // latitude.  Unfortunately, lines of constant latitude are curves on
409
  // the sphere.  They can intersect a straight edge in 0, 1, or 2 points.
410
0
  ABSL_DCHECK(S2::IsUnitLength(a));
411
0
  ABSL_DCHECK(S2::IsUnitLength(b));
412
413
  // First, compute the normal to the plane AB that points vaguely north.
414
0
  Vector3_d z = S2::RobustCrossProd(a, b).Normalize();
415
0
  if (z[2] < 0) z = -z;
416
417
  // Extend this to an orthonormal frame (x,y,z) where x is the direction
418
  // where the great circle through AB achieves its maximum latitude.
419
0
  Vector3_d y = S2::RobustCrossProd(z, S2Point(0, 0, 1)).Normalize();
420
0
  Vector3_d x = y.CrossProd(z);
421
0
  ABSL_DCHECK(S2::IsUnitLength(x));
422
0
  ABSL_DCHECK_GE(x[2], 0);
423
424
  // Compute the angle "theta" from the x-axis (in the x-y plane defined
425
  // above) where the great circle intersects the given line of latitude.
426
0
  double sin_lat = sin(lat);
427
0
  if (fabs(sin_lat) >= x[2]) {
428
0
    return false;  // The great circle does not reach the given latitude.
429
0
  }
430
0
  ABSL_DCHECK_GT(x[2], 0);
431
0
  double cos_theta = sin_lat / x[2];
432
0
  double sin_theta = sqrt(1 - cos_theta * cos_theta);
433
0
  double theta = atan2(sin_theta, cos_theta);
434
435
  // The candidate intersection points are located +/- theta in the x-y
436
  // plane.  For an intersection to be valid, we need to check that the
437
  // intersection point is contained in the interior of the edge AB and
438
  // also that it is contained within the given longitude interval "lng".
439
440
  // Compute the range of theta values spanned by the edge AB.
441
0
  S1Interval ab_theta = S1Interval::FromPointPair(
442
0
      atan2(a.DotProd(y), a.DotProd(x)),
443
0
      atan2(b.DotProd(y), b.DotProd(x)));
444
445
0
  if (ab_theta.Contains(theta)) {
446
    // Check if the intersection point is also in the given "lng" interval.
447
0
    S2Point isect = x * cos_theta + y * sin_theta;
448
0
    if (lng.Contains(atan2(isect[1], isect[0]))) return true;
449
0
  }
450
0
  if (ab_theta.Contains(-theta)) {
451
    // Check if the intersection point is also in the given "lng" interval.
452
0
    S2Point isect = x * cos_theta - y * sin_theta;
453
0
    if (lng.Contains(atan2(isect[1], isect[0]))) return true;
454
0
  }
455
0
  return false;
456
0
}
457
458
0
bool S2LatLngRect::Intersects(const S2Cell& cell) const {
459
  // First we eliminate the cases where one region completely contains the
460
  // other.  Once these are disposed of, then the regions will intersect
461
  // if and only if their boundaries intersect.
462
463
0
  if (is_empty()) return false;
464
0
  if (Contains(cell.GetCenterRaw())) return true;
465
0
  if (cell.Contains(GetCenter().ToPoint())) return true;
466
467
  // Quick rejection test (not required for correctness).
468
0
  if (!Intersects(cell.GetRectBound())) return false;
469
470
  // Precompute the cell vertices as points and latitude-longitudes.  We also
471
  // check whether the S2Cell contains any corner of the rectangle, or
472
  // vice-versa, since the edge-crossing tests only check the edge interiors.
473
474
0
  S2Point cell_v[4];
475
0
  S2LatLng cell_ll[4];
476
0
  for (int i = 0; i < 4; ++i) {
477
0
    cell_v[i] = cell.GetVertex(i);  // Must be normalized.
478
0
    cell_ll[i] = S2LatLng(cell_v[i]);
479
0
    if (Contains(cell_ll[i])) return true;
480
0
    if (cell.Contains(GetVertex(i).ToPoint())) return true;
481
0
  }
482
483
  // Now check whether the boundaries intersect.  Unfortunately, a
484
  // latitude-longitude rectangle does not have straight edges -- two edges
485
  // are curved, and at least one of them is concave.
486
487
0
  for (int i = 0; i < 4; ++i) {
488
0
    S1Interval edge_lng = S1Interval::FromPointPair(
489
0
        cell_ll[i].lng().radians(), cell_ll[(i+1)&3].lng().radians());
490
0
    if (!lng_.Intersects(edge_lng)) continue;
491
492
0
    const S2Point& a = cell_v[i];
493
0
    const S2Point& b = cell_v[(i+1)&3];
494
0
    if (edge_lng.Contains(lng_.lo())) {
495
0
      if (IntersectsLngEdge(a, b, lat_, lng_.lo())) return true;
496
0
    }
497
0
    if (edge_lng.Contains(lng_.hi())) {
498
0
      if (IntersectsLngEdge(a, b, lat_, lng_.hi())) return true;
499
0
    }
500
0
    if (IntersectsLatEdge(a, b, lat_.lo(), lng_)) return true;
501
0
    if (IntersectsLatEdge(a, b, lat_.hi(), lng_)) return true;
502
0
  }
503
0
  return false;
504
0
}
505
506
0
S1Angle S2LatLngRect::GetDistance(const S2LatLngRect& other) const {
507
0
  const S2LatLngRect& a = *this;
508
0
  const S2LatLngRect& b = other;
509
0
  ABSL_DCHECK(!a.is_empty());
510
0
  ABSL_DCHECK(!b.is_empty());
511
512
  // First, handle the trivial cases where the longitude intervals overlap.
513
0
  if (a.lng().Intersects(b.lng())) {
514
0
    if (a.lat().Intersects(b.lat()))
515
0
      return S1Angle::Radians(0);  // Intersection between a and b.
516
517
    // We found an overlap in the longitude interval, but not in the latitude
518
    // interval. This means the shortest path travels along some line of
519
    // longitude connecting the high-latitude of the lower rect with the
520
    // low-latitude of the higher rect.
521
0
    S1Angle lo, hi;
522
0
    if (a.lat().lo() > b.lat().hi()) {
523
0
      lo = b.lat_hi();
524
0
      hi = a.lat_lo();
525
0
    } else {
526
0
      lo = a.lat_hi();
527
0
      hi = b.lat_lo();
528
0
    }
529
0
    return hi - lo;
530
0
  }
531
532
  // The longitude intervals don't overlap. In this case, the closest points
533
  // occur somewhere on the pair of longitudinal edges which are nearest in
534
  // longitude-space.
535
0
  S1Angle a_lng, b_lng;
536
0
  S1Interval lo_hi = S1Interval::FromPointPair(a.lng().lo(), b.lng().hi());
537
0
  S1Interval hi_lo = S1Interval::FromPointPair(a.lng().hi(), b.lng().lo());
538
0
  if (lo_hi.GetLength() < hi_lo.GetLength()) {
539
0
    a_lng = a.lng_lo();
540
0
    b_lng = b.lng_hi();
541
0
  } else {
542
0
    a_lng = a.lng_hi();
543
0
    b_lng = b.lng_lo();
544
0
  }
545
546
  // The shortest distance between the two longitudinal segments will include at
547
  // least one segment endpoint. We could probably narrow this down further to a
548
  // single point-edge distance by comparing the relative latitudes of the
549
  // endpoints, but for the sake of clarity, we'll do all four point-edge
550
  // distance tests.
551
0
  S2Point a_lo = S2LatLng(a.lat_lo(), a_lng).ToPoint();
552
0
  S2Point a_hi = S2LatLng(a.lat_hi(), a_lng).ToPoint();
553
0
  S2Point b_lo = S2LatLng(b.lat_lo(), b_lng).ToPoint();
554
0
  S2Point b_hi = S2LatLng(b.lat_hi(), b_lng).ToPoint();
555
0
  return min(S2::GetDistance(a_lo, b_lo, b_hi),
556
0
         min(S2::GetDistance(a_hi, b_lo, b_hi),
557
0
         min(S2::GetDistance(b_lo, a_lo, a_hi),
558
0
             S2::GetDistance(b_hi, a_lo, a_hi))));
559
0
}
560
561
0
S1Angle S2LatLngRect::GetDistance(const S2LatLng& p) const {
562
  // The algorithm here is the same as in GetDistance(S2LatLngRect), only
563
  // with simplified calculations.
564
0
  const S2LatLngRect& a = *this;
565
0
  ABSL_DLOG_IF(ERROR, a.is_empty())
566
0
      << "Empty S2LatLngRect in S2LatLngRect::GetDistance: " << a;
567
0
  ABSL_DLOG_IF(ERROR, !p.is_valid())
568
0
      << "Invalid S2LatLng in S2LatLngRect::GetDistance: " << p;
569
570
0
  if (a.lng().Contains(p.lng().radians())) {
571
0
    return S1Angle::Radians(max(0.0, max(p.lat().radians() - a.lat().hi(),
572
0
                                         a.lat().lo() - p.lat().radians())));
573
0
  }
574
575
0
  S1Interval interval(a.lng().hi(), a.lng().GetComplementCenter());
576
0
  double a_lng;
577
0
  if (interval.Contains(p.lng().radians())) {
578
0
    a_lng = a.lng().hi();
579
0
  } else {
580
0
    a_lng = a.lng().lo();
581
0
  }
582
0
  S2Point lo = S2LatLng::FromRadians(a.lat().lo(), a_lng).ToPoint();
583
0
  S2Point hi = S2LatLng::FromRadians(a.lat().hi(), a_lng).ToPoint();
584
0
  return S2::GetDistance(p.ToPoint(), lo, hi);
585
0
}
586
587
0
S1Angle S2LatLngRect::GetHausdorffDistance(const S2LatLngRect& other) const {
588
0
  return max(GetDirectedHausdorffDistance(other),
589
0
             other.GetDirectedHausdorffDistance(*this));
590
0
}
591
592
S1Angle S2LatLngRect::GetDirectedHausdorffDistance(
593
0
    const S2LatLngRect& other) const {
594
0
  if (is_empty()) {
595
0
    return S1Angle::Radians(0);
596
0
  }
597
0
  if (other.is_empty()) {
598
0
    return S1Angle::Radians(M_PI);  // maximum possible distance on S2
599
0
  }
600
601
0
  double lng_distance = lng().GetDirectedHausdorffDistance(other.lng());
602
0
  ABSL_DCHECK_GE(lng_distance, 0);
603
0
  return GetDirectedHausdorffDistance(lng_distance, lat(), other.lat());
604
0
}
605
606
// Return the directed Hausdorff distance from one longitudinal edge spanning
607
// latitude range 'a_lat' to the other longitudinal edge spanning latitude
608
// range 'b_lat', with their longitudinal difference given by 'lng_diff'.
609
S1Angle S2LatLngRect::GetDirectedHausdorffDistance(
610
0
    double lng_diff, const R1Interval& a, const R1Interval& b) {
611
  // By symmetry, we can assume a's longitude is 0 and b's longitude is
612
  // lng_diff. Call b's two endpoints b_lo and b_hi. Let H be the hemisphere
613
  // containing a and delimited by the longitude line of b. The Voronoi diagram
614
  // of b on H has three edges (portions of great circles) all orthogonal to b
615
  // and meeting at b_lo cross b_hi.
616
  // E1: (b_lo, b_lo cross b_hi)
617
  // E2: (b_hi, b_lo cross b_hi)
618
  // E3: (-b_mid, b_lo cross b_hi), where b_mid is the midpoint of b
619
  //
620
  // They subdivide H into three Voronoi regions. Depending on how longitude 0
621
  // (which contains edge a) intersects these regions, we distinguish two cases:
622
  // Case 1: it intersects three regions. This occurs when lng_diff <= M_PI_2.
623
  // Case 2: it intersects only two regions. This occurs when lng_diff > M_PI_2.
624
  //
625
  // In the first case, the directed Hausdorff distance to edge b can only be
626
  // realized by the following points on a:
627
  // A1: two endpoints of a.
628
  // A2: intersection of a with the equator, if b also intersects the equator.
629
  //
630
  // In the second case, the directed Hausdorff distance to edge b can only be
631
  // realized by the following points on a:
632
  // B1: two endpoints of a.
633
  // B2: intersection of a with E3
634
  // B3: farthest point from b_lo to the interior of D, and farthest point from
635
  //     b_hi to the interior of U, if any, where D (resp. U) is the portion
636
  //     of edge a below (resp. above) the intersection point from B2.
637
638
0
  ABSL_DCHECK_GE(lng_diff, 0);
639
0
  ABSL_DCHECK_LE(lng_diff, M_PI);
640
641
0
  if (lng_diff == 0) {
642
0
    return S1Angle::Radians(a.GetDirectedHausdorffDistance(b));
643
0
  }
644
645
  // Assumed longitude of b.
646
0
  double b_lng = lng_diff;
647
  // Two endpoints of b.
648
0
  S2Point b_lo = S2LatLng::FromRadians(b.lo(), b_lng).ToPoint();
649
0
  S2Point b_hi = S2LatLng::FromRadians(b.hi(), b_lng).ToPoint();
650
651
  // Handling of each case outlined at the top of the function starts here.
652
  // This is initialized a few lines below.
653
0
  S1Angle max_distance;
654
655
  // Cases A1 and B1.
656
0
  S2Point a_lo = S2LatLng::FromRadians(a.lo(), 0).ToPoint();
657
0
  S2Point a_hi = S2LatLng::FromRadians(a.hi(), 0).ToPoint();
658
0
  max_distance = S2::GetDistance(a_lo, b_lo, b_hi);
659
0
  max_distance = max(
660
0
      max_distance, S2::GetDistance(a_hi, b_lo, b_hi));
661
662
0
  if (lng_diff <= M_PI_2) {
663
    // Case A2.
664
0
    if (a.Contains(0) && b.Contains(0)) {
665
0
      max_distance = max(max_distance, S1Angle::Radians(lng_diff));
666
0
    }
667
0
  } else {
668
    // Case B2.
669
0
    const S2Point& p = GetBisectorIntersection(b, b_lng);
670
0
    double p_lat = S2LatLng::Latitude(p).radians();
671
0
    if (a.Contains(p_lat)) {
672
0
      max_distance = max(max_distance, S1Angle(p, b_lo));
673
0
    }
674
675
    // Case B3.
676
0
    if (p_lat > a.lo()) {
677
0
      max_distance = max(max_distance, GetInteriorMaxDistance(
678
0
          R1Interval(a.lo(), min(p_lat, a.hi())), b_lo));
679
0
    }
680
0
    if (p_lat < a.hi()) {
681
0
      max_distance = max(max_distance, GetInteriorMaxDistance(
682
0
          R1Interval(max(p_lat, a.lo()), a.hi()), b_hi));
683
0
    }
684
0
  }
685
686
0
  return max_distance;
687
0
}
688
689
// Return the intersection of longitude 0 with the bisector of an edge
690
// on longitude 'lng' and spanning latitude range 'lat'.
691
S2Point S2LatLngRect::GetBisectorIntersection(const R1Interval& lat,
692
0
                                              double lng) {
693
0
  lng = fabs(lng);
694
0
  double lat_center = lat.GetCenter();
695
  // A vector orthogonal to the bisector of the given longitudinal edge.
696
0
  S2LatLng ortho_bisector;
697
0
  if (lat_center >= 0) {
698
0
    ortho_bisector = S2LatLng::FromRadians(lat_center - M_PI_2, lng);
699
0
  } else {
700
0
    ortho_bisector = S2LatLng::FromRadians(-lat_center - M_PI_2, lng - M_PI);
701
0
  }
702
  // A vector orthogonal to longitude 0.
703
0
  static const S2Point ortho_lng = S2Point(0, -1, 0);
704
0
  return S2::RobustCrossProd(ortho_lng, ortho_bisector.ToPoint());
705
0
}
706
707
// Return max distance from a point b to the segment spanning latitude range
708
// a_lat on longitude 0, if the max occurs in the interior of a_lat. Otherwise
709
// return -1.
710
S1Angle S2LatLngRect::GetInteriorMaxDistance(const R1Interval& a_lat,
711
0
                                             const S2Point& b) {
712
  // Longitude 0 is in the y=0 plane. b.x() >= 0 implies that the maximum
713
  // does not occur in the interior of a_lat.
714
0
  if (a_lat.is_empty() || b.x() >= 0) return S1Angle::Radians(-1);
715
716
  // Project b to the y=0 plane. The antipodal of the normalized projection is
717
  // the point at which the maximum distance from b occurs, if it is contained
718
  // in a_lat.
719
0
  S2Point intersection_point = S2Point(-b.x(), 0, -b.z()).Normalize();
720
0
  if (a_lat.InteriorContains(
721
0
      S2LatLng::Latitude(intersection_point).radians())) {
722
0
    return S1Angle(b, intersection_point);
723
0
  } else {
724
0
    return S1Angle::Radians(-1);
725
0
  }
726
0
}
727
728
0
bool S2LatLngRect::Contains(const S2Point& p) const {
729
0
  return Contains(S2LatLng(p));
730
0
}
731
732
bool S2LatLngRect::ApproxEquals(const S2LatLngRect& other,
733
0
                                S1Angle max_error) const {
734
0
  return (lat_.ApproxEquals(other.lat_, max_error.radians()) &&
735
0
          lng_.ApproxEquals(other.lng_, max_error.radians()));
736
0
}
737
738
bool S2LatLngRect::ApproxEquals(const S2LatLngRect& other,
739
0
                                const S2LatLng& max_error) const {
740
0
  return (lat_.ApproxEquals(other.lat_, max_error.lat().radians()) &&
741
0
          lng_.ApproxEquals(other.lng_, max_error.lng().radians()));
742
0
}
743
744
0
std::ostream& operator<<(std::ostream& os, const S2LatLngRect& r) {
745
0
  return os << "[Lo" << r.lo() << ", Hi" << r.hi() << "]";
746
0
}