1/****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36
37#include "qdeclarativepolylinemapitem_p.h"
38#include "qdeclarativepolylinemapitem_p_p.h"
39#include "qdeclarativerectanglemapitem_p_p.h"
40#include "qdeclarativecirclemapitem_p_p.h"
41#include "qlocationutils_p.h"
42#include "qdeclarativegeomapitemutils_p.h"
43#include "error_messages_p.h"
44#include "locationvaluetypehelper_p.h"
45#include "qdoublevector2d_p.h"
46#include <QtLocation/private/qgeomap_p.h>
47#include <QtPositioning/private/qwebmercator_p.h>
48
49#include <QtCore/QScopedValueRollback>
50#include <QtQml/QQmlInfo>
51#include <QtQml/private/qqmlengine_p.h>
52#include <QPainter>
53#include <QPainterPath>
54#include <QPainterPathStroker>
55#include <qnumeric.h>
56
57#include <QtGui/private/qvectorpath_p.h>
58#include <QtGui/private/qtriangulatingstroker_p.h>
59#include <QtGui/private/qtriangulator_p.h>
60
61#include <QtPositioning/private/qclipperutils_p.h>
62#include <QtPositioning/private/qgeopath_p.h>
63#include <QtQuick/private/qsgmaterialshader_p.h>
64#include <array>
65#include <QThreadPool>
66#include <QRunnable>
67#include <QtLocation/private/qgeomapparameter_p.h>
68#include "qgeosimplify_p.h"
69
70QT_BEGIN_NAMESPACE
71
72struct ThreadPool // to have a thread pool with max 1 thread for geometry processing
73{
74 ThreadPool ()
75 {
76 m_threadPool.setMaxThreadCount(1);
77 }
78
79 void start(QRunnable *runnable, int priority = 0)
80 {
81 m_threadPool.start(runnable, priority);
82 }
83
84 QThreadPool m_threadPool;
85};
86
87Q_GLOBAL_STATIC(ThreadPool, threadPool)
88
89
90static const double kClipperScaleFactor = 281474976710656.0; // 48 bits of precision
91
92static inline IntPoint toIntPoint(const double x, const double y)
93{
94 return IntPoint(cInt(x * kClipperScaleFactor), cInt(y * kClipperScaleFactor));
95}
96
97static IntPoint toIntPoint(const QDoubleVector2D &p)
98{
99 return toIntPoint(x: p.x(), y: p.y());
100}
101
102static bool get_line_intersection(const double p0_x,
103 const double p0_y,
104 const double p1_x,
105 const double p1_y,
106 const double p2_x,
107 const double p2_y,
108 const double p3_x,
109 const double p3_y,
110 double *i_x,
111 double *i_y,
112 double *i_t)
113{
114 const double s10_x = p1_x - p0_x;
115 const double s10_y = p1_y - p0_y;
116 const double s32_x = p3_x - p2_x;
117 const double s32_y = p3_y - p2_y;
118
119 const double denom = s10_x * s32_y - s32_x * s10_y;
120 if (denom == 0.0)
121 return false; // Collinear
122 const bool denomPositive = denom > 0;
123
124 const double s02_x = p0_x - p2_x;
125 const double s02_y = p0_y - p2_y;
126 const double s_numer = s10_x * s02_y - s10_y * s02_x;
127 if ((s_numer < 0.0) == denomPositive)
128 return false; // No collision
129
130 const double t_numer = s32_x * s02_y - s32_y * s02_x;
131 if ((t_numer < 0.0) == denomPositive)
132 return false; // No collision
133
134 if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
135 return false; // No collision
136 // Collision detected
137 *i_t = t_numer / denom;
138 *i_x = p0_x + (*i_t * s10_x);
139 *i_y = p0_y + (*i_t * s10_y);
140
141 return true;
142}
143
144enum SegmentType {
145 NoIntersection,
146 OneIntersection,
147 TwoIntersections
148};
149
150static QList<QList<QDoubleVector2D> > clipLine(
151 const QList<QDoubleVector2D> &l,
152 const QList<QDoubleVector2D> &poly)
153{
154 QList<QList<QDoubleVector2D> > res;
155 if (poly.size() < 2 || l.size() < 2)
156 return res;
157
158 // Step 1: build edges
159 std::vector<std::array<double, 4> > edges;
160 for (int i = 1; i < poly.size(); i++)
161 edges.push_back(x: { ._M_elems: { poly.at(i: i-1).x(), poly.at(i: i-1).y(), poly.at(i).x(), poly.at(i).y() } });
162 edges.push_back(x: { ._M_elems: { poly.at(i: poly.size()-1).x(), poly.at(i: poly.size()-1).y(), poly.at(i: 0).x(), poly.at(i: 0).y() } });
163
164 // Build Path to check for containment, for edges not intersecting
165 // This step could be speeded up by forcing the orientation of the polygon, and testing the cross products in the step
166 // below, thus avoiding to resort to clipper.
167 Path clip;
168 for (const auto &v: poly)
169 clip.push_back(x: toIntPoint(p: v));
170
171 // Step 2: check each segment against each edge
172 QList<QDoubleVector2D> subLine;
173 std::array<double, 4> intersections = { ._M_elems: { 0.0, 0.0, 0.0, 0.0 } };
174
175 for (int i = 0; i < l.size() - 1; ++i) {
176 SegmentType type = NoIntersection;
177 double t = -1; // valid values are in [0, 1]. Only written if intersects
178 double previousT = t;
179 double i_x, i_y;
180
181 const int firstContained = c2t::clip2tri::pointInPolygon(pt: toIntPoint(x: l.at(i).x(), y: l.at(i).y()), path: clip);
182 const int secondContained = c2t::clip2tri::pointInPolygon(pt: toIntPoint(x: l.at(i: i+1).x(), y: l.at(i: i+1).y()), path: clip);
183
184 if (firstContained && secondContained) { // Second most common condition, test early and skip inner loop if possible
185 if (!subLine.size())
186 subLine.push_back(t: l.at(i)); // the initial element has to be pushed now.
187 subLine.push_back(t: l.at(i: i+1));
188 continue;
189 }
190
191 for (unsigned int j = 0; j < edges.size(); ++j) {
192 const bool intersects = get_line_intersection(p0_x: l.at(i).x(),
193 p0_y: l.at(i).y(),
194 p1_x: l.at(i: i+1).x(),
195 p1_y: l.at(i: i+1).y(),
196 p2_x: edges.at(n: j).at(n: 0),
197 p2_y: edges.at(n: j).at(n: 1),
198 p3_x: edges.at(n: j).at(n: 2),
199 p3_y: edges.at(n: j).at(n: 3),
200 i_x: &i_x,
201 i_y: &i_y,
202 i_t: &t);
203 if (intersects) {
204 if (previousT >= 0.0) { //One intersection already hit
205 if (t < previousT) { // Reorder
206 intersections[2] = intersections[0];
207 intersections[3] = intersections[1];
208 intersections[0] = i_x;
209 intersections[1] = i_y;
210 } else {
211 intersections[2] = i_x;
212 intersections[3] = i_y;
213 }
214
215 type = TwoIntersections;
216 break; // no need to check anything else
217 } else { // First intersection
218 intersections[0] = i_x;
219 intersections[1] = i_y;
220 type = OneIntersection;
221 }
222 previousT = t;
223 }
224 }
225
226 if (type == NoIntersection) {
227 if (!firstContained && !secondContained) { // Both outside
228 subLine.clear();
229 } else if (firstContained && secondContained) {
230 // Handled above already.
231 } else { // Mismatch between PointInPolygon and get_line_intersection. Treat it as no intersection
232 if (subLine.size())
233 res.push_back(t: subLine);
234 subLine.clear();
235 }
236 } else if (type == OneIntersection) { // Need to check the following cases to avoid mismatch with PointInPolygon result.
237 if (firstContained <= 0 && secondContained > 0) { // subLine MUST be empty
238 if (!subLine.size())
239 subLine.push_back(t: QDoubleVector2D(intersections[0], intersections[1]));
240 subLine.push_back(t: l.at(i: i+1));
241 } else if (firstContained > 0 && secondContained <= 0) { // subLine MUST NOT be empty
242 if (!subLine.size())
243 subLine.push_back(t: l.at(i));
244 subLine.push_back(t: QDoubleVector2D(intersections[0], intersections[1]));
245 res.push_back(t: subLine);
246 subLine.clear();
247 } else {
248 if (subLine.size())
249 res.push_back(t: subLine);
250 subLine.clear();
251 }
252 } else { // Two
253 // restart strip
254 subLine.clear();
255 subLine.push_back(t: QDoubleVector2D(intersections[0], intersections[1]));
256 subLine.push_back(t: QDoubleVector2D(intersections[2], intersections[3]));
257 res.push_back(t: subLine);
258 subLine.clear();
259 }
260 }
261
262 if (subLine.size())
263 res.push_back(t: subLine);
264 return res;
265}
266
267/*!
268 \qmltype MapPolyline
269 \instantiates QDeclarativePolylineMapItem
270 \inqmlmodule QtLocation
271 \ingroup qml-QtLocation5-maps
272 \since QtLocation 5.0
273
274 \brief The MapPolyline type displays a polyline on a map.
275
276 The MapPolyline type displays a polyline on a map, specified in terms of an ordered list of
277 \l {coordinate}{coordinates}. The \l {coordinate}{coordinates} on
278 the path cannot be directly changed after being added to the Polyline. Instead, copy the
279 \l path into a var, modify the copy and reassign the copy back to the \l path.
280
281 \code
282 var path = mapPolyline.path;
283 path[0].latitude = 5;
284 mapPolyline.path = path;
285 \endcode
286
287 Coordinates can also be added and removed at any time using the \l addCoordinate and
288 \l removeCoordinate methods.
289
290 By default, the polyline is displayed as a 1-pixel thick black line. This
291 can be changed using the \l line.width and \l line.color properties.
292
293 \section2 Performance
294
295 MapPolylines have a rendering cost that is O(n) with respect to the number
296 of vertices. This means that the per frame cost of having a polyline on
297 the Map grows in direct proportion to the number of points in the polyline.
298
299 Like the other map objects, MapPolyline is normally drawn without a smooth
300 appearance. Setting the \l {Item::opacity}{opacity} property will force the object to
301 be blended, which decreases performance considerably depending on the hardware in use.
302
303 \section2 Example Usage
304
305 The following snippet shows a MapPolyline with 4 points, making a shape
306 like the top part of a "question mark" (?), near Brisbane, Australia.
307 The line drawn is 3 pixels in width and green in color.
308
309 \code
310 Map {
311 MapPolyline {
312 line.width: 3
313 line.color: 'green'
314 path: [
315 { latitude: -27, longitude: 153.0 },
316 { latitude: -27, longitude: 154.1 },
317 { latitude: -28, longitude: 153.5 },
318 { latitude: -29, longitude: 153.5 }
319 ]
320 }
321 }
322 \endcode
323
324 \image api-mappolyline.png
325*/
326
327/*!
328 \qmlproperty bool QtLocation::MapPolyline::autoFadeIn
329
330 This property holds whether the item automatically fades in when zooming into the map
331 starting from very low zoom levels. By default this is \c true.
332 Setting this property to \c false causes the map item to always have the opacity specified
333 with the \l QtQuick::Item::opacity property, which is 1.0 by default.
334
335 \since 5.14
336*/
337
338QDeclarativeMapLineProperties::QDeclarativeMapLineProperties(QObject *parent) :
339 QObject(parent),
340 width_(1.0),
341 color_(Qt::black)
342{
343}
344
345/*!
346 \internal
347*/
348QColor QDeclarativeMapLineProperties::color() const
349{
350 return color_;
351}
352
353/*!
354 \internal
355*/
356void QDeclarativeMapLineProperties::setColor(const QColor &color)
357{
358 if (color_ == color)
359 return;
360
361 color_ = color;
362 emit colorChanged(color: color_);
363}
364
365/*!
366 \internal
367*/
368qreal QDeclarativeMapLineProperties::width() const
369{
370 return width_;
371}
372
373/*!
374 \internal
375*/
376void QDeclarativeMapLineProperties::setWidth(qreal width)
377{
378 if (width_ == width)
379 return;
380
381 width_ = width;
382 emit widthChanged(width: width_);
383}
384
385QGeoMapPolylineGeometry::QGeoMapPolylineGeometry()
386{
387}
388
389QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap &map,
390 const QList<QDoubleVector2D> &path,
391 QDoubleVector2D &leftBoundWrapped)
392{
393 /*
394 * Approach:
395 * 1) project coordinates to wrapped web mercator, and do unwrapBelowX
396 * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons)
397 * 2.1) recalculate the origin and geoLeftBound to prevent these parameters from ending in unprojectable areas
398 * 2.2) ensure the left bound does not wrap around due to QGeoCoordinate <-> clipper conversions
399 */
400 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
401 srcOrigin_ = geoLeftBound_;
402
403 double unwrapBelowX = 0;
404 leftBoundWrapped = p.wrapMapProjection(projection: p.geoToMapProjection(coordinate: geoLeftBound_));
405 if (preserveGeometry_)
406 unwrapBelowX = leftBoundWrapped.x();
407
408 QList<QDoubleVector2D> wrappedPath;
409 wrappedPath.reserve(alloc: path.size());
410 QDoubleVector2D wrappedLeftBound(qInf(), qInf());
411 // 1)
412 for (int i = 0; i < path.size(); ++i) {
413 const QDoubleVector2D &coord = path.at(i);
414 QDoubleVector2D wrappedProjection = p.wrapMapProjection(projection: coord);
415
416 // We can get NaN if the map isn't set up correctly, or the projection
417 // is faulty -- probably best thing to do is abort
418 if (!qIsFinite(d: wrappedProjection.x()) || !qIsFinite(d: wrappedProjection.y()))
419 return QList<QList<QDoubleVector2D> >();
420
421 const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x());
422 // unwrap x to preserve geometry if moved to border of map
423 if (preserveGeometry_ && isPointLessThanUnwrapBelowX) {
424 double distance = wrappedProjection.x() - unwrapBelowX;
425 if (distance < 0.0)
426 distance += 1.0;
427 wrappedProjection.setX(unwrapBelowX + distance);
428 }
429 if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) {
430 wrappedLeftBound = wrappedProjection;
431 }
432 wrappedPath.append(t: wrappedProjection);
433 }
434
435#ifdef QT_LOCATION_DEBUG
436 m_wrappedPath = wrappedPath;
437#endif
438
439 // 2)
440 QList<QList<QDoubleVector2D> > clippedPaths;
441 const QList<QDoubleVector2D> &visibleRegion = p.projectableGeometry();
442 if (visibleRegion.size()) {
443 clippedPaths = clipLine(l: wrappedPath, poly: visibleRegion);
444
445 // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X
446 QDoubleVector2D lb(qInf(), qInf());
447 for (const QList<QDoubleVector2D> &path: clippedPaths) {
448 for (const QDoubleVector2D &p: path) {
449 if (p == leftBoundWrapped) {
450 lb = p;
451 break;
452 } else if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) {
453 // y-minimization needed to find the same point on polygon and border
454 lb = p;
455 }
456 }
457 }
458 if (qIsInf(d: lb.x()))
459 return QList<QList<QDoubleVector2D> >();
460
461 // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which
462 // in turn will make the geometry wrap around.
463 lb.setX(qMax(a: wrappedLeftBound.x(), b: lb.x()));
464 leftBoundWrapped = lb;
465 } else {
466 clippedPaths.append(t: wrappedPath);
467 }
468
469#ifdef QT_LOCATION_DEBUG
470 m_clippedPaths = clippedPaths;
471#endif
472
473 return clippedPaths;
474}
475
476void QGeoMapPolylineGeometry::pathToScreen(const QGeoMap &map,
477 const QList<QList<QDoubleVector2D> > &clippedPaths,
478 const QDoubleVector2D &leftBoundWrapped)
479{
480 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
481 // 3) project the resulting geometry to screen position and calculate screen bounds
482 double minX = qInf();
483 double minY = qInf();
484 double maxX = -qInf();
485 double maxY = -qInf();
486 srcOrigin_ = p.mapProjectionToGeo(projection: p.unwrapMapProjection(wrappedProjection: leftBoundWrapped));
487 QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(wrappedProjection: leftBoundWrapped);
488 for (const QList<QDoubleVector2D> &path: clippedPaths) {
489 QDoubleVector2D lastAddedPoint;
490 for (int i = 0; i < path.size(); ++i) {
491 QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(wrappedProjection: path.at(i));
492 point = point - origin; // (0,0) if point == geoLeftBound_
493
494 minX = qMin(a: point.x(), b: minX);
495 minY = qMin(a: point.y(), b: minY);
496 maxX = qMax(a: point.x(), b: maxX);
497 maxY = qMax(a: point.y(), b: maxY);
498
499 if (i == 0) {
500 srcPoints_ << point.x() << point.y();
501 srcPointTypes_ << QPainterPath::MoveToElement;
502 lastAddedPoint = point;
503 } else {
504 if ((point - lastAddedPoint).manhattanLength() > 3 ||
505 i == path.size() - 1) {
506 srcPoints_ << point.x() << point.y();
507 srcPointTypes_ << QPainterPath::LineToElement;
508 lastAddedPoint = point;
509 }
510 }
511 }
512 }
513
514 sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
515}
516
517/*!
518 \internal
519*/
520void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
521 const QList<QDoubleVector2D> &path,
522 const QGeoCoordinate geoLeftBound)
523{
524 if (!sourceDirty_)
525 return;
526
527 geoLeftBound_ = geoLeftBound;
528
529 // clear the old data and reserve enough memory
530 srcPoints_.clear();
531 srcPoints_.reserve(asize: path.size() * 2);
532 srcPointTypes_.clear();
533 srcPointTypes_.reserve(asize: path.size());
534
535 /*
536 * Approach:
537 * 1) project coordinates to wrapped web mercator, and do unwrapBelowX
538 * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons)
539 * 3) project the resulting geometry to screen position and calculate screen bounds
540 */
541
542 QDoubleVector2D leftBoundWrapped;
543 // 1, 2)
544 const QList<QList<QDoubleVector2D> > &clippedPaths = clipPath(map, path, leftBoundWrapped);
545
546 // 3)
547 pathToScreen(map, clippedPaths, leftBoundWrapped);
548}
549
550// *** SCREEN CLIPPING *** //
551
552enum ClipPointType {
553 InsidePoint = 0x00,
554 LeftPoint = 0x01,
555 RightPoint = 0x02,
556 BottomPoint = 0x04,
557 TopPoint = 0x08
558};
559
560static inline int clipPointType(qreal x, qreal y, const QRectF &rect)
561{
562 int type = InsidePoint;
563 if (x < rect.left())
564 type |= LeftPoint;
565 else if (x > rect.right())
566 type |= RightPoint;
567 if (y < rect.top())
568 type |= TopPoint;
569 else if (y > rect.bottom())
570 type |= BottomPoint;
571 return type;
572}
573
574static void clipSegmentToRect(qreal x0, qreal y0, qreal x1, qreal y1,
575 const QRectF &clipRect,
576 QVector<qreal> &outPoints,
577 QVector<QPainterPath::ElementType> &outTypes)
578{
579 int type0 = clipPointType(x: x0, y: y0, rect: clipRect);
580 int type1 = clipPointType(x: x1, y: y1, rect: clipRect);
581 bool accept = false;
582
583 while (true) {
584 if (!(type0 | type1)) {
585 accept = true;
586 break;
587 } else if (type0 & type1) {
588 break;
589 } else {
590 qreal x = 0.0;
591 qreal y = 0.0;
592 int outsideType = type0 ? type0 : type1;
593
594 if (outsideType & BottomPoint) {
595 x = x0 + (x1 - x0) * (clipRect.bottom() - y0) / (y1 - y0);
596 y = clipRect.bottom() - 0.1;
597 } else if (outsideType & TopPoint) {
598 x = x0 + (x1 - x0) * (clipRect.top() - y0) / (y1 - y0);
599 y = clipRect.top() + 0.1;
600 } else if (outsideType & RightPoint) {
601 y = y0 + (y1 - y0) * (clipRect.right() - x0) / (x1 - x0);
602 x = clipRect.right() - 0.1;
603 } else if (outsideType & LeftPoint) {
604 y = y0 + (y1 - y0) * (clipRect.left() - x0) / (x1 - x0);
605 x = clipRect.left() + 0.1;
606 }
607
608 if (outsideType == type0) {
609 x0 = x;
610 y0 = y;
611 type0 = clipPointType(x: x0, y: y0, rect: clipRect);
612 } else {
613 x1 = x;
614 y1 = y;
615 type1 = clipPointType(x: x1, y: y1, rect: clipRect);
616 }
617 }
618 }
619
620 if (accept) {
621 if (outPoints.size() >= 2) {
622 qreal lastX, lastY;
623 lastY = outPoints.at(i: outPoints.size() - 1);
624 lastX = outPoints.at(i: outPoints.size() - 2);
625
626 if (!qFuzzyCompare(p1: lastY, p2: y0) || !qFuzzyCompare(p1: lastX, p2: x0)) {
627 outTypes << QPainterPath::MoveToElement;
628 outPoints << x0 << y0;
629 }
630 } else {
631 outTypes << QPainterPath::MoveToElement;
632 outPoints << x0 << y0;
633 }
634
635 outTypes << QPainterPath::LineToElement;
636 outPoints << x1 << y1;
637 }
638}
639
640static void clipPathToRect(const QVector<qreal> &points,
641 const QVector<QPainterPath::ElementType> &types,
642 const QRectF &clipRect,
643 QVector<qreal> &outPoints,
644 QVector<QPainterPath::ElementType> &outTypes)
645{
646 outPoints.clear();
647 outPoints.reserve(asize: points.size());
648 outTypes.clear();
649 outTypes.reserve(asize: types.size());
650
651 qreal lastX = 0;
652 qreal lastY = 0; // or else used uninitialized
653 for (int i = 0; i < types.size(); ++i) {
654 if (i > 0 && types[i] != QPainterPath::MoveToElement) {
655 qreal x = points[i * 2], y = points[i * 2 + 1];
656 clipSegmentToRect(x0: lastX, y0: lastY, x1: x, y1: y, clipRect, outPoints, outTypes);
657 }
658
659 lastX = points[i * 2];
660 lastY = points[i * 2 + 1];
661 }
662}
663
664////////////////////////////////////////////////////////////////////////////
665
666/*!
667 \internal
668*/
669void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
670 qreal strokeWidth,
671 bool adjustTranslation)
672{
673 if (!screenDirty_)
674 return;
675
676 QPointF origin = map.geoProjection().coordinateToItemPosition(coordinate: srcOrigin_, clipToViewport: false).toPointF();
677
678 if (!qIsFinite(d: origin.x()) || !qIsFinite(d: origin.y()) || srcPointTypes_.size() < 2) { // the line might have been clipped away.
679 clear();
680 return;
681 }
682
683 // Create the viewport rect in the same coordinate system
684 // as the actual points
685 QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight());
686 viewport.adjust(xp1: -strokeWidth, yp1: -strokeWidth, xp2: strokeWidth * 2, yp2: strokeWidth * 2);
687 viewport.translate(p: -1 * origin);
688
689 QVector<qreal> points;
690 QVector<QPainterPath::ElementType> types;
691
692 if (clipToViewport_) {
693 // Although the geometry has already been clipped against the visible region in wrapped mercator space.
694 // This is currently still needed to prevent a number of artifacts deriving from QTriangulatingStroker processing
695 // very large lines (that is, polylines that span many pixels in screen space)
696 clipPathToRect(points: srcPoints_, types: srcPointTypes_, clipRect: viewport, outPoints&: points, outTypes&: types);
697 } else {
698 points = srcPoints_;
699 types = srcPointTypes_;
700 }
701
702 QVectorPath vp(points.data(), types.size(), types.data());
703 QTriangulatingStroker ts;
704 // As of Qt5.11, the clip argument is not actually used, in the call below.
705 ts.process(path: vp, pen: QPen(QBrush(Qt::black), strokeWidth), clip: QRectF(), hints: QPainter::Qt4CompatiblePainting);
706
707 clear();
708
709 // Nothing is on the screen
710 if (ts.vertexCount() == 0)
711 return;
712
713 // QTriangulatingStroker#vertexCount is actually the length of the array,
714 // not the number of vertices
715 screenVertices_.reserve(asize: ts.vertexCount());
716
717 QRectF bb;
718
719 QPointF pt;
720 const float *vs = ts.vertices();
721 for (int i = 0; i < (ts.vertexCount()/2*2); i += 2) {
722 pt = QPointF(vs[i], vs[i + 1]);
723 screenVertices_ << pt;
724
725 if (!qIsFinite(d: pt.x()) || !qIsFinite(d: pt.y()))
726 break;
727
728 if (!bb.contains(p: pt)) {
729 if (pt.x() < bb.left())
730 bb.setLeft(pt.x());
731
732 if (pt.x() > bb.right())
733 bb.setRight(pt.x());
734
735 if (pt.y() < bb.top())
736 bb.setTop(pt.y());
737
738 if (pt.y() > bb.bottom())
739 bb.setBottom(pt.y());
740 }
741 }
742
743 screenBounds_ = bb;
744 const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) * 0.5: QPointF();
745 this->translate( offset: -1 * sourceBounds_.topLeft() + strokeOffset);
746}
747
748void QGeoMapPolylineGeometry::clearSource()
749{
750 srcPoints_.clear();
751 srcPointTypes_.clear();
752}
753
754bool QGeoMapPolylineGeometry::contains(const QPointF &point) const
755{
756 // screenOutline_.contains(screenPoint) doesn't work, as, it appears, that
757 // screenOutline_ for QGeoMapPolylineGeometry is empty (QRectF(0,0 0x0))
758 const QVector<QPointF> &verts = vertices();
759 QPolygonF tri;
760 for (int i = 0; i < verts.size(); ++i) {
761 tri << verts[i];
762 if (tri.size() == 3) {
763 if (tri.containsPoint(pt: point,fillRule: Qt::OddEvenFill))
764 return true;
765 tri.remove(i: 0);
766 }
767 }
768
769 return false;
770}
771
772#if QT_CONFIG(opengl)
773void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly)
774{
775 if (!sourceDirty_)
776 return;
777 QGeoPath p(poly.path());
778 if (poly.path().size() && poly.path().last() != poly.path().first())
779 p.addCoordinate(coordinate: poly.path().first());
780 updateSourcePoints(map, poly: p);
781}
782
783void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPath &poly)
784{
785 if (!sourceDirty_)
786 return;
787 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
788
789 // build the actual path
790 // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
791
792
793 QDoubleVector2D leftBoundWrapped;
794 // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
795 QList<QDoubleVector2D> wrappedPath;
796 QDeclarativeGeoMapItemUtils::wrapPath(perimeter: poly.path(), geoLeftBound: geoLeftBound_, p,
797 wrappedPath, leftBoundWrapped: &leftBoundWrapped);
798
799 const QGeoRectangle &boundingRectangle = poly.boundingGeoRectangle();
800 updateSourcePoints(p, wrappedPath, boundingRectangle);
801}
802
803void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoProjectionWebMercator &p,
804 const QList<QDoubleVector2D> &wrappedPath,
805 const QGeoRectangle &boundingRectangle) {
806 if (!sourceDirty_)
807 return;
808 // 1.1) do the same for the bbox
809 // Beware: vertical lines (or horizontal lines) might have an "empty" bbox. Check for that
810
811 QGeoCoordinate topLeft = boundingRectangle.topLeft();
812 QGeoCoordinate bottomRight = boundingRectangle.bottomRight();
813 const qreal epsilon = 0.000001;
814 if (qFuzzyCompare(p1: topLeft.latitude(), p2: bottomRight.latitude())) {
815 topLeft.setLatitude(qBound(min: -90.0, val: topLeft.latitude() + epsilon ,max: 90.0));
816 bottomRight.setLatitude(qBound(min: -90.0, val: bottomRight.latitude() - epsilon ,max: 90.0));
817 }
818 if (qFuzzyCompare(p1: topLeft.longitude(), p2: bottomRight.longitude())) {
819 topLeft.setLongitude(QLocationUtils::wrapLong(lng: topLeft.longitude() - epsilon));
820 bottomRight.setLongitude(QLocationUtils::wrapLong(lng: bottomRight.longitude() + epsilon));
821 }
822 QGeoPolygon bbox(QGeoRectangle(topLeft, bottomRight));
823 QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1;
824 QDeclarativeGeoMapItemUtils::wrapPath(perimeter: bbox.path(), geoLeftBound: bbox.boundingGeoRectangle().topLeft(), p,
825 wrappedPath&: wrappedBbox, wrappedPathMinus1&: wrappedBboxMinus1, wrappedPathPlus1&: wrappedBboxPlus1, leftBoundWrapped: &m_bboxLeftBoundWrapped);
826
827 // New pointers, some old LOD task might still be running and operating on the old pointers.
828 resetLOD();
829
830 for (const auto &v: qAsConst(t: wrappedPath)) m_screenVertices->append(t: v);
831
832 m_wrappedPolygons.resize(asize: 3);
833 m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1;
834 m_wrappedPolygons[1].wrappedBboxes = wrappedBbox;
835 m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1;
836 srcOrigin_ = geoLeftBound_;
837}
838
839void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect)
840{
841 const QGeoPath path(QDeclarativeRectangleMapItemPrivateCPU::perimeter(rect));
842 updateSourcePoints(map, poly: path);
843}
844
845void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoCircle &circle)
846{
847 if (!sourceDirty_)
848 return;
849 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
850
851 QDoubleVector2D leftBoundWrapped;
852 // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
853 QList<QGeoCoordinate> path;
854 QGeoCoordinate leftBound;
855 QList<QDoubleVector2D> wrappedPath;
856 QDeclarativeCircleMapItemPrivateCPU::calculatePeripheralPoints(path, center: circle.center(), distance: circle.radius(), steps: QDeclarativeCircleMapItemPrivateCPU::CircleSamples, leftBound);
857 path << path.first();
858 geoLeftBound_ = leftBound;
859 QDeclarativeGeoMapItemUtils::wrapPath(perimeter: path, geoLeftBound: leftBound, p, wrappedPath, leftBoundWrapped: &leftBoundWrapped);
860 const QGeoRectangle &boundingRectangle = circle.boundingGeoRectangle();
861 updateSourcePoints(p, wrappedPath, boundingRectangle);
862}
863
864void QGeoMapPolylineGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth, bool /*adjustTranslation*/)
865{
866 if (map.viewportWidth() == 0 || map.viewportHeight() == 0) {
867 clear();
868 return;
869 }
870
871 // 1) identify which set to use: std, +1 or -1
872 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
873 const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(coordinate: srcOrigin_);
874 m_wrapOffset = p.projectionWrapFactor(projection: leftBoundMercator) + 1; // +1 to get the offset into QLists
875
876 if (sourceDirty_) {
877 // 1.1) select geometry set
878 // This could theoretically be skipped for those polylines whose bbox is not even projectable.
879 // However, such optimization could only be introduced if not calculating bboxes lazily.
880 // Hence not doing it.
881// if (m_screenVertices.size() > 1)
882 m_dataChanged = true;
883 }
884
885 updateQuickGeometry(p, strokeWidth);
886}
887
888void QGeoMapPolylineGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth)
889{
890 // 2) clip bbox
891 // BBox handling -- this is related to the bounding box geometry
892 // that has to inevitably follow the old projection codepath
893 // As it needs to provide projected coordinates for QtQuick interaction.
894 // This could be futher optimized to be updated in a lazy fashion.
895 const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(i: m_wrapOffset).wrappedBboxes;
896 QList<QList<QDoubleVector2D> > clippedBbox;
897 QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped;
898 bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1));
899 QDeclarativeGeoMapItemUtils::clipPolygon(wrappedPath: wrappedBbox, p, clippedPaths&: clippedBbox, leftBoundWrapped: &bboxLeftBoundWrapped, closed: false);
900
901 // 3) project bbox
902 QPainterPath ppi;
903
904 if ( !clippedBbox.size() ||
905 clippedBbox.first().size() < 3) {
906 sourceBounds_ = screenBounds_ = QRectF();
907 firstPointOffset_ = QPointF();
908 screenOutline_ = ppi;
909 return;
910 }
911
912 QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox: clippedBbox.first(), p, projectedBbox&: ppi); // Using first because a clipped box should always result in one polygon
913 const QRectF brect = ppi.boundingRect();
914 firstPointOffset_ = QPointF(brect.topLeft());
915 sourceBounds_ = brect;
916 screenOutline_ = ppi;
917
918 // 4) Set Screen bbox
919 screenBounds_ = brect;
920 sourceBounds_.setX(0);
921 sourceBounds_.setY(0);
922 sourceBounds_.setWidth(brect.width() + strokeWidth);
923 sourceBounds_.setHeight(brect.height() + strokeWidth);
924}
925#endif // QT_CONFIG(opengl)
926
927/*
928 * QDeclarativePolygonMapItem Private Implementations
929 */
930
931QDeclarativePolylineMapItemPrivate::~QDeclarativePolylineMapItemPrivate() {}
932
933QDeclarativePolylineMapItemPrivateCPU::~QDeclarativePolylineMapItemPrivateCPU() {}
934
935#if QT_CONFIG(opengl)
936QDeclarativePolylineMapItemPrivateOpenGLLineStrip::~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() {}
937
938QDeclarativePolylineMapItemPrivateOpenGLExtruded::~QDeclarativePolylineMapItemPrivateOpenGLExtruded() {}
939#endif
940
941/*
942 * QDeclarativePolygonMapItem Implementation
943 */
944
945struct PolylineBackendSelector
946{
947#if QT_CONFIG(opengl)
948 PolylineBackendSelector()
949 {
950 backend = (qgetenv(varName: "QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolylineMapItem::OpenGLExtruded : QDeclarativePolylineMapItem::Software;
951 }
952#endif
953 QDeclarativePolylineMapItem::Backend backend = QDeclarativePolylineMapItem::Software;
954};
955
956Q_GLOBAL_STATIC(PolylineBackendSelector, mapPolylineBackendSelector)
957
958QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent)
959: QDeclarativeGeoMapItemBase(parent),
960 m_line(this),
961 m_dirtyMaterial(true),
962 m_updatingGeometry(false),
963 m_d(new QDeclarativePolylineMapItemPrivateCPU(*this))
964{
965 m_itemType = QGeoMap::MapPolyline;
966 m_geopath = QGeoPathEager();
967 setFlag(flag: ItemHasContents, enabled: true);
968 QObject::connect(sender: &m_line, SIGNAL(colorChanged(QColor)),
969 receiver: this, SLOT(updateAfterLinePropertiesChanged()));
970 QObject::connect(sender: &m_line, SIGNAL(widthChanged(qreal)),
971 receiver: this, SLOT(updateAfterLinePropertiesChanged()));
972 setBackend(mapPolylineBackendSelector->backend);
973}
974
975QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem()
976{
977}
978
979/*!
980 \internal
981*/
982void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged()
983{
984 m_d->onLinePropertiesChanged();
985}
986
987/*!
988 \internal
989*/
990void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
991{
992 QDeclarativeGeoMapItemBase::setMap(quickMap,map);
993 if (map)
994 m_d->onMapSet();
995}
996
997/*!
998 \qmlproperty list<coordinate> MapPolyline::path
999
1000 This property holds the ordered list of coordinates which
1001 define the polyline.
1002*/
1003
1004QJSValue QDeclarativePolylineMapItem::path() const
1005{
1006 return fromList(object: this, list: m_geopath.path());
1007}
1008
1009void QDeclarativePolylineMapItem::setPath(const QJSValue &value)
1010{
1011 if (!value.isArray())
1012 return;
1013
1014 setPathFromGeoList(toList(object: this, value));
1015}
1016
1017/*!
1018 \qmlmethod void MapPolyline::setPath(geopath path)
1019
1020 Sets the \a path using a geopath type.
1021
1022 \since 5.10
1023
1024 \sa path
1025*/
1026void QDeclarativePolylineMapItem::setPath(const QGeoPath &path)
1027{
1028 if (m_geopath.path() == path.path())
1029 return;
1030
1031 m_geopath = QGeoPathEager(path);
1032 m_d->onGeoGeometryChanged();
1033 emit pathChanged();
1034}
1035
1036/*!
1037 \internal
1038*/
1039void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> &path)
1040{
1041 if (m_geopath.path() == path)
1042 return;
1043
1044 m_geopath.setPath(path);
1045
1046 m_d->onGeoGeometryChanged();
1047 emit pathChanged();
1048}
1049
1050/*!
1051 \qmlmethod int MapPolyline::pathLength()
1052
1053 Returns the number of coordinates of the polyline.
1054
1055 \since QtLocation 5.6
1056
1057 \sa path
1058*/
1059int QDeclarativePolylineMapItem::pathLength() const
1060{
1061 return m_geopath.path().length();
1062}
1063
1064/*!
1065 \qmlmethod void MapPolyline::addCoordinate(coordinate)
1066
1067 Adds the specified \a coordinate to the end of the path.
1068
1069 \sa insertCoordinate, removeCoordinate, path
1070*/
1071void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate)
1072{
1073 if (!coordinate.isValid())
1074 return;
1075
1076 m_geopath.addCoordinate(coordinate);
1077
1078 m_d->onGeoGeometryUpdated();
1079 emit pathChanged();
1080}
1081
1082/*!
1083 \qmlmethod void MapPolyline::insertCoordinate(index, coordinate)
1084
1085 Inserts a \a coordinate to the path at the given \a index.
1086
1087 \since QtLocation 5.6
1088
1089 \sa addCoordinate, removeCoordinate, path
1090*/
1091void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate)
1092{
1093 if (index < 0 || index > m_geopath.path().length())
1094 return;
1095
1096 m_geopath.insertCoordinate(index, coordinate);
1097
1098 m_d->onGeoGeometryChanged();
1099 emit pathChanged();
1100}
1101
1102/*!
1103 \qmlmethod void MapPolyline::replaceCoordinate(index, coordinate)
1104
1105 Replaces the coordinate in the current path at the given \a index
1106 with the new \a coordinate.
1107
1108 \since QtLocation 5.6
1109
1110 \sa addCoordinate, insertCoordinate, removeCoordinate, path
1111*/
1112void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate)
1113{
1114 if (index < 0 || index >= m_geopath.path().length())
1115 return;
1116
1117 m_geopath.replaceCoordinate(index, coordinate);
1118
1119 m_d->onGeoGeometryChanged();
1120 emit pathChanged();
1121}
1122
1123/*!
1124 \qmlmethod coordinate MapPolyline::coordinateAt(index)
1125
1126 Gets the coordinate of the polyline at the given \a index.
1127 If the index is outside the path's bounds then an invalid
1128 coordinate is returned.
1129
1130 \since QtLocation 5.6
1131*/
1132QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const
1133{
1134 if (index < 0 || index >= m_geopath.path().length())
1135 return QGeoCoordinate();
1136
1137 return m_geopath.coordinateAt(index);
1138}
1139
1140/*!
1141 \qmlmethod coordinate MapPolyline::containsCoordinate(coordinate)
1142
1143 Returns true if the given \a coordinate is part of the path.
1144
1145 \since QtLocation 5.6
1146*/
1147bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate)
1148{
1149 return m_geopath.containsCoordinate(coordinate);
1150}
1151
1152/*!
1153 \qmlmethod void MapPolyline::removeCoordinate(coordinate)
1154
1155 Removes \a coordinate from the path. If there are multiple instances of the
1156 same coordinate, the one added last is removed.
1157
1158 If \a coordinate is not in the path this method does nothing.
1159
1160 \sa addCoordinate, insertCoordinate, path
1161*/
1162void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate)
1163{
1164 int length = m_geopath.path().length();
1165 m_geopath.removeCoordinate(coordinate);
1166 if (m_geopath.path().length() == length)
1167 return;
1168
1169 m_d->onGeoGeometryChanged();
1170 emit pathChanged();
1171}
1172
1173/*!
1174 \qmlmethod void MapPolyline::removeCoordinate(index)
1175
1176 Removes a coordinate from the path at the given \a index.
1177
1178 If \a index is invalid then this method does nothing.
1179
1180 \since QtLocation 5.6
1181
1182 \sa addCoordinate, insertCoordinate, path
1183*/
1184void QDeclarativePolylineMapItem::removeCoordinate(int index)
1185{
1186 if (index < 0 || index >= m_geopath.path().length())
1187 return;
1188
1189 m_geopath.removeCoordinate(index);
1190
1191 m_d->onGeoGeometryChanged();
1192 emit pathChanged();
1193}
1194
1195/*!
1196 \qmlpropertygroup Location::MapPolyline::line
1197 \qmlproperty int MapPolyline::line.width
1198 \qmlproperty color MapPolyline::line.color
1199
1200 This property is part of the line property group. The line
1201 property group holds the width and color used to draw the line.
1202
1203 The width is in pixels and is independent of the zoom level of the map.
1204 The default values correspond to a black border with a width of 1 pixel.
1205
1206 For no line, use a width of 0 or a transparent color.
1207*/
1208
1209QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line()
1210{
1211 return &m_line;
1212}
1213
1214/*!
1215 \qmlproperty MapPolyline.Backend QtLocation::MapPolyline::backend
1216
1217 This property holds which backend is in use to render the map item.
1218 Valid values are \b MapPolyline.Software and \b{MapPolyline.OpenGLLineStrip}
1219 and \b{MapPolyline.OpenGLExtruded}.
1220 The default value is \b{MapPolyline.Software}.
1221
1222 \note \b{The release of this API with Qt 5.15 is a Technology Preview}.
1223 Ideally, as the OpenGL backends for map items mature, there will be
1224 no more need to also offer the legacy software-projection backend.
1225 So this property will likely disappear at some later point.
1226 To select OpenGL-accelerated item backends without using this property,
1227 it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS
1228 to \b{1}.
1229 Also note that all current OpenGL backends won't work as expected when enabling
1230 layers on the individual item, or when running on OpenGL core profiles greater than 2.x.
1231
1232 \since 5.15
1233*/
1234QDeclarativePolylineMapItem::Backend QDeclarativePolylineMapItem::backend() const
1235{
1236 return m_backend;
1237}
1238
1239void QDeclarativePolylineMapItem::setBackend(QDeclarativePolylineMapItem::Backend b)
1240{
1241 if (b == m_backend)
1242 return;
1243 m_backend = b;
1244 QScopedPointer<QDeclarativePolylineMapItemPrivate> d(
1245 (m_backend == Software)
1246 ? static_cast<QDeclarativePolylineMapItemPrivate *>(
1247 new QDeclarativePolylineMapItemPrivateCPU(*this))
1248#if QT_CONFIG(opengl)
1249 : ((m_backend == OpenGLExtruded)
1250 ? static_cast<QDeclarativePolylineMapItemPrivate *>(
1251 new QDeclarativePolylineMapItemPrivateOpenGLExtruded(*this))
1252 : static_cast<QDeclarativePolylineMapItemPrivate *>(
1253 new QDeclarativePolylineMapItemPrivateOpenGLLineStrip(
1254 *this))));
1255#else
1256 : nullptr);
1257 qFatal("Requested non software rendering backend, but source code is compiled wihtout opengl "
1258 "support");
1259#endif
1260 m_d.swap(other&: d);
1261 m_d->onGeoGeometryChanged();
1262 emit backendChanged();
1263}
1264
1265/*!
1266 \internal
1267*/
1268void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
1269{
1270 if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopath.isValid() || m_updatingGeometry) {
1271 QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry);
1272 return;
1273 }
1274 // TODO: change the algorithm to preserve the distances and size!
1275 QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(pos: QDoubleVector2D(newGeometry.center()), clipToViewport: false);
1276 QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(pos: QDoubleVector2D(oldGeometry.center()), clipToViewport: false);
1277 if (!newCenter.isValid() || !oldCenter.isValid())
1278 return;
1279 double offsetLongi = newCenter.longitude() - oldCenter.longitude();
1280 double offsetLati = newCenter.latitude() - oldCenter.latitude();
1281 if (offsetLati == 0.0 && offsetLongi == 0.0)
1282 return;
1283
1284 m_geopath.translate(degreesLatitude: offsetLati, degreesLongitude: offsetLongi);
1285 m_d->onGeoGeometryChanged();
1286 emit pathChanged();
1287
1288 // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested
1289 // call to this function.
1290}
1291
1292/*!
1293 \internal
1294*/
1295void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
1296{
1297 if (event.mapSize.isEmpty())
1298 return;
1299
1300 m_d->afterViewportChanged();
1301}
1302
1303/*!
1304 \internal
1305*/
1306void QDeclarativePolylineMapItem::updatePolish()
1307{
1308 if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
1309 return;
1310 m_d->updatePolish();
1311}
1312
1313void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p,
1314 const char *propertyName,
1315 bool update)
1316{
1317 static const QByteArrayList acceptedParameterTypes = QByteArrayList()
1318 << QByteArrayLiteral("lineCap")
1319 << QByteArrayLiteral("pen");
1320 switch (acceptedParameterTypes.indexOf(t: QByteArray(propertyName))) {
1321 case -1:
1322 qWarning() << "Invalid property " << QLatin1String(propertyName) << " for parameter lineStyle";
1323 break;
1324 case 0: // lineCap
1325 {
1326 const QVariant lineCap = p->property(name: "lineCap");
1327 m_d->m_penCapStyle = lineCap.value<Qt::PenCapStyle>(); // if invalid, will return 0 == FlatCap
1328 if (update)
1329 markSourceDirtyAndUpdate();
1330 break;
1331 }
1332 case 1: // penStyle
1333 {
1334 const QVariant penStyle = p->property(name: "pen");
1335 m_d->m_penStyle = penStyle.value<Qt::PenStyle>();
1336 if (m_d->m_penStyle == Qt::NoPen)
1337 m_d->m_penStyle = Qt::SolidLine;
1338 if (update)
1339 markSourceDirtyAndUpdate();
1340 break;
1341 }
1342 }
1343}
1344
1345void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName)
1346{
1347 updateLineStyleParameter(p, propertyName, update: true);
1348}
1349
1350void QDeclarativePolylineMapItem::componentComplete()
1351{
1352 QQuickItem::componentComplete();
1353 // Set up Dynamic Parameters
1354 QList<QGeoMapParameter *> dynamicParameters = quickChildren<QGeoMapParameter>();
1355 for (QGeoMapParameter *p : qAsConst(t&: dynamicParameters)) {
1356 if (p->type() == QLatin1String("lineStyle")) {
1357 updateLineStyleParameter(p, propertyName: "lineCap", update: false);
1358 updateLineStyleParameter(p, propertyName: "pen", update: false);
1359 connect(sender: p, signal: &QGeoMapParameter::propertyUpdated,
1360 receiver: this, slot: static_cast<void (QDeclarativePolylineMapItem::*)(QGeoMapParameter *, const char *)>(&QDeclarativePolylineMapItem::updateLineStyleParameter));
1361 markSourceDirtyAndUpdate();
1362 }
1363 }
1364}
1365
1366void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate()
1367{
1368 m_d->markSourceDirtyAndUpdate();
1369}
1370
1371/*!
1372 \internal
1373*/
1374QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
1375{
1376 return m_d->updateMapItemPaintNode(oldNode, data);
1377}
1378
1379bool QDeclarativePolylineMapItem::contains(const QPointF &point) const
1380{
1381 return m_d->contains(point);
1382}
1383
1384const QGeoShape &QDeclarativePolylineMapItem::geoShape() const
1385{
1386 return m_geopath;
1387}
1388
1389void QDeclarativePolylineMapItem::setGeoShape(const QGeoShape &shape)
1390{
1391 const QGeoPath geopath(shape); // if shape isn't a path, path will be created as a default-constructed path
1392 setPath(geopath);
1393}
1394
1395//////////////////////////////////////////////////////////////////////
1396
1397/*!
1398 \internal
1399*/
1400VisibleNode::VisibleNode() : m_blocked{true}, m_visible{true}
1401{
1402
1403}
1404
1405VisibleNode::~VisibleNode()
1406{
1407
1408}
1409
1410/*!
1411 \internal
1412*/
1413bool VisibleNode::subtreeBlocked() const
1414{
1415 return m_blocked || !m_visible;
1416}
1417
1418/*!
1419 \internal
1420*/
1421void VisibleNode::setSubtreeBlocked(bool blocked)
1422{
1423 m_blocked = blocked;
1424}
1425
1426bool VisibleNode::visible() const
1427{
1428 return m_visible;
1429}
1430
1431/*!
1432 \internal
1433*/
1434void VisibleNode::setVisible(bool visible)
1435{
1436 m_visible = visible;
1437}
1438
1439/*!
1440 \internal
1441*/
1442MapItemGeometryNode::~MapItemGeometryNode()
1443{
1444
1445}
1446
1447bool MapItemGeometryNode::isSubtreeBlocked() const
1448{
1449 return subtreeBlocked();
1450}
1451
1452
1453/*!
1454 \internal
1455*/
1456MapPolylineNode::MapPolylineNode() :
1457 geometry_(QSGGeometry::defaultAttributes_Point2D(),0)
1458{
1459 geometry_.setDrawingMode(QSGGeometry::DrawTriangleStrip);
1460 QSGGeometryNode::setMaterial(&fill_material_);
1461 QSGGeometryNode::setGeometry(&geometry_);
1462}
1463
1464
1465/*!
1466 \internal
1467*/
1468MapPolylineNode::~MapPolylineNode()
1469{
1470}
1471
1472/*!
1473 \internal
1474*/
1475void MapPolylineNode::update(const QColor &fillColor,
1476 const QGeoMapItemGeometry *shape)
1477{
1478 if (shape->size() == 0) {
1479 setSubtreeBlocked(true);
1480 return;
1481 } else {
1482 setSubtreeBlocked(false);
1483 }
1484
1485 QSGGeometry *fill = QSGGeometryNode::geometry();
1486 shape->allocateAndFill(geom: fill);
1487 markDirty(bits: DirtyGeometry);
1488
1489 if (fillColor != fill_material_.color()) {
1490 fill_material_.setColor(fillColor);
1491 setMaterial(&fill_material_);
1492 markDirty(bits: DirtyMaterial);
1493 }
1494}
1495
1496#if QT_CONFIG(opengl)
1497MapPolylineNodeOpenGLLineStrip::MapPolylineNodeOpenGLLineStrip()
1498: geometry_(QSGGeometry::defaultAttributes_Point2D(), 0)
1499{
1500 geometry_.setDrawingMode(QSGGeometry::DrawLineStrip);
1501 QSGGeometryNode::setMaterial(&fill_material_);
1502 QSGGeometryNode::setGeometry(&geometry_);
1503}
1504
1505MapPolylineNodeOpenGLLineStrip::~MapPolylineNodeOpenGLLineStrip()
1506{
1507
1508}
1509
1510void MapPolylineNodeOpenGLLineStrip::update(const QColor &fillColor,
1511 const qreal lineWidth,
1512 const QGeoMapPolylineGeometryOpenGL *shape,
1513 const QMatrix4x4 &geoProjection,
1514 const QDoubleVector3D &center,
1515 const Qt::PenCapStyle /*capStyle*/)
1516{
1517 if (shape->m_screenVertices->size() < 2) {
1518 setSubtreeBlocked(true);
1519 return;
1520 } else {
1521 setSubtreeBlocked(false);
1522 }
1523
1524 QSGGeometry *fill = QSGGeometryNode::geometry();
1525 if (shape->m_dataChanged) {
1526 shape->allocateAndFillLineStrip(geom: fill);
1527 markDirty(bits: DirtyGeometry);
1528 shape->m_dataChanged = false;
1529 }
1530 fill->setLineWidth(lineWidth);
1531 fill_material_.setLineWidth(lineWidth); // to make the material not compare equal if linewidth changes
1532
1533// if (fillColor != fill_material_.color())
1534 {
1535 fill_material_.setWrapOffset(shape->m_wrapOffset - 1);
1536 fill_material_.setColor(fillColor);
1537 fill_material_.setGeoProjection(geoProjection);
1538 fill_material_.setCenter(center);
1539 setMaterial(&fill_material_);
1540 markDirty(bits: DirtyMaterial);
1541 }
1542}
1543
1544MapPolylineShaderLineStrip::MapPolylineShaderLineStrip() : QSGMaterialShader(*new QSGMaterialShaderPrivate)
1545{
1546
1547}
1548
1549void MapPolylineShaderLineStrip::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
1550{
1551 Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
1552 MapPolylineMaterial *oldMaterial = static_cast<MapPolylineMaterial *>(oldEffect);
1553 MapPolylineMaterial *newMaterial = static_cast<MapPolylineMaterial *>(newEffect);
1554
1555 const QColor &c = newMaterial->color();
1556 const QMatrix4x4 &geoProjection = newMaterial->geoProjection();
1557 const QDoubleVector3D &center = newMaterial->center();
1558
1559 QVector3D vecCenter, vecCenter_lowpart;
1560 for (int i = 0; i < 3; i++)
1561 QLocationUtils::split_double(input: center.get(i), hipart: &vecCenter[i], lopart: &vecCenter_lowpart[i]);
1562
1563 if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) {
1564 float opacity = state.opacity() * c.alphaF();
1565 QVector4D v(c.redF() * opacity,
1566 c.greenF() * opacity,
1567 c.blueF() * opacity,
1568 opacity);
1569 program()->setUniformValue(location: m_color_id, value: v);
1570 }
1571
1572 if (state.isMatrixDirty())
1573 {
1574 program()->setUniformValue(location: m_matrix_id, value: state.projectionMatrix());
1575 }
1576
1577 program()->setUniformValue(location: m_mapProjection_id, value: geoProjection);
1578
1579 program()->setUniformValue(location: m_center_id, value: vecCenter);
1580 program()->setUniformValue(location: m_center_lowpart_id, value: vecCenter_lowpart);
1581 program()->setUniformValue(location: m_wrapOffset_id, value: float(newMaterial->wrapOffset()));
1582}
1583
1584const char * const *MapPolylineShaderLineStrip::attributeNames() const
1585{
1586 static char const *const attr[] = { "vertex", nullptr };
1587 return attr;
1588}
1589
1590QSGMaterialShader *MapPolylineMaterial::createShader() const
1591{
1592 return new MapPolylineShaderLineStrip();
1593}
1594
1595QSGMaterialType *MapPolylineMaterial::type() const
1596{
1597 static QSGMaterialType type;
1598 return &type;
1599}
1600
1601int MapPolylineMaterial::compare(const QSGMaterial *other) const
1602{
1603 const MapPolylineMaterial &o = *static_cast<const MapPolylineMaterial *>(other);
1604 if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset && o.m_lineWidth == m_lineWidth)
1605 return QSGFlatColorMaterial::compare(other);
1606 return -1;
1607}
1608
1609const QSGGeometry::AttributeSet &MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated()
1610{
1611 return MapPolylineEntry::attributes();
1612}
1613
1614MapPolylineNodeOpenGLExtruded::MapPolylineNodeOpenGLExtruded()
1615: m_geometryTriangulating(MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated(),
1616 0 /* vtx cnt */, 0 /* index cnt */, QSGGeometry::UnsignedIntType /* index type */)
1617{
1618 m_geometryTriangulating.setDrawingMode(QSGGeometry::DrawTriangles);
1619 QSGGeometryNode::setMaterial(&fill_material_);
1620 QSGGeometryNode::setGeometry(&m_geometryTriangulating);
1621}
1622
1623MapPolylineNodeOpenGLExtruded::~MapPolylineNodeOpenGLExtruded()
1624{
1625
1626}
1627
1628bool QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom,
1629 bool closed,
1630 unsigned int zoom) const
1631{
1632 // Select LOD. Generate if not present. Assign it to m_screenVertices;
1633 if (m_dataChanged) {
1634 // it means that the data really changed.
1635 // So synchronously produce LOD 1, and enqueue the requested one if != 0 or 1.
1636 // Select 0 if 0 is requested, or 1 in all other cases.
1637 selectLODOnDataChanged(zoom, leftBound: m_bboxLeftBoundWrapped.x());
1638 } else {
1639 // Data has not changed, but active LOD != requested LOD.
1640 // So, if there are no active tasks, try to change to the correct one.
1641 if (!selectLODOnLODMismatch(zoom, leftBound: m_bboxLeftBoundWrapped.x(), closed))
1642 return false;
1643 }
1644
1645 const QVector<QDeclarativeGeoMapItemUtils::vec2> &v = *m_screenVertices;
1646 if (v.size() < 2) {
1647 geom->allocate(vertexCount: 0, indexCount: 0);
1648 return true;
1649 }
1650 const int numSegments = (v.size() - 1);
1651
1652 const int numIndices = numSegments * 6; // six vertices per line segment
1653 geom->allocate(vertexCount: numIndices);
1654 MapPolylineNodeOpenGLExtruded::MapPolylineEntry *vertices =
1655 static_cast<MapPolylineNodeOpenGLExtruded::MapPolylineEntry *>(geom->vertexData());
1656
1657 for (int i = 0; i < numSegments; ++i) {
1658 MapPolylineNodeOpenGLExtruded::MapPolylineEntry e;
1659 const QDeclarativeGeoMapItemUtils::vec2 &cur = v[i];
1660 const QDeclarativeGeoMapItemUtils::vec2 &next = v[i+1];
1661 e.triangletype = 1.0;
1662 e.next = next;
1663 e.prev = cur;
1664 e.pos = cur;
1665 e.direction = 1.0;
1666 e.vertextype = -1.0;
1667 vertices[i*6] = e;
1668 e.direction = -1.0;
1669 vertices[i*6+1] = e;
1670 e.pos = next;
1671 e.vertextype = 1.0;
1672 vertices[i*6+2] = e;
1673
1674 // Second tri
1675 e.triangletype = -1.0;
1676 e.direction = -1.0;
1677 vertices[i*6+3] = e;
1678 e.direction = 1.0;
1679 vertices[i*6+4] = e;
1680 e.pos = cur;
1681 e.vertextype = -1.0;
1682 vertices[i*6+5] = e;
1683
1684 if (i != 0) {
1685 vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[i-1];
1686 } else {
1687 if (closed) {
1688 vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[numSegments - 1];
1689 } else {
1690 vertices[i*6].triangletype = vertices[i*6+1].triangletype = vertices[i*6+5].triangletype = 2.0;
1691 }
1692 }
1693 if (i != numSegments - 1) {
1694 vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[i+2];
1695 } else {
1696 if (closed) {
1697 vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[1];
1698 } else {
1699 vertices[i*6+2].triangletype = vertices[i*6+3].triangletype = vertices[i*6+4].triangletype = 3.0;
1700 }
1701 }
1702 }
1703 return true;
1704}
1705
1706void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom,
1707 int lod) const
1708{
1709 // Select LOD. Generate if not present. Assign it to m_screenVertices;
1710 Q_UNUSED(lod)
1711
1712 const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = *m_screenVertices;
1713 geom->allocate(vertexCount: vx.size());
1714
1715 QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D();
1716 for (int i = 0; i < vx.size(); ++i)
1717 pts[i].set(nx: vx[i].x, ny: vx[i].y);
1718}
1719
1720void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor,
1721 const float lineWidth,
1722 const QGeoMapPolylineGeometryOpenGL *shape,
1723 const QMatrix4x4 geoProjection,
1724 const QDoubleVector3D center,
1725 const Qt::PenCapStyle capStyle,
1726 bool closed,
1727 unsigned int zoom)
1728{
1729 // shape->size() == number of triangles
1730 if (shape->m_screenVertices->size() < 2
1731 || lineWidth < 0.5 || fillColor.alpha() == 0) { // number of points
1732 setSubtreeBlocked(true);
1733 return;
1734 } else {
1735 setSubtreeBlocked(false);
1736 }
1737
1738 QSGGeometry *fill = QSGGeometryNode::geometry();
1739 if (shape->m_dataChanged || !shape->isLODActive(lod: zoom) || !fill->vertexCount()) { // fill->vertexCount for when node gets destroyed by MapItemBase bcoz of opacity, then recreated.
1740 if (shape->allocateAndFillEntries(geom: fill, closed, zoom)) {
1741 markDirty(bits: DirtyGeometry);
1742 shape->m_dataChanged = false;
1743 }
1744 }
1745
1746 // Update this
1747// if (fillColor != fill_material_.color())
1748 {
1749 fill_material_.setWrapOffset(shape->m_wrapOffset - 1);
1750 fill_material_.setColor(fillColor);
1751 fill_material_.setGeoProjection(geoProjection);
1752 fill_material_.setCenter(center);
1753 fill_material_.setLineWidth(lineWidth);
1754 fill_material_.setMiter(capStyle != Qt::FlatCap);
1755 setMaterial(&fill_material_);
1756 markDirty(bits: DirtyMaterial);
1757 }
1758}
1759
1760MapPolylineShaderExtruded::MapPolylineShaderExtruded() : QSGMaterialShader(*new QSGMaterialShaderPrivate)
1761{
1762
1763}
1764
1765void MapPolylineShaderExtruded::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
1766{
1767 Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
1768 MapPolylineMaterialExtruded *oldMaterial = static_cast<MapPolylineMaterialExtruded *>(oldEffect);
1769 MapPolylineMaterialExtruded *newMaterial = static_cast<MapPolylineMaterialExtruded *>(newEffect);
1770
1771 const QColor &c = newMaterial->color();
1772 const QMatrix4x4 &geoProjection = newMaterial->geoProjection();
1773 const QDoubleVector3D &center = newMaterial->center();
1774
1775 QVector3D vecCenter, vecCenter_lowpart;
1776 for (int i = 0; i < 3; i++)
1777 QLocationUtils::split_double(input: center.get(i), hipart: &vecCenter[i], lopart: &vecCenter_lowpart[i]);
1778
1779 if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) {
1780 float opacity = state.opacity() * c.alphaF();
1781 QVector4D v(c.redF() * opacity,
1782 c.greenF() * opacity,
1783 c.blueF() * opacity,
1784 opacity);
1785 program()->setUniformValue(location: m_color_id, value: v);
1786 }
1787
1788 if (state.isMatrixDirty())
1789 {
1790 program()->setUniformValue(location: m_matrix_id, value: state.projectionMatrix());
1791 }
1792
1793 // ToDo: dirty-flag all this
1794 program()->setUniformValue(location: m_mapProjection_id, value: geoProjection);
1795
1796 program()->setUniformValue(location: m_center_id, value: vecCenter);
1797 program()->setUniformValue(location: m_center_lowpart_id, value: vecCenter_lowpart);
1798 program()->setUniformValue(location: m_miter_id, value: newMaterial->miter());
1799 program()->setUniformValue(location: m_lineWidth_id, value: newMaterial->lineWidth());
1800 program()->setUniformValue(location: m_wrapOffset_id, value: float(newMaterial->wrapOffset()));
1801
1802 const QRectF viewportRect = state.viewportRect();
1803 const float aspect = float(viewportRect.width() / viewportRect.height());
1804 program()->setUniformValue(location: m_aspect_id, value: aspect);
1805}
1806
1807const char * const *MapPolylineShaderExtruded::attributeNames() const
1808{
1809 return MapPolylineNodeOpenGLExtruded::MapPolylineEntry::attributeNames();
1810}
1811
1812QSGMaterialShader *MapPolylineMaterialExtruded::createShader() const
1813{
1814 return new MapPolylineShaderExtruded();
1815}
1816
1817QSGMaterialType *MapPolylineMaterialExtruded::type() const
1818{
1819 static QSGMaterialType type;
1820 return &type;
1821}
1822
1823int MapPolylineMaterialExtruded::compare(const QSGMaterial *other) const
1824{
1825 const MapPolylineMaterialExtruded &o = *static_cast<const MapPolylineMaterialExtruded *>(other);
1826 if (o.m_miter == m_miter)
1827 return MapPolylineMaterial::compare(other);
1828 return -1;
1829}
1830
1831const char *MapPolylineShaderExtruded::vertexShaderMiteredSegments() const
1832{
1833 return
1834 "attribute highp vec4 vertex;\n"
1835 "attribute highp vec4 previous;\n"
1836 "attribute highp vec4 next;\n"
1837 "attribute lowp float direction;\n"
1838 "attribute lowp float triangletype;\n"
1839 "attribute lowp float vertextype;\n" // -1.0 if it is the "left" end of the segment, 1.0 if it is the "right" end.
1840 "\n"
1841 "uniform highp mat4 qt_Matrix;\n"
1842 "uniform highp mat4 mapProjection;\n"
1843 "uniform highp vec3 center;\n"
1844 "uniform highp vec3 center_lowpart;\n"
1845 "uniform lowp float lineWidth;\n"
1846 "uniform lowp float aspect;\n"
1847 "uniform lowp int miter;\n" // currently unused
1848 "uniform lowp vec4 color;\n"
1849 "uniform lowp float wrapOffset;\n"
1850 "\n"
1851 "varying vec4 primitivecolor;\n"
1852 "\n"
1853 " \n"
1854 "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n"
1855 "void main() {\n" // ln 22
1856 " primitivecolor = color;\n"
1857 " vec2 aspectVec = vec2(aspect, 1.0);\n"
1858 " mat4 projViewModel = qt_Matrix * mapProjection;\n"
1859 " vec4 cur = wrapped(vertex) - vec4(center, 0.0);\n"
1860 " cur = cur - vec4(center_lowpart, 0.0);\n"
1861 " vec4 prev = wrapped(previous) - vec4(center, 0.0);\n"
1862 " prev = prev - vec4(center_lowpart, 0.0);\n"
1863 " vec4 nex = wrapped(next) - vec4(center, 0.0);\n"
1864 " nex = nex - vec4(center_lowpart, 0.0);\n"
1865 "\n"
1866 " vec4 centerProjected = projViewModel * vec4(center, 1.0);\n"
1867 " vec4 previousProjected = projViewModel * prev;\n"
1868 " vec4 currentProjected = projViewModel * cur;\n"
1869 " vec4 nextProjected = projViewModel * nex;\n"
1870 "\n"
1871 " //get 2D screen space with W divide and aspect correction\n"
1872 " vec2 currentScreen = (currentProjected.xy / currentProjected.w) * aspectVec;\n"
1873 " vec2 previousScreen = (previousProjected.xy / previousProjected.w) * aspectVec;\n"
1874 " vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n"
1875 " float len = (lineWidth);\n"
1876 " float orientation = direction;\n"
1877 " bool clipped = false;\n"
1878 " bool otherEndBelowFrustum = false;\n"
1879 " //starting point uses (next - current)\n"
1880 " vec2 dir = vec2(0.0);\n"
1881 " if (vertextype < 0.0) {\n"
1882 " dir = normalize(nextScreen - currentScreen);\n"
1883 " if (nextProjected.z < 0.0) dir = -dir;\n"
1884 " } else { \n"
1885 " dir = normalize(currentScreen - previousScreen);\n"
1886 " if (previousProjected.z < 0.0) dir = -dir;\n"
1887 " }\n"
1888 // first, clip current, and make sure currentProjected.z is > 0
1889 " if (currentProjected.z < 0.0) {\n"
1890 " if ((nextProjected.z > 0.0 && vertextype < 0.0) || (vertextype > 0.0 && previousProjected.z > 0.0)) {\n"
1891 " dir = -dir;\n"
1892 " clipped = true;\n"
1893 " if (vertextype < 0.0 && nextProjected.y / nextProjected.w < -1.0) otherEndBelowFrustum = true;\n"
1894 " else if (vertextype > 0.0 && previousProjected.y / previousProjected.w < -1.0) otherEndBelowFrustum = true;\n"
1895 " } else {\n"
1896 " primitivecolor = vec4(0.0,0.0,0.0,0.0);\n"
1897 " gl_Position = vec4(-10000000.0, -1000000000.0, -1000000000.0, 1);\n" // get the vertex out of the way if the segment is fully invisible
1898 " return;\n"
1899 " }\n"
1900 " } else if (triangletype < 2.0) {\n" // vertex in the view, try to miter
1901 " //get directions from (C - B) and (B - A)\n"
1902 " vec2 dirA = normalize((currentScreen - previousScreen));\n"
1903 " if (previousProjected.z < 0.0) dirA = -dirA;\n"
1904 " vec2 dirB = normalize((nextScreen - currentScreen));\n"
1905 " //now compute the miter join normal and length\n"
1906 " if (nextProjected.z < 0.0) dirB = -dirB;\n"
1907 " vec2 tangent = normalize(dirA + dirB);\n"
1908 " vec2 perp = vec2(-dirA.y, dirA.x);\n"
1909 " vec2 vmiter = vec2(-tangent.y, tangent.x);\n"
1910 " len = lineWidth / dot(vmiter, perp);\n"
1911 // The following is an attempt to have a segment-length based miter threshold.
1912 // A mediocre workaround until better mitering will be added.
1913 " float lenTreshold = clamp( min(length((currentProjected.xy - previousProjected.xy) / aspectVec),"
1914 " length((nextProjected.xy - currentProjected.xy) / aspectVec)), 3.0, 6.0 ) * 0.5;\n"
1915 " if (len < lineWidth * lenTreshold && len > -lineWidth * lenTreshold \n"
1916 " ) {\n"
1917 " dir = tangent;\n"
1918 " } else {\n"
1919 " len = lineWidth;\n"
1920 " }\n"
1921 " }\n"
1922 " vec4 offset;\n"
1923 " if (!clipped) {\n"
1924 " vec2 normal = normalize(vec2(-dir.y, dir.x));\n"
1925 " normal *= len;\n" // fracZL apparently was needed before the (-2.0 / qt_Matrix[1][1]) factor was introduced
1926 " normal /= aspectVec;\n" // straighten the normal up again
1927 " float scaleFactor = currentProjected.w / centerProjected.w;\n"
1928 " offset = vec4(normal * orientation * scaleFactor * (centerProjected.w / (-2.0 / qt_Matrix[1][1])), 0.0, 0.0);\n" // ToDo: figure out why (-2.0 / qt_Matrix[1][1]), that is empirically what works
1929 " gl_Position = currentProjected + offset;\n"
1930 " } else {\n"
1931 " if (otherEndBelowFrustum) offset = vec4((dir * 1.0) / aspectVec, 0.0, 0.0);\n" // the if is necessary otherwise it seems the direction vector still flips in some obscure cases.
1932 " else offset = vec4((dir * 500000000000.0) / aspectVec, 0.0, 0.0);\n" // Hack alert: just 1 triangle, long enough to look like a rectangle.
1933 " if (vertextype < 0.0) gl_Position = nextProjected - offset; else gl_Position = previousProjected + offset;\n"
1934 " }\n"
1935 "}\n";
1936}
1937
1938QVector<QDeclarativeGeoMapItemUtils::vec2> QGeoMapItemLODGeometry::getSimplified(
1939 QVector<QDeclarativeGeoMapItemUtils::vec2> &wrappedPath, // reference as it gets copied in the nested call
1940 double leftBoundWrapped,
1941 unsigned int zoom)
1942{
1943 // Try a simplify step
1944 QList<QDoubleVector2D> data;
1945 for (auto e: wrappedPath)
1946 data << e.toDoubleVector2D();
1947 const QList<QDoubleVector2D> simplified = QGeoSimplify::geoSimplifyZL(points: data,
1948 leftBound: leftBoundWrapped,
1949 zoomLevel: zoom);
1950
1951 data.clear();
1952 QVector<QDeclarativeGeoMapItemUtils::vec2> simple;
1953 for (auto e: simplified)
1954 simple << e;
1955 return simple;
1956}
1957
1958
1959bool QGeoMapItemLODGeometry::isLODActive(unsigned int lod) const
1960{
1961 return m_screenVertices == m_verticesLOD[zoomToLOD(zoom: lod)].data();
1962}
1963
1964class PolylineSimplifyTask : public QRunnable
1965{
1966public:
1967 PolylineSimplifyTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call
1968 const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output,
1969 double leftBound,
1970 unsigned int zoom,
1971 QSharedPointer<unsigned int> &working)
1972 : m_zoom(zoom)
1973 , m_leftBound(leftBound)
1974 , m_input(input)
1975 , m_output(output)
1976 , m_working(working)
1977 {
1978 Q_ASSERT(!input.isNull());
1979 Q_ASSERT(!output.isNull());
1980 }
1981
1982 ~PolylineSimplifyTask() override;
1983
1984 void run() override
1985 {
1986 // Skip sending notifications for now. Updated data will be picked up eventually.
1987 // ToDo: figure out how to connect a signal from here to a slot in the item.
1988 *m_working = QGeoMapPolylineGeometryOpenGL::zoomToLOD(zoom: m_zoom);
1989 const QVector<QDeclarativeGeoMapItemUtils::vec2> res =
1990 QGeoMapPolylineGeometryOpenGL::getSimplified( wrappedPath&: *m_input,
1991 leftBoundWrapped: m_leftBound,
1992 zoom: QGeoMapPolylineGeometryOpenGL::zoomForLOD(zoom: m_zoom));
1993 *m_output = res;
1994 *m_working = 0;
1995 }
1996
1997 unsigned int m_zoom;
1998 double m_leftBound;
1999 QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > m_input, m_output;
2000 QSharedPointer<unsigned int> m_working;
2001};
2002
2003void QGeoMapItemLODGeometry::enqueueSimplificationTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input,
2004 const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output,
2005 double leftBound,
2006 unsigned int zoom,
2007 QSharedPointer<unsigned int> &working)
2008{
2009 Q_ASSERT(!input.isNull());
2010 Q_ASSERT(!output.isNull());
2011 PolylineSimplifyTask *task = new PolylineSimplifyTask(input,
2012 output,
2013 leftBound,
2014 zoom,
2015 working);
2016 threadPool->start(runnable: task);
2017}
2018
2019PolylineSimplifyTask::~PolylineSimplifyTask() {}
2020
2021void QGeoMapItemLODGeometry::selectLOD(unsigned int zoom, double leftBound, bool /* closed */) // closed to tell if this is a polygon or a polyline.
2022{
2023 unsigned int requestedLod = zoomToLOD(zoom);
2024 if (!m_verticesLOD[requestedLod].isNull()) {
2025 m_screenVertices = m_verticesLOD[requestedLod].data();
2026 } else if (!m_verticesLOD.at(n: 0)->isEmpty()) {
2027 // if here, zoomToLOD != 0 and no current working task.
2028 // So select the last filled LOD != m_working (lower-bounded by 1,
2029 // guaranteed to exist), and enqueue the right one
2030 m_verticesLOD[requestedLod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2031 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2032
2033 for (unsigned int i = requestedLod - 1; i >= 1; i--) {
2034 if (*m_working != i && !m_verticesLOD[i].isNull()) {
2035 m_screenVertices = m_verticesLOD[i].data();
2036 break;
2037 } else if (i == 1) {
2038 // get 1 synchronously if not computed already
2039 m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2040 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2041 *m_verticesLOD[1] = getSimplified( wrappedPath&: *m_verticesLOD[0],
2042 leftBoundWrapped: leftBound,
2043 zoom: zoomForLOD(zoom: 0));
2044 if (requestedLod == 1)
2045 return;
2046 }
2047 }
2048
2049 enqueueSimplificationTask( input: m_verticesLOD.at(n: 0),
2050 output: m_verticesLOD[requestedLod],
2051 leftBound,
2052 zoom,
2053 working&: m_working);
2054
2055 }
2056}
2057
2058void QGeoMapItemLODGeometry::selectLODOnDataChanged(unsigned int zoom, double leftBound) const
2059{
2060 unsigned int lod = zoomToLOD(zoom);
2061 if (lod > 0) {
2062 // Generate ZL 1 as fallback for all cases != 0. Do not do if 0 is requested
2063 // (= old behavior, LOD disabled)
2064 m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2065 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2066 *m_verticesLOD[1] = getSimplified( wrappedPath&: *m_verticesLOD[0],
2067 leftBoundWrapped: leftBound,
2068 zoom: zoomForLOD(zoom: 0));
2069 }
2070 if (lod > 1) {
2071 if (!m_verticesLOD[lod])
2072 m_verticesLOD[lod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2073 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2074 enqueueSimplificationTask( input: m_verticesLOD.at(n: 0),
2075 output: m_verticesLOD[lod],
2076 leftBound,
2077 zoom,
2078 working&: m_working);
2079 }
2080 m_screenVertices = m_verticesLOD[qMin<unsigned int>(a: lod, b: 1)].data(); // return only 0,1 synchronously
2081}
2082
2083unsigned int QGeoMapItemLODGeometry::zoomToLOD(unsigned int zoom)
2084{
2085 unsigned int res;
2086 if (zoom > 20)
2087 res = 0;
2088 else
2089 res = qBound<unsigned int>(min: 3, val: zoom, max: 20) / 3; // bound LOD'ing between ZL 3 and 20. Every 3 ZoomLevels
2090 return res;
2091}
2092
2093unsigned int QGeoMapItemLODGeometry::zoomForLOD(unsigned int zoom)
2094{
2095 unsigned int res = (qBound<unsigned int>(min: 3, val: zoom, max: 20) / 3) * 3;
2096 if (zoom < 6)
2097 return res;
2098 return res + 1; // give more resolution when closing in
2099}
2100#endif // QT_CONFIG(opengl)
2101
2102QT_END_NAMESPACE
2103

source code of qtlocation/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp