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
70 QT_BEGIN_NAMESPACE
71
72 struct ThreadPool // to have a thread pool with max 1 thread for geometry processing
73 {
ThreadPoolThreadPool74 ThreadPool ()
75 {
76 m_threadPool.setMaxThreadCount(1);
77 }
78
startThreadPool79 void start(QRunnable *runnable, int priority = 0)
80 {
81 m_threadPool.start(runnable, priority);
82 }
83
84 QThreadPool m_threadPool;
85 };
86
87 Q_GLOBAL_STATIC(ThreadPool, threadPool)
88
89
90 static const double kClipperScaleFactor = 281474976710656.0; // 48 bits of precision
91
toIntPoint(const double x,const double y)92 static inline IntPoint toIntPoint(const double x, const double y)
93 {
94 return IntPoint(cInt(x * kClipperScaleFactor), cInt(y * kClipperScaleFactor));
95 }
96
toIntPoint(const QDoubleVector2D & p)97 static IntPoint toIntPoint(const QDoubleVector2D &p)
98 {
99 return toIntPoint(p.x(), p.y());
100 }
101
get_line_intersection(const double p0_x,const double p0_y,const double p1_x,const double p1_y,const double p2_x,const double p2_y,const double p3_x,const double p3_y,double * i_x,double * i_y,double * i_t)102 static 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
144 enum SegmentType {
145 NoIntersection,
146 OneIntersection,
147 TwoIntersections
148 };
149
clipLine(const QList<QDoubleVector2D> & l,const QList<QDoubleVector2D> & poly)150 static 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({ { poly.at(i-1).x(), poly.at(i-1).y(), poly.at(i).x(), poly.at(i).y() } });
162 edges.push_back({ { poly.at(poly.size()-1).x(), poly.at(poly.size()-1).y(), poly.at(0).x(), poly.at(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(toIntPoint(v));
170
171 // Step 2: check each segment against each edge
172 QList<QDoubleVector2D> subLine;
173 std::array<double, 4> intersections = { { 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(toIntPoint(l.at(i).x(), l.at(i).y()), clip);
182 const int secondContained = c2t::clip2tri::pointInPolygon(toIntPoint(l.at(i+1).x(), l.at(i+1).y()), 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(l.at(i)); // the initial element has to be pushed now.
187 subLine.push_back(l.at(i+1));
188 continue;
189 }
190
191 for (unsigned int j = 0; j < edges.size(); ++j) {
192 const bool intersects = get_line_intersection(l.at(i).x(),
193 l.at(i).y(),
194 l.at(i+1).x(),
195 l.at(i+1).y(),
196 edges.at(j).at(0),
197 edges.at(j).at(1),
198 edges.at(j).at(2),
199 edges.at(j).at(3),
200 &i_x,
201 &i_y,
202 &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(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(QDoubleVector2D(intersections[0], intersections[1]));
240 subLine.push_back(l.at(i+1));
241 } else if (firstContained > 0 && secondContained <= 0) { // subLine MUST NOT be empty
242 if (!subLine.size())
243 subLine.push_back(l.at(i));
244 subLine.push_back(QDoubleVector2D(intersections[0], intersections[1]));
245 res.push_back(subLine);
246 subLine.clear();
247 } else {
248 if (subLine.size())
249 res.push_back(subLine);
250 subLine.clear();
251 }
252 } else { // Two
253 // restart strip
254 subLine.clear();
255 subLine.push_back(QDoubleVector2D(intersections[0], intersections[1]));
256 subLine.push_back(QDoubleVector2D(intersections[2], intersections[3]));
257 res.push_back(subLine);
258 subLine.clear();
259 }
260 }
261
262 if (subLine.size())
263 res.push_back(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
QDeclarativeMapLineProperties(QObject * parent)338 QDeclarativeMapLineProperties::QDeclarativeMapLineProperties(QObject *parent) :
339 QObject(parent),
340 width_(1.0),
341 color_(Qt::black)
342 {
343 }
344
345 /*!
346 \internal
347 */
color() const348 QColor QDeclarativeMapLineProperties::color() const
349 {
350 return color_;
351 }
352
353 /*!
354 \internal
355 */
setColor(const QColor & color)356 void QDeclarativeMapLineProperties::setColor(const QColor &color)
357 {
358 if (color_ == color)
359 return;
360
361 color_ = color;
362 emit colorChanged(color_);
363 }
364
365 /*!
366 \internal
367 */
width() const368 qreal QDeclarativeMapLineProperties::width() const
369 {
370 return width_;
371 }
372
373 /*!
374 \internal
375 */
setWidth(qreal width)376 void QDeclarativeMapLineProperties::setWidth(qreal width)
377 {
378 if (width_ == width)
379 return;
380
381 width_ = width;
382 emit widthChanged(width_);
383 }
384
QGeoMapPolylineGeometry()385 QGeoMapPolylineGeometry::QGeoMapPolylineGeometry()
386 {
387 }
388
clipPath(const QGeoMap & map,const QList<QDoubleVector2D> & path,QDoubleVector2D & leftBoundWrapped)389 QList<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(p.geoToMapProjection(geoLeftBound_));
405 if (preserveGeometry_)
406 unwrapBelowX = leftBoundWrapped.x();
407
408 QList<QDoubleVector2D> wrappedPath;
409 wrappedPath.reserve(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(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(wrappedProjection.x()) || !qIsFinite(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(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(wrappedPath, 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(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(wrappedLeftBound.x(), lb.x()));
464 leftBoundWrapped = lb;
465 } else {
466 clippedPaths.append(wrappedPath);
467 }
468
469 #ifdef QT_LOCATION_DEBUG
470 m_clippedPaths = clippedPaths;
471 #endif
472
473 return clippedPaths;
474 }
475
pathToScreen(const QGeoMap & map,const QList<QList<QDoubleVector2D>> & clippedPaths,const QDoubleVector2D & leftBoundWrapped)476 void 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(p.unwrapMapProjection(leftBoundWrapped));
487 QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(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(path.at(i));
492 point = point - origin; // (0,0) if point == geoLeftBound_
493
494 minX = qMin(point.x(), minX);
495 minY = qMin(point.y(), minY);
496 maxX = qMax(point.x(), maxX);
497 maxY = qMax(point.y(), 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 */
updateSourcePoints(const QGeoMap & map,const QList<QDoubleVector2D> & path,const QGeoCoordinate geoLeftBound)520 void 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(path.size() * 2);
532 srcPointTypes_.clear();
533 srcPointTypes_.reserve(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
552 enum ClipPointType {
553 InsidePoint = 0x00,
554 LeftPoint = 0x01,
555 RightPoint = 0x02,
556 BottomPoint = 0x04,
557 TopPoint = 0x08
558 };
559
clipPointType(qreal x,qreal y,const QRectF & rect)560 static 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
clipSegmentToRect(qreal x0,qreal y0,qreal x1,qreal y1,const QRectF & clipRect,QVector<qreal> & outPoints,QVector<QPainterPath::ElementType> & outTypes)574 static 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(x0, y0, clipRect);
580 int type1 = clipPointType(x1, y1, 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(x0, y0, clipRect);
612 } else {
613 x1 = x;
614 y1 = y;
615 type1 = clipPointType(x1, y1, clipRect);
616 }
617 }
618 }
619
620 if (accept) {
621 if (outPoints.size() >= 2) {
622 qreal lastX, lastY;
623 lastY = outPoints.at(outPoints.size() - 1);
624 lastX = outPoints.at(outPoints.size() - 2);
625
626 if (!qFuzzyCompare(lastY, y0) || !qFuzzyCompare(lastX, 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
clipPathToRect(const QVector<qreal> & points,const QVector<QPainterPath::ElementType> & types,const QRectF & clipRect,QVector<qreal> & outPoints,QVector<QPainterPath::ElementType> & outTypes)640 static 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(points.size());
648 outTypes.clear();
649 outTypes.reserve(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(lastX, lastY, x, 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 */
updateScreenPoints(const QGeoMap & map,qreal strokeWidth,bool adjustTranslation)669 void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
670 qreal strokeWidth,
671 bool adjustTranslation)
672 {
673 if (!screenDirty_)
674 return;
675
676 QPointF origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false).toPointF();
677
678 if (!qIsFinite(origin.x()) || !qIsFinite(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(-strokeWidth, -strokeWidth, strokeWidth * 2, strokeWidth * 2);
687 viewport.translate(-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(srcPoints_, srcPointTypes_, viewport, points, 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(vp, QPen(QBrush(Qt::black), strokeWidth), QRectF(), 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(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(pt.x()) || !qIsFinite(pt.y()))
726 break;
727
728 if (!bb.contains(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( -1 * sourceBounds_.topLeft() + strokeOffset);
746 }
747
clearSource()748 void QGeoMapPolylineGeometry::clearSource()
749 {
750 srcPoints_.clear();
751 srcPointTypes_.clear();
752 }
753
contains(const QPointF & point) const754 bool 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(point,Qt::OddEvenFill))
764 return true;
765 tri.remove(0);
766 }
767 }
768
769 return false;
770 }
771
updateSourcePoints(const QGeoMap & map,const QGeoPolygon & poly)772 void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly)
773 {
774 if (!sourceDirty_)
775 return;
776 QGeoPath p(poly.path());
777 if (poly.path().size() && poly.path().last() != poly.path().first())
778 p.addCoordinate(poly.path().first());
779 updateSourcePoints(map, p);
780 }
781
updateSourcePoints(const QGeoMap & map,const QGeoPath & poly)782 void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPath &poly)
783 {
784 if (!sourceDirty_)
785 return;
786 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
787
788 // build the actual path
789 // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
790
791
792 QDoubleVector2D leftBoundWrapped;
793 // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
794 QList<QDoubleVector2D> wrappedPath;
795 QDeclarativeGeoMapItemUtils::wrapPath(poly.path(), geoLeftBound_, p,
796 wrappedPath, &leftBoundWrapped);
797
798 const QGeoRectangle &boundingRectangle = poly.boundingGeoRectangle();
799 updateSourcePoints(p, wrappedPath, boundingRectangle);
800 }
801
updateSourcePoints(const QGeoProjectionWebMercator & p,const QList<QDoubleVector2D> & wrappedPath,const QGeoRectangle & boundingRectangle)802 void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoProjectionWebMercator &p,
803 const QList<QDoubleVector2D> &wrappedPath,
804 const QGeoRectangle &boundingRectangle) {
805 if (!sourceDirty_)
806 return;
807 // 1.1) do the same for the bbox
808 // Beware: vertical lines (or horizontal lines) might have an "empty" bbox. Check for that
809
810 QGeoCoordinate topLeft = boundingRectangle.topLeft();
811 QGeoCoordinate bottomRight = boundingRectangle.bottomRight();
812 const qreal epsilon = 0.000001;
813 if (qFuzzyCompare(topLeft.latitude(), bottomRight.latitude())) {
814 topLeft.setLatitude(qBound(-90.0, topLeft.latitude() + epsilon ,90.0));
815 bottomRight.setLatitude(qBound(-90.0, bottomRight.latitude() - epsilon ,90.0));
816 }
817 if (qFuzzyCompare(topLeft.longitude(), bottomRight.longitude())) {
818 topLeft.setLongitude(QLocationUtils::wrapLong(topLeft.longitude() - epsilon));
819 bottomRight.setLongitude(QLocationUtils::wrapLong(bottomRight.longitude() + epsilon));
820 }
821 QGeoPolygon bbox(QGeoRectangle(topLeft, bottomRight));
822 QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1;
823 QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p,
824 wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped);
825
826 // New pointers, some old LOD task might still be running and operating on the old pointers.
827 resetLOD();
828
829 for (const auto &v: qAsConst(wrappedPath)) m_screenVertices->append(v);
830
831 m_wrappedPolygons.resize(3);
832 m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1;
833 m_wrappedPolygons[1].wrappedBboxes = wrappedBbox;
834 m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1;
835 srcOrigin_ = geoLeftBound_;
836 }
837
updateSourcePoints(const QGeoMap & map,const QGeoRectangle & rect)838 void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect)
839 {
840 const QGeoPath path(QDeclarativeRectangleMapItemPrivateCPU::perimeter(rect));
841 updateSourcePoints(map, path);
842 }
843
updateSourcePoints(const QGeoMap & map,const QGeoCircle & circle)844 void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoCircle &circle)
845 {
846 if (!sourceDirty_)
847 return;
848 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
849
850 QDoubleVector2D leftBoundWrapped;
851 // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
852 QList<QGeoCoordinate> path;
853 QGeoCoordinate leftBound;
854 QList<QDoubleVector2D> wrappedPath;
855 QDeclarativeCircleMapItemPrivateCPU::calculatePeripheralPoints(path, circle.center(), circle.radius(), QDeclarativeCircleMapItemPrivateCPU::CircleSamples, leftBound);
856 path << path.first();
857 geoLeftBound_ = leftBound;
858 QDeclarativeGeoMapItemUtils::wrapPath(path, leftBound, p, wrappedPath, &leftBoundWrapped);
859 const QGeoRectangle &boundingRectangle = circle.boundingGeoRectangle();
860 updateSourcePoints(p, wrappedPath, boundingRectangle);
861 }
862
updateScreenPoints(const QGeoMap & map,qreal strokeWidth,bool)863 void QGeoMapPolylineGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth, bool /*adjustTranslation*/)
864 {
865 if (map.viewportWidth() == 0 || map.viewportHeight() == 0) {
866 clear();
867 return;
868 }
869
870 // 1) identify which set to use: std, +1 or -1
871 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
872 const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(srcOrigin_);
873 m_wrapOffset = p.projectionWrapFactor(leftBoundMercator) + 1; // +1 to get the offset into QLists
874
875 if (sourceDirty_) {
876 // 1.1) select geometry set
877 // This could theoretically be skipped for those polylines whose bbox is not even projectable.
878 // However, such optimization could only be introduced if not calculating bboxes lazily.
879 // Hence not doing it.
880 // if (m_screenVertices.size() > 1)
881 m_dataChanged = true;
882 }
883
884 updateQuickGeometry(p, strokeWidth);
885 }
886
updateQuickGeometry(const QGeoProjectionWebMercator & p,qreal strokeWidth)887 void QGeoMapPolylineGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth)
888 {
889 // 2) clip bbox
890 // BBox handling -- this is related to the bounding box geometry
891 // that has to inevitably follow the old projection codepath
892 // As it needs to provide projected coordinates for QtQuick interaction.
893 // This could be futher optimized to be updated in a lazy fashion.
894 const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(m_wrapOffset).wrappedBboxes;
895 QList<QList<QDoubleVector2D> > clippedBbox;
896 QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped;
897 bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1));
898 QDeclarativeGeoMapItemUtils::clipPolygon(wrappedBbox, p, clippedBbox, &bboxLeftBoundWrapped, false);
899
900 // 3) project bbox
901 QPainterPath ppi;
902
903 if ( !clippedBbox.size() ||
904 clippedBbox.first().size() < 3) {
905 sourceBounds_ = screenBounds_ = QRectF();
906 firstPointOffset_ = QPointF();
907 screenOutline_ = ppi;
908 return;
909 }
910
911 QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox.first(), p, ppi); // Using first because a clipped box should always result in one polygon
912 const QRectF brect = ppi.boundingRect();
913 firstPointOffset_ = QPointF(brect.topLeft());
914 sourceBounds_ = brect;
915 screenOutline_ = ppi;
916
917 // 4) Set Screen bbox
918 screenBounds_ = brect;
919 sourceBounds_.setX(0);
920 sourceBounds_.setY(0);
921 sourceBounds_.setWidth(brect.width() + strokeWidth);
922 sourceBounds_.setHeight(brect.height() + strokeWidth);
923 }
924
925 /*
926 * QDeclarativePolygonMapItem Private Implementations
927 */
928
~QDeclarativePolylineMapItemPrivate()929 QDeclarativePolylineMapItemPrivate::~QDeclarativePolylineMapItemPrivate() {}
930
931
~QDeclarativePolylineMapItemPrivateCPU()932 QDeclarativePolylineMapItemPrivateCPU::~QDeclarativePolylineMapItemPrivateCPU() {}
933
~QDeclarativePolylineMapItemPrivateOpenGLLineStrip()934 QDeclarativePolylineMapItemPrivateOpenGLLineStrip::~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() {}
935
~QDeclarativePolylineMapItemPrivateOpenGLExtruded()936 QDeclarativePolylineMapItemPrivateOpenGLExtruded::~QDeclarativePolylineMapItemPrivateOpenGLExtruded() {}
937
938 /*
939 * QDeclarativePolygonMapItem Implementation
940 */
941
942 struct PolylineBackendSelector
943 {
PolylineBackendSelectorPolylineBackendSelector944 PolylineBackendSelector()
945 {
946 backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolylineMapItem::OpenGLExtruded : QDeclarativePolylineMapItem::Software;
947 }
948 QDeclarativePolylineMapItem::Backend backend = QDeclarativePolylineMapItem::Software;
949 };
950
Q_GLOBAL_STATIC(PolylineBackendSelector,mapPolylineBackendSelector)951 Q_GLOBAL_STATIC(PolylineBackendSelector, mapPolylineBackendSelector)
952
953 QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent)
954 : QDeclarativeGeoMapItemBase(parent),
955 m_line(this),
956 m_dirtyMaterial(true),
957 m_updatingGeometry(false),
958 m_d(new QDeclarativePolylineMapItemPrivateCPU(*this))
959 {
960 m_itemType = QGeoMap::MapPolyline;
961 m_geopath = QGeoPathEager();
962 setFlag(ItemHasContents, true);
963 QObject::connect(&m_line, SIGNAL(colorChanged(QColor)),
964 this, SLOT(updateAfterLinePropertiesChanged()));
965 QObject::connect(&m_line, SIGNAL(widthChanged(qreal)),
966 this, SLOT(updateAfterLinePropertiesChanged()));
967 setBackend(mapPolylineBackendSelector->backend);
968 }
969
~QDeclarativePolylineMapItem()970 QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem()
971 {
972 }
973
974 /*!
975 \internal
976 */
updateAfterLinePropertiesChanged()977 void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged()
978 {
979 m_d->onLinePropertiesChanged();
980 }
981
982 /*!
983 \internal
984 */
setMap(QDeclarativeGeoMap * quickMap,QGeoMap * map)985 void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
986 {
987 QDeclarativeGeoMapItemBase::setMap(quickMap,map);
988 if (map)
989 m_d->onMapSet();
990 }
991
992 /*!
993 \qmlproperty list<coordinate> MapPolyline::path
994
995 This property holds the ordered list of coordinates which
996 define the polyline.
997 */
998
path() const999 QJSValue QDeclarativePolylineMapItem::path() const
1000 {
1001 return fromList(this, m_geopath.path());
1002 }
1003
setPath(const QJSValue & value)1004 void QDeclarativePolylineMapItem::setPath(const QJSValue &value)
1005 {
1006 if (!value.isArray())
1007 return;
1008
1009 setPathFromGeoList(toList(this, value));
1010 }
1011
1012 /*!
1013 \qmlmethod void MapPolyline::setPath(geopath path)
1014
1015 Sets the \a path using a geopath type.
1016
1017 \since 5.10
1018
1019 \sa path
1020 */
setPath(const QGeoPath & path)1021 void QDeclarativePolylineMapItem::setPath(const QGeoPath &path)
1022 {
1023 if (m_geopath.path() == path.path())
1024 return;
1025
1026 m_geopath = QGeoPathEager(path);
1027 m_d->onGeoGeometryChanged();
1028 emit pathChanged();
1029 }
1030
1031 /*!
1032 \internal
1033 */
setPathFromGeoList(const QList<QGeoCoordinate> & path)1034 void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> &path)
1035 {
1036 if (m_geopath.path() == path)
1037 return;
1038
1039 m_geopath.setPath(path);
1040
1041 m_d->onGeoGeometryChanged();
1042 emit pathChanged();
1043 }
1044
1045 /*!
1046 \qmlmethod int MapPolyline::pathLength()
1047
1048 Returns the number of coordinates of the polyline.
1049
1050 \since QtLocation 5.6
1051
1052 \sa path
1053 */
pathLength() const1054 int QDeclarativePolylineMapItem::pathLength() const
1055 {
1056 return m_geopath.path().length();
1057 }
1058
1059 /*!
1060 \qmlmethod void MapPolyline::addCoordinate(coordinate)
1061
1062 Adds the specified \a coordinate to the end of the path.
1063
1064 \sa insertCoordinate, removeCoordinate, path
1065 */
addCoordinate(const QGeoCoordinate & coordinate)1066 void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate)
1067 {
1068 if (!coordinate.isValid())
1069 return;
1070
1071 m_geopath.addCoordinate(coordinate);
1072
1073 m_d->onGeoGeometryUpdated();
1074 emit pathChanged();
1075 }
1076
1077 /*!
1078 \qmlmethod void MapPolyline::insertCoordinate(index, coordinate)
1079
1080 Inserts a \a coordinate to the path at the given \a index.
1081
1082 \since QtLocation 5.6
1083
1084 \sa addCoordinate, removeCoordinate, path
1085 */
insertCoordinate(int index,const QGeoCoordinate & coordinate)1086 void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate)
1087 {
1088 if (index < 0 || index > m_geopath.path().length())
1089 return;
1090
1091 m_geopath.insertCoordinate(index, coordinate);
1092
1093 m_d->onGeoGeometryChanged();
1094 emit pathChanged();
1095 }
1096
1097 /*!
1098 \qmlmethod void MapPolyline::replaceCoordinate(index, coordinate)
1099
1100 Replaces the coordinate in the current path at the given \a index
1101 with the new \a coordinate.
1102
1103 \since QtLocation 5.6
1104
1105 \sa addCoordinate, insertCoordinate, removeCoordinate, path
1106 */
replaceCoordinate(int index,const QGeoCoordinate & coordinate)1107 void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate)
1108 {
1109 if (index < 0 || index >= m_geopath.path().length())
1110 return;
1111
1112 m_geopath.replaceCoordinate(index, coordinate);
1113
1114 m_d->onGeoGeometryChanged();
1115 emit pathChanged();
1116 }
1117
1118 /*!
1119 \qmlmethod coordinate MapPolyline::coordinateAt(index)
1120
1121 Gets the coordinate of the polyline at the given \a index.
1122 If the index is outside the path's bounds then an invalid
1123 coordinate is returned.
1124
1125 \since QtLocation 5.6
1126 */
coordinateAt(int index) const1127 QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const
1128 {
1129 if (index < 0 || index >= m_geopath.path().length())
1130 return QGeoCoordinate();
1131
1132 return m_geopath.coordinateAt(index);
1133 }
1134
1135 /*!
1136 \qmlmethod coordinate MapPolyline::containsCoordinate(coordinate)
1137
1138 Returns true if the given \a coordinate is part of the path.
1139
1140 \since QtLocation 5.6
1141 */
containsCoordinate(const QGeoCoordinate & coordinate)1142 bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate)
1143 {
1144 return m_geopath.containsCoordinate(coordinate);
1145 }
1146
1147 /*!
1148 \qmlmethod void MapPolyline::removeCoordinate(coordinate)
1149
1150 Removes \a coordinate from the path. If there are multiple instances of the
1151 same coordinate, the one added last is removed.
1152
1153 If \a coordinate is not in the path this method does nothing.
1154
1155 \sa addCoordinate, insertCoordinate, path
1156 */
removeCoordinate(const QGeoCoordinate & coordinate)1157 void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate)
1158 {
1159 int length = m_geopath.path().length();
1160 m_geopath.removeCoordinate(coordinate);
1161 if (m_geopath.path().length() == length)
1162 return;
1163
1164 m_d->onGeoGeometryChanged();
1165 emit pathChanged();
1166 }
1167
1168 /*!
1169 \qmlmethod void MapPolyline::removeCoordinate(index)
1170
1171 Removes a coordinate from the path at the given \a index.
1172
1173 If \a index is invalid then this method does nothing.
1174
1175 \since QtLocation 5.6
1176
1177 \sa addCoordinate, insertCoordinate, path
1178 */
removeCoordinate(int index)1179 void QDeclarativePolylineMapItem::removeCoordinate(int index)
1180 {
1181 if (index < 0 || index >= m_geopath.path().length())
1182 return;
1183
1184 m_geopath.removeCoordinate(index);
1185
1186 m_d->onGeoGeometryChanged();
1187 emit pathChanged();
1188 }
1189
1190 /*!
1191 \qmlpropertygroup Location::MapPolyline::line
1192 \qmlproperty int MapPolyline::line.width
1193 \qmlproperty color MapPolyline::line.color
1194
1195 This property is part of the line property group. The line
1196 property group holds the width and color used to draw the line.
1197
1198 The width is in pixels and is independent of the zoom level of the map.
1199 The default values correspond to a black border with a width of 1 pixel.
1200
1201 For no line, use a width of 0 or a transparent color.
1202 */
1203
line()1204 QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line()
1205 {
1206 return &m_line;
1207 }
1208
1209 /*!
1210 \qmlproperty MapPolyline.Backend QtLocation::MapPolyline::backend
1211
1212 This property holds which backend is in use to render the map item.
1213 Valid values are \b MapPolyline.Software and \b{MapPolyline.OpenGLLineStrip}
1214 and \b{MapPolyline.OpenGLExtruded}.
1215 The default value is \b{MapPolyline.Software}.
1216
1217 \note \b{The release of this API with Qt 5.15 is a Technology Preview}.
1218 Ideally, as the OpenGL backends for map items mature, there will be
1219 no more need to also offer the legacy software-projection backend.
1220 So this property will likely disappear at some later point.
1221 To select OpenGL-accelerated item backends without using this property,
1222 it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS
1223 to \b{1}.
1224 Also note that all current OpenGL backends won't work as expected when enabling
1225 layers on the individual item, or when running on OpenGL core profiles greater than 2.x.
1226
1227 \since 5.15
1228 */
backend() const1229 QDeclarativePolylineMapItem::Backend QDeclarativePolylineMapItem::backend() const
1230 {
1231 return m_backend;
1232 }
1233
setBackend(QDeclarativePolylineMapItem::Backend b)1234 void QDeclarativePolylineMapItem::setBackend(QDeclarativePolylineMapItem::Backend b)
1235 {
1236 if (b == m_backend)
1237 return;
1238 m_backend = b;
1239 QScopedPointer<QDeclarativePolylineMapItemPrivate> d((m_backend == Software)
1240 ? static_cast<QDeclarativePolylineMapItemPrivate *>(new QDeclarativePolylineMapItemPrivateCPU(*this))
1241 : ((m_backend == OpenGLExtruded)
1242 ? static_cast<QDeclarativePolylineMapItemPrivate * >(new QDeclarativePolylineMapItemPrivateOpenGLExtruded(*this))
1243 : static_cast<QDeclarativePolylineMapItemPrivate * >(new QDeclarativePolylineMapItemPrivateOpenGLLineStrip(*this))));
1244 m_d.swap(d);
1245 m_d->onGeoGeometryChanged();
1246 emit backendChanged();
1247 }
1248
1249 /*!
1250 \internal
1251 */
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)1252 void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
1253 {
1254 if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopath.isValid() || m_updatingGeometry) {
1255 QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry);
1256 return;
1257 }
1258 // TODO: change the algorithm to preserve the distances and size!
1259 QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(newGeometry.center()), false);
1260 QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(oldGeometry.center()), false);
1261 if (!newCenter.isValid() || !oldCenter.isValid())
1262 return;
1263 double offsetLongi = newCenter.longitude() - oldCenter.longitude();
1264 double offsetLati = newCenter.latitude() - oldCenter.latitude();
1265 if (offsetLati == 0.0 && offsetLongi == 0.0)
1266 return;
1267
1268 m_geopath.translate(offsetLati, offsetLongi);
1269 m_d->onGeoGeometryChanged();
1270 emit pathChanged();
1271
1272 // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested
1273 // call to this function.
1274 }
1275
1276 /*!
1277 \internal
1278 */
afterViewportChanged(const QGeoMapViewportChangeEvent & event)1279 void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
1280 {
1281 if (event.mapSize.isEmpty())
1282 return;
1283
1284 m_d->afterViewportChanged();
1285 }
1286
1287 /*!
1288 \internal
1289 */
updatePolish()1290 void QDeclarativePolylineMapItem::updatePolish()
1291 {
1292 if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
1293 return;
1294 m_d->updatePolish();
1295 }
1296
updateLineStyleParameter(QGeoMapParameter * p,const char * propertyName,bool update)1297 void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p,
1298 const char *propertyName,
1299 bool update)
1300 {
1301 static const QByteArrayList acceptedParameterTypes = QByteArrayList()
1302 << QByteArrayLiteral("lineCap")
1303 << QByteArrayLiteral("pen");
1304 switch (acceptedParameterTypes.indexOf(QByteArray(propertyName))) {
1305 case -1:
1306 qWarning() << "Invalid property " << QLatin1String(propertyName) << " for parameter lineStyle";
1307 break;
1308 case 0: // lineCap
1309 {
1310 const QVariant lineCap = p->property("lineCap");
1311 m_d->m_penCapStyle = lineCap.value<Qt::PenCapStyle>(); // if invalid, will return 0 == FlatCap
1312 if (update)
1313 markSourceDirtyAndUpdate();
1314 break;
1315 }
1316 case 1: // penStyle
1317 {
1318 const QVariant penStyle = p->property("pen");
1319 m_d->m_penStyle = penStyle.value<Qt::PenStyle>();
1320 if (m_d->m_penStyle == Qt::NoPen)
1321 m_d->m_penStyle = Qt::SolidLine;
1322 if (update)
1323 markSourceDirtyAndUpdate();
1324 break;
1325 }
1326 }
1327 }
1328
updateLineStyleParameter(QGeoMapParameter * p,const char * propertyName)1329 void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName)
1330 {
1331 updateLineStyleParameter(p, propertyName, true);
1332 }
1333
componentComplete()1334 void QDeclarativePolylineMapItem::componentComplete()
1335 {
1336 QQuickItem::componentComplete();
1337 // Set up Dynamic Parameters
1338 QList<QGeoMapParameter *> dynamicParameters = quickChildren<QGeoMapParameter>();
1339 for (QGeoMapParameter *p : qAsConst(dynamicParameters)) {
1340 if (p->type() == QLatin1String("lineStyle")) {
1341 updateLineStyleParameter(p, "lineCap", false);
1342 updateLineStyleParameter(p, "pen", false);
1343 connect(p, &QGeoMapParameter::propertyUpdated,
1344 this, static_cast<void (QDeclarativePolylineMapItem::*)(QGeoMapParameter *, const char *)>(&QDeclarativePolylineMapItem::updateLineStyleParameter));
1345 markSourceDirtyAndUpdate();
1346 }
1347 }
1348 }
1349
markSourceDirtyAndUpdate()1350 void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate()
1351 {
1352 m_d->markSourceDirtyAndUpdate();
1353 }
1354
1355 /*!
1356 \internal
1357 */
updateMapItemPaintNode(QSGNode * oldNode,UpdatePaintNodeData * data)1358 QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
1359 {
1360 return m_d->updateMapItemPaintNode(oldNode, data);
1361 }
1362
contains(const QPointF & point) const1363 bool QDeclarativePolylineMapItem::contains(const QPointF &point) const
1364 {
1365 return m_d->contains(point);
1366 }
1367
geoShape() const1368 const QGeoShape &QDeclarativePolylineMapItem::geoShape() const
1369 {
1370 return m_geopath;
1371 }
1372
setGeoShape(const QGeoShape & shape)1373 void QDeclarativePolylineMapItem::setGeoShape(const QGeoShape &shape)
1374 {
1375 const QGeoPath geopath(shape); // if shape isn't a path, path will be created as a default-constructed path
1376 setPath(geopath);
1377 }
1378
1379 //////////////////////////////////////////////////////////////////////
1380
1381 /*!
1382 \internal
1383 */
VisibleNode()1384 VisibleNode::VisibleNode() : m_blocked{true}, m_visible{true}
1385 {
1386
1387 }
1388
~VisibleNode()1389 VisibleNode::~VisibleNode()
1390 {
1391
1392 }
1393
1394 /*!
1395 \internal
1396 */
subtreeBlocked() const1397 bool VisibleNode::subtreeBlocked() const
1398 {
1399 return m_blocked || !m_visible;
1400 }
1401
1402 /*!
1403 \internal
1404 */
setSubtreeBlocked(bool blocked)1405 void VisibleNode::setSubtreeBlocked(bool blocked)
1406 {
1407 m_blocked = blocked;
1408 }
1409
visible() const1410 bool VisibleNode::visible() const
1411 {
1412 return m_visible;
1413 }
1414
1415 /*!
1416 \internal
1417 */
setVisible(bool visible)1418 void VisibleNode::setVisible(bool visible)
1419 {
1420 m_visible = visible;
1421 }
1422
1423 /*!
1424 \internal
1425 */
~MapItemGeometryNode()1426 MapItemGeometryNode::~MapItemGeometryNode()
1427 {
1428
1429 }
1430
isSubtreeBlocked() const1431 bool MapItemGeometryNode::isSubtreeBlocked() const
1432 {
1433 return subtreeBlocked();
1434 }
1435
1436
1437 /*!
1438 \internal
1439 */
MapPolylineNode()1440 MapPolylineNode::MapPolylineNode() :
1441 geometry_(QSGGeometry::defaultAttributes_Point2D(),0)
1442 {
1443 geometry_.setDrawingMode(QSGGeometry::DrawTriangleStrip);
1444 QSGGeometryNode::setMaterial(&fill_material_);
1445 QSGGeometryNode::setGeometry(&geometry_);
1446 }
1447
1448
1449 /*!
1450 \internal
1451 */
~MapPolylineNode()1452 MapPolylineNode::~MapPolylineNode()
1453 {
1454 }
1455
1456 /*!
1457 \internal
1458 */
update(const QColor & fillColor,const QGeoMapItemGeometry * shape)1459 void MapPolylineNode::update(const QColor &fillColor,
1460 const QGeoMapItemGeometry *shape)
1461 {
1462 if (shape->size() == 0) {
1463 setSubtreeBlocked(true);
1464 return;
1465 } else {
1466 setSubtreeBlocked(false);
1467 }
1468
1469 QSGGeometry *fill = QSGGeometryNode::geometry();
1470 shape->allocateAndFill(fill);
1471 markDirty(DirtyGeometry);
1472
1473 if (fillColor != fill_material_.color()) {
1474 fill_material_.setColor(fillColor);
1475 setMaterial(&fill_material_);
1476 markDirty(DirtyMaterial);
1477 }
1478 }
1479
MapPolylineNodeOpenGLLineStrip()1480 MapPolylineNodeOpenGLLineStrip::MapPolylineNodeOpenGLLineStrip()
1481 : geometry_(QSGGeometry::defaultAttributes_Point2D(), 0)
1482 {
1483 geometry_.setDrawingMode(QSGGeometry::DrawLineStrip);
1484 QSGGeometryNode::setMaterial(&fill_material_);
1485 QSGGeometryNode::setGeometry(&geometry_);
1486 }
1487
~MapPolylineNodeOpenGLLineStrip()1488 MapPolylineNodeOpenGLLineStrip::~MapPolylineNodeOpenGLLineStrip()
1489 {
1490
1491 }
1492
update(const QColor & fillColor,const qreal lineWidth,const QGeoMapPolylineGeometryOpenGL * shape,const QMatrix4x4 & geoProjection,const QDoubleVector3D & center,const Qt::PenCapStyle)1493 void MapPolylineNodeOpenGLLineStrip::update(const QColor &fillColor,
1494 const qreal lineWidth,
1495 const QGeoMapPolylineGeometryOpenGL *shape,
1496 const QMatrix4x4 &geoProjection,
1497 const QDoubleVector3D ¢er,
1498 const Qt::PenCapStyle /*capStyle*/)
1499 {
1500 if (shape->m_screenVertices->size() < 2) {
1501 setSubtreeBlocked(true);
1502 return;
1503 } else {
1504 setSubtreeBlocked(false);
1505 }
1506
1507 QSGGeometry *fill = QSGGeometryNode::geometry();
1508 if (shape->m_dataChanged) {
1509 shape->allocateAndFillLineStrip(fill);
1510 markDirty(DirtyGeometry);
1511 shape->m_dataChanged = false;
1512 }
1513 fill->setLineWidth(lineWidth);
1514 fill_material_.setLineWidth(lineWidth); // to make the material not compare equal if linewidth changes
1515
1516 // if (fillColor != fill_material_.color())
1517 {
1518 fill_material_.setWrapOffset(shape->m_wrapOffset - 1);
1519 fill_material_.setColor(fillColor);
1520 fill_material_.setGeoProjection(geoProjection);
1521 fill_material_.setCenter(center);
1522 setMaterial(&fill_material_);
1523 markDirty(DirtyMaterial);
1524 }
1525 }
1526
MapPolylineShaderLineStrip()1527 MapPolylineShaderLineStrip::MapPolylineShaderLineStrip() : QSGMaterialShader(*new QSGMaterialShaderPrivate)
1528 {
1529
1530 }
1531
updateState(const QSGMaterialShader::RenderState & state,QSGMaterial * newEffect,QSGMaterial * oldEffect)1532 void MapPolylineShaderLineStrip::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
1533 {
1534 Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
1535 MapPolylineMaterial *oldMaterial = static_cast<MapPolylineMaterial *>(oldEffect);
1536 MapPolylineMaterial *newMaterial = static_cast<MapPolylineMaterial *>(newEffect);
1537
1538 const QColor &c = newMaterial->color();
1539 const QMatrix4x4 &geoProjection = newMaterial->geoProjection();
1540 const QDoubleVector3D ¢er = newMaterial->center();
1541
1542 QVector3D vecCenter, vecCenter_lowpart;
1543 for (int i = 0; i < 3; i++)
1544 QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]);
1545
1546 if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) {
1547 float opacity = state.opacity() * c.alphaF();
1548 QVector4D v(c.redF() * opacity,
1549 c.greenF() * opacity,
1550 c.blueF() * opacity,
1551 opacity);
1552 program()->setUniformValue(m_color_id, v);
1553 }
1554
1555 if (state.isMatrixDirty())
1556 {
1557 program()->setUniformValue(m_matrix_id, state.projectionMatrix());
1558 }
1559
1560 program()->setUniformValue(m_mapProjection_id, geoProjection);
1561
1562 program()->setUniformValue(m_center_id, vecCenter);
1563 program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart);
1564 program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset()));
1565 }
1566
attributeNames() const1567 const char * const *MapPolylineShaderLineStrip::attributeNames() const
1568 {
1569 static char const *const attr[] = { "vertex", nullptr };
1570 return attr;
1571 }
1572
createShader() const1573 QSGMaterialShader *MapPolylineMaterial::createShader() const
1574 {
1575 return new MapPolylineShaderLineStrip();
1576 }
1577
type() const1578 QSGMaterialType *MapPolylineMaterial::type() const
1579 {
1580 static QSGMaterialType type;
1581 return &type;
1582 }
1583
compare(const QSGMaterial * other) const1584 int MapPolylineMaterial::compare(const QSGMaterial *other) const
1585 {
1586 const MapPolylineMaterial &o = *static_cast<const MapPolylineMaterial *>(other);
1587 if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset && o.m_lineWidth == m_lineWidth)
1588 return QSGFlatColorMaterial::compare(other);
1589 return -1;
1590 }
1591
attributesMapPolylineTriangulated()1592 const QSGGeometry::AttributeSet &MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated()
1593 {
1594 return MapPolylineEntry::attributes();
1595 }
1596
MapPolylineNodeOpenGLExtruded()1597 MapPolylineNodeOpenGLExtruded::MapPolylineNodeOpenGLExtruded()
1598 : m_geometryTriangulating(MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated(),
1599 0 /* vtx cnt */, 0 /* index cnt */, QSGGeometry::UnsignedIntType /* index type */)
1600 {
1601 m_geometryTriangulating.setDrawingMode(QSGGeometry::DrawTriangles);
1602 QSGGeometryNode::setMaterial(&fill_material_);
1603 QSGGeometryNode::setGeometry(&m_geometryTriangulating);
1604 }
1605
~MapPolylineNodeOpenGLExtruded()1606 MapPolylineNodeOpenGLExtruded::~MapPolylineNodeOpenGLExtruded()
1607 {
1608
1609 }
1610
allocateAndFillEntries(QSGGeometry * geom,bool closed,unsigned int zoom) const1611 bool QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom,
1612 bool closed,
1613 unsigned int zoom) const
1614 {
1615 // Select LOD. Generate if not present. Assign it to m_screenVertices;
1616 if (m_dataChanged) {
1617 // it means that the data really changed.
1618 // So synchronously produce LOD 1, and enqueue the requested one if != 0 or 1.
1619 // Select 0 if 0 is requested, or 1 in all other cases.
1620 selectLODOnDataChanged(zoom, m_bboxLeftBoundWrapped.x());
1621 } else {
1622 // Data has not changed, but active LOD != requested LOD.
1623 // So, if there are no active tasks, try to change to the correct one.
1624 if (!selectLODOnLODMismatch(zoom, m_bboxLeftBoundWrapped.x(), closed))
1625 return false;
1626 }
1627
1628 const QVector<QDeclarativeGeoMapItemUtils::vec2> &v = *m_screenVertices;
1629 if (v.size() < 2) {
1630 geom->allocate(0, 0);
1631 return true;
1632 }
1633 const int numSegments = (v.size() - 1);
1634
1635 const int numIndices = numSegments * 6; // six vertices per line segment
1636 geom->allocate(numIndices);
1637 MapPolylineNodeOpenGLExtruded::MapPolylineEntry *vertices =
1638 static_cast<MapPolylineNodeOpenGLExtruded::MapPolylineEntry *>(geom->vertexData());
1639
1640 for (int i = 0; i < numSegments; ++i) {
1641 MapPolylineNodeOpenGLExtruded::MapPolylineEntry e;
1642 const QDeclarativeGeoMapItemUtils::vec2 &cur = v[i];
1643 const QDeclarativeGeoMapItemUtils::vec2 &next = v[i+1];
1644 e.triangletype = 1.0;
1645 e.next = next;
1646 e.prev = cur;
1647 e.pos = cur;
1648 e.direction = 1.0;
1649 e.vertextype = -1.0;
1650 vertices[i*6] = e;
1651 e.direction = -1.0;
1652 vertices[i*6+1] = e;
1653 e.pos = next;
1654 e.vertextype = 1.0;
1655 vertices[i*6+2] = e;
1656
1657 // Second tri
1658 e.triangletype = -1.0;
1659 e.direction = -1.0;
1660 vertices[i*6+3] = e;
1661 e.direction = 1.0;
1662 vertices[i*6+4] = e;
1663 e.pos = cur;
1664 e.vertextype = -1.0;
1665 vertices[i*6+5] = e;
1666
1667 if (i != 0) {
1668 vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[i-1];
1669 } else {
1670 if (closed) {
1671 vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[numSegments - 1];
1672 } else {
1673 vertices[i*6].triangletype = vertices[i*6+1].triangletype = vertices[i*6+5].triangletype = 2.0;
1674 }
1675 }
1676 if (i != numSegments - 1) {
1677 vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[i+2];
1678 } else {
1679 if (closed) {
1680 vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[1];
1681 } else {
1682 vertices[i*6+2].triangletype = vertices[i*6+3].triangletype = vertices[i*6+4].triangletype = 3.0;
1683 }
1684 }
1685 }
1686 return true;
1687 }
1688
allocateAndFillLineStrip(QSGGeometry * geom,int lod) const1689 void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom,
1690 int lod) const
1691 {
1692 // Select LOD. Generate if not present. Assign it to m_screenVertices;
1693 Q_UNUSED(lod)
1694
1695 const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = *m_screenVertices;
1696 geom->allocate(vx.size());
1697
1698 QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D();
1699 for (int i = 0; i < vx.size(); ++i)
1700 pts[i].set(vx[i].x, vx[i].y);
1701 }
1702
update(const QColor & fillColor,const float lineWidth,const QGeoMapPolylineGeometryOpenGL * shape,const QMatrix4x4 geoProjection,const QDoubleVector3D center,const Qt::PenCapStyle capStyle,bool closed,unsigned int zoom)1703 void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor,
1704 const float lineWidth,
1705 const QGeoMapPolylineGeometryOpenGL *shape,
1706 const QMatrix4x4 geoProjection,
1707 const QDoubleVector3D center,
1708 const Qt::PenCapStyle capStyle,
1709 bool closed,
1710 unsigned int zoom)
1711 {
1712 // shape->size() == number of triangles
1713 if (shape->m_screenVertices->size() < 2
1714 || lineWidth < 0.5 || fillColor.alpha() == 0) { // number of points
1715 setSubtreeBlocked(true);
1716 return;
1717 } else {
1718 setSubtreeBlocked(false);
1719 }
1720
1721 QSGGeometry *fill = QSGGeometryNode::geometry();
1722 if (shape->m_dataChanged || !shape->isLODActive(zoom) || !fill->vertexCount()) { // fill->vertexCount for when node gets destroyed by MapItemBase bcoz of opacity, then recreated.
1723 if (shape->allocateAndFillEntries(fill, closed, zoom)) {
1724 markDirty(DirtyGeometry);
1725 shape->m_dataChanged = false;
1726 }
1727 }
1728
1729 // Update this
1730 // if (fillColor != fill_material_.color())
1731 {
1732 fill_material_.setWrapOffset(shape->m_wrapOffset - 1);
1733 fill_material_.setColor(fillColor);
1734 fill_material_.setGeoProjection(geoProjection);
1735 fill_material_.setCenter(center);
1736 fill_material_.setLineWidth(lineWidth);
1737 fill_material_.setMiter(capStyle != Qt::FlatCap);
1738 setMaterial(&fill_material_);
1739 markDirty(DirtyMaterial);
1740 }
1741 }
1742
MapPolylineShaderExtruded()1743 MapPolylineShaderExtruded::MapPolylineShaderExtruded() : QSGMaterialShader(*new QSGMaterialShaderPrivate)
1744 {
1745
1746 }
1747
updateState(const QSGMaterialShader::RenderState & state,QSGMaterial * newEffect,QSGMaterial * oldEffect)1748 void MapPolylineShaderExtruded::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
1749 {
1750 Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
1751 MapPolylineMaterialExtruded *oldMaterial = static_cast<MapPolylineMaterialExtruded *>(oldEffect);
1752 MapPolylineMaterialExtruded *newMaterial = static_cast<MapPolylineMaterialExtruded *>(newEffect);
1753
1754 const QColor &c = newMaterial->color();
1755 const QMatrix4x4 &geoProjection = newMaterial->geoProjection();
1756 const QDoubleVector3D ¢er = newMaterial->center();
1757
1758 QVector3D vecCenter, vecCenter_lowpart;
1759 for (int i = 0; i < 3; i++)
1760 QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]);
1761
1762 if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) {
1763 float opacity = state.opacity() * c.alphaF();
1764 QVector4D v(c.redF() * opacity,
1765 c.greenF() * opacity,
1766 c.blueF() * opacity,
1767 opacity);
1768 program()->setUniformValue(m_color_id, v);
1769 }
1770
1771 if (state.isMatrixDirty())
1772 {
1773 program()->setUniformValue(m_matrix_id, state.projectionMatrix());
1774 }
1775
1776 // ToDo: dirty-flag all this
1777 program()->setUniformValue(m_mapProjection_id, geoProjection);
1778
1779 program()->setUniformValue(m_center_id, vecCenter);
1780 program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart);
1781 program()->setUniformValue(m_miter_id, newMaterial->miter());
1782 program()->setUniformValue(m_lineWidth_id, newMaterial->lineWidth());
1783 program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset()));
1784
1785 const QRectF viewportRect = state.viewportRect();
1786 const float aspect = float(viewportRect.width() / viewportRect.height());
1787 program()->setUniformValue(m_aspect_id, aspect);
1788 }
1789
attributeNames() const1790 const char * const *MapPolylineShaderExtruded::attributeNames() const
1791 {
1792 return MapPolylineNodeOpenGLExtruded::MapPolylineEntry::attributeNames();
1793 }
1794
createShader() const1795 QSGMaterialShader *MapPolylineMaterialExtruded::createShader() const
1796 {
1797 return new MapPolylineShaderExtruded();
1798 }
1799
type() const1800 QSGMaterialType *MapPolylineMaterialExtruded::type() const
1801 {
1802 static QSGMaterialType type;
1803 return &type;
1804 }
1805
compare(const QSGMaterial * other) const1806 int MapPolylineMaterialExtruded::compare(const QSGMaterial *other) const
1807 {
1808 const MapPolylineMaterialExtruded &o = *static_cast<const MapPolylineMaterialExtruded *>(other);
1809 if (o.m_miter == m_miter)
1810 return MapPolylineMaterial::compare(other);
1811 return -1;
1812 }
1813
vertexShaderMiteredSegments() const1814 const char *MapPolylineShaderExtruded::vertexShaderMiteredSegments() const
1815 {
1816 return
1817 "attribute highp vec4 vertex;\n"
1818 "attribute highp vec4 previous;\n"
1819 "attribute highp vec4 next;\n"
1820 "attribute lowp float direction;\n"
1821 "attribute lowp float triangletype;\n"
1822 "attribute lowp float vertextype;\n" // -1.0 if it is the "left" end of the segment, 1.0 if it is the "right" end.
1823 "\n"
1824 "uniform highp mat4 qt_Matrix;\n"
1825 "uniform highp mat4 mapProjection;\n"
1826 "uniform highp vec3 center;\n"
1827 "uniform highp vec3 center_lowpart;\n"
1828 "uniform lowp float lineWidth;\n"
1829 "uniform lowp float aspect;\n"
1830 "uniform lowp int miter;\n" // currently unused
1831 "uniform lowp vec4 color;\n"
1832 "uniform lowp float wrapOffset;\n"
1833 "\n"
1834 "varying vec4 primitivecolor;\n"
1835 "\n"
1836 " \n"
1837 "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n"
1838 "void main() {\n" // ln 22
1839 " primitivecolor = color;\n"
1840 " vec2 aspectVec = vec2(aspect, 1.0);\n"
1841 " mat4 projViewModel = qt_Matrix * mapProjection;\n"
1842 " vec4 cur = wrapped(vertex) - vec4(center, 0.0);\n"
1843 " cur = cur - vec4(center_lowpart, 0.0);\n"
1844 " vec4 prev = wrapped(previous) - vec4(center, 0.0);\n"
1845 " prev = prev - vec4(center_lowpart, 0.0);\n"
1846 " vec4 nex = wrapped(next) - vec4(center, 0.0);\n"
1847 " nex = nex - vec4(center_lowpart, 0.0);\n"
1848 "\n"
1849 " vec4 centerProjected = projViewModel * vec4(center, 1.0);\n"
1850 " vec4 previousProjected = projViewModel * prev;\n"
1851 " vec4 currentProjected = projViewModel * cur;\n"
1852 " vec4 nextProjected = projViewModel * nex;\n"
1853 "\n"
1854 " //get 2D screen space with W divide and aspect correction\n"
1855 " vec2 currentScreen = (currentProjected.xy / currentProjected.w) * aspectVec;\n"
1856 " vec2 previousScreen = (previousProjected.xy / previousProjected.w) * aspectVec;\n"
1857 " vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n"
1858 " float len = (lineWidth);\n"
1859 " float orientation = direction;\n"
1860 " bool clipped = false;\n"
1861 " bool otherEndBelowFrustum = false;\n"
1862 " //starting point uses (next - current)\n"
1863 " vec2 dir = vec2(0.0);\n"
1864 " if (vertextype < 0.0) {\n"
1865 " dir = normalize(nextScreen - currentScreen);\n"
1866 " if (nextProjected.z < 0.0) dir = -dir;\n"
1867 " } else { \n"
1868 " dir = normalize(currentScreen - previousScreen);\n"
1869 " if (previousProjected.z < 0.0) dir = -dir;\n"
1870 " }\n"
1871 // first, clip current, and make sure currentProjected.z is > 0
1872 " if (currentProjected.z < 0.0) {\n"
1873 " if ((nextProjected.z > 0.0 && vertextype < 0.0) || (vertextype > 0.0 && previousProjected.z > 0.0)) {\n"
1874 " dir = -dir;\n"
1875 " clipped = true;\n"
1876 " if (vertextype < 0.0 && nextProjected.y / nextProjected.w < -1.0) otherEndBelowFrustum = true;\n"
1877 " else if (vertextype > 0.0 && previousProjected.y / previousProjected.w < -1.0) otherEndBelowFrustum = true;\n"
1878 " } else {\n"
1879 " primitivecolor = vec4(0.0,0.0,0.0,0.0);\n"
1880 " gl_Position = vec4(-10000000.0, -1000000000.0, -1000000000.0, 1);\n" // get the vertex out of the way if the segment is fully invisible
1881 " return;\n"
1882 " }\n"
1883 " } else if (triangletype < 2.0) {\n" // vertex in the view, try to miter
1884 " //get directions from (C - B) and (B - A)\n"
1885 " vec2 dirA = normalize((currentScreen - previousScreen));\n"
1886 " if (previousProjected.z < 0.0) dirA = -dirA;\n"
1887 " vec2 dirB = normalize((nextScreen - currentScreen));\n"
1888 " //now compute the miter join normal and length\n"
1889 " if (nextProjected.z < 0.0) dirB = -dirB;\n"
1890 " vec2 tangent = normalize(dirA + dirB);\n"
1891 " vec2 perp = vec2(-dirA.y, dirA.x);\n"
1892 " vec2 vmiter = vec2(-tangent.y, tangent.x);\n"
1893 " len = lineWidth / dot(vmiter, perp);\n"
1894 // The following is an attempt to have a segment-length based miter threshold.
1895 // A mediocre workaround until better mitering will be added.
1896 " float lenTreshold = clamp( min(length((currentProjected.xy - previousProjected.xy) / aspectVec),"
1897 " length((nextProjected.xy - currentProjected.xy) / aspectVec)), 3.0, 6.0 ) * 0.5;\n"
1898 " if (len < lineWidth * lenTreshold && len > -lineWidth * lenTreshold \n"
1899 " ) {\n"
1900 " dir = tangent;\n"
1901 " } else {\n"
1902 " len = lineWidth;\n"
1903 " }\n"
1904 " }\n"
1905 " vec4 offset;\n"
1906 " if (!clipped) {\n"
1907 " vec2 normal = normalize(vec2(-dir.y, dir.x));\n"
1908 " normal *= len;\n" // fracZL apparently was needed before the (-2.0 / qt_Matrix[1][1]) factor was introduced
1909 " normal /= aspectVec;\n" // straighten the normal up again
1910 " float scaleFactor = currentProjected.w / centerProjected.w;\n"
1911 " 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
1912 " gl_Position = currentProjected + offset;\n"
1913 " } else {\n"
1914 " 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.
1915 " else offset = vec4((dir * 500000000000.0) / aspectVec, 0.0, 0.0);\n" // Hack alert: just 1 triangle, long enough to look like a rectangle.
1916 " if (vertextype < 0.0) gl_Position = nextProjected - offset; else gl_Position = previousProjected + offset;\n"
1917 " }\n"
1918 "}\n";
1919 }
1920
getSimplified(QVector<QDeclarativeGeoMapItemUtils::vec2> & wrappedPath,double leftBoundWrapped,unsigned int zoom)1921 QVector<QDeclarativeGeoMapItemUtils::vec2> QGeoMapItemLODGeometry::getSimplified(
1922 QVector<QDeclarativeGeoMapItemUtils::vec2> &wrappedPath, // reference as it gets copied in the nested call
1923 double leftBoundWrapped,
1924 unsigned int zoom)
1925 {
1926 // Try a simplify step
1927 QList<QDoubleVector2D> data;
1928 for (auto e: wrappedPath)
1929 data << e.toDoubleVector2D();
1930 const QList<QDoubleVector2D> simplified = QGeoSimplify::geoSimplifyZL(data,
1931 leftBoundWrapped,
1932 zoom);
1933
1934 data.clear();
1935 QVector<QDeclarativeGeoMapItemUtils::vec2> simple;
1936 for (auto e: simplified)
1937 simple << e;
1938 return simple;
1939 }
1940
1941
isLODActive(unsigned int lod) const1942 bool QGeoMapItemLODGeometry::isLODActive(unsigned int lod) const
1943 {
1944 return m_screenVertices == m_verticesLOD[zoomToLOD(lod)].data();
1945 }
1946
1947 class PolylineSimplifyTask : public QRunnable
1948 {
1949 public:
PolylineSimplifyTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>> & input,const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>> & output,double leftBound,unsigned int zoom,QSharedPointer<unsigned int> & working)1950 PolylineSimplifyTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call
1951 const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output,
1952 double leftBound,
1953 unsigned int zoom,
1954 QSharedPointer<unsigned int> &working)
1955 : m_zoom(zoom)
1956 , m_leftBound(leftBound)
1957 , m_input(input)
1958 , m_output(output)
1959 , m_working(working)
1960 {
1961 Q_ASSERT(!input.isNull());
1962 Q_ASSERT(!output.isNull());
1963 }
1964
1965 ~PolylineSimplifyTask() override;
1966
run()1967 void run() override
1968 {
1969 // Skip sending notifications for now. Updated data will be picked up eventually.
1970 // ToDo: figure out how to connect a signal from here to a slot in the item.
1971 *m_working = QGeoMapPolylineGeometryOpenGL::zoomToLOD(m_zoom);
1972 const QVector<QDeclarativeGeoMapItemUtils::vec2> res =
1973 QGeoMapPolylineGeometryOpenGL::getSimplified( *m_input,
1974 m_leftBound,
1975 QGeoMapPolylineGeometryOpenGL::zoomForLOD(m_zoom));
1976 *m_output = res;
1977 *m_working = 0;
1978 }
1979
1980 unsigned int m_zoom;
1981 double m_leftBound;
1982 QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > m_input, m_output;
1983 QSharedPointer<unsigned int> m_working;
1984 };
1985
enqueueSimplificationTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>> & input,const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>> & output,double leftBound,unsigned int zoom,QSharedPointer<unsigned int> & working)1986 void QGeoMapItemLODGeometry::enqueueSimplificationTask(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input,
1987 const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output,
1988 double leftBound,
1989 unsigned int zoom,
1990 QSharedPointer<unsigned int> &working)
1991 {
1992 Q_ASSERT(!input.isNull());
1993 Q_ASSERT(!output.isNull());
1994 PolylineSimplifyTask *task = new PolylineSimplifyTask(input,
1995 output,
1996 leftBound,
1997 zoom,
1998 working);
1999 threadPool->start(task);
2000 }
2001
~PolylineSimplifyTask()2002 PolylineSimplifyTask::~PolylineSimplifyTask() {}
2003
selectLOD(unsigned int zoom,double leftBound,bool)2004 void QGeoMapItemLODGeometry::selectLOD(unsigned int zoom, double leftBound, bool /* closed */) // closed to tell if this is a polygon or a polyline.
2005 {
2006 unsigned int requestedLod = zoomToLOD(zoom);
2007 if (!m_verticesLOD[requestedLod].isNull()) {
2008 m_screenVertices = m_verticesLOD[requestedLod].data();
2009 } else if (!m_verticesLOD.at(0)->isEmpty()) {
2010 // if here, zoomToLOD != 0 and no current working task.
2011 // So select the last filled LOD != m_working (lower-bounded by 1,
2012 // guaranteed to exist), and enqueue the right one
2013 m_verticesLOD[requestedLod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2014 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2015
2016 for (unsigned int i = requestedLod - 1; i >= 1; i--) {
2017 if (*m_working != i && !m_verticesLOD[i].isNull()) {
2018 m_screenVertices = m_verticesLOD[i].data();
2019 break;
2020 } else if (i == 1) {
2021 // get 1 synchronously if not computed already
2022 m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2023 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2024 *m_verticesLOD[1] = getSimplified( *m_verticesLOD[0],
2025 leftBound,
2026 zoomForLOD(0));
2027 if (requestedLod == 1)
2028 return;
2029 }
2030 }
2031
2032 enqueueSimplificationTask( m_verticesLOD.at(0),
2033 m_verticesLOD[requestedLod],
2034 leftBound,
2035 zoom,
2036 m_working);
2037
2038 }
2039 }
2040
selectLODOnDataChanged(unsigned int zoom,double leftBound) const2041 void QGeoMapItemLODGeometry::selectLODOnDataChanged(unsigned int zoom, double leftBound) const
2042 {
2043 unsigned int lod = zoomToLOD(zoom);
2044 if (lod > 0) {
2045 // Generate ZL 1 as fallback for all cases != 0. Do not do if 0 is requested
2046 // (= old behavior, LOD disabled)
2047 m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2048 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2049 *m_verticesLOD[1] = getSimplified( *m_verticesLOD[0],
2050 leftBound,
2051 zoomForLOD(0));
2052 }
2053 if (lod > 1) {
2054 if (!m_verticesLOD[lod])
2055 m_verticesLOD[lod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>(
2056 new QVector<QDeclarativeGeoMapItemUtils::vec2>);
2057 enqueueSimplificationTask( m_verticesLOD.at(0),
2058 m_verticesLOD[lod],
2059 leftBound,
2060 zoom,
2061 m_working);
2062 }
2063 m_screenVertices = m_verticesLOD[qMin<unsigned int>(lod, 1)].data(); // return only 0,1 synchronously
2064 }
2065
zoomToLOD(unsigned int zoom)2066 unsigned int QGeoMapItemLODGeometry::zoomToLOD(unsigned int zoom)
2067 {
2068 unsigned int res;
2069 if (zoom > 20)
2070 res = 0;
2071 else
2072 res = qBound<unsigned int>(3, zoom, 20) / 3; // bound LOD'ing between ZL 3 and 20. Every 3 ZoomLevels
2073 return res;
2074 }
2075
zoomForLOD(unsigned int zoom)2076 unsigned int QGeoMapItemLODGeometry::zoomForLOD(unsigned int zoom)
2077 {
2078 unsigned int res = (qBound<unsigned int>(3, zoom, 20) / 3) * 3;
2079 if (zoom < 6)
2080 return res;
2081 return res + 1; // give more resolution when closing in
2082 }
2083
2084 QT_END_NAMESPACE
2085