Coverage Report

Created: 2026-10-17 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s1chord_angle.cc
Line
Count
Source
1
// Copyright 2013 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/s1chord_angle.h"
19
20
#include <algorithm>
21
#include <cfloat>
22
#include <cmath>
23
#include <ostream>
24
25
#include "absl/log/absl_check.h"
26
#include "s2/s1angle.h"
27
28
using std::max;
29
using std::min;
30
using std::nextafter;
31
32
static constexpr double kMaxLength2 = 4.0;
33
34
0
S1Angle S1ChordAngle::ToAngle() const {
35
0
  if (is_negative()) return S1Angle::Radians(-1);
36
0
  if (is_infinity()) return S1Angle::Infinity();
37
0
  return S1Angle::Radians(2 * asin(0.5 * sqrt(length2_)));
38
0
}
39
40
0
S1ChordAngle S1ChordAngle::Successor() const {
41
0
  if (length2_ >= kMaxLength2) return Infinity();
42
0
  if (length2_ < 0.0) return Zero();
43
0
  return S1ChordAngle(nextafter(length2_, 10.0));
44
0
}
45
46
0
S1ChordAngle S1ChordAngle::Predecessor() const {
47
0
  if (length2_ <= 0.0) return Negative();
48
0
  if (length2_ > kMaxLength2) return Straight();
49
0
  return S1ChordAngle(nextafter(length2_, -10.0));
50
0
}
51
52
0
S1ChordAngle S1ChordAngle::PlusError(double error) const {
53
  // If angle is Negative() or Infinity(), don't change it.
54
  // Otherwise clamp it to the valid range.
55
0
  if (is_special()) return *this;
56
0
  return S1ChordAngle(max(0.0, min(kMaxLength2, length2_ + error)));
57
0
}
58
59
0
double S1ChordAngle::GetS2PointConstructorMaxError() const {
60
  // There is a relative error of 2.5 * DBL_EPSILON when computing the squared
61
  // distance, plus a relative error of 2 * DBL_EPSILON and an absolute error
62
  // of (16 * DBL_EPSILON**2) because the lengths of the input points may
63
  // differ from 1 by up to (2 * DBL_EPSILON) each.  (This is the maximum
64
  // length error in S2Point::Normalize.)
65
0
  return 4.5 * DBL_EPSILON * length2_ + 16 * DBL_EPSILON * DBL_EPSILON;
66
0
}
67
68
0
double S1ChordAngle::GetS1AngleConstructorMaxError() const {
69
  // Assuming that an accurate math library is being used, the sin() call and
70
  // the multiply each have a relative error of 0.5 * DBL_EPSILON.  However
71
  // the sin() error is squared.
72
0
  return 1.5 * DBL_EPSILON * length2_;
73
0
}
74
75
0
S1ChordAngle operator+(S1ChordAngle a, S1ChordAngle b) {
76
  // Error Analysis:
77
  //
78
  //   u is the unit round-off, equal to ε/2 = 2^-53
79
  //   x and y below are both computed with (1+u)² error.  So we have
80
  //     length2 = x + y + 2*sqrt(x * y)
81
  //     length2 = ((x + y)(1+u)³ + 2*sqrt((x * y)((1+u)³))(1+u))(1+u)
82
  //
83
  //   Taking (1+u)^1.5 out of the square root and rounding (1+u)^2.5 to (1+u)³:
84
  //     length2 = (x + y + 2*sqrt(x * y))(1+u)⁴
85
  //
86
  //   Bounding (1+u)⁴ as 4*1.01u = 4.04*ε/2 = 2.02ε, total relative error is:
87
  //     length2()*2.02ε.
88
89
  // Note that this method is much more efficient than converting the chord
90
  // angles to S1Angles and adding those.  It requires only one square root
91
  // plus a few additions and multiplications.
92
0
  ABSL_DCHECK(!a.is_special()) << a;
93
0
  ABSL_DCHECK(!b.is_special()) << b;
94
95
  // Optimization for the common case where "b" is an error tolerance
96
  // parameter that happens to be set to zero.
97
0
  double a2 = a.length2(), b2 = b.length2();
98
0
  if (b2 == 0) return a;
99
100
  // Clamp the angle sum to at most 180 degrees.
101
0
  if (a2 + b2 >= kMaxLength2) return S1ChordAngle::Straight();
102
103
  // Let "a" and "b" be the (non-squared) chord lengths, and let c = a+b.
104
  // Let A, B, and C be the corresponding half-angles (a = 2*sin(A), etc).
105
  // Then the formula below can be derived from c = 2 * sin(A+B) and the
106
  // relationships   sin(A+B) = sin(A)*cos(B) + sin(B)*cos(A)
107
  //                 cos(X) = sqrt(1 - sin^2(X)) .
108
109
0
  double x = a2 * (1 - 0.25 * b2);  // is_valid() => non-negative
110
0
  double y = b2 * (1 - 0.25 * a2);  // is_valid() => non-negative
111
0
  return S1ChordAngle(min(kMaxLength2, x + y + 2 * sqrt(x * y)));
112
0
}
113
114
0
S1ChordAngle operator-(S1ChordAngle a, S1ChordAngle b) {
115
  // See comments in operator+().
116
0
  ABSL_DCHECK(!a.is_special()) << a;
117
0
  ABSL_DCHECK(!b.is_special()) << b;
118
0
  double a2 = a.length2(), b2 = b.length2();
119
0
  if (b2 == 0) return a;
120
0
  if (a2 <= b2) return S1ChordAngle::Zero();
121
0
  double x = a2 * (1 - 0.25 * b2);
122
0
  double y = b2 * (1 - 0.25 * a2);
123
124
  // The calculation below is formulated differently (with two square roots
125
  // rather than one) to avoid excessive cancellation error when two nearly
126
  // equal values are subtracted.
127
0
  double c = max(0.0, sqrt(x) - sqrt(y));
128
0
  return S1ChordAngle(c * c);
129
0
}
130
131
0
double sin2(S1ChordAngle a) {
132
0
  ABSL_DCHECK(!a.is_special());
133
  // Let "a" be the (non-squared) chord length, and let A be the corresponding
134
  // half-angle (a = 2*sin(A)).  The formula below can be derived from:
135
  //   sin(2*A) = 2 * sin(A) * cos(A)
136
  //   cos^2(A) = 1 - sin^2(A)
137
  // This is much faster than converting to an angle and computing its sine.
138
0
  return a.length2() * (1 - 0.25 * a.length2());
139
0
}
140
141
0
double sin(S1ChordAngle a) {
142
0
  return sqrt(sin2(a));
143
0
}
144
145
0
double cos(S1ChordAngle a) {
146
  // cos(2*A) = cos^2(A) - sin^2(A) = 1 - 2*sin^2(A)
147
0
  ABSL_DCHECK(!a.is_special());
148
0
  return 1 - 0.5 * a.length2();
149
0
}
150
151
0
double tan(S1ChordAngle a) {
152
0
  return sin(a) / cos(a);
153
0
}
154
155
0
std::ostream& operator<<(std::ostream& os, S1ChordAngle a) {
156
0
  return os << a.ToAngle();
157
0
}