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
4#include "qgeopath.h"
5#include "qgeopolygon.h"
6#include "qgeopath_p.h"
7
9#include "qnumeric.h"
11#include "qwebmercator_p.h"
12
16
17QT_IMPL_METATYPE_EXTERN(QGeoPath)
18
19constexpr auto kWarningString = u"The path has more elements than fit into an int. "
20 "This can cause errors while querying elements from QML";
21
22/*!
23 \class QGeoPath
24 \inmodule QtPositioning
25 \ingroup QtPositioning-positioning
26 \since 5.9
27
28 \brief The QGeoPath class defines a geographic path.
29
30 The path is defined by an ordered list of \l QGeoCoordinate objects.
31
32 Each two adjacent elements in the path are intended to be connected
33 together by the shortest line segment of constant bearing passing
34 through both elements.
35 This type of connection can cross the dateline in the longitudinal direction,
36 but never crosses the poles.
37
38 This is relevant for the calculation of the bounding box returned by
39 \l QGeoShape::boundingGeoRectangle() for this shape, which will have the latitude of
40 the top left corner set to the maximum latitude in the path point set.
41 Similarly, the latitude of the bottom right corner will be the minimum latitude
42 in the path point set.
43
44 This class is a \l Q_GADGET.
45 It can be \l{Cpp_value_integration_positioning}{directly used from C++ and QML}.
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 * QGeoPathPrivate & friends
341 *
342*******************************************************************************/
343
344QGeoPathPrivate::QGeoPathPrivate()
345: QGeoShapePrivate(QGeoShape::PathType)
346{
347
348}
349
350QGeoPathPrivate::QGeoPathPrivate(const QList<QGeoCoordinate> &path, const qreal width)
351: QGeoShapePrivate(QGeoShape::PathType)
352{
353 setPath(path);
354 setWidth(width);
355}
356
357QGeoPathPrivate::~QGeoPathPrivate()
358{
359
360}
361
362QGeoShapePrivate *QGeoPathPrivate::clone() const
363{
364 return new QGeoPathPrivate(*this);
365}
366
367bool QGeoPathPrivate::isValid() const
368{
369 return !isEmpty();
370}
371
372bool QGeoPathPrivate::isEmpty() const
373{
374 return path().isEmpty(); // this should perhaps return geometric emptiness, less than 2 points for line, or empty polygon for polygons
375}
376
377QGeoCoordinate QGeoPathPrivate::center() const
378{
379 return boundingGeoRectangle().center();
380}
381
382bool QGeoPathPrivate::operator==(const QGeoShapePrivate &other) const
383{
384 if (!QGeoShapePrivate::operator==(other))
385 return false;
386
387 const QGeoPathPrivate &otherPath = static_cast<const QGeoPathPrivate &>(other);
388 if (m_path.size() != otherPath.m_path.size())
389 return false;
390 return m_width == otherPath.m_width && m_path == otherPath.m_path;
391}
392
393const QList<QGeoCoordinate> &QGeoPathPrivate::path() const
394{
395 return m_path;
396}
397
398bool QGeoPathPrivate::lineContains(const QGeoCoordinate &coordinate) const
399{
400 // Unoptimized approach:
401 // - consider each segment of the path
402 // - project it into mercator space (rhumb lines are straight in mercator space)
403 // - find closest point to coordinate
404 // - unproject the closest point
405 // - calculate coordinate to closest point distance with distanceTo()
406 // - if not within lineRadius, advance
407 //
408 // To keep wrapping into the equation:
409 // If the mercator x value of a coordinate of the line, or the coordinate parameter, is less
410 // than mercator(m_bbox).x, add that to the conversion.
411
412 if (m_bboxDirty)
413 const_cast<QGeoPathPrivate &>(*this).computeBoundingBox();
414
415 double lineRadius = qMax(width() * 0.5, 0.2); // minimum radius: 20cm
416
417 if (m_path.isEmpty())
418 return false;
419 else if (m_path.size() == 1)
420 return (m_path[0].distanceTo(coordinate) <= lineRadius);
421
422 QDoubleVector2D p = QWebMercator::coordToMercator(coordinate);
423 if (p.x() < m_leftBoundWrapped)
424 p.setX(p.x() + m_leftBoundWrapped); // unwrap X
425
426 QDoubleVector2D a;
427 QDoubleVector2D b;
428 if (!m_path.isEmpty()) {
429 a = QWebMercator::coordToMercator(m_path[0]);
430 if (a.x() < m_leftBoundWrapped)
431 a.setX(a.x() + m_leftBoundWrapped); // unwrap X
432 }
433 for (qsizetype i = 1; i < m_path.size(); i++) {
434 b = QWebMercator::coordToMercator(m_path[i]);
435 if (b.x() < m_leftBoundWrapped)
436 b.setX(b.x() + m_leftBoundWrapped); // unwrap X
437 if (b == a)
438 continue;
439
440 double u = ((p.x() - a.x()) * (b.x() - a.x()) + (p.y() - a.y()) * (b.y() - a.y()) ) / (b - a).lengthSquared();
441 QDoubleVector2D intersection(a.x() + u * (b.x() - a.x()) , a.y() + u * (b.y() - a.y()) );
442
443 QDoubleVector2D candidate = ( (p-a).length() < (p-b).length() ) ? a : b;
444
445 if (u > 0 && u < 1
446 && (p-intersection).length() < (p-candidate).length() ) // And it falls in the segment
447 candidate = intersection;
448
449
450 if (candidate.x() > 1.0)
451 candidate.setX(candidate.x() - m_leftBoundWrapped); // wrap X
452
453 QGeoCoordinate closest = QWebMercator::mercatorToCoord(candidate);
454
455 double distanceMeters = coordinate.distanceTo(closest);
456 if (distanceMeters <= lineRadius)
457 return true;
458
459 // swap
460 a = b;
461 }
462
463 // Last check if the coordinate is on the left of leftBoundMercator, but close enough to
464 // m_path[0]
465 return (m_path[0].distanceTo(coordinate) <= lineRadius);
466}
467
468bool QGeoPathPrivate::contains(const QGeoCoordinate &coordinate) const
469{
470 return lineContains(coordinate);
471}
472
473qreal QGeoPathPrivate::width() const
474{
475 return m_width;
476}
477
478void QGeoPathPrivate::setWidth(const qreal &width)
479{
480 if (qIsNaN(width) || width < 0.0)
481 return;
482 m_width = width;
483}
484
485double QGeoPathPrivate::length(qsizetype indexFrom, qsizetype indexTo) const
486{
487 if (path().isEmpty())
488 return 0.0;
489
490 bool wrap = indexTo == -1;
491 if (indexTo < 0 || indexTo >= path().size())
492 indexTo = path().size() - 1;
493 double len = 0.0;
494 // TODO: consider calculating the length of the actual rhumb line segments
495 // instead of the shortest path from A to B.
496 for (qsizetype i = indexFrom; i < indexTo; i++)
497 len += m_path[i].distanceTo(m_path[i+1]);
498 if (wrap)
499 len += m_path.last().distanceTo(m_path.first());
500 return len;
501}
502
503qsizetype QGeoPathPrivate::size() const
504{
505 return m_path.size();
506}
507
508QGeoCoordinate QGeoPathPrivate::coordinateAt(qsizetype index) const
509{
510 if (index < 0 || index >= m_path.size())
511 return QGeoCoordinate();
512
513 return m_path.at(index);
514}
515
516bool QGeoPathPrivate::containsCoordinate(const QGeoCoordinate &coordinate) const
517{
518 return m_path.indexOf(coordinate) > -1;
519}
520
521void QGeoPathPrivate::translate(double degreesLatitude, double degreesLongitude)
522{
523 // Need min/maxLati, so update bbox
524 QList<double> m_deltaXs;
525 double m_minX, m_maxX, m_minLati, m_maxLati;
526 m_bboxDirty = false;
527 computeBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
528
529 if (degreesLatitude > 0.0)
530 degreesLatitude = qMin(degreesLatitude, 90.0 - m_maxLati);
531 else
532 degreesLatitude = qMax(degreesLatitude, -90.0 - m_minLati);
533 for (QGeoCoordinate &p: m_path) {
534 p.setLatitude(p.latitude() + degreesLatitude);
535 p.setLongitude(QLocationUtils::wrapLong(p.longitude() + degreesLongitude));
536 }
537 m_bbox.translate(degreesLatitude, degreesLongitude);
538 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
539}
540
541QGeoRectangle QGeoPathPrivate::boundingGeoRectangle() const
542{
543 if (m_bboxDirty)
544 const_cast<QGeoPathPrivate &>(*this).computeBoundingBox();
545 return m_bbox;
546}
547
548size_t QGeoPathPrivate::hash(size_t seed) const
549{
550 const size_t res = qHashRange(m_path.cbegin(), m_path.cend(), seed);
551 return qHashMulti(seed, res, m_width);
552}
553
554void QGeoPathPrivate::setPath(const QList<QGeoCoordinate> &path)
555{
556 for (const QGeoCoordinate &c: path)
557 if (!c.isValid())
558 return;
559 m_path = path;
560 markDirty();
561}
562
563void QGeoPathPrivate::clearPath()
564{
565 m_path.clear();
566 markDirty();
567}
568
569void QGeoPathPrivate::addCoordinate(const QGeoCoordinate &coordinate)
570{
571 if (!coordinate.isValid())
572 return;
573 m_path.append(coordinate);
574 markDirty();
575}
576
577void QGeoPathPrivate::insertCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
578{
579 if (index < 0 || index > m_path.size() || !coordinate.isValid())
580 return;
581 m_path.insert(index, coordinate);
582 markDirty();
583}
584
585void QGeoPathPrivate::replaceCoordinate(qsizetype index, const QGeoCoordinate &coordinate)
586{
587 if (index < 0 || index >= m_path.size() || !coordinate.isValid())
588 return;
589 m_path[index] = coordinate;
590 markDirty();
591}
592
593void QGeoPathPrivate::removeCoordinate(const QGeoCoordinate &coordinate)
594{
595 qsizetype index = m_path.lastIndexOf(coordinate);
596 removeCoordinate(index);
597}
598
599void QGeoPathPrivate::removeCoordinate(qsizetype index)
600{
601 if (index < 0 || index >= m_path.size())
602 return;
603 m_path.removeAt(index);
604 markDirty();
605}
606
607void QGeoPathPrivate::markDirty()
608{
609 m_bboxDirty = true;
610}
611
612void QGeoPathPrivate::computeBoundingBox()
613{
614 QList<double> m_deltaXs;
615 double m_minX, m_maxX, m_minLati, m_maxLati;
616 m_bboxDirty = false;
617 computeBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
618 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
619}
620
621QGeoPathPrivateEager::QGeoPathPrivateEager()
622: QGeoPathPrivate()
623{
624 m_bboxDirty = false; // never dirty on the eager version
625}
626
627QGeoPathPrivateEager::QGeoPathPrivateEager(const QList<QGeoCoordinate> &path, const qreal width)
628: QGeoPathPrivate(path, width)
629{
630 m_bboxDirty = false; // never dirty on the eager version
631}
632
633QGeoPathPrivateEager::~QGeoPathPrivateEager()
634{
635
636}
637
638QGeoShapePrivate *QGeoPathPrivateEager::clone() const
639{
640 return new QGeoPathPrivateEager(*this);
641}
642
643void QGeoPathPrivateEager::markDirty()
644{
645 computeBoundingBox();
646}
647
648void QGeoPathPrivateEager::translate(double degreesLatitude, double degreesLongitude)
649{
650 if (degreesLatitude > 0.0)
651 degreesLatitude = qMin(degreesLatitude, 90.0 - m_maxLati);
652 else
653 degreesLatitude = qMax(degreesLatitude, -90.0 - m_minLati);
654 for (QGeoCoordinate &p: m_path) {
655 p.setLatitude(p.latitude() + degreesLatitude);
656 p.setLongitude(QLocationUtils::wrapLong(p.longitude() + degreesLongitude));
657 }
658 m_bbox.translate(degreesLatitude, degreesLongitude);
659 m_minLati += degreesLatitude;
660 m_maxLati += degreesLatitude;
661 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
662}
663
664void QGeoPathPrivateEager::addCoordinate(const QGeoCoordinate &coordinate)
665{
666 if (!coordinate.isValid())
667 return;
668 m_path.append(coordinate);
669 //m_clipperDirty = true; // clipper not used in polylines
670 updateBoundingBox();
671}
672
673void QGeoPathPrivateEager::QGeoPathPrivateEager::computeBoundingBox()
674{
675 computeBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
676 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
677}
678
679void QGeoPathPrivateEager::QGeoPathPrivateEager::updateBoundingBox()
680{
681 updateBBox(m_path, m_deltaXs, m_minX, m_maxX, m_minLati, m_maxLati, m_bbox);
682 m_leftBoundWrapped = QWebMercator::coordToMercator(m_bbox.topLeft()).x();
683}
684
685QGeoPathEager::QGeoPathEager() : QGeoPath()
686{
687 d_ptr = new QGeoPathPrivateEager;
688}
689
690QGeoPathEager::QGeoPathEager(const QList<QGeoCoordinate> &path, const qreal &width) : QGeoPath()
691{
692 d_ptr = new QGeoPathPrivateEager(path, width);
693}
694
695QGeoPathEager::QGeoPathEager(const QGeoPath &other) : QGeoPath()
696{
697 d_ptr = new QGeoPathPrivateEager;
698 setPath(other.path());
699 setWidth(other.width());
700}
701
702QGeoPathEager::QGeoPathEager(const QGeoShape &other) : QGeoPath()
703{
704 if (other.type() == QGeoShape::PathType)
705 *this = QGeoPathEager(QGeoPath(other));
706 else
707 d_ptr = new QGeoPathPrivateEager;
708}
709
710QGeoPathEager::~QGeoPathEager() {}
711
712QT_END_NAMESPACE
713
714#include "moc_qgeopath_p.cpp"
715#include "moc_qgeopath.cpp"
Combined button and popup list for selecting options.