00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #ifdef HAVE_CONFIG_H
00019 # include <simgear_config.h>
00020 #endif
00021
00022 #include <cmath>
00023
00024 #include "SGMath.hxx"
00025
00026
00027
00028 #define _EQURAD 6378137.0
00029 #define _FLATTENING 298.257223563
00030
00031
00032 #if 0
00033 #define _SQUASH (1 - 1/_FLATTENING)
00034 #define _STRETCH (1/_SQUASH)
00035 #define _POLRAD (EQURAD * _SQUASH)
00036 #else
00037
00038
00039
00040
00041 #define _SQUASH 0.9966471893352525192801545
00042 #define _STRETCH 1.0033640898209764189003079
00043 #define _POLRAD 6356752.3142451794975639668
00044 #endif
00045
00046
00047 const double SGGeodesy::EQURAD = _EQURAD;
00048 const double SGGeodesy::iFLATTENING = _FLATTENING;
00049 const double SGGeodesy::SQUASH = _SQUASH;
00050 const double SGGeodesy::STRETCH = _STRETCH;
00051 const double SGGeodesy::POLRAD = _POLRAD;
00052
00053
00054
00055
00056 #define E2 fabs(1 - _SQUASH*_SQUASH)
00057 static double a = _EQURAD;
00058 static double ra2 = 1/(_EQURAD*_EQURAD);
00059 static double e = sqrt(E2);
00060 static double e2 = E2;
00061 static double e4 = E2*E2;
00062
00063 #undef _EQURAD
00064 #undef _FLATTENING
00065 #undef _SQUASH
00066 #undef _STRETCH
00067 #undef _POLRAD
00068 #undef E2
00069
00070 void
00071 SGGeodesy::SGCartToGeod(const SGVec3<double>& cart, SGGeod& geod)
00072 {
00073
00074
00075
00076
00077 double X = cart(0);
00078 double Y = cart(1);
00079 double Z = cart(2);
00080 double XXpYY = X*X+Y*Y;
00081 double sqrtXXpYY = sqrt(XXpYY);
00082 double p = XXpYY*ra2;
00083 double q = Z*Z*(1-e2)*ra2;
00084 double r = 1/6.0*(p+q-e4);
00085 double s = e4*p*q/(4*r*r*r);
00086 double t = pow(1+s+sqrt(s*(2+s)), 1/3.0);
00087 double u = r*(1+t+1/t);
00088 double v = sqrt(u*u+e4*q);
00089 double w = e2*(u+v-q)/(2*v);
00090 double k = sqrt(u+v+w*w)-w;
00091 double D = k*sqrtXXpYY/(k+e2);
00092 geod.setLongitudeRad(2*atan2(Y, X+sqrtXXpYY));
00093 double sqrtDDpZZ = sqrt(D*D+Z*Z);
00094 geod.setLatitudeRad(2*atan2(Z, D+sqrtDDpZZ));
00095 geod.setElevationM((k+e2-1)*sqrtDDpZZ/k);
00096 }
00097
00098 void
00099 SGGeodesy::SGGeodToCart(const SGGeod& geod, SGVec3<double>& cart)
00100 {
00101
00102
00103
00104
00105 double lambda = geod.getLongitudeRad();
00106 double phi = geod.getLatitudeRad();
00107 double h = geod.getElevationM();
00108 double sphi = sin(phi);
00109 double n = a/sqrt(1-e2*sphi*sphi);
00110 double cphi = cos(phi);
00111 double slambda = sin(lambda);
00112 double clambda = cos(lambda);
00113 cart(0) = (h+n)*cphi*clambda;
00114 cart(1) = (h+n)*cphi*slambda;
00115 cart(2) = (h+n-e2*n)*sphi;
00116 }
00117
00118 double
00119 SGGeodesy::SGGeodToSeaLevelRadius(const SGGeod& geod)
00120 {
00121
00122
00123 double phi = geod.getLatitudeRad();
00124 double sphi = sin(phi);
00125 double sphi2 = sphi*sphi;
00126 return a*sqrt((1 + (e4 - 2*e2)*sphi2)/(1 - e2*sphi2));
00127 }
00128
00129 void
00130 SGGeodesy::SGCartToGeoc(const SGVec3<double>& cart, SGGeoc& geoc)
00131 {
00132 double minVal = SGLimits<double>::min();
00133 if (fabs(cart(0)) < minVal && fabs(cart(1)) < minVal)
00134 geoc.setLongitudeRad(0);
00135 else
00136 geoc.setLongitudeRad(atan2(cart(1), cart(0)));
00137
00138 double nxy = sqrt(cart(0)*cart(0) + cart(1)*cart(1));
00139 if (fabs(nxy) < minVal && fabs(cart(2)) < minVal)
00140 geoc.setLatitudeRad(0);
00141 else
00142 geoc.setLatitudeRad(atan2(cart(2), nxy));
00143
00144 geoc.setRadiusM(norm(cart));
00145 }
00146
00147 void
00148 SGGeodesy::SGGeocToCart(const SGGeoc& geoc, SGVec3<double>& cart)
00149 {
00150 double lat = geoc.getLatitudeRad();
00151 double lon = geoc.getLongitudeRad();
00152 double slat = sin(lat);
00153 double clat = cos(lat);
00154 double slon = sin(lon);
00155 double clon = cos(lon);
00156 cart = geoc.getRadiusM()*SGVec3<double>(clat*clon, clat*slon, slat);
00157 }
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188 static inline double M0( double e2 ) {
00189
00190 return SGMiscd::pi()*0.5*(1.0 - e2*( 1.0/4.0 + e2*( 3.0/64.0 +
00191 e2*(5.0/256.0) )));
00192 }
00193
00194
00195
00196
00197 static int _geo_direct_wgs_84 ( double lat1, double lon1, double az1,
00198 double s, double *lat2, double *lon2,
00199 double *az2 )
00200 {
00201 double a = SGGeodesy::EQURAD, rf = SGGeodesy::iFLATTENING;
00202 double testv = 1.0E-10;
00203 double f = ( rf > 0.0 ? 1.0/rf : 0.0 );
00204 double b = a*(1.0-f);
00205 double e2 = f*(2.0-f);
00206 double phi1 = SGMiscd::deg2rad(lat1), lam1 = SGMiscd::deg2rad(lon1);
00207 double sinphi1 = sin(phi1), cosphi1 = cos(phi1);
00208 double azm1 = SGMiscd::deg2rad(az1);
00209 double sinaz1 = sin(azm1), cosaz1 = cos(azm1);
00210
00211
00212 if( fabs(s) < 0.01 ) {
00213 *lat2 = lat1;
00214 *lon2 = lon1;
00215 *az2 = 180.0 + az1;
00216 if( *az2 > 360.0 ) *az2 -= 360.0;
00217 return 0;
00218 } else if( SGLimitsd::min() < fabs(cosphi1) ) {
00219
00220 double tanu1 = sqrt(1.0-e2)*sinphi1/cosphi1;
00221 double sig1 = atan2(tanu1,cosaz1);
00222 double cosu1 = 1.0/sqrt( 1.0 + tanu1*tanu1 ), sinu1 = tanu1*cosu1;
00223 double sinaz = cosu1*sinaz1, cos2saz = 1.0-sinaz*sinaz;
00224 double us = cos2saz*e2/(1.0-e2);
00225
00226
00227 double ta = 1.0+us*(4096.0+us*(-768.0+us*(320.0-175.0*us)))/16384.0,
00228 tb = us*(256.0+us*(-128.0+us*(74.0-47.0*us)))/1024.0,
00229 tc = 0;
00230
00231
00232 double first = s/(b*ta);
00233 double sig = first;
00234 double c2sigm, sinsig,cossig, temp,denom,rnumer, dlams, dlam;
00235 do {
00236 c2sigm = cos(2.0*sig1+sig);
00237 sinsig = sin(sig); cossig = cos(sig);
00238 temp = sig;
00239 sig = first +
00240 tb*sinsig*(c2sigm+tb*(cossig*(-1.0+2.0*c2sigm*c2sigm) -
00241 tb*c2sigm*(-3.0+4.0*sinsig*sinsig)
00242 *(-3.0+4.0*c2sigm*c2sigm)/6.0)
00243 /4.0);
00244 } while( fabs(sig-temp) > testv);
00245
00246
00247
00248 temp = sinu1*sinsig-cosu1*cossig*cosaz1;
00249 denom = (1.0-f)*sqrt(sinaz*sinaz+temp*temp);
00250
00251
00252 rnumer = sinu1*cossig+cosu1*sinsig*cosaz1;
00253 *lat2 = SGMiscd::rad2deg(atan2(rnumer,denom));
00254
00255
00256 rnumer = sinsig*sinaz1;
00257 denom = cosu1*cossig-sinu1*sinsig*cosaz1;
00258 dlams = atan2(rnumer,denom);
00259
00260
00261 tc = f*cos2saz*(4.0+f*(4.0-3.0*cos2saz))/16.0;
00262
00263
00264 dlam = dlams-(1.0-tc)*f*sinaz*(sig+tc*sinsig*
00265 (c2sigm+
00266 tc*cossig*(-1.0+2.0*
00267 c2sigm*c2sigm)));
00268 *lon2 = SGMiscd::rad2deg(lam1+dlam);
00269 if (*lon2 > 180.0 ) *lon2 -= 360.0;
00270 if (*lon2 < -180.0 ) *lon2 += 360.0;
00271
00272
00273 *az2 = SGMiscd::rad2deg(atan2(-sinaz,temp));
00274 if ( fabs(*az2) < testv ) *az2 = 0.0;
00275 if( *az2 < 0.0) *az2 += 360.0;
00276 return 0;
00277 } else {
00278 double dM = a*M0(e2) - s;
00279 double paz = ( phi1 < 0.0 ? 180.0 : 0.0 );
00280 double zero = 0.0f;
00281 return _geo_direct_wgs_84( zero, lon1, paz, dM, lat2, lon2, az2 );
00282 }
00283 }
00284
00285 bool
00286 SGGeodesy::direct(const SGGeod& p1, double course1,
00287 double distance, SGGeod& p2, double& course2)
00288 {
00289 double lat2, lon2;
00290 int ret = _geo_direct_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
00291 course1, distance, &lat2, &lon2, &course2);
00292 p2.setLatitudeDeg(lat2);
00293 p2.setLongitudeDeg(lon2);
00294 p2.setElevationM(0);
00295 return ret == 0;
00296 }
00297
00298
00299
00300
00301 static int _geo_inverse_wgs_84( double lat1, double lon1, double lat2,
00302 double lon2, double *az1, double *az2,
00303 double *s )
00304 {
00305 double a = SGGeodesy::EQURAD, rf = SGGeodesy::iFLATTENING;
00306 int iter=0;
00307 double testv = 1.0E-10;
00308 double f = ( rf > 0.0 ? 1.0/rf : 0.0 );
00309 double b = a*(1.0-f);
00310
00311 double phi1 = SGMiscd::deg2rad(lat1), lam1 = SGMiscd::deg2rad(lon1);
00312 double sinphi1 = sin(phi1), cosphi1 = cos(phi1);
00313 double phi2 = SGMiscd::deg2rad(lat2), lam2 = SGMiscd::deg2rad(lon2);
00314 double sinphi2 = sin(phi2), cosphi2 = cos(phi2);
00315
00316 if( (fabs(lat1-lat2) < testv &&
00317 ( fabs(lon1-lon2) < testv) || fabs(lat1-90.0) < testv ) )
00318 {
00319
00320 *az1 = 0.0; *az2 = 0.0; *s = 0.0;
00321 return 0;
00322 } else if( fabs(cosphi1) < testv ) {
00323
00324 int k = _geo_inverse_wgs_84( lat2,lon2,lat1,lon1, az1,az2,s );
00325 k = k;
00326 b = *az1; *az1 = *az2; *az2 = b;
00327 return 0;
00328 } else if( fabs(cosphi2) < testv ) {
00329
00330 double _lon1 = lon1 + 180.0f;
00331 int k = _geo_inverse_wgs_84( lat1, lon1, lat1, _lon1,
00332 az1, az2, s );
00333 k = k;
00334 *s /= 2.0;
00335 *az2 = *az1 + 180.0;
00336 if( *az2 > 360.0 ) *az2 -= 360.0;
00337 return 0;
00338 } else if( (fabs( fabs(lon1-lon2) - 180 ) < testv) &&
00339 (fabs(lat1+lat2) < testv) )
00340 {
00341
00342 double s1,s2;
00343 _geo_inverse_wgs_84( lat1,lon1, lat1,lon2, az1,az2, &s1 );
00344 _geo_inverse_wgs_84( lat2,lon2, lat1,lon2, az1,az2, &s2 );
00345 *az2 = *az1;
00346 *s = s1 + s2;
00347 return 0;
00348 } else {
00349
00350 double dlam = lam2 - lam1, dlams = dlam;
00351 double sdlams,cdlams, sig,sinsig,cossig, sinaz,
00352 cos2saz, c2sigm;
00353 double tc,temp, us,rnumer,denom, ta,tb;
00354 double cosu1,sinu1, sinu2,cosu2;
00355
00356
00357 temp = (1.0-f)*sinphi1/cosphi1;
00358 cosu1 = 1.0/sqrt(1.0+temp*temp);
00359 sinu1 = temp*cosu1;
00360 temp = (1.0-f)*sinphi2/cosphi2;
00361 cosu2 = 1.0/sqrt(1.0+temp*temp);
00362 sinu2 = temp*cosu2;
00363
00364 do {
00365 sdlams = sin(dlams), cdlams = cos(dlams);
00366 sinsig = sqrt(cosu2*cosu2*sdlams*sdlams+
00367 (cosu1*sinu2-sinu1*cosu2*cdlams)*
00368 (cosu1*sinu2-sinu1*cosu2*cdlams));
00369 cossig = sinu1*sinu2+cosu1*cosu2*cdlams;
00370
00371 sig = atan2(sinsig,cossig);
00372 sinaz = cosu1*cosu2*sdlams/sinsig;
00373 cos2saz = 1.0-sinaz*sinaz;
00374 c2sigm = (sinu1 == 0.0 || sinu2 == 0.0 ? cossig :
00375 cossig-2.0*sinu1*sinu2/cos2saz);
00376 tc = f*cos2saz*(4.0+f*(4.0-3.0*cos2saz))/16.0;
00377 temp = dlams;
00378 dlams = dlam+(1.0-tc)*f*sinaz*
00379 (sig+tc*sinsig*
00380 (c2sigm+tc*cossig*(-1.0+2.0*c2sigm*c2sigm)));
00381 if (fabs(dlams) > SGMiscd::pi() && iter++ > 50) {
00382 return iter;
00383 }
00384 } while ( fabs(temp-dlams) > testv);
00385
00386 us = cos2saz*(a*a-b*b)/(b*b);
00387
00388 rnumer = -(cosu1*sdlams);
00389 denom = sinu1*cosu2-cosu1*sinu2*cdlams;
00390 *az2 = SGMiscd::rad2deg(atan2(rnumer,denom));
00391 if( fabs(*az2) < testv ) *az2 = 0.0;
00392 if(*az2 < 0.0) *az2 += 360.0;
00393
00394
00395 rnumer = cosu2*sdlams;
00396 denom = cosu1*sinu2-sinu1*cosu2*cdlams;
00397 *az1 = SGMiscd::rad2deg(atan2(rnumer,denom));
00398 if( fabs(*az1) < testv ) *az1 = 0.0;
00399 if(*az1 < 0.0) *az1 += 360.0;
00400
00401
00402 ta = 1.0+us*(4096.0+us*(-768.0+us*(320.0-175.0*us)))/
00403 16384.0;
00404 tb = us*(256.0+us*(-128.0+us*(74.0-47.0*us)))/1024.0;
00405
00406
00407 *s = b*ta*(sig-tb*sinsig*
00408 (c2sigm+tb*(cossig*(-1.0+2.0*c2sigm*c2sigm)-tb*
00409 c2sigm*(-3.0+4.0*sinsig*sinsig)*
00410 (-3.0+4.0*c2sigm*c2sigm)/6.0)/
00411 4.0));
00412 return 0;
00413 }
00414 }
00415
00416 bool
00417 SGGeodesy::inverse(const SGGeod& p1, const SGGeod& p2, double& course1,
00418 double& course2, double& distance)
00419 {
00420 int ret = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(),
00421 p2.getLatitudeDeg(), p2.getLongitudeDeg(),
00422 &course1, &course2, &distance);
00423 return ret == 0;
00424 }
00425
00427
00428 void
00429 SGGeodesy::advanceRadM(const SGGeoc& geoc, double course, double distance,
00430 SGGeoc& result)
00431 {
00432 result.setRadiusM(geoc.getRadiusM());
00433
00434
00435
00436
00437
00438
00439
00440
00441 distance *= SG_METER_TO_NM * SG_NM_TO_RAD;
00442
00443 double sinDistance = sin(distance);
00444 double cosDistance = cos(distance);
00445
00446 double sinLat = sin(geoc.getLatitudeRad()) * cosDistance +
00447 cos(geoc.getLatitudeRad()) * sinDistance * cos(course);
00448 sinLat = SGMiscd::clip(sinLat, -1, 1);
00449 result.setLatitudeRad(asin(sinLat));
00450 double cosLat = cos(result.getLatitudeRad());
00451
00452
00453 if (cosLat <= SGLimitsd::min()) {
00454
00455 result.setLongitudeRad(geoc.getLongitudeRad());
00456 } else {
00457 double tmp = SGMiscd::clip(sin(course) * sinDistance / cosLat, -1, 1);
00458 double lon = SGMiscd::normalizeAngle(geoc.getLongitudeRad() - asin( tmp ));
00459 result.setLongitudeRad(lon);
00460 }
00461 }
00462
00463 double
00464 SGGeodesy::courseRad(const SGGeoc& from, const SGGeoc& to)
00465 {
00466 double diffLon = to.getLongitudeRad() - from.getLongitudeRad();
00467
00468 double sinLatFrom = sin(from.getLatitudeRad());
00469 double cosLatFrom = cos(from.getLatitudeRad());
00470
00471 double sinLatTo = sin(to.getLatitudeRad());
00472 double cosLatTo = cos(to.getLatitudeRad());
00473
00474 double x = cosLatTo*sin(diffLon);
00475 double y = cosLatFrom*sinLatTo - sinLatFrom*cosLatTo*cos(diffLon);
00476
00477
00478 if (fabs(x) <= SGLimitsd::min() && fabs(y) <= SGLimitsd::min())
00479 return 0;
00480
00481 double c = atan2(x, y);
00482 if (c >= 0)
00483 return SGMiscd::twopi() - c;
00484 else
00485 return -c;
00486 }
00487
00488 double
00489 SGGeodesy::distanceM(const SGGeoc& from, const SGGeoc& to)
00490 {
00491
00492
00493 double cosLatFrom = cos(from.getLatitudeRad());
00494 double cosLatTo = cos(to.getLatitudeRad());
00495 double tmp1 = sin(0.5*(from.getLatitudeRad() - to.getLatitudeRad()));
00496 double tmp2 = sin(0.5*(from.getLongitudeRad() - to.getLongitudeRad()));
00497 double square = tmp1*tmp1 + cosLatFrom*cosLatTo*tmp2*tmp2;
00498 double s = SGMiscd::min(sqrt(SGMiscd::max(square, 0)), 1);
00499 return 2 * asin(s) * SG_RAD_TO_NM * SG_NM_TO_METER;
00500 }