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 // Original author: ericv@google.com (Eric Veach) 16 // Converted to D: madric@gmail.com (Vijay Nayar) 17 18 module s2.s2measures; 19 20 // Defines various angle and area measures on the sphere. 21 22 import s2.s2point; 23 import s2.util.math.vector; 24 25 import s2pred = s2.s2predicates; 26 import s2pointutil = s2.s2pointutil; 27 import math = std.math; 28 import algorithm = std.algorithm; 29 30 // Return the interior angle at the vertex B in the triangle ABC. The 31 // return value is always in the range [0, Pi]. All points should be 32 // normalized. Ensures that Angle(a,b,c) == Angle(c,b,a) for all a,b,c. 33 // 34 // The angle is undefined if A or C is diametrically opposite from B, and 35 // becomes numerically unstable as the length of edge AB or BC approaches 36 // 180 degrees. 37 double angle(in S2Point a, in S2Point b, in S2Point c) { 38 // RobustCrossProd() is necessary to get good accuracy when two of the input 39 // points are very close together. 40 return s2pointutil.robustCrossProd(a, b).angle(s2pointutil.robustCrossProd(c, b)); 41 } 42 43 // Return the exterior angle at vertex B in the triangle ABC. The return 44 // value is positive if ABC is counterclockwise and negative otherwise. If 45 // you imagine an ant walking from A to B to C, this is the angle that the 46 // ant turns at vertex B (positive = left = CCW, negative = right = CW). 47 // This quantity is also known as the "geodesic curvature" at B. 48 // 49 // Ensures that TurnAngle(a,b,c) == -TurnAngle(c,b,a) for all distinct 50 // a,b,c. The result is undefined if (a == b || b == c), but is either 51 // -Pi or Pi if (a == c). All points should be normalized. 52 double turnAngle(in S2Point a, in S2Point b, in S2Point c) { 53 // We use RobustCrossProd() to get good accuracy when two points are very 54 // close together, and Sign() to ensure that the sign is correct for 55 // turns that are close to 180 degrees. 56 // 57 // Unfortunately we can't save RobustCrossProd(a, b) and pass it as the 58 // optional 4th argument to Sign(), because Sign() requires a.CrossProd(b) 59 // exactly (the robust version differs in magnitude). 60 double angle = s2pointutil.robustCrossProd(a, b).angle(s2pointutil.robustCrossProd(b, c)); 61 62 // Don't return Sign() * angle because it is legal to have (a == c). 63 return (s2pred.sign(a, b, c) > 0) ? angle : -angle; 64 } 65 66 // Return the area of triangle ABC. This method combines two different 67 // algorithms to get accurate results for both large and small triangles. 68 // The maximum error is about 5e-15 (about 0.25 square meters on the Earth's 69 // surface), the same as GirardArea() below, but unlike that method it is 70 // also accurate for small triangles. Example: when the true area is 100 71 // square meters, Area() yields an error about 1 trillion times smaller than 72 // GirardArea(). 73 // 74 // All points should be unit length, and no two points should be antipodal. 75 // The area is always positive. 76 double area(in S2Point a, in S2Point b, in S2Point c) 77 in { 78 assert(s2pointutil.isUnitLength(a)); 79 assert(s2pointutil.isUnitLength(b)); 80 assert(s2pointutil.isUnitLength(c)); 81 } do { 82 // This method is based on l'Huilier's theorem, 83 // 84 // tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2)) 85 // 86 // where E is the spherical excess of the triangle (i.e. its area), 87 // a, b, c, are the side lengths, and 88 // s is the semiperimeter (a + b + c) / 2 . 89 // 90 // The only significant source of error using l'Huilier's method is the 91 // cancellation error of the terms (s-a), (s-b), (s-c). This leads to a 92 // *relative* error of about 1e-16 * s / min(s-a, s-b, s-c). This compares 93 // to a relative error of about 1e-15 / E using Girard's formula, where E is 94 // the true area of the triangle. Girard's formula can be even worse than 95 // this for very small triangles, e.g. a triangle with a true area of 1e-30 96 // might evaluate to 1e-5. 97 // 98 // So, we prefer l'Huilier's formula unless dmin < s * (0.1 * E), where 99 // dmin = min(s-a, s-b, s-c). This basically includes all triangles 100 // except for extremely long and skinny ones. 101 // 102 // Since we don't know E, we would like a conservative upper bound on 103 // the triangle area in terms of s and dmin. It's possible to show that 104 // E <= k1 * s * sqrt(s * dmin), where k1 = 2*sqrt(3)/Pi (about 1). 105 // Using this, it's easy to show that we should always use l'Huilier's 106 // method if dmin >= k2 * s^5, where k2 is about 1e-2. Furthermore, 107 // if dmin < k2 * s^5, the triangle area is at most k3 * s^4, where 108 // k3 is about 0.1. Since the best case error using Girard's formula 109 // is about 1e-15, this means that we shouldn't even consider it unless 110 // s >= 3e-4 or so. 111 112 // We use volatile doubles to force the compiler to truncate all of these 113 // quantities to 64 bits. Otherwise it may compute a value of dmin > 0 114 // simply because it chose to spill one of the intermediate values to 115 // memory but not one of the others. 116 double sa = b.angle(c); 117 double sb = c.angle(a); 118 double sc = a.angle(b); 119 double s = 0.5 * (sa + sb + sc); 120 if (s >= 3e-4) { 121 // Consider whether Girard's formula might be more accurate. 122 double s2 = s * s; 123 double dmin = s - algorithm.max(sa, algorithm.max(sb, sc)); 124 if (dmin < 1e-2 * s * s2 * s2) { 125 // This triangle is skinny enough to consider Girard's formula. 126 double area = girardArea(a, b, c); 127 if (dmin < s * (0.1 * area)) { 128 return area; 129 } 130 } 131 } 132 // Use l'Huilier's formula. 133 return 4 * math.atan(math.sqrt(algorithm.max(0.0, math.tan(0.5 * s) * math.tan(0.5 * (s - sa)) 134 * math.tan(0.5 * (s - sb)) * math.tan(0.5 * (s - sc))))); 135 } 136 137 // Return the area of the triangle computed using Girard's formula. All 138 // points should be unit length, and no two points should be antipodal. 139 // 140 // This method is about twice as fast as Area() but has poor relative 141 // accuracy for small triangles. The maximum error is about 5e-15 (about 142 // 0.25 square meters on the Earth's surface) and the average error is about 143 // 1e-15. These bounds apply to triangles of any size, even as the maximum 144 // edge length of the triangle approaches 180 degrees. But note that for 145 // such triangles, tiny perturbations of the input points can change the 146 // true mathematical area dramatically. 147 double girardArea(in S2Point a, in S2Point b, in S2Point c) { 148 // This is equivalent to the usual Girard's formula but is slightly more 149 // accurate, faster to compute, and handles a == b == c without a special 150 // case. RobustCrossProd() is necessary to get good accuracy when two of 151 // the input points are very close together. 152 153 Vector3_d ab = s2pointutil.robustCrossProd(a, b); 154 Vector3_d bc = s2pointutil.robustCrossProd(b, c); 155 Vector3_d ac = s2pointutil.robustCrossProd(a, c); 156 return algorithm.max(0.0, ab.angle(ac) - ab.angle(bc) + bc.angle(ac)); 157 } 158 159 // Like Area(), but returns a positive value for counterclockwise triangles 160 // and a negative value otherwise. 161 double signedArea(in S2Point a, in S2Point b, in S2Point c) { 162 return s2pred.sign(a, b, c) * area(a, b, c); 163 }