diff --git a/src/lib/marble/geodata/data/GeoDataCoordinates.cpp b/src/lib/marble/geodata/data/GeoDataCoordinates.cpp index fd2a675bb..74175ea72 100644 --- a/src/lib/marble/geodata/data/GeoDataCoordinates.cpp +++ b/src/lib/marble/geodata/data/GeoDataCoordinates.cpp @@ -1,1706 +1,1722 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2004-2007 Torsten Rahn // Copyright 2007-2008 Inge Wallin // Copyright 2008 Patrick Spendrin // Copyright 2011 Friedrich W. H. Kossebau // Copyright 2011 Bernhard Beschow // Copyright 2015 Alejandro Garcia Montoro // #include "GeoDataCoordinates.h" #include "GeoDataCoordinates_p.h" #include #include #include #include #include #include #include #include #include #include #include "MarbleGlobal.h" #include "MarbleDebug.h" #include "Quaternion.h" namespace Marble { // Helper class for GeoDataCoordinates::fromString(...) class LonLatParser { private: enum DirPosition { PrefixDir, PostfixDir }; + /** + * Parses the double value from the input string in system locale + * if it contains the system locale decimalpoint char, + * otherwise parses it in the C locale. + */ + static double parseDouble(const QString& input); static QString createDecimalPointExp(); static QString regExp( const QString& string ); static void getLocaleList( QStringList& localeList, const QString& localeListString, const QLatin1String& placeholder, const QString& separator ); static bool isDirection( const QString& input, const QString& direction); static bool isDirection( const QString& input, const QStringList& directions); static bool isOneOfDirections( const QString& input, const QString& firstDirection, const QString& secondDirection, bool& isFirstDirection); static bool isOneOfDirections( const QString& input, const QStringList& firstDirections, const QStringList& secondDirections, bool& isFirstDirection); /** * function template for the function calculating the degree value from * the captured texts with the degree, the minutes, the seconds and the signedness * (or less, depending on what the function actually expects) * @param regex the regexp to take the texts from * @param c the index in the list of captured texts of @p regex to start with * @param isPosHemisphere if the texts of the degree value are relative to the pos hemisphere * @return the calculated degree value */ static qreal degreeValueFromDMS( const QRegExp& regex, int c, bool isPosHemisphere ); static qreal degreeValueFromDM( const QRegExp& regex, int c, bool isPosHemisphere ); static qreal degreeValueFromD( const QRegExp& regex, int c, bool isPosHemisphere ); public: LonLatParser(); /** * @brief parses the complete @p input string and sets the lon and lat properties if successful. * @param input the string to parse, must not have other content than the coordinates * @return @c true on successful parsing, @c false otherwise. */ bool parse( const QString& input ); /** * @brief return the lon value from the last successful parsing */ qreal lon() const { return m_lon; } /** * @brief return the lat value from the last successful parsing */ qreal lat() const { return m_lat; } private: /** * @brief tries to parse the input with the given reg expression and get the lon and lat values * @param input the string to parse, must not have other content than the coordinates * @param dirPosition position of the dir in the list of captured texts * @return @c true on successful parsing, @c false otherwise. */ bool tryMatchFromDms( const QString& input, DirPosition dirPosition ); bool tryMatchFromDm( const QString& input, DirPosition dirPosition ); bool tryMatchFromD( const QString& input, DirPosition dirPosition ); /** * @brief initializes also all properties which only need to be lazily initialized */ void initAll(); /** * @brief checks if the both passed directions are correct, also returns more data about them * @param dir1 first direction string * @param dir1 second direction string * @param isDir1LonDir is set to @c true if first direction string is a longitude direction, * @c false otherwise * @param isLonDirPosHemisphere is set to @c true if longitude direction is in positive hemisphere, * @c false otherwise * @param isLatDirPosHemisphere is set to @c true if latitude direction is in positive hemisphere, * @c false otherwise * @return @c true if @p dir1 and @p dir2 are correct, @c false otherwise. */ bool isCorrectDirections( const QString& dir1, const QString& dir2, bool& isDir1LonDir, bool& isLonDirPosHemisphere, bool& isLatDirPosHemisphere ) const; bool isLocaleLonDirection( const QString& input, bool& isDirPosHemisphere ) const; bool isLocaleLatDirection( const QString& input, bool& isDirPosHemisphere ) const; bool isLonDirection( const QString& input, bool& isDirPosHemisphere ) const; bool isLatDirection( const QString& input, bool& isDirPosHemisphere ) const; private: qreal m_lon; qreal m_lat; private: // helper values const QString m_north; const QString m_east; const QString m_south; const QString m_west; const QString m_decimalPointExp; private: // helper value, lazily set, in initAll(); QStringList m_northLocale; QStringList m_eastLocale; QStringList m_southLocale; QStringList m_westLocale; QStringList m_degreeLocale; QStringList m_minutesLocale; QStringList m_secondsLocale; QString m_dirCapExp; QString m_degreeExp; QString m_minutesExp; QString m_secondsExp; }; LonLatParser::LonLatParser() : m_lon( 0.0 ) , m_lat( 0.0 ) , m_north( QLatin1String("n") ) , m_east( QLatin1String("e") ) , m_south( QLatin1String("s") ) , m_west( QLatin1String("w") ) , m_decimalPointExp( createDecimalPointExp() ) { } void LonLatParser::initAll() { // already all initialized? if (! m_dirCapExp.isEmpty() ) return; const QLatin1String placeholder = QLatin1String("*"); const QString separator = QLatin1String("|"); getLocaleList( m_northLocale, GeoDataCoordinates::tr( "*", "North direction terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); getLocaleList( m_eastLocale, GeoDataCoordinates::tr( "*", "East direction terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); getLocaleList( m_southLocale, GeoDataCoordinates::tr( "*", "South direction terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); getLocaleList( m_westLocale, GeoDataCoordinates::tr( "*", "West direction terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); // use a set to remove duplicates QSet dirs = QSet() << m_north << m_east << m_south << m_west; dirs += m_northLocale.toSet(); dirs += m_eastLocale.toSet(); dirs += m_southLocale.toSet(); dirs += m_westLocale.toSet(); QString fullNamesExp; QString simpleLetters; foreach( const QString& dir, dirs ) { // collect simple letters if ((dir.length() == 1) && (QLatin1Char('a')<=dir.at(0)) && (dir.at(0)<=QLatin1Char('z'))) { simpleLetters += dir; continue; } // okay to add '|' also for last, separates from firstLetters fullNamesExp += regExp(dir) + QLatin1Char('|'); } // Sets "(north|east|south|west|[nesw])" in en, as translated names match untranslated ones m_dirCapExp = QLatin1Char('(') + fullNamesExp + QLatin1Char('[') + simpleLetters + QLatin1String("])"); // expressions for symbols of degree, minutes and seconds getLocaleList( m_degreeLocale, GeoDataCoordinates::tr( "*", "Degree symbol terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); getLocaleList( m_minutesLocale, GeoDataCoordinates::tr( "*", "Minutes symbol terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); getLocaleList( m_secondsLocale, GeoDataCoordinates::tr( "*", "Seconds symbol terms, see http://techbase.kde.org/Projects/Marble/GeoDataCoordinatesTranslation" ), placeholder, separator ); // Used unicode regexp expressions: // x00B0: ° DEGREE SIGN // x00BA: º MASCULINE ORDINAL INDICATOR (found used as degree sign) // x2032: ′ PRIME (minutes) // x00B4: ´ ACUTE ACCENT (found as minutes sign) // x02CA: ˊ MODIFIER LETTER ACUTE ACCENT // x2019: ’ RIGHT SINGLE QUOTATION MARK // x2033: ″ DOUBLE PRIME (seconds) // x201D: ” RIGHT DOUBLE QUOTATION MARK m_degreeExp = QLatin1String("\\x00B0|\\x00BA"); foreach(const QString& symbol, m_degreeLocale) { m_degreeExp += QLatin1Char('|') + regExp(symbol); } m_minutesExp = QLatin1String("'|\\x2032|\\x00B4|\\x20C2|\\x2019"); foreach(const QString& symbol, m_minutesLocale) { m_minutesExp += QLatin1Char('|') + regExp(symbol); } m_secondsExp = QLatin1String("\"|\\x2033|\\x201D|''|\\x2032\\x2032|\\x00B4\\x00B4|\\x20C2\\x20C2|\\x2019\\x2019"); foreach(const QString& symbol, m_secondsLocale) { m_secondsExp += QLatin1Char('|') + regExp(symbol); } } bool LonLatParser::parse( const QString& string ) { QString input = string.toLower().trimmed(); // #1: Just two numbers, no directions, e.g. 74.2245 -32.2434 (assumes lat lon) { const QString numberCapExp = QString::fromLatin1( "([-+]?\\d{1,3}%1?\\d*(?:[eE][+-]?\\d+)?)(?:,|;|\\s)\\s*" "([-+]?\\d{1,3}%1?\\d*(?:[eE][+-]?\\d+)?)" ).arg(m_decimalPointExp); const QRegExp regex = QRegExp( numberCapExp ); if( regex.exactMatch(input) ) { - m_lon = regex.cap(2).toDouble(); - m_lat = regex.cap(1).toDouble(); + m_lon = parseDouble(regex.cap(2)); + m_lat = parseDouble(regex.cap(1)); return true; } } initAll(); if ( tryMatchFromD( input, PostfixDir ) ) { return true; } if ( tryMatchFromD( input, PrefixDir ) ) { return true; } if ( tryMatchFromDms( input, PostfixDir ) ) { return true; } if ( tryMatchFromDms( input, PrefixDir ) ) { return true; } if ( tryMatchFromDm( input, PostfixDir ) ) { return true; } if ( tryMatchFromDm( input, PrefixDir ) ) { return true; } return false; } // #3: Sexagesimal bool LonLatParser::tryMatchFromDms( const QString& input, DirPosition dirPosition ) { // direction as postfix const char postfixCapExp[] = "([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2})(?:%4|\\s)\\s*" "(\\d{1,2}%1?\\d*)(?:%5)?\\s*%2[,;]?\\s*" "([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2})(?:%4|\\s)\\s*" "(\\d{1,2}%1?\\d*)(?:%5)?\\s*%2"; // direction as prefix const char prefixCapExp[] = "%2\\s*([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2})(?:%4|\\s)\\s*" "(\\d{1,2}%1?\\d*)(?:%5)?\\s*(?:,|;|\\s)\\s*" "%2\\s*([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2})(?:%4|\\s)\\s*" "(\\d{1,2}%1?\\d*)(?:%5)?"; const char *expTemplate = ( dirPosition == PostfixDir ) ? postfixCapExp : prefixCapExp; const QString numberCapExp = QString::fromLatin1( expTemplate ).arg( m_decimalPointExp, m_dirCapExp, m_degreeExp, m_minutesExp, m_secondsExp); const QRegExp regex = QRegExp( numberCapExp ); if( !regex.exactMatch( input ) ) { return false; } bool isDir1LonDir; bool isLonDirPosHemisphere; bool isLatDirPosHemisphere; const QString dir1 = regex.cap( dirPosition == PostfixDir ? 5 : 1 ); const QString dir2 = regex.cap( dirPosition == PostfixDir ? 10 : 6 ); if ( !isCorrectDirections( dir1, dir2, isDir1LonDir, isLonDirPosHemisphere, isLatDirPosHemisphere ) ) { return false; } const int valueStartIndex1 = (dirPosition == PostfixDir ? 1 : 2); const int valueStartIndex2 = (dirPosition == PostfixDir ? 6 : 7); m_lon = degreeValueFromDMS( regex, isDir1LonDir ? valueStartIndex1 : valueStartIndex2, isLonDirPosHemisphere ); m_lat = degreeValueFromDMS( regex, isDir1LonDir ? valueStartIndex2 : valueStartIndex1, isLatDirPosHemisphere ); return true; } // #4: Sexagesimal with minute precision bool LonLatParser::tryMatchFromDm( const QString& input, DirPosition dirPosition ) { // direction as postfix const char postfixCapExp[] = "([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2}%1?\\d*)(?:%4)?\\s*%2[,;]?\\s*" "([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2}%1?\\d*)(?:%4)?\\s*%2"; // direction as prefix const char prefixCapExp[] = "%2\\s*([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2}%1?\\d*)(?:%4)?\\s*(?:,|;|\\s)\\s*" "%2\\s*([-+]?)(\\d{1,3})(?:%3|\\s)\\s*(\\d{1,2}%1?\\d*)(?:%4)?"; const char *expTemplate = ( dirPosition == PostfixDir ) ? postfixCapExp : prefixCapExp; const QString numberCapExp = QString::fromLatin1( expTemplate ).arg( m_decimalPointExp, m_dirCapExp, m_degreeExp, m_minutesExp ); const QRegExp regex = QRegExp( numberCapExp ); if( !regex.exactMatch(input) ) { return false; } bool isDir1LonDir; bool isLonDirPosHemisphere; bool isLatDirPosHemisphere; const QString dir1 = regex.cap( dirPosition == PostfixDir ? 4 : 1 ); const QString dir2 = regex.cap( dirPosition == PostfixDir ? 8 : 5 ); if ( !isCorrectDirections( dir1, dir2, isDir1LonDir, isLonDirPosHemisphere, isLatDirPosHemisphere ) ) { return false; } const int valueStartIndex1 = ( dirPosition == PostfixDir ? 1 : 2 ); const int valueStartIndex2 = ( dirPosition == PostfixDir ? 5 : 6 ); m_lon = degreeValueFromDM( regex, isDir1LonDir ? valueStartIndex1 : valueStartIndex2, isLonDirPosHemisphere ); m_lat = degreeValueFromDM( regex, isDir1LonDir ? valueStartIndex2 : valueStartIndex1, isLatDirPosHemisphere ); return true; } // #2: Two numbers with directions bool LonLatParser::tryMatchFromD( const QString& input, DirPosition dirPosition ) { // direction as postfix, e.g. 74.2245 N 32.2434 W const char postfixCapExp[] = "([-+]?\\d{1,3}%1?\\d*)(?:%3)?(?:\\s*)%2(?:,|;|\\s)\\s*" "([-+]?\\d{1,3}%1?\\d*)(?:%3)?(?:\\s*)%2"; // direction as prefix, e.g. N 74.2245 W 32.2434 const char prefixCapExp[] = "%2\\s*([-+]?\\d{1,3}%1?\\d*)(?:%3)?\\s*(?:,|;|\\s)\\s*" "%2\\s*([-+]?\\d{1,3}%1?\\d*)(?:%3)?"; const char *expTemplate = ( dirPosition == PostfixDir ) ? postfixCapExp : prefixCapExp; const QString numberCapExp = QString::fromLatin1( expTemplate ).arg( m_decimalPointExp, m_dirCapExp, m_degreeExp ); const QRegExp regex = QRegExp( numberCapExp ); if( !regex.exactMatch( input ) ) { return false; } bool isDir1LonDir; bool isLonDirPosHemisphere; bool isLatDirPosHemisphere; const QString dir1 = regex.cap( dirPosition == PostfixDir ? 2 : 1 ); const QString dir2 = regex.cap( dirPosition == PostfixDir ? 4 : 3 ); if ( !isCorrectDirections( dir1, dir2, isDir1LonDir, isLonDirPosHemisphere, isLatDirPosHemisphere ) ) { return false; } const int valueStartIndex1 = ( dirPosition == PostfixDir ? 1 : 2 ); const int valueStartIndex2 = ( dirPosition == PostfixDir ? 3 : 4 ); m_lon = degreeValueFromD( regex, isDir1LonDir ? valueStartIndex1 : valueStartIndex2, isLonDirPosHemisphere ); m_lat = degreeValueFromD( regex, isDir1LonDir ? valueStartIndex2 : valueStartIndex1, isLatDirPosHemisphere ); return true; } +double LonLatParser::parseDouble(const QString& input) +{ + // Decide by decimalpoint if system locale or C locale should be tried. + // Otherwise if first trying with a system locale when the string is in C locale, + // the "." might be misinterpreted as thousands group separator and thus a wrong + // value yielded + QLocale locale = QLocale::system(); + return input.contains(locale.decimalPoint()) ? locale.toDouble(input) : input.toDouble(); +} + QString LonLatParser::createDecimalPointExp() { const QChar decimalPoint = QLocale::system().decimalPoint(); return (decimalPoint == QLatin1Char('.')) ? QString::fromLatin1("\\.") : QLatin1String("[.") + decimalPoint + QLatin1Char(']'); } QString LonLatParser::regExp(const QString& string) { QString result; for (int i = 0; i < string.length(); ++i) { const QChar c = string.at(i); if ((QLatin1Char('a') <= c) && (c <= QLatin1Char('z'))) { result += c; } else if (c.isSpace()) { result += QLatin1String("\\s"); } else if (c == QLatin1Char('.')) { result += QLatin1String("\\."); } else { result += QString::fromLatin1("\\x%1").arg(c.unicode(), 4, 16, QLatin1Char('0')); } } return result; } void LonLatParser::getLocaleList( QStringList& localeList, const QString& localeListString, const QLatin1String& placeholder, const QString& separator ) { const QString lowerLocaleListString = localeListString.toLower(); if (lowerLocaleListString != placeholder) { localeList = lowerLocaleListString.split(separator, QString::SkipEmptyParts); } } bool LonLatParser::isDirection( const QString& input, const QStringList& directions ) { return ( directions.contains(input) ); } bool LonLatParser::isDirection( const QString& input, const QString& direction ) { return ( input == direction ); } bool LonLatParser::isOneOfDirections( const QString& input, const QString& firstDirection, const QString& secondDirection, bool& isFirstDirection ) { isFirstDirection = isDirection(input, firstDirection); return isFirstDirection || isDirection(input, secondDirection); } bool LonLatParser::isOneOfDirections( const QString& input, const QStringList& firstDirections, const QStringList& secondDirections, bool& isFirstDirection ) { isFirstDirection = isDirection(input, firstDirections); return isFirstDirection || isDirection(input, secondDirections); } bool LonLatParser::isLocaleLonDirection( const QString& input, bool& isDirPosHemisphere ) const { return isOneOfDirections(input, m_eastLocale, m_westLocale, isDirPosHemisphere); } bool LonLatParser::isLocaleLatDirection( const QString& input, bool& isDirPosHemisphere ) const { return isOneOfDirections(input, m_northLocale, m_southLocale, isDirPosHemisphere); } bool LonLatParser::isLonDirection( const QString& input, bool& isDirPosHemisphere ) const { return isOneOfDirections(input, m_east, m_west, isDirPosHemisphere); } bool LonLatParser::isLatDirection( const QString& input, bool& isDirPosHemisphere ) const { return isOneOfDirections(input, m_north, m_south, isDirPosHemisphere); } qreal LonLatParser::degreeValueFromDMS( const QRegExp& regex, int c, bool isPosHemisphere ) { const bool isNegativeValue = (regex.cap( c++ ) == QLatin1String("-")); const uint degree = regex.cap( c++ ).toUInt(); const uint minutes = regex.cap( c++ ).toUInt(); - const qreal seconds = regex.cap( c ).toDouble(); + const qreal seconds = parseDouble(regex.cap( c )); qreal result = degree + (minutes*MIN2HOUR) + (seconds*SEC2HOUR); if (isNegativeValue) result *= -1; if (! isPosHemisphere) result *= -1; return result; } qreal LonLatParser::degreeValueFromDM( const QRegExp& regex, int c, bool isPosHemisphere ) { const bool isNegativeValue = (regex.cap( c++ ) == QLatin1String("-")); const uint degree = regex.cap( c++ ).toUInt(); - const qreal minutes = regex.cap( c ).toDouble(); + const qreal minutes = parseDouble(regex.cap( c )); qreal result = degree + (minutes*MIN2HOUR); if (isNegativeValue) result *= -1; if (! isPosHemisphere) result *= -1; return result; } qreal LonLatParser::degreeValueFromD( const QRegExp& regex, int c, bool isPosHemisphere ) { - qreal result = regex.cap( c ).toDouble(); + qreal result = parseDouble(regex.cap( c )); if (! isPosHemisphere) result *= -1; return result; } bool LonLatParser::isCorrectDirections(const QString& dir1, const QString& dir2, bool& isDir1LonDir, bool& isLonDirPosHemisphere, bool& isLatDirPosHemisphere) const { // first try localized names isDir1LonDir = isLocaleLonDirection(dir1, isLonDirPosHemisphere); const bool resultLocale = isDir1LonDir ? isLocaleLatDirection(dir2, isLatDirPosHemisphere) : (isLocaleLatDirection(dir1, isLatDirPosHemisphere) && isLocaleLonDirection(dir2, isLonDirPosHemisphere)); if (resultLocale) return resultLocale; // fallback to try english names as lingua franca isDir1LonDir = isLonDirection(dir1, isLonDirPosHemisphere); return isDir1LonDir ? isLatDirection(dir2, isLatDirPosHemisphere) : (isLatDirection(dir1, isLatDirPosHemisphere) && isLonDirection(dir2, isLonDirPosHemisphere)); } const qreal GeoDataCoordinatesPrivate::sm_semiMajorAxis = 6378137.0; const qreal GeoDataCoordinatesPrivate::sm_semiMinorAxis = 6356752.314; const qreal GeoDataCoordinatesPrivate::sm_eccentricitySquared = 6.69437999013e-03; const qreal GeoDataCoordinatesPrivate::sm_utmScaleFactor = 0.9996; GeoDataCoordinates::Notation GeoDataCoordinates::s_notation = GeoDataCoordinates::DMS; const GeoDataCoordinates GeoDataCoordinates::null = GeoDataCoordinates( 0, 0, 0 ); // don't use default constructor! GeoDataCoordinates::GeoDataCoordinates( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit, int _detail ) : d( new GeoDataCoordinatesPrivate( _lon, _lat, _alt, unit, _detail ) ) { d->ref.ref(); } /* simply copy the d pointer * it will be replaced in the detach function instead */ GeoDataCoordinates::GeoDataCoordinates( const GeoDataCoordinates& other ) : d( other.d ) { d->ref.ref(); } /* simply copy null's d pointer * it will be replaced in the detach function */ GeoDataCoordinates::GeoDataCoordinates() : d( null.d ) { d->ref.ref(); } /* * only delete the private d pointer if the number of references is 0 * remember that all copies share the same d pointer! */ GeoDataCoordinates::~GeoDataCoordinates() { delete d->m_q; d->m_q = 0; if (!d->ref.deref()) delete d; #ifdef DEBUG_GEODATA // mDebug() << "delete coordinates"; #endif } bool GeoDataCoordinates::isValid() const { return d != null.d; } /* * if only one copy exists, return * else make a new private d pointer object and assign the values of the current * one to it * at the end, if the number of references thus reaches 0 delete it * this state shouldn't happen, but if it does, we have to clean up behind us. */ void GeoDataCoordinates::detach() { if(d->ref.load() == 1) { delete d->m_q; d->m_q = 0; return; } GeoDataCoordinatesPrivate *new_d = new GeoDataCoordinatesPrivate( *d ); if (!d->ref.deref()) delete d; d = new_d; d->ref.ref(); } /* * call detach() at the start of all non-static, non-const functions */ void GeoDataCoordinates::set( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit ) { detach(); d->m_altitude = _alt; switch( unit ){ default: case Radian: d->m_lon = _lon; d->m_lat = _lat; break; case Degree: d->m_lon = _lon * DEG2RAD; d->m_lat = _lat * DEG2RAD; break; } } /* * call detach() at the start of all non-static, non-const functions */ void GeoDataCoordinates::setLongitude( qreal _lon, GeoDataCoordinates::Unit unit ) { detach(); switch( unit ){ default: case Radian: d->m_lon = _lon; break; case Degree: d->m_lon = _lon * DEG2RAD; break; } } /* * call detach() at the start of all non-static, non-const functions */ void GeoDataCoordinates::setLatitude( qreal _lat, GeoDataCoordinates::Unit unit ) { detach(); switch( unit ){ case Radian: d->m_lat = _lat; break; case Degree: d->m_lat = _lat * DEG2RAD; break; } } void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, GeoDataCoordinates::Unit unit ) const { switch ( unit ) { default: case Radian: lon = d->m_lon; lat = d->m_lat; break; case Degree: lon = d->m_lon * RAD2DEG; lat = d->m_lat * RAD2DEG; break; } } void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, qreal& alt, GeoDataCoordinates::Unit unit ) const { geoCoordinates( lon, lat, unit ); alt = d->m_altitude; } qreal GeoDataCoordinates::longitude( GeoDataCoordinates::Unit unit ) const { switch ( unit ) { default: case Radian: return d->m_lon; case Degree: return d->m_lon * RAD2DEG; } } qreal GeoDataCoordinates::latitude( GeoDataCoordinates::Unit unit ) const { switch ( unit ) { default: case Radian: return d->m_lat; case Degree: return d->m_lat * RAD2DEG; } } //static GeoDataCoordinates::Notation GeoDataCoordinates::defaultNotation() { return s_notation; } //static void GeoDataCoordinates::setDefaultNotation( GeoDataCoordinates::Notation notation ) { s_notation = notation; } //static qreal GeoDataCoordinates::normalizeLon( qreal lon, GeoDataCoordinates::Unit unit ) { qreal halfCircle; if ( unit == GeoDataCoordinates::Radian ) { halfCircle = M_PI; } else { halfCircle = 180; } if ( lon > halfCircle ) { int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) ); return lon - ( cycles * 2 * halfCircle ); } if ( lon < -halfCircle ) { int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) ); return lon - ( cycles * 2 * halfCircle ); } return lon; } //static qreal GeoDataCoordinates::normalizeLat( qreal lat, GeoDataCoordinates::Unit unit ) { qreal halfCircle; if ( unit == GeoDataCoordinates::Radian ) { halfCircle = M_PI; } else { halfCircle = 180; } if ( lat > ( halfCircle / 2.0 ) ) { int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { // pi/2 < lat < pi temp = halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( halfCircle / 2.0 ) ) { return ( halfCircle - temp ); } if ( temp < ( -halfCircle / 2.0 ) ) { return ( -halfCircle - temp ); } return temp; } if ( lat < ( -halfCircle / 2.0 ) ) { int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { temp = -halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( +halfCircle / 2.0 ) ) { return ( +halfCircle - temp ); } if ( temp < ( -halfCircle / 2.0 ) ) { return ( -halfCircle - temp ); } return temp; } return lat; } //static void GeoDataCoordinates::normalizeLonLat( qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit ) { qreal halfCircle; if ( unit == GeoDataCoordinates::Radian ) { halfCircle = M_PI; } else { halfCircle = 180; } if ( lon > +halfCircle ) { int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) ); lon = lon - ( cycles * 2 * halfCircle ); } if ( lon < -halfCircle ) { int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) ); lon = lon - ( cycles * 2 * halfCircle ); } if ( lat > ( +halfCircle / 2.0 ) ) { int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { // pi/2 < lat < pi temp = halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( +halfCircle / 2.0 ) ) { lat = +halfCircle - temp; } if ( temp < ( -halfCircle / 2.0 ) ) { lat = -halfCircle - temp; } lat = temp; if( lon > 0 ) { lon = -halfCircle + lon; } else { lon = halfCircle + lon; } } if ( lat < ( -halfCircle / 2.0 ) ) { int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { temp = -halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( +halfCircle / 2.0 ) ) { lat = +halfCircle - temp; } if ( temp < ( -halfCircle / 2.0 ) ) { lat = -halfCircle - temp; } lat = temp; if( lon > 0 ) { lon = -halfCircle + lon; } else { lon = halfCircle + lon; } } return; } GeoDataCoordinates GeoDataCoordinates::fromString( const QString& string, bool& successful ) { LonLatParser parser; successful = parser.parse(string); if (successful) { return GeoDataCoordinates( parser.lon(), parser.lat(), 0, GeoDataCoordinates::Degree ); } else { return GeoDataCoordinates(); } } QString GeoDataCoordinates::toString() const { return GeoDataCoordinates::toString( s_notation ); } QString GeoDataCoordinates::toString( GeoDataCoordinates::Notation notation, int precision ) const { QString coordString; if( notation == GeoDataCoordinates::UTM ){ int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat); // Handle lack of UTM zone number in the poles QString zoneString = ( zoneNumber > 0 ) ? QString::number(zoneNumber) : QString(""); QString bandString = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat); QString eastingString = QString::number(GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat), 'f', 2); QString northingString = QString::number(GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat), 'f', 2); return QString("%1%2 %3 m E, %4 m N").arg(zoneString).arg(bandString).arg(eastingString).arg(northingString); } else{ coordString = lonToString( d->m_lon, notation, Radian, precision ) + QString(", ") + latToString( d->m_lat, notation, Radian, precision ); } return coordString; } QString GeoDataCoordinates::lonToString( qreal lon, GeoDataCoordinates::Notation notation, GeoDataCoordinates::Unit unit, int precision, char format ) { if( notation == GeoDataCoordinates::UTM ){ /** * @FIXME: UTM needs lon + lat to know zone number and easting * By now, this code returns the zone+easting of the point * (lon, equator), but this can differ a lot at different locations * See bug 347536 https://bugs.kde.org/show_bug.cgi?id=347536 */ qreal lonRad = ( unit == Radian ) ? lon : lon * DEG2RAD; int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(lonRad, 0); // Handle lack of UTM zone number in the poles QString result = ( zoneNumber > 0 ) ? QString::number(zoneNumber) : QString(""); if(precision > 0){ QString eastingString = QString::number( GeoDataCoordinatesPrivate::lonLatToEasting(lonRad, 0), 'f', 2 ); result += QString(" %1 m E").arg(eastingString); } return result; } QString weString = ( lon < 0 ) ? tr("W") : tr("E"); QString lonString; qreal lonDegF = ( unit == Degree ) ? fabs( lon ) : fabs( (qreal)(lon) * RAD2DEG ); // Take care of -1 case precision = ( precision < 0 ) ? 5 : precision; if ( notation == DMS || notation == DM ) { int lonDeg = (int) lonDegF; qreal lonMinF = 60 * (lonDegF - lonDeg); int lonMin = (int) lonMinF; qreal lonSecF = 60 * (lonMinF - lonMin); int lonSec = (int) lonSecF; // Adjustment for fuzziness (like 49.999999999999999999999) if ( precision == 0 ) { lonDeg = qRound( lonDegF ); } else if ( precision <= 2 ) { lonMin = qRound( lonMinF ); } else if ( precision <= 4 && notation == DMS ) { lonSec = qRound( lonSecF ); } else { if ( notation == DMS ) { lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); } else { lonMin = lonMinF = qRound( lonMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 ); } } if (lonSec > 59 && notation == DMS ) { lonSec = lonSecF = 0; lonMin = lonMinF = lonMinF + 1; } if (lonMin > 59) { lonMin = lonMinF = 0; lonDeg = lonDegF = lonDegF + 1; } // Evaluate the string lonString = QString::fromUtf8("%1\xc2\xb0").arg(lonDeg, 3, 10, QChar(' ') ); if ( precision == 0 ) { return lonString + weString; } if ( notation == DMS || precision < 3 ) { lonString += QString(" %2\'").arg(lonMin, 2, 10, QChar('0') ); } if ( precision < 3 ) { return lonString + weString; } if ( notation == DMS ) { // Includes -1 case! if ( precision < 5 ) { lonString += QString(" %3\"").arg(lonSec, 2, 'f', 0, QChar('0') ); return lonString + weString; } lonString += QString(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QChar('0') ); } else { lonString += QString(" %L3'").arg(lonMinF, precision + 1, 'f', precision - 2, QChar('0') ); } } else if ( notation == GeoDataCoordinates::Decimal ) { lonString = QString::fromUtf8("%L1\xc2\xb0").arg(lonDegF, 4 + precision, format, precision, QChar(' ') ); } else if ( notation == GeoDataCoordinates::Astro ) { if (lon < 0) { lon += ( unit == Degree ) ? 360 : 2 * M_PI; } qreal lonHourF = ( unit == Degree ) ? fabs( lon/15.0 ) : fabs( (qreal)(lon/15.0) * RAD2DEG ); int lonHour = (int) lonHourF; qreal lonMinF = 60 * (lonHourF - lonHour); int lonMin = (int) lonMinF; qreal lonSecF = 60 * (lonMinF - lonMin); int lonSec = (int) lonSecF; // Adjustment for fuzziness (like 49.999999999999999999999) if ( precision == 0 ) { lonHour = qRound( lonHourF ); } else if ( precision <= 2 ) { lonMin = qRound( lonMinF ); } else if ( precision <= 4 ) { lonSec = qRound( lonSecF ); } else { lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); } if (lonSec > 59 ) { lonSec = lonSecF = 0; lonMin = lonMinF = lonMinF + 1; } if (lonMin > 59) { lonMin = lonMinF = 0; lonHour = lonHourF = lonHourF + 1; } // Evaluate the string lonString = QString::fromUtf8("%1h").arg(lonHour, 3, 10, QChar(' ') ); if ( precision == 0 ) { return lonString; } lonString += QString(" %2\'").arg(lonMin, 2, 10, QChar('0') ); if ( precision < 3 ) { return lonString; } // Includes -1 case! if ( precision < 5 ) { lonString += QString(" %3\"").arg(lonSec, 2, 'f', 0, QChar('0') ); return lonString; } lonString += QString(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QChar('0') ); return lonString; } return lonString + weString; } QString GeoDataCoordinates::lonToString() const { return GeoDataCoordinates::lonToString( d->m_lon , s_notation ); } QString GeoDataCoordinates::latToString( qreal lat, GeoDataCoordinates::Notation notation, GeoDataCoordinates::Unit unit, int precision, char format ) { if( notation == GeoDataCoordinates::UTM ){ /** * @FIXME: UTM needs lon + lat to know latitude band and northing * By now, this code returns the band+northing of the point * (meridian, lat), but this can differ a lot at different locations * See bug 347536 https://bugs.kde.org/show_bug.cgi?id=347536 */ qreal latRad = ( unit == Radian ) ? lat : lat * DEG2RAD; QString result = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(0, latRad); if ( precision > 0 ){ QString northingString = QString::number( GeoDataCoordinatesPrivate::lonLatToNorthing(0, latRad), 'f', 2 ); result += QString(" %1 m N").arg(northingString); } return result; } QString pmString; QString nsString; if (notation == GeoDataCoordinates::Astro){ pmString = ( lat > 0 ) ? "+" : "-"; } else { nsString = ( lat > 0 ) ? tr("N") : tr("S"); } QString latString; qreal latDegF = ( unit == Degree ) ? fabs( lat ) : fabs( (qreal)(lat) * RAD2DEG ); // Take care of -1 case precision = ( precision < 0 ) ? 5 : precision; if ( notation == DMS || notation == DM || notation == Astro) { int latDeg = (int) latDegF; qreal latMinF = 60 * (latDegF - latDeg); int latMin = (int) latMinF; qreal latSecF = 60 * (latMinF - latMin); int latSec = (int) latSecF; // Adjustment for fuzziness (like 49.999999999999999999999) if ( precision == 0 ) { latDeg = qRound( latDegF ); } else if ( precision <= 2 ) { latMin = qRound( latMinF ); } else if ( precision <= 4 && notation == DMS ) { latSec = qRound( latSecF ); } else { if ( notation == DMS || notation == Astro ) { latSec = latSecF = qRound( latSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); } else { latMin = latMinF = qRound( latMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 ); } } if (latSec > 59 && ( notation == DMS || notation == Astro )) { latSecF = 0; latSec = latSecF; latMin = latMin + 1; } if (latMin > 59) { latMinF = 0; latMin = latMinF; latDeg = latDeg + 1; } // Evaluate the string latString = QString::fromUtf8("%1\xc2\xb0").arg(latDeg, 3, 10, QChar(' ') ); if ( precision == 0 ) { return pmString + latString + nsString; } if ( notation == DMS || notation == Astro || precision < 3 ) { latString += QString(" %2\'").arg(latMin, 2, 10, QChar('0') ); } if ( precision < 3 ) { return pmString + latString + nsString; } if ( notation == DMS || notation == Astro ) { // Includes -1 case! if ( precision < 5 ) { latString += QString(" %3\"").arg(latSec, 2, 'f', 0, QChar('0') ); return latString + nsString; } latString += QString(" %L3\"").arg(latSecF, precision - 1, 'f', precision - 4, QChar('0') ); } else { latString += QString(" %L3'").arg(latMinF, precision + 1, 'f', precision - 2, QChar('0') ); } } else // notation = GeoDataCoordinates::Decimal { latString = QString::fromUtf8("%L1\xc2\xb0").arg(latDegF, 4 + precision, format, precision, QChar(' ') ); } return pmString + latString + nsString; } QString GeoDataCoordinates::latToString() const { return GeoDataCoordinates::latToString( d->m_lat, s_notation ); } bool GeoDataCoordinates::operator==( const GeoDataCoordinates &rhs ) const { return *d == *rhs.d; } bool GeoDataCoordinates::operator!=( const GeoDataCoordinates &rhs ) const { return *d != *rhs.d; } void GeoDataCoordinates::setAltitude( const qreal altitude ) { detach(); d->m_altitude = altitude; } qreal GeoDataCoordinates::altitude() const { return d->m_altitude; } int GeoDataCoordinates::utmZone() const{ return GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat); } qreal GeoDataCoordinates::utmEasting() const{ return GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat); } QString GeoDataCoordinates::utmLatitudeBand() const{ return GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat); } qreal GeoDataCoordinates::utmNorthing() const{ return GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat); } int GeoDataCoordinates::detail() const { return d->m_detail; } void GeoDataCoordinates::setDetail( const int det ) { detach(); d->m_detail = det; } GeoDataCoordinates GeoDataCoordinates::rotateAround( const GeoDataCoordinates &axis, qreal angle, Unit unit ) const { const Quaternion quatAxis = Quaternion::fromEuler( -axis.latitude() , axis.longitude(), 0 ); const Quaternion rotationAmount = Quaternion::fromEuler( 0, 0, unit == Radian ? angle : angle * DEG2RAD ); const Quaternion resultAxis = quatAxis * rotationAmount * quatAxis.inverse(); Quaternion rotatedQuat = quaternion(); rotatedQuat.rotateAroundAxis(resultAxis); qreal rotatedLon, rotatedLat; rotatedQuat.getSpherical(rotatedLon, rotatedLat); return GeoDataCoordinates(rotatedLon, rotatedLat, altitude()); } qreal GeoDataCoordinates::bearing( const GeoDataCoordinates &other, Unit unit, BearingType type ) const { if ( type == FinalBearing ) { double const offset = unit == Degree ? 180.0 : M_PI; return offset + other.bearing( *this, unit, InitialBearing ); } qreal const delta = other.d->m_lon - d->m_lon; double const bearing = atan2( sin ( delta ) * cos ( other.d->m_lat ), cos( d->m_lat ) * sin( other.d->m_lat ) - sin( d->m_lat ) * cos( other.d->m_lat ) * cos ( delta ) ); return unit == Radian ? bearing : bearing * RAD2DEG; } GeoDataCoordinates GeoDataCoordinates::moveByBearing( qreal bearing, qreal distance ) const { qreal newLat = asin( sin(d->m_lat) * cos(distance) + cos(d->m_lat) * sin(distance) * cos(bearing) ); qreal newLon = d->m_lon + atan2( sin(bearing) * sin(distance) * cos(d->m_lat), cos(distance) - sin(d->m_lat) * sin(newLat) ); return GeoDataCoordinates( newLon, newLat ); } const Quaternion& GeoDataCoordinates::quaternion() const { if (d->m_q == 0) { d->m_q = new Quaternion(); *d->m_q = Quaternion::fromSpherical( d->m_lon , d->m_lat ); } return *d->m_q; } GeoDataCoordinates GeoDataCoordinates::interpolate( const GeoDataCoordinates &target, double t_ ) const { double const t = qBound( 0.0, t_, 1.0 ); Quaternion const quat = Quaternion::slerp( quaternion(), target.quaternion(), t ); qreal lon, lat; quat.getSpherical( lon, lat ); double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude; return GeoDataCoordinates( lon, lat, alt ); } GeoDataCoordinates GeoDataCoordinates::interpolate( const GeoDataCoordinates &before, const GeoDataCoordinates &target, const GeoDataCoordinates &after, double t_ ) const { double const t = qBound( 0.0, t_, 1.0 ); Quaternion const b1 = GeoDataCoordinatesPrivate::basePoint( before.quaternion(), quaternion(), target.quaternion() ); Quaternion const a2 = GeoDataCoordinatesPrivate::basePoint( quaternion(), target.quaternion(), after.quaternion() ); Quaternion const a = Quaternion::slerp( quaternion(), target.quaternion(), t ); Quaternion const b = Quaternion::slerp( b1, a2, t ); Quaternion c = Quaternion::slerp( a, b, 2 * t * (1.0-t) ); qreal lon, lat; c.getSpherical( lon, lat ); // @todo spline interpolation of altitude? double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude; return GeoDataCoordinates( lon, lat, alt ); } bool GeoDataCoordinates::isPole( Pole pole ) const { // Evaluate the most likely case first: // The case where we haven't hit the pole and where our latitude is normalized // to the range of 90 deg S ... 90 deg N if ( fabs( (qreal) 2.0 * d->m_lat ) < M_PI ) { return false; } else { if ( fabs( (qreal) 2.0 * d->m_lat ) == M_PI ) { // Ok, we have hit a pole. Now let's check whether it's the one we've asked for: if ( pole == AnyPole ){ return true; } else { if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) { return true; } if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) { return true; } return false; } } // else { // FIXME: Should we just normalize latitude and longitude and be done? // While this might work well for persistent data it would create some // possible overhead for temporary data, so this needs careful thinking. mDebug() << "GeoDataCoordinates not normalized!"; // Only as a last resort we cover the unlikely case where // the latitude is not normalized to the range of // 90 deg S ... 90 deg N if ( fabs( (qreal) 2.0 * normalizeLat( d->m_lat ) ) < M_PI ) { return false; } else { // Ok, we have hit a pole. Now let's check whether it's the one we've asked for: if ( pole == AnyPole ){ return true; } else { if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) { return true; } if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) { return true; } return false; } } } } } GeoDataCoordinates& GeoDataCoordinates::operator=( const GeoDataCoordinates &other ) { qAtomicAssign(d, other.d); return *this; } void GeoDataCoordinates::pack( QDataStream& stream ) const { stream << d->m_lon; stream << d->m_lat; stream << d->m_altitude; } void GeoDataCoordinates::unpack( QDataStream& stream ) { // call detach even though it shouldn't be needed - one never knows detach(); stream >> d->m_lon; stream >> d->m_lat; stream >> d->m_altitude; } Quaternion GeoDataCoordinatesPrivate::basePoint( const Quaternion &q1, const Quaternion &q2, const Quaternion &q3 ) { Quaternion const a = (q2.inverse() * q3).log(); Quaternion const b = (q2.inverse() * q1).log(); return q2 * ((a+b)*-0.25).exp(); } qreal GeoDataCoordinatesPrivate::arcLengthOfMeridian( qreal phi ) { // Precalculate n qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis) / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis); // Precalculate alpha qreal const alpha = ( (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0) * (1.0 + (qPow (n, 2.0) / 4.0) + (qPow (n, 4.0) / 64.0) ); // Precalculate beta qreal const beta = (-3.0 * n / 2.0) + (9.0 * qPow (n, 3.0) / 16.0) + (-3.0 * qPow (n, 5.0) / 32.0); // Precalculate gamma qreal const gamma = (15.0 * qPow (n, 2.0) / 16.0) + (-15.0 * qPow (n, 4.0) / 32.0); // Precalculate delta qreal const delta = (-35.0 * qPow (n, 3.0) / 48.0) + (105.0 * qPow (n, 5.0) / 256.0); // Precalculate epsilon qreal const epsilon = (315.0 * qPow (n, 4.0) / 512.0); // Now calculate the sum of the series and return qreal const result = alpha * (phi + (beta * qSin (2.0 * phi)) + (gamma * qSin (4.0 * phi)) + (delta * qSin (6.0 * phi)) + (epsilon * qSin (8.0 * phi))); return result; } qreal GeoDataCoordinatesPrivate::centralMeridianUTM( qreal zone ) { return DEG2RAD*(-183.0 + (zone * 6.0)); } qreal GeoDataCoordinatesPrivate::footpointLatitude( qreal northing ) { // Precalculate n (Eq. 10.18) qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis) / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis); // Precalculate alpha (Eq. 10.22) // (Same as alpha in Eq. 10.17) qreal const alpha = ((GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0) * (1 + (qPow (n, 2.0) / 4) + (qPow (n, 4.0) / 64)); // Precalculate y (Eq. 10.23) qreal const y = northing / alpha; // Precalculate beta (Eq. 10.22) qreal const beta = (3.0 * n / 2.0) + (-27.0 * qPow (n, 3.0) / 32.0) + (269.0 * qPow (n, 5.0) / 512.0); // Precalculate gamma (Eq. 10.22) qreal const gamma = (21.0 * qPow (n, 2.0) / 16.0) + (-55.0 * qPow (n, 4.0) / 32.0); // Precalculate delta (Eq. 10.22) qreal const delta = (151.0 * qPow (n, 3.0) / 96.0) + (-417.0 * qPow (n, 5.0) / 128.0); // Precalculate epsilon (Eq. 10.22) qreal const epsilon = (1097.0 * qPow (n, 4.0) / 512.0); // Now calculate the sum of the series (Eq. 10.21) qreal const result = y + (beta * qSin (2.0 * y)) + (gamma * qSin (4.0 * y)) + (delta * qSin (6.0 * y)) + (epsilon * qSin (8.0 * y)); return result; } QPointF GeoDataCoordinatesPrivate::mapLonLatToXY( qreal lambda, qreal phi, qreal lambda0 ) { // Equation (10.15) // Precalculate second numerical eccentricity qreal const ep2 = (qPow (GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) - qPow (GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0)) / qPow (GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0); // Precalculate the square of nu, just an auxiliar quantity qreal const nu2 = ep2 * qPow (qCos(phi), 2.0); // Precalculate the radius of curvature in prime vertical qreal const N = qPow (GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) / (GeoDataCoordinatesPrivate::sm_semiMinorAxis * qSqrt (1 + nu2)); // Precalculate the tangent of phi and its square qreal const t = qTan (phi); qreal const t2 = t * t; // Precalculate longitude difference qreal const l = lambda - lambda0; /* * Precalculate coefficients for l**n in the equations below * so a normal human being can read the expressions for easting * and northing * -- l**1 and l**2 have coefficients of 1.0 * * The actual used coefficients starts at coef[1], just to * follow the meaningful nomenclature in equation 10.15 * (coef[n] corresponds to qPow(l,n) factor) */ QVector coef(9); coef[0] = coef[1] = coef[2] = 1.0; coef[3] = 1.0 - t2 + nu2; coef[4] = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2); coef[5] = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2 - 58.0 * t2 * nu2; coef[6] = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2 - 330.0 * t2 * nu2; coef[7] = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2); coef[8] = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2); // Calculate easting (x) qreal easting = N * qCos(phi) * coef[1] * l + (N / 6.0 * qPow (qCos(phi), 3.0) * coef[3] * qPow (l, 3.0)) + (N / 120.0 * qPow (qCos(phi), 5.0) * coef[5] * qPow (l, 5.0)) + (N / 5040.0 * qPow (qCos(phi), 7.0) * coef[7] * qPow (l, 7.0)); // Calculate northing (y) qreal northing = arcLengthOfMeridian (phi) + (t / 2.0 * N * qPow (qCos(phi), 2.0) * coef[2] * qPow (l, 2.0)) + (t / 24.0 * N * qPow (qCos(phi), 4.0) * coef[4] * qPow (l, 4.0)) + (t / 720.0 * N * qPow (qCos(phi), 6.0) * coef[6] * qPow (l, 6.0)) + (t / 40320.0 * N * qPow (qCos(phi), 8.0) * coef[8] * qPow (l, 8.0)); return QPointF(easting, northing); } int GeoDataCoordinatesPrivate::lonLatToZone( qreal lon, qreal lat ){ // Converts lon and lat to degrees qreal lonDeg = lon * RAD2DEG; qreal latDeg = lat * RAD2DEG; /* Round the value of the longitude when the distance to the nearest integer * is less than 0.0000001. This avoids fuzzy values such as -114.0000000001, which * can produce a misbehaviour when calculating the zone associated at the borders * of the zone intervals (for example, the interval [-114, -108[ is associated with * zone number 12; if the following rounding is not done, the value returned by * lonLatToZone(114,0) is 11 instead of 12, as the function actually receives * -114.0000000001, which is in the interval [-120,-114[, associated to zone 11 */ qreal precision = 0.0000001; if ( qAbs(lonDeg - qFloor(lonDeg)) < precision || qAbs(lonDeg - qCeil(lonDeg)) < precision ){ lonDeg = qRound(lonDeg); } // There is no numbering associated to the poles, special value 0 is returned. if ( latDeg < -80 || latDeg > 84 ) { return 0; } // Obtains the zone number handling all the so called "exceptions" // See problem: http://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system#Exceptions // See solution: http://gis.stackexchange.com/questions/13291/computing-utm-zone-from-lat-long-point // General int zoneNumber = static_cast( (lonDeg+180) / 6.0 ) + 1; // Southwest Norway if ( latDeg >= 56 && latDeg < 64 && lonDeg >= 3 && lonDeg < 12 ) { zoneNumber = 32; } // Svalbard if ( latDeg >= 72 && latDeg < 84 ) { if ( lonDeg >= 0 && lonDeg < 9 ) { zoneNumber = 31; } else if ( lonDeg >= 9 && lonDeg < 21 ) { zoneNumber = 33; } else if ( lonDeg >= 21 && lonDeg < 33 ) { zoneNumber = 35; } else if ( lonDeg >= 33 && lonDeg < 42 ) { zoneNumber = 37; } } return zoneNumber; } qreal GeoDataCoordinatesPrivate::lonLatToEasting( qreal lon, qreal lat ){ int zoneNumber = lonLatToZone( lon, lat ); if ( zoneNumber == 0 ){ qreal lonDeg = lon * RAD2DEG; zoneNumber = static_cast( (lonDeg+180) / 6.0 ) + 1; } QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) ); // Adjust easting and northing for UTM system qreal easting = coordinates.x() * GeoDataCoordinatesPrivate::sm_utmScaleFactor + 500000.0; return easting; } QString GeoDataCoordinatesPrivate::lonLatToLatitudeBand( qreal lon, qreal lat ){ // Obtains the latitude bands handling all the so called "exceptions" // Converts lon and lat to degrees qreal lonDeg = lon * RAD2DEG; qreal latDeg = lat * RAD2DEG; // Regular latitude bands between 80 S and 80 N (that is, between 10 and 170 in the [0,180] interval) int bandLetterIndex = 24; //Avoids "may be used uninitialized" warning if ( latDeg < -80 ) { // South pole (A for zones 1-30, B for zones 31-60) bandLetterIndex = ( (lonDeg+180) < 6*31 ) ? 0 : 1; } else if ( latDeg >= -80 && latDeg <= 80 ) { // General (+2 because the general lettering starts in C) bandLetterIndex = static_cast( (latDeg+80.0) / 8.0 ) + 2; } else if ( latDeg >= 80 && latDeg < 84 ) { // Band X is extended 4 more degrees bandLetterIndex = 21; } else if ( latDeg >= 84 ) { // North pole (Y for zones 1-30, Z for zones 31-60) bandLetterIndex = ((lonDeg+180) < 6*31) ? 22 : 23; } return QString( "ABCDEFGHJKLMNPQRSTUVWXYZ?" ).at( bandLetterIndex ); } qreal GeoDataCoordinatesPrivate::lonLatToNorthing( qreal lon, qreal lat ){ int zoneNumber = lonLatToZone( lon, lat ); if ( zoneNumber == 0 ){ qreal lonDeg = lon * RAD2DEG; zoneNumber = static_cast( (lonDeg+180) / 6.0 ) + 1; } QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) ); qreal northing = coordinates.y() * GeoDataCoordinatesPrivate::sm_utmScaleFactor; if ( northing < 0.0 ) { northing += 10000000.0; } return northing; } uint qHash(const GeoDataCoordinates &coordinates) { QString lon, lat, alt; lon.setNum(coordinates.longitude(), 'f', 10); lat.setNum(coordinates.latitude(), 'f', 10); alt.setNum(coordinates.altitude(), 'f', 3); return qHash(lon % lat % alt); } } diff --git a/tests/TestGeoDataCoordinates.cpp b/tests/TestGeoDataCoordinates.cpp index a07d05bf7..d40bda2a8 100644 --- a/tests/TestGeoDataCoordinates.cpp +++ b/tests/TestGeoDataCoordinates.cpp @@ -1,1872 +1,1886 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // Copyright 2010 Matias Kallio // Copyright 2011 Friedrich W. H. Kossebau // Copyright 2012 Bernhard Beschow #include "MarbleGlobal.h" #include "MarbleWidget.h" #include "AbstractFloatItem.h" #include "GeoDataCoordinates.h" #include "TestUtils.h" #include #include #include #include #include using namespace Marble; class TestGeoDataCoordinates : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testConstruction(); void testSet_Degree(); void testSet_Radian(); void testSetLongitude_Degree(); void testSetLongitude_Radian(); void testSetLatitude_Degree(); void testSetLatitude_Radian(); void testAltitude(); void testOperatorAssignment(); void testDetail(); void testIsPole_data(); void testIsPole(); void testNotation(); void testNormalizeLat_data(); void testNormalizeLat(); void testNormalizeLon_data(); void testNormalizeLon(); void testNormalize_data(); void testNormalize(); // void testFromStringDMS_data(); // void testFromStringDMS(); // void testFromStringDM_data(); // void testFromStringDM(); void testFromStringD_data(); void testFromStringD(); void testFromLocaleString_data(); void testFromLocaleString(); void testToString_Decimal_data(); void testToString_Decimal(); void testToString_DMS_data(); void testToString_DMS(); void testToString_DM_data(); void testToString_DM(); void testPack_data(); void testPack(); void testUTM_data(); void testUTM(); }; void TestGeoDataCoordinates::initTestCase() { QLocale::setDefault( QLocale::c() ); // needed for testing toString* conversions } /* * test constructors */ void TestGeoDataCoordinates::testConstruction() { GeoDataCoordinates invalid1; QVERIFY(!invalid1.isValid()); GeoDataCoordinates invalid2(invalid1); QVERIFY(!invalid2.isValid()); QVERIFY(!invalid1.isValid()); QCOMPARE(invalid1, invalid2); const qreal lon = 164.77; const qreal lat = 55.9; const qreal alt = 400.003; GeoDataCoordinates coordinates3(lon, lat, alt, GeoDataCoordinates::Degree); QVERIFY(coordinates3.isValid()); QCOMPARE(coordinates3, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); QVERIFY(coordinates3 != invalid1); QVERIFY(coordinates3 != invalid2); QCOMPARE(coordinates3.longitude(GeoDataCoordinates::Degree), lon); QCOMPARE(coordinates3.longitude(), lon*DEG2RAD); QCOMPARE(coordinates3.latitude(GeoDataCoordinates::Degree), lat); QCOMPARE(coordinates3.latitude(), lat*DEG2RAD); QCOMPARE(coordinates3.altitude(), alt); qreal myLongitude = 0; qreal myLatitude = 0; coordinates3.geoCoordinates(myLongitude, myLatitude, GeoDataCoordinates::Degree); QCOMPARE(myLongitude, lon); QCOMPARE(myLatitude, lat); myLongitude = 0; myLatitude = 0; coordinates3.geoCoordinates(myLongitude, myLatitude); QCOMPARE(myLongitude, lon*DEG2RAD); QCOMPARE(myLatitude, lat*DEG2RAD); GeoDataCoordinates coordinates4(lon*DEG2RAD, lat*DEG2RAD, alt); QVERIFY(coordinates4.isValid()); QCOMPARE(coordinates4, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); QCOMPARE(coordinates4, coordinates3); QVERIFY(coordinates4 != invalid1); QVERIFY(coordinates4 != invalid2); GeoDataCoordinates coordinates5(coordinates3); QVERIFY(coordinates5.isValid()); QCOMPARE(coordinates5, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); QCOMPARE(coordinates5, coordinates3); QCOMPARE(coordinates5, coordinates4); QVERIFY(coordinates5 != invalid1); QVERIFY(coordinates5 != invalid2); GeoDataCoordinates coordinates6(invalid1.longitude(), invalid1.latitude(), invalid1.altitude(), GeoDataCoordinates::Radian, invalid1.detail()); QVERIFY(coordinates6.isValid()); // it should be valid, even though QCOMPARE(coordinates6, invalid1); // it is equal to an invalid one } /* * test setting coordinates in degree */ void TestGeoDataCoordinates::testSet_Degree() { const qreal lon = 345.8; const qreal lat = 70.3; const qreal alt = 1000.9; GeoDataCoordinates coordinates1; // invalid coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.set(0, 0, 0, GeoDataCoordinates::Degree); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0, GeoDataCoordinates::Degree)); } /* * test setting coordinates in radian */ void TestGeoDataCoordinates::testSet_Radian() { const qreal lon = 1.3; const qreal lat = 0.7; const qreal alt = 6886.44; GeoDataCoordinates coordinates1; // invalid coordinates1.set(lon, lat, alt); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.set(0, 0, 0); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0)); } /* * test setLongitude() in degree */ void TestGeoDataCoordinates::testSetLongitude_Degree() { const qreal lon = 143.8; GeoDataCoordinates coordinates1; // invalid coordinates1.setLongitude(lon, GeoDataCoordinates::Degree); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.setLongitude(0, GeoDataCoordinates::Degree); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(lon, 0, 0, GeoDataCoordinates::Degree)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0, GeoDataCoordinates::Degree)); } /* * test setLongitude() in radian */ void TestGeoDataCoordinates::testSetLongitude_Radian() { const qreal lon = 2.5; GeoDataCoordinates coordinates1; // invalid coordinates1.setLongitude(lon); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.setLongitude(0); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(lon, 0)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0)); } /* * test setLatitude() and latitude() in degree */ void TestGeoDataCoordinates::testSetLatitude_Degree() { const qreal lat = 75.0; GeoDataCoordinates coordinates1; // invalid coordinates1.setLatitude(lat, GeoDataCoordinates::Degree); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.setLatitude(0, GeoDataCoordinates::Degree); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(0, lat, 0, GeoDataCoordinates::Degree)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0, GeoDataCoordinates::Degree)); } /* * test setLatitude() in radian */ void TestGeoDataCoordinates::testSetLatitude_Radian() { const qreal lat = 1.2; GeoDataCoordinates coordinates1; // invalid coordinates1.setLatitude(lat); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.setLatitude(0); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(0, lat)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0)); } /* * test setAltitude() */ void TestGeoDataCoordinates::testAltitude() { const qreal alt = 400; GeoDataCoordinates coordinates1; // invalid coordinates1.setAltitude(alt); QVERIFY(coordinates1.isValid()); GeoDataCoordinates coordinates2(coordinates1); coordinates2.setAltitude(0); QVERIFY(coordinates2.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(0, 0, alt)); QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0)); } void TestGeoDataCoordinates::testOperatorAssignment() { const qreal lon = 123.4; const qreal lat = 56.7; const qreal alt = 890.1; const GeoDataCoordinates coordinates1(lon, lat, alt, GeoDataCoordinates::Degree); const GeoDataCoordinates coordinates2(0, 0, 0); GeoDataCoordinates coordinates3; // invalid coordinates3 = coordinates1; QVERIFY(coordinates3.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); // stays unmodified QCOMPARE(coordinates3, coordinates1); coordinates3 = GeoDataCoordinates(); QVERIFY(!coordinates3.isValid()); GeoDataCoordinates coordinates4(coordinates1); coordinates4 = coordinates2; QVERIFY(coordinates4.isValid()); QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); // stays unmodified QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0)); // stays unmodified QCOMPARE(coordinates4, coordinates2); } /* * test setDetail() and detail() */ void TestGeoDataCoordinates::testDetail() { const int detailnumber = 15; GeoDataCoordinates coordinates1; coordinates1.setDetail(detailnumber); GeoDataCoordinates coordinates2(coordinates1); coordinates2.setDetail(0); QCOMPARE(coordinates1.detail(), detailnumber); } /* * test setDefaultNotation() and defaultNotation */ void TestGeoDataCoordinates::testNotation() { GeoDataCoordinates::setDefaultNotation(GeoDataCoordinates::Decimal); QVERIFY(GeoDataCoordinates::defaultNotation() == GeoDataCoordinates::Decimal); GeoDataCoordinates::setDefaultNotation(GeoDataCoordinates::DMS); QVERIFY(GeoDataCoordinates::defaultNotation() == GeoDataCoordinates::DMS); } /* * test data for testIsPole() */ void TestGeoDataCoordinates::testIsPole_data() { QTest::addColumn("lon"); QTest::addColumn("lat"); QTest::addColumn("alt"); QTest::addColumn("pole"); QTest::newRow("false") << qreal(50.0) << qreal(50.0) << qreal(0.0) << "false_pole"; QTest::newRow("south") << qreal(0.0) << qreal(-90.0) << qreal(0.0) << "south_pole"; QTest::newRow("north") << qreal(0.0) << qreal(90.0) << qreal(0.0) << "north_pole"; } /* * Test isPole-method */ void TestGeoDataCoordinates::testIsPole() { QFETCH(qreal, lon); QFETCH(qreal, lat); QFETCH(qreal, alt); QFETCH(QString, pole); GeoDataCoordinates coordinates1; if(pole == "false_pole") { coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree); QVERIFY(coordinates1.isPole() == false); } else if(pole == "south_pole") { coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree); QVERIFY(coordinates1.isPole(SouthPole)); } else if(pole == "north_pole") { coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree); QVERIFY(coordinates1.isPole(NorthPole)); } } void TestGeoDataCoordinates::testNormalizeLat_data() { QTest::addColumn( "latRadian" ); QTest::newRow( "north pole" ) << qreal(M_PI / 2); QTest::newRow( "south pole" ) << qreal(- M_PI / 2); QTest::newRow( "somewhere" ) << qreal(1.0); } void TestGeoDataCoordinates::testNormalizeLat() { QFETCH( qreal, latRadian ); qreal latDegree = RAD2DEG * latRadian; for ( int i = 1; i < 10; ++i ) { if ( ( i % 2 ) == 0 ) { QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI, GeoDataCoordinates::Radian ), latRadian ); QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI ), latRadian ); QCOMPARE( GeoDataCoordinates::normalizeLat( latDegree + i * 180, GeoDataCoordinates::Degree ), latDegree ); } else { QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI, GeoDataCoordinates::Radian ), -latRadian ); QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI ), -latRadian ); QCOMPARE( GeoDataCoordinates::normalizeLat( latDegree + i * 180, GeoDataCoordinates::Degree ), -latDegree ); } } } void TestGeoDataCoordinates::testNormalizeLon_data() { QTest::addColumn( "lonRadian" ); QTest::newRow( "half east" ) << qreal(M_PI / 2); QTest::newRow( "half west" ) << qreal(- M_PI / 2); QTest::newRow( "somewhere" ) << qreal(1.0); QTest::newRow( "date line east" ) << qreal(M_PI); QTest::newRow( "date line west" ) << - qreal(M_PI); } void TestGeoDataCoordinates::testNormalizeLon() { QFETCH( qreal, lonRadian ); qreal lonDegree = RAD2DEG * lonRadian; for ( int i = 1; i < 10; ++i ) { if ( lonRadian == qreal(M_PI) || lonRadian == qreal(-M_PI) ) { int lonRadianLarge = qRound( lonRadian * 1000 ); int lonDegreeLarge = qRound( lonDegree * 1000 ); if ( qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI ) * 1000 ) != lonRadianLarge && qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI ) * 1000 ) != -lonRadianLarge ) { QFAIL( "Error at M_PI/-M_PI" ); } if ( qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI, GeoDataCoordinates::Radian ) * 1000 ) != lonRadianLarge && qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI, GeoDataCoordinates::Radian ) * 1000 ) != -lonRadianLarge ) { QFAIL( "Error at M_PI/-M_PI" ); } if ( qRound( GeoDataCoordinates::normalizeLon( lonDegree + i * 360, GeoDataCoordinates::Degree ) * 1000 ) != lonDegreeLarge && qRound( GeoDataCoordinates::normalizeLon( lonDegree + i * 360, GeoDataCoordinates::Degree ) * 1000 ) != -lonDegreeLarge ) { QFAIL( "Error at M_PI/-M_PI" ); } } else { QCOMPARE( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI, GeoDataCoordinates::Radian ), lonRadian ); QCOMPARE( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI ), lonRadian ); QCOMPARE( GeoDataCoordinates::normalizeLon( lonDegree + i * 360, GeoDataCoordinates::Degree ), lonDegree ); } } } /* * test data for testNormalize() */ void TestGeoDataCoordinates::testNormalize_data() { QTest::addColumn("lon"); QTest::addColumn("lat"); QTest::addColumn("unit"); QTest::newRow("deg") << qreal(200.0) << qreal(130.0) << "degree"; QTest::newRow("rad") << qreal(3.6) << qreal(2.7) << "radian"; } /* * test normalizeLon(), normalizeLat() and normalizeLonLat() */ void TestGeoDataCoordinates::testNormalize() { QFETCH(qreal, lon); QFETCH(qreal, lat); QFETCH(QString, unit); if(unit == "degree") { QCOMPARE(GeoDataCoordinates::normalizeLon(lon, GeoDataCoordinates::Degree), qreal(-160)); QCOMPARE(GeoDataCoordinates::normalizeLat(lat, GeoDataCoordinates::Degree), qreal(50)); qreal normalized_lon = lon; qreal normalized_lat = lat; GeoDataCoordinates::normalizeLonLat( normalized_lon, normalized_lat, GeoDataCoordinates::Degree); QCOMPARE(normalized_lon, qreal(20)); QCOMPARE(normalized_lat, qreal(50)); } else if (unit == "radian") { // Compare up to three decimals qreal value = GeoDataCoordinates::normalizeLon(lon, GeoDataCoordinates::Radian); QCOMPARE(ceil(value * 1000) / 1000, qreal(-2.683)); value = GeoDataCoordinates::normalizeLat(lat, GeoDataCoordinates::Radian); QCOMPARE(ceil(value * 1000) / 1000, qreal(0.442)); qreal normalized_lon = lon; qreal normalized_lat = lat; GeoDataCoordinates::normalizeLonLat( normalized_lon, normalized_lat, GeoDataCoordinates::Radian); QCOMPARE(ceil(normalized_lon * 1000) / 1000, qreal(0.459)); QCOMPARE(ceil(normalized_lat * 1000) / 1000, qreal(0.442)); } } enum SignType {NoSign, PositiveSign, NegativeSign}; enum SphereType {PosSphere, NegSphere}; enum UnitsType {NoUnits, WithUnits}; enum SpacesType {NoSpaces, WithSpaces}; +enum LocaleType {CLocale, SystemLocale}; //static QString //createDegreeString(SignType signType, // int degreeValue, int minutesValue, qreal secondsValue, // UnitsType unitsType, SpacesType spacesType) //{ // QString string; // // add degree // if (signType != NoSign) string.append(QLatin1Char(signType==PositiveSign?'+':'-')); // string.append(QString::number(degreeValue)); // if (unitsType == WithUnits) string.append(QChar(0xb0)); // // add minutes // string.append(QLatin1Char(' ') + QString::number(minutesValue)); // if (unitsType == WithUnits) string.append(QLatin1Char('\'')); // // add seconds // string.append(QString::fromLatin1(" %L1").arg(secondsValue, 0, 'f', 10)); // if (unitsType == WithUnits) string.append(QLatin1Char('"')); // if (spacesType == WithSpaces) string.append(QLatin1Char(' ')); // return string; //} //static QString //createDegreeString(SignType signType, // int degreeValue, qreal minutesValue, // UnitsType unitsType, SpacesType spacesType) //{ // QString string; // // add degree // if (signType != NoSign) string.append(QLatin1Char(signType==PositiveSign?'+':'-')); // string.append(QString::number(degreeValue)); // if (unitsType == WithUnits) string.append(QChar(0xb0)); // // add minutes // string.append(QString::fromLatin1(" %L1").arg(minutesValue, 0, 'f', 10)); // if (unitsType == WithUnits) string.append(QLatin1Char('\'')); // if (spacesType == WithSpaces) string.append(QLatin1Char(' ')); // return string; //} static QString createDegreeString(SignType signType, qreal degreeValue, + LocaleType locale, UnitsType unitsType, SpacesType spacesType) { QString string; // add degree if (signType != NoSign) string.append(QLatin1Char(signType==PositiveSign?'+':'-')); - string.append(QString::fromLatin1("%L1").arg(degreeValue, 0, 'f', 10)); + if (locale == CLocale) { + string.append(QString::number(degreeValue, 'f', 10)); + } else { + string.append(QLocale::system().toString(degreeValue, 'f', 10)); + } if (unitsType == WithUnits) string.append(QChar(0xb0)); if (spacesType == WithSpaces) string.append(QLatin1Char(' ')); return string; } /* * test data for testStringDMS() */ //void TestGeoDataCoordinates::testFromStringDMS_data() //{ // QTest::addColumn("string"); // QTest::addColumn("lon"); // QTest::addColumn("lat"); // const QVector signTypes = QVector() // << NoSign << PositiveSign << NegativeSign; // const QVector sphereTypes = QVector() // << PosSphere << NegSphere; // const QVector unitsTypes = QVector() // << NoUnits << WithUnits; // const QVector spacesTypes = QVector() // << NoSpaces << WithSpaces; // const QVector degreeSamples = QVector() // << 0 << 140 << 180; // const QVector minutesSamples = QVector() // << 0 << 23 << 59; // const QVector secondsSamples = QVector() // << 0.0 << 3.14159 << 59.9999999; // foreach(const UnitsType unitsType, unitsTypes) { // foreach(const SpacesType spacesType, spacesTypes) { // // lon // foreach(const SphereType lonSphere, sphereTypes) { // foreach(const SignType lonSignType, signTypes) { // const bool lonIsPositive = // (lonSphere==PosSphere && lonSignType!=NegativeSign) || // (lonSphere==NegSphere && lonSignType==NegativeSign); // foreach(const uint lonDegree, degreeSamples) { // foreach(const uint lonMinutes, minutesSamples) { // if(lonDegree == 180 && lonMinutes != 0) continue; // foreach(const qreal lonSeconds, secondsSamples) { // if(lonDegree == 180 && lonSeconds != 0.0) continue; // // lat // foreach(const SphereType latSphere, sphereTypes) { // foreach(const SignType latSignType, signTypes) { // const bool latIsPositive = // (latSphere==PosSphere && latSignType!=NegativeSign) || // (latSphere==NegSphere && latSignType==NegativeSign); // foreach(const uint latDegree, degreeSamples) { // foreach(const uint latMinutes, minutesSamples) { // if(latDegree == 180 && latMinutes != 0) continue; // foreach(const qreal latSeconds, secondsSamples) { // if(latDegree == 180 && latSeconds != 0.0) continue; // // actual construction // // Create lon & lat values // qreal lon = (qreal)lonDegree + lonMinutes*MIN2HOUR + lonSeconds*SEC2HOUR; // if( ! lonIsPositive ) // lon *= -1; // qreal lat = (qreal)latDegree + latMinutes*MIN2HOUR + latSeconds*SEC2HOUR; // if( ! latIsPositive ) // lat *= -1; // // Create string // QString string; // string.append(createDegreeString(latSignType, // latDegree, latMinutes, latSeconds, // unitsType, spacesType)); // string.append(QLatin1Char(latSphere==PosSphere?'N':'S')); // string.append(QLatin1Char(' ')); // string.append(createDegreeString(lonSignType, // lonDegree, lonMinutes, lonSeconds, // unitsType, spacesType)); // string.append(QLatin1Char(lonSphere==PosSphere?'E':'W')); // // Create row title // QString rowTitle; // rowTitle.append(QLatin1String(spacesType==WithSpaces?"spaced dir":"unspaced dir")) // .append(QLatin1String(unitsType==WithUnits?"|units":"|no units")) // .append(QLatin1String("|lon:")) // .append(QLatin1Char(lonIsPositive?'+':'-')) // .append(QString::number(lonDegree)+QChar(0xb0)) // .append(QString::number(lonMinutes)+QLatin1Char('\'')) // .append(QString::fromLatin1("%L1").arg(lonSeconds, 0, 'f', 10)+QLatin1Char('"')) // .append(QLatin1Char(lonSphere==PosSphere?'P':'N')) // .append(QLatin1String("|lat:")) // .append(QLatin1Char(latIsPositive?'+':'-')) // .append(QString::number(latDegree)+QChar(0xb0)) // .append(QString::number(latMinutes)+QLatin1Char('\'')) // .append(QString::fromLatin1("%L1").arg(latSeconds, 0, 'f', 10)+QLatin1Char('"')) // .append(QLatin1Char(latSphere==PosSphere?'P':'N')) // .append(QLatin1Char('|')).append(string).append(QLatin1Char('|')); // QTest::newRow(rowTitle.toLatin1()) // << string // << lon // << lat; // } // } // } // } // } // } // } // } // } // } // } // } //} ///* // * test fromString() with DMS notation // */ //void TestGeoDataCoordinates::testFromStringDMS() //{ // QFETCH(QString, string); // QFETCH(qreal, lon); // QFETCH(qreal, lat); // bool succeeded = false; // const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded); // if(! succeeded) // qWarning() << "Could not parse"<("string"); // QTest::addColumn("lon"); // QTest::addColumn("lat"); // const QVector signTypes = QVector() // << NoSign << PositiveSign << NegativeSign; // const QVector sphereTypes = QVector() // << PosSphere << NegSphere; // const QVector unitsTypes = QVector() // << NoUnits << WithUnits; // const QVector spacesTypes = QVector() // << NoSpaces << WithSpaces; // const QVector degreeSamples = QVector() // << 0 << 140 << 180; // const QVector minutesSamples = QVector() // << 0.0 << 3.14159 << 59.9999999; // foreach(const UnitsType unitsType, unitsTypes) { // foreach(const SpacesType spacesType, spacesTypes) { // // lon // foreach(const SphereType lonSphere, sphereTypes) { // foreach(const SignType lonSignType, signTypes) { // const bool lonIsPositive = // (lonSphere==PosSphere && lonSignType!=NegativeSign) || // (lonSphere==NegSphere && lonSignType==NegativeSign); // foreach(const uint lonDegree, degreeSamples) { // foreach(const qreal lonMinutes, minutesSamples) { // if(lonDegree == 180 && lonMinutes != 0.0) continue; // // lat // foreach(const SphereType latSphere, sphereTypes) { // foreach(const SignType latSignType, signTypes) { // const bool latIsPositive = // (latSphere==PosSphere && latSignType!=NegativeSign) || // (latSphere==NegSphere && latSignType==NegativeSign); // foreach(const uint latDegree, degreeSamples) { // foreach(const qreal latMinutes, minutesSamples) { // if(latDegree == 180 && latMinutes != 0.0) continue; // // actual construction // // Create lon & lat values // qreal lon = (qreal)lonDegree + lonMinutes*MIN2HOUR; // if( ! lonIsPositive ) // lon *= -1; // qreal lat = (qreal)latDegree + latMinutes*MIN2HOUR; // if( ! latIsPositive ) // lat *= -1; // // Create string // QString string; // string.append(createDegreeString(latSignType, // latDegree, latMinutes, // unitsType, spacesType)); // string.append(QLatin1Char(latSphere==PosSphere?'N':'S')); // string.append(QLatin1Char(' ')); // string.append(createDegreeString(lonSignType, // lonDegree, lonMinutes, // unitsType, spacesType)); // string.append(QLatin1Char(lonSphere==PosSphere?'E':'W')); // // Create row title // QString rowTitle; // rowTitle.append(QLatin1String(spacesType==WithSpaces?"spaced dir":"unspaced dir")) // .append(QLatin1String(unitsType==WithUnits?"|units":"|no units")) // .append(QLatin1String("|lon:")) // .append(QLatin1Char(lonIsPositive?'+':'-')) // .append(QString::number(lonDegree)+QChar(0xb0)) // .append(QString::fromLatin1("%L1").arg(lonMinutes, 0, 'f', 10)+QLatin1Char('\'')) // .append(QLatin1Char(lonSphere==PosSphere?'P':'N')) // .append(QLatin1String("|lat:")) // .append(QLatin1Char(latIsPositive?'+':'-')) // .append(QString::number(latDegree)+QChar(0xb0)) // .append(QString::fromLatin1("%L1").arg(latMinutes, 0, 'f', 10)+QLatin1Char('\'')) // .append(QLatin1Char(latSphere==PosSphere?'P':'N')) // .append(QLatin1Char('|')).append(string).append(QLatin1Char('|')); // QTest::newRow(rowTitle.toLatin1()) // << string // << lon // << lat; // } // } // } // } // } // } // } // } // } // } //} ///* // * test fromString() with DM notation // */ //void TestGeoDataCoordinates::testFromStringDM() //{ // QFETCH(QString, string); // QFETCH(qreal, lon); // QFETCH(qreal, lat); // bool succeeded = false; // const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded); // if(! succeeded) // qWarning() << "Could not parse"<("string"); QTest::addColumn("lon"); QTest::addColumn("lat"); const QVector signTypes = QVector() << NoSign << PositiveSign << NegativeSign; const QVector sphereTypes = QVector() << PosSphere << NegSphere; const QVector unitsTypes = QVector() << NoUnits << WithUnits; const QVector spacesTypes = QVector() << NoSpaces << WithSpaces; + const QVector localeTypes = QVector() + << CLocale << SystemLocale; const QVector degreeSamples = QVector() << qreal(0.0) << qreal(3.14159) << qreal(180.0); foreach(const UnitsType unitsType, unitsTypes) { foreach(const SpacesType spacesType, spacesTypes) { // lon foreach(const SphereType lonSphere, sphereTypes) { foreach(const SignType lonSignType, signTypes) { const bool lonIsPositive = (lonSphere==PosSphere && lonSignType!=NegativeSign) || (lonSphere==NegSphere && lonSignType==NegativeSign); foreach(const qreal lonDegree, degreeSamples) { // lat foreach(const SphereType latSphere, sphereTypes) { foreach(const SignType latSignType, signTypes) { const bool latIsPositive = (latSphere==PosSphere && latSignType!=NegativeSign) || (latSphere==NegSphere && latSignType==NegativeSign); foreach(const qreal latDegree, degreeSamples) { + // locale + foreach(const LocaleType locale, localeTypes) { // actual construction // Create lon & lat values qreal lon = lonDegree; if (! lonIsPositive) lon *= -1; qreal lat = latDegree; if (! latIsPositive) lat *= -1; // Create string QString string; string.append(createDegreeString(latSignType, latDegree, + locale, unitsType, spacesType)); string.append(QLatin1Char(latSphere==PosSphere?'N':'S')); string.append(QLatin1Char(' ')); string.append(createDegreeString(lonSignType, lonDegree, + locale, unitsType, spacesType)); string.append(QLatin1Char(lonSphere==PosSphere?'E':'W')); // Create row title QString rowTitle; rowTitle.append(QLatin1String(spacesType==WithSpaces?"spaced dir":"unspaced dir")) .append(QLatin1String(unitsType==WithUnits?"|units":"|no units")) .append(QLatin1String("|lon:")) .append(QLatin1Char(lonIsPositive?'+':'-')) - .append(QString::fromLatin1("%L1").arg(lonDegree, 0, 'f', 10)+QChar(0xb0)) + .append(QString::number(lonDegree, 'f', 10)+QChar(0xb0)) .append(QLatin1Char(lonSphere==PosSphere?'P':'N')) .append(QLatin1String("|lat:")) .append(QLatin1Char(latIsPositive?'+':'-')) - .append(QString::fromLatin1("%L1").arg(latDegree, 0, 'f', 10)+QChar(0xb0)) + .append(QString::number(latDegree, 'f', 10)+QChar(0xb0)) .append(QLatin1Char(latSphere==PosSphere?'P':'N')) + .append(QLatin1Char('|')).append(QLatin1Char(locale==CLocale?'C':'L')) .append(QLatin1Char('|')).append(string).append(QLatin1Char('|')); QTest::newRow(rowTitle.toLatin1()) << string << lon << lat; } } } } } } } } + } QTest::newRow("scientific notation") << "0.0,1.0e-2" << qreal(1.0e-2) << qreal(0.0); QTest::newRow("scientific notation") << "-2.4E0 1.0e-18" << qreal(1e-18) << qreal(-2.4e0); QTest::newRow("scientific notation") << "1.14e-02;1.33e+01" << qreal(1.33e1) << qreal(1.14e-2); } /* * test fromString() with DM notation */ void TestGeoDataCoordinates::testFromStringD() { QFETCH(QString, string); QFETCH(qreal, lon); QFETCH(qreal, lat); bool succeeded = false; const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded); if(! succeeded) qWarning() << "Could not parse"<& _samples) : name(QString::fromUtf8(_name)) , degree(QString::fromUtf8(_degree)) , minutes(QString::fromUtf8(_minutes)) , seconds(QString::fromUtf8(_seconds)) , north(QString::fromUtf8(_north)) , south(QString::fromUtf8(_south)) , east(QString::fromUtf8(_east)) , west(QString::fromUtf8(_west)) , samples(_samples) {} QString name; QString degree; QString minutes; QString seconds; QString north; QString south; QString east; QString west; QVector samples; }; void TestGeoDataCoordinates::testFromLocaleString_data() { QTest::addColumn("degree"); QTest::addColumn("minutes"); QTest::addColumn("seconds"); QTest::addColumn("north"); QTest::addColumn("south"); QTest::addColumn("east"); QTest::addColumn("west"); QTest::addColumn("string"); QTest::addColumn("lon"); QTest::addColumn("lat"); const QVector languages = QVector() << Language( "English", "*", // degree "*", // minutes "*", // seconds "*", // north "*", // south "*", // east "*", // west QVector() << Sample( "London", "N051 30.150′ W000 07.234′", -0.12056666666666666921, 51.50249999999999772626) << Sample( "Ålgård", "N58.764828 E5.855483", 5.85548300000000043752, 58.76482800000000139562)) << Language( "Japanese", "度", // degree "分", // minutes "秒", // seconds "北緯", // north "南緯", // south "東経", // east "西経", // west QVector() << Sample( "London", "北緯51度30分28秒 西経0度07分41秒", -0.12805555555555556135, 51.50777777777777544088) << Sample( "Sydney", "南緯33度52分06秒 東経151度12分31秒", 151.20861111111111085847, -33.86833333333333229120)) << Language( "Korean", "도", // degree "분", // minutes "초", // seconds "북위", // north "남위", // south "동경", // east "서경", // west QVector() << Sample( "London", "북위 51도 30분 26초, 서경 0도 7분 39초", -0.12750000000000000222, 51.50722222222222512755) << Sample( "Sydney", "남위 33도 31분 56초, 동경 151도 12분 40초", 151.21111111111110858474, -33.53222222222222370647)) #if 0 << Language( "Galician", "", // degree "", // minutes "", // seconds "N", //north "S", // south "L|E", // east "O|W", // west QVector() << Sample( "Campamento", "36º10,67´N 5º24,29´W", -5.40483333333333337833, 36.17783333333333217752)) #endif << Language( "German", "*", // degree "*", // minutes "*", // seconds "N", //north "S", // south "O", // east "W", // west QVector() << Sample( "London", "51° 31′ N, 0° 7′ W", -0.11666666666666666852, 51.51666666666666571928)) << Language( "Greek", "", // degree "", // minutes "", // seconds "Β", // north "Ν", // south "Α", // east "Δ", // west QVector() << Sample( "Χαλκίδα", "38° 28′ Β 23° 36′ Α", 23.6, 38.46666666666666856)) << Language( "Dutch", "", // degree "", // minutes "", // seconds "N|NB", // north "Z|ZB", // south "O|OL", // east "W|WL", // west QVector() << Sample( "Amersfoort", "N 52° 8′ 32.14″ , E 5° 24′ 56.09″", 5.41558055555555561966, 52.14226111111111094942) #if 0 << Sample( "London", "51°30'00,55\" NB 0°07'34,45\" WL", -0.12623611111111110450, 51.50015277777777811252) << Sample( "Amsterdam", "52°22'12,78\" NB 4°53'42,60\" OL", 4.89516666666666644403, 52.37021666666666419587) << Sample( "Capetown", "33°55'29,52\" ZB 18°25'26,60\" OL", 18.42405555555555451974, -33.92486666666666650372) #endif ) << Language( "Polish", "", // degree "", // minutes "", // seconds "Pn.|Pn", // north "Płd.|Płd", // south "Wschod.|Wschod|Wsch.|Wsch|Ws.|Ws", // east "Zach.|Zach|Z", // west QVector() << Sample( "Warsaw", "52°13′56″Pn. 21°00′30″Ws.", 21.00833333333333285964, 52.23222222222221944321)) #if 0 << Language( "Esperanto", "", // degree "", // minutes "", // seconds "N", // north "S", // south "Or", // east "Ok", // west QVector() << Sample( "London", "52° 8′ 32,14″ N; 5° 24′ 56,09″ Or", 5.41558055555555561966, 52.14226111111111094942)) #endif << Language( "Norwegian", "", // degree "", // minutes "", // seconds "N", // north "S", // south "Ø", // east "V", // west QVector() << Sample( "London", "51° 30′ 25” N 0° 7′ 39” V", -0.12750000000000000222, 51.50694444444444286546) << Sample( "Ålgård", "58° 45′ 53.38″ N 5° 51′ 19.74″ Ø", 5.85548333333333292927, 58.76482777777777499750)) << Language( "Swedish", "", // degree "", // minutes "", // seconds "N", // north "S", // south "O", // east "V", // west QVector() << Sample( "London", "51°30′29″N 0°7′29″V", -0.12472222222222222043, 51.50805555555555770297) << Sample( "Sydney", "33°31′56″S 151°12′40″O", 151.21111111111110858474, -33.53222222222222370647)) << Language( "Icelandic", "", // degree "", // minutes "", // seconds "N", //north "S", // south "A", // east "V", // west //TODO: "breidd 51°30'26\" N, lengd 0°7'39\" V" // London QVector() << Sample( "Sydney", "33°31'56\" S, 151°12'40\" A", 151.21111111111110858474, -33.53222222222222370647)) << Language( "Turkish", "", // degree "", // minutes "", // seconds "K", // north "G", // south "D", // east "B", // west QVector() << Sample( "London", "51° 30′ 28″ K, 0° 7′ 41″ B", -0.12805555555555556135, 51.50777777777777544088)) << Language( "Spanish", // (incl. Latin America) "", // degree "", // minutes "", // seconds "N", // north "S", // south "E", // east "O|W", // west QVector() << Sample( "London", "51°30′25″N 00°07′39″O", -0.12750000000000000222, 51.50694444444444286546) << Sample( "Else", "52° 8′ 32.14″ N, 5° 24′ 56.09″ W", -5.41558055555555561966, 52.14226111111111094942) << Sample( "Bogotá", "4°35’53″N 74°4’33″O", -74.07583333333333541759, 4.59805555555555667269)) << Language( "French", "", // degree "", // minutes "", // seconds "N", // north "S", // south "E", // east "O", // west QVector() << Sample( "London", "51° 30′ 18″ N 0° 04′ 43″ O", -0.07861111111111110383, 51.50500000000000255795)) << Language( "Portuguese", // incl. Brazilian Portuguese "", // degree "", // minutes "", // seconds "N", // north "S", // south "E|L", // east "O", // west QVector() << Sample( "London", "52° 8′ 32.14″ N, 5° 24′ 56.09″ E", 5.41558055555555561966, 52.14226111111111094942)) << Language( "Arabic", "", // degree "", // minutes "", // seconds "شمال", // north "جنوب", // south "شرق", // east "غرب", // west QVector() << Sample( "Warsaw", "52°13′56″ شمال 21°00′30″ شرق", 21.00833333333333285964, 52.23222222222221944321)) << Language( "Russian", "", //"град", "градусов" // degree "", //"мин", "минут" // minutes "", //"сек", "секунд" // seconds "с. ш.", // north "ю. ш.", // south "в. д.", // east "з. д.", // west QVector() << Sample( "London", "51°30′26″ с. ш. 0°07′39″ з. д.", -0.12750000000000000222, 51.50722222222222512755)) << Language( "Ukrainian", "", // degree "", // minutes "", // seconds "пн. ш.", // north "пд. ш.", // south "сх. д.", // east "зх. д.", // west QVector() << Sample( "London", "51°30' пн. ш. 0°07' сх. д.", 0.11666666666666666852, 51.50000000000000000000) << Sample( "Sydney", "33°52'10'' пд. ш. 151°12'30'' сх. д.", 151.20833333333334280724, -33.86944444444444712872) << Sample( "Rio de Janeiro", "22°54'30'' пд. ш. 43°11'47'' зх. д.", -43.19638888888889027839, -22.90833333333333499127)) << Language( "Bulgarian", "", // degree "", // minutes "", // seconds "с. ш.", // north "ю. ш.", // south "и. д.", // east "и. д.", // west QVector() << Sample( "London", "51°30′26″ с. ш. 0°07′39″ и. д.", 0.12750000000000000222, 51.50722222222222512755)) << Language( "Czech", "", // degree "", // minutes "", // seconds "s. š.", // north "j. š.", // south "z. d.", // east "v. d.", // west QVector() << Sample( "London", "51°30′42″ s. š., 0°02′56″ z. d.", 0.04888888888888889145, 51.51166666666666316132) << Sample( "Sydney", "33° 52′ j. š., 151° 13′ v. d.", -151.21666666666669698316, -33.86666666666666714036)) << Language( "Hindi", "", // degree "", // minutes "", // seconds "उ", // north "द", // south "पू", // east "प", // west QVector() << Sample( "London", "51°30′25″उ 00°07′39″पू", 0.12750000000000000222, 51.50694444444444286546)) << Language( "Tamil", "", // degree "", // minutes "", // seconds "வ", // north "தெ", // south "கி", // east "மே", // west QVector() << Sample( "London", "51°30′25″ வ 00°07′39″ கி", 0.12750000000000000222, 51.50694444444444286546)) ; foreach( const Language& language, languages ) { foreach( const Sample& sample, language.samples ) { const QString rowTitle = language.name + QLatin1String("|") + sample.name + QLatin1String("|lon:") + QString::fromLatin1("%L1").arg(sample.lon, 0, 'f', 10) + QLatin1String("|lat:") + QString::fromLatin1("%L1").arg(sample.lat, 0, 'f', 10); QTest::newRow(rowTitle.toLatin1()) << language.degree << language.minutes << language.seconds << language.north << language.south << language.east << language.west << sample.string << sample.lon << sample.lat; } } } void TestGeoDataCoordinates::testFromLocaleString() { QFETCH(QString, degree); QFETCH(QString, minutes); QFETCH(QString, seconds); QFETCH(QString, north); QFETCH(QString, south); QFETCH(QString, east); QFETCH(QString, west); QFETCH(QString, string); QFETCH(qreal, lon); QFETCH(qreal, lat); FromStringRegExpTranslator translator(degree, minutes, seconds, north, south, east, west); QCoreApplication::installTranslator(&translator); bool succeeded = false; const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded); if(! succeeded) qWarning() << "Could not parse"<("lon"); QTest::addColumn("lat"); QTest::addColumn("precision"); QTest::addColumn("expected"); addRow() << qreal(150.0) << qreal(80.0) << 0 << QString::fromUtf8( " 150°E, 80°N" ); addRow() << qreal(150.0) << qreal(80.0) << 1 << QString::fromUtf8( "150.0°E, 80.0°N" ); addRow() << qreal(150.0) << qreal(80.0) << 2 << QString::fromUtf8( "150.00°E, 80.00°N" ); addRow() << qreal(150.0) << qreal(80.0) << 3 << QString::fromUtf8( "150.000°E, 80.000°N" ); addRow() << qreal(150.0) << qreal(80.0) << 4 << QString::fromUtf8( "150.0000°E, 80.0000°N" ); addRow() << qreal(150.0) << qreal(80.0) << 5 << QString::fromUtf8( "150.00000°E, 80.00000°N" ); addRow() << qreal(149.6) << qreal(79.6) << 0 << QString::fromUtf8( " 150°E, 80°N" ); addRow() << qreal(149.96) << qreal(79.96) << 0 << QString::fromUtf8( " 150°E, 80°N" ); addRow() << qreal(149.6) << qreal(79.6) << 1 << QString::fromUtf8( "149.6°E, 79.6°N" ); addRow() << qreal(149.96) << qreal(79.96) << 1 << QString::fromUtf8( "150.0°E, 80.0°N" ); addRow() << qreal(149.996) << qreal(79.996) << 1 << QString::fromUtf8( "150.0°E, 80.0°N" ); addRow() << qreal(149.96) << qreal(79.96) << 2 << QString::fromUtf8( "149.96°E, 79.96°N" ); addRow() << qreal(149.996) << qreal(79.996) << 2 << QString::fromUtf8( "150.00°E, 80.00°N" ); addRow() << qreal(149.9996) << qreal(79.9996) << 2 << QString::fromUtf8( "150.00°E, 80.00°N" ); addRow() << qreal(149.996) << qreal(79.996) << 3 << QString::fromUtf8( "149.996°E, 79.996°N" ); addRow() << qreal(149.9996) << qreal(79.9996) << 3 << QString::fromUtf8( "150.000°E, 80.000°N" ); addRow() << qreal(149.99996) << qreal(79.99996) << 3 << QString::fromUtf8( "150.000°E, 80.000°N" ); addRow() << qreal(149.9996) << qreal(79.9996) << 4 << QString::fromUtf8( "149.9996°E, 79.9996°N" ); addRow() << qreal(149.99996) << qreal(79.99996) << 4 << QString::fromUtf8( "150.0000°E, 80.0000°N" ); addRow() << qreal(149.999996) << qreal(79.999996) << 4 << QString::fromUtf8( "150.0000°E, 80.0000°N" ); addRow() << qreal(149.99996) << qreal(79.99996) << 5 << QString::fromUtf8( "149.99996°E, 79.99996°N" ); addRow() << qreal(149.999996) << qreal(79.999996) << 5 << QString::fromUtf8( "150.00000°E, 80.00000°N" ); addRow() << qreal(149.9999996) << qreal(79.9999996) << 5 << QString::fromUtf8( "150.00000°E, 80.00000°N" ); addRow() << qreal(149.999996) << qreal(79.999996) << 6 << QString::fromUtf8( "149.999996°E, 79.999996°N" ); addRow() << qreal(149.9999996) << qreal(79.9999996) << 6 << QString::fromUtf8( "150.000000°E, 80.000000°N" ); addRow() << qreal(150.1) << qreal(80.1) << 0 << QString::fromUtf8( " 150°E, 80°N" ); addRow() << qreal(150.01) << qreal(80.01) << 0 << QString::fromUtf8( " 150°E, 80°N" ); addRow() << qreal(150.1) << qreal(80.1) << 1 << QString::fromUtf8( "150.1°E, 80.1°N" ); addRow() << qreal(150.01) << qreal(80.01) << 1 << QString::fromUtf8( "150.0°E, 80.0°N" ); addRow() << qreal(150.001) << qreal(80.001) << 1 << QString::fromUtf8( "150.0°E, 80.0°N" ); addRow() << qreal(150.01) << qreal(80.01) << 2 << QString::fromUtf8( "150.01°E, 80.01°N" ); addRow() << qreal(150.001) << qreal(80.001) << 2 << QString::fromUtf8( "150.00°E, 80.00°N" ); addRow() << qreal(150.0001) << qreal(80.0001) << 2 << QString::fromUtf8( "150.00°E, 80.00°N" ); addRow() << qreal(150.001) << qreal(80.001) << 3 << QString::fromUtf8( "150.001°E, 80.001°N" ); addRow() << qreal(150.0001) << qreal(80.0001) << 3 << QString::fromUtf8( "150.000°E, 80.000°N" ); addRow() << qreal(150.00001) << qreal(80.00001) << 3 << QString::fromUtf8( "150.000°E, 80.000°N" ); addRow() << qreal(150.0001) << qreal(80.0001) << 4 << QString::fromUtf8( "150.0001°E, 80.0001°N" ); addRow() << qreal(150.00001) << qreal(80.00001) << 4 << QString::fromUtf8( "150.0000°E, 80.0000°N" ); addRow() << qreal(150.000001) << qreal(80.000001) << 4 << QString::fromUtf8( "150.0000°E, 80.0000°N" ); addRow() << qreal(150.00001) << qreal(80.00001) << 5 << QString::fromUtf8( "150.00001°E, 80.00001°N" ); addRow() << qreal(150.000001) << qreal(80.000001) << 5 << QString::fromUtf8( "150.00000°E, 80.00000°N" ); addRow() << qreal(150.0000001) << qreal(80.0000001) << 5 << QString::fromUtf8( "150.00000°E, 80.00000°N" ); addRow() << qreal(150.000001) << qreal(80.000001) << 6 << QString::fromUtf8( "150.000001°E, 80.000001°N" ); addRow() << qreal(150.0000001) << qreal(80.0000001) << 6 << QString::fromUtf8( "150.000000°E, 80.000000°N" ); } /* * test toString() */ void TestGeoDataCoordinates::testToString_Decimal() { QFETCH( qreal, lon ); QFETCH( qreal, lat ); QFETCH( int, precision ); QFETCH( QString, expected ); const GeoDataCoordinates coordinates( lon, lat, 0, GeoDataCoordinates::Degree ); const QString result = coordinates.toString( GeoDataCoordinates::Decimal, precision ); QCOMPARE( result, expected ); } /* * test data for toString() */ void TestGeoDataCoordinates::testToString_DMS_data() { QTest::addColumn("lon"); QTest::addColumn("lat"); QTest::addColumn("precision"); QTest::addColumn("expected"); addRow() << qreal(0.) << qreal(0.) << 0 << QString::fromUtf8( " 0°E, 0°S" ); addRow() << qreal(150.) << qreal(80.) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(149. + 31./60) << qreal(79. + 31./60) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(149. + 30./60 + 31./3600) << qreal(79. + 30./60 + 31./3600) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(149. + 30./60 + 30.51/3600) << qreal(79. + 30./60 + 30.51/3600) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(150. + 29./60) << qreal(80. + 29./60) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(150. + 29./60 + 29./3600) << qreal(80. + 29./60 + 29./3600) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(150. + 29./60 + 29.49/3600) << qreal(80. + 29./60 + 29.49/3600) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(0.) << qreal(0.) << 1 << QString::fromUtf8( " 0° 00'E, 0° 00'S" ); addRow() << qreal(150.) << qreal(80.) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(149. + 59./60 + 31./3600) << qreal(79. + 59./60 + 31./3600) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(149. + 59./60 + 30.51/3600) << qreal(79. + 59./60 + 30.51/3600) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(150. + 29./3600) << qreal(80. + 29./3600) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(150. + 29.49/3600) << qreal(80. + 29.49/3600) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(0.) << qreal(0.) << 2 << QString::fromUtf8( " 0° 00'E, 0° 00'S" ); addRow() << qreal(150.) << qreal(80.) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(149. + 59./60 + 31./3600) << qreal(79. + 59./60 + 31./3600) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(149. + 59./60 + 30.51/3600) << qreal(79. + 59./60 + 30.51/3600) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(150. + 29./3600) << qreal(80. + 29./3600) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(150. + 29.49/3600) << qreal(80. + 29.49/3600) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(0.) << qreal(0.) << 3 << QString::fromUtf8( " 0° 00' 00\"E, 0° 00' 00\"S" ); addRow() << qreal(150.) << qreal(80.) << 3 << QString::fromUtf8( "150° 00' 00\"E, 80° 00' 00\"N" ); addRow() << qreal(149. + 59./60 + 59.51/3600) << qreal(79. + 59./60 + 59.51/3600) << 3 << QString::fromUtf8( "150° 00' 00\"E, 80° 00' 00\"N" ); addRow() << qreal(150. + 0.49/3600) << qreal(80. + 0.49/3600) << 3 << QString::fromUtf8( "150° 00' 00\"E, 80° 00' 00\"N" ); addRow() << qreal(0.) << qreal(0.) << 4 << QString::fromUtf8( " 0° 00' 00\"E, 0° 00' 00\"S" ); addRow() << qreal(150.) << qreal(80.) << 4 << QString::fromUtf8( "150° 00' 00\"E, 80° 00' 00\"N" ); addRow() << qreal(149. + 59./60 + 59.51/3600) << qreal(79. + 59./60 + 59.51/3600) << 4 << QString::fromUtf8( "150° 00' 00\"E, 80° 00' 00\"N" ); addRow() << qreal(150. + 0.49/3600) << qreal(80. + 0.49/3600) << 4 << QString::fromUtf8( "150° 00' 00\"E, 80° 00' 00\"N" ); addRow() << qreal(0.) << qreal(0.) << 5 << QString::fromUtf8( " 0° 00' 00.0\"E, 0° 00' 00.0\"S" ); addRow() << qreal(150.) << qreal(80.) << 5 << QString::fromUtf8( "150° 00' 00.0\"E, 80° 00' 00.0\"N" ); addRow() << qreal(149. + 59./60 + 59.951/3600) << qreal(79. + 59./60 + 59.951/3600) << 5 << QString::fromUtf8( "150° 00' 00.0\"E, 80° 00' 00.0\"N" ); addRow() << qreal(150. + 0.049/3600) << qreal(80. + 0.049/3600) << 5 << QString::fromUtf8( "150° 00' 00.0\"E, 80° 00' 00.0\"N" ); addRow() << qreal(0.) << qreal(0.) << 6 << QString::fromUtf8( " 0° 00' 00.00\"E, 0° 00' 00.00\"S" ); addRow() << qreal(150.) << qreal(80.) << 6 << QString::fromUtf8( "150° 00' 00.00\"E, 80° 00' 00.00\"N" ); addRow() << qreal(149. + 59./60 + 59.9951/3600) << qreal(79. + 59./60 + 59.9951/3600) << 6 << QString::fromUtf8( "150° 00' 00.00\"E, 80° 00' 00.00\"N" ); addRow() << qreal(150. + 0.0049/3600) << qreal(80. + 0.0049/3600) << 6 << QString::fromUtf8( "150° 00' 00.00\"E, 80° 00' 00.00\"N" ); } /* * test toString() */ void TestGeoDataCoordinates::testToString_DMS() { QFETCH( qreal, lon ); QFETCH( qreal, lat ); QFETCH( int, precision ); QFETCH( QString, expected ); const GeoDataCoordinates coordinates( lon, lat, 0, GeoDataCoordinates::Degree ); const QString result = coordinates.toString( GeoDataCoordinates::DMS, precision ); QCOMPARE( result, expected ); } /* * test data for toString() */ void TestGeoDataCoordinates::testToString_DM_data() { QTest::addColumn("lon"); QTest::addColumn("lat"); QTest::addColumn("precision"); QTest::addColumn("expected"); addRow() << qreal(0.) << qreal(0.) << 0 << QString::fromUtf8( " 0°E, 0°S" ); addRow() << qreal(150.) << qreal(80.) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(149. + 31./60) << qreal(79. + 31./60) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(149. + 30.51/60) << qreal(79. + 30.51/60) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(150. + 29./60) << qreal(80. + 29./60) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(150. + 29.49/60) << qreal(80. + 29.49/60) << 0 << QString::fromUtf8( "150°E, 80°N" ); addRow() << qreal(0.) << qreal(0.) << 1 << QString::fromUtf8( " 0° 00'E, 0° 00'S" ); addRow() << qreal(150.) << qreal(80.) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(149. + 59.51/60) << qreal(79. + 59.51/60) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(150. + 0.49/60) << qreal(80. + 0.49/60) << 1 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(0.) << qreal(0.) << 2 << QString::fromUtf8( " 0° 00'E, 0° 00'S" ); addRow() << qreal(150.) << qreal(80.) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(149. + 59.51/60) << qreal(79. + 59.51/60) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(150. + 0.49/60) << qreal(80. + 0.49/60) << 2 << QString::fromUtf8( "150° 00'E, 80° 00'N" ); addRow() << qreal(0.) << qreal(0.) << 3 << QString::fromUtf8( " 0° 00.0'E, 0° 00.0'S" ); addRow() << qreal(150.) << qreal(80.) << 3 << QString::fromUtf8( "150° 00.0'E, 80° 00.0'N" ); addRow() << qreal(149. + 59.951/60) << qreal(79. + 59.951/60) << 3 << QString::fromUtf8( "150° 00.0'E, 80° 00.0'N" ); addRow() << qreal(150. + 0.049/60) << qreal(80. + 0.049/60) << 3 << QString::fromUtf8( "150° 00.0'E, 80° 00.0'N" ); addRow() << qreal(0.) << qreal(0.) << 4 << QString::fromUtf8( " 0° 00.00'E, 0° 00.00'S" ); addRow() << qreal(150.) << qreal(80.) << 4 << QString::fromUtf8( "150° 00.00'E, 80° 00.00'N" ); addRow() << qreal(149. + 59.9951/60) << qreal(79. + 59.9951/60) << 4 << QString::fromUtf8( "150° 00.00'E, 80° 00.00'N" ); addRow() << qreal(150. + 0.0049/60) << qreal(80. + 0.0049/60) << 4 << QString::fromUtf8( "150° 00.00'E, 80° 00.00'N" ); } /* * test toString() */ void TestGeoDataCoordinates::testToString_DM() { QFETCH( qreal, lon ); QFETCH( qreal, lat ); QFETCH( int, precision ); QFETCH( QString, expected ); const GeoDataCoordinates coordinates( lon, lat, 0, GeoDataCoordinates::Degree ); const QString result = coordinates.toString( GeoDataCoordinates::DM, precision ); QCOMPARE( result, expected ); } /* * test data for testPack() */ void TestGeoDataCoordinates::testPack_data() { QTest::addColumn("lon"); QTest::addColumn("lat"); QTest::addColumn("alt"); QTest::newRow("deg") << qreal(180.0) << qreal(90.0) << qreal(400.0); } /* * test pack() and unPack() */ void TestGeoDataCoordinates::testPack() { QFETCH(qreal, lon); QFETCH(qreal, lat); QFETCH(qreal, alt); GeoDataCoordinates coordinates1,coordinates2; coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree); QTemporaryFile file; if(file.open()) { QDataStream out(&file); coordinates1.pack(out); } file.close(); if(file.open()) { QDataStream in(&file); coordinates2.unpack(in); } file.close(); QCOMPARE(coordinates1.longitude(GeoDataCoordinates::Degree), coordinates2.longitude(GeoDataCoordinates::Degree)); QCOMPARE(coordinates1.latitude(GeoDataCoordinates::Degree), coordinates2.latitude(GeoDataCoordinates::Degree)); QCOMPARE(coordinates1.altitude(), coordinates2.altitude()); } /* * test data for testUTM() */ void TestGeoDataCoordinates::testUTM_data() { QTest::addColumn("lon"); QTest::addColumn("lat"); QTest::addColumn("zone"); QTest::addColumn("latitudeBand"); QTest::addColumn("easting"); QTest::addColumn("northing"); /* Randomly selected locations, converted to UTM with the following * tools to check their correctness: * http://home.hiwaay.net/~taylorc/toolbox/geography/geoutm.html * http://www.earthpoint.us/Convert.aspx * http://www.synnatschke.de/geo-tools/coordinate-converter.php * http://www.latlong.net/lat-long-utm.html * http://leware.net/geo/utmgoogle.htm * http://geographiclib.sourceforge.net/cgi-bin/GeoConvert */ // Equator addRow() << qreal(-180.0) << qreal(0.0) << 1 << "N" << 16602144 << 0; addRow() << qreal(0) << qreal(0.0) << 31 << "N" << 16602144 << 0; addRow() << qreal(150.567) << qreal(0.0) << 56 << "N" << 22918607 << 0; // Zone borders int zoneNumber = 1; for ( int i = -180; i <= 180; i += 6 ){ addRow() << qreal(i) << qreal(0.0) << zoneNumber << "N" << 16602144 << 0; zoneNumber++; } // Northern hemisphere addRow() << qreal(-180.0) << qreal(15) << 1 << "P" << 17734904 << 166051369; addRow() << qreal(0) << qreal(60.5) << 31 << "V" << 33523714 << 671085271; addRow() << qreal(150.567) << qreal(75.123) << 56 << "X" << 43029080 << 833876115; // Southern hemisphere addRow() << qreal(-3.5) << qreal(-50) << 30 << "F" << 46416654 << 446124952; addRow() << qreal(22.56) << qreal(-62.456) << 34 << "E" << 58047905 << 307404780; // Exceptions // North pole (no zone associated, so it returns 0) addRow() << qreal(-100.0) << qreal(85.0) << 0 << "Y" << 49026986 << 943981733; addRow() << qreal(100.0) << qreal(85.0) << 0 << "Z" << 50973014 << 943981733; // South pole (no zone associated, so it returns 0) addRow() << qreal(-100.0) << qreal(-85.0) << 0 << "A" << 49026986 << 56018267; addRow() << qreal(100.0) << qreal(-85.0) << 0 << "B" << 50973014 << 56018267; // Stavanger, in southwestern Norway, is in zone 32 addRow() << qreal(5.73) << qreal(58.97) << 32 << "V" << 31201538 << 654131013; // Same longitude, at the equator, is in zone 31 addRow() << qreal(5.73) << qreal(0.0) << 31 << "N" << 80389643 << 0; // Svalbard is in zone 33 addRow() << qreal(10.55) << qreal(78.88) << 33 << "X" << 40427848 << 876023047; // Same longitude, at the equator, is in zone 32 addRow() << qreal(10.55) << qreal(0.0) << 32 << "N" << 67249738 << 0; } /* * test UTM-related functions: * - utmZone() * - utmLatitudeBand() * - utmEasting() * - utmNorthing() */ void TestGeoDataCoordinates::testUTM(){ QFETCH(qreal, lon); QFETCH(qreal, lat); QFETCH(int, zone); QFETCH(QString, latitudeBand); QFETCH(int, easting); QFETCH(int, northing); GeoDataCoordinates coordinates; coordinates.set(lon, lat, 0, GeoDataCoordinates::Degree); QCOMPARE(coordinates.utmZone(), zone); QCOMPARE(coordinates.utmLatitudeBand(), latitudeBand); /* Comparing integers is safer than comparing qreals. As the expected * values are expressed in centimeters, the actual values are converted * to this unit. */ int actualEasting = qRound( 100.0 * coordinates.utmEasting() ); int actualNorthing = qRound( 100.0 * coordinates.utmNorthing() ); QCOMPARE( actualEasting, easting ); QCOMPARE( actualNorthing, northing ); } QTEST_MAIN(TestGeoDataCoordinates) #include "TestGeoDataCoordinates.moc"