diff -r cfcbf08528c4 -r 2b40d63a9c3d qtmobility/src/location/qgeocoordinate.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qtmobility/src/location/qgeocoordinate.cpp Fri Apr 16 15:51:22 2010 +0300 @@ -0,0 +1,571 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgeocoordinate.h" +#include "qlocationutils_p.h" + +#include +#include +#include +#include +#include + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +QTM_BEGIN_NAMESPACE + +static const double qgeocoordinate_EARTH_MEAN_RADIUS = 6371.0072; + +inline static double qgeocoordinate_degToRad(double deg) +{ + return deg * M_PI / 180; +} +inline static double qgeocoordinate_radToDeg(double rad) +{ + return rad * 180 / M_PI; +} + + +class QGeoCoordinatePrivate +{ +public: + double lat; + double lng; + double alt; + + QGeoCoordinatePrivate() { + lat = qQNaN(); + lng = qQNaN(); + alt = qQNaN(); + } +}; + + +/*! + \class QGeoCoordinate + \brief The QGeoCoordinate class defines a geographical position on the surface of the Earth. + \ingroup location + + A QGeoCoordinate is defined by latitude, longitude, and optionally, altitude. + + Use type() to determine whether a coordinate is a 2D coordinate (has + latitude and longitude only) or 3D coordinate (has latitude, longitude + and altitude). Use distanceTo() and azimuthTo() to calculate the distance + and bearing between coordinates. + + The coordinate values should be specified using the WGS84 datum. +*/ + +/*! + \enum QGeoCoordinate::CoordinateType + Defines the types of a coordinate. + + \value InvalidCoordinate An invalid coordinate. A coordinate is invalid if its latitude or longitude values are invalid. + \value Coordinate2D A coordinate with valid latitude and longitude values. + \value Coordinate3D A coordinate with valid latitude and longitude values, and also an altitude value. +*/ + +/*! + \enum QGeoCoordinate::CoordinateFormat + Defines the possible formatting options for toString(). + + \value Degrees Returns a string representation of the coordinates in decimal degrees format. + \value DegreesWithHemisphere Returns a string representation of the coordinates in decimal degrees format, using 'N', 'S', 'E' or 'W' to indicate the hemispheres of the coordinates. + \value DegreesMinutes Returns a string representation of the coordinates in degrees-minutes format. + \value DegreesMinutesWithHemisphere Returns a string representation of the coordinates in degrees-minutes format, using 'N', 'S', 'E' or 'W' to indicate the hemispheres of the coordinates. + \value DegreesMinutesSeconds Returns a string representation of the coordinates in degrees-minutes-seconds format. + \value DegreesMinutesSecondsWithHemisphere Returns a string representation of the coordinates in degrees-minutes-seconds format, using 'N', 'S', 'E' or 'W' to indicate the hemispheres of the coordinates. + + \sa toString() +*/ + + +/*! + Constructs a coordinate. The coordinate will be invalid until + setLatitude() and setLongitude() have been called. +*/ +QGeoCoordinate::QGeoCoordinate() + : d(new QGeoCoordinatePrivate) +{ +} + +/*! + Constructs a coordinate with the given \a latitude and \a longitude. + + If the latitude is not between -90 to 90 inclusive, or the longitude + is not between -180 to 180 inclusive, none of the values are set and + the type() will be QGeoCoordinate::InvalidCoordinate. + + \sa isValid() +*/ +QGeoCoordinate::QGeoCoordinate(double latitude, double longitude) + : d(new QGeoCoordinatePrivate) +{ + if (QLocationUtils::isValidLat(latitude) && QLocationUtils::isValidLong(longitude)) { + d->lat = latitude; + d->lng = longitude; + } +} + +/*! + Constructs a coordinate with the given \a latitude, \a longitude + and \a altitude. + + If the latitude is not between -90 to 90 inclusive, or the longitude + is not between -180 to 180 inclusive, none of the values are set and + the type() will be QGeoCoordinate::InvalidCoordinate. + + Note that \a altitude specifies the metres above sea level. + + \sa isValid() +*/ +QGeoCoordinate::QGeoCoordinate(double latitude, double longitude, double altitude) + : d(new QGeoCoordinatePrivate) +{ + if (QLocationUtils::isValidLat(latitude) && QLocationUtils::isValidLong(longitude)) { + d->lat = latitude; + d->lng = longitude; + d->alt = altitude; + } +} + +/*! + Constructs a coordinate from the contents of \a other. +*/ +QGeoCoordinate::QGeoCoordinate(const QGeoCoordinate &other) + : d(new QGeoCoordinatePrivate) +{ + operator=(other); +} + +/*! + Destroys the coordinate object. +*/ +QGeoCoordinate::~QGeoCoordinate() +{ + delete d; +} + +/*! + Assigns \a other to this coordinate and returns a reference to this + coordinate. +*/ +QGeoCoordinate &QGeoCoordinate::operator=(const QGeoCoordinate & other) +{ + if (this == &other) + return *this; + + d->lat = other.d->lat; + d->lng = other.d->lng; + d->alt = other.d->alt; + + return *this; +} + +/*! + Returns true if the latitude, longitude and altitude of this + coordinate are the same as those of \a other. +*/ +bool QGeoCoordinate::operator==(const QGeoCoordinate &other) const +{ + return ((qIsNaN(d->lat) && qIsNaN(other.d->lat)) || qFuzzyCompare(d->lat, other.d->lat)) + && ((qIsNaN(d->lng) && qIsNaN(other.d->lng)) || qFuzzyCompare(d->lng, other.d->lng)) + && ((qIsNaN(d->alt) && qIsNaN(other.d->alt)) || qFuzzyCompare(d->alt, other.d->alt)); +} + +/*! + \fn bool QGeoCoordinate::operator!=(const QGeoCoordinate &other) const; + + Returns true if the latitude, longitude or altitude of this + coordinate are not the same as those of \a other. +*/ + +/*! + Returns true if the type() is Coordinate2D or Coordinate3D. +*/ +bool QGeoCoordinate::isValid() const +{ + CoordinateType t = type(); + return t == Coordinate2D || t == Coordinate3D; +} + +/*! + Returns the type of this coordinate. +*/ +QGeoCoordinate::CoordinateType QGeoCoordinate::type() const +{ + if (QLocationUtils::isValidLat(d->lat) + && QLocationUtils::isValidLong(d->lng)) { + if (qIsNaN(d->alt)) + return Coordinate2D; + return Coordinate3D; + } + return InvalidCoordinate; +} + + +/*! + Returns the latitude, in decimal degrees. The return value is undefined + if the latitude has not been set. + + A positive latitude indicates the Northern Hemisphere, and a negative + latitude indicates the Southern Hemisphere. + + \sa setLatitude(), type() +*/ +double QGeoCoordinate::latitude() const +{ + return d->lat; +} + +/*! + Sets the latitude (in decimal degrees) to \a latitude. The value should + be in the WGS84 datum. + + To be valid, the latitude must be between -90 to 90 inclusive. + + \sa latitude() +*/ +void QGeoCoordinate::setLatitude(double latitude) +{ + d->lat = latitude; +} + +/*! + Returns the longitude, in decimal degrees. The return value is undefined + if the longitude has not been set. + + A positive longitude indicates the Eastern Hemisphere, and a negative + longitude indicates the Western Hemisphere. + + \sa setLongitude(), type() +*/ +double QGeoCoordinate::longitude() const +{ + return d->lng; +} + +/*! + Sets the longitude (in decimal degrees) to \a longitude. The value should + be in the WGS84 datum. + + To be valid, the longitude must be between -180 to 180 inclusive. + + \sa longitude() +*/ +void QGeoCoordinate::setLongitude(double longitude) +{ + d->lng = longitude; +} + +/*! + Returns the altitude (meters above sea level). + + The return value is undefined if the altitude has not been set. + + \sa setAltitude(), type() +*/ +double QGeoCoordinate::altitude() const +{ + return d->alt; +} + +/*! + Sets the altitude (meters above sea level) to \a altitude. + + \sa altitude() +*/ +void QGeoCoordinate::setAltitude(double altitude) +{ + d->alt = altitude; +} + +/*! + Returns the distance (in meters) from this coordinate to the coordinate + specified by \a other. Altitude is not used in the calculation. + + This calculation returns the great-circle distance between the two + coordinates, with an assumption that the Earth is spherical for the + purpose of this calculation. + + Returns 0 if the type of this coordinate or the type of \a other is + QGeoCoordinate::InvalidCoordinate. +*/ +qreal QGeoCoordinate::distanceTo(const QGeoCoordinate &other) const +{ + if (type() == QGeoCoordinate::InvalidCoordinate + || other.type() == QGeoCoordinate::InvalidCoordinate) { + return 0; + } + + // Haversine formula + double dlat = qgeocoordinate_degToRad(other.d->lat - d->lat); + double dlon = qgeocoordinate_degToRad(other.d->lng - d->lng); + double y = sin(dlat / 2.0) * sin(dlat / 2.0) + + cos(qgeocoordinate_degToRad(d->lat)) + * cos(qgeocoordinate_degToRad(other.d->lat)) + * sin(dlon / 2.0) * sin(dlon / 2.0); + double x = 2 * atan2(sqrt(y), sqrt(1 - y)); + return qreal(x * qgeocoordinate_EARTH_MEAN_RADIUS * 1000); +} + +/*! + Returns the azimuth (or bearing) in degrees from this coordinate to the + coordinate specified by \a other. Altitude is not used in the calculation. + + There is an assumption that the Earth is spherical for the purpose of + this calculation. + + Returns 0 if the type of this coordinate or the type of \a other is + QGeoCoordinate::InvalidCoordinate. +*/ +qreal QGeoCoordinate::azimuthTo(const QGeoCoordinate &other) const +{ + if (type() == QGeoCoordinate::InvalidCoordinate + || other.type() == QGeoCoordinate::InvalidCoordinate) { + return 0; + } + + double dlon = qgeocoordinate_degToRad(other.d->lng - d->lng); + double lat1Rad = qgeocoordinate_degToRad(d->lat); + double lat2Rad = qgeocoordinate_degToRad(other.d->lat); + + double y = sin(dlon) * cos(lat2Rad); + double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dlon); + + double whole; + double fraction = modf(qgeocoordinate_radToDeg(atan2(y, x)), &whole); + return qreal((int(whole + 360) % 360) + fraction); +} + +/*! + Returns this coordinate as a string in the specified \a format. + + For example, if this coordinate has a latitude of -27.46758, a longitude + of 153.027892 and an altitude of 28.1, these are the strings + returned depending on \a format: + + \table + \header + \o \a format value + \o Returned string + \row + \o \l Degrees + \o -27.46758\unicode{0xB0}, 153.02789\unicode{0xB0}, 28.1m + \row + \o \l DegreesWithHemisphere + \o 27.46758\unicode{0xB0} S, 153.02789\unicode{0xB0} E, 28.1m + \row + \o \l DegreesMinutes + \o -27\unicode{0xB0} 28.054', 153\unicode{0xB0} 1.673', 28.1m + \row + \o \l DegreesMinutesWithHemisphere + \o 27\unicode{0xB0} 28.054 S', 153\unicode{0xB0} 1.673' E, 28.1m + \row + \o \l DegreesMinutesSeconds + \o -27\unicode{0xB0} 28' 3.2", 153\unicode{0xB0} 1' 40.4", 28.1m + \row + \o \l DegreesMinutesSecondsWithHemisphere + \o 27\unicode{0xB0} 28' 3.2" S, 153\unicode{0xB0} 1' 40.4" E, 28.1m + \endtable + + The altitude field is omitted if no altitude is set. + + If the coordinate is invalid, an empty string is returned. +*/ +QString QGeoCoordinate::toString(CoordinateFormat format) const +{ + if (type() == QGeoCoordinate::InvalidCoordinate) + return QString(); + + QString latStr; + QString longStr; + + double absLat = qAbs(d->lat); + double absLng = qAbs(d->lng); + QChar symbol(0x00B0); // degrees symbol + + switch (format) { + case Degrees: + case DegreesWithHemisphere: { + latStr = QString::number(absLat, 'f', 5) + symbol; + longStr = QString::number(absLng, 'f', 5) + symbol; + break; + } + case DegreesMinutes: + case DegreesMinutesWithHemisphere: { + double latMin = (absLat - int(absLat)) * 60; + double lngMin = (absLng - int(absLng)) * 60; + latStr = QString("%1%2 %3'") + .arg(QString::number(int(absLat))) + .arg(symbol) + .arg(QString::number(latMin, 'f', 3)); + longStr = QString("%1%2 %3'") + .arg(QString::number(int(absLng))) + .arg(symbol) + .arg(QString::number(lngMin, 'f', 3)); + break; + } + case DegreesMinutesSeconds: + case DegreesMinutesSecondsWithHemisphere: { + double latMin = (absLat - int(absLat)) * 60; + double lngMin = (absLng - int(absLng)) * 60; + double latSec = (latMin - int(latMin)) * 60; + double lngSec = (lngMin - int(lngMin)) * 60; + + latStr = QString("%1%2 %3' %4\"") + .arg(QString::number(int(absLat))) + .arg(symbol) + .arg(QString::number(int(latMin))) + .arg(QString::number(latSec, 'f', 1)); + longStr = QString("%1%2 %3' %4\"") + .arg(QString::number(int(absLng))) + .arg(symbol) + .arg(QString::number(int(lngMin))) + .arg(QString::number(lngSec, 'f', 1)); + break; + } + } + + // now add the "-" to the start, or append the hemisphere char + switch (format) { + case Degrees: + case DegreesMinutes: + case DegreesMinutesSeconds: { + if (d->lat < 0) + latStr.insert(0, "-"); + if (d->lng < 0) + longStr.insert(0, "-"); + break; + } + case DegreesWithHemisphere: + case DegreesMinutesWithHemisphere: + case DegreesMinutesSecondsWithHemisphere: { + if (d->lat < 0) + latStr.append(" S"); + else if (d->lat > 0) + latStr.append(" N"); + if (d->lng < 0) + longStr.append(" W"); + else if (d->lng > 0) + longStr.append(" E"); + break; + } + } + + if (qIsNaN(d->alt)) + return QString("%1, %2").arg(latStr, longStr); + return QString("%1, %2, %3m").arg(latStr, longStr, QString::number(d->alt)); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeoCoordinate &coord) +{ + double lat = coord.latitude(); + double lng = coord.longitude(); + + dbg.nospace() << "QGeoCoordinate("; + if (qIsNaN(lat)) + dbg.nospace() << '?'; + else + dbg.nospace() << lat; + dbg.nospace() << ", "; + if (qIsNaN(lng)) + dbg.nospace() << '?'; + else + dbg.nospace() << lng; + if (coord.type() == QGeoCoordinate::Coordinate3D) { + dbg.nospace() << ", "; + dbg.nospace() << coord.altitude(); + } + dbg.nospace() << ')'; + return dbg; +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate) + + \relates QGeoCoordinate + + Writes the given \a coordinate to the specified \a stream. + + \sa {Format of the QDataStream Operators} +*/ + +QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate) +{ + stream << coordinate.latitude(); + stream << coordinate.longitude(); + stream << coordinate.altitude(); + return stream; +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate) + \relates QGeoCoordinate + + Reads a coordinate from the specified \a stream into the given + \a coordinate. + + \sa {Format of the QDataStream Operators} +*/ + +QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate) +{ + double value; + stream >> value; + coordinate.setLatitude(value); + stream >> value; + coordinate.setLongitude(value); + stream >> value; + coordinate.setAltitude(value); + return stream; +} +#endif + +QTM_END_NAMESPACE