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 }