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 
40 #include "qgeocircle.h"
41 #include "qgeocircle_p.h"
42 
43 #include "qgeocoordinate.h"
44 #include "qnumeric.h"
45 #include "qlocationutils_p.h"
46 
47 #include "qdoublevector2d_p.h"
48 #include "qdoublevector3d_p.h"
49 #include <cmath>
50 QT_BEGIN_NAMESPACE
51 
52 /*!
53     \class QGeoCircle
54     \inmodule QtPositioning
55     \ingroup QtPositioning-positioning
56     \since 5.2
57 
58     \brief The QGeoCircle class defines a circular geographic area.
59 
60     The circle is defined in terms of a QGeoCoordinate which specifies the
61     center of the circle and a qreal which specifies the radius of the circle
62     in meters.
63 
64     The circle is considered invalid if the center coordinate is invalid
65     or if the radius is less than zero.
66 
67     This class is a \l Q_GADGET since Qt 5.5.  It can be
68     \l{Cpp_value_integration_positioning}{directly used from C++ and QML}.
69 */
70 
71 /*!
72     \property QGeoCircle::center
73     \brief This property holds the center coordinate for the geo circle.
74 
75     The circle is considered invalid if this property contains an invalid
76     coordinate.
77 
78     A default constructed QGeoCircle uses an invalid \l QGeoCoordinate
79     as center.
80 
81     While this property is introduced in Qt 5.5, the related accessor functions
82     exist since the first version of this class.
83 
84     \since 5.5
85 */
86 
87 /*!
88     \property QGeoCircle::radius
89     \brief This property holds the circle radius in meters.
90 
91     The circle is considered invalid if this property is negative.
92 
93     By default, the radius is initialized with \c -1.
94 
95     While this property is introduced in Qt 5.5, the related accessor functions
96     exist since the first version of this class.
97 
98     \since 5.5
99 */
100 
d_func()101 inline QGeoCirclePrivate *QGeoCircle::d_func()
102 {
103     return static_cast<QGeoCirclePrivate *>(d_ptr.data());
104 }
105 
d_func() const106 inline const QGeoCirclePrivate *QGeoCircle::d_func() const
107 {
108     return static_cast<const QGeoCirclePrivate *>(d_ptr.constData());
109 }
110 
111 struct CircleVariantConversions
112 {
CircleVariantConversionsCircleVariantConversions113     CircleVariantConversions()
114     {
115         QMetaType::registerConverter<QGeoShape, QGeoCircle>();
116         QMetaType::registerConverter<QGeoCircle, QGeoShape>();
117     }
118 };
119 
Q_GLOBAL_STATIC(CircleVariantConversions,initCircleConversions)120 Q_GLOBAL_STATIC(CircleVariantConversions, initCircleConversions)
121 
122 /*!
123     Constructs a new, invalid geo circle.
124 */
125 QGeoCircle::QGeoCircle()
126 :   QGeoShape(new QGeoCirclePrivate)
127 {
128     initCircleConversions();
129 }
130 
131 /*!
132     Constructs a new geo circle centered at \a center and with a radius of \a radius meters.
133 */
QGeoCircle(const QGeoCoordinate & center,qreal radius)134 QGeoCircle::QGeoCircle(const QGeoCoordinate &center, qreal radius)
135 {
136     initCircleConversions();
137     d_ptr = new QGeoCirclePrivate(center, radius);
138 }
139 
140 /*!
141     Constructs a new geo circle from the contents of \a other.
142 */
QGeoCircle(const QGeoCircle & other)143 QGeoCircle::QGeoCircle(const QGeoCircle &other)
144 :   QGeoShape(other)
145 {
146     initCircleConversions();
147 }
148 
149 /*!
150     Constructs a new geo circle from the contents of \a other.
151 */
QGeoCircle(const QGeoShape & other)152 QGeoCircle::QGeoCircle(const QGeoShape &other)
153 :   QGeoShape(other)
154 {
155     initCircleConversions();
156     if (type() != QGeoShape::CircleType)
157         d_ptr = new QGeoCirclePrivate;
158 }
159 
160 /*!
161     Destroys this geo circle.
162 */
~QGeoCircle()163 QGeoCircle::~QGeoCircle() {}
164 
165 /*!
166     Assigns \a other to this geo circle and returns a reference to this geo circle.
167 */
operator =(const QGeoCircle & other)168 QGeoCircle &QGeoCircle::operator=(const QGeoCircle &other)
169 {
170     QGeoShape::operator=(other);
171     return *this;
172 }
173 
174 /*!
175     Returns whether this geo circle is equal to \a other.
176 */
operator ==(const QGeoCircle & other) const177 bool QGeoCircle::operator==(const QGeoCircle &other) const
178 {
179     Q_D(const QGeoCircle);
180 
181     return *d == *other.d_func();
182 }
183 
184 /*!
185     Returns whether this geo circle is not equal to \a other.
186 */
operator !=(const QGeoCircle & other) const187 bool QGeoCircle::operator!=(const QGeoCircle &other) const
188 {
189     Q_D(const QGeoCircle);
190 
191     return !(*d == *other.d_func());
192 }
193 
isValid() const194 bool QGeoCirclePrivate::isValid() const
195 {
196     return m_center.isValid() && !qIsNaN(m_radius) && m_radius >= -1e-7;
197 }
198 
isEmpty() const199 bool QGeoCirclePrivate::isEmpty() const
200 {
201     return !isValid() || m_radius <= 1e-7;
202 }
203 
204 /*!
205     Sets the center coordinate of this geo circle to \a center.
206 */
setCenter(const QGeoCoordinate & center)207 void QGeoCircle::setCenter(const QGeoCoordinate &center)
208 {
209     Q_D(QGeoCircle);
210 
211     d->setCenter(center);
212 }
213 
214 /*!
215     Returns the center coordinate of this geo circle. Equivalent to QGeoShape::center().
216 */
center() const217 QGeoCoordinate QGeoCircle::center() const
218 {
219     Q_D(const QGeoCircle);
220 
221     return d->center();
222 }
223 
224 /*!
225     Sets the radius in meters of this geo circle to \a radius.
226 */
setRadius(qreal radius)227 void QGeoCircle::setRadius(qreal radius)
228 {
229     Q_D(QGeoCircle);
230 
231     d->setRadius(radius);
232 }
233 
234 /*!
235     Returns the radius in meters of this geo circle.
236 */
radius() const237 qreal QGeoCircle::radius() const
238 {
239     Q_D(const QGeoCircle);
240 
241     return d->m_radius;
242 }
243 
contains(const QGeoCoordinate & coordinate) const244 bool QGeoCirclePrivate::contains(const QGeoCoordinate &coordinate) const
245 {
246     if (!isValid() || !coordinate.isValid())
247         return false;
248 
249     // see QTBUG-41447 for details
250     qreal distance = m_center.distanceTo(coordinate);
251     if (qFuzzyCompare(distance, m_radius) || distance <= m_radius)
252         return true;
253 
254     return false;
255 }
256 
center() const257 QGeoCoordinate QGeoCirclePrivate::center() const
258 {
259     return m_center;
260 }
261 
boundingGeoRectangle() const262 QGeoRectangle QGeoCirclePrivate::boundingGeoRectangle() const
263 {
264     return m_bbox;
265 }
266 
updateBoundingBox()267 void QGeoCirclePrivate::updateBoundingBox()
268 {
269     if (isEmpty()) {
270         if (m_center.isValid()) {
271             m_bbox.setTopLeft(m_center);
272             m_bbox.setBottomRight(m_center);
273         }
274         return;
275     }
276 
277     bool crossNorth = crossNorthPole();
278     bool crossSouth = crossSouthPole();
279 
280     if (crossNorth && crossSouth) {
281         // Circle crossing both poles fills the whole map
282         m_bbox = QGeoRectangle(QGeoCoordinate(90.0, -180.0), QGeoCoordinate(-90.0, 180.0));
283     } else if (crossNorth) {
284         // Circle crossing one pole fills the map in the longitudinal direction
285         m_bbox = QGeoRectangle(QGeoCoordinate(90.0, -180.0), QGeoCoordinate(m_center.atDistanceAndAzimuth(m_radius, 180.0).latitude(), 180.0));
286     } else if (crossSouth) {
287         m_bbox = QGeoRectangle(QGeoCoordinate(m_center.atDistanceAndAzimuth(m_radius, 0.0).latitude(), -180.0), QGeoCoordinate(-90, 180.0));
288     } else {
289         // Regular circle not crossing anything
290 
291         // Calculate geo bounding box of the circle
292         //
293         // A circle tangential point with a meridian, together with pole and
294         // the circle center create a spherical triangle.
295         // Finding the tangential point with the spherical law of sines:
296         //
297         // * lon_delta_in_rad : delta between the circle center and a tangential
298         //   point (absolute value).
299         // * r_in_rad : angular radius of the circle
300         // * lat_in_rad : latitude of the circle center
301         // * alpha_in_rad : angle between meridian and radius of the circle.
302         //   At the tangential point, sin(alpha_in_rad) == 1.
303         // * lat_delta_in_rad - absolute delta of latitudes between the circle center and
304         //   any of the two points where the great circle going through the circle
305         //   center and the pole crosses the circle. In other words, the points
306         //   on the circle with azimuth 0 or 180.
307         //
308         //  Using:
309         //  sin(lon_delta_in_rad)/sin(r_in_rad) = sin(alpha_in_rad)/sin(pi/2 - lat_in_rad)
310 
311         double r_in_rad = m_radius / QLocationUtils::earthMeanRadius(); // angular r
312         double lat_delta_in_deg = QLocationUtils::degrees(r_in_rad);
313         double lon_delta_in_deg = QLocationUtils::degrees(std::asin(
314                std::sin(r_in_rad) /
315                std::cos(QLocationUtils::radians(m_center.latitude()))
316             ));
317 
318         QGeoCoordinate topLeft;
319         topLeft.setLatitude(QLocationUtils::clipLat(m_center.latitude() + lat_delta_in_deg));
320         topLeft.setLongitude(QLocationUtils::wrapLong(m_center.longitude() - lon_delta_in_deg));
321         QGeoCoordinate bottomRight;
322         bottomRight.setLatitude(QLocationUtils::clipLat(m_center.latitude() - lat_delta_in_deg));
323         bottomRight.setLongitude(QLocationUtils::wrapLong(m_center.longitude() + lon_delta_in_deg));
324 
325         m_bbox = QGeoRectangle(topLeft, bottomRight);
326     }
327 }
328 
setCenter(const QGeoCoordinate & c)329 void QGeoCirclePrivate::setCenter(const QGeoCoordinate &c)
330 {
331     m_center = c;
332     updateBoundingBox();
333 }
334 
setRadius(const qreal r)335 void QGeoCirclePrivate::setRadius(const qreal r)
336 {
337     m_radius = r;
338     updateBoundingBox();
339 }
340 
crossNorthPole() const341 bool QGeoCirclePrivate::crossNorthPole() const
342 {
343     const QGeoCoordinate northPole(90.0, m_center.longitude());
344     qreal distanceToPole = m_center.distanceTo(northPole);
345     if (distanceToPole < m_radius)
346         return true;
347     return false;
348 }
349 
crossSouthPole() const350 bool QGeoCirclePrivate::crossSouthPole() const
351 {
352     const QGeoCoordinate southPole(-90.0, m_center.longitude());
353     qreal distanceToPole = m_center.distanceTo(southPole);
354     if (distanceToPole < m_radius)
355         return true;
356     return false;
357 }
358 
359 /*
360   Extends the circle to include \a coordinate.
361 */
extendShape(const QGeoCoordinate & coordinate)362 void QGeoCirclePrivate::extendShape(const QGeoCoordinate &coordinate)
363 {
364     if (!isValid() || !coordinate.isValid() || contains(coordinate))
365         return;
366 
367     setRadius(m_center.distanceTo(coordinate));
368 }
369 
370 /*!
371     Translates this geo circle by \a degreesLatitude northwards and \a degreesLongitude eastwards.
372 
373     Negative values of \a degreesLatitude and \a degreesLongitude correspond to
374     southward and westward translation respectively.
375 */
translate(double degreesLatitude,double degreesLongitude)376 void QGeoCircle::translate(double degreesLatitude, double degreesLongitude)
377 {
378     // TODO handle dlat, dlon larger than 360 degrees
379 
380     Q_D(QGeoCircle);
381 
382     double lat = d->m_center.latitude();
383     double lon = d->m_center.longitude();
384 
385     lat += degreesLatitude;
386     lon += degreesLongitude;
387     lon = QLocationUtils::wrapLong(lon);
388 
389     // TODO: remove this and simply clip latitude.
390     if (lat > 90.0) {
391         lat = 180.0 - lat;
392         if (lon < 0.0)
393             lon = 180.0;
394         else
395             lon -= 180;
396     }
397 
398     if (lat < -90.0) {
399         lat = 180.0 + lat;
400         if (lon < 0.0)
401             lon = 180.0;
402         else
403             lon -= 180;
404     }
405 
406     d->setCenter(QGeoCoordinate(lat, lon));
407 }
408 
409 /*!
410     Returns a copy of this geo circle translated by \a degreesLatitude northwards and
411     \a degreesLongitude eastwards.
412 
413     Negative values of \a degreesLatitude and \a degreesLongitude correspond to
414     southward and westward translation respectively.
415 
416     \sa translate()
417 */
translated(double degreesLatitude,double degreesLongitude) const418 QGeoCircle QGeoCircle::translated(double degreesLatitude, double degreesLongitude) const
419 {
420     QGeoCircle result(*this);
421     result.translate(degreesLatitude, degreesLongitude);
422     return result;
423 }
424 
425 /*!
426     Extends the geo circle to also cover the coordinate \a coordinate
427 
428     \since 5.9
429 */
extendCircle(const QGeoCoordinate & coordinate)430 void QGeoCircle::extendCircle(const QGeoCoordinate &coordinate)
431 {
432     Q_D(QGeoCircle);
433     d->extendShape(coordinate);
434 }
435 
436 /*!
437     Returns the geo circle properties as a string.
438 
439     \since 5.5
440 */
441 
toString() const442 QString QGeoCircle::toString() const
443 {
444     if (type() != QGeoShape::CircleType) {
445         qWarning("Not a circle");
446         return QStringLiteral("QGeoCircle(not a circle)");
447     }
448 
449     return QStringLiteral("QGeoCircle({%1, %2}, %3)")
450         .arg(center().latitude())
451         .arg(center().longitude())
452         .arg(radius());
453 }
454 
455 /*******************************************************************************
456 *******************************************************************************/
457 
QGeoCirclePrivate()458 QGeoCirclePrivate::QGeoCirclePrivate()
459 :   QGeoShapePrivate(QGeoShape::CircleType), m_radius(-1.0)
460 {
461 }
462 
QGeoCirclePrivate(const QGeoCoordinate & center,qreal radius)463 QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCoordinate &center, qreal radius)
464 :   QGeoShapePrivate(QGeoShape::CircleType), m_center(center), m_radius(radius)
465 {
466     updateBoundingBox();
467 }
468 
QGeoCirclePrivate(const QGeoCirclePrivate & other)469 QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCirclePrivate &other)
470 :   QGeoShapePrivate(QGeoShape::CircleType), m_center(other.m_center),
471     m_radius(other.m_radius), m_bbox(other.m_bbox)
472 {
473 }
474 
~QGeoCirclePrivate()475 QGeoCirclePrivate::~QGeoCirclePrivate() {}
476 
clone() const477 QGeoShapePrivate *QGeoCirclePrivate::clone() const
478 {
479     return new QGeoCirclePrivate(*this);
480 }
481 
operator ==(const QGeoShapePrivate & other) const482 bool QGeoCirclePrivate::operator==(const QGeoShapePrivate &other) const
483 {
484     if (!QGeoShapePrivate::operator==(other))
485         return false;
486 
487     const QGeoCirclePrivate &otherCircle = static_cast<const QGeoCirclePrivate &>(other);
488 
489     return m_radius == otherCircle.m_radius && m_center == otherCircle.m_center;
490 }
491 
492 QT_END_NAMESPACE
493