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