File indexing completed on 2025-01-05 03:58:54
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2004-2007 Torsten Rahn <tackat@kde.org> 0004 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org> 0005 // SPDX-FileCopyrightText: 2008 Patrick Spendrin <ps_ml@gmx.de> 0006 // SPDX-FileCopyrightText: 2011 Friedrich W. H. Kossebau <kossebau@kde.org> 0007 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de> 0008 // SPDX-FileCopyrightText: 2015 Alejandro Garcia Montoro <alejandro.garciamontoro@gmail.com> 0009 // 0010 0011 #include "GeoDataCoordinates.h" 0012 #include "GeoDataCoordinates_p.h" 0013 #include "LonLatParser_p.h" 0014 0015 #include <qmath.h> 0016 #include <QDataStream> 0017 #include <QPointF> 0018 0019 #include <klocalizedstring.h> 0020 0021 #include "MarbleMath.h" 0022 #include "Quaternion.h" 0023 0024 #include "digikam_debug.h" 0025 0026 namespace Marble 0027 { 0028 0029 const qreal GeoDataCoordinatesPrivate::sm_semiMajorAxis = 6378137.0; 0030 const qreal GeoDataCoordinatesPrivate::sm_semiMinorAxis = 6356752.314; 0031 const qreal GeoDataCoordinatesPrivate::sm_eccentricitySquared = 6.69437999013e-03; 0032 const qreal GeoDataCoordinatesPrivate::sm_utmScaleFactor = 0.9996; 0033 GeoDataCoordinates::Notation GeoDataCoordinates::s_notation = GeoDataCoordinates::DMS; 0034 0035 const GeoDataCoordinates GeoDataCoordinates::null = GeoDataCoordinates( 0, 0, 0 ); // don't use default constructor! 0036 0037 GeoDataCoordinates::GeoDataCoordinates( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit, int _detail ) 0038 : d( new GeoDataCoordinatesPrivate( _lon, _lat, _alt, unit, _detail ) ) 0039 { 0040 d->ref.ref(); 0041 } 0042 0043 /* simply copy the d pointer 0044 * it will be replaced in the detach function instead 0045 */ 0046 GeoDataCoordinates::GeoDataCoordinates( const GeoDataCoordinates& other ) 0047 : d( other.d ) 0048 { 0049 d->ref.ref(); 0050 } 0051 0052 /* simply copy null's d pointer 0053 * it will be replaced in the detach function 0054 */ 0055 GeoDataCoordinates::GeoDataCoordinates() 0056 : d( null.d ) 0057 { 0058 d->ref.ref(); 0059 } 0060 0061 /* 0062 * only delete the private d pointer if the number of references is 0 0063 * remember that all copies share the same d pointer! 0064 */ 0065 GeoDataCoordinates::~GeoDataCoordinates() 0066 { 0067 delete d->m_q; 0068 d->m_q = nullptr; 0069 0070 if (!d->ref.deref()) 0071 delete d; 0072 #ifdef DEBUG_GEODATA 0073 // qCDebug(DIGIKAM_MARBLE_LOG) << "delete coordinates"; 0074 #endif 0075 } 0076 0077 bool GeoDataCoordinates::isValid() const 0078 { 0079 return d != null.d; 0080 } 0081 0082 /* 0083 * if only one copy exists, return 0084 * else make a new private d pointer object and assign the values of the current 0085 * one to it 0086 * at the end, if the number of references thus reaches 0 delete it 0087 * this state shouldn't happen, but if it does, we have to clean up behind us. 0088 */ 0089 void GeoDataCoordinates::detach() 0090 { 0091 delete d->m_q; 0092 d->m_q = nullptr; 0093 0094 if(d->ref.fetchAndAddRelaxed(0) == 1) { 0095 return; 0096 } 0097 0098 GeoDataCoordinatesPrivate *new_d = new GeoDataCoordinatesPrivate( *d ); 0099 0100 if (!d->ref.deref()) { 0101 delete d; 0102 } 0103 0104 d = new_d; 0105 d->ref.ref(); 0106 } 0107 0108 /* 0109 * call detach() at the start of all non-static, non-const functions 0110 */ 0111 void GeoDataCoordinates::set( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit ) 0112 { 0113 detach(); 0114 d->m_altitude = _alt; 0115 switch( unit ){ 0116 default: 0117 case Radian: 0118 d->m_lon = _lon; 0119 d->m_lat = _lat; 0120 break; 0121 case Degree: 0122 d->m_lon = _lon * DEG2RAD; 0123 d->m_lat = _lat * DEG2RAD; 0124 break; 0125 } 0126 } 0127 0128 /* 0129 * call detach() at the start of all non-static, non-const functions 0130 */ 0131 void GeoDataCoordinates::setLongitude( qreal _lon, GeoDataCoordinates::Unit unit ) 0132 { 0133 detach(); 0134 switch( unit ){ 0135 default: 0136 case Radian: 0137 d->m_lon = _lon; 0138 break; 0139 case Degree: 0140 d->m_lon = _lon * DEG2RAD; 0141 break; 0142 } 0143 } 0144 0145 0146 /* 0147 * call detach() at the start of all non-static, non-const functions 0148 */ 0149 void GeoDataCoordinates::setLatitude( qreal _lat, GeoDataCoordinates::Unit unit ) 0150 { 0151 detach(); 0152 switch( unit ){ 0153 case Radian: 0154 d->m_lat = _lat; 0155 break; 0156 case Degree: 0157 d->m_lat = _lat * DEG2RAD; 0158 break; 0159 } 0160 } 0161 0162 void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, 0163 GeoDataCoordinates::Unit unit ) const 0164 { 0165 switch ( unit ) 0166 { 0167 default: 0168 case Radian: 0169 lon = d->m_lon; 0170 lat = d->m_lat; 0171 break; 0172 case Degree: 0173 lon = d->m_lon * RAD2DEG; 0174 lat = d->m_lat * RAD2DEG; 0175 break; 0176 } 0177 } 0178 0179 void GeoDataCoordinates::geoCoordinates(qreal &lon, qreal &lat) const 0180 { 0181 lon = d->m_lon; 0182 lat = d->m_lat; 0183 } 0184 0185 void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, qreal& alt, 0186 GeoDataCoordinates::Unit unit ) const 0187 { 0188 geoCoordinates( lon, lat, unit ); 0189 alt = d->m_altitude; 0190 } 0191 0192 void GeoDataCoordinates::geoCoordinates(qreal &lon, qreal &lat, qreal &alt) const 0193 { 0194 lon = d->m_lon; 0195 lat = d->m_lat; 0196 alt = d->m_altitude; 0197 } 0198 0199 qreal GeoDataCoordinates::longitude( GeoDataCoordinates::Unit unit ) const 0200 { 0201 switch ( unit ) 0202 { 0203 default: 0204 case Radian: 0205 return d->m_lon; 0206 case Degree: 0207 return d->m_lon * RAD2DEG; 0208 } 0209 } 0210 0211 qreal GeoDataCoordinates::longitude() const 0212 { 0213 return d->m_lon; 0214 } 0215 0216 qreal GeoDataCoordinates::latitude( GeoDataCoordinates::Unit unit ) const 0217 { 0218 switch ( unit ) 0219 { 0220 default: 0221 case Radian: 0222 return d->m_lat; 0223 case Degree: 0224 return d->m_lat * RAD2DEG; 0225 } 0226 } 0227 0228 qreal GeoDataCoordinates::latitude() const 0229 { 0230 return d->m_lat; 0231 } 0232 0233 //static 0234 GeoDataCoordinates::Notation GeoDataCoordinates::defaultNotation() 0235 { 0236 return s_notation; 0237 } 0238 0239 //static 0240 void GeoDataCoordinates::setDefaultNotation( GeoDataCoordinates::Notation notation ) 0241 { 0242 s_notation = notation; 0243 } 0244 0245 //static 0246 qreal GeoDataCoordinates::normalizeLon( qreal lon, GeoDataCoordinates::Unit unit ) 0247 { 0248 qreal halfCircle; 0249 if ( unit == GeoDataCoordinates::Radian ) { 0250 halfCircle = M_PI; 0251 } 0252 else { 0253 halfCircle = 180; 0254 } 0255 0256 if ( lon > halfCircle ) { 0257 int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) ); 0258 return lon - ( cycles * 2 * halfCircle ); 0259 } 0260 if ( lon < -halfCircle ) { 0261 int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) ); 0262 return lon - ( cycles * 2 * halfCircle ); 0263 } 0264 0265 return lon; 0266 } 0267 0268 //static 0269 qreal GeoDataCoordinates::normalizeLat( qreal lat, GeoDataCoordinates::Unit unit ) 0270 { 0271 qreal halfCircle; 0272 if ( unit == GeoDataCoordinates::Radian ) { 0273 halfCircle = M_PI; 0274 } 0275 else { 0276 halfCircle = 180; 0277 } 0278 0279 if ( lat > ( halfCircle / 2.0 ) ) { 0280 int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) ); 0281 qreal temp; 0282 if( cycles == 0 ) { // pi/2 < lat < pi 0283 temp = halfCircle - lat; 0284 } else { 0285 temp = lat - ( cycles * 2 * halfCircle ); 0286 } 0287 if ( temp > ( halfCircle / 2.0 ) ) { 0288 return ( halfCircle - temp ); 0289 } 0290 if ( temp < ( -halfCircle / 2.0 ) ) { 0291 return ( -halfCircle - temp ); 0292 } 0293 return temp; 0294 } 0295 if ( lat < ( -halfCircle / 2.0 ) ) { 0296 int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) ); 0297 qreal temp; 0298 if( cycles == 0 ) { 0299 temp = -halfCircle - lat; 0300 } else { 0301 temp = lat - ( cycles * 2 * halfCircle ); 0302 } 0303 if ( temp > ( +halfCircle / 2.0 ) ) { 0304 return ( +halfCircle - temp ); 0305 } 0306 if ( temp < ( -halfCircle / 2.0 ) ) { 0307 return ( -halfCircle - temp ); 0308 } 0309 return temp; 0310 } 0311 return lat; 0312 } 0313 0314 //static 0315 void GeoDataCoordinates::normalizeLonLat( qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit ) 0316 { 0317 qreal halfCircle; 0318 if ( unit == GeoDataCoordinates::Radian ) { 0319 halfCircle = M_PI; 0320 } 0321 else { 0322 halfCircle = 180; 0323 } 0324 0325 if ( lon > +halfCircle ) { 0326 int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) ); 0327 lon = lon - ( cycles * 2 * halfCircle ); 0328 } 0329 if ( lon < -halfCircle ) { 0330 int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) ); 0331 lon = lon - ( cycles * 2 * halfCircle ); 0332 } 0333 0334 if ( lat > ( +halfCircle / 2.0 ) ) { 0335 int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) ); 0336 qreal temp; 0337 if( cycles == 0 ) { // pi/2 < lat < pi 0338 temp = halfCircle - lat; 0339 } else { 0340 temp = lat - ( cycles * 2 * halfCircle ); 0341 } 0342 if ( temp > ( +halfCircle / 2.0 ) ) { 0343 lat = +halfCircle - temp; 0344 } 0345 if ( temp < ( -halfCircle / 2.0 ) ) { 0346 lat = -halfCircle - temp; 0347 } 0348 lat = temp; 0349 if( lon > 0 ) { 0350 lon = -halfCircle + lon; 0351 } else { 0352 lon = halfCircle + lon; 0353 } 0354 } 0355 if ( lat < ( -halfCircle / 2.0 ) ) { 0356 int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) ); 0357 qreal temp; 0358 if( cycles == 0 ) { 0359 temp = -halfCircle - lat; 0360 } else { 0361 temp = lat - ( cycles * 2 * halfCircle ); 0362 } 0363 if ( temp > ( +halfCircle / 2.0 ) ) { 0364 lat = +halfCircle - temp; 0365 } 0366 if ( temp < ( -halfCircle / 2.0 ) ) { 0367 lat = -halfCircle - temp; 0368 } 0369 lat = temp; 0370 if( lon > 0 ) { 0371 lon = -halfCircle + lon; 0372 } else { 0373 lon = halfCircle + lon; 0374 } 0375 } 0376 return; 0377 } 0378 0379 GeoDataCoordinates GeoDataCoordinates::fromString( const QString& string, bool& successful ) 0380 { 0381 LonLatParser parser; 0382 successful = parser.parse(string); 0383 if (successful) { 0384 return GeoDataCoordinates( parser.lon(), parser.lat(), 0, GeoDataCoordinates::Degree ); 0385 } else { 0386 return GeoDataCoordinates(); 0387 } 0388 } 0389 0390 0391 QString GeoDataCoordinates::toString() const 0392 { 0393 return GeoDataCoordinates::toString( s_notation ); 0394 } 0395 0396 QString GeoDataCoordinates::toString( GeoDataCoordinates::Notation notation, int precision ) const 0397 { 0398 QString coordString; 0399 0400 if( notation == GeoDataCoordinates::UTM ){ 0401 int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat); 0402 0403 // Handle lack of UTM zone number in the poles 0404 const QString zoneString = (zoneNumber > 0) ? QString::number(zoneNumber) : QString(); 0405 0406 QString bandString = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat); 0407 0408 QString eastingString = QString::number(GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat), 'f', 2); 0409 QString northingString = QString::number(GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat), 'f', 2); 0410 0411 return QString::fromUtf8("%1%2 %3 m E, %4 m N").arg(zoneString, bandString, eastingString, northingString); 0412 } 0413 else{ 0414 coordString = lonToString( d->m_lon, notation, Radian, precision ) 0415 + QLatin1String(", ") 0416 + latToString( d->m_lat, notation, Radian, precision ); 0417 } 0418 0419 return coordString; 0420 } 0421 0422 QString GeoDataCoordinates::lonToString( qreal lon, GeoDataCoordinates::Notation notation, 0423 GeoDataCoordinates::Unit unit, 0424 int precision, 0425 char format ) 0426 { 0427 if( notation == GeoDataCoordinates::UTM ){ 0428 /** 0429 * @FIXME: UTM needs lon + lat to know zone number and easting 0430 * By now, this code returns the zone+easting of the point 0431 * (lon, equator), but this can differ a lot at different locations 0432 * See bug 347536 https://bugs.kde.org/show_bug.cgi?id=347536 0433 */ 0434 0435 qreal lonRad = ( unit == Radian ) ? lon : lon * DEG2RAD; 0436 0437 int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(lonRad, 0); 0438 0439 // Handle lack of UTM zone number in the poles 0440 QString result = (zoneNumber > 0) ? QString::number(zoneNumber) : QString(); 0441 0442 if(precision > 0){ 0443 QString eastingString = QString::number( GeoDataCoordinatesPrivate::lonLatToEasting(lonRad, 0), 'f', 2 ); 0444 result += QString::fromUtf8(" %1 m E").arg(eastingString); 0445 } 0446 0447 return result; 0448 } 0449 0450 QString weString = ( lon < 0 ) ? i18n("W") : i18n("E"); 0451 0452 QString lonString; 0453 0454 qreal lonDegF = ( unit == Degree ) ? fabs( lon ) : fabs( (qreal)(lon) * RAD2DEG ); 0455 0456 // Take care of -1 case 0457 precision = ( precision < 0 ) ? 5 : precision; 0458 0459 if ( notation == DMS || notation == DM ) { 0460 int lonDeg = (int) lonDegF; 0461 qreal lonMinF = 60 * (lonDegF - lonDeg); 0462 int lonMin = (int) lonMinF; 0463 qreal lonSecF = 60 * (lonMinF - lonMin); 0464 int lonSec = (int) lonSecF; 0465 0466 // Adjustment for fuzziness (like 49.999999999999999999999) 0467 if ( precision == 0 ) { 0468 lonDeg = qRound( lonDegF ); 0469 } else if ( precision <= 2 ) { 0470 lonMin = qRound( lonMinF ); 0471 } else if ( precision <= 4 && notation == DMS ) { 0472 lonSec = qRound( lonSecF ); 0473 } else { 0474 if ( notation == DMS ) { 0475 lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); 0476 } 0477 else { 0478 lonMin = lonMinF = qRound( lonMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 ); 0479 } 0480 } 0481 0482 if (lonSec > 59 && notation == DMS ) { 0483 lonSec = lonSecF = 0; 0484 lonMin = lonMinF = lonMinF + 1; 0485 } 0486 if (lonMin > 59) { 0487 lonMin = lonMinF = 0; 0488 lonDeg = lonDegF = lonDegF + 1; 0489 } 0490 0491 // Evaluate the string 0492 lonString = QString::fromUtf8("%1\xc2\xb0").arg(lonDeg, 3, 10, QLatin1Char(' ')); 0493 0494 if ( precision == 0 ) { 0495 return lonString + weString; 0496 } 0497 0498 if ( notation == DMS || precision < 3 ) { 0499 lonString += QString::fromUtf8(" %2\'").arg(lonMin, 2, 10, QLatin1Char('0')); 0500 } 0501 0502 if ( precision < 3 ) { 0503 return lonString + weString; 0504 } 0505 0506 if ( notation == DMS ) { 0507 // Includes -1 case! 0508 if ( precision < 5 ) { 0509 lonString += QString::fromUtf8(" %3\"").arg(lonSec, 2, 'f', 0, QLatin1Char('0')); 0510 return lonString + weString; 0511 } 0512 0513 lonString += QString::fromUtf8(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QLatin1Char('0')); 0514 } 0515 else { 0516 lonString += QString::fromUtf8(" %L3'").arg(lonMinF, precision + 1, 'f', precision - 2, QLatin1Char('0')); 0517 } 0518 } 0519 else if ( notation == GeoDataCoordinates::Decimal ) 0520 { 0521 lonString = QString::fromUtf8("%L1\xc2\xb0").arg(lonDegF, 4 + precision, format, precision, QLatin1Char(' ')); 0522 } 0523 else if ( notation == GeoDataCoordinates::Astro ) 0524 { 0525 if (lon < 0) { 0526 lon += ( unit == Degree ) ? 360 : 2 * M_PI; 0527 } 0528 0529 qreal lonHourF = ( unit == Degree ) ? fabs( lon/15.0 ) : fabs( (qreal)(lon/15.0) * RAD2DEG ); 0530 int lonHour = (int) lonHourF; 0531 qreal lonMinF = 60 * (lonHourF - lonHour); 0532 int lonMin = (int) lonMinF; 0533 qreal lonSecF = 60 * (lonMinF - lonMin); 0534 int lonSec = (int) lonSecF; 0535 0536 // Adjustment for fuzziness (like 49.999999999999999999999) 0537 if ( precision == 0 ) { 0538 lonHour = qRound( lonHourF ); 0539 } else if ( precision <= 2 ) { 0540 lonMin = qRound( lonMinF ); 0541 } else if ( precision <= 4 ) { 0542 lonSec = qRound( lonSecF ); 0543 } else { 0544 lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); 0545 } 0546 0547 if (lonSec > 59 ) { 0548 lonSec = lonSecF = 0; 0549 lonMin = lonMinF = lonMinF + 1; 0550 } 0551 if (lonMin > 59) { 0552 lonMin = lonMinF = 0; 0553 lonHour = lonHourF = lonHourF + 1; 0554 } 0555 0556 // Evaluate the string 0557 lonString = QString::fromUtf8("%1h").arg(lonHour, 3, 10, QLatin1Char(' ')); 0558 0559 if ( precision == 0 ) { 0560 return lonString; 0561 } 0562 0563 lonString += QString::fromUtf8(" %2\'").arg(lonMin, 2, 10, QLatin1Char('0')); 0564 0565 if ( precision < 3 ) { 0566 return lonString; 0567 } 0568 0569 // Includes -1 case! 0570 if ( precision < 5 ) { 0571 lonString += QString::fromUtf8(" %3\"").arg(lonSec, 2, 'f', 0, QLatin1Char('0')); 0572 return lonString; 0573 } 0574 0575 lonString += QString::fromUtf8(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QLatin1Char('0')); 0576 return lonString; 0577 } 0578 0579 return lonString + weString; 0580 } 0581 0582 QString GeoDataCoordinates::lonToString() const 0583 { 0584 return GeoDataCoordinates::lonToString( d->m_lon , s_notation ); 0585 } 0586 0587 QString GeoDataCoordinates::latToString( qreal lat, GeoDataCoordinates::Notation notation, 0588 GeoDataCoordinates::Unit unit, 0589 int precision, 0590 char format ) 0591 { 0592 if( notation == GeoDataCoordinates::UTM ){ 0593 /** 0594 * @FIXME: UTM needs lon + lat to know latitude band and northing 0595 * By now, this code returns the band+northing of the point 0596 * (meridian, lat), but this can differ a lot at different locations 0597 * See bug 347536 https://bugs.kde.org/show_bug.cgi?id=347536 0598 */ 0599 0600 qreal latRad = ( unit == Radian ) ? lat : lat * DEG2RAD; 0601 0602 QString result = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(0, latRad); 0603 0604 if ( precision > 0 ){ 0605 QString northingString = QString::number( GeoDataCoordinatesPrivate::lonLatToNorthing(0, latRad), 'f', 2 ); 0606 result += QString::fromUtf8(" %1 m N").arg(northingString); 0607 } 0608 0609 return result; 0610 } 0611 0612 QString pmString; 0613 QString nsString; 0614 0615 if (notation == GeoDataCoordinates::Astro){ 0616 pmString = ( lat > 0 ) ? QLatin1String("+") : QLatin1String("-"); 0617 } 0618 else { 0619 nsString = ( lat > 0 ) ? i18n("N") : i18n("S"); 0620 } 0621 0622 QString latString; 0623 0624 qreal latDegF = ( unit == Degree ) ? fabs( lat ) : fabs( (qreal)(lat) * RAD2DEG ); 0625 0626 // Take care of -1 case 0627 precision = ( precision < 0 ) ? 5 : precision; 0628 0629 if ( notation == DMS || notation == DM || notation == Astro) { 0630 int latDeg = (int) latDegF; 0631 qreal latMinF = 60 * (latDegF - latDeg); 0632 int latMin = (int) latMinF; 0633 qreal latSecF = 60 * (latMinF - latMin); 0634 int latSec = (int) latSecF; 0635 0636 // Adjustment for fuzziness (like 49.999999999999999999999) 0637 if ( precision == 0 ) { 0638 latDeg = qRound( latDegF ); 0639 } else if ( precision <= 2 ) { 0640 latMin = qRound( latMinF ); 0641 } else if ( precision <= 4 && notation == DMS ) { 0642 latSec = qRound( latSecF ); 0643 } else { 0644 if ( notation == DMS || notation == Astro ) { 0645 latSec = latSecF = qRound( latSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); 0646 } 0647 else { 0648 latMin = latMinF = qRound( latMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 ); 0649 } 0650 } 0651 0652 if (latSec > 59 && ( notation == DMS || notation == Astro )) { 0653 latSecF = 0; 0654 latSec = latSecF; 0655 latMin = latMin + 1; 0656 } 0657 if (latMin > 59) { 0658 latMinF = 0; 0659 latMin = latMinF; 0660 latDeg = latDeg + 1; 0661 } 0662 0663 // Evaluate the string 0664 latString = QString::fromUtf8("%1\xc2\xb0").arg(latDeg, 3, 10, QLatin1Char(' ')); 0665 0666 if ( precision == 0 ) { 0667 return pmString + latString + nsString; 0668 } 0669 0670 if ( notation == DMS || notation == Astro || precision < 3 ) { 0671 latString += QString::fromUtf8(" %2\'").arg(latMin, 2, 10, QLatin1Char('0')); 0672 } 0673 0674 if ( precision < 3 ) { 0675 return pmString + latString + nsString; 0676 } 0677 0678 if ( notation == DMS || notation == Astro ) { 0679 // Includes -1 case! 0680 if ( precision < 5 ) { 0681 latString += QString::fromUtf8(" %3\"").arg(latSec, 2, 'f', 0, QLatin1Char('0')); 0682 return latString + nsString; 0683 } 0684 0685 latString += QString::fromUtf8(" %L3\"").arg(latSecF, precision - 1, 'f', precision - 4, QLatin1Char('0')); 0686 } 0687 else { 0688 latString += QString::fromUtf8(" %L3'").arg(latMinF, precision + 1, 'f', precision - 2, QLatin1Char('0')); 0689 } 0690 } 0691 else // notation = GeoDataCoordinates::Decimal 0692 { 0693 latString = QString::fromUtf8("%L1\xc2\xb0").arg(latDegF, 4 + precision, format, precision, QLatin1Char(' ')); 0694 } 0695 return pmString + latString + nsString; 0696 } 0697 0698 QString GeoDataCoordinates::latToString() const 0699 { 0700 return GeoDataCoordinates::latToString( d->m_lat, s_notation ); 0701 } 0702 0703 bool GeoDataCoordinates::operator==( const GeoDataCoordinates &rhs ) const 0704 { 0705 return *d == *rhs.d; 0706 } 0707 0708 bool GeoDataCoordinates::operator!=( const GeoDataCoordinates &rhs ) const 0709 { 0710 return *d != *rhs.d; 0711 } 0712 0713 void GeoDataCoordinates::setAltitude( const qreal altitude ) 0714 { 0715 detach(); 0716 d->m_altitude = altitude; 0717 } 0718 0719 qreal GeoDataCoordinates::altitude() const 0720 { 0721 return d->m_altitude; 0722 } 0723 0724 int GeoDataCoordinates::utmZone() const{ 0725 return GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat); 0726 } 0727 0728 qreal GeoDataCoordinates::utmEasting() const{ 0729 return GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat); 0730 } 0731 0732 QString GeoDataCoordinates::utmLatitudeBand() const{ 0733 return GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat); 0734 } 0735 0736 qreal GeoDataCoordinates::utmNorthing() const{ 0737 return GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat); 0738 } 0739 0740 quint8 GeoDataCoordinates::detail() const 0741 { 0742 return d->m_detail; 0743 } 0744 0745 void GeoDataCoordinates::setDetail(quint8 detail) 0746 { 0747 detach(); 0748 d->m_detail = detail; 0749 } 0750 0751 GeoDataCoordinates GeoDataCoordinates::rotateAround( const GeoDataCoordinates &axis, qreal angle, Unit unit ) const 0752 { 0753 const Quaternion quatAxis = Quaternion::fromEuler( -axis.latitude() , axis.longitude(), 0 ); 0754 const Quaternion rotationAmount = Quaternion::fromEuler( 0, 0, unit == Radian ? angle : angle * DEG2RAD ); 0755 const Quaternion resultAxis = quatAxis * rotationAmount * quatAxis.inverse(); 0756 0757 return rotateAround(resultAxis); 0758 } 0759 0760 GeoDataCoordinates GeoDataCoordinates::rotateAround(const Quaternion &rotAxis) const 0761 { 0762 Quaternion rotatedQuat = quaternion(); 0763 rotatedQuat.rotateAroundAxis(rotAxis); 0764 qreal rotatedLon, rotatedLat; 0765 rotatedQuat.getSpherical(rotatedLon, rotatedLat); 0766 return GeoDataCoordinates(rotatedLon, rotatedLat, altitude()); 0767 } 0768 0769 qreal GeoDataCoordinates::bearing( const GeoDataCoordinates &other, Unit unit, BearingType type ) const 0770 { 0771 if ( type == FinalBearing ) { 0772 double const offset = unit == Degree ? 180.0 : M_PI; 0773 return offset + other.bearing( *this, unit, InitialBearing ); 0774 } 0775 0776 qreal const delta = other.d->m_lon - d->m_lon; 0777 double const bearing = atan2( sin ( delta ) * cos ( other.d->m_lat ), 0778 cos( d->m_lat ) * sin( other.d->m_lat ) - sin( d->m_lat ) * cos( other.d->m_lat ) * cos ( delta ) ); 0779 return unit == Radian ? bearing : bearing * RAD2DEG; 0780 } 0781 0782 GeoDataCoordinates GeoDataCoordinates::moveByBearing( qreal bearing, qreal distance ) const 0783 { 0784 qreal newLat = asin( sin(d->m_lat) * cos(distance) + 0785 cos(d->m_lat) * sin(distance) * cos(bearing) ); 0786 qreal newLon = d->m_lon + atan2( sin(bearing) * sin(distance) * cos(d->m_lat), 0787 cos(distance) - sin(d->m_lat) * sin(newLat) ); 0788 0789 return GeoDataCoordinates( newLon, newLat ); 0790 } 0791 0792 const Quaternion& GeoDataCoordinates::quaternion() const 0793 { 0794 if (d->m_q == nullptr) { 0795 d->m_q = new Quaternion(Quaternion::fromSpherical( d->m_lon , d->m_lat )); 0796 } 0797 return *d->m_q; 0798 } 0799 0800 GeoDataCoordinates GeoDataCoordinates::interpolate( const GeoDataCoordinates &target, double t_ ) const 0801 { 0802 double const t = qBound( 0.0, t_, 1.0 ); 0803 Quaternion const quat = Quaternion::slerp( quaternion(), target.quaternion(), t ); 0804 qreal lon, lat; 0805 quat.getSpherical( lon, lat ); 0806 double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude; 0807 return GeoDataCoordinates( lon, lat, alt ); 0808 } 0809 0810 GeoDataCoordinates GeoDataCoordinates::nlerp(const GeoDataCoordinates &target, double t) const 0811 { 0812 qreal lon = 0.0; 0813 qreal lat = 0.0; 0814 0815 const Quaternion itpos = Quaternion::nlerp(quaternion(), target.quaternion(), t); 0816 itpos.getSpherical(lon, lat); 0817 0818 const qreal altitude = 0.5 * (d->m_altitude + target.altitude()); 0819 0820 return GeoDataCoordinates(lon, lat, altitude); 0821 } 0822 0823 GeoDataCoordinates GeoDataCoordinates::interpolate( const GeoDataCoordinates &before, const GeoDataCoordinates &target, const GeoDataCoordinates &after, double t_ ) const 0824 { 0825 double const t = qBound( 0.0, t_, 1.0 ); 0826 Quaternion const b1 = GeoDataCoordinatesPrivate::basePoint( before.quaternion(), quaternion(), target.quaternion() ); 0827 Quaternion const a2 = GeoDataCoordinatesPrivate::basePoint( quaternion(), target.quaternion(), after.quaternion() ); 0828 Quaternion const a = Quaternion::slerp( quaternion(), target.quaternion(), t ); 0829 Quaternion const b = Quaternion::slerp( b1, a2, t ); 0830 Quaternion c = Quaternion::slerp( a, b, 2 * t * (1.0-t) ); 0831 qreal lon, lat; 0832 c.getSpherical( lon, lat ); 0833 // @todo spline interpolation of altitude? 0834 double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude; 0835 return GeoDataCoordinates( lon, lat, alt ); 0836 } 0837 0838 bool GeoDataCoordinates::isPole( Pole pole ) const 0839 { 0840 // Evaluate the most likely case first: 0841 // The case where we haven't hit the pole and where our latitude is normalized 0842 // to the range of 90 deg S ... 90 deg N 0843 if ( fabs( (qreal) 2.0 * d->m_lat ) < M_PI ) { 0844 return false; 0845 } 0846 else { 0847 if ( fabs( (qreal) 2.0 * d->m_lat ) == M_PI ) { 0848 // Ok, we have hit a pole. Now let's check whether it's the one we've asked for: 0849 if ( pole == AnyPole ){ 0850 return true; 0851 } 0852 else { 0853 if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) { 0854 return true; 0855 } 0856 if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) { 0857 return true; 0858 } 0859 return false; 0860 } 0861 } 0862 // 0863 else { 0864 // FIXME: Should we just normalize latitude and longitude and be done? 0865 // While this might work well for persistent data it would create some 0866 // possible overhead for temporary data, so this needs careful thinking. 0867 qCDebug(DIGIKAM_MARBLE_LOG) << "GeoDataCoordinates not normalized!"; 0868 0869 // Only as a last resort we cover the unlikely case where 0870 // the latitude is not normalized to the range of 0871 // 90 deg S ... 90 deg N 0872 if ( fabs( (qreal) 2.0 * normalizeLat( d->m_lat ) ) < M_PI ) { 0873 return false; 0874 } 0875 else { 0876 // Ok, we have hit a pole. Now let's check whether it's the one we've asked for: 0877 if ( pole == AnyPole ){ 0878 return true; 0879 } 0880 else { 0881 if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) { 0882 return true; 0883 } 0884 if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) { 0885 return true; 0886 } 0887 return false; 0888 } 0889 } 0890 } 0891 } 0892 } 0893 0894 qreal GeoDataCoordinates::sphericalDistanceTo(const GeoDataCoordinates &other) const 0895 { 0896 qreal lon2, lat2; 0897 other.geoCoordinates( lon2, lat2 ); 0898 0899 // FIXME: Take the altitude into account! 0900 0901 return distanceSphere(d->m_lon, d->m_lat, lon2, lat2); 0902 } 0903 0904 GeoDataCoordinates& GeoDataCoordinates::operator=( const GeoDataCoordinates &other ) 0905 { 0906 qAtomicAssign(d, other.d); 0907 return *this; 0908 } 0909 0910 void GeoDataCoordinates::pack( QDataStream& stream ) const 0911 { 0912 stream << d->m_lon; 0913 stream << d->m_lat; 0914 stream << d->m_altitude; 0915 } 0916 0917 void GeoDataCoordinates::unpack( QDataStream& stream ) 0918 { 0919 // call detach even though it shouldn't be needed - one never knows 0920 detach(); 0921 stream >> d->m_lon; 0922 stream >> d->m_lat; 0923 stream >> d->m_altitude; 0924 } 0925 0926 Quaternion GeoDataCoordinatesPrivate::basePoint( const Quaternion &q1, const Quaternion &q2, const Quaternion &q3 ) 0927 { 0928 Quaternion const a = (q2.inverse() * q3).log(); 0929 Quaternion const b = (q2.inverse() * q1).log(); 0930 return q2 * ((a+b)*-0.25).exp(); 0931 } 0932 0933 0934 0935 qreal GeoDataCoordinatesPrivate::arcLengthOfMeridian( qreal phi ) 0936 { 0937 // Precalculate n 0938 qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis) 0939 / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis); 0940 0941 // Precalculate alpha 0942 qreal const alpha = ( (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0) 0943 * (1.0 + (qPow (n, 2.0) / 4.0) + (qPow (n, 4.0) / 64.0) ); 0944 0945 // Precalculate beta 0946 qreal const beta = (-3.0 * n / 2.0) 0947 + (9.0 * qPow (n, 3.0) / 16.0) 0948 + (-3.0 * qPow (n, 5.0) / 32.0); 0949 0950 // Precalculate gamma 0951 qreal const gamma = (15.0 * qPow (n, 2.0) / 16.0) 0952 + (-15.0 * qPow (n, 4.0) / 32.0); 0953 0954 // Precalculate delta 0955 qreal const delta = (-35.0 * qPow (n, 3.0) / 48.0) 0956 + (105.0 * qPow (n, 5.0) / 256.0); 0957 0958 // Precalculate epsilon 0959 qreal const epsilon = (315.0 * qPow (n, 4.0) / 512.0); 0960 0961 // Now calculate the sum of the series and return 0962 qreal const result = alpha * (phi + (beta * qSin (2.0 * phi)) 0963 + (gamma * qSin (4.0 * phi)) 0964 + (delta * qSin (6.0 * phi)) 0965 + (epsilon * qSin (8.0 * phi))); 0966 0967 return result; 0968 } 0969 0970 qreal GeoDataCoordinatesPrivate::centralMeridianUTM( qreal zone ) 0971 { 0972 return DEG2RAD*(-183.0 + (zone * 6.0)); 0973 } 0974 0975 qreal GeoDataCoordinatesPrivate::footpointLatitude( qreal northing ) 0976 { 0977 // Precalculate n (Eq. 10.18) 0978 qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis) 0979 / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis); 0980 0981 // Precalculate alpha (Eq. 10.22) 0982 // (Same as alpha in Eq. 10.17) 0983 qreal const alpha = ((GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0) 0984 * (1 + (qPow (n, 2.0) / 4) + (qPow (n, 4.0) / 64)); 0985 0986 // Precalculate y (Eq. 10.23) 0987 qreal const y = northing / alpha; 0988 0989 // Precalculate beta (Eq. 10.22) 0990 qreal const beta = (3.0 * n / 2.0) + (-27.0 * qPow (n, 3.0) / 32.0) 0991 + (269.0 * qPow (n, 5.0) / 512.0); 0992 0993 // Precalculate gamma (Eq. 10.22) 0994 qreal const gamma = (21.0 * qPow (n, 2.0) / 16.0) 0995 + (-55.0 * qPow (n, 4.0) / 32.0); 0996 0997 // Precalculate delta (Eq. 10.22) 0998 qreal const delta = (151.0 * qPow (n, 3.0) / 96.0) 0999 + (-417.0 * qPow (n, 5.0) / 128.0); 1000 1001 // Precalculate epsilon (Eq. 10.22) 1002 qreal const epsilon = (1097.0 * qPow (n, 4.0) / 512.0); 1003 1004 // Now calculate the sum of the series (Eq. 10.21) 1005 qreal const result = y + (beta * qSin (2.0 * y)) 1006 + (gamma * qSin (4.0 * y)) 1007 + (delta * qSin (6.0 * y)) 1008 + (epsilon * qSin (8.0 * y)); 1009 1010 return result; 1011 } 1012 1013 QPointF GeoDataCoordinatesPrivate::mapLonLatToXY( qreal lambda, qreal phi, qreal lambda0 ) 1014 { 1015 // Equation (10.15) 1016 1017 // Precalculate second numerical eccentricity 1018 const qreal ep2 = (qPow(GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) - qPow(GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0)) 1019 / qPow(GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0); 1020 1021 // Precalculate the square of nu, just an auxilar quantity 1022 const qreal nu2 = ep2 * qPow(qCos(phi), 2.0); 1023 1024 // Precalculate the radius of curvature in prime vertical 1025 const qreal N = qPow(GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) / (GeoDataCoordinatesPrivate::sm_semiMinorAxis * qSqrt(1 + nu2)); 1026 1027 // Precalculate the tangent of phi and its square 1028 const qreal t = qTan(phi); 1029 const qreal t2 = t * t; 1030 1031 // Precalculate longitude difference 1032 const qreal l = lambda - lambda0; 1033 1034 /* 1035 * Precalculate coefficients for l**n in the equations below 1036 * so a normal human being can read the expressions for easting 1037 * and northing 1038 * -- l**1 and l**2 have coefficients of 1.0 1039 * 1040 * The actual used coefficients starts at coef[1], just to 1041 * follow the meaningful nomenclature in equation 10.15 1042 * (coef[n] corresponds to qPow(l,n) factor) 1043 */ 1044 1045 const qreal coef1 = 1; 1046 const qreal coef2 = 1; 1047 const qreal coef3 = 1.0 - t2 + nu2; 1048 const qreal coef4 = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2); 1049 const qreal coef5 = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2 - 58.0 * t2 * nu2; 1050 const qreal coef6 = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2 - 330.0 * t2 * nu2; 1051 const qreal coef7 = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2); 1052 const qreal coef8 = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2); 1053 1054 // Calculate easting (x) 1055 const qreal easting = N * qCos(phi) * coef1 * l 1056 + (N / 6.0 * qPow(qCos(phi), 3.0) * coef3 * qPow(l, 3.0)) 1057 + (N / 120.0 * qPow(qCos(phi), 5.0) * coef5 * qPow(l, 5.0)) 1058 + (N / 5040.0 * qPow(qCos(phi), 7.0) * coef7 * qPow(l, 7.0)); 1059 1060 // Calculate northing (y) 1061 const qreal northing = arcLengthOfMeridian (phi) 1062 + (t / 2.0 * N * qPow(qCos(phi), 2.0) * coef2 * qPow(l, 2.0)) 1063 + (t / 24.0 * N * qPow(qCos(phi), 4.0) * coef4 * qPow(l, 4.0)) 1064 + (t / 720.0 * N * qPow(qCos(phi), 6.0) * coef6 * qPow(l, 6.0)) 1065 + (t / 40320.0 * N * qPow(qCos(phi), 8.0) * coef8 * qPow(l, 8.0)); 1066 1067 return QPointF(easting, northing); 1068 } 1069 1070 int GeoDataCoordinatesPrivate::lonLatToZone( qreal lon, qreal lat ){ 1071 // Converts lon and lat to degrees 1072 qreal lonDeg = lon * RAD2DEG; 1073 qreal latDeg = lat * RAD2DEG; 1074 1075 /* Round the value of the longitude when the distance to the nearest integer 1076 * is less than 0.0000001. This avoids fuzzy values such as -114.0000000001, which 1077 * can produce a misbehaviour when calculating the zone associated at the borders 1078 * of the zone intervals (for example, the interval [-114, -108[ is associated with 1079 * zone number 12; if the following rounding is not done, the value returned by 1080 * lonLatToZone(114,0) is 11 instead of 12, as the function actually receives 1081 * -114.0000000001, which is in the interval [-120,-114[, associated to zone 11 1082 */ 1083 qreal precision = 0.0000001; 1084 1085 if ( qAbs(lonDeg - qFloor(lonDeg)) < precision || qAbs(lonDeg - qCeil(lonDeg)) < precision ){ 1086 lonDeg = qRound(lonDeg); 1087 } 1088 1089 // There is no numbering associated to the poles, special value 0 is returned. 1090 if ( latDeg < -80 || latDeg > 84 ) { 1091 return 0; 1092 } 1093 1094 // Obtains the zone number handling all the so called "exceptions" 1095 // See problem: https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system#Exceptions 1096 // See solution: https://gis.stackexchange.com/questions/13291/computing-utm-zone-from-lat-long-point 1097 1098 // General 1099 int zoneNumber = static_cast<int>( (lonDeg+180) / 6.0 ) + 1; 1100 1101 // Southwest Norway 1102 if ( latDeg >= 56 && latDeg < 64 && lonDeg >= 3 && lonDeg < 12 ) { 1103 zoneNumber = 32; 1104 } 1105 1106 // Svalbard 1107 if ( latDeg >= 72 && latDeg < 84 ) { 1108 if ( lonDeg >= 0 && lonDeg < 9 ) { 1109 zoneNumber = 31; 1110 } else if ( lonDeg >= 9 && lonDeg < 21 ) { 1111 zoneNumber = 33; 1112 } else if ( lonDeg >= 21 && lonDeg < 33 ) { 1113 zoneNumber = 35; 1114 } else if ( lonDeg >= 33 && lonDeg < 42 ) { 1115 zoneNumber = 37; 1116 } 1117 } 1118 1119 return zoneNumber; 1120 } 1121 1122 qreal GeoDataCoordinatesPrivate::lonLatToEasting( qreal lon, qreal lat ){ 1123 int zoneNumber = lonLatToZone( lon, lat ); 1124 1125 if ( zoneNumber == 0 ){ 1126 qreal lonDeg = lon * RAD2DEG; 1127 zoneNumber = static_cast<int>( (lonDeg+180) / 6.0 ) + 1; 1128 } 1129 1130 QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) ); 1131 1132 // Adjust easting and northing for UTM system 1133 qreal easting = coordinates.x() * GeoDataCoordinatesPrivate::sm_utmScaleFactor + 500000.0; 1134 1135 return easting; 1136 } 1137 1138 QString GeoDataCoordinatesPrivate::lonLatToLatitudeBand( qreal lon, qreal lat ){ 1139 // Obtains the latitude bands handling all the so called "exceptions" 1140 1141 // Converts lon and lat to degrees 1142 qreal lonDeg = lon * RAD2DEG; 1143 qreal latDeg = lat * RAD2DEG; 1144 1145 // Regular latitude bands between 80 S and 80 N (that is, between 10 and 170 in the [0,180] interval) 1146 int bandLetterIndex = 24; //Avoids "may be used uninitialized" warning 1147 1148 if ( latDeg < -80 ) { 1149 // South pole (A for zones 1-30, B for zones 31-60) 1150 bandLetterIndex = ( (lonDeg+180) < 6*31 ) ? 0 : 1; 1151 } else if ( latDeg >= -80 && latDeg <= 80 ) { 1152 // General (+2 because the general lettering starts in C) 1153 bandLetterIndex = static_cast<int>( (latDeg+80.0) / 8.0 ) + 2; 1154 } else if ( latDeg >= 80 && latDeg < 84 ) { 1155 // Band X is extended 4 more degrees 1156 bandLetterIndex = 21; 1157 } else if ( latDeg >= 84 ) { 1158 // North pole (Y for zones 1-30, Z for zones 31-60) 1159 bandLetterIndex = ((lonDeg+180) < 6*31) ? 22 : 23; 1160 } 1161 1162 return QStringLiteral( "ABCDEFGHJKLMNPQRSTUVWXYZ?" ).at( bandLetterIndex ); 1163 } 1164 1165 qreal GeoDataCoordinatesPrivate::lonLatToNorthing( qreal lon, qreal lat ){ 1166 int zoneNumber = lonLatToZone( lon, lat ); 1167 1168 if ( zoneNumber == 0 ){ 1169 qreal lonDeg = lon * RAD2DEG; 1170 zoneNumber = static_cast<int>( (lonDeg+180) / 6.0 ) + 1; 1171 } 1172 1173 QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) ); 1174 1175 qreal northing = coordinates.y() * GeoDataCoordinatesPrivate::sm_utmScaleFactor; 1176 1177 if ( northing < 0.0 ) { 1178 northing += 10000000.0; 1179 } 1180 1181 return northing; 1182 } 1183 1184 size_t qHash(const GeoDataCoordinates &coordinates) 1185 { 1186 uint seed = ::qHash(coordinates.altitude()); 1187 seed = ::qHash(coordinates.latitude(), seed); 1188 1189 return ::qHash(coordinates.longitude(), seed); 1190 } 1191 1192 }