Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgeocircle.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qgeocircle.h"
5#include "qgeocircle_p.h"
6
8#include "qnumeric.h"
10
13#include <cmath>
15
16QT_IMPL_METATYPE_EXTERN(QGeoCircle)
17
18/*!
19 \class QGeoCircle
20 \inmodule QtPositioning
21 \ingroup QtPositioning-positioning
22 \since 5.2
23
24 \brief The QGeoCircle class defines a circular geographic area.
25
26 The circle is defined in terms of a QGeoCoordinate which specifies the
27 center of the circle and a qreal which specifies the radius of the circle
28 in meters.
29
30 The circle is considered invalid if the center coordinate is invalid
31 or if the radius is less than zero.
32
33 This class is also accessible in QML as \l[QML]{geoCircle}.
34*/
35
36/*!
37 \property QGeoCircle::center
38 \brief This property holds the center coordinate for the geo circle.
39
40 The circle is considered invalid if this property contains an invalid
41 coordinate.
42
43 A default constructed QGeoCircle uses an invalid \l QGeoCoordinate
44 as center.
45
46 While this property is introduced in Qt 5.5, the related accessor functions
47 exist since the first version of this class.
48
49 \since 5.5
50*/
51
52/*!
53 \property QGeoCircle::radius
54 \brief This property holds the circle radius in meters.
55
56 The circle is considered invalid if this property is negative.
57
58 By default, the radius is initialized with \c -1.
59
60 While this property is introduced in Qt 5.5, the related accessor functions
61 exist since the first version of this class.
62
63 \since 5.5
64*/
65
66inline QGeoCirclePrivate *QGeoCircle::d_func()
67{
68 return static_cast<QGeoCirclePrivate *>(d_ptr.data());
69}
70
71inline const QGeoCirclePrivate *QGeoCircle::d_func() const
72{
73 return static_cast<const QGeoCirclePrivate *>(d_ptr.constData());
74}
75
76/*!
77 Constructs a new, invalid geo circle.
78*/
79QGeoCircle::QGeoCircle()
80: QGeoShape(new QGeoCirclePrivate)
81{
82}
83
84/*!
85 Constructs a new geo circle centered at \a center and with a radius of \a radius meters.
86*/
87QGeoCircle::QGeoCircle(const QGeoCoordinate &center, qreal radius)
88{
89 d_ptr = new QGeoCirclePrivate(center, radius);
90}
91
92/*!
93 Constructs a new geo circle from the contents of \a other.
94*/
95QGeoCircle::QGeoCircle(const QGeoCircle &other)
96: QGeoShape(other)
97{
98}
99
100/*!
101 Constructs a new geo circle from the contents of \a other.
102*/
103QGeoCircle::QGeoCircle(const QGeoShape &other)
104: QGeoShape(other)
105{
106 if (type() != QGeoShape::CircleType)
107 d_ptr = new QGeoCirclePrivate;
108}
109
110/*!
111 Destroys this geo circle.
112*/
113QGeoCircle::~QGeoCircle() {}
114
115/*!
116 Assigns \a other to this geo circle and returns a reference to this geo circle.
117*/
118QGeoCircle &QGeoCircle::operator=(const QGeoCircle &other)
119{
120 QGeoShape::operator=(other);
121 return *this;
122}
123/*!
124 * \class QGeoCirclePrivate
125 * \inmodule QtPositioning
126 * \internal
127 */
128bool QGeoCirclePrivate::isValid() const
129{
130 return m_center.isValid() && !qIsNaN(m_radius) && m_radius >= -1e-7;
131}
132
133bool QGeoCirclePrivate::isEmpty() const
134{
135 return !isValid() || m_radius <= 1e-7;
136}
137
138/*!
139 Sets the center coordinate of this geo circle to \a center.
140*/
141void QGeoCircle::setCenter(const QGeoCoordinate &center)
142{
143 Q_D(QGeoCircle);
144
145 d->setCenter(center);
146}
147
148/*!
149 Returns the center coordinate of this geo circle. Equivalent to QGeoShape::center().
150*/
151QGeoCoordinate QGeoCircle::center() const
152{
153 Q_D(const QGeoCircle);
154
155 return d->center();
156}
157
158/*!
159 Sets the radius in meters of this geo circle to \a radius.
160*/
161void QGeoCircle::setRadius(qreal radius)
162{
163 Q_D(QGeoCircle);
164
165 d->setRadius(radius);
166}
167
168/*!
169 Returns the radius in meters of this geo circle.
170*/
171qreal QGeoCircle::radius() const
172{
173 Q_D(const QGeoCircle);
174
175 return d->m_radius;
176}
177
178bool QGeoCirclePrivate::contains(const QGeoCoordinate &coordinate) const
179{
180 if (!isValid() || !coordinate.isValid())
181 return false;
182
183 // see QTBUG-41447 for details
184 qreal distance = m_center.distanceTo(coordinate);
185 if (qFuzzyCompare(distance, m_radius) || distance <= m_radius)
186 return true;
187
188 return false;
189}
190
191QGeoCoordinate QGeoCirclePrivate::center() const
192{
193 return m_center;
194}
195
196QGeoRectangle QGeoCirclePrivate::boundingGeoRectangle() const
197{
198 return m_bbox;
199}
200
201void QGeoCirclePrivate::updateBoundingBox()
202{
203 if (isEmpty()) {
204 if (m_center.isValid()) {
205 m_bbox.setTopLeft(m_center);
206 m_bbox.setBottomRight(m_center);
207 }
208 return;
209 }
210
211 bool crossNorth = crossNorthPole();
212 bool crossSouth = crossSouthPole();
213
214 if (crossNorth && crossSouth) {
215 // Circle crossing both poles fills the whole map
216 m_bbox = QGeoRectangle(QGeoCoordinate(90.0, -180.0), QGeoCoordinate(-90.0, 180.0));
217 } else if (crossNorth) {
218 // Circle crossing one pole fills the map in the longitudinal direction
219 m_bbox = QGeoRectangle(QGeoCoordinate(90.0, -180.0), QGeoCoordinate(m_center.atDistanceAndAzimuth(m_radius, 180.0).latitude(), 180.0));
220 } else if (crossSouth) {
221 m_bbox = QGeoRectangle(QGeoCoordinate(m_center.atDistanceAndAzimuth(m_radius, 0.0).latitude(), -180.0), QGeoCoordinate(-90, 180.0));
222 } else {
223 // Regular circle not crossing anything
224
225 // Calculate geo bounding box of the circle
226 //
227 // A circle tangential point with a meridian, together with pole and
228 // the circle center create a spherical triangle.
229 // Finding the tangential point with the spherical law of sines:
230 //
231 // * lon_delta_in_rad : delta between the circle center and a tangential
232 // point (absolute value).
233 // * r_in_rad : angular radius of the circle
234 // * lat_in_rad : latitude of the circle center
235 // * alpha_in_rad : angle between meridian and radius of the circle.
236 // At the tangential point, sin(alpha_in_rad) == 1.
237 // * lat_delta_in_rad - absolute delta of latitudes between the circle center and
238 // any of the two points where the great circle going through the circle
239 // center and the pole crosses the circle. In other words, the points
240 // on the circle with azimuth 0 or 180.
241 //
242 // Using:
243 // sin(lon_delta_in_rad)/sin(r_in_rad) = sin(alpha_in_rad)/sin(pi/2 - lat_in_rad)
244
245 double r_in_rad = m_radius / QLocationUtils::earthMeanRadius(); // angular r
246 double lat_delta_in_deg = QLocationUtils::degrees(r_in_rad);
247 double lon_delta_in_deg = QLocationUtils::degrees(std::asin(
248 std::sin(r_in_rad) /
249 std::cos(QLocationUtils::radians(m_center.latitude()))
250 ));
251
252 QGeoCoordinate topLeft;
253 topLeft.setLatitude(QLocationUtils::clipLat(m_center.latitude() + lat_delta_in_deg));
254 topLeft.setLongitude(QLocationUtils::wrapLong(m_center.longitude() - lon_delta_in_deg));
255 QGeoCoordinate bottomRight;
256 bottomRight.setLatitude(QLocationUtils::clipLat(m_center.latitude() - lat_delta_in_deg));
257 bottomRight.setLongitude(QLocationUtils::wrapLong(m_center.longitude() + lon_delta_in_deg));
258
259 m_bbox = QGeoRectangle(topLeft, bottomRight);
260 }
261}
262
263void QGeoCirclePrivate::setCenter(const QGeoCoordinate &c)
264{
265 m_center = c;
266 updateBoundingBox();
267}
268
269void QGeoCirclePrivate::setRadius(const qreal r)
270{
271 m_radius = r;
272 updateBoundingBox();
273}
274
275bool QGeoCirclePrivate::crossNorthPole() const
276{
277 const QGeoCoordinate northPole(90.0, m_center.longitude());
278 qreal distanceToPole = m_center.distanceTo(northPole);
279 if (distanceToPole < m_radius)
280 return true;
281 return false;
282}
283
284bool QGeoCirclePrivate::crossSouthPole() const
285{
286 const QGeoCoordinate southPole(-90.0, m_center.longitude());
287 qreal distanceToPole = m_center.distanceTo(southPole);
288 if (distanceToPole < m_radius)
289 return true;
290 return false;
291}
292
293/*
294 Extends the circle to include \a coordinate.
295*/
296void QGeoCirclePrivate::extendCircle(const QGeoCoordinate &coordinate)
297{
298 if (!isValid() || !coordinate.isValid() || contains(coordinate))
299 return;
300
301 setRadius(m_center.distanceTo(coordinate));
302}
303
304/*!
305 Translates this geo circle by \a degreesLatitude northwards and \a degreesLongitude eastwards.
306
307 Negative values of \a degreesLatitude and \a degreesLongitude correspond to
308 southward and westward translation respectively.
309*/
310void QGeoCircle::translate(double degreesLatitude, double degreesLongitude)
311{
312 // TODO handle dlat, dlon larger than 360 degrees
313
314 Q_D(QGeoCircle);
315
316 double lat = d->m_center.latitude();
317 double lon = d->m_center.longitude();
318
319 lat += degreesLatitude;
320 lon += degreesLongitude;
321 lon = QLocationUtils::wrapLong(lon);
322
323 // TODO: remove this and simply clip latitude.
324 if (lat > 90.0) {
325 lat = 180.0 - lat;
326 if (lon < 0.0)
327 lon = 180.0;
328 else
329 lon -= 180;
330 }
331
332 if (lat < -90.0) {
333 lat = 180.0 + lat;
334 if (lon < 0.0)
335 lon = 180.0;
336 else
337 lon -= 180;
338 }
339
340 d->setCenter(QGeoCoordinate(lat, lon));
341}
342
343/*!
344 Returns a copy of this geo circle translated by \a degreesLatitude northwards and
345 \a degreesLongitude eastwards.
346
347 Negative values of \a degreesLatitude and \a degreesLongitude correspond to
348 southward and westward translation respectively.
349
350 \sa translate()
351*/
352QGeoCircle QGeoCircle::translated(double degreesLatitude, double degreesLongitude) const
353{
354 QGeoCircle result(*this);
355 result.translate(degreesLatitude, degreesLongitude);
356 return result;
357}
358
359/*!
360 Extends the geo circle to also cover the coordinate \a coordinate
361
362 \since 5.9
363*/
364void QGeoCircle::extendCircle(const QGeoCoordinate &coordinate)
365{
366 Q_D(QGeoCircle);
367 d->extendCircle(coordinate);
368}
369
370/*!
371 Returns the geo circle properties as a string.
372
373 \since 5.5
374*/
375
376QString QGeoCircle::toString() const
377{
378 if (type() != QGeoShape::CircleType) {
379 qWarning("Not a circle");
380 return QStringLiteral("QGeoCircle(not a circle)");
381 }
382
383 return QStringLiteral("QGeoCircle({%1, %2}, %3)")
384 .arg(center().latitude())
385 .arg(center().longitude())
386 .arg(radius());
387}
388
389/*******************************************************************************
390*******************************************************************************/
391
392QGeoCirclePrivate::QGeoCirclePrivate()
393: QGeoShapePrivate(QGeoShape::CircleType), m_radius(-1.0)
394{
395}
396
397QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCoordinate &center, qreal radius)
398: QGeoShapePrivate(QGeoShape::CircleType), m_center(center), m_radius(radius)
399{
400 updateBoundingBox();
401}
402
403QGeoCirclePrivate::QGeoCirclePrivate(const QGeoCirclePrivate &other)
404: QGeoShapePrivate(QGeoShape::CircleType), m_center(other.m_center),
405 m_radius(other.m_radius), m_bbox(other.m_bbox)
406{
407}
408
409QGeoCirclePrivate::~QGeoCirclePrivate() {}
410
411QGeoShapePrivate *QGeoCirclePrivate::clone() const
412{
413 return new QGeoCirclePrivate(*this);
414}
415
416bool QGeoCirclePrivate::operator==(const QGeoShapePrivate &other) const
417{
418 if (!QGeoShapePrivate::operator==(other))
419 return false;
420
421 const QGeoCirclePrivate &otherCircle = static_cast<const QGeoCirclePrivate &>(other);
422
423 return m_radius == otherCircle.m_radius && m_center == otherCircle.m_center;
424}
425
426size_t QGeoCirclePrivate::hash(size_t seed) const
427{
428 return qHashMulti(seed, m_center, m_radius);
429}
430
431QT_END_NAMESPACE
432
433#include "moc_qgeocircle.cpp"
Combined button and popup list for selecting options.