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