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
qgeopolygon.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 "qgeopolygon.h"
6#include "qgeopath_p.h"
7#include "qgeocircle.h"
8
10#include "qnumeric.h"
12#include "qwebmercator_p.h"
13
16#include "qwebmercator_p.h"
17
19
20QT_IMPL_METATYPE_EXTERN(QGeoPolygon)
21
22constexpr int kMaxInt = std::numeric_limits<int>::max();
23constexpr auto kTooManyHoles = u"The polygon has more holes than fit into an int. "
24 "This can cause errors while querying holes from QML";
25constexpr auto kTooManyElements = u"The polygon has more elements than fit into an int. "
26 "This can cause errors while querying elements from QML";
27
28/*!
29 \class QGeoPolygon
30 \inmodule QtPositioning
31 \ingroup QtPositioning-positioning
32 \since 5.10
33
34 \brief The QGeoPolygon class defines a geographic polygon.
35
36 The polygon is defined by an ordered list of \l QGeoCoordinate objects
37 representing its perimeter.
38
39 Each two adjacent elements in this list are intended to be connected
40 together by the shortest line segment of constant bearing passing
41 through both elements.
42 This type of connection can cross the date line in the longitudinal direction,
43 but never crosses the poles.
44
45 This is relevant for the calculation of the bounding box returned by
46 \l QGeoShape::boundingGeoRectangle() for this shape, which will have the latitude of
47 the top left corner set to the maximum latitude in the path point set.
48 Similarly, the latitude of the bottom right corner will be the minimum latitude
49 in the path point set.
50
51 This class is a \l Q_GADGET.
52 It can be \l{Cpp_value_integration_positioning}{directly used from C++ and QML}.
53*/
54
55/*
56 \property QGeoPolygon::path
57 \brief This property holds the list of coordinates for the geo polygon.
58
59 The polygon is both invalid and empty if it contains no coordinate.
60
61 A default constructed QGeoPolygon is therefore invalid.
62*/
63
64inline QGeoPolygonPrivate *QGeoPolygon::d_func()
65{
66 return static_cast<QGeoPolygonPrivate *>(d_ptr.data());
67}
68
69inline const QGeoPolygonPrivate *QGeoPolygon::d_func() const
70{
71 return static_cast<const QGeoPolygonPrivate *>(d_ptr.constData());
72}
73
74/*!
75 Constructs a new, empty geo polygon.
76*/
77QGeoPolygon::QGeoPolygon()
78: QGeoShape(new QGeoPolygonPrivate())
79{
80}
81
82/*!
83 Constructs a new geo polygon from the coordinates specified
84 in \a path.
85*/
86QGeoPolygon::QGeoPolygon(const QList<QGeoCoordinate> &path)
87: QGeoShape(new QGeoPolygonPrivate(path))
88{
89}
90
91/*!
92 Constructs a new geo polygon from the contents of \a other.
93*/
94QGeoPolygon::QGeoPolygon(const QGeoPolygon &other)
95: QGeoShape(other)
96{
97}
98
99static void calculatePeripheralPoints(QList<QGeoCoordinate> &path,
100 const QGeoCircle &circle,
101 int steps)
102{
103 const QGeoCoordinate &center = circle.center();
104 const qreal distance = circle.radius();
105 // Calculate points based on great-circle distance
106 // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function
107 // but tweaked here for computing multiple points
108
109 // pre-calculations
110 steps = qMax(steps, 3);
111 qreal centerLon = center.longitude();
112 qreal latRad = QLocationUtils::radians(center.latitude());
113 qreal lonRad = QLocationUtils::radians(centerLon);
114 qreal cosLatRad = std::cos(latRad);
115 qreal sinLatRad = std::sin(latRad);
116 qreal ratio = (distance / QLocationUtils::earthMeanRadius());
117 qreal cosRatio = std::cos(ratio);
118 qreal sinRatio = std::sin(ratio);
119 qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio;
120 qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio;
121 for (int i = 0; i < steps; ++i) {
122 qreal azimuthRad = 2 * M_PI * i / steps;
123 qreal resultLatRad = std::asin(sinLatRad_x_cosRatio
124 + cosLatRad_x_sinRatio * std::cos(azimuthRad));
125 qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio,
126 cosRatio - sinLatRad * std::sin(resultLatRad));
127 qreal lat2 = QLocationUtils::degrees(resultLatRad);
128 qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad));
129
130 path << QGeoCoordinate(lat2, lon2, center.altitude());
131 }
132}
133
134/*!
135 Constructs a new geo polygon from the contents of \a other.
136*/
137QGeoPolygon::QGeoPolygon(const QGeoShape &other)
138: QGeoShape(other)
139{
140 if (type() != QGeoShape::PolygonType) {
141 QGeoPolygonPrivate *poly = new QGeoPolygonPrivate();
142 if (type() == QGeoShape::CircleType) {
143 const QGeoCircle &circle = static_cast<const QGeoCircle &>(other);
144 QList<QGeoCoordinate> perimeter;
145 calculatePeripheralPoints(perimeter, circle, 128);
146 poly->setPath(perimeter);
147 } else if (type() == QGeoShape::RectangleType) {
148 const QGeoRectangle &rect = static_cast<const QGeoRectangle &>(other);
149 QList<QGeoCoordinate> perimeter;
150 perimeter << rect.topLeft() << rect.topRight()
151 << rect.bottomRight() << rect.bottomLeft();
152 poly->setPath(perimeter);
153 }
154 d_ptr = poly;
155 }
156}
157
158/*!
159 Destroys this polygon.
160*/
161QGeoPolygon::~QGeoPolygon() {}
162
163/*!
164 Assigns \a other to this geo polygon and returns a reference to this geo polygon.
165*/
166QGeoPolygon &QGeoPolygon::operator=(const QGeoPolygon &other)
167{
168 QGeoShape::operator=(other);
169 return *this;
170}
171
172/*!
173 Sets the perimeter of the polygon based on a list of coordinates \a path.
174
175 \since QtPositioning 5.12
176*/
177void QGeoPolygon::setPerimeter(const QList<QGeoCoordinate> &path)
178{
179 Q_D(QGeoPolygon);
180 return d->setPath(path);
181}
182
183/*!
184 \property QGeoPolygon::perimeter
185 \brief the coordinates of the polygon's perimeter.
186*/
187
188/*!
189 Returns all the elements of the polygon's perimeter.
190
191 \since QtPositioning 5.12
192*/
193const QList<QGeoCoordinate> &QGeoPolygon::perimeter() const
194{
195 Q_D(const QGeoPolygon);
196 return d->path();
197}
198
199/*!
200 Translates this geo polygon by \a degreesLatitude northwards and \a degreesLongitude eastwards.
201
202 Negative values of \a degreesLatitude and \a degreesLongitude correspond to
203 southward and westward translation respectively.
204*/
205void QGeoPolygon::translate(double degreesLatitude, double degreesLongitude)
206{
207 Q_D(QGeoPolygon);
208 d->translate(degreesLatitude, degreesLongitude);
209}
210
211/*!
212 Returns a copy of this geo polygon translated by \a degreesLatitude northwards and
213 \a degreesLongitude eastwards.
214
215 Negative values of \a degreesLatitude and \a degreesLongitude correspond to
216 southward and westward translation respectively.
217
218 \sa translate()
219*/
220QGeoPolygon QGeoPolygon::translated(double degreesLatitude, double degreesLongitude) const
221{
222 QGeoPolygon result(*this);
223 result.translate(degreesLatitude, degreesLongitude);
224 return result;
225}
226
227/*!
228 Returns the length of the polygon's perimeter, in meters, from the element \a indexFrom to the element \a indexTo.
229 The length is intended to be the sum of the shortest distances for each pair of adjacent points.
230*/
231double QGeoPolygon::length(qsizetype indexFrom, qsizetype indexTo) const
232{
233 Q_D(const QGeoPolygon);
234 return d->length(indexFrom, indexTo);
235}
236
237/*!
238 Returns the number of elements in the polygon.
239
240 \since 5.10
241*/
242qsizetype QGeoPolygon::size() const
243{
244 Q_D(const QGeoPolygon);
245 const qsizetype result = d->size();
246 if (result > kMaxInt)
247 qWarning() << kTooManyElements;
248 return result;
249}
250
251/*!
252 Appends \a coordinate to the polygon.
253*/
254void QGeoPolygon::addCoordinate(const QGeoCoordinate &coordinate)
255{
256 Q_D(QGeoPolygon);
257 d->addCoordinate(coordinate);
258 if (d->size() > kMaxInt)
259 qWarning() << kTooManyElements;
260}
261
262/*!
263 Inserts \a coordinate at the specified \a index.
264*/
265void QGeoPolygon::insertCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
266{
267 Q_D(QGeoPolygon);
268 d->insertCoordinate(index, coordinate);
269}
270
271/*!
272 Replaces the path element at the specified \a index with \a coordinate.
273*/
274void QGeoPolygon::replaceCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
275{
276 Q_D(QGeoPolygon);
277 d->replaceCoordinate(index, coordinate);
278}
279
280/*!
281 Returns the coordinate at \a index .
282*/
283QGeoCoordinate QGeoPolygon::coordinateAt(qsizetype index) const
284{
285 Q_D(const QGeoPolygon);
286 return d->coordinateAt(index);
287}
288
289/*!
290 Returns true if the polygon's perimeter contains \a coordinate as one of the elements.
291*/
292bool QGeoPolygon::containsCoordinate(const QGeoCoordinate &coordinate) const
293{
294 Q_D(const QGeoPolygon);
295 return d->containsCoordinate(coordinate);
296}
297
298/*!
299 Removes the last occurrence of \a coordinate from the polygon.
300*/
301void QGeoPolygon::removeCoordinate(const QGeoCoordinate &coordinate)
302{
303 Q_D(QGeoPolygon);
304 d->removeCoordinate(coordinate);
305}
306
307/*!
308 Removes element at position \a index from the polygon.
309*/
310void QGeoPolygon::removeCoordinate(qsizetype index)
311{
312 Q_D(QGeoPolygon);
313 d->removeCoordinate(index);
314}
315
316/*!
317 Returns the geo polygon properties as a string.
318*/
319QString QGeoPolygon::toString() const
320{
321 if (type() != QGeoShape::PolygonType) {
322 qWarning("Not a polygon");
323 return QStringLiteral("QGeoPolygon(not a polygon)");
324 }
325
326 QString pathString;
327 for (const auto &p : perimeter())
328 pathString += p.toString() + QLatin1StringView("; ");
329 pathString.chop(2);
330
331 return QStringLiteral("QGeoPolygon([ %1 ])").arg(pathString);
332}
333
334/*!
335 Sets the \a holePath for a hole inside the polygon. The hole is a
336 QVariant containing a QList<QGeoCoordinate>.
337
338 \since 5.12
339*/
340void QGeoPolygon::addHole(const QVariant &holePath)
341{
342 QList<QGeoCoordinate> qgcHolePath;
343 if (holePath.canConvert<QVariantList>()) {
344 const QVariantList qvlHolePath = holePath.toList();
345 for (const QVariant &vertex : qvlHolePath) {
346 if (vertex.canConvert<QGeoCoordinate>())
347 qgcHolePath << vertex.value<QGeoCoordinate>();
348 }
349 }
350 //ToDo: add QGeoShape support
351 addHole(qgcHolePath);
352}
353
354/*!
355 Overloaded method. Sets the \a holePath for a hole inside the polygon. The hole is a QList<QGeoCoordinate>.
356
357 \since 5.12
358*/
359void QGeoPolygon::addHole(const QList<QGeoCoordinate> &holePath)
360{
361 Q_D(QGeoPolygon);
362 d->addHole(holePath);
363 if (d->holesCount() > kMaxInt)
364 qDebug() << kTooManyHoles;
365}
366
367/*!
368 Returns a QVariant containing a QList<QGeoCoordinate>
369 which represents the hole at \a index.
370
371 \since 5.12
372*/
373const QVariantList QGeoPolygon::hole(qsizetype index) const
374{
375 Q_D(const QGeoPolygon);
376 QVariantList holeCoordinates;
377 for (const QGeoCoordinate &coords: d->holePath(index))
378 holeCoordinates << QVariant::fromValue(coords);
379 return holeCoordinates;
380}
381
382/*!
383 Returns a QList<QGeoCoordinate> which represents the hole at \a index.
384
385 \since 5.12
386*/
387const QList<QGeoCoordinate> QGeoPolygon::holePath(qsizetype index) const
388{
389 Q_D(const QGeoPolygon);
390 return d->holePath(index);
391}
392
393/*!
394 Removes element at position \a index from the list of holes.
395
396 \since 5.12
397*/
398void QGeoPolygon::removeHole(qsizetype index)
399{
400 Q_D(QGeoPolygon);
401 return d->removeHole(index);
402}
403
404/*!
405 Returns the number of holes.
406
407 \since 5.12
408*/
409qsizetype QGeoPolygon::holesCount() const
410{
411 Q_D(const QGeoPolygon);
412 const qsizetype result = d->holesCount();
413 if (result > kMaxInt)
414 qWarning() << kTooManyHoles;
415 return result;
416}
417
418/*******************************************************************************
419 *
420 * QGeoPathPrivate & friends
421 *
422*******************************************************************************/
423
424QGeoPolygonPrivate::QGeoPolygonPrivate()
425: QGeoPathPrivate()
426{
427 type = QGeoShape::PolygonType;
428}
429
430QGeoPolygonPrivate::QGeoPolygonPrivate(const QList<QGeoCoordinate> &path)
431: QGeoPathPrivate(path)
432{
433 type = QGeoShape::PolygonType;
434}
435
436QGeoPolygonPrivate::~QGeoPolygonPrivate() {}
437
438QGeoShapePrivate *QGeoPolygonPrivate::clone() const
439{
440 return new QGeoPolygonPrivate(*this);
441}
442
443bool QGeoPolygonPrivate::isValid() const
444{
445 return path().size() > 2;
446}
447
448bool QGeoPolygonPrivate::contains(const QGeoCoordinate &coordinate) const
449{
450 return polygonContains(coordinate);
451}
452
453inline static void translatePoly( QList<QGeoCoordinate> &m_path,
454 QList<QList<QGeoCoordinate>> &m_holesList,
455 QGeoRectangle &m_bbox,
456 double degreesLatitude,
457 double degreesLongitude,
458 double m_maxLati,
459 double m_minLati)
460{
461 if (degreesLatitude > 0.0)
462 degreesLatitude = qMin(degreesLatitude, 90.0 - m_maxLati);
463 else
464 degreesLatitude = qMax(degreesLatitude, -90.0 - m_minLati);
465 for (QGeoCoordinate &p: m_path) {
466 p.setLatitude(p.latitude() + degreesLatitude);
467 p.setLongitude(QLocationUtils::wrapLong(p.longitude() + degreesLongitude));
468 }
469 if (!m_holesList.isEmpty()){
470 for (QList<QGeoCoordinate> &hole: m_holesList){
471 for (QGeoCoordinate &holeVertex: hole){
472 holeVertex.setLatitude(holeVertex.latitude() + degreesLatitude);
473 holeVertex.setLongitude(QLocationUtils::wrapLong(holeVertex.longitude() + degreesLongitude));
474 }
475 }
476 }
477 m_bbox.translate(degreesLatitude, degreesLongitude);
478}
479
480void QGeoPolygonPrivate::translate(double degreesLatitude, double degreesLongitude)
481{
482 // Need min/maxLati, so update bbox
483 QList<double> m_deltaXs;
484 double m_minX, m_maxX, m_minLati, m_maxLati;
485 m_bboxDirty = false; // Updated in translatePoly
486 computeBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
487 translatePoly(m_path, m_holesList, m_bbox, degreesLatitude, degreesLongitude, m_maxLati, m_minLati);
488 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
489 m_clipperDirty = true;
490}
491
492bool QGeoPolygonPrivate::operator==(const QGeoShapePrivate &other) const
493{
494 if (!QGeoShapePrivate::operator==(other)) // checks type
495 return false;
496
497 const QGeoPolygonPrivate &otherPath = static_cast<const QGeoPolygonPrivate &>(other);
498 if (m_path.size() != otherPath.m_path.size()
499 || m_holesList.size() != otherPath.m_holesList.size())
500 return false;
501 return m_path == otherPath.m_path && m_holesList == otherPath.m_holesList;
502}
503
504size_t QGeoPolygonPrivate::hash(size_t seed) const
505{
506 const size_t pointsHash = qHashRange(m_path.cbegin(), m_path.cend(), seed);
507 const size_t holesHash = qHashRange(m_holesList.cbegin(), m_holesList.cend(), seed);
508 return qHashMulti(seed, pointsHash, holesHash);
509}
510
511void QGeoPolygonPrivate::addHole(const QList<QGeoCoordinate> &holePath)
512{
513 for (const QGeoCoordinate &holeVertex: holePath)
514 if (!holeVertex.isValid())
515 return;
516
517 m_holesList << holePath;
518 // ToDo: mark clipper dirty when hole caching gets added
519}
520
521const QList<QGeoCoordinate> QGeoPolygonPrivate::holePath(qsizetype index) const
522{
523 return m_holesList.at(index);
524}
525
526void QGeoPolygonPrivate::removeHole(qsizetype index)
527{
528 if (index < 0 || index >= m_holesList.size())
529 return;
530
531 m_holesList.removeAt(index);
532 // ToDo: mark clipper dirty when hole caching gets added
533}
534
535qsizetype QGeoPolygonPrivate::holesCount() const
536{
537 return m_holesList.size();
538}
539
540bool QGeoPolygonPrivate::polygonContains(const QGeoCoordinate &coordinate) const
541{
542 if (m_clipperDirty)
543 const_cast<QGeoPolygonPrivate *>(this)->updateClipperPath(); // this one updates bbox too if needed
544
545 QDoubleVector2D coord = QWebMercator::coordToMercator(coordinate);
546
547 if (coord.x() < m_leftBoundWrapped)
548 coord.setX(coord.x() + 1.0);
549
550 if (!m_clipperWrapper.pointInPolygon(coord))
551 return false;
552
553 // else iterates the holes List checking whether the point is contained inside the holes
554 for (const QList<QGeoCoordinate> &holePath : std::as_const(m_holesList)) {
555 // ToDo: cache these
556 QGeoPolygon holePolygon;
557 holePolygon.setPerimeter(holePath);
558 if (holePolygon.contains(coordinate))
559 return false;
560 }
561 return true;
562}
563
564void QGeoPolygonPrivate::markDirty()
565{
566 m_bboxDirty = m_clipperDirty = true;
567}
568
569void QGeoPolygonPrivate::updateClipperPath()
570{
571 if (m_bboxDirty)
572 computeBoundingBox();
573 m_clipperDirty = false;
574
575 QList<QDoubleVector2D> preservedPath;
576 for (const QGeoCoordinate &c : m_path) {
577 QDoubleVector2D crd = QWebMercator::coordToMercator(c);
578 if (crd.x() < m_leftBoundWrapped)
579 crd.setX(crd.x() + 1.0);
580 preservedPath << crd;
581 }
582 m_clipperWrapper.setPolygon(preservedPath);
583}
584
585QGeoPolygonPrivateEager::QGeoPolygonPrivateEager() : QGeoPolygonPrivate()
586{
587 m_bboxDirty = false; // never dirty on the eager version
588}
589
590QGeoPolygonPrivateEager::QGeoPolygonPrivateEager(const QList<QGeoCoordinate> &path) : QGeoPolygonPrivate(path)
591{
592 m_bboxDirty = false; // never dirty on the eager version
593}
594
595QGeoPolygonPrivateEager::~QGeoPolygonPrivateEager()
596{
597
598}
599
600QGeoShapePrivate *QGeoPolygonPrivateEager::clone() const
601{
602 return new QGeoPolygonPrivate(*this);
603}
604
605void QGeoPolygonPrivateEager::translate(double degreesLatitude, double degreesLongitude)
606{
607 translatePoly(m_path, m_holesList, m_bbox, degreesLatitude, degreesLongitude, m_maxLati, m_minLati);
608 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
609 m_clipperDirty = true;
610}
611
612void QGeoPolygonPrivateEager::markDirty()
613{
614 m_clipperDirty = true;
615 computeBoundingBox();
616}
617
618void QGeoPolygonPrivateEager::addCoordinate(const QGeoCoordinate &coordinate)
619{
620 if (!coordinate.isValid())
621 return;
622 m_path.append(coordinate);
623 m_clipperDirty = true;
624 updateBoundingBox(); // do not markDirty as it uses computeBoundingBox instead
625}
626
627void QGeoPolygonPrivateEager::computeBoundingBox()
628{
629 computeBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
630 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
631}
632
633void QGeoPolygonPrivateEager::updateBoundingBox()
634{
635 updateBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
636}
637
638QGeoPolygonEager::QGeoPolygonEager() : QGeoPolygon()
639{
640 d_ptr = new QGeoPolygonPrivateEager;
641}
642
643QGeoPolygonEager::QGeoPolygonEager(const QList<QGeoCoordinate> &path) : QGeoPolygon()
644{
645 d_ptr = new QGeoPolygonPrivateEager(path);
646}
647
648QGeoPolygonEager::QGeoPolygonEager(const QGeoPolygon &other) : QGeoPolygon()
649{
650 // without being able to dynamic_cast the d_ptr, only way to be sure is to reconstruct a new QGeoPolygonPrivateEager
651 d_ptr = new QGeoPolygonPrivateEager;
652 setPerimeter(other.perimeter());
653 for (qsizetype i = 0; i < other.holesCount(); i++)
654 addHole(other.holePath(i));
655}
656
657QGeoPolygonEager::QGeoPolygonEager(const QGeoShape &other) : QGeoPolygon()
658{
659 if (other.type() == QGeoShape::PolygonType)
660 *this = QGeoPolygonEager(QGeoPolygon(other));
661 else
662 d_ptr = new QGeoPolygonPrivateEager;
663}
664
665QGeoPolygonEager::~QGeoPolygonEager()
666{
667
668}
669
670QT_END_NAMESPACE
671
672#include "moc_qgeopolygon_p.cpp"
673#include "moc_qgeopolygon.cpp"
static void calculatePeripheralPoints(QList< QGeoCoordinate > &path, const QGeoCircle &circle, int steps)
static void translatePoly(QList< QGeoCoordinate > &m_path, QList< QList< QGeoCoordinate > > &m_holesList, QGeoRectangle &m_bbox, double degreesLatitude, double degreesLongitude, double m_maxLati, double m_minLati)
constexpr auto kTooManyElements
constexpr auto kTooManyHoles
#define M_PI
Definition qmath.h:200