File indexing completed on 2025-01-05 03:56:26

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-05-05
0007  * Description : Geodetic tools based from an implementation written by
0008  *               Daniele Franzoni and Martin Desruisseaux from
0009  *               GeoTools Project Managment Committee (PMC), https://geotools.org
0010  *
0011  * SPDX-FileCopyrightText: 2008-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  * SPDX-FileCopyrightText: 2015-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "geodetictools.h"
0019 
0020 // C++ includes
0021 
0022 #include <cstdlib>
0023 #include <cfloat>
0024 
0025 namespace Digikam
0026 {
0027 
0028 using namespace Coordinates;
0029 
0030 GeodeticCalculator::GeodeticCalculator(const Ellipsoid& e)
0031     : m_ellipsoid       (e),
0032       m_lat1            (0),
0033       m_long1           (0),
0034       m_lat2            (0),
0035       m_long2           (0),
0036       m_distance        (0),
0037       m_azimuth         (0),
0038       m_destinationValid(false),
0039       m_directionValid  (false)
0040 {
0041     m_semiMajorAxis       = m_ellipsoid.semiMajorAxis();
0042     m_semiMinorAxis       = m_ellipsoid.semiMinorAxis();
0043 
0044     // constants
0045 
0046     TOLERANCE_0           = 5.0e-15,
0047     TOLERANCE_1           = 5.0e-14,
0048     TOLERANCE_2           = 5.0e-13,
0049     TOLERANCE_3           = 7.0e-3;
0050     TOLERANCE_CHECK       = 1E-8;
0051 
0052     // calculation of GPNHRI parameters
0053 
0054     f                     = (m_semiMajorAxis-m_semiMinorAxis) / m_semiMajorAxis;
0055     fo                    = 1.0 - f;
0056     f2                    = f*f;
0057     f3                    = f*f2;
0058     f4                    = f*f3;
0059     m_eccentricitySquared = f * (2.0-f);
0060 
0061     // Calculation of GNPARC parameters
0062 
0063     const double E2       = m_eccentricitySquared;
0064     const double E4       = E2*E2;
0065     const double E6       = E4*E2;
0066     const double E8       = E6*E2;
0067     const double EX       = E8*E2;
0068 
0069     A =  1.0+0.75*E2+0.703125*E4+0.68359375 *E6+0.67291259765625*E8+0.6661834716796875 *EX;
0070     B =      0.75*E2+0.9375  *E4+1.025390625*E6+1.07666015625   *E8+1.1103057861328125 *EX;
0071     C =              0.234375*E4+0.41015625 *E6+0.538330078125  *E8+0.63446044921875   *EX;
0072     D =                          0.068359375*E6+0.15380859375   *E8+0.23792266845703125*EX;
0073     E =                                         0.01922607421875*E8+0.0528717041015625 *EX;
0074     F =                                                             0.00528717041015625*EX;
0075 
0076     m_maxOrthodromicDistance = m_semiMajorAxis * (1.0-E2) * M_PI * A - 1.0;
0077 
0078     T1 = 1.0;
0079     T2 = -0.25*f*(1.0 + f + f2);
0080     T4 = 0.1875 * f2 * (1.0+2.25*f);
0081     T6 = 0.1953125 * f3;
0082 
0083     const double a = f3*(1.0+2.25*f);
0084     a01            = -f2*(1.0+f+f2)/4.0;
0085     a02            = 0.1875*a;
0086     a03            = -0.1953125*f4;
0087     a21            = -a01;
0088     a22            = -0.25*a;
0089     a23            = 0.29296875*f4;
0090     a42            = 0.03125*a;
0091     a43            = 0.05859375*f4;
0092     a63            = 5.0*f4/768.0;
0093 }
0094 
0095 double GeodeticCalculator::castToAngleRange(const double alpha)
0096 {
0097     return (alpha - (2*M_PI) * floor(alpha/(2*M_PI) + 0.5));
0098 }
0099 
0100 bool GeodeticCalculator::checkLatitude(double* latitude)
0101 {
0102     if ((*latitude >= -90.0) && (*latitude <= 90.0))
0103     {
0104         *latitude = toRadians(*latitude);
0105 
0106         return true;
0107     }
0108 
0109     return false;
0110 }
0111 
0112 bool GeodeticCalculator::checkLongitude(double* longitude)
0113 {
0114     if ((*longitude >= -180.0) && (*longitude <= 180.0))
0115     {
0116         *longitude = toRadians(*longitude);
0117 
0118         return true;
0119     }
0120 
0121     return false;
0122 }
0123 
0124 bool GeodeticCalculator::checkAzimuth(double* azimuth)
0125 {
0126     if ((*azimuth >= -180.0) && (*azimuth <= 180.0))
0127     {
0128         *azimuth = toRadians(*azimuth);
0129 
0130         return true;
0131     }
0132 
0133     return false;
0134 }
0135 
0136 bool GeodeticCalculator::checkOrthodromicDistance(const double distance)
0137 {
0138     return ((distance >= 0.0) && (distance <= m_maxOrthodromicDistance));
0139 }
0140 
0141 Ellipsoid GeodeticCalculator::ellipsoid() const
0142 {
0143     return m_ellipsoid;
0144 }
0145 
0146 void GeodeticCalculator::setStartingGeographicPoint(double longitude, double latitude)
0147 {
0148     if (!checkLongitude(&longitude) || !checkLatitude(&latitude))
0149     {
0150         return;
0151     }
0152 
0153     // Check passed. Now performs the changes in this object.
0154 
0155     m_long1            = longitude;
0156     m_lat1             = latitude;
0157     m_destinationValid = false;
0158     m_directionValid   = false;
0159 }
0160 
0161 void GeodeticCalculator::setDestinationGeographicPoint(double longitude, double latitude)
0162 {
0163     if (!checkLongitude(&longitude) || !checkLatitude(&latitude))
0164     {
0165         return;
0166     }
0167 
0168     // Check passed. Now performs the changes in this object.
0169 
0170     m_long2            = longitude;
0171     m_lat2             = latitude;
0172     m_destinationValid = true;
0173     m_directionValid   = false;
0174 }
0175 
0176 bool GeodeticCalculator::destinationGeographicPoint(double* longitude, double* latitude)
0177 {
0178     if (!m_destinationValid)
0179     {
0180         if (!computeDestinationPoint())
0181         {
0182             return false;
0183         }
0184     }
0185 
0186     *longitude = toDegrees(m_long2);
0187     *latitude  = toDegrees(m_lat2);
0188 
0189     return true;
0190 }
0191 
0192 QPointF GeodeticCalculator::destinationGeographicPoint()
0193 {
0194     double x = 0.0;
0195     double y = 0.0;
0196     destinationGeographicPoint(&x, &y);
0197     QPointF point;
0198     point.setX(x);
0199     point.setY(y);
0200 
0201     return point;
0202 }
0203 
0204 void GeodeticCalculator::setDirection(double azimuth, double distance)
0205 {
0206     // Check first in case an exception is raised
0207     // (in other words, we change all or nothing).
0208 
0209     if (!checkAzimuth(&azimuth))
0210     {
0211         return;
0212     }
0213 
0214     if (!checkOrthodromicDistance(distance))
0215     {
0216         return;
0217     }
0218 
0219     // Check passed. Now performs the changes in this object.
0220 
0221     m_azimuth          = azimuth;
0222     m_distance         = distance;
0223     m_destinationValid = false;
0224     m_directionValid   = true;
0225 }
0226 
0227 double GeodeticCalculator::azimuth()
0228 {
0229     if (!m_directionValid)
0230     {
0231         computeDirection();
0232     }
0233 
0234     return toDegrees(m_azimuth);
0235 }
0236 
0237 double GeodeticCalculator::orthodromicDistance()
0238 {
0239     if (!m_directionValid)
0240     {
0241         computeDirection();
0242         checkOrthodromicDistance();
0243     }
0244 
0245     return m_distance;
0246 }
0247 
0248 bool GeodeticCalculator::checkOrthodromicDistance()
0249 {
0250     double check = m_ellipsoid.orthodromicDistance(toDegrees(m_long1), toDegrees(m_lat1),
0251                                                    toDegrees(m_long2), toDegrees(m_lat2));
0252     check = fabs(m_distance - check);
0253 
0254     return (check <= (m_distance+1) * TOLERANCE_CHECK);
0255 }
0256 
0257 bool GeodeticCalculator::computeDestinationPoint()
0258 {
0259     if (!m_directionValid)
0260     {
0261         return false;
0262     }
0263 
0264     // Protect internal variables from changes
0265 
0266     const double lat1     = m_lat1;
0267     const double long1    = m_long1;
0268     const double azimuth  = m_azimuth;
0269     const double distance = m_distance;
0270 
0271     /*
0272      * Solution of the geodetic direct problem after T.Vincenty.
0273      * Modified Rainsford's method with Helmert's elliptical terms.
0274      * Effective in any azimuth and at any distance short of antipodal.
0275      *
0276      * Latitudes and longitudes in radians positive North and East.
0277      * Forward azimuths at both points returned in radians from North.
0278      *
0279      * Programmed for CDC-6600 by LCDR L.Pfeifer NGS ROCKVILLE MD 18FEB75
0280      * Modified for IBM SYSTEM 360 by John G.Gergen NGS ROCKVILLE MD 7507
0281      * Ported from Fortran to Java by Daniele Franzoni.
0282      *
0283      * Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/forward.for
0284      *         subroutine DIRECT1
0285      */
0286     double TU  = fo*sin(lat1) / cos(lat1);
0287     double SF  = sin(azimuth);
0288     double CF  = cos(azimuth);
0289     double BAZ = (CF!=0) ? atan2(TU,CF)*2.0 : 0;
0290     double CU  = 1/sqrt(TU*TU + 1.0);
0291     double SU  = TU*CU;
0292     double SA  = CU*SF;
0293     double C2A = 1.0 - SA*SA;
0294     double X   = sqrt((1.0/fo/fo-1)*C2A+1.0) + 1.0;
0295     X          = (X-2.0)/X;
0296     double C   = 1.0-X;
0297     C          = (X*X/4.0+1.0)/C;
0298     double D   = (0.375*X*X-1.0)*X;
0299     TU         = distance / fo / m_semiMajorAxis / C;
0300     double Y   = TU;
0301     double SY, CY, CZ, E;
0302 
0303     do
0304     {
0305         SY = sin(Y);
0306         CY = cos(Y);
0307         CZ = cos(BAZ+Y);
0308         E  = CZ*CZ*2.0-1.0;
0309         C  = Y;
0310         X  = E*CY;
0311         Y  = E+E-1.0;
0312         Y  = (((SY*SY*4.0-3.0)*Y*CZ*D/6.0+X)*D/4.0-CZ)*SY*D+TU;
0313     }
0314     while (fabs(Y-C) > TOLERANCE_1);
0315 
0316     BAZ                = CU*CY*CF - SU*SY;
0317     C                  = fo*sqrt(SA*SA+BAZ*BAZ);
0318     D                  = SU*CY + CU*SY*CF;
0319     m_lat2             = atan2(D,C);
0320     C                  = CU*CY-SU*SY*CF;
0321     X                  = atan2(SY*SF,C);
0322     C                  = ((-3.0*C2A+4.0)*f+4.0)*C2A*f/16.0;
0323     D                  = ((E*CY*C+CZ)*SY*C+Y)*SA;
0324     m_long2            = long1+X - (1.0-C)*D*f;
0325     m_long2            = castToAngleRange(m_long2);
0326     m_destinationValid = true;
0327 
0328     return true;
0329 }
0330 
0331 double GeodeticCalculator::meridianArcLength(double latitude1, double latitude2)
0332 {
0333     if (!checkLatitude(&latitude1) || !checkLatitude(&latitude2))
0334     {
0335         return 0.0;
0336     }
0337 
0338     return meridianArcLengthRadians(latitude1, latitude2);
0339 }
0340 
0341 double GeodeticCalculator::meridianArcLengthRadians(double P1, double P2)
0342 {
0343     /*
0344      * Latitudes P1 and P2 in radians positive North and East.
0345      * Forward azimuths at both points returned in radians from North.
0346      *
0347      * Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/inverse.for
0348      *         subroutine GPNARC
0349      *         version    200005.26
0350      *         written by Robert (Sid) Safford
0351      *
0352      * Ported from Fortran to Java by Daniele Franzoni.
0353      */
0354     double S1 = fabs(P1);
0355     double S2 = fabs(P2);
0356     double DA = (P2-P1);
0357 
0358     // Check for a 90 degree lookup
0359 
0360     if ((S1 > TOLERANCE_0) || (S2 <= (M_PI/2-TOLERANCE_0)) || (S2 >= (M_PI/2+TOLERANCE_0)))
0361     {
0362         const double DB = sin(P2* 2.0) - sin(P1* 2.0);
0363         const double DC = sin(P2* 4.0) - sin(P1* 4.0);
0364         const double DD = sin(P2* 6.0) - sin(P1* 6.0);
0365         const double DE = sin(P2* 8.0) - sin(P1* 8.0);
0366         const double DF = sin(P2*10.0) - sin(P1*10.0);
0367 
0368         // Compute the S2 part of the series expansion
0369 
0370         S2              = -DB*B/2.0 + DC*C/4.0 - DD*D/6.0 + DE*E/8.0 - DF*F/10.0;
0371     }
0372 
0373     // Compute the S1 part of the series expansion
0374 
0375     S1        = DA*A;
0376 
0377     // Compute the arc length
0378 
0379     return fabs(m_semiMajorAxis * (1.0-m_eccentricitySquared) * (S1+S2));
0380 }
0381 
0382 /**
0383  * Computes the azimuth and orthodromic distance from the
0384  * startingGeographicPoint() and the
0385  * destinationGeographicPoint().
0386  */
0387 bool GeodeticCalculator::computeDirection()
0388 {
0389     if (!m_destinationValid)
0390     {
0391         return false;
0392     }
0393 
0394     // Protect internal variables from change.
0395 
0396     const double long1 = m_long1;
0397     const double lat1  = m_lat1;
0398     const double long2 = m_long2;
0399     const double lat2  = m_lat2;
0400 
0401     /*
0402      * Solution of the geodetic inverse problem after T.Vincenty.
0403      * Modified Rainsford's method with Helmert's elliptical terms.
0404      * Effective in any azimuth and at any distance short of antipodal.
0405      *
0406      * Latitudes and longitudes in radians positive North and East.
0407      * Forward azimuths at both points returned in radians from North.
0408      *
0409      * Programmed for CDC-6600 by LCDR L.Pfeifer NGS ROCKVILLE MD 18FEB75
0410      * Modified for IBM SYSTEM 360 by John G.Gergen NGS ROCKVILLE MD 7507
0411      * Ported from Fortran to Java by Daniele Franzoni.
0412      *
0413      * Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/inverse.for
0414      *         subroutine GPNHRI
0415      *         version    200208.09
0416      *         written by robert (sid) safford
0417      */
0418     const double dlon = castToAngleRange(long2-long1);
0419     const double ss   = fabs(dlon);
0420 
0421     if (ss < TOLERANCE_1)
0422     {
0423         m_distance       = meridianArcLengthRadians(lat1, lat2);
0424         m_azimuth        = (lat2>lat1) ? 0.0 : M_PI;
0425         m_directionValid = true;
0426 
0427         return true;
0428     }
0429 
0430     /*
0431      * Computes the limit in longitude (alimit), it is equal
0432      * to twice  the distance from the equator to the pole,
0433      * as measured along the equator
0434      */
0435 
0436     // tests for antinodal difference
0437 
0438     const double ESQP   = m_eccentricitySquared / (1.0-m_eccentricitySquared);
0439     const double alimit = M_PI*fo;
0440 
0441     if ((ss >= alimit)        &&
0442         (lat1 < TOLERANCE_3)  &&
0443         (lat1 > -TOLERANCE_3) &&
0444         (lat2 < TOLERANCE_3)  &&
0445         (lat2 > -TOLERANCE_3))
0446     {
0447         // Computes an approximate AZ
0448 
0449         const double CONS = (M_PI-ss)/(M_PI*f);
0450         double AZ         = asin(CONS);
0451         int iter          = 0;
0452         double AZ_TEMP, S, AO;
0453 
0454         do
0455         {
0456             if (++iter > 8)
0457             {
0458                 // ERROR
0459 
0460                 return false;
0461             }
0462 
0463             S                 = cos(AZ);
0464             const double C2   = S*S;
0465 
0466             // Compute new AO
0467 
0468             AO                = T1 + T2*C2 + T4*C2*C2 + T6*C2*C2*C2;
0469             const double _CS_ = CONS/AO;
0470             S                 = asin(_CS_);
0471             AZ_TEMP           = AZ;
0472             AZ                = S;
0473         }
0474         while (fabs(S-AZ_TEMP) >= TOLERANCE_2);
0475 
0476         const double AZ1 = (dlon < 0.0) ? 2.0*M_PI - S : S;
0477         m_azimuth        = castToAngleRange(AZ1);
0478 /*
0479         const double AZ2 = 2.0*M_PI - AZ1;
0480 */
0481         S                = cos(AZ1);
0482 
0483         // Equatorial - geodesic(S-s) SMS
0484 
0485         const double U2  = ESQP*S*S;
0486         const double U4  = U2*U2;
0487         const double U6  = U4*U2;
0488         const double U8  = U6*U2;
0489         const double BO  =  1.0                  +
0490                             0.25             *U2 +
0491                             0.046875         *U4 +
0492                             0.01953125       *U6 +
0493                             -0.01068115234375*U8;
0494         S                = sin(AZ1);
0495         const double SMS = m_semiMajorAxis*M_PI*(1.0 - f*fabs(S)*AO - BO*fo);
0496         m_distance       = m_semiMajorAxis*ss - SMS;
0497         m_directionValid = true;
0498 
0499         return true;
0500     }
0501 
0502     // the reduced latitudes
0503 
0504     const double  u1 = atan(fo*sin(lat1)/cos(lat1));
0505     const double  u2 = atan(fo*sin(lat2)/cos(lat2));
0506     const double su1 = sin(u1);
0507     const double cu1 = cos(u1);
0508     const double su2 = sin(u2);
0509     const double cu2 = cos(u2);
0510     double xy, w, q2, q4, q6, r2, r3, sig, ssig, slon, clon, sinalf, ab=dlon;
0511     int kcount       = 0;
0512 
0513     do
0514     {
0515         if (++kcount > 8)
0516         {
0517             // ERROR
0518 
0519             return false;
0520         }
0521 
0522         clon              = cos(ab);
0523         slon              = sin(ab);
0524         const double csig = su1*su2 + cu1*cu2*clon;
0525         ssig              = sqrt(slon*cu2*slon*cu2 + (su2*cu1-su1*cu2*clon)*(su2*cu1-su1*cu2*clon));
0526         sig               = atan2(ssig, csig);
0527         sinalf            = cu1*cu2*slon/ssig;
0528         w                 = (1.0 - sinalf*sinalf);
0529         const double t4   = w*w;
0530         const double t6   = w*t4;
0531 
0532         // the coefficients of type a
0533 
0534         const double ao   = f+a01*w+a02*t4+a03*t6;
0535         const double a2   =   a21*w+a22*t4+a23*t6;
0536         const double a4   =         a42*t4+a43*t6;
0537         const double a6   =                a63*t6;
0538 
0539         // the multiple angle functions
0540 
0541         double qo         = 0.0;
0542 
0543         if (w > TOLERANCE_0)
0544         {
0545             qo = -2.0*su1*su2/w;
0546         }
0547 
0548         q2 = csig + qo;
0549         q4 = 2.0*q2*q2 - 1.0;
0550         q6 = q2*(4.0*q2*q2 - 3.0);
0551         r2 = 2.0*ssig*csig;
0552         r3 = ssig*(3.0 - 4.0*ssig*ssig);
0553 
0554         // the longitude difference
0555 
0556         const double s = sinalf*(ao*sig + a2*ssig*q2 + a4*r2*q4 + a6*r3*q6);
0557         double xz      = dlon+s;
0558         xy             = fabs(xz-ab);
0559         ab             = dlon+s;
0560     }
0561     while (xy >= TOLERANCE_1);
0562 
0563     const double z  = ESQP*w;
0564     const double bo = 1.0 + z*( 1.0/4.0 + z*(-3.0/  64.0 + z*(  5.0/256.0 - z*(175.0/16384.0))));
0565     const double b2 =       z*(-1.0/4.0 + z*( 1.0/  16.0 + z*(-15.0/512.0 + z*( 35.0/ 2048.0))));
0566     const double b4 =                   z*z*(-1.0/ 128.0 + z*(  3.0/512.0 - z*( 35.0/ 8192.0)));
0567     const double b6 =                                  z*z*z*(-1.0/1536.0 + z*(  5.0/ 6144.0));
0568 
0569     // The distance in ellispoid axis units.
0570 
0571     m_distance = m_semiMinorAxis * (bo*sig + b2*ssig*q2 + b4*r2*q4 + b6*r3*q6);
0572     double az1 = (dlon < 0.0) ? M_PI*(3.0/2.0) : M_PI/2.0;
0573 
0574     // now compute the az1 & az2 for latitudes not on the equator
0575 
0576     if ((fabs(su1) >= TOLERANCE_0) || (fabs(su2) >= TOLERANCE_0))
0577     {
0578         const double tana1 = slon*cu2 / (su2*cu1 - clon*su1*cu2);
0579         const double sina1 = sinalf/cu1;
0580 
0581         // azimuths from north,longitudes positive east
0582 
0583         az1                = atan2(sina1, sina1/tana1);
0584     }
0585 
0586     m_azimuth        = castToAngleRange(az1);
0587     m_directionValid = true;
0588 
0589     return true;
0590 }
0591 
0592 /*
0593 / **
0594  * Calculates the geodetic curve between two points in the referenced ellipsoid.
0595  * A curve in the ellipsoid is a path which points contain the longitude and latitude
0596  * of the points in the geodetic curve. The geodetic curve is computed from the
0597  * {@linkplain #getStartingGeographicPoint starting point} to the
0598  * {@linkplain #getDestinationGeographicPoint destination point}.
0599  *
0600  * @param  numberOfPoints The number of vertex in the geodetic curve.
0601  *         NOTE: This argument is only a hint and may be ignored
0602  *         in future version (if we compute a real curve rather than a list of line
0603  *         segments).
0604  * @return The path that represents the geodetic curve from the
0605  *         {@linkplain #getStartingGeographicPoint starting point} to the
0606  *         {@linkplain #getDestinationGeographicPoint destination point}.
0607  *
0608  * @todo We should check for cases where the path cross the 90N, 90S, 90E or 90W boundaries.
0609  * /
0610 public Shape getGeodeticCurve(const int numberOfPoints) {
0611     if (numberOfPoints < 0)
0612         return Shape;
0613     if (!directionValid) {
0614         computeDirection();
0615     }
0616     if (!destinationValid) {
0617         computeDestinationPoint();
0618     }
0619     const double         long2 = this->long2;
0620     const double          lat2 = this->lat2;
0621     const double      distance = this->distance;
0622     const double deltaDistance = distance / (numberOfPoints+1);
0623     final GeneralPath     path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, numberOfPoints+1);
0624     path.moveTo((float)toDegrees(long1),
0625                 (float)toDegrees(lat1));
0626     for (int i=1; i<numberOfPoints; ++i) {
0627         this->distance = i*deltaDistance;
0628         computeDestinationPoint();
0629         path.lineTo((float)toDegrees(this->long2),
0630                     (float)toDegrees(this->lat2));
0631     }
0632     this->long2    = long2;
0633     this->lat2     = lat2;
0634     this->distance = distance;
0635     path.lineTo((float)toDegrees(long2),
0636                 (float)toDegrees(lat2));
0637     return path;
0638 }
0639 
0640 / **
0641  * Calculates the geodetic curve between two points in the referenced ellipsoid.
0642  * A curve in the ellipsoid is a path which points contain the longitude and latitude
0643  * of the points in the geodetic curve. The geodetic curve is computed from the
0644  * {@linkplain #getStartingGeographicPoint starting point} to the
0645  * {@linkplain #getDestinationGeographicPoint destination point}.
0646  *
0647  * @return The path that represents the geodetic curve from the
0648  *         {@linkplain #getStartingGeographicPoint starting point} to the
0649  *         {@linkplain #getDestinationGeographicPoint destination point}.
0650  * /
0651 public Shape getGeodeticCurve() {
0652     return getGeodeticCurve(10);
0653 }
0654 */
0655 
0656 // ---------------------------------------------------------------------------------
0657 
0658 Ellipsoid Ellipsoid::WGS84()
0659 {
0660     return createFlattenedSphere(QLatin1String("WGS84"), 6378137.0, 298.257223563);
0661 }
0662 
0663 Ellipsoid Ellipsoid::GRS80()
0664 {
0665     return createFlattenedSphere(QLatin1String("GRS80"), 6378137.0, 298.257222101);
0666 }
0667 
0668 Ellipsoid Ellipsoid::INTERNATIONAL_1924()
0669 {
0670     return createFlattenedSphere(QLatin1String("International 1924"), 6378388.0, 297.0);
0671 }
0672 
0673 Ellipsoid Ellipsoid::CLARKE_1866()
0674 {
0675     return createFlattenedSphere(QLatin1String("Clarke 1866"), 6378206.4, 294.9786982);
0676 }
0677 
0678 Ellipsoid Ellipsoid::SPHERE()
0679 {
0680     return createEllipsoid(QLatin1String("SPHERE"), 6371000, 6371000);
0681 }
0682 
0683 Ellipsoid::Ellipsoid(const QString& name,
0684                      double semiMajorAxis,
0685                      double semiMinorAxis,
0686                      double inverseFlattening,
0687                      bool ivfDefinitive)
0688     : name               (name),
0689       m_semiMajorAxis    (semiMajorAxis),
0690       m_semiMinorAxis    (semiMinorAxis),
0691       m_inverseFlattening(inverseFlattening),
0692       m_ivfDefinitive    (ivfDefinitive),
0693       m_isSphere         (false)
0694 {
0695 }
0696 
0697 Ellipsoid::Ellipsoid(const QString& name,
0698                      double radius,
0699                      bool ivfDefinitive)
0700     : name               (name),
0701       m_semiMajorAxis    (radius),
0702       m_semiMinorAxis    (radius),
0703       m_inverseFlattening(DBL_MAX),
0704       m_ivfDefinitive    (ivfDefinitive),
0705       m_isSphere         (true)
0706 {
0707 }
0708 
0709 Ellipsoid Ellipsoid::createEllipsoid(const QString& name,
0710                                      double m_semiMajorAxis,
0711                                      double m_semiMinorAxis)
0712 {
0713     if (m_semiMajorAxis == m_semiMinorAxis)
0714     {
0715         return Ellipsoid(name, m_semiMajorAxis, false);
0716     }
0717     else
0718     {
0719         return Ellipsoid(name, m_semiMajorAxis, m_semiMinorAxis,
0720                          m_semiMajorAxis/(m_semiMajorAxis-m_semiMinorAxis), false);
0721     }
0722 }
0723 
0724 Ellipsoid Ellipsoid::createFlattenedSphere(const QString& name,
0725                                            double m_semiMajorAxis,
0726                                            double m_inverseFlattening)
0727 {
0728     if (m_inverseFlattening == DBL_MAX)
0729     {
0730         return Ellipsoid(name, m_semiMajorAxis, true);
0731     }
0732     else
0733     {
0734         return Ellipsoid(name, m_semiMajorAxis,
0735                          m_semiMajorAxis*(1-1/m_inverseFlattening),
0736                          m_inverseFlattening, true);
0737     }
0738 }
0739 
0740 double Ellipsoid::semiMajorAxis() const
0741 {
0742     return m_semiMajorAxis;
0743 }
0744 
0745 double Ellipsoid::semiMinorAxis() const
0746 {
0747     return m_semiMinorAxis;
0748 }
0749 
0750 double Ellipsoid::eccentricity() const
0751 {
0752     if (m_isSphere)
0753     {
0754         return 0.0;
0755     }
0756 
0757     const double f = 1-m_semiMinorAxis / m_semiMajorAxis;
0758 
0759     return sqrt(2*f - f*f);
0760 }
0761 
0762 double Ellipsoid::inverseFlattening() const
0763 {
0764     return m_inverseFlattening;
0765 }
0766 
0767 bool Ellipsoid::isIvfDefinitive() const
0768 {
0769     return m_ivfDefinitive;
0770 }
0771 
0772 bool Ellipsoid::isSphere() const
0773 {
0774     return (m_semiMajorAxis == m_semiMinorAxis);
0775 }
0776 
0777 double Ellipsoid::orthodromicDistance(double x1, double y1, double x2, double y2)
0778 {
0779     x1 = toRadians(x1);
0780     y1 = toRadians(y1);
0781     x2 = toRadians(x2);
0782     y2 = toRadians(y2);
0783 
0784     /*
0785      * Solution of the geodetic inverse problem after T.Vincenty.
0786      * Modified Rainsford's method with Helmert's elliptical terms.
0787      * Effective in any azimuth and at any distance short of antipodal.
0788      *
0789      * Latitudes and longitudes in radians positive North and East.
0790      * Forward azimuths at both points returned in radians from North.
0791      *
0792      * Programmed for CDC-6600 by LCDR L.Pfeifer NGS ROCKVILLE MD 18FEB75
0793      * Modified for IBM SYSTEM 360 by John G.Gergen NGS ROCKVILLE MD 7507
0794      * Ported from Fortran to Java by Martin Desruisseaux.
0795      *
0796      * Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/inverse.for
0797      *         subroutine INVER1
0798      */
0799     const int    MAX_ITERATIONS = 100;
0800     const double EPS            = 0.5E-13;
0801     const double F              = 1/m_inverseFlattening;
0802     const double R              = 1-F;
0803 
0804     double tu1 = R * sin(y1) / cos(y1);
0805     double tu2 = R * sin(y2) / cos(y2);
0806     double cu1 = 1 / sqrt(tu1*tu1 + 1);
0807     double cu2 = 1 / sqrt(tu2*tu2 + 1);
0808     double su1 = cu1*tu1;
0809     double s   = cu1*cu2;
0810     double baz = s*tu2;
0811     double faz = baz*tu1;
0812     double x   = x2-x1;
0813 
0814     for (int i = 0 ; i < MAX_ITERATIONS ; ++i)
0815     {
0816         const double sx  = sin(x);
0817         const double cx  = cos(x);
0818         tu1              = cu2*sx;
0819         tu2              = baz - su1*cu2*cx;
0820         const double sy  = sqrt(tu1*tu1 + tu2*tu2);
0821         const double cy  = s*cx + faz;
0822         const double y   = atan2(sy, cy);
0823         const double SA  = s*sx/sy;
0824         const double c2a = 1 - SA*SA;
0825         double cz        = faz+faz;
0826 
0827         if (c2a > 0)
0828         {
0829             cz = -cz/c2a + cy;
0830         }
0831 
0832         double e = cz*cz*2 - 1;
0833         double c = ((-3*c2a+4)*F+4)*c2a*F/16;
0834         double d = x;
0835         x        = ((e*cy*c+cz)*sy*c+y)*SA;
0836         x        = (1-c)*x*F + x2-x1;
0837 
0838         if (fabs(d-x) <= EPS)
0839         {
0840             if (false)
0841             {
0842                 // 'faz' and 'baz' are forward azimuths at both points.
0843                 // Since the current API can't returns this result, it
0844                 // doesn't worth to compute it at this time.
0845 
0846                 faz = atan2(tu1, tu2);
0847                 baz = atan2(cu1*sx, baz*cx - su1*cu2)+M_PI;
0848             }
0849 
0850             x = sqrt((1/(R*R)-1) * c2a + 1)+1;
0851             x = (x-2)/x;
0852             c = 1-x;
0853             c = (x*x/4 + 1)/c;
0854             d = (0.375*x*x - 1)*x;
0855             x = e*cy;
0856             s = 1-2*e;
0857             s = ((((sy*sy*4 - 3)*s*cz*d/6-x)*d/4+cz)*sy*d+y)*c*R*m_semiMajorAxis;
0858 
0859             return s;
0860         }
0861     }
0862 
0863     // No convergence. It may be because coordinate points
0864     // are equals or because they are at antipodes.
0865 
0866     const double LEPS = 1E-10;
0867 
0868     if ((fabs(x1-x2) <= LEPS) && (fabs(y1-y2) <= LEPS))
0869     {
0870         return 0.0; // Coordinate points are equals
0871     }
0872 
0873     if ((fabs(y1) <= LEPS) && (fabs(y2) <= LEPS))
0874     {
0875         return fabs(x1-x2) * m_semiMajorAxis; // Points are on the equator.
0876     }
0877 
0878     // Other cases: no solution for this algorithm.
0879 
0880     return 0.0;
0881 }
0882 
0883 double Ellipsoid::radiusOfCurvature(double latitude)
0884 {
0885     // WARNING: Code not from geotools
0886 
0887     double esquare = pow(eccentricity(), 2);
0888 
0889     return (m_semiMajorAxis * sqrt(1 - esquare) / (1 - esquare * pow( sin(toRadians(latitude)), 2)));
0890 }
0891 
0892 } // namespace Digikam