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