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