gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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)