chordangle.go (11668B)
1 // Copyright 2015 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 package s1 16 17 import ( 18 "math" 19 ) 20 21 // ChordAngle represents the angle subtended by a chord (i.e., the straight 22 // line segment connecting two points on the sphere). Its representation 23 // makes it very efficient for computing and comparing distances, but unlike 24 // Angle it is only capable of representing angles between 0 and π radians. 25 // Generally, ChordAngle should only be used in loops where many angles need 26 // to be calculated and compared. Otherwise it is simpler to use Angle. 27 // 28 // ChordAngle loses some accuracy as the angle approaches π radians. 29 // There are several different ways to measure this error, including the 30 // representational error (i.e., how accurately ChordAngle can represent 31 // angles near π radians), the conversion error (i.e., how much precision is 32 // lost when an Angle is converted to an ChordAngle), and the measurement 33 // error (i.e., how accurate the ChordAngle(a, b) constructor is when the 34 // points A and B are separated by angles close to π radians). All of these 35 // errors differ by a small constant factor. 36 // 37 // For the measurement error (which is the largest of these errors and also 38 // the most important in practice), let the angle between A and B be (π - x) 39 // radians, i.e. A and B are within "x" radians of being antipodal. The 40 // corresponding chord length is 41 // 42 // r = 2 * sin((π - x) / 2) = 2 * cos(x / 2) 43 // 44 // For values of x not close to π the relative error in the squared chord 45 // length is at most 4.5 * dblEpsilon (see MaxPointError below). 46 // The relative error in "r" is thus at most 2.25 * dblEpsilon ~= 5e-16. To 47 // convert this error into an equivalent angle, we have 48 // 49 // |dr / dx| = sin(x / 2) 50 // 51 // and therefore 52 // 53 // |dx| = dr / sin(x / 2) 54 // = 5e-16 * (2 * cos(x / 2)) / sin(x / 2) 55 // = 1e-15 / tan(x / 2) 56 // 57 // The maximum error is attained when 58 // 59 // x = |dx| 60 // = 1e-15 / tan(x / 2) 61 // ~= 1e-15 / (x / 2) 62 // ~= sqrt(2e-15) 63 // 64 // In summary, the measurement error for an angle (π - x) is at most 65 // 66 // dx = min(1e-15 / tan(x / 2), sqrt(2e-15)) 67 // (~= min(2e-15 / x, sqrt(2e-15)) when x is small) 68 // 69 // On the Earth's surface (assuming a radius of 6371km), this corresponds to 70 // the following worst-case measurement errors: 71 // 72 // Accuracy: Unless antipodal to within: 73 // --------- --------------------------- 74 // 6.4 nanometers 10,000 km (90 degrees) 75 // 1 micrometer 81.2 kilometers 76 // 1 millimeter 81.2 meters 77 // 1 centimeter 8.12 meters 78 // 28.5 centimeters 28.5 centimeters 79 // 80 // The representational and conversion errors referred to earlier are somewhat 81 // smaller than this. For example, maximum distance between adjacent 82 // representable ChordAngle values is only 13.5 cm rather than 28.5 cm. To 83 // see this, observe that the closest representable value to r^2 = 4 is 84 // r^2 = 4 * (1 - dblEpsilon / 2). Thus r = 2 * (1 - dblEpsilon / 4) and 85 // the angle between these two representable values is 86 // 87 // x = 2 * acos(r / 2) 88 // = 2 * acos(1 - dblEpsilon / 4) 89 // ~= 2 * asin(sqrt(dblEpsilon / 2) 90 // ~= sqrt(2 * dblEpsilon) 91 // ~= 2.1e-8 92 // 93 // which is 13.5 cm on the Earth's surface. 94 // 95 // The worst case rounding error occurs when the value halfway between these 96 // two representable values is rounded up to 4. This halfway value is 97 // r^2 = (4 * (1 - dblEpsilon / 4)), thus r = 2 * (1 - dblEpsilon / 8) and 98 // the worst case rounding error is 99 // 100 // x = 2 * acos(r / 2) 101 // = 2 * acos(1 - dblEpsilon / 8) 102 // ~= 2 * asin(sqrt(dblEpsilon / 4) 103 // ~= sqrt(dblEpsilon) 104 // ~= 1.5e-8 105 // 106 // which is 9.5 cm on the Earth's surface. 107 type ChordAngle float64 108 109 const ( 110 // NegativeChordAngle represents a chord angle smaller than the zero angle. 111 // The only valid operations on a NegativeChordAngle are comparisons, 112 // Angle conversions, and Successor/Predecessor. 113 NegativeChordAngle = ChordAngle(-1) 114 115 // RightChordAngle represents a chord angle of 90 degrees (a "right angle"). 116 RightChordAngle = ChordAngle(2) 117 118 // StraightChordAngle represents a chord angle of 180 degrees (a "straight angle"). 119 // This is the maximum finite chord angle. 120 StraightChordAngle = ChordAngle(4) 121 122 // maxLength2 is the square of the maximum length allowed in a ChordAngle. 123 maxLength2 = 4.0 124 ) 125 126 // ChordAngleFromAngle returns a ChordAngle from the given Angle. 127 func ChordAngleFromAngle(a Angle) ChordAngle { 128 if a < 0 { 129 return NegativeChordAngle 130 } 131 if a.isInf() { 132 return InfChordAngle() 133 } 134 l := 2 * math.Sin(0.5*math.Min(math.Pi, a.Radians())) 135 return ChordAngle(l * l) 136 } 137 138 // ChordAngleFromSquaredLength returns a ChordAngle from the squared chord length. 139 // Note that the argument is automatically clamped to a maximum of 4 to 140 // handle possible roundoff errors. The argument must be non-negative. 141 func ChordAngleFromSquaredLength(length2 float64) ChordAngle { 142 if length2 > maxLength2 { 143 return StraightChordAngle 144 } 145 return ChordAngle(length2) 146 } 147 148 // Expanded returns a new ChordAngle that has been adjusted by the given error 149 // bound (which can be positive or negative). Error should be the value 150 // returned by either MaxPointError or MaxAngleError. For example: 151 // a := ChordAngleFromPoints(x, y) 152 // a1 := a.Expanded(a.MaxPointError()) 153 func (c ChordAngle) Expanded(e float64) ChordAngle { 154 // If the angle is special, don't change it. Otherwise clamp it to the valid range. 155 if c.isSpecial() { 156 return c 157 } 158 return ChordAngle(math.Max(0.0, math.Min(maxLength2, float64(c)+e))) 159 } 160 161 // Angle converts this ChordAngle to an Angle. 162 func (c ChordAngle) Angle() Angle { 163 if c < 0 { 164 return -1 * Radian 165 } 166 if c.isInf() { 167 return InfAngle() 168 } 169 return Angle(2 * math.Asin(0.5*math.Sqrt(float64(c)))) 170 } 171 172 // InfChordAngle returns a chord angle larger than any finite chord angle. 173 // The only valid operations on an InfChordAngle are comparisons, Angle 174 // conversions, and Successor/Predecessor. 175 func InfChordAngle() ChordAngle { 176 return ChordAngle(math.Inf(1)) 177 } 178 179 // isInf reports whether this ChordAngle is infinite. 180 func (c ChordAngle) isInf() bool { 181 return math.IsInf(float64(c), 1) 182 } 183 184 // isSpecial reports whether this ChordAngle is one of the special cases. 185 func (c ChordAngle) isSpecial() bool { 186 return c < 0 || c.isInf() 187 } 188 189 // isValid reports whether this ChordAngle is valid or not. 190 func (c ChordAngle) isValid() bool { 191 return (c >= 0 && c <= maxLength2) || c.isSpecial() 192 } 193 194 // Successor returns the smallest representable ChordAngle larger than this one. 195 // This can be used to convert a "<" comparison to a "<=" comparison. 196 // 197 // Note the following special cases: 198 // NegativeChordAngle.Successor == 0 199 // StraightChordAngle.Successor == InfChordAngle 200 // InfChordAngle.Successor == InfChordAngle 201 func (c ChordAngle) Successor() ChordAngle { 202 if c >= maxLength2 { 203 return InfChordAngle() 204 } 205 if c < 0 { 206 return 0 207 } 208 return ChordAngle(math.Nextafter(float64(c), 10.0)) 209 } 210 211 // Predecessor returns the largest representable ChordAngle less than this one. 212 // 213 // Note the following special cases: 214 // InfChordAngle.Predecessor == StraightChordAngle 215 // ChordAngle(0).Predecessor == NegativeChordAngle 216 // NegativeChordAngle.Predecessor == NegativeChordAngle 217 func (c ChordAngle) Predecessor() ChordAngle { 218 if c <= 0 { 219 return NegativeChordAngle 220 } 221 if c > maxLength2 { 222 return StraightChordAngle 223 } 224 225 return ChordAngle(math.Nextafter(float64(c), -10.0)) 226 } 227 228 // MaxPointError returns the maximum error size for a ChordAngle constructed 229 // from 2 Points x and y, assuming that x and y are normalized to within the 230 // bounds guaranteed by s2.Point.Normalize. The error is defined with respect to 231 // the true distance after the points are projected to lie exactly on the sphere. 232 func (c ChordAngle) MaxPointError() float64 { 233 // There is a relative error of (2.5*dblEpsilon) when computing the squared 234 // distance, plus a relative error of 2 * dblEpsilon, plus an absolute error 235 // of (16 * dblEpsilon**2) because the lengths of the input points may differ 236 // from 1 by up to (2*dblEpsilon) each. (This is the maximum error in Normalize). 237 return 4.5*dblEpsilon*float64(c) + 16*dblEpsilon*dblEpsilon 238 } 239 240 // MaxAngleError returns the maximum error for a ChordAngle constructed 241 // as an Angle distance. 242 func (c ChordAngle) MaxAngleError() float64 { 243 return dblEpsilon * float64(c) 244 } 245 246 // Add adds the other ChordAngle to this one and returns the resulting value. 247 // This method assumes the ChordAngles are not special. 248 func (c ChordAngle) Add(other ChordAngle) ChordAngle { 249 // Note that this method (and Sub) is much more efficient than converting 250 // the ChordAngle to an Angle and adding those and converting back. It 251 // requires only one square root plus a few additions and multiplications. 252 253 // Optimization for the common case where b is an error tolerance 254 // parameter that happens to be set to zero. 255 if other == 0 { 256 return c 257 } 258 259 // Clamp the angle sum to at most 180 degrees. 260 if c+other >= maxLength2 { 261 return StraightChordAngle 262 } 263 264 // Let a and b be the (non-squared) chord lengths, and let c = a+b. 265 // Let A, B, and C be the corresponding half-angles (a = 2*sin(A), etc). 266 // Then the formula below can be derived from c = 2 * sin(A+B) and the 267 // relationships sin(A+B) = sin(A)*cos(B) + sin(B)*cos(A) 268 // cos(X) = sqrt(1 - sin^2(X)) 269 x := float64(c * (1 - 0.25*other)) 270 y := float64(other * (1 - 0.25*c)) 271 return ChordAngle(math.Min(maxLength2, x+y+2*math.Sqrt(x*y))) 272 } 273 274 // Sub subtracts the other ChordAngle from this one and returns the resulting 275 // value. This method assumes the ChordAngles are not special. 276 func (c ChordAngle) Sub(other ChordAngle) ChordAngle { 277 if other == 0 { 278 return c 279 } 280 if c <= other { 281 return 0 282 } 283 x := float64(c * (1 - 0.25*other)) 284 y := float64(other * (1 - 0.25*c)) 285 return ChordAngle(math.Max(0.0, x+y-2*math.Sqrt(x*y))) 286 } 287 288 // Sin returns the sine of this chord angle. This method is more efficient 289 // than converting to Angle and performing the computation. 290 func (c ChordAngle) Sin() float64 { 291 return math.Sqrt(c.Sin2()) 292 } 293 294 // Sin2 returns the square of the sine of this chord angle. 295 // It is more efficient than Sin. 296 func (c ChordAngle) Sin2() float64 { 297 // Let a be the (non-squared) chord length, and let A be the corresponding 298 // half-angle (a = 2*sin(A)). The formula below can be derived from: 299 // sin(2*A) = 2 * sin(A) * cos(A) 300 // cos^2(A) = 1 - sin^2(A) 301 // This is much faster than converting to an angle and computing its sine. 302 return float64(c * (1 - 0.25*c)) 303 } 304 305 // Cos returns the cosine of this chord angle. This method is more efficient 306 // than converting to Angle and performing the computation. 307 func (c ChordAngle) Cos() float64 { 308 // cos(2*A) = cos^2(A) - sin^2(A) = 1 - 2*sin^2(A) 309 return float64(1 - 0.5*c) 310 } 311 312 // Tan returns the tangent of this chord angle. 313 func (c ChordAngle) Tan() float64 { 314 return c.Sin() / c.Cos() 315 } 316 317 // TODO(roberts): Differences from C++: 318 // Helpers to/from E5/E6/E7 319 // Helpers to/from degrees and radians directly. 320 // FastUpperBoundFrom(angle Angle)