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 }