OpenCPN Partial API docs
geodesic.cpp
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <math.h>
5 
6 #include "model/geodesic.h"
7 
8 /* These methods implemented using the Vincenty method.
9  *
10  * Vincenty's original paper is available here:
11  * http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
12  *
13  * Great examples of the methods are available at these locations
14  * http://www.movable-type.co.uk/scripts/latlong-vincenty.html
15  * http://www.codeproject.com/KB/cs/Vincentys_Formula.aspx
16  *
17  * The movable-type.co.uk page contains code (C) 2002-2008 Chris Veness
18  * and available under a LGPL license.
19  *
20  * The codeproject.com page contains code available under the
21  * Code Project Open License (CPOL) 1.02.
22  *
23  * These references are a courtesy only, as the code here is original
24  * and not a derivitive work of the code provided on these pages.
25  */
26 
27 /*
28  * The Vincenty method does not converge for antipodal points, but the
29  * code here handles this special case. Since the earth is a flattened
30  * sphere, the shortest route is over the poles. The trip via the North or
31  * South Pole is equivalent, so we arbitrarily pick the South Pole.
32  *
33  * The length of a great circle route halfway around the world through the poles
34  * is 0.5 of the circumference of a circle with a radius of the semi minor axis.
35  */
36 
37 double Geodesic::GreatCircleDistBear(double Lon1, double Lat1, double Lon2,
38  double Lat2, double *Dist, double *Bear1,
39  double *Bear2) {
40  /* Geodesic parameters per WGS-84 */
41  double a = GEODESIC_WGS84_SEMI_MAJORAXIS; /* in meters */
42  double b = GEODESIC_WGS84_SEMI_MINORAXIS; /* in meters */
43  double f = (GEODESIC_WGS84_SEMI_MAJORAXIS - GEODESIC_WGS84_SEMI_MINORAXIS) /
44  GEODESIC_WGS84_SEMI_MAJORAXIS; /* Flattening */
45 
46  double dLon; /* Change in longitude */
47  double rLat1, rLat2; /* Reduced Latitude */
48  double lambda, lambdaprime; /* Counting variables */
49  double sinrLat1, cosrLat1, sinrLat2, cosrLat2, sinlambda,
50  coslambda; /* Intermediate calculations */
51  double sinsigma, cossigma, cos2sigmam, sigma, sinalpha, cos2alpha,
52  C; /* Intermediate calculations */
53  double u2, A, B; /* Intermediate calculations */
54  double dist; /* Great circle distance */
55  int itersleft = 50; /* Convergence attempts */
56 
57  /* Initialize the output variables */
58  if (Dist) *Dist = 0.0;
59  if (Bear1) *Bear1 = 0.0;
60  if (Bear2) *Bear2 = 0.0;
61 
62  if (fabs(Lon1 - Lon2) < 1e-12 && fabs(Lat1 - Lat2) < 1e-12) {
63  /* The start and end points are the same - the distance is zero */
64  return 0.0;
65  }
66 
67  /* Convert inputs from degrees to radians */
68  Lon1 = GEODESIC_DEG2RAD(Lon1);
69  Lat1 = GEODESIC_DEG2RAD(Lat1);
70  Lon2 = GEODESIC_DEG2RAD(Lon2);
71  Lat2 = GEODESIC_DEG2RAD(Lat2);
72 
73  /* Start the algorithm */
74  rLat1 = atan((1 - f) * tan(Lat1));
75  rLat2 = atan((1 - f) * tan(Lat2));
76  sinrLat1 = sin(rLat1);
77  cosrLat1 = cos(rLat1);
78  sinrLat2 = sin(rLat2);
79  cosrLat2 = cos(rLat2);
80  dLon = Lon2 - Lon1;
81 
82  lambda = dLon;
83  lambdaprime = 2 * M_PI;
84 
85  do {
86  sinlambda = sin(lambda);
87  coslambda = cos(lambda);
88  sinsigma =
89  sqrt(pow(cosrLat2 * sinlambda, 2.0) +
90  pow(cosrLat1 * sinrLat2 - sinrLat1 * cosrLat2 * coslambda, 2.0));
91  if (sinsigma < 1e-12) {
92  /* The points are antipodal */
93  dist = M_PI * b;
94  if (Dist) *Dist = dist;
95  if (Bear1) *Bear1 = 180.0; /* Start heading for the South Pole */
96  if (Bear2) *Bear2 = 0.0; /* Wind up heading for the North Pole */
97 
98  return dist;
99  }
100  cossigma = sinrLat1 * sinrLat2 + cosrLat1 * cosrLat2 * coslambda;
101  sigma = atan2(sinsigma, cossigma);
102  if (sinsigma == 0.0)
103  sinalpha = 0.0;
104  else
105  sinalpha = cosrLat1 * cosrLat2 * sinlambda / sinsigma;
106  cos2alpha = 1 - pow(sinalpha, 2.0);
107  if (cos2alpha == 0.0)
108  cos2sigmam = 0.0;
109  else
110  cos2sigmam = cossigma - 2 * sinrLat1 * sinrLat2 / cos2alpha;
111  // if( isnan( cos2sigmam ) ) cos2sigmam = 0.0; /* Special case to handle
112  // circles at the equator */
113  C = f / 16 * cos2alpha * (4 + f * (4 - 3 * cos2alpha));
114  lambdaprime = lambda;
115  lambda =
116  dLon +
117  (1.0 - C) * f * sinalpha *
118  (sigma + C * sinsigma *
119  (cos2sigmam +
120  C * cossigma * (-1.0 + 2.0 * pow(cos2sigmam, 2.0))));
121  } while (fabs(lambda - lambdaprime) > 1e-12 && (itersleft--));
122 
123  if (itersleft == 0) {
124  /* It didn't converge. Assume antipodal points. */
125  dist = M_PI * b;
126  if (Dist) *Dist = dist;
127  if (Bear1) *Bear1 = 180.0; /* Start heading for the South Pole */
128  if (Bear2) *Bear2 = 0.0; /* Wind up heading for the North Pole */
129 
130  return dist;
131  }
132  u2 = cos2alpha * (pow(a, 2.0) - pow(b, 2.0)) / pow(b, 2.0);
133  A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)));
134  B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 74 * u2)));
135  dist = b * A *
136  (sigma -
137  B * sinsigma *
138  (cos2sigmam +
139  B / 4 *
140  (cossigma * (-1.0 + 2.0 * pow(cos2sigmam, 2.0)) -
141  B / 6 * cos2sigmam * (-3.0 + 4.0 * pow(sinsigma, 2.0)) *
142  (-3 + 4 * pow(cos2sigmam, 2.0)))));
143  if (Dist) *Dist = dist;
144  if (Bear1) {
145  *Bear1 = GEODESIC_RAD2DEG(
146  atan2(cosrLat2 * sinlambda,
147  cosrLat1 * sinrLat2 - sinrLat1 * cosrLat2 * coslambda));
148  while (*Bear1 < 0.0) *Bear1 += 360.0;
149  }
150  if (Bear2) {
151  *Bear2 = GEODESIC_RAD2DEG(
152  atan2(cosrLat1 * sinlambda,
153  -sinrLat1 * cosrLat2 + cosrLat1 * sinrLat2 * coslambda));
154  while (*Bear2 < 0.0) *Bear2 += 360.0;
155  }
156  return dist;
157 }
158 
159 /* Vincenty's direct solution. The equation numbers related to
160  * the equation numbers in the following:
161  * http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
162  */
163 void Geodesic::GreatCircleTravel(double Lon1, double Lat1, double Dist,
164  double Bear1, double *Lon2, double *Lat2,
165  double *Bear2) {
166  /* Geodesic parameters per WGS-84 */
167  double a = GEODESIC_WGS84_SEMI_MAJORAXIS; /* in meters */
168  double b = GEODESIC_WGS84_SEMI_MINORAXIS; /* in meters */
169  double f = (GEODESIC_WGS84_SEMI_MAJORAXIS - GEODESIC_WGS84_SEMI_MINORAXIS) /
170  GEODESIC_WGS84_SEMI_MAJORAXIS; /* Flattening */
171 
172  /* Initialize to where we started from */
173  if (Lon2) *Lon2 = Lon1;
174  if (Lat2) *Lat2 = Lat1;
175  if (Bear2) *Bear2 = Bear1;
176 
177  if (Dist < 1e-12) {
178  /* There's no distance to travel, so we're done. */
179  return;
180  }
181 
182  /* Convert inputs from degrees to radians */
183  Lon1 = GEODESIC_DEG2RAD(Lon1);
184  Lat1 = GEODESIC_DEG2RAD(Lat1);
185  Bear1 = GEODESIC_DEG2RAD(Bear1);
186  if (Lon2) *Lon2 = Lon1;
187  if (Lat2) *Lat2 = Lat1;
188  if (Bear2) *Bear2 = Bear1;
189 
190  double sigma1; /* Angular distance on the sphere from equator to Lon1/Lat1 */
191  double rLat1; /* Reduced Latitude */
192  double sinrLat1; /* Sine of reduced latitude */
193  double cosrLat1; /* Cosine of reduced latitude */
194  double sinalpha1; /* Sine of the initial bearing */
195  double cosalpha1; /* Cosine of the initial bearing */
196  double sinalpha; /* Sine of the azimuth of the geodesic at the equator */
197  double sin2alpha; /* sinalpha^2 */
198  double cos2alpha; /* cosalpha^2 */
199  double sigma; /* Angular distance on the sphere */
200  double sinsigma; /* Sine of the angular distance on the sphere */
201  double cossigma; /* Cosine of the angular distance on the sphere */
202  double sigmaprime; /* Previous value of sigma */
203  double lambda; /* Difference in longitude on an auxiliary sphere */
204  double L; /* Difference in longitude, positive East */
205  double u2, A, B, C, twosigmam, costwosigmam, cos2twosigmam, deltasigma,
206  distoverba; /* Intermediate calculations */
207 
208  /* Start the algorithm */
209  rLat1 = (1.0 - f) * tan(Lat1); /* tan U=(1-f)*tan(phi) */
210  cosrLat1 = 1.0 / sqrt(1.0 + rLat1 * rLat1); /* via trig identity */
211  sinrLat1 = rLat1 * cosrLat1; /* via trig identity */
212  sinalpha1 = sin(Bear1);
213  cosalpha1 = cos(Bear1);
214 
215  sigma1 = atan2(rLat1, cosalpha1); /* Eq. 1 */
216 
217  sinalpha = cosrLat1 * sinalpha1; /* Eq. 2 */
218  sin2alpha = sinalpha * sinalpha;
219 
220  cos2alpha = 1 - (sin2alpha); /* cos2=1-sin2 */
221  u2 = cos2alpha * ((a * a) - (b * b)) / (b * b);
222  A = 1 +
223  (u2 / 16384) * (4096 + u2 * (-768 + u2 * (320 - 175 * u2))); /* Eq. 3 */
224 
225  B = (u2 / 1024) * (256 + u2 * (-128 + u2 * (74 - 47 * u2))); /* Eq. 4 */
226 
227  distoverba = Dist / (b * A);
228  sigma = distoverba;
229  sigmaprime = sigma - 1.0; /* Something to get the loop started */
230  while (fabs(sigmaprime - sigma) > 1e-12) {
231  twosigmam = 2 * sigma1 + sigma; /* Eq. 5 */
232  costwosigmam = cos(twosigmam);
233  cos2twosigmam = costwosigmam * costwosigmam;
234  sinsigma = sin(sigma);
235 
236  deltasigma = B * sinsigma *
237  (costwosigmam + (B / 4.0) * /* Eq. 6 */
238  (cos(sigma) * (-1 + 2 * cos2twosigmam) -
239  (B / 6.0) * costwosigmam *
240  (-3 + 4 * sinsigma * sinsigma) *
241  (-3 + 4 * cos2twosigmam)));
242 
243  sigmaprime = sigma;
244  sigma = distoverba + deltasigma; /* Eq. 7 */
245  }
246  sinsigma = sin(sigma);
247  cossigma = cos(sigma);
248  twosigmam = 2 * sigma1 + sigma; /* Eq. 5 */
249  costwosigmam = cos(twosigmam);
250  cos2twosigmam = costwosigmam * costwosigmam;
251 
252  if (Lat2) {
253  *Lat2 = atan2(/* Eq. 8 */
254  sinrLat1 * cossigma + cosrLat1 * sinsigma * cosalpha1,
255  (1 - f) *
256  sqrt(sin2alpha + pow(sinrLat1 * sinsigma -
257  cosrLat1 * cossigma * cosalpha1,
258  2.0)));
259  *Lat2 = GEODESIC_RAD2DEG(*Lat2);
260  }
261 
262  if (Lon2) {
263  lambda = atan2(sinsigma * sinalpha1, /* Eq. 9 */
264  cosrLat1 * cossigma - sinrLat1 * sinsigma * cosalpha1);
265 
266  C = (f / 16.0) * cos2alpha * (4 + f * (4 - 3 * cos2alpha)); /* Eq. 10 */
267 
268  L = lambda - (1 - C) * f * sinalpha *
269  (sigma + C * sinsigma * /* Eq. 11 */
270  (costwosigmam +
271  C * cossigma * (-1 + 2 * cos2twosigmam)));
272  *Lon2 = GEODESIC_RAD2DEG(Lon1 + L);
273  }
274 
275  if (Bear2) {
276  *Bear2 = atan2(sinalpha, /* Eq. 12 */
277  -sinrLat1 * sinsigma + cosrLat1 * cossigma * cosalpha1);
278  *Bear2 = GEODESIC_RAD2DEG(*Bear2);
279  }
280 
281  return;
282 }