point_measures.go (6420B)
1 // Copyright 2018 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 s2 16 17 import ( 18 "math" 19 20 "github.com/golang/geo/s1" 21 ) 22 23 // PointArea returns the area of triangle ABC. This method combines two different 24 // algorithms to get accurate results for both large and small triangles. 25 // The maximum error is about 5e-15 (about 0.25 square meters on the Earth's 26 // surface), the same as GirardArea below, but unlike that method it is 27 // also accurate for small triangles. Example: when the true area is 100 28 // square meters, PointArea yields an error about 1 trillion times smaller than 29 // GirardArea. 30 // 31 // All points should be unit length, and no two points should be antipodal. 32 // The area is always positive. 33 func PointArea(a, b, c Point) float64 { 34 // This method is based on l'Huilier's theorem, 35 // 36 // tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2)) 37 // 38 // where E is the spherical excess of the triangle (i.e. its area), 39 // a, b, c are the side lengths, and 40 // s is the semiperimeter (a + b + c) / 2. 41 // 42 // The only significant source of error using l'Huilier's method is the 43 // cancellation error of the terms (s-a), (s-b), (s-c). This leads to a 44 // *relative* error of about 1e-16 * s / min(s-a, s-b, s-c). This compares 45 // to a relative error of about 1e-15 / E using Girard's formula, where E is 46 // the true area of the triangle. Girard's formula can be even worse than 47 // this for very small triangles, e.g. a triangle with a true area of 1e-30 48 // might evaluate to 1e-5. 49 // 50 // So, we prefer l'Huilier's formula unless dmin < s * (0.1 * E), where 51 // dmin = min(s-a, s-b, s-c). This basically includes all triangles 52 // except for extremely long and skinny ones. 53 // 54 // Since we don't know E, we would like a conservative upper bound on 55 // the triangle area in terms of s and dmin. It's possible to show that 56 // E <= k1 * s * sqrt(s * dmin), where k1 = 2*sqrt(3)/Pi (about 1). 57 // Using this, it's easy to show that we should always use l'Huilier's 58 // method if dmin >= k2 * s^5, where k2 is about 1e-2. Furthermore, 59 // if dmin < k2 * s^5, the triangle area is at most k3 * s^4, where 60 // k3 is about 0.1. Since the best case error using Girard's formula 61 // is about 1e-15, this means that we shouldn't even consider it unless 62 // s >= 3e-4 or so. 63 sa := float64(b.Angle(c.Vector)) 64 sb := float64(c.Angle(a.Vector)) 65 sc := float64(a.Angle(b.Vector)) 66 s := 0.5 * (sa + sb + sc) 67 if s >= 3e-4 { 68 // Consider whether Girard's formula might be more accurate. 69 dmin := s - math.Max(sa, math.Max(sb, sc)) 70 if dmin < 1e-2*s*s*s*s*s { 71 // This triangle is skinny enough to use Girard's formula. 72 area := GirardArea(a, b, c) 73 if dmin < s*0.1*area { 74 return area 75 } 76 } 77 } 78 79 // Use l'Huilier's formula. 80 return 4 * math.Atan(math.Sqrt(math.Max(0.0, math.Tan(0.5*s)*math.Tan(0.5*(s-sa))* 81 math.Tan(0.5*(s-sb))*math.Tan(0.5*(s-sc))))) 82 } 83 84 // GirardArea returns the area of the triangle computed using Girard's formula. 85 // All points should be unit length, and no two points should be antipodal. 86 // 87 // This method is about twice as fast as PointArea() but has poor relative 88 // accuracy for small triangles. The maximum error is about 5e-15 (about 89 // 0.25 square meters on the Earth's surface) and the average error is about 90 // 1e-15. These bounds apply to triangles of any size, even as the maximum 91 // edge length of the triangle approaches 180 degrees. But note that for 92 // such triangles, tiny perturbations of the input points can change the 93 // true mathematical area dramatically. 94 func GirardArea(a, b, c Point) float64 { 95 // This is equivalent to the usual Girard's formula but is slightly more 96 // accurate, faster to compute, and handles a == b == c without a special 97 // case. PointCross is necessary to get good accuracy when two of 98 // the input points are very close together. 99 ab := a.PointCross(b) 100 bc := b.PointCross(c) 101 ac := a.PointCross(c) 102 103 area := float64(ab.Angle(ac.Vector) - ab.Angle(bc.Vector) + bc.Angle(ac.Vector)) 104 if area < 0 { 105 area = 0 106 } 107 return area 108 } 109 110 // SignedArea returns a positive value for counterclockwise triangles and a negative 111 // value otherwise (similar to PointArea). 112 func SignedArea(a, b, c Point) float64 { 113 return float64(RobustSign(a, b, c)) * PointArea(a, b, c) 114 } 115 116 // Angle returns the interior angle at the vertex B in the triangle ABC. The 117 // return value is always in the range [0, pi]. All points should be 118 // normalized. Ensures that Angle(a,b,c) == Angle(c,b,a) for all a,b,c. 119 // 120 // The angle is undefined if A or C is diametrically opposite from B, and 121 // becomes numerically unstable as the length of edge AB or BC approaches 122 // 180 degrees. 123 func Angle(a, b, c Point) s1.Angle { 124 // PointCross is necessary to get good accuracy when two of the input 125 // points are very close together. 126 return a.PointCross(b).Angle(c.PointCross(b).Vector) 127 } 128 129 // TurnAngle returns the exterior angle at vertex B in the triangle ABC. The 130 // return value is positive if ABC is counterclockwise and negative otherwise. 131 // If you imagine an ant walking from A to B to C, this is the angle that the 132 // ant turns at vertex B (positive = left = CCW, negative = right = CW). 133 // This quantity is also known as the "geodesic curvature" at B. 134 // 135 // Ensures that TurnAngle(a,b,c) == -TurnAngle(c,b,a) for all distinct 136 // a,b,c. The result is undefined if (a == b || b == c), but is either 137 // -Pi or Pi if (a == c). All points should be normalized. 138 func TurnAngle(a, b, c Point) s1.Angle { 139 // We use PointCross to get good accuracy when two points are very 140 // close together, and RobustSign to ensure that the sign is correct for 141 // turns that are close to 180 degrees. 142 angle := a.PointCross(b).Angle(b.PointCross(c).Vector) 143 144 // Don't return RobustSign * angle because it is legal to have (a == c). 145 if RobustSign(a, b, c) == CounterClockwise { 146 return angle 147 } 148 return -angle 149 }