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 }