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>
50QT_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
101inline QGeoCirclePrivate *QGeoCircle::d_func()
102{
103 return static_cast<QGeoCirclePrivate *>(d_ptr.data());
104}
105
106inline const QGeoCirclePrivate *QGeoCircle::d_func() const
107{
108 return static_cast<const QGeoCirclePrivate *>(d_ptr.constData());
109}
110
111struct CircleVariantConversions
112{
113 CircleVariantConversions()
114 {
115 QMetaType::registerConverter<QGeoShape, QGeoCircle>();
116 QMetaType::registerConverter<QGeoCircle, QGeoShape>();
117 }
118};
119
120Q_GLOBAL_STATIC(CircleVariantConversions, initCircleConversions)
121
122/*!
123 Constructs a new, invalid geo circle.
124*/
125QGeoCircle::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*/
134QGeoCircle::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*/
143QGeoCircle::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*/
152QGeoCircle::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*/
163QGeoCircle::~QGeoCircle() {}
164
165/*!
166 Assigns \a other to this geo circle and returns a reference to this geo circle.
167*/
168QGeoCircle &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*/
177bool 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*/
187bool QGeoCircle::operator!=(const QGeoCircle &other) const
188{
189 Q_D(const QGeoCircle);
190
191 return !(*d == *other.d_func());
192}
193
194bool QGeoCirclePrivate::isValid() const
195{
196 return m_center.isValid() && !qIsNaN(d: m_radius) && m_radius >= -1e-7;
197}
198
199bool 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*/
207void 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*/
217QGeoCoordinate 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*/
227void 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*/
237qreal QGeoCircle::radius() const
238{
239 Q_D(const QGeoCircle);
240
241 return d->m_radius;
242}
243
244bool 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(other: coordinate);
251 if (qFuzzyCompare(p1: distance, p2: m_radius) || distance <= m_radius)
252 return true;
253
254 return false;
255}
256
257QGeoCoordinate QGeoCirclePrivate::center() const
258{
259 return m_center;
260}
261
262QGeoRectangle QGeoCirclePrivate::boundingGeoRectangle() const
263{
264 return m_bbox;
265}
266
267void 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(distance: m_radius, azimuth: 180.0).latitude(), 180.0));
286 } else if (crossSouth) {
287 m_bbox = QGeoRectangle(QGeoCoordinate(m_center.atDistanceAndAzimuth(distance: m_radius, azimuth: 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(radians: r_in_rad);
313 double lon_delta_in_deg = QLocationUtils::degrees(radians: std::asin(
314 x: std::sin(x: r_in_rad) /
315 std::cos(x: QLocationUtils::radians(degrees: m_center.latitude()))
316 ));
317
318 QGeoCoordinate topLeft;
319 topLeft.setLatitude(QLocationUtils::clipLat(lat: m_center.latitude() + lat_delta_in_deg));
320 topLeft.setLongitude(QLocationUtils::wrapLong(lng: m_center.longitude() - lon_delta_in_deg));
321 QGeoCoordinate bottomRight;
322 bottomRight.setLatitude(QLocationUtils::clipLat(lat: m_center.latitude() - lat_delta_in_deg));
323 bottomRight.setLongitude(QLocationUtils::wrapLong(lng: m_center.longitude() + lon_delta_in_deg));
324
325 m_bbox = QGeoRectangle(topLeft, bottomRight);
326 }
327}
328
329void QGeoCirclePrivate::setCenter(const QGeoCoordinate &c)
330{
331 m_center = c;
332 updateBoundingBox();
333}
334
335void QGeoCirclePrivate::setRadius(const qreal r)
336{
337 m_radius = r;
338 updateBoundingBox();
339}
340
341bool QGeoCirclePrivate::crossNorthPole() const
342{
343 const QGeoCoordinate northPole(90.0, m_center.longitude());
344 qreal distanceToPole = m_center.distanceTo(other: northPole);
345 if (distanceToPole < m_radius)
346 return true;
347 return false;
348}
349
350bool QGeoCirclePrivate::crossSouthPole() const
351{
352 const QGeoCoordinate southPole(-90.0, m_center.longitude());
353 qreal distanceToPole = m_center.distanceTo(other: southPole);
354 if (distanceToPole < m_radius)
355 return true;
356 return false;
357}
358
359/*
360 Extends the circle to include \a coordinate.
361*/
362void QGeoCirclePrivate::extendShape(const QGeoCoordinate &coordinate)
363{
364 if (!isValid() || !coordinate.isValid() || contains(coordinate))
365 return;
366
367 setRadius(m_center.distanceTo(other: 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*/
376void 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(lng: 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*/
418QGeoCircle 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*/
430void 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
442QString QGeoCircle::toString() const
443{
444 if (type() != QGeoShape::CircleType) {
445 qWarning(msg: "Not a circle");
446 return QStringLiteral("QGeoCircle(not a circle)");
447 }
448
449 return QStringLiteral("QGeoCircle({%1, %2}, %3)")
450 .arg(a: center().latitude())
451 .arg(a: center().longitude())
452 .arg(a: radius());
453}
454
455/*******************************************************************************
456*******************************************************************************/
457
458QGeoCirclePrivate::QGeoCirclePrivate()
459: QGeoShapePrivate(QGeoShape::CircleType), m_radius(-1.0)
460{
461}
462
463QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCoordinate &center, qreal radius)
464: QGeoShapePrivate(QGeoShape::CircleType), m_center(center), m_radius(radius)
465{
466 updateBoundingBox();
467}
468
469QGeoCirclePrivate::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
475QGeoCirclePrivate::~QGeoCirclePrivate() {}
476
477QGeoShapePrivate *QGeoCirclePrivate::clone() const
478{
479 return new QGeoCirclePrivate(*this);
480}
481
482bool 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
492QT_END_NAMESPACE
493

source code of qtlocation/src/positioning/qgeocircle.cpp