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