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