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