File indexing completed on 2024-05-12 03:50:10

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