Coverage Report

Created: 2026-05-30 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s2cap.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/s2cap.h"
19
20
#include <algorithm>
21
#include <cfloat>
22
#include <cmath>
23
#include <ostream>
24
#include <vector>
25
26
#include "absl/flags/flag.h"
27
#include "absl/log/absl_check.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/s2cell.h"
34
#include "s2/s2cell_id.h"
35
#include "s2/s2debug.h"
36
#include "s2/s2edge_distances.h"
37
#include "s2/s2latlng.h"
38
#include "s2/s2latlng_rect.h"
39
#include "s2/s2metrics.h"
40
#include "s2/s2point.h"
41
#include "s2/s2pointutil.h"
42
#include "s2/s2region.h"
43
44
using std::fabs;
45
using std::max;
46
using std::vector;
47
48
0
double S2Cap::GetArea() const {
49
0
  return 2 * M_PI * max(0.0, height());
50
0
}
51
52
0
S2Point S2Cap::GetCentroid() const {
53
  // From symmetry, the centroid of the cap must be somewhere on the line
54
  // from the origin to the center of the cap on the surface of the sphere.
55
  // When a sphere is divided into slices of constant thickness by a set of
56
  // parallel planes, all slices have the same surface area. This implies
57
  // that the radial component of the centroid is simply the midpoint of the
58
  // range of radial distances spanned by the cap. That is easily computed
59
  // from the cap height.
60
0
  if (is_empty()) return S2Point();
61
0
  double r = 1.0 - 0.5 * height();
62
0
  return r * GetArea() * center_;
63
0
}
64
65
0
S2Cap S2Cap::Complement() const {
66
  // The complement of a full cap is an empty cap, not a singleton.
67
  // Also make sure that the complement of an empty cap is full.
68
0
  if (is_full()) return Empty();
69
0
  if (is_empty()) return Full();
70
0
  return S2Cap(-center_, S1ChordAngle::FromLength2(4 - radius_.length2()));
71
0
}
72
73
0
bool S2Cap::Contains(const S2Cap& other) const {
74
0
  if (is_full() || other.is_empty()) return true;
75
0
  return radius_ >= S1ChordAngle(center_, other.center_) + other.radius_;
76
0
}
77
78
0
bool S2Cap::Intersects(const S2Cap& other) const {
79
0
  if (is_empty() || other.is_empty()) return false;
80
0
  return radius_ + other.radius_ >= S1ChordAngle(center_, other.center_);
81
0
}
82
83
0
bool S2Cap::InteriorIntersects(const S2Cap& other) const {
84
  // Make sure this cap has an interior and the other cap is non-empty.
85
0
  if (radius_.length2() <= 0 || other.is_empty()) return false;
86
0
  return radius_ + other.radius_ > S1ChordAngle(center_, other.center_);
87
0
}
88
89
0
void S2Cap::AddPoint(const S2Point& p) {
90
  // Compute the squared chord length, then convert it into a height.
91
0
  ABSL_DCHECK(S2::IsUnitLength(p));
92
0
  if (is_empty()) {
93
0
    center_ = p;
94
0
    radius_ = S1ChordAngle::Zero();
95
0
  } else {
96
    // After calling cap.AddPoint(p), cap.Contains(p) must be true.  However
97
    // we don't need to do anything special to achieve this because Contains()
98
    // does exactly the same distance calculation that we do here.
99
0
    radius_ = max(radius_, S1ChordAngle(center_, p));
100
0
  }
101
0
}
102
103
0
void S2Cap::AddCap(const S2Cap& other) {
104
0
  if (is_empty()) {
105
0
    *this = other;
106
0
  } else if (!other.is_empty()) {
107
    // We round up the distance to ensure that the cap is actually contained.
108
0
    S1ChordAngle dist = S1ChordAngle(center_, other.center_) + other.radius_;
109
0
    dist = dist.PlusError(  //
110
0
        (2 * DBL_EPSILON + S1ChordAngle::kRelativeSumError) * dist.length2());
111
0
    radius_ = max(radius_, dist);
112
0
  }
113
0
}
114
115
0
S2Cap S2Cap::Expanded(S1Angle distance) const {
116
0
  ABSL_DCHECK_GE(distance.radians(), 0);
117
0
  if (is_empty()) return Empty();
118
0
  return S2Cap(center_, radius_ + S1ChordAngle(distance));
119
0
}
120
121
0
S2Cap S2Cap::Union(const S2Cap& other) const {
122
0
  if (radius_ < other.radius_) {
123
0
    return other.Union(*this);
124
0
  }
125
0
  if (is_full() || other.is_empty()) {
126
0
    return *this;
127
0
  }
128
  // This calculation would be more efficient using S1ChordAngles.
129
0
  S1Angle this_radius = GetRadius();
130
0
  S1Angle other_radius = other.GetRadius();
131
0
  S1Angle distance(center(), other.center());
132
0
  if (this_radius >= distance + other_radius) {
133
0
    return *this;
134
0
  } else {
135
0
    S1Angle result_radius = 0.5 * (distance + this_radius + other_radius);
136
0
    S2Point result_center =
137
0
        S2::GetPointOnLine(center(), other.center(),
138
0
                           0.5 * (distance - this_radius + other_radius));
139
0
    return S2Cap(result_center, result_radius);
140
0
  }
141
0
}
142
143
0
S2Region* S2Cap::Clone() const {
144
0
  return new S2Cap(*this);
145
0
}
146
147
0
S2Cap S2Cap::GetCapBound() const {
148
0
  return *this;
149
0
}
150
151
0
S2LatLngRect S2Cap::GetRectBound() const {
152
0
  if (is_empty()) return S2LatLngRect::Empty();
153
154
  // Convert the center to a (lat,lng) pair, and compute the cap angle.
155
0
  S2LatLng center_ll(center_);
156
0
  double cap_angle = GetRadius().radians();
157
158
0
  bool all_longitudes = false;
159
0
  double lat[2], lng[2];
160
0
  lng[0] = -M_PI;
161
0
  lng[1] = M_PI;
162
163
  // Check whether cap includes the south pole.
164
0
  lat[0] = center_ll.lat().radians() - cap_angle;
165
0
  if (lat[0] <= -M_PI_2) {
166
0
    lat[0] = -M_PI_2;
167
0
    all_longitudes = true;
168
0
  }
169
  // Check whether cap includes the north pole.
170
0
  lat[1] = center_ll.lat().radians() + cap_angle;
171
0
  if (lat[1] >= M_PI_2) {
172
0
    lat[1] = M_PI_2;
173
0
    all_longitudes = true;
174
0
  }
175
0
  if (!all_longitudes) {
176
    // Compute the range of longitudes covered by the cap.  We use the law
177
    // of sines for spherical triangles.  Consider the triangle ABC where
178
    // A is the north pole, B is the center of the cap, and C is the point
179
    // of tangency between the cap boundary and a line of longitude.  Then
180
    // C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
181
    // we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
182
    // Here "a" is the cap angle, and "c" is the colatitude (90 degrees
183
    // minus the latitude).  This formula also works for negative latitudes.
184
    //
185
    // The formula for sin(a) follows from the relationship h = 1 - cos(a).
186
187
0
    double sin_a = sin(radius_);
188
0
    double sin_c = cos(center_ll.lat().radians());
189
0
    if (sin_a <= sin_c) {
190
0
      double angle_A = asin(sin_a / sin_c);
191
0
      lng[0] = remainder(center_ll.lng().radians() - angle_A, 2 * M_PI);
192
0
      lng[1] = remainder(center_ll.lng().radians() + angle_A, 2 * M_PI);
193
0
    }
194
0
  }
195
0
  return S2LatLngRect(R1Interval(lat[0], lat[1]),
196
0
                      S1Interval(lng[0], lng[1]));
197
0
}
198
199
// Computes a covering of the S2Cap.  In general the covering consists of at
200
// most 4 cells except for very large caps, which may need up to 6 cells.
201
// The output is not sorted.
202
0
void S2Cap::GetCellUnionBound(vector<S2CellId>* cell_ids) const {
203
  // TODO(ericv): The covering could be made quite a bit tighter by mapping
204
  // the cap to a rectangle in (i,j)-space and finding a covering for that.
205
0
  cell_ids->clear();
206
207
  // Find the maximum level such that the cap contains at most one cell vertex
208
  // and such that S2CellId::AppendVertexNeighbors() can be called.
209
0
  int level = S2::kMinWidth.GetLevelForMinValue(GetRadius().radians()) - 1;
210
211
  // If level < 0, then more than three face cells are required.
212
0
  if (level < 0) {
213
0
    cell_ids->reserve(6);
214
0
    for (int face = 0; face < 6; ++face) {
215
0
      cell_ids->push_back(S2CellId::FromFace(face));
216
0
    }
217
0
  } else {
218
    // The covering consists of the 4 cells at the given level that share the
219
    // cell vertex that is closest to the cap center.
220
0
    cell_ids->reserve(4);
221
0
    S2CellId(center_).AppendVertexNeighbors(level, cell_ids);
222
0
  }
223
0
}
224
225
0
bool S2Cap::Intersects(const S2Cell& cell, const S2Point* vertices) const {
226
  // Return true if this cap intersects any point of 'cell' excluding its
227
  // vertices (which are assumed to already have been checked).
228
229
  // If the cap is a hemisphere or larger, the cell and the complement of the
230
  // cap are both convex.  Therefore since no vertex of the cell is contained,
231
  // no other interior point of the cell is contained either.
232
0
  if (radius_ >= S1ChordAngle::Right()) return false;
233
234
  // We need to check for empty caps due to the center check just below.
235
0
  if (is_empty()) return false;
236
237
  // Optimization: return true if the cell contains the cap center.  (This
238
  // allows half of the edge checks below to be skipped.)
239
0
  if (cell.Contains(center_)) return true;
240
241
  // At this point we know that the cell does not contain the cap center,
242
  // and the cap does not contain any cell vertex.  The only way that they
243
  // can intersect is if the cap intersects the interior of some edge.
244
245
0
  double sin2_angle = sin2(radius_);
246
0
  for (int k = 0; k < 4; ++k) {
247
0
    S2Point edge = cell.GetEdgeRaw(k);
248
0
    double dot = center_.DotProd(edge);
249
0
    if (dot > 0) {
250
      // The center is in the interior half-space defined by the edge.  We don't
251
      // need to consider these edges, since if the cap intersects this edge
252
      // then it also intersects the edge on the opposite side of the cell
253
      // (because we know the center is not contained with the cell).
254
0
      continue;
255
0
    }
256
    // The Norm2() factor is necessary because "edge" is not normalized.
257
0
    if (dot * dot > sin2_angle * edge.Norm2()) {
258
0
      return false;  // Entire cap is on the exterior side of this edge.
259
0
    }
260
    // Otherwise, the great circle containing this edge intersects
261
    // the interior of the cap.  We just need to check whether the point
262
    // of closest approach occurs between the two edge endpoints.
263
0
    Vector3_d dir = edge.CrossProd(center_);
264
0
    if (dir.DotProd(vertices[k]) < 0 && dir.DotProd(vertices[(k+1)&3]) > 0)
265
0
      return true;
266
0
  }
267
0
  return false;
268
0
}
269
270
0
bool S2Cap::Contains(const S2Cell& cell) const {
271
  // If the cap does not contain all cell vertices, return false.
272
  // We check the vertices before taking the Complement() because we can't
273
  // accurately represent the complement of a very small cap (a height
274
  // of 2-epsilon is rounded off to 2).
275
0
  S2Point vertices[4];
276
0
  for (int k = 0; k < 4; ++k) {
277
0
    vertices[k] = cell.GetVertex(k);
278
0
    if (!Contains(vertices[k])) return false;
279
0
  }
280
  // Otherwise, return true if the complement of the cap does not intersect
281
  // the cell.  (This test is slightly conservative, because technically we
282
  // want Complement().InteriorIntersects() here.)
283
0
  return !Complement().Intersects(cell, vertices);
284
0
}
285
286
0
bool S2Cap::MayIntersect(const S2Cell& cell) const {
287
  // If the cap contains any cell vertex, return true.
288
0
  S2Point vertices[4];
289
0
  for (int k = 0; k < 4; ++k) {
290
0
    vertices[k] = cell.GetVertex(k);
291
0
    if (Contains(vertices[k])) return true;
292
0
  }
293
0
  return Intersects(cell, vertices);
294
0
}
295
296
0
bool S2Cap::Contains(const S2Point& p) const {
297
0
  ABSL_DCHECK(S2::IsUnitLength(p));
298
0
  return S1ChordAngle(center_, p) <= radius_;
299
0
}
300
301
0
bool S2Cap::InteriorContains(const S2Point& p) const {
302
0
  ABSL_DCHECK(S2::IsUnitLength(p));
303
0
  return is_full() || S1ChordAngle(center_, p) < radius_;
304
0
}
305
306
0
bool S2Cap::operator==(const S2Cap& other) const {
307
0
  return (center_ == other.center_ && radius_ == other.radius_) ||
308
0
         (is_empty() && other.is_empty()) ||
309
0
         (is_full() && other.is_full());
310
0
}
311
312
0
bool S2Cap::ApproxEquals(const S2Cap& other, S1Angle max_error_angle) const {
313
0
  const double max_error = max_error_angle.radians();
314
0
  const double r2 = radius_.length2();
315
0
  const double other_r2 = other.radius_.length2();
316
0
  return (S2::ApproxEquals(center_, other.center_, max_error_angle) &&
317
0
          fabs(r2 - other_r2) <= max_error) ||
318
0
         (is_empty() && other_r2 <= max_error) ||
319
0
         (other.is_empty() && r2 <= max_error) ||
320
0
         (is_full() && other_r2 >= 2 - max_error) ||
321
0
         (other.is_full() && r2 >= 2 - max_error);
322
0
}
323
324
0
std::ostream& operator<<(std::ostream& os, const S2Cap& cap) {
325
0
  return os << "[Center=" << cap.center()
326
0
            << ", Radius=" << cap.GetRadius() << "]";
327
0
}
328
329
0
void S2Cap::Encode(Encoder* encoder) const {
330
0
  encoder->Ensure(4 * sizeof(double));
331
332
0
  encoder->putdouble(center_.x());
333
0
  encoder->putdouble(center_.y());
334
0
  encoder->putdouble(center_.z());
335
0
  encoder->putdouble(radius_.length2());
336
337
0
  ABSL_DCHECK_GE(encoder->avail(), 0);
338
0
}
339
340
0
bool S2Cap::Decode(Decoder* decoder) {
341
0
  if (decoder->avail() < 4 * sizeof(double)) return false;
342
343
0
  double x = decoder->getdouble();
344
0
  double y = decoder->getdouble();
345
0
  double z = decoder->getdouble();
346
0
  center_ = S2Point(x, y, z);
347
0
  radius_ = S1ChordAngle::FromLength2(decoder->getdouble());
348
349
0
  if (absl::GetFlag(FLAGS_s2debug)) {
350
0
    ABSL_CHECK(is_valid()) << "Invalid S2Cap: " << *this;
351
0
  }
352
0
  return true;
353
0
}