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 &center,
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 &center = 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 &center = 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