1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtPositioning module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 #include "qgeocoordinate.h"
40 #include "qgeocoordinate_p.h"
41 #include "qlocationutils_p.h"
42 
43 #include <QDateTime>
44 #include <QHash>
45 #include <QDataStream>
46 #include <QDebug>
47 #include <QMetaType>
48 #include <qnumeric.h>
49 #include <qmath.h>
50 
51 QT_BEGIN_NAMESPACE
52 
53 
54 struct CoordinateStreamOperators
55 {
CoordinateStreamOperatorsCoordinateStreamOperators56     CoordinateStreamOperators()
57     {
58 #ifndef QT_NO_DATASTREAM
59         qRegisterMetaTypeStreamOperators<QGeoCoordinate>();
60 #endif
61 #ifndef QT_NO_DEBUG_STREAM
62         QMetaType::registerDebugStreamOperator<QGeoCoordinate>();
63 #endif
64     }
65 };
66 Q_GLOBAL_STATIC(CoordinateStreamOperators, initStreamOperators);
67 
68 
69 static const double qgeocoordinate_EARTH_MEAN_RADIUS = 6371.0072;
70 
71 
QGeoCoordinatePrivate()72 QGeoCoordinatePrivate::QGeoCoordinatePrivate():
73     lat(qQNaN()),
74     lng(qQNaN()),
75     alt(qQNaN())
76 {}
77 
QGeoCoordinatePrivate(const QGeoCoordinatePrivate & other)78 QGeoCoordinatePrivate::QGeoCoordinatePrivate(const QGeoCoordinatePrivate &other)
79     : QSharedData(other),
80       lat(other.lat),
81       lng(other.lng),
82       alt(other.alt)
83 {}
84 
~QGeoCoordinatePrivate()85 QGeoCoordinatePrivate::~QGeoCoordinatePrivate()
86 {}
87 
88 
QGeoMercatorCoordinatePrivate()89 QGeoMercatorCoordinatePrivate::QGeoMercatorCoordinatePrivate():
90     QGeoCoordinatePrivate(),
91     m_mercatorX(qQNaN()),
92     m_mercatorY(qQNaN())
93 {}
94 
QGeoMercatorCoordinatePrivate(const QGeoMercatorCoordinatePrivate & other)95 QGeoMercatorCoordinatePrivate::QGeoMercatorCoordinatePrivate(const QGeoMercatorCoordinatePrivate &other)
96     : QGeoCoordinatePrivate(other),
97       m_mercatorX(other.m_mercatorX),
98       m_mercatorY(other.m_mercatorY)
99 {}
100 
~QGeoMercatorCoordinatePrivate()101 QGeoMercatorCoordinatePrivate::~QGeoMercatorCoordinatePrivate()
102 {}
103 
104 /*!
105     \class QGeoCoordinate
106     \inmodule QtPositioning
107     \ingroup QtPositioning-positioning
108     \since 5.2
109 
110     \brief The QGeoCoordinate class defines a geographical position on the surface of the Earth.
111 
112     A QGeoCoordinate is defined by latitude, longitude, and optionally, altitude.
113 
114     Use type() to determine whether a coordinate is a 2D coordinate (has
115     latitude and longitude only) or 3D coordinate (has latitude, longitude
116     and altitude). Use distanceTo() and azimuthTo() to calculate the distance
117     and bearing between coordinates.
118 
119     The coordinate values should be specified using the WGS84 datum. For more information
120     on geographical terms see this article on \l {http://en.wikipedia.org/wiki/Geographic_coordinate_system}{coordinates} and
121     another on \l {http://en.wikipedia.org/wiki/Geodetic_system}{geodetic systems}
122     including WGS84.
123 
124     Azimuth in this context is equivalent to a compass bearing based on true north.
125 
126     This class is a \l Q_GADGET since Qt 5.5. It can be
127     \l{Cpp_value_integration_positioning}{directly used from C++ and QML}.
128 */
129 
130 /*!
131     \enum QGeoCoordinate::CoordinateType
132     Defines the types of a coordinate.
133 
134     \value InvalidCoordinate An invalid coordinate. A coordinate is invalid if its latitude or longitude values are invalid.
135     \value Coordinate2D A coordinate with valid latitude and longitude values.
136     \value Coordinate3D A coordinate with valid latitude and longitude values, and also an altitude value.
137 */
138 
139 /*!
140     \enum QGeoCoordinate::CoordinateFormat
141     Defines the possible formatting options for toString().
142 
143     \value Degrees Returns a string representation of the coordinates in decimal degrees format.
144     \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.
145     \value DegreesMinutes Returns a string representation of the coordinates in degrees-minutes format.
146     \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.
147     \value DegreesMinutesSeconds Returns a string representation of the coordinates in degrees-minutes-seconds format.
148     \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.
149 
150     \sa toString()
151 */
152 
153 /*!
154     \property QGeoCoordinate::latitude
155     \brief This property holds the latitude in decimal degrees.
156 
157     The property is undefined (\l {qQNaN()}) if the latitude has not been set.
158     A positive latitude indicates the Northern Hemisphere, and a negative
159     latitude indicates the Southern Hemisphere. When setting the latitude the
160     new value should be in the
161     \l {http://en.wikipedia.org/wiki/World_Geodetic_System}{WGS84} datum format.
162 
163     To be valid, the latitude must be between -90 to 90 inclusive.
164 
165     While this property is introduced in Qt 5.5, the related accessor functions
166     exist since the first version of this class.
167 
168     \since 5.5
169 */
170 
171 /*!
172     \property QGeoCoordinate::longitude
173     \brief This property holds the longitude in decimal degrees.
174 
175     The property is undefined (\l {qQNaN()}) if the longitude has not been set.
176     A positive longitude indicates the Eastern Hemisphere, and a negative
177     longitude indicates the Western Hemisphere. When setting the longitude the
178     new value should be in the
179     \l {http://en.wikipedia.org/wiki/World_Geodetic_System}{WGS84} datum format.
180 
181     To be valid, the longitude must be between -180 to 180 inclusive.
182 
183     While this property is introduced in Qt 5.5, the related accessor functions
184     exist since the first version of this class.
185 
186     \since 5.5
187 */
188 
189 /*!
190     \property QGeoCoordinate::altitude
191     \brief This property holds the altitude in meters above sea level.
192 
193     The property is undefined (\l {qQNaN()}) if the altitude has not been set.
194 
195     While this property is introduced in Qt 5.5, the related accessor functions
196     exist since the first version of this class.
197 
198     \since 5.5
199 */
200 
201 /*!
202     \property QGeoCoordinate::isValid
203     \brief This property holds the validity of this geo coordinate.
204 
205     The geo coordinate is valid if the \l [CPP]{longitude} and \l [CPP]{latitude}
206     properties have been set to valid values.
207 
208     While this property is introduced in Qt 5.5, the related accessor functions
209     exist since the first version of this class.
210 
211     \since 5.5
212 */
213 
214 /*!
215     Constructs a coordinate. The coordinate will be invalid until
216     setLatitude() and setLongitude() have been called.
217 */
QGeoCoordinate()218 QGeoCoordinate::QGeoCoordinate()
219         : d(new QGeoCoordinatePrivate)
220 {
221 #ifndef QT_NO_DATASTREAM
222     initStreamOperators();
223 #endif
224 }
225 
226 /*!
227     Constructs a coordinate with the given \a latitude and \a longitude.
228 
229     If the latitude is not between -90 to 90 inclusive, or the longitude
230     is not between -180 to 180 inclusive, none of the values are set and
231     the type() will be QGeoCoordinate::InvalidCoordinate.
232 
233     \sa isValid()
234 */
QGeoCoordinate(double latitude,double longitude)235 QGeoCoordinate::QGeoCoordinate(double latitude, double longitude)
236         : d(new QGeoCoordinatePrivate)
237 {
238 #ifndef QT_NO_DATASTREAM
239     initStreamOperators();
240 #endif
241 
242     if (QLocationUtils::isValidLat(latitude) && QLocationUtils::isValidLong(longitude)) {
243         d->lat = latitude;
244         d->lng = longitude;
245     }
246 }
247 
248 /*!
249     Constructs a coordinate with the given \a latitude, \a longitude
250     and \a altitude.
251 
252     If the latitude is not between -90 to 90 inclusive, or the longitude
253     is not between -180 to 180 inclusive, none of the values are set and
254     the type() will be QGeoCoordinate::InvalidCoordinate.
255 
256     Note that \a altitude specifies the meters above sea level.
257 
258     \sa isValid()
259 */
QGeoCoordinate(double latitude,double longitude,double altitude)260 QGeoCoordinate::QGeoCoordinate(double latitude, double longitude, double altitude)
261         : d(new QGeoCoordinatePrivate)
262 {
263 #ifndef QT_NO_DATASTREAM
264     initStreamOperators();
265 #endif
266 
267     if (QLocationUtils::isValidLat(latitude) && QLocationUtils::isValidLong(longitude)) {
268         d->lat = latitude;
269         d->lng = longitude;
270         d->alt = altitude;
271     }
272 }
273 
274 /*!
275     Constructs a coordinate from the contents of \a other.
276 */
QGeoCoordinate(const QGeoCoordinate & other)277 QGeoCoordinate::QGeoCoordinate(const QGeoCoordinate &other)
278         : d(other.d)
279 {}
280 
281 /*!
282     Assigns \a other to this coordinate and returns a reference to this coordinate.
283 */
operator =(const QGeoCoordinate & other)284 QGeoCoordinate &QGeoCoordinate::operator=(const QGeoCoordinate &other)
285 {
286     if (this == &other)
287         return *this;
288 
289     d = other.d;
290     return (*this);
291 }
292 
293 /*!
294     Destroys the coordinate object.
295 */
~QGeoCoordinate()296 QGeoCoordinate::~QGeoCoordinate()
297 {
298 }
299 
300 /*!
301     Returns true if the latitude, longitude and altitude of this
302     coordinate are the same as those of \a other.
303 
304     The longitude will be ignored if the latitude is +/- 90 degrees.
305 */
operator ==(const QGeoCoordinate & other) const306 bool QGeoCoordinate::operator==(const QGeoCoordinate &other) const
307 {
308     bool latEqual = (qIsNaN(d->lat) && qIsNaN(other.d->lat))
309                         || qFuzzyCompare(d->lat, other.d->lat);
310     bool lngEqual = (qIsNaN(d->lng) && qIsNaN(other.d->lng))
311                         || qFuzzyCompare(d->lng, other.d->lng);
312     bool altEqual = (qIsNaN(d->alt) && qIsNaN(other.d->alt))
313                         || qFuzzyCompare(d->alt, other.d->alt);
314 
315     if (!qIsNaN(d->lat) && ((d->lat == 90.0) || (d->lat == -90.0)))
316         lngEqual = true;
317 
318     return (latEqual && lngEqual && altEqual);
319 }
320 
321 /*!
322     \fn bool QGeoCoordinate::operator!=(const QGeoCoordinate &other) const
323 
324     Returns \c true if latitude, longitude, or altitude of this
325     coordinate are not identical to \a other.
326 */
327 
328 /*!
329     Returns \c true if the \l longitude and \l latitude are valid.
330 */
isValid() const331 bool QGeoCoordinate::isValid() const
332 {
333     CoordinateType t = type();
334     return t == Coordinate2D || t == Coordinate3D;
335 }
336 
337 /*!
338     Returns the type of this coordinate.
339 */
type() const340 QGeoCoordinate::CoordinateType QGeoCoordinate::type() const
341 {
342     if (QLocationUtils::isValidLat(d->lat)
343             && QLocationUtils::isValidLong(d->lng)) {
344         if (qIsNaN(d->alt))
345             return Coordinate2D;
346         return Coordinate3D;
347     }
348     return InvalidCoordinate;
349 }
350 
351 
352 /*!
353     Returns the latitude, in decimal degrees. The return value is undefined
354     if the latitude has not been set.
355 
356     A positive latitude indicates the Northern Hemisphere, and a negative
357     latitude indicates the Southern Hemisphere.
358 
359     \sa setLatitude(), type()
360 */
latitude() const361 double QGeoCoordinate::latitude() const
362 {
363     return d->lat;
364 }
365 
366 /*!
367     Sets the latitude (in decimal degrees) to \a latitude. The value should
368     be in the WGS84 datum.
369 
370     To be valid, the latitude must be between -90 to 90 inclusive.
371 
372     \sa latitude()
373 */
setLatitude(double latitude)374 void QGeoCoordinate::setLatitude(double latitude)
375 {
376     d->lat = latitude;
377 }
378 
379 /*!
380     Returns the longitude, in decimal degrees. The return value is undefined
381     if the longitude has not been set.
382 
383     A positive longitude indicates the Eastern Hemisphere, and a negative
384     longitude indicates the Western Hemisphere.
385 
386     \sa setLongitude(), type()
387 */
longitude() const388 double QGeoCoordinate::longitude() const
389 {
390     return d->lng;
391 }
392 
393 /*!
394     Sets the longitude (in decimal degrees) to \a longitude. The value should
395     be in the WGS84 datum.
396 
397     To be valid, the longitude must be between -180 to 180 inclusive.
398 
399     \sa longitude()
400 */
setLongitude(double longitude)401 void QGeoCoordinate::setLongitude(double longitude)
402 {
403     d->lng = longitude;
404 }
405 
406 /*!
407     Returns the altitude (meters above sea level).
408 
409     The return value is undefined if the altitude has not been set.
410 
411     \sa setAltitude(), type()
412 */
altitude() const413 double QGeoCoordinate::altitude() const
414 {
415     return d->alt;
416 }
417 
418 /*!
419     Sets the altitude (meters above sea level) to \a altitude.
420 
421     \sa altitude()
422 */
setAltitude(double altitude)423 void QGeoCoordinate::setAltitude(double altitude)
424 {
425     d->alt = altitude;
426 }
427 
428 /*!
429     Returns the distance (in meters) from this coordinate to the coordinate
430     specified by \a other. Altitude is not used in the calculation.
431 
432     This calculation returns the great-circle distance between the two
433     coordinates, with an assumption that the Earth is spherical for the
434     purpose of this calculation.
435 
436     Returns 0 if the type of this coordinate or the type of \a other is
437     QGeoCoordinate::InvalidCoordinate.
438 */
distanceTo(const QGeoCoordinate & other) const439 qreal QGeoCoordinate::distanceTo(const QGeoCoordinate &other) const
440 {
441     if (type() == QGeoCoordinate::InvalidCoordinate
442             || other.type() == QGeoCoordinate::InvalidCoordinate) {
443         return 0;
444     }
445 
446     // Haversine formula
447     double dlat = qDegreesToRadians(other.d->lat - d->lat);
448     double dlon = qDegreesToRadians(other.d->lng - d->lng);
449     double haversine_dlat = sin(dlat / 2.0);
450     haversine_dlat *= haversine_dlat;
451     double haversine_dlon = sin(dlon / 2.0);
452     haversine_dlon *= haversine_dlon;
453     double y = haversine_dlat
454              + cos(qDegreesToRadians(d->lat))
455              * cos(qDegreesToRadians(other.d->lat))
456              * haversine_dlon;
457     double x = 2 * asin(sqrt(y));
458     return qreal(x * qgeocoordinate_EARTH_MEAN_RADIUS * 1000);
459 }
460 
461 /*!
462     Returns the azimuth (or bearing) in degrees from this coordinate to the
463     coordinate specified by \a other. Altitude is not used in the calculation.
464 
465     The bearing returned is the bearing from the origin to \a other along the
466     great-circle between the two coordinates. There is an assumption that the
467     Earth is spherical for the purpose of this calculation.
468 
469     Returns 0 if the type of this coordinate or the type of \a other is
470     QGeoCoordinate::InvalidCoordinate.
471 */
azimuthTo(const QGeoCoordinate & other) const472 qreal QGeoCoordinate::azimuthTo(const QGeoCoordinate &other) const
473 {
474     if (type() == QGeoCoordinate::InvalidCoordinate
475             || other.type() == QGeoCoordinate::InvalidCoordinate) {
476         return 0;
477     }
478 
479     double dlon = qDegreesToRadians(other.d->lng - d->lng);
480     double lat1Rad = qDegreesToRadians(d->lat);
481     double lat2Rad = qDegreesToRadians(other.d->lat);
482 
483     double y = sin(dlon) * cos(lat2Rad);
484     double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dlon);
485 
486     double azimuth = qRadiansToDegrees(atan2(y, x)) + 360.0;
487     double whole;
488     double fraction = modf(azimuth, &whole);
489     return qreal((int(whole + 360) % 360) + fraction);
490 }
491 
atDistanceAndAzimuth(const QGeoCoordinate & coord,qreal distance,qreal azimuth,double * lon,double * lat)492 void QGeoCoordinatePrivate::atDistanceAndAzimuth(const QGeoCoordinate &coord,
493                                                  qreal distance, qreal azimuth,
494                                                  double *lon, double *lat)
495 {
496     double latRad = qDegreesToRadians(coord.d->lat);
497     double lonRad = qDegreesToRadians(coord.d->lng);
498     double cosLatRad = cos(latRad);
499     double sinLatRad = sin(latRad);
500 
501     double azimuthRad = qDegreesToRadians(azimuth);
502 
503     double ratio = (distance / (qgeocoordinate_EARTH_MEAN_RADIUS * 1000.0));
504     double cosRatio = cos(ratio);
505     double sinRatio = sin(ratio);
506 
507     double resultLatRad = asin(sinLatRad * cosRatio
508                                + cosLatRad * sinRatio * cos(azimuthRad));
509     double resultLonRad = lonRad + atan2(sin(azimuthRad) * sinRatio * cosLatRad,
510                                    cosRatio - sinLatRad * sin(resultLatRad));
511 
512     *lat = qRadiansToDegrees(resultLatRad);
513     *lon = qRadiansToDegrees(resultLonRad);
514 }
515 
516 /*!
517     Returns the coordinate that is reached by traveling \a distance meters
518     from the current coordinate at \a azimuth (or bearing) along a great-circle.
519     There is an assumption that the Earth is spherical for the purpose of this
520     calculation.
521 
522     The altitude will have \a distanceUp added to it.
523 
524     Returns an invalid coordinate if this coordinate is invalid.
525 */
atDistanceAndAzimuth(qreal distance,qreal azimuth,qreal distanceUp) const526 QGeoCoordinate QGeoCoordinate::atDistanceAndAzimuth(qreal distance, qreal azimuth, qreal distanceUp) const
527 {
528     if (!isValid())
529         return QGeoCoordinate();
530 
531     double resultLon, resultLat;
532     QGeoCoordinatePrivate::atDistanceAndAzimuth(*this, distance, azimuth,
533                                                 &resultLon, &resultLat);
534     double resultAlt = d->alt + distanceUp;
535     return QGeoCoordinate(resultLat, QLocationUtils::wrapLong(resultLon), resultAlt);
536 }
537 
538 /*!
539     Returns this coordinate as a string in the specified \a format.
540 
541     For example, if this coordinate has a latitude of -27.46758, a longitude
542     of 153.027892 and an altitude of 28.1, these are the strings
543     returned depending on \a format:
544 
545     \table
546     \header
547         \li \a format value
548         \li Returned string
549     \row
550         \li \l Degrees
551         \li -27.46758\unicode{0xB0}, 153.02789\unicode{0xB0}, 28.1m
552     \row
553         \li \l DegreesWithHemisphere
554         \li 27.46758\unicode{0xB0} S, 153.02789\unicode{0xB0} E, 28.1m
555     \row
556         \li \l DegreesMinutes
557         \li -27\unicode{0xB0} 28.054', 153\unicode{0xB0} 1.673', 28.1m
558     \row
559         \li \l DegreesMinutesWithHemisphere
560         \li 27\unicode{0xB0} 28.054 S', 153\unicode{0xB0} 1.673' E, 28.1m
561     \row
562         \li \l DegreesMinutesSeconds
563         \li -27\unicode{0xB0} 28' 3.2", 153\unicode{0xB0} 1' 40.4", 28.1m
564     \row
565         \li \l DegreesMinutesSecondsWithHemisphere
566         \li 27\unicode{0xB0} 28' 3.2" S, 153\unicode{0xB0} 1' 40.4" E, 28.1m
567     \endtable
568 
569     The altitude field is omitted if no altitude is set.
570 
571     If the coordinate is invalid, an empty string is returned.
572 */
toString(CoordinateFormat format) const573 QString QGeoCoordinate::toString(CoordinateFormat format) const
574 {
575     if (type() == QGeoCoordinate::InvalidCoordinate)
576         return QString();
577 
578     QString latStr;
579     QString longStr;
580 
581     double absLat = qAbs(d->lat);
582     double absLng = qAbs(d->lng);
583     QChar symbol(0x00B0);   // degrees symbol
584 
585     switch (format) {
586         case Degrees:
587         case DegreesWithHemisphere: {
588             latStr = QString::number(absLat, 'f', 5) + symbol;
589             longStr = QString::number(absLng, 'f', 5) + symbol;
590             break;
591         }
592         case DegreesMinutes:
593         case DegreesMinutesWithHemisphere: {
594             double latMin = (absLat - int(absLat)) * 60;
595             double lngMin = (absLng - int(absLng)) * 60;
596 
597             if (qRound(latMin) >= 60) {
598                 absLat++;
599                 latMin = qAbs(latMin - 60.0f);
600                 //avoid invalid latitude due to latMin rounding below
601                 if (qRound(absLat) >= 90)
602                     latMin = 0.0f;
603             }
604             if (qRound(lngMin) >= 60) {
605                 absLng++;
606                 lngMin = qAbs(lngMin - 60.0f);
607                 // avoid invalid longitude due to lngMin rounding below
608                 if (qRound(absLng) >= 180)
609                     lngMin = 0.0f;
610             }
611 
612             latStr = QString::fromLatin1("%1%2 %3'")
613                      .arg(QString::number(int(absLat)))
614                      .arg(symbol)
615                      .arg(QString::number(latMin, 'f', 3));
616             longStr = QString::fromLatin1("%1%2 %3'")
617                       .arg(QString::number(int(absLng)))
618                       .arg(symbol)
619                       .arg(QString::number(lngMin, 'f', 3));
620             break;
621         }
622         case DegreesMinutesSeconds:
623         case DegreesMinutesSecondsWithHemisphere: {
624             double latMin = (absLat - int(absLat)) * 60;
625             double lngMin = (absLng - int(absLng)) * 60;
626             double latSec = (latMin - int(latMin)) * 60;
627             double lngSec = (lngMin - int(lngMin)) * 60;
628 
629             // overflow to full minutes
630             if (qRound(latSec) >= 60) {
631                 latMin++;
632                 latSec = qAbs(latSec - 60.0f);
633                 // overflow to full degrees
634                 if (qRound(latMin) >= 60) {
635                     absLat++;
636                     latMin = qAbs(latMin - 60.0f);
637                     // avoid invalid latitude due to latSec rounding below
638                     if (qRound(absLat) >= 90)
639                         latSec = 0.0f;
640                 }
641             }
642             if (qRound(lngSec) >= 60) {
643                 lngMin++;
644                 lngSec = qAbs(lngSec - 60.0f);
645                 if (qRound(lngMin) >= 60) {
646                     absLng++;
647                     lngMin = qAbs(lngMin - 60.0f);
648                     // avoid invalid longitude due to lngSec rounding below
649                     if (qRound(absLng) >= 180)
650                         lngSec = 0.0f;
651                 }
652             }
653 
654             latStr = QString::fromLatin1("%1%2 %3' %4\"")
655                      .arg(QString::number(int(absLat)))
656                      .arg(symbol)
657                      .arg(QString::number(int(latMin)))
658                      .arg(QString::number(latSec, 'f', 1));
659             longStr = QString::fromLatin1("%1%2 %3' %4\"")
660                       .arg(QString::number(int(absLng)))
661                       .arg(symbol)
662                       .arg(QString::number(int(lngMin)))
663                       .arg(QString::number(lngSec, 'f', 1));
664             break;
665         }
666     }
667 
668     // now add the "-" to the start, or append the hemisphere char
669     switch (format) {
670         case Degrees:
671         case DegreesMinutes:
672         case DegreesMinutesSeconds: {
673             if (d->lat < 0)
674                 latStr.insert(0, QStringLiteral("-"));
675             if (d->lng < 0)
676                 longStr.insert(0, QStringLiteral("-"));
677             break;
678         }
679         case DegreesWithHemisphere:
680         case DegreesMinutesWithHemisphere:
681         case DegreesMinutesSecondsWithHemisphere: {
682             if (d->lat < 0)
683                 latStr.append(QString::fromLatin1(" S"));
684             else if (d->lat > 0)
685                 latStr.append(QString::fromLatin1(" N"));
686             if (d->lng < 0)
687                 longStr.append(QString::fromLatin1(" W"));
688             else if (d->lng > 0)
689                 longStr.append(QString::fromLatin1(" E"));
690             break;
691         }
692     }
693 
694     if (qIsNaN(d->alt))
695         return QString::fromLatin1("%1, %2").arg(latStr, longStr);
696     return QString::fromLatin1("%1, %2, %3m").arg(latStr, longStr, QString::number(d->alt));
697 }
698 
QGeoCoordinate(QGeoCoordinatePrivate & dd)699 QGeoCoordinate::QGeoCoordinate(QGeoCoordinatePrivate &dd):
700     d(&dd)
701 {
702 }
703 
704 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug dbg,const QGeoCoordinate & coord)705 QDebug operator<<(QDebug dbg, const QGeoCoordinate &coord)
706 {
707     QDebugStateSaver saver(dbg);
708     double lat = coord.latitude();
709     double lng = coord.longitude();
710 
711     QTextStreamManipulator tsm = qSetRealNumberPrecision(11);
712     dbg << tsm;
713     dbg.nospace() << "QGeoCoordinate(";
714     if (qIsNaN(lat))
715         dbg << '?';
716     else
717         dbg << lat;
718     dbg << ", ";
719     if (qIsNaN(lng))
720         dbg << '?';
721     else
722         dbg << lng;
723     if (coord.type() == QGeoCoordinate::Coordinate3D) {
724         dbg << ", ";
725         dbg << coord.altitude();
726     }
727     dbg << ')';
728     return dbg;
729 }
730 #endif
731 
732 #ifndef QT_NO_DATASTREAM
733 /*!
734     \fn QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate)
735 
736     \relates QGeoCoordinate
737 
738     Writes the given \a coordinate to the specified \a stream.
739 
740     \sa {Serializing Qt Data Types}
741 */
742 
operator <<(QDataStream & stream,const QGeoCoordinate & coordinate)743 QDataStream &operator<<(QDataStream &stream, const QGeoCoordinate &coordinate)
744 {
745     stream << coordinate.latitude();
746     stream << coordinate.longitude();
747     stream << coordinate.altitude();
748     return stream;
749 }
750 #endif
751 
752 #ifndef QT_NO_DATASTREAM
753 /*!
754     \fn  QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate)
755     \relates QGeoCoordinate
756 
757     Reads a coordinate from the specified \a stream into the given
758     \a coordinate.
759 
760     \sa {Serializing Qt Data Types}
761 */
762 
operator >>(QDataStream & stream,QGeoCoordinate & coordinate)763 QDataStream &operator>>(QDataStream &stream, QGeoCoordinate &coordinate)
764 {
765     double value;
766     stream >> value;
767     coordinate.setLatitude(value);
768     stream >> value;
769     coordinate.setLongitude(value);
770     stream >> value;
771     coordinate.setAltitude(value);
772     return stream;
773 }
774 #endif
775 
776 /*! \fn uint qHash(const QGeoCoordinate &coordinate, uint seed = 0)
777     \relates QHash
778     \since Qt 5.7
779 
780     Returns a hash value for \a coordinate, using \a seed to seed the calculation.
781 */
qHash(const QGeoCoordinate & coordinate,uint seed)782 uint qHash(const QGeoCoordinate &coordinate, uint seed)
783 {
784     QtPrivate::QHashCombine hash;
785     // north and south pole are geographically equivalent (no matter the longitude)
786     if (coordinate.latitude() != 90.0 && coordinate.latitude() != -90.0)
787         seed = hash(seed, coordinate.longitude());
788     seed = hash(seed, coordinate.latitude());
789     seed = hash(seed, coordinate.altitude());
790     return seed;
791 }
792 
793 QT_END_NAMESPACE
794