X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fmath%2FSGGeodesy.cxx;h=b9974f14c66601be2063896c99d59fc8bfa5adc3;hb=3cd4c5566f8c418bc9bbb6026072eb0192027993;hp=079c368b547411d2c39f5d79dad6d495e49c5b61;hpb=4e7fe460a5c5c1b64dd1d540bc197dbb89614b7f;p=simgear.git diff --git a/simgear/math/SGGeodesy.cxx b/simgear/math/SGGeodesy.cxx index 079c368b..b9974f14 100644 --- a/simgear/math/SGGeodesy.cxx +++ b/simgear/math/SGGeodesy.cxx @@ -10,10 +10,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // -// You should have received a copy of the GNU Library General Public -// License along with this library; if not, write to the -// Free Software Foundation, Inc., 59 Temple Place - Suite 330, -// Boston, MA 02111-1307, USA. +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // #ifdef HAVE_CONFIG_H @@ -22,14 +21,16 @@ #include +#include #include "SGMath.hxx" // These are hard numbers from the WGS84 standard. DON'T MODIFY // unless you want to change the datum. #define _EQURAD 6378137.0 -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +#define _FLATTENING 298.257223563 + +// These are derived quantities more useful to the code: +#if 0 #define _SQUASH (1 - 1/_FLATTENING) #define _STRETCH (1/_SQUASH) #define _POLRAD (EQURAD * _SQUASH) @@ -56,7 +57,7 @@ const double SGGeodesy::POLRAD = _POLRAD; #define E2 fabs(1 - _SQUASH*_SQUASH) static double a = _EQURAD; static double ra2 = 1/(_EQURAD*_EQURAD); -static double e = sqrt(E2); +//static double e = sqrt(E2); static double e2 = E2; static double e4 = E2*E2; @@ -78,11 +79,30 @@ SGGeodesy::SGCartToGeod(const SGVec3& cart, SGGeod& geod) double Y = cart(1); double Z = cart(2); double XXpYY = X*X+Y*Y; + if( XXpYY + Z*Z < 25 ) { + // This function fails near the geocenter region, so catch that special case here. + // Define the innermost sphere of small radius as earth center and return the + // coordinates 0/0/-EQURAD. It may be any other place on geoide's surface, + // the Northpole, Hawaii or Wentorf. This one was easy to code ;-) + geod.setLongitudeRad( 0.0 ); + geod.setLongitudeRad( 0.0 ); + geod.setElevationM( -EQURAD ); + return; + } + double sqrtXXpYY = sqrt(XXpYY); double p = XXpYY*ra2; double q = Z*Z*(1-e2)*ra2; double r = 1/6.0*(p+q-e4); double s = e4*p*q/(4*r*r*r); +/* + s*(2+s) is negative for s = [-2..0] + slightly negative values for s due to floating point rounding errors + cause nan for sqrt(s*(2+s)) + We can probably clamp the resulting parable to positive numbers +*/ + if( s >= -2.0 && s <= 0.0 ) + s = 0.0; double t = pow(1+s+sqrt(s*(2+s)), 1/3.0); double u = r*(1+t+1/t); double v = sqrt(u*u+e4*q); @@ -155,3 +175,387 @@ SGGeodesy::SGGeocToCart(const SGGeoc& geoc, SGVec3& cart) double clon = cos(lon); cart = geoc.getRadiusM()*SGVec3(clat*clon, clat*slon, slat); } + +// Notes: +// +// The XYZ/cartesian coordinate system in use puts the X axis through +// zero lat/lon (off west Africa), the Z axis through the north pole, +// and the Y axis through 90 degrees longitude (in the Indian Ocean). +// +// All latitude and longitude values are in radians. Altitude is in +// meters, with zero on the WGS84 ellipsoid. +// +// The code below makes use of the notion of "squashed" space. This +// is a 2D cylindrical coordinate system where the radius from the Z +// axis is multiplied by SQUASH; the earth in this space is a perfect +// circle with a radius of POLRAD. + +//////////////////////////////////////////////////////////////////////// +// +// Direct and inverse distance functions +// +// Proceedings of the 7th International Symposium on Geodetic +// Computations, 1985 +// +// "The Nested Coefficient Method for Accurate Solutions of Direct and +// Inverse Geodetic Problems With Any Length" +// +// Zhang Xue-Lian +// pp 747-763 +// +// modified for FlightGear to use WGS84 only -- Norman Vine + +static inline double M0( double e2 ) { + //double e4 = e2*e2; + return SGMiscd::pi()*0.5*(1.0 - e2*( 1.0/4.0 + e2*( 3.0/64.0 + + e2*(5.0/256.0) ))); +} + + +// given, lat1, lon1, az1 and distance (s), calculate lat2, lon2 +// and az2. Lat, lon, and azimuth are in degrees. distance in meters +static int _geo_direct_wgs_84 ( double lat1, double lon1, double az1, + double s, double *lat2, double *lon2, + double *az2 ) +{ + double a = SGGeodesy::EQURAD, rf = SGGeodesy::iFLATTENING; + double testv = 1.0E-10; + double f = ( rf > 0.0 ? 1.0/rf : 0.0 ); + double b = a*(1.0-f); + double e2 = f*(2.0-f); + double phi1 = SGMiscd::deg2rad(lat1), lam1 = SGMiscd::deg2rad(lon1); + double sinphi1 = sin(phi1), cosphi1 = cos(phi1); + double azm1 = SGMiscd::deg2rad(az1); + double sinaz1 = sin(azm1), cosaz1 = cos(azm1); + + + if( fabs(s) < 0.01 ) { // distance < centimeter => congruency + *lat2 = lat1; + *lon2 = lon1; + *az2 = 180.0 + az1; + if( *az2 > 360.0 ) *az2 -= 360.0; + return 0; + } else if( SGLimitsd::min() < fabs(cosphi1) ) { // non-polar origin + // u1 is reduced latitude + double tanu1 = sqrt(1.0-e2)*sinphi1/cosphi1; + double sig1 = atan2(tanu1,cosaz1); + double cosu1 = 1.0/sqrt( 1.0 + tanu1*tanu1 ), sinu1 = tanu1*cosu1; + double sinaz = cosu1*sinaz1, cos2saz = 1.0-sinaz*sinaz; + double us = cos2saz*e2/(1.0-e2); + + // Terms + double ta = 1.0+us*(4096.0+us*(-768.0+us*(320.0-175.0*us)))/16384.0, + tb = us*(256.0+us*(-128.0+us*(74.0-47.0*us)))/1024.0, + tc = 0; + + // FIRST ESTIMATE OF SIGMA (SIG) + double first = s/(b*ta); // !! + double sig = first; + double c2sigm, sinsig,cossig, temp,denom,rnumer, dlams, dlam; + do { + c2sigm = cos(2.0*sig1+sig); + sinsig = sin(sig); cossig = cos(sig); + temp = sig; + sig = first + + tb*sinsig*(c2sigm+tb*(cossig*(-1.0+2.0*c2sigm*c2sigm) - + tb*c2sigm*(-3.0+4.0*sinsig*sinsig) + *(-3.0+4.0*c2sigm*c2sigm)/6.0) + /4.0); + } while( fabs(sig-temp) > testv); + + // LATITUDE OF POINT 2 + // DENOMINATOR IN 2 PARTS (TEMP ALSO USED LATER) + temp = sinu1*sinsig-cosu1*cossig*cosaz1; + denom = (1.0-f)*sqrt(sinaz*sinaz+temp*temp); + + // NUMERATOR + rnumer = sinu1*cossig+cosu1*sinsig*cosaz1; + *lat2 = SGMiscd::rad2deg(atan2(rnumer,denom)); + + // DIFFERENCE IN LONGITUDE ON AUXILARY SPHERE (DLAMS ) + rnumer = sinsig*sinaz1; + denom = cosu1*cossig-sinu1*sinsig*cosaz1; + dlams = atan2(rnumer,denom); + + // TERM C + tc = f*cos2saz*(4.0+f*(4.0-3.0*cos2saz))/16.0; + + // DIFFERENCE IN LONGITUDE + dlam = dlams-(1.0-tc)*f*sinaz*(sig+tc*sinsig* + (c2sigm+ + tc*cossig*(-1.0+2.0* + c2sigm*c2sigm))); + *lon2 = SGMiscd::rad2deg(lam1+dlam); + if (*lon2 > 180.0 ) *lon2 -= 360.0; + if (*lon2 < -180.0 ) *lon2 += 360.0; + + // AZIMUTH - FROM NORTH + *az2 = SGMiscd::rad2deg(atan2(-sinaz,temp)); + if ( fabs(*az2) < testv ) *az2 = 0.0; + if( *az2 < 0.0) *az2 += 360.0; + return 0; + } else { // phi1 == 90 degrees, polar origin + double dM = a*M0(e2) - s; + double paz = ( phi1 < 0.0 ? 180.0 : 0.0 ); + double zero = 0.0f; + return _geo_direct_wgs_84( zero, lon1, paz, dM, lat2, lon2, az2 ); + } +} + +bool +SGGeodesy::direct(const SGGeod& p1, double course1, + double distance, SGGeod& p2, double& course2) +{ + double lat2, lon2; + int ret = _geo_direct_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(), + course1, distance, &lat2, &lon2, &course2); + p2.setLatitudeDeg(lat2); + p2.setLongitudeDeg(lon2); + p2.setElevationM(0); + return ret == 0; +} + +// given lat1, lon1, lat2, lon2, calculate starting and ending +// az1, az2 and distance (s). Lat, lon, and azimuth are in degrees. +// distance in meters +static int _geo_inverse_wgs_84( double lat1, double lon1, double lat2, + double lon2, double *az1, double *az2, + double *s ) +{ + double a = SGGeodesy::EQURAD, rf = SGGeodesy::iFLATTENING; + int iter=0; + double testv = 1.0E-10; + double f = ( rf > 0.0 ? 1.0/rf : 0.0 ); + double b = a*(1.0-f); + // double e2 = f*(2.0-f); // unused in this routine + double phi1 = SGMiscd::deg2rad(lat1), lam1 = SGMiscd::deg2rad(lon1); + double sinphi1 = sin(phi1), cosphi1 = cos(phi1); + double phi2 = SGMiscd::deg2rad(lat2), lam2 = SGMiscd::deg2rad(lon2); + double sinphi2 = sin(phi2), cosphi2 = cos(phi2); + + if( (fabs(lat1-lat2) < testv && + ( fabs(lon1-lon2) < testv)) || (fabs(lat1-90.0) < testv ) ) + { + // TWO STATIONS ARE IDENTICAL : SET DISTANCE & AZIMUTHS TO ZERO */ + *az1 = 0.0; *az2 = 0.0; *s = 0.0; + return 0; + } else if( fabs(cosphi1) < testv ) { + // initial point is polar + int k = _geo_inverse_wgs_84( lat2,lon2,lat1,lon1, az1,az2,s ); + k = k; // avoid compiler error since return result is unused + b = *az1; *az1 = *az2; *az2 = b; + return 0; + } else if( fabs(cosphi2) < testv ) { + // terminal point is polar + double _lon1 = lon1 + 180.0f; + int k = _geo_inverse_wgs_84( lat1, lon1, lat1, _lon1, + az1, az2, s ); + k = k; // avoid compiler error since return result is unused + *s /= 2.0; + *az2 = *az1 + 180.0; + if( *az2 > 360.0 ) *az2 -= 360.0; + return 0; + } else if( (fabs( fabs(lon1-lon2) - 180 ) < testv) && + (fabs(lat1+lat2) < testv) ) + { + // Geodesic passes through the pole (antipodal) + double s1,s2; + _geo_inverse_wgs_84( lat1,lon1, lat1,lon2, az1,az2, &s1 ); + _geo_inverse_wgs_84( lat2,lon2, lat1,lon2, az1,az2, &s2 ); + *az2 = *az1; + *s = s1 + s2; + return 0; + } else { + // antipodal and polar points don't get here + double dlam = lam2 - lam1, dlams = dlam; + double sdlams,cdlams, sig,sinsig,cossig, sinaz, + cos2saz, c2sigm; + double tc,temp, us,rnumer,denom, ta,tb; + double cosu1,sinu1, sinu2,cosu2; + + // Reduced latitudes + temp = (1.0-f)*sinphi1/cosphi1; + cosu1 = 1.0/sqrt(1.0+temp*temp); + sinu1 = temp*cosu1; + temp = (1.0-f)*sinphi2/cosphi2; + cosu2 = 1.0/sqrt(1.0+temp*temp); + sinu2 = temp*cosu2; + + do { + sdlams = sin(dlams), cdlams = cos(dlams); + sinsig = sqrt(cosu2*cosu2*sdlams*sdlams+ + (cosu1*sinu2-sinu1*cosu2*cdlams)* + (cosu1*sinu2-sinu1*cosu2*cdlams)); + cossig = sinu1*sinu2+cosu1*cosu2*cdlams; + + sig = atan2(sinsig,cossig); + sinaz = cosu1*cosu2*sdlams/sinsig; + cos2saz = 1.0-sinaz*sinaz; + c2sigm = (sinu1 == 0.0 || sinu2 == 0.0 ? cossig : + cossig-2.0*sinu1*sinu2/cos2saz); + tc = f*cos2saz*(4.0+f*(4.0-3.0*cos2saz))/16.0; + temp = dlams; + dlams = dlam+(1.0-tc)*f*sinaz* + (sig+tc*sinsig* + (c2sigm+tc*cossig*(-1.0+2.0*c2sigm*c2sigm))); + if (fabs(dlams) > SGMiscd::pi() && iter++ > 50) { + return iter; + } + } while ( fabs(temp-dlams) > testv); + + us = cos2saz*(a*a-b*b)/(b*b); // !! + // BACK AZIMUTH FROM NORTH + rnumer = -(cosu1*sdlams); + denom = sinu1*cosu2-cosu1*sinu2*cdlams; + *az2 = SGMiscd::rad2deg(atan2(rnumer,denom)); + if( fabs(*az2) < testv ) *az2 = 0.0; + if(*az2 < 0.0) *az2 += 360.0; + + // FORWARD AZIMUTH FROM NORTH + rnumer = cosu2*sdlams; + denom = cosu1*sinu2-sinu1*cosu2*cdlams; + *az1 = SGMiscd::rad2deg(atan2(rnumer,denom)); + if( fabs(*az1) < testv ) *az1 = 0.0; + if(*az1 < 0.0) *az1 += 360.0; + + // Terms a & b + ta = 1.0+us*(4096.0+us*(-768.0+us*(320.0-175.0*us)))/ + 16384.0; + tb = us*(256.0+us*(-128.0+us*(74.0-47.0*us)))/1024.0; + + // GEODETIC DISTANCE + *s = b*ta*(sig-tb*sinsig* + (c2sigm+tb*(cossig*(-1.0+2.0*c2sigm*c2sigm)-tb* + c2sigm*(-3.0+4.0*sinsig*sinsig)* + (-3.0+4.0*c2sigm*c2sigm)/6.0)/ + 4.0)); + return 0; + } +} + +bool +SGGeodesy::inverse(const SGGeod& p1, const SGGeod& p2, double& course1, + double& course2, double& distance) +{ + int ret = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(), + p2.getLatitudeDeg(), p2.getLongitudeDeg(), + &course1, &course2, &distance); + return ret == 0; +} + +double +SGGeodesy::courseDeg(const SGGeod& p1, const SGGeod& p2) +{ + double course1, course2, distance; + int r = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(), + p2.getLatitudeDeg(), p2.getLongitudeDeg(), + &course1, &course2, &distance); + if (r != 0) { + throw sg_exception("SGGeodesy::courseDeg, unable to compute course"); + } + + return course1; +} + +double +SGGeodesy::distanceM(const SGGeod& p1, const SGGeod& p2) +{ + double course1, course2, distance; + int r = _geo_inverse_wgs_84(p1.getLatitudeDeg(), p1.getLongitudeDeg(), + p2.getLatitudeDeg(), p2.getLongitudeDeg(), + &course1, &course2, &distance); + if (r != 0) { + throw sg_exception("SGGeodesy::distanceM, unable to compute distance"); + } + + return distance; +} + +double +SGGeodesy::distanceNm(const SGGeod& from, const SGGeod& to) +{ + return distanceM(from, to) * SG_METER_TO_NM; +} + +/// Geocentric routines + +void +SGGeodesy::advanceRadM(const SGGeoc& geoc, double course, double distance, + SGGeoc& result) +{ + result.setRadiusM(geoc.getRadiusM()); + + // lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc)) + // IF (cos(lat)=0) + // lon=lon1 // endpoint a pole + // ELSE + // lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi + // ENDIF + + distance *= SG_METER_TO_NM * SG_NM_TO_RAD; + + double sinDistance = sin(distance); + double cosDistance = cos(distance); + + double sinLat = sin(geoc.getLatitudeRad()) * cosDistance + + cos(geoc.getLatitudeRad()) * sinDistance * cos(course); + sinLat = SGMiscd::clip(sinLat, -1, 1); + result.setLatitudeRad(asin(sinLat)); + double cosLat = cos(result.getLatitudeRad()); + + + if (cosLat <= SGLimitsd::min()) { + // endpoint a pole + result.setLongitudeRad(geoc.getLongitudeRad()); + } else { + double tmp = SGMiscd::clip(sin(course) * sinDistance / cosLat, -1, 1); + double lon = SGMiscd::normalizeAngle(geoc.getLongitudeRad() - asin( tmp )); + result.setLongitudeRad(lon); + } +} + +double +SGGeodesy::courseRad(const SGGeoc& from, const SGGeoc& to) +{ + double diffLon = to.getLongitudeRad() - from.getLongitudeRad(); + + double sinLatFrom = sin(from.getLatitudeRad()); + double cosLatFrom = cos(from.getLatitudeRad()); + + double sinLatTo = sin(to.getLatitudeRad()); + double cosLatTo = cos(to.getLatitudeRad()); + + double x = cosLatTo*sin(diffLon); + double y = cosLatFrom*sinLatTo - sinLatFrom*cosLatTo*cos(diffLon); + + // guard atan2 returning NaN's + if (fabs(x) <= SGLimitsd::min() && fabs(y) <= SGLimitsd::min()) + return 0; + + double c = atan2(x, y); + if (c >= 0) + return SGMiscd::twopi() - c; + else + return -c; +} + +double +SGGeodesy::distanceRad(const SGGeoc& from, const SGGeoc& to) +{ + // d = 2*asin(sqrt((sin((lat1-lat2)/2))^2 + + // cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2))^2)) + double cosLatFrom = cos(from.getLatitudeRad()); + double cosLatTo = cos(to.getLatitudeRad()); + double tmp1 = sin(0.5*(from.getLatitudeRad() - to.getLatitudeRad())); + double tmp2 = sin(0.5*(from.getLongitudeRad() - to.getLongitudeRad())); + double square = tmp1*tmp1 + cosLatFrom*cosLatTo*tmp2*tmp2; + double s = SGMiscd::min(sqrt(SGMiscd::max(square, 0)), 1); + return 2 * asin(s); +} + + +double +SGGeodesy::distanceM(const SGGeoc& from, const SGGeoc& to) +{ + return distanceRad(from, to) * SG_RAD_TO_NM * SG_NM_TO_METER; +}