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
qgeopath.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// Qt-Security score:significant reason:default
4
5#include "qgeopath.h"
6#include "qgeopolygon.h"
7#include "qgeopath_p.h"
8
10#include "qnumeric.h"
12#include "qwebmercator_p.h"
13
17
18QT_IMPL_METATYPE_EXTERN(QGeoPath)
19
20constexpr auto kWarningString = u"The path has more elements than fit into an int. "
21 "This can cause errors while querying elements from QML";
22
23/*!
24 \class QGeoPath
25 \inmodule QtPositioning
26 \ingroup QtPositioning-positioning
27 \since 5.9
28
29 \brief The QGeoPath class defines a geographic path.
30
31 The path is defined by an ordered list of \l QGeoCoordinate objects.
32
33 Each two adjacent elements in the path are intended to be connected
34 together by the shortest line segment of constant bearing passing
35 through both elements.
36 This type of connection can cross the dateline in the longitudinal direction,
37 but never crosses the poles.
38
39 This is relevant for the calculation of the bounding box returned by
40 \l QGeoShape::boundingGeoRectangle() for this shape, which will have the latitude of
41 the top left corner set to the maximum latitude in the path point set.
42 Similarly, the latitude of the bottom right corner will be the minimum latitude
43 in the path point set.
44
45 This class is also accessible in QML as \l[QML]{geoPath}.
46
47 A QGeoPath is both invalid and empty if it contains no coordinate.
48
49 \note A default constructed QGeoPath is both invalid and empty as it does not contain any coordinates.
50*/
51
52/*!
53 \property QGeoPath::path
54 \brief This property holds the list of coordinates for the geo path.
55
56 \note The coordinates cannot be processed in place. To change the value
57 of this property, retrieve the complete list of coordinates, process them,
58 and assign the new value to the property.
59*/
60
61inline QGeoPathPrivate *QGeoPath::d_func()
62{
63 return static_cast<QGeoPathPrivate *>(d_ptr.data());
64}
65
66inline const QGeoPathPrivate *QGeoPath::d_func() const
67{
68 return static_cast<const QGeoPathPrivate *>(d_ptr.constData());
69}
70
71/*!
72 Constructs a new, empty geo path.
73*/
74QGeoPath::QGeoPath()
75: QGeoShape(new QGeoPathPrivate())
76{
77}
78
79/*!
80 Constructs a new geo path from a list of coordinates
81 (\a path and \a width).
82*/
83QGeoPath::QGeoPath(const QList<QGeoCoordinate> &path, const qreal &width)
84: QGeoShape(new QGeoPathPrivate(path, width))
85{
86}
87
88/*!
89 Constructs a new geo path from the contents of \a other.
90*/
91QGeoPath::QGeoPath(const QGeoPath &other)
92: QGeoShape(other)
93{
94}
95
96/*!
97 Constructs a new geo path from the contents of \a other.
98*/
99QGeoPath::QGeoPath(const QGeoShape &other)
100: QGeoShape(other)
101{
102 if (type() != QGeoShape::PathType)
103 d_ptr = new QGeoPathPrivate();
104}
105
106/*!
107 Destroys this path.
108*/
109QGeoPath::~QGeoPath() {}
110
111/*!
112 Assigns \a other to this geo path and returns a reference to this geo path.
113*/
114QGeoPath &QGeoPath::operator=(const QGeoPath &other)
115{
116 QGeoShape::operator=(other);
117 return *this;
118}
119
120/*!
121 Sets all the elements of the \a path.
122*/
123void QGeoPath::setPath(const QList<QGeoCoordinate> &path)
124{
125 Q_D(QGeoPath);
126 return d->setPath(path);
127}
128
129/*!
130 Returns all the elements of the path.
131*/
132const QList<QGeoCoordinate> &QGeoPath::path() const
133{
134 Q_D(const QGeoPath);
135 return d->path();
136}
137
138/*!
139 Clears the path.
140
141 \since 5.12
142*/
143void QGeoPath::clearPath()
144{
145 Q_D(QGeoPath);
146 d->clearPath();
147}
148
149/*!
150 Sets all the elements of the path.
151
152 \internal
153*/
154void QGeoPath::setVariantPath(const QVariantList &path)
155{
156 Q_D(QGeoPath);
157 QList<QGeoCoordinate> p;
158 for (const auto &c: path) {
159 if (c.canConvert<QGeoCoordinate>())
160 p << c.value<QGeoCoordinate>();
161 }
162 d->setPath(p);
163}
164/*!
165 Returns all the elements of the path.
166
167 \internal
168*/
169QVariantList QGeoPath::variantPath() const
170{
171 Q_D(const QGeoPath);
172 QVariantList p;
173 for (const auto &c: d->path())
174 p << QVariant::fromValue(c);
175 return p;
176}
177
178
179/*!
180 \property QGeoPath::width
181
182 \brief the width of the path in meters.
183*/
184void QGeoPath::setWidth(const qreal &width)
185{
186 Q_D(QGeoPath);
187 d->setWidth(width);
188}
189
190/*!
191 Returns the width of the path, in meters. This information is used in the \l contains method.
192 The default value is 0.
193*/
194qreal QGeoPath::width() const
195{
196 Q_D(const QGeoPath);
197 return d->width();
198}
199
200/*!
201 Translates this geo path by \a degreesLatitude northwards and \a degreesLongitude eastwards.
202
203 Negative values of \a degreesLatitude and \a degreesLongitude correspond to
204 southward and westward translation respectively.
205*/
206void QGeoPath::translate(double degreesLatitude, double degreesLongitude)
207{
208 Q_D(QGeoPath);
209 d->translate(degreesLatitude, degreesLongitude);
210}
211
212/*!
213 Returns a copy of this geo path translated by \a degreesLatitude northwards and
214 \a degreesLongitude eastwards.
215
216 Negative values of \a degreesLatitude and \a degreesLongitude correspond to
217 southward and westward translation respectively.
218
219 \sa translate()
220*/
221QGeoPath QGeoPath::translated(double degreesLatitude, double degreesLongitude) const
222{
223 QGeoPath result(*this);
224 result.translate(degreesLatitude, degreesLongitude);
225 return result;
226}
227
228/*!
229 Returns the length of the path, in meters, from the element \a indexFrom to the element \a indexTo.
230 The length is intended to be the sum of the shortest distances for each pair of adjacent points.
231
232 If \a indexTo is -1 (the default value), the length will be including the distance between last coordinate
233 and the first (closed loop).
234 To retrieve the length for the path, use 0 for \a indexFrom and \l QGeoPath::size() - 1 for \a indexTo.
235*/
236double QGeoPath::length(qsizetype indexFrom, qsizetype indexTo) const
237{
238 Q_D(const QGeoPath);
239 return d->length(indexFrom, indexTo);
240}
241
242/*!
243 Returns the number of elements in the path.
244
245 \since 5.10
246*/
247qsizetype QGeoPath::size() const
248{
249 Q_D(const QGeoPath);
250 const qsizetype result = d->size();
251 if (result > std::numeric_limits<int>::max())
252 qWarning() << kWarningString;
253 return result;
254}
255
256/*!
257 Appends \a coordinate to the path.
258*/
259void QGeoPath::addCoordinate(const QGeoCoordinate &coordinate)
260{
261 Q_D(QGeoPath);
262 d->addCoordinate(coordinate);
263 if (d->size() > std::numeric_limits<int>::max())
264 qWarning() << kWarningString;
265}
266
267/*!
268 Inserts \a coordinate at the specified \a index.
269*/
270void QGeoPath::insertCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
271{
272 Q_D(QGeoPath);
273 d->insertCoordinate(index, coordinate);
274}
275
276/*!
277 Replaces the path element at the specified \a index with \a coordinate.
278*/
279void QGeoPath::replaceCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
280{
281 Q_D(QGeoPath);
282 d->replaceCoordinate(index, coordinate);
283}
284
285/*!
286 Returns the coordinate at \a index .
287*/
288QGeoCoordinate QGeoPath::coordinateAt(qsizetype index) const
289{
290 Q_D(const QGeoPath);
291 return d->coordinateAt(index);
292}
293
294/*!
295 Returns true if the path contains \a coordinate as one of the elements.
296*/
297bool QGeoPath::containsCoordinate(const QGeoCoordinate &coordinate) const
298{
299 Q_D(const QGeoPath);
300 return d->containsCoordinate(coordinate);
301}
302
303/*!
304 Removes the last occurrence of \a coordinate from the path.
305*/
306void QGeoPath::removeCoordinate(const QGeoCoordinate &coordinate)
307{
308 Q_D(QGeoPath);
309 d->removeCoordinate(coordinate);
310}
311
312/*!
313 Removes element at position \a index from the path.
314*/
315void QGeoPath::removeCoordinate(qsizetype index)
316{
317 Q_D(QGeoPath);
318 d->removeCoordinate(index);
319}
320
321/*!
322 Returns the geo path properties as a string.
323*/
324QString QGeoPath::toString() const
325{
326 if (type() != QGeoShape::PathType) {
327 qWarning("Not a path");
328 return QStringLiteral("QGeoPath(not a path)");
329 }
330
331 QString pathString;
332 for (const auto &p : path())
333 pathString += p.toString() + QLatin1Char(',');
334
335 return QStringLiteral("QGeoPath([ %1 ])").arg(pathString);
336}
337
338/*******************************************************************************
339 *
340 * QGeoPathPrivateBase & friends
341 *
342*******************************************************************************/
343
344QGeoPathPrivateBase::QGeoPathPrivateBase()
345 : QGeoShapePrivate(QGeoShape::PathType)
346{
347}
348
349QGeoPathPrivateBase::QGeoPathPrivateBase(const QList<QGeoCoordinate> &path)
350 : QGeoPathPrivateBase()
351{
352 setPath(path);
353}
354
355QGeoPathPrivateBase::~QGeoPathPrivateBase()
356{
357}
358
359bool QGeoPathPrivateBase::isValid() const
360{
361 return !isEmpty();
362}
363
364bool QGeoPathPrivateBase::isEmpty() const
365{
366 return path().isEmpty(); // this should perhaps return geometric emptiness, less than 2 points for line, or empty polygon for polygons
367}
368
369QGeoCoordinate QGeoPathPrivateBase::center() const
370{
371 return boundingGeoRectangle().center();
372}
373
374bool QGeoPathPrivateBase::operator==(const QGeoShapePrivate &other) const
375{
376 if (!QGeoShapePrivate::operator==(other)) // checks type
377 return false;
378
379 const QGeoPathPrivateBase &otherBase = static_cast<const QGeoPathPrivateBase &>(other);
380 return m_path == otherBase.m_path;
381}
382
383QGeoRectangle QGeoPathPrivateBase::boundingGeoRectangle() const
384{
385 if (m_bboxDirty)
386 const_cast<QGeoPathPrivateBase *>(this)->computeBoundingBox();
387 return m_bbox;
388}
389
390size_t QGeoPathPrivateBase::hash(size_t seed) const
391{
392 return qHashRange(m_path.cbegin(), m_path.cend(), seed);
393}
394
395const QList<QGeoCoordinate> &QGeoPathPrivateBase::path() const
396{
397 return m_path;
398}
399
400double QGeoPathPrivateBase::length(qsizetype indexFrom, qsizetype indexTo) const
401{
402 if (path().isEmpty())
403 return 0.0;
404
405 bool wrap = indexTo == -1;
406 if (indexTo < 0 || indexTo >= path().size())
407 indexTo = path().size() - 1;
408 double len = 0.0;
409 // TODO: consider calculating the length of the actual rhumb line segments
410 // instead of the shortest path from A to B.
411 for (qsizetype i = indexFrom; i < indexTo; i++)
412 len += m_path[i].distanceTo(m_path[i + 1]);
413 if (wrap)
414 len += m_path.last().distanceTo(m_path.first());
415 return len;
416}
417
418qsizetype QGeoPathPrivateBase::size() const
419{
420 return m_path.size();
421}
422
423QGeoCoordinate QGeoPathPrivateBase::coordinateAt(qsizetype index) const
424{
425 if (index < 0 || index >= m_path.size())
426 return QGeoCoordinate();
427
428 return m_path.at(index);
429}
430
431bool QGeoPathPrivateBase::containsCoordinate(const QGeoCoordinate &coordinate) const
432{
433 return m_path.indexOf(coordinate) > -1;
434}
435
436void QGeoPathPrivateBase::translate(double degreesLatitude, double degreesLongitude)
437{
438 // Need min/maxLati, so update bbox
439 QList<double> deltaXs;
440 double minX, maxX, minLati, maxLati;
441 m_bboxDirty = false;
442 computeBBox(m_path, deltaXs, minX, maxX, minLati, maxLati, m_bbox);
443
444 if (degreesLatitude > 0.0)
445 degreesLatitude = qMin(degreesLatitude, 90.0 - maxLati);
446 else
447 degreesLatitude = qMax(degreesLatitude, -90.0 - minLati);
448 for (QGeoCoordinate &p: m_path) {
449 p.setLatitude(p.latitude() + degreesLatitude);
450 p.setLongitude(QLocationUtils::wrapLong(p.longitude() + degreesLongitude));
451 }
452 m_bbox.translate(degreesLatitude, degreesLongitude);
453 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
454}
455
456void QGeoPathPrivateBase::setPath(const QList<QGeoCoordinate> &path)
457{
458 for (const QGeoCoordinate &c: path) {
459 if (!c.isValid())
460 return;
461 }
462 m_path = path;
463 markDirty();
464}
465
466void QGeoPathPrivateBase::clearPath()
467{
468 m_path.clear();
469 markDirty();
470}
471
472void QGeoPathPrivateBase::addCoordinate(const QGeoCoordinate &coordinate)
473{
474 if (!coordinate.isValid())
475 return;
476 m_path.append(coordinate);
477 markDirty();
478}
479
480void QGeoPathPrivateBase::insertCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
481{
482 if (index < 0 || index > m_path.size() || !coordinate.isValid())
483 return;
484 m_path.insert(index, coordinate);
485 markDirty();
486}
487
488void QGeoPathPrivateBase::replaceCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
489{
490 if (index < 0 || index >= m_path.size() || !coordinate.isValid())
491 return;
492 m_path[index] = coordinate;
493 markDirty();
494}
495
496void QGeoPathPrivateBase::removeCoordinate(const QGeoCoordinate &coordinate)
497{
498 qsizetype index = m_path.lastIndexOf(coordinate);
499 removeCoordinate(index);
500}
501
502void QGeoPathPrivateBase::removeCoordinate(qsizetype index)
503{
504 if (index < 0 || index >= m_path.size())
505 return;
506 m_path.removeAt(index);
507 markDirty();
508}
509
510void QGeoPathPrivateBase::computeBoundingBox()
511{
512 QList<double> deltaXs;
513 double minX, maxX, minLati, maxLati;
514 m_bboxDirty = false;
515 computeBBox(m_path, deltaXs, minX, maxX, minLati, maxLati, m_bbox);
516 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
517}
518
519void QGeoPathPrivateBase::markDirty()
520{
521 m_bboxDirty = true;
522}
523
524
525
526QGeoPathPrivate::QGeoPathPrivate()
527 : QGeoPathPrivateBase()
528{
529}
530
531QGeoPathPrivate::QGeoPathPrivate(const QList<QGeoCoordinate> &path, const qreal width)
532 : QGeoPathPrivateBase(path)
533{
534 setWidth(width);
535}
536
537QGeoPathPrivate::~QGeoPathPrivate()
538{
539}
540
541QGeoShapePrivate *QGeoPathPrivate::clone() const
542{
543 return new QGeoPathPrivate(*this);
544}
545
546bool QGeoPathPrivate::operator==(const QGeoShapePrivate &other) const
547{
548 if (!QGeoPathPrivateBase::operator==(other)) // checks type
549 return false;
550
551 const QGeoPathPrivate &otherPath = static_cast<const QGeoPathPrivate &>(other);
552 return m_width == otherPath.m_width;
553}
554
555bool QGeoPathPrivate::lineContains(const QGeoCoordinate &coordinate) const
556{
557 // Unoptimized approach:
558 // - consider each segment of the path (a rhumb line)
559 // - project it into Mercator space (in which it is straight)
560 // - find Mercator-closest point to coordinate
561 // - unproject the closest point
562 // - calculate coordinate to closest point distance with distanceTo()
563 // - if not within lineRadius, advance to next segment.
564 //
565 // When considering each segment, wrap its start-point to x-within 0.5 of
566 // its end-point and (initially) the target point to x-within the mid-point
567 // of the thus-narrowed segment. That position will usually find the actual
568 // Mercator-closest point and makes it easy to test for the obscure case
569 // where a different wrap-position for it might work better; when that is
570 // possible, try that other wrap-position for it in a second pass.
571
572 double lineRadius = qMax(width() * 0.5, 0.2); // minimum radius: 20cm
573
574 if (m_path.isEmpty())
575 return false;
576 else if (m_path.size() == 1)
577 return (m_path[0].distanceTo(coordinate) <= lineRadius);
578
579 Q_ASSERT(m_path.size() > 1);
580 const QDoubleVector2D pt = QWebMercator::coordToMercator(coordinate);
581 QDoubleVector2D last = QWebMercator::coordToMercator(m_path[0]);
582 for (qsizetype i = 1; i < m_path.size(); i++) {
583 const QDoubleVector2D here = QWebMercator::coordToMercator(m_path[i]);
584 // Wrap last to gets its x() within ±0.5 of that of here:
585 if (here.x() > last.x() + 0.5)
586 last.setX(last.x() + 1.0);
587 else if (here.x() < last.x() - 0.5)
588 last.setX(last.x() - 1.0);
589
590 if (here == last) {
591 // The whole line segment is one point, so easy to test.
592 if (m_path[i].distanceTo(coordinate) <= lineRadius)
593 return true;
594 continue;
595 }
596
597 QDoubleVector2D p = pt;
598 {
599 QDoubleVector2D mid = (last + here) / 2.0;
600 // Wrap p to gets its x() within ±0.5 of that of mid:
601 if (p.x() > mid.x() + 0.5)
602 p.setX(p.x() - 1.0);
603 else if (p.x() < mid.x() - 0.5)
604 p.setX(p.x() + 1.0);
605 }
606 // See comment on updating p after j == 0; loop to catch the corner case.
607 for (int j = 0; j < 2; ++j) {
608 const QDoubleVector2D pml = p - last, hml = here - last;
609 const double u = (pml.x() * hml.x() + pml.y() * hml.y()) / hml.lengthSquared();
610 // Interpolate between 0 < u < 1, use nearer end otherwise:
611 const QDoubleVector2D candidate = u > 0 ? u < 1 ? last + u * hml : here : last;
612 const double distance = coordinate.distanceTo(QWebMercator::mercatorToCoord(candidate));
613 if (distance <= lineRadius)
614 return true;
615
616 if (j == 0) {
617 /* Our initial p's .x() is within 0.5 of mid.x(), hence of at
618 least one of last.x() and here.x(). If it's within 0.5 of
619 both of these, the candidate we just tried is definitely our
620 best bet. Otherwise - though it's very obscure and can't
621 matter unless lineRadius is comparable with the rhumb line's
622 distance from the planet's spin axis - there are technically
623 two parabolic segments p could be in (y-closer to one end but
624 x-closer to the other), that would put p further from
625 last:here than from its more x()-distant end's wrapped
626 version the other side of p. So wrap p to its version that
627 would be closer to that distant end, were this to arise.
628 */
629 if (p.x() > qMin(last.x(), here.x()) + 0.5)
630 p.setX(p.x() - 1.0);
631 else if (p.x() < qMax(last.x(), here.x()) - 0.5)
632 p.setX(p.x() + 1.0);
633 else
634 break;
635 }
636 }
637 // swap
638 last = here;
639 }
640
641 return false;
642}
643
644bool QGeoPathPrivate::contains(const QGeoCoordinate &coordinate) const
645{
646 return lineContains(coordinate);
647}
648
649qreal QGeoPathPrivate::width() const
650{
651 return m_width;
652}
653
654void QGeoPathPrivate::setWidth(const qreal &width)
655{
656 if (qIsNaN(width) || width < 0.0)
657 return;
658 m_width = width;
659}
660
661size_t QGeoPathPrivate::hash(size_t seed) const
662{
663 const size_t res = QGeoPathPrivateBase::hash(seed);
664 return qHashMulti(seed, res, m_width);
665}
666
667QGeoPathPrivateEager::QGeoPathPrivateEager()
668: QGeoPathPrivate()
669{
670 m_bboxDirty = false; // never dirty on the eager version
671}
672
673QGeoPathPrivateEager::QGeoPathPrivateEager(const QList<QGeoCoordinate> &path, const qreal width)
674: QGeoPathPrivate(path, width)
675{
676 m_bboxDirty = false; // never dirty on the eager version
677 markDirty(); // calculate the cached values
678}
679
680QGeoPathPrivateEager::~QGeoPathPrivateEager()
681{
682
683}
684
685QGeoShapePrivate *QGeoPathPrivateEager::clone() const
686{
687 return new QGeoPathPrivateEager(*this);
688}
689
690void QGeoPathPrivateEager::markDirty()
691{
692 // do the calculations directly
693 computeBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
694 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
695}
696
697void QGeoPathPrivateEager::translate(double degreesLatitude, double degreesLongitude)
698{
699 if (degreesLatitude > 0.0)
700 degreesLatitude = qMin(degreesLatitude, 90.0 - m_maxLati);
701 else
702 degreesLatitude = qMax(degreesLatitude, -90.0 - m_minLati);
703 for (QGeoCoordinate &p: m_path) {
704 p.setLatitude(p.latitude() + degreesLatitude);
705 p.setLongitude(QLocationUtils::wrapLong(p.longitude() + degreesLongitude));
706 }
707 m_bbox.translate(degreesLatitude, degreesLongitude);
708 m_minLati += degreesLatitude;
709 m_maxLati += degreesLatitude;
710 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
711}
712
713void QGeoPathPrivateEager::addCoordinate(const QGeoCoordinate &coordinate)
714{
715 if (!coordinate.isValid())
716 return;
717 m_path.append(coordinate);
718 //m_clipperDirty = true; // clipper not used in polylines
719 updateBoundingBox();
720}
721
722void QGeoPathPrivateEager::QGeoPathPrivateEager::computeBoundingBox()
723{
724 Q_UNREACHABLE();
725}
726
727void QGeoPathPrivateEager::QGeoPathPrivateEager::updateBoundingBox()
728{
729 updateBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
730 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
731}
732
733QGeoPathEager::QGeoPathEager() : QGeoPath()
734{
735 d_ptr = new QGeoPathPrivateEager;
736}
737
738QGeoPathEager::QGeoPathEager(const QList<QGeoCoordinate> &path, const qreal &width) : QGeoPath()
739{
740 d_ptr = new QGeoPathPrivateEager(path, width);
741}
742
743QGeoPathEager::QGeoPathEager(const QGeoPath &other) : QGeoPath()
744{
745 d_ptr = new QGeoPathPrivateEager;
746 setPath(other.path());
747 setWidth(other.width());
748}
749
750QGeoPathEager::QGeoPathEager(const QGeoShape &other) : QGeoPath()
751{
752 if (other.type() == QGeoShape::PathType)
753 *this = QGeoPathEager(QGeoPath(other));
754 else
755 d_ptr = new QGeoPathPrivateEager;
756}
757
758QGeoPathEager::~QGeoPathEager() {}
759
760QT_END_NAMESPACE
761
762#include "moc_qgeopath_p.cpp"
763#include "moc_qgeopath.cpp"
Combined button and popup list for selecting options.