]> git.mxchange.org Git - simgear.git/blob - simgear/math/SGGeodesy.cxx
math: Add integer valued vector types.
[simgear.git] / simgear / math / SGGeodesy.cxx
1 // Copyright (C) 2006  Mathias Froehlich - Mathias.Froehlich@web.de
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 //
17
18 #ifdef HAVE_CONFIG_H
19 #  include <simgear_config.h>
20 #endif
21
22 #include <cmath>
23
24 #include <simgear/sg_inlines.h>
25 #include <simgear/structure/exception.hxx>
26 #include <simgear/debug/logstream.hxx>
27
28 #include "SGMath.hxx"
29
30 // These are hard numbers from the WGS84 standard.  DON'T MODIFY
31 // unless you want to change the datum.
32 #define _EQURAD 6378137.0
33 #define _FLATTENING 298.257223563
34
35 // These are derived quantities more useful to the code:
36 #if 0
37 #define _SQUASH (1 - 1/_FLATTENING)
38 #define _STRETCH (1/_SQUASH)
39 #define _POLRAD (EQURAD * _SQUASH)
40 #else
41 // High-precision versions of the above produced with an arbitrary
42 // precision calculator (the compiler might lose a few bits in the FPU
43 // operations).  These are specified to 81 bits of mantissa, which is
44 // higher than any FPU known to me:
45 #define _SQUASH    0.9966471893352525192801545
46 #define _STRETCH   1.0033640898209764189003079
47 #define _POLRAD    6356752.3142451794975639668
48 #endif
49
50 // The constants from the WGS84 standard
51 const double SGGeodesy::EQURAD = _EQURAD;
52 const double SGGeodesy::iFLATTENING = _FLATTENING;
53 const double SGGeodesy::SQUASH = _SQUASH;
54 const double SGGeodesy::STRETCH = _STRETCH;
55 const double SGGeodesy::POLRAD = _POLRAD;
56
57 // additional derived and precomputable ones
58 // for the geodetic conversion algorithm
59
60 #define E2 fabs(1 - _SQUASH*_SQUASH)
61 static double a = _EQURAD;
62 static double ra2 = 1/(_EQURAD*_EQURAD);
63 //static double e = sqrt(E2);
64 static double e2 = E2;
65 static double e4 = E2*E2;
66
67 #undef _EQURAD
68 #undef _FLATTENING
69 #undef _SQUASH
70 #undef _STRETCH
71 #undef _POLRAD
72 #undef E2
73
74 void
75 SGGeodesy::SGCartToGeod(const SGVec3<double>& cart, SGGeod& geod)
76 {
77   // according to
78   // H. Vermeille,
79   // Direct transformation from geocentric to geodetic ccordinates,
80   // Journal of Geodesy (2002) 76:451-454
81   double X = cart(0);
82   double Y = cart(1);
83   double Z = cart(2);
84   double XXpYY = X*X+Y*Y;
85   if( XXpYY + Z*Z < 25 ) {
86     // This function fails near the geocenter region, so catch that special case here.
87     // Define the innermost sphere of small radius as earth center and return the 
88     // coordinates 0/0/-EQURAD. It may be any other place on geoide's surface,
89     // the Northpole, Hawaii or Wentorf. This one was easy to code ;-)
90     geod.setLongitudeRad( 0.0 );
91     geod.setLatitudeRad( 0.0 );
92     geod.setElevationM( -EQURAD );
93     return;
94   }
95     
96   double sqrtXXpYY = sqrt(XXpYY);
97   double p = XXpYY*ra2;
98   double q = Z*Z*(1-e2)*ra2;
99   double r = 1/6.0*(p+q-e4);
100   double s = e4*p*q/(4*r*r*r);
101 /* 
102   s*(2+s) is negative for s = [-2..0]
103   slightly negative values for s due to floating point rounding errors
104   cause nan for sqrt(s*(2+s))
105   We can probably clamp the resulting parable to positive numbers
106 */
107   if( s >= -2.0 && s <= 0.0 )
108     s = 0.0;
109   double t = pow(1+s+sqrt(s*(2+s)), 1/3.0);
110   double u = r*(1+t+1/t);
111   double v = sqrt(u*u+e4*q);
112   double w = e2*(u+v-q)/(2*v);
113   double k = sqrt(u+v+w*w)-w;
114   double D = k*sqrtXXpYY/(k+e2);
115   geod.setLongitudeRad(2*atan2(Y, X+sqrtXXpYY));
116   double sqrtDDpZZ = sqrt(D*D+Z*Z);
117   geod.setLatitudeRad(2*atan2(Z, D+sqrtDDpZZ));
118   geod.setElevationM((k+e2-1)*sqrtDDpZZ/k);
119 }
120
121 void
122 SGGeodesy::SGGeodToCart(const SGGeod& geod, SGVec3<double>& cart)
123 {
124   // according to
125   // H. Vermeille,
126   // Direct transformation from geocentric to geodetic ccordinates,
127   // Journal of Geodesy (2002) 76:451-454
128   double lambda = geod.getLongitudeRad();
129   double phi = geod.getLatitudeRad();
130   double h = geod.getElevationM();
131   double sphi = sin(phi);
132   double n = a/sqrt(1-e2*sphi*sphi);
133   double cphi = cos(phi);
134   double slambda = sin(lambda);
135   double clambda = cos(lambda);
136   cart(0) = (h+n)*cphi*clambda;
137   cart(1) = (h+n)*cphi*slambda;
138   cart(2) = (h+n-e2*n)*sphi;
139 }
140
141 double
142 SGGeodesy::SGGeodToSeaLevelRadius(const SGGeod& geod)
143 {
144   // this is just a simplified version of the SGGeodToCart function above,
145   // substitute h = 0, take the 2-norm of the cartesian vector and simplify
146   double phi = geod.getLatitudeRad();
147   double sphi = sin(phi);
148   double sphi2 = sphi*sphi;
149   return a*sqrt((1 + (e4 - 2*e2)*sphi2)/(1 - e2*sphi2));
150 }
151
152 void
153 SGGeodesy::SGCartToGeoc(const SGVec3<double>& cart, SGGeoc& geoc)
154 {
155   double minVal = SGLimits<double>::min();
156   if (fabs(cart(0)) < minVal && fabs(cart(1)) < minVal)
157     geoc.setLongitudeRad(0);
158   else
159     geoc.setLongitudeRad(atan2(cart(1), cart(0)));
160
161   double nxy = sqrt(cart(0)*cart(0) + cart(1)*cart(1));
162   if (fabs(nxy) < minVal && fabs(cart(2)) < minVal)
163     geoc.setLatitudeRad(0);
164   else
165     geoc.setLatitudeRad(atan2(cart(2), nxy));
166
167   geoc.setRadiusM(norm(cart));
168 }
169
170 void
171 SGGeodesy::SGGeocToCart(const SGGeoc& geoc, SGVec3<double>& cart)
172 {
173   double lat = geoc.getLatitudeRad();
174   double lon = geoc.getLongitudeRad();
175   double slat = sin(lat);
176   double clat = cos(lat);
177   double slon = sin(lon);
178   double clon = cos(lon);
179   cart = geoc.getRadiusM()*SGVec3<double>(clat*clon, clat*slon, slat);
180 }
181
182 // Notes:
183 //
184 // The XYZ/cartesian coordinate system in use puts the X axis through
185 // zero lat/lon (off west Africa), the Z axis through the north pole,
186 // and the Y axis through 90 degrees longitude (in the Indian Ocean).
187 //
188 // All latitude and longitude values are in radians.  Altitude is in
189 // meters, with zero on the WGS84 ellipsoid.
190 //
191 // The code below makes use of the notion of "squashed" space.  This
192 // is a 2D cylindrical coordinate system where the radius from the Z
193 // axis is multiplied by SQUASH; the earth in this space is a perfect
194 // circle with a radius of POLRAD.
195
196 ////////////////////////////////////////////////////////////////////////
197 //
198 // Direct and inverse distance functions 
199 //
200 // Proceedings of the 7th International Symposium on Geodetic
201 // Computations, 1985
202 //
203 // "The Nested Coefficient Method for Accurate Solutions of Direct and
204 // Inverse Geodetic Problems With Any Length"
205 //
206 // Zhang Xue-Lian
207 // pp 747-763
208 //
209 // modified for FlightGear to use WGS84 only -- Norman Vine
210
211 static inline double M0( double e2 ) {
212     //double e4 = e2*e2;
213   return SGMiscd::pi()*0.5*(1.0 - e2*( 1.0/4.0 + e2*( 3.0/64.0 + 
214                                                   e2*(5.0/256.0) )));
215 }
216
217
218 // given, lat1, lon1, az1 and distance (s), calculate lat2, lon2
219 // and az2.  Lat, lon, and azimuth are in degrees.  distance in meters
220 static int _geo_direct_wgs_84 ( double lat1, double lon1, double az1,
221                         double s, double *lat2, double *lon2,
222                         double *az2 )
223 {
224     double a = SGGeodesy::EQURAD, rf = SGGeodesy::iFLATTENING;
225     double testv = 1.0E-10;
226     double f = ( rf > 0.0 ? 1.0/rf : 0.0 );
227     double b = a*(1.0-f);
228     double e2 = f*(2.0-f);
229     double phi1 = SGMiscd::deg2rad(lat1), lam1 = SGMiscd::deg2rad(lon1);
230     double sinphi1 = sin(phi1), cosphi1 = cos(phi1);
231     double azm1 = SGMiscd::deg2rad(az1);
232     double sinaz1 = sin(azm1), cosaz1 = cos(azm1);
233         
234         
235     if( fabs(s) < 0.01 ) {      // distance < centimeter => congruency
236         *lat2 = lat1;
237         *lon2 = lon1;
238         *az2 = 180.0 + az1;
239         if( *az2 > 360.0 ) *az2 -= 360.0;
240         return 0;
241     } else if( SGLimitsd::min() < fabs(cosphi1) ) {     // non-polar origin
242         // u1 is reduced latitude
243         double tanu1 = sqrt(1.0-e2)*sinphi1/cosphi1;
244         double sig1 = atan2(tanu1,cosaz1);
245         double cosu1 = 1.0/sqrt( 1.0 + tanu1*tanu1 ), sinu1 = tanu1*cosu1;
246         double sinaz =  cosu1*sinaz1, cos2saz = 1.0-sinaz*sinaz;
247         double us = cos2saz*e2/(1.0-e2);
248
249         // Terms
250         double  ta = 1.0+us*(4096.0+us*(-768.0+us*(320.0-175.0*us)))/16384.0,
251             tb = us*(256.0+us*(-128.0+us*(74.0-47.0*us)))/1024.0,
252             tc = 0;
253
254         // FIRST ESTIMATE OF SIGMA (SIG)
255         double first = s/(b*ta);  // !!
256         double sig = first;
257         double c2sigm, sinsig,cossig, temp,denom,rnumer, dlams, dlam;
258         do {
259             c2sigm = cos(2.0*sig1+sig);
260             sinsig = sin(sig); cossig = cos(sig);
261             temp = sig;
262             sig = first + 
263                 tb*sinsig*(c2sigm+tb*(cossig*(-1.0+2.0*c2sigm*c2sigm) - 
264                                       tb*c2sigm*(-3.0+4.0*sinsig*sinsig)
265                                       *(-3.0+4.0*c2sigm*c2sigm)/6.0)
266                            /4.0);
267         } while( fabs(sig-temp) > testv);
268
269         // LATITUDE OF POINT 2
270         // DENOMINATOR IN 2 PARTS (TEMP ALSO USED LATER)
271         temp = sinu1*sinsig-cosu1*cossig*cosaz1;
272         denom = (1.0-f)*sqrt(sinaz*sinaz+temp*temp);
273
274         // NUMERATOR
275         rnumer = sinu1*cossig+cosu1*sinsig*cosaz1;
276         *lat2 = SGMiscd::rad2deg(atan2(rnumer,denom));
277
278         // DIFFERENCE IN LONGITUDE ON AUXILARY SPHERE (DLAMS )
279         rnumer = sinsig*sinaz1;
280         denom = cosu1*cossig-sinu1*sinsig*cosaz1;
281         dlams = atan2(rnumer,denom);
282
283         // TERM C
284         tc = f*cos2saz*(4.0+f*(4.0-3.0*cos2saz))/16.0;
285
286         // DIFFERENCE IN LONGITUDE
287         dlam = dlams-(1.0-tc)*f*sinaz*(sig+tc*sinsig*
288                                        (c2sigm+
289                                         tc*cossig*(-1.0+2.0*
290                                                    c2sigm*c2sigm)));
291         *lon2 = SGMiscd::rad2deg(lam1+dlam);
292         if (*lon2 > 180.0  ) *lon2 -= 360.0;
293         if (*lon2 < -180.0 ) *lon2 += 360.0;
294
295         // AZIMUTH - FROM NORTH
296         *az2 = SGMiscd::rad2deg(atan2(-sinaz,temp));
297         if ( fabs(*az2) < testv ) *az2 = 0.0;
298         if( *az2 < 0.0) *az2 += 360.0;
299         return 0;
300     } else {                    // phi1 == 90 degrees, polar origin
301         double dM = a*M0(e2) - s;
302         double paz = ( phi1 < 0.0 ? 180.0 : 0.0 );
303         double zero = 0.0f;
304         return _geo_direct_wgs_84( zero, lon1, paz, dM, lat2, lon2, az2 );
305     } 
306 }
307
308 bool
309 SGGeodesy::direct(const SGGeod& p1, double course1,
310                   double distance, SGGeod& p2, double& course2)
311 {
312   double lat2, lon2;
313   int ret = _geo_direct_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
314                                course1, distance, &lat2, &lon2, &course2);
315   p2.setLatitudeDeg(lat2);
316   p2.setLongitudeDeg(lon2);
317   p2.setElevationM(0);
318   return ret == 0;
319 }
320
321 SGGeod
322 SGGeodesy::direct(const SGGeod& p1, double course1, double distance)
323 {
324   double lat2, lon2, course2;
325   int ret = _geo_direct_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
326                                course1, distance, &lat2, &lon2, &course2);
327   if (ret != 0) {
328     throw sg_exception("_geo_direct_wgs_84 failed");
329   }
330   
331   SGGeod p2;
332   p2.setLatitudeDeg(lat2);
333   p2.setLongitudeDeg(lon2);
334   p2.setElevationM(0);
335   return p2;
336 }
337
338
339 // given lat1, lon1, lat2, lon2, calculate starting and ending
340 // az1, az2 and distance (s).  Lat, lon, and azimuth are in degrees.
341 // distance in meters
342 static int _geo_inverse_wgs_84( double lat1, double lon1, double lat2,
343                         double lon2, double *az1, double *az2,
344                         double *s )
345 {
346     double a = SGGeodesy::EQURAD, rf = SGGeodesy::iFLATTENING;
347     int iter=0;
348     double testv = 1.0E-10;
349     double f = ( rf > 0.0 ? 1.0/rf : 0.0 );
350     double b = a*(1.0-f);
351     // double e2 = f*(2.0-f); // unused in this routine
352     double phi1 = SGMiscd::deg2rad(lat1), lam1 = SGMiscd::deg2rad(lon1);
353     double sinphi1 = sin(phi1), cosphi1 = cos(phi1);
354     double phi2 = SGMiscd::deg2rad(lat2), lam2 = SGMiscd::deg2rad(lon2);
355     double sinphi2 = sin(phi2), cosphi2 = cos(phi2);
356         
357     if( (fabs(lat1-lat2) < testv && 
358          ( fabs(lon1-lon2) < testv)) || (fabs(lat1-90.0) < testv ) )
359     {   
360         // TWO STATIONS ARE IDENTICAL : SET DISTANCE & AZIMUTHS TO ZERO */
361         *az1 = 0.0; *az2 = 0.0; *s = 0.0;
362         return 0;
363     } else if(  fabs(cosphi1) < testv ) {
364         // initial point is polar
365         int k = _geo_inverse_wgs_84( lat2,lon2,lat1,lon1, az1,az2,s );
366         SG_UNUSED(k);
367     
368         b = *az1; *az1 = *az2; *az2 = b;
369         return 0;
370     } else if( fabs(cosphi2) < testv ) {
371         // terminal point is polar
372         double _lon1 = lon1 + 180.0f;
373         int k = _geo_inverse_wgs_84( lat1, lon1, lat1, _lon1, 
374                                     az1, az2, s );
375         SG_UNUSED(k);
376     
377         *s /= 2.0;
378         *az2 = *az1 + 180.0;
379         if( *az2 > 360.0 ) *az2 -= 360.0; 
380         return 0;
381     } else if( (fabs( fabs(lon1-lon2) - 180 ) < testv) && 
382                (fabs(lat1+lat2) < testv) ) 
383     {
384         // Geodesic passes through the pole (antipodal)
385         double s1,s2;
386         _geo_inverse_wgs_84( lat1,lon1, lat1,lon2, az1,az2, &s1 );
387         _geo_inverse_wgs_84( lat2,lon2, lat1,lon2, az1,az2, &s2 );
388         *az2 = *az1;
389         *s = s1 + s2;
390         return 0;
391     } else {
392         // antipodal and polar points don't get here
393         double dlam = lam2 - lam1, dlams = dlam;
394         double sdlams,cdlams, sig,sinsig,cossig, sinaz,
395             cos2saz, c2sigm;
396         double tc,temp, us,rnumer,denom, ta,tb;
397         double cosu1,sinu1, sinu2,cosu2;
398
399         // Reduced latitudes
400         temp = (1.0-f)*sinphi1/cosphi1;
401         cosu1 = 1.0/sqrt(1.0+temp*temp);
402         sinu1 = temp*cosu1;
403         temp = (1.0-f)*sinphi2/cosphi2;
404         cosu2 = 1.0/sqrt(1.0+temp*temp);
405         sinu2 = temp*cosu2;
406     
407         do {
408             sdlams = sin(dlams), cdlams = cos(dlams);
409             sinsig = sqrt(cosu2*cosu2*sdlams*sdlams+
410                           (cosu1*sinu2-sinu1*cosu2*cdlams)*
411                           (cosu1*sinu2-sinu1*cosu2*cdlams));
412             cossig = sinu1*sinu2+cosu1*cosu2*cdlams;
413             
414             sig = atan2(sinsig,cossig);
415             sinaz = cosu1*cosu2*sdlams/sinsig;
416             cos2saz = 1.0-sinaz*sinaz;
417             c2sigm = (sinu1 == 0.0 || sinu2 == 0.0 ? cossig : 
418                       cossig-2.0*sinu1*sinu2/cos2saz);
419             tc = f*cos2saz*(4.0+f*(4.0-3.0*cos2saz))/16.0;
420             temp = dlams;
421             dlams = dlam+(1.0-tc)*f*sinaz*
422                 (sig+tc*sinsig*
423                  (c2sigm+tc*cossig*(-1.0+2.0*c2sigm*c2sigm)));
424             if (fabs(dlams) > SGMiscd::pi() && iter++ > 50) {
425                 return iter;
426             }
427         } while ( fabs(temp-dlams) > testv);
428
429         us = cos2saz*(a*a-b*b)/(b*b); // !!
430         // BACK AZIMUTH FROM NORTH
431         rnumer = -(cosu1*sdlams);
432         denom = sinu1*cosu2-cosu1*sinu2*cdlams;
433         *az2 = SGMiscd::rad2deg(atan2(rnumer,denom));
434         if( fabs(*az2) < testv ) *az2 = 0.0;
435         if(*az2 < 0.0) *az2 += 360.0;
436
437         // FORWARD AZIMUTH FROM NORTH
438         rnumer = cosu2*sdlams;
439         denom = cosu1*sinu2-sinu1*cosu2*cdlams;
440         *az1 = SGMiscd::rad2deg(atan2(rnumer,denom));
441         if( fabs(*az1) < testv ) *az1 = 0.0;
442         if(*az1 < 0.0) *az1 += 360.0;
443
444         // Terms a & b
445         ta = 1.0+us*(4096.0+us*(-768.0+us*(320.0-175.0*us)))/
446             16384.0;
447         tb = us*(256.0+us*(-128.0+us*(74.0-47.0*us)))/1024.0;
448
449         // GEODETIC DISTANCE
450         *s = b*ta*(sig-tb*sinsig*
451                    (c2sigm+tb*(cossig*(-1.0+2.0*c2sigm*c2sigm)-tb*
452                                c2sigm*(-3.0+4.0*sinsig*sinsig)*
453                                (-3.0+4.0*c2sigm*c2sigm)/6.0)/
454                     4.0));
455         return 0;
456     }
457 }
458
459 bool
460 SGGeodesy::inverse(const SGGeod& p1, const SGGeod& p2, double& course1,
461                    double& course2, double& distance)
462 {
463   int ret = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
464                                 p2.getLatitudeDeg(), p2.getLongitudeDeg(),
465                                 &course1, &course2, &distance);
466   return ret == 0;
467 }
468
469 double
470 SGGeodesy::courseDeg(const SGGeod& p1, const SGGeod& p2)
471 {
472   double course1, course2, distance;
473   int r = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
474                                 p2.getLatitudeDeg(), p2.getLongitudeDeg(),
475                                 &course1, &course2, &distance);
476   if (r != 0) {
477     throw sg_exception("SGGeodesy::courseDeg, unable to compute course");
478   }
479   
480   return course1;
481 }
482
483 double
484 SGGeodesy::distanceM(const SGGeod& p1, const SGGeod& p2)
485 {
486   double course1, course2, distance;
487   int r = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
488                                 p2.getLatitudeDeg(), p2.getLongitudeDeg(),
489                                 &course1, &course2, &distance);
490   if (r != 0) {
491     throw sg_exception("SGGeodesy::distanceM, unable to compute distance");
492   }
493   
494   return distance;
495 }
496
497 double
498 SGGeodesy::distanceNm(const SGGeod& from, const SGGeod& to)
499 {
500   return distanceM(from, to) * SG_METER_TO_NM;
501 }
502
503 /// Geocentric routines
504
505 void
506 SGGeodesy::advanceRadM(const SGGeoc& geoc, double course, double distance,
507                        SGGeoc& result)
508 {
509   result.setRadiusM(geoc.getRadiusM());
510
511   // lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
512   // IF (cos(lat)=0)
513   //   lon=lon1      // endpoint a pole
514   // ELSE
515   //   lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi
516   // ENDIF
517   
518   distance *= SG_METER_TO_NM * SG_NM_TO_RAD;
519   
520   double sinDistance = sin(distance);
521   double cosDistance = cos(distance);
522
523   double sinLat = sin(geoc.getLatitudeRad()) * cosDistance +
524     cos(geoc.getLatitudeRad()) * sinDistance * cos(course);
525   sinLat = SGMiscd::clip(sinLat, -1, 1);
526   result.setLatitudeRad(asin(sinLat));
527   double cosLat = cos(result.getLatitudeRad());
528   
529   
530   if (cosLat <= SGLimitsd::min()) {
531     // endpoint a pole
532     result.setLongitudeRad(geoc.getLongitudeRad());
533   } else {
534     double tmp = SGMiscd::clip(sin(course) * sinDistance / cosLat, -1, 1);
535     double lon = SGMiscd::normalizeAngle(-geoc.getLongitudeRad() - asin( tmp ));
536     result.setLongitudeRad(-lon);
537   }
538 }
539
540 double
541 SGGeodesy::courseRad(const SGGeoc& from, const SGGeoc& to)
542 {
543   //double diffLon = to.getLongitudeRad() - from.getLongitudeRad();
544   double diffLon = from.getLongitudeRad() - to.getLongitudeRad();
545
546   double sinLatFrom = sin(from.getLatitudeRad());
547   double cosLatFrom = cos(from.getLatitudeRad());
548
549   double sinLatTo = sin(to.getLatitudeRad());
550   double cosLatTo = cos(to.getLatitudeRad());
551
552   double x = cosLatTo*sin(diffLon);
553   double y = cosLatFrom*sinLatTo - sinLatFrom*cosLatTo*cos(diffLon);
554
555   // guard atan2 returning NaN's
556   if (fabs(x) <= SGLimitsd::min() && fabs(y) <= SGLimitsd::min())
557     return 0;
558
559   double c = atan2(x, y);
560   if (c >= 0)
561     return SGMiscd::twopi() - c;
562   else
563     return -c;
564 }
565
566 double
567 SGGeodesy::distanceRad(const SGGeoc& from, const SGGeoc& to)
568 {
569   // d = 2*asin(sqrt((sin((lat1-lat2)/2))^2 +
570   //            cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2))^2))
571   double cosLatFrom = cos(from.getLatitudeRad());
572   double cosLatTo = cos(to.getLatitudeRad());
573   double tmp1 = sin(0.5*(from.getLatitudeRad() - to.getLatitudeRad()));
574   double tmp2 = sin(0.5*(from.getLongitudeRad() - to.getLongitudeRad()));
575   double square = tmp1*tmp1 + cosLatFrom*cosLatTo*tmp2*tmp2;
576   double s = SGMiscd::min(sqrt(SGMiscd::max(square, 0)), 1);
577   return 2 * asin(s);
578 }
579
580
581 double
582 SGGeodesy::distanceM(const SGGeoc& from, const SGGeoc& to)
583 {
584   return distanceRad(from, to) * SG_RAD_TO_NM * SG_NM_TO_METER;
585 }
586
587 bool 
588 SGGeodesy::radialIntersection(const SGGeoc& a, double r1, 
589     const SGGeoc& b, double r2, SGGeoc& result)
590 {
591   // implementation of
592   // http://williams.best.vwh.net/avform.htm#Intersection
593
594   double crs13 = r1 * SG_DEGREES_TO_RADIANS;
595   double crs23 = r2 * SG_DEGREES_TO_RADIANS;
596   double dst12 = SGGeodesy::distanceRad(a, b);
597
598   //IF sin(lon2-lon1)<0
599   // crs12=acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
600   // crs21=2.*pi-acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
601   // ELSE
602   // crs12=2.*pi-acos((sin(lat2)-sin(lat1)*cos(dst12))/(sin(dst12)*cos(lat1)))
603   // crs21=acos((sin(lat1)-sin(lat2)*cos(dst12))/(sin(dst12)*cos(lat2)))
604   // ENDIF
605   double crs12 = SGGeodesy::courseRad(a, b),
606          crs21 = SGGeodesy::courseRad(b, a);
607
608   double sinLat1 = sin(a.getLatitudeRad());
609   double cosLat1 = cos(a.getLatitudeRad());
610   double sinDst12 = sin(dst12);
611   double cosDst12 = cos(dst12);
612   
613   double ang1 = SGMiscd::normalizeAngle2(crs13-crs12);
614   double ang2 = SGMiscd::normalizeAngle2(crs21-crs23);
615    
616   if ((sin(ang1) == 0.0) && (sin(ang2) == 0.0)) {
617     SG_LOG(SG_GENERAL, SG_WARN, "SGGeodesy::radialIntersection: infinity of intersections");
618     return false;
619   }
620
621   if ((sin(ang1)*sin(ang2))<0.0) {
622     SG_LOG(SG_GENERAL, SG_WARN, "SGGeodesy::radialIntersection: intersection ambiguous");
623     return false;
624   }
625
626   ang1 = fabs(ang1);
627   ang2 = fabs(ang2);
628
629   //ang3=acos(-cos(ang1)*cos(ang2)+sin(ang1)*sin(ang2)*cos(dst12)) 
630   //dst13=atan2(sin(dst12)*sin(ang1)*sin(ang2),cos(ang2)+cos(ang1)*cos(ang3))
631   //lat3=asin(sin(lat1)*cos(dst13)+cos(lat1)*sin(dst13)*cos(crs13))
632   //lon3=mod(lon1-dlon+pi,2*pi)-pi
633
634   double ang3 = acos(-cos(ang1) * cos(ang2) + sin(ang1) * sin(ang2) * cosDst12);
635   double dst13 = atan2(sinDst12 * sin(ang1) * sin(ang2), cos(ang2) + cos(ang1)*cos(ang3));
636   double lat3 = asin(sinLat1 * cos(dst13) + cosLat1 * sin(dst13) * cos(crs13));
637   //dlon=atan2(sin(crs13)*sin(dst13)*cos(lat1),cos(dst13)-sin(lat1)*sin(lat3))
638   double dlon = atan2(sin(crs13)*sin(dst13)*cosLat1, cos(dst13)- (sinLat1 * sin(lat3)));
639   double lon3 = SGMiscd::normalizeAngle(-a.getLongitudeRad()-dlon);
640
641   result = SGGeoc::fromRadM(-lon3, lat3, a.getRadiusM());
642   return true;
643 }
644     
645 bool
646 SGGeodesy::radialIntersection(const SGGeod& a, double aRadial, 
647     const SGGeod& b, double bRadial, SGGeod& result)
648 {
649   SGGeoc r;
650   bool ok = radialIntersection(SGGeoc::fromGeod(a), aRadial, 
651     SGGeoc::fromGeod(b), bRadial, r);
652   if (!ok) {
653     return false;
654   }
655   
656   result = SGGeod::fromGeoc(r);
657   return true;
658 }