Coverage Report

Created: 2025-11-15 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s1interval.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/s1interval.h"
19
20
#include <algorithm>
21
#include <cfloat>
22
#include <cmath>
23
24
#include "absl/log/absl_check.h"
25
26
using std::fabs;
27
using std::max;
28
29
0
S1Interval S1Interval::FromPoint(double p) {
30
0
  if (p == -M_PI) p = M_PI;
31
0
  return S1Interval(p, p, ARGS_CHECKED);
32
0
}
33
34
0
double S1Interval::GetCenter() const {
35
0
  double center = 0.5 * (lo() + hi());
36
0
  if (!is_inverted()) return center;
37
  // Return the center in the range (-Pi, Pi].
38
0
  return (center <= 0) ? (center + M_PI) : (center - M_PI);
39
0
}
40
41
0
double S1Interval::GetLength() const {
42
0
  double length = hi() - lo();
43
0
  if (length >= 0) return length;
44
0
  length += 2 * M_PI;
45
  // Empty intervals have a negative length.
46
0
  return (length > 0) ? length : -1;
47
0
}
48
49
0
S1Interval S1Interval::Complement() const {
50
0
  if (lo() == hi()) return Full();   // Singleton.
51
0
  return S1Interval(hi(), lo(), ARGS_CHECKED);  // Handles empty and full.
52
0
}
53
54
0
double S1Interval::GetComplementCenter() const {
55
0
  if (lo() != hi()) {
56
0
    return Complement().GetCenter();
57
0
  } else {  // Singleton.
58
0
    return (hi() <= 0) ? (hi() + M_PI) : (hi() - M_PI);
59
0
  }
60
0
}
61
62
0
bool S1Interval::FastContains(double p) const {
63
0
  if (is_inverted()) {
64
0
    return (p >= lo() || p <= hi()) && !is_empty();
65
0
  } else {
66
0
    return p >= lo() && p <= hi();
67
0
  }
68
0
}
69
70
0
bool S1Interval::Contains(double p) const {
71
  // Works for empty, full, and singleton intervals.
72
0
  ABSL_DCHECK_LE(fabs(p), M_PI);
73
0
  if (p == -M_PI) p = M_PI;
74
0
  return FastContains(p);
75
0
}
76
77
0
bool S1Interval::InteriorContains(double p) const {
78
  // Works for empty, full, and singleton intervals.
79
0
  ABSL_DCHECK_LE(fabs(p), M_PI);
80
0
  if (p == -M_PI) p = M_PI;
81
82
0
  if (is_inverted()) {
83
0
    return p > lo() || p < hi();
84
0
  } else {
85
0
    return (p > lo() && p < hi()) || is_full();
86
0
  }
87
0
}
88
89
0
bool S1Interval::Contains(const S1Interval& y) const {
90
  // It might be helpful to compare the structure of these tests to
91
  // the simpler Contains(double) method above.
92
93
0
  if (is_inverted()) {
94
0
    if (y.is_inverted()) return y.lo() >= lo() && y.hi() <= hi();
95
0
    return (y.lo() >= lo() || y.hi() <= hi()) && !is_empty();
96
0
  } else {
97
0
    if (y.is_inverted()) return is_full() || y.is_empty();
98
0
    return y.lo() >= lo() && y.hi() <= hi();
99
0
  }
100
0
}
101
102
0
bool S1Interval::InteriorContains(const S1Interval& y) const {
103
0
  if (is_inverted()) {
104
0
    if (!y.is_inverted()) return y.lo() > lo() || y.hi() < hi();
105
0
    return (y.lo() > lo() && y.hi() < hi()) || y.is_empty();
106
0
  } else {
107
0
    if (y.is_inverted()) return is_full() || y.is_empty();
108
0
    return (y.lo() > lo() && y.hi() < hi()) || is_full();
109
0
  }
110
0
}
111
112
0
bool S1Interval::Intersects(const S1Interval& y) const {
113
0
  if (is_empty() || y.is_empty()) return false;
114
0
  if (is_inverted()) {
115
    // Every non-empty inverted interval contains Pi.
116
0
    return y.is_inverted() || y.lo() <= hi() || y.hi() >= lo();
117
0
  } else {
118
0
    if (y.is_inverted()) return y.lo() <= hi() || y.hi() >= lo();
119
0
    return y.lo() <= hi() && y.hi() >= lo();
120
0
  }
121
0
}
122
123
0
bool S1Interval::InteriorIntersects(const S1Interval& y) const {
124
0
  if (is_empty() || y.is_empty() || lo() == hi()) return false;
125
0
  if (is_inverted()) {
126
0
    return y.is_inverted() || y.lo() < hi() || y.hi() > lo();
127
0
  } else {
128
0
    if (y.is_inverted()) return y.lo() < hi() || y.hi() > lo();
129
0
    return (y.lo() < hi() && y.hi() > lo()) || is_full();
130
0
  }
131
0
}
132
133
0
inline static double PositiveDistance(double a, double b) {
134
  // Compute the distance from "a" to "b" in the range [0, 2*Pi).
135
  // This is equivalent to (remainder(b - a - M_PI, 2 * M_PI) + M_PI),
136
  // except that it is more numerically stable (it does not lose
137
  // precision for very small positive distances).
138
0
  double d = b - a;
139
0
  if (d >= 0) return d;
140
  // We want to ensure that if b == Pi and a == (-Pi + eps),
141
  // the return result is approximately 2*Pi and not zero.
142
0
  return (b + M_PI) - (a - M_PI);
143
0
}
144
145
0
double S1Interval::GetDirectedHausdorffDistance(const S1Interval& y) const {
146
0
  if (y.Contains(*this)) return 0.0;  // this includes the case *this is empty
147
0
  if (y.is_empty()) return M_PI;  // maximum possible distance on S1
148
149
0
  double y_complement_center = y.GetComplementCenter();
150
0
  if (Contains(y_complement_center)) {
151
0
    return PositiveDistance(y.hi(), y_complement_center);
152
0
  } else {
153
    // The Hausdorff distance is realized by either two hi() endpoints or two
154
    // lo() endpoints, whichever is farther apart.
155
0
    double hi_hi = S1Interval(y.hi(), y_complement_center).Contains(hi()) ?
156
0
        PositiveDistance(y.hi(), hi()) : 0;
157
0
    double lo_lo = S1Interval(y_complement_center, y.lo()).Contains(lo()) ?
158
0
        PositiveDistance(lo(), y.lo()) : 0;
159
0
    ABSL_DCHECK(hi_hi > 0 || lo_lo > 0);
160
0
    return max(hi_hi, lo_lo);
161
0
  }
162
0
}
163
164
0
void S1Interval::AddPoint(double p) {
165
0
  ABSL_DCHECK_LE(fabs(p), M_PI);
166
0
  if (p == -M_PI) p = M_PI;
167
168
0
  if (FastContains(p)) return;
169
0
  if (is_empty()) {
170
0
    set_hi(p);
171
0
    set_lo(p);
172
0
  } else {
173
    // Compute distance from p to each endpoint.
174
0
    double dlo = PositiveDistance(p, lo());
175
0
    double dhi = PositiveDistance(hi(), p);
176
0
    if (dlo < dhi) {
177
0
      set_lo(p);
178
0
    } else {
179
0
      set_hi(p);
180
0
    }
181
    // Adding a point can never turn a non-full interval into a full one.
182
0
  }
183
0
}
184
185
0
double S1Interval::Project(double p) const {
186
0
  ABSL_DCHECK(!is_empty());
187
0
  ABSL_DCHECK_LE(fabs(p), M_PI);
188
0
  if (p == -M_PI) p = M_PI;
189
0
  if (FastContains(p)) return p;
190
  // Compute distance from p to each endpoint.
191
0
  double dlo = PositiveDistance(p, lo());
192
0
  double dhi = PositiveDistance(hi(), p);
193
0
  return (dlo < dhi) ? lo() : hi();
194
0
}
195
196
0
S1Interval S1Interval::FromPointPair(double p1, double p2) {
197
0
  ABSL_DCHECK_LE(fabs(p1), M_PI);
198
0
  ABSL_DCHECK_LE(fabs(p2), M_PI);
199
0
  if (p1 == -M_PI) p1 = M_PI;
200
0
  if (p2 == -M_PI) p2 = M_PI;
201
0
  if (PositiveDistance(p1, p2) <= M_PI) {
202
0
    return S1Interval(p1, p2, ARGS_CHECKED);
203
0
  } else {
204
0
    return S1Interval(p2, p1, ARGS_CHECKED);
205
0
  }
206
0
}
207
208
0
S1Interval S1Interval::Expanded(double margin) const {
209
0
  if (margin >= 0) {
210
0
    if (is_empty()) return *this;
211
    // Check whether this interval will be full after expansion, allowing
212
    // for a 1-bit rounding error when computing each endpoint.
213
0
    if (GetLength() + 2 * margin + 2 * DBL_EPSILON >= 2 * M_PI) return Full();
214
0
  } else {
215
0
    if (is_full()) return *this;
216
    // Check whether this interval will be empty after expansion, allowing
217
    // for a 1-bit rounding error when computing each endpoint.
218
0
    if (GetLength() + 2 * margin - 2 * DBL_EPSILON <= 0) return Empty();
219
0
  }
220
0
  S1Interval result(remainder(lo() - margin, 2*M_PI),
221
0
                    remainder(hi() + margin, 2*M_PI));
222
0
  if (result.lo() <= -M_PI) result.set_lo(M_PI);
223
0
  return result;
224
0
}
225
226
0
S1Interval S1Interval::Union(const S1Interval& y) const {
227
  // The y.is_full() case is handled correctly in all cases by the code
228
  // below, but can follow three separate code paths depending on whether
229
  // this interval is inverted, is non-inverted but contains Pi, or neither.
230
231
0
  if (y.is_empty()) return *this;
232
0
  if (FastContains(y.lo())) {
233
0
    if (FastContains(y.hi())) {
234
      // Either this interval contains y, or the union of the two
235
      // intervals is the Full() interval.
236
0
      if (Contains(y)) return *this;  // is_full() code path
237
0
      return Full();
238
0
    }
239
0
    return S1Interval(lo(), y.hi(), ARGS_CHECKED);
240
0
  }
241
0
  if (FastContains(y.hi())) return S1Interval(y.lo(), hi(), ARGS_CHECKED);
242
243
  // This interval contains neither endpoint of y.  This means that either y
244
  // contains all of this interval, or the two intervals are disjoint.
245
0
  if (is_empty() || y.FastContains(lo())) return y;
246
247
  // Check which pair of endpoints are closer together.
248
0
  double dlo = PositiveDistance(y.hi(), lo());
249
0
  double dhi = PositiveDistance(hi(), y.lo());
250
0
  if (dlo < dhi) {
251
0
    return S1Interval(y.lo(), hi(), ARGS_CHECKED);
252
0
  } else {
253
0
    return S1Interval(lo(), y.hi(), ARGS_CHECKED);
254
0
  }
255
0
}
256
257
0
S1Interval S1Interval::Intersection(const S1Interval& y) const {
258
  // The y.is_full() case is handled correctly in all cases by the code
259
  // below, but can follow three separate code paths depending on whether
260
  // this interval is inverted, is non-inverted but contains Pi, or neither.
261
262
0
  if (y.is_empty()) return Empty();
263
0
  if (FastContains(y.lo())) {
264
0
    if (FastContains(y.hi())) {
265
      // Either this interval contains y, or the region of intersection
266
      // consists of two disjoint subintervals.  In either case, we want
267
      // to return the shorter of the two original intervals.
268
0
      if (y.GetLength() < GetLength()) return y;  // is_full() code path
269
0
      return *this;
270
0
    }
271
0
    return S1Interval(y.lo(), hi(), ARGS_CHECKED);
272
0
  }
273
0
  if (FastContains(y.hi())) return S1Interval(lo(), y.hi(), ARGS_CHECKED);
274
275
  // This interval contains neither endpoint of y.  This means that either y
276
  // contains all of this interval, or the two intervals are disjoint.
277
278
0
  if (y.FastContains(lo())) return *this;  // is_empty() okay here
279
0
  ABSL_DCHECK(!Intersects(y));
280
0
  return Empty();
281
0
}
282
283
0
bool S1Interval::ApproxEquals(const S1Interval& y, double max_error) const {
284
  // Full and empty intervals require special cases because the "endpoints"
285
  // are considered to be positioned arbitrarily.
286
0
  if (is_empty()) return y.GetLength() <= 2 * max_error;
287
0
  if (y.is_empty()) return GetLength() <= 2 * max_error;
288
0
  if (is_full()) return y.GetLength() >= 2 * (M_PI - max_error);
289
0
  if (y.is_full()) return GetLength() >= 2 * (M_PI - max_error);
290
291
  // The purpose of the last test below is to verify that moving the endpoints
292
  // does not invert the interval, e.g. [-1e20, 1e20] vs. [1e20, -1e20].
293
0
  return (fabs(remainder(y.lo() - lo(), 2 * M_PI)) <= max_error &&
294
0
          fabs(remainder(y.hi() - hi(), 2 * M_PI)) <= max_error &&
295
0
          fabs(GetLength() - y.GetLength()) <= 2 * max_error);
296
0
}