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 "qdeclarativegeomapitemutils_p.h"
38 #include "qdeclarativepolygonmapitem_p.h"
39 #include "qdeclarativepolylinemapitem_p_p.h"
40 #include "qdeclarativepolygonmapitem_p_p.h"
41 #include "qdeclarativerectanglemapitem_p_p.h"
42 #include "qlocationutils_p.h"
43 #include "error_messages_p.h"
44 #include "locationvaluetypehelper_p.h"
45 #include <QtLocation/private/qgeomap_p.h>
46 
47 #include <QtCore/QScopedValueRollback>
48 #include <QtGui/private/qtriangulator_p.h>
49 #include <QtQml/QQmlInfo>
50 #include <QtQml/private/qqmlengine_p.h>
51 #include <QPainter>
52 #include <QPainterPath>
53 #include <qnumeric.h>
54 
55 #include <QtPositioning/private/qdoublevector2d_p.h>
56 #include <QtPositioning/private/qclipperutils_p.h>
57 #include <QtPositioning/private/qgeopolygon_p.h>
58 #include <QtPositioning/private/qwebmercator_p.h>
59 #include <QtQuick/private/qsgmaterialshader_p.h>
60 #include <QtQuick/private/qquickitem_p.h>
61 #include <QtQuick/qsgnode.h>
62 
63 /* poly2tri triangulator includes */
64 #include <clip2tri.h>
65 #include <earcut.hpp>
66 #include <array>
67 
68 QT_BEGIN_NAMESPACE
69 
70 /*!
71     \qmltype MapPolygon
72     \instantiates QDeclarativePolygonMapItem
73     \inqmlmodule QtLocation
74     \ingroup qml-QtLocation5-maps
75     \since QtLocation 5.5
76 
77     \brief The MapPolygon type displays a polygon on a Map.
78 
79     The MapPolygon type displays a polygon on a Map, specified in terms of an ordered list of
80     \l {QtPositioning::coordinate}{coordinates}. For best appearance and results, polygons should be
81     simple (not self-intersecting).
82 
83     The \l {QtPositioning::coordinate}{coordinates} on the path cannot be directly changed after
84     being added to the Polygon.  Instead, copy the \l path into a var, modify the copy and reassign
85     the copy back to the \l path.
86 
87     \code
88     var path = mapPolygon.path;
89     path[0].latitude = 5;
90     mapPolygon.path = path;
91     \endcode
92 
93     Coordinates can also be added and removed at any time using the \l addCoordinate and
94     \l removeCoordinate methods.
95 
96     For drawing rectangles with "straight" edges (same latitude across one
97     edge, same latitude across the other), the \l MapRectangle type provides
98     a simpler, two-point API.
99 
100     By default, the polygon is displayed as a 1 pixel black border with no
101     fill. To change its appearance, use the \l color, \l border.color and
102     \l border.width properties.
103 
104     \note Since MapPolygons are geographic items, dragging a MapPolygon
105     (through the use of \l MouseArea) causes its vertices to be
106     recalculated in the geographic coordinate space. The edges retain the
107     same geographic lengths (latitude and longitude differences between the
108     vertices), but they remain straight. Apparent stretching of the item occurs
109     when dragged to a different latitude.
110 
111     \section2 Performance
112 
113     MapPolygons have a rendering cost that is O(n) with respect to the number
114     of vertices. This means that the per frame cost of having a Polygon on the
115     Map grows in direct proportion to the number of points on the Polygon. There
116     is an additional triangulation cost (approximately O(n log n)) which is
117     currently paid with each frame, but in future may be paid only upon adding
118     or removing points.
119 
120     Like the other map objects, MapPolygon is normally drawn without a smooth
121     appearance. Setting the \l {Item::opacity}{opacity} property will force the object to
122     be blended, which decreases performance considerably depending on the hardware in use.
123 
124     \section2 Example Usage
125 
126     The following snippet shows a MapPolygon being used to display a triangle,
127     with three vertices near Brisbane, Australia. The triangle is filled in
128     green, with a 1 pixel black border.
129 
130     \code
131     Map {
132         MapPolygon {
133             color: 'green'
134             path: [
135                 { latitude: -27, longitude: 153.0 },
136                 { latitude: -27, longitude: 154.1 },
137                 { latitude: -28, longitude: 153.5 }
138             ]
139         }
140     }
141     \endcode
142 
143     \image api-mappolygon.png
144 */
145 
146 /*!
147     \qmlproperty bool QtLocation::MapPolygon::autoFadeIn
148 
149     This property holds whether the item automatically fades in when zooming into the map
150     starting from very low zoom levels. By default this is \c true.
151     Setting this property to \c false causes the map item to always have the opacity specified
152     with the \l QtQuick::Item::opacity property, which is 1.0 by default.
153 
154     \since 5.14
155 */
156 
QGeoMapPolygonGeometry()157 QGeoMapPolygonGeometry::QGeoMapPolygonGeometry()
158 :   assumeSimple_(false)
159 {
160 }
161 
162 /*!
163     \internal
164 */
updateSourcePoints(const QGeoMap & map,const QList<QDoubleVector2D> & path)165 void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map,
166                                                 const QList<QDoubleVector2D> &path)
167 {
168     if (!sourceDirty_)
169         return;
170     const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
171     srcPath_ = QPainterPath();
172 
173     // build the actual path
174     // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
175     srcOrigin_ = geoLeftBound_;
176     double unwrapBelowX = 0;
177     QDoubleVector2D leftBoundWrapped = p.wrapMapProjection(p.geoToMapProjection(geoLeftBound_));
178     if (preserveGeometry_)
179         unwrapBelowX = leftBoundWrapped.x();
180 
181     QList<QDoubleVector2D> wrappedPath;
182     wrappedPath.reserve(path.size());
183     QDoubleVector2D wrappedLeftBound(qInf(), qInf());
184     // 1)
185     for (int i = 0; i < path.size(); ++i) {
186         const QDoubleVector2D &coord = path.at(i);
187         QDoubleVector2D wrappedProjection = p.wrapMapProjection(coord);
188 
189         // We can get NaN if the map isn't set up correctly, or the projection
190         // is faulty -- probably best thing to do is abort
191         if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y()))
192             return;
193 
194         const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x());
195         // unwrap x to preserve geometry if moved to border of map
196         if (preserveGeometry_ && isPointLessThanUnwrapBelowX) {
197             double distance = wrappedProjection.x() - unwrapBelowX;
198             if (distance < 0.0)
199                 distance += 1.0;
200             wrappedProjection.setX(unwrapBelowX + distance);
201         }
202         if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) {
203             wrappedLeftBound = wrappedProjection;
204         }
205         wrappedPath.append(wrappedProjection);
206     }
207 
208     // 2)
209     QList<QList<QDoubleVector2D> > clippedPaths;
210     const QList<QDoubleVector2D> &visibleRegion = p.projectableGeometry();
211     if (visibleRegion.size()) {
212         c2t::clip2tri clipper;
213         clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), true);
214         clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
215         Paths res = clipper.execute(c2t::clip2tri::Intersection, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd);
216         clippedPaths = QClipperUtils::pathsToQList(res);
217 
218         // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X
219         QDoubleVector2D lb(qInf(), qInf());
220         for (const QList<QDoubleVector2D> &path: clippedPaths)
221             for (const QDoubleVector2D &p: path)
222                 if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y()))
223                     // y-minimization needed to find the same point on polygon and border
224                     lb = p;
225 
226         if (qIsInf(lb.x())) // e.g., when the polygon is clipped entirely
227             return;
228 
229         // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which
230         //      in turn will make the geometry wrap around.
231         lb.setX(qMax(wrappedLeftBound.x(), lb.x()));
232         leftBoundWrapped = lb;
233         srcOrigin_ = p.mapProjectionToGeo(p.unwrapMapProjection(lb));
234     } else {
235         clippedPaths.append(wrappedPath);
236     }
237 
238     // 3)
239     QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(leftBoundWrapped);
240     for (const QList<QDoubleVector2D> &path: clippedPaths) {
241         QDoubleVector2D lastAddedPoint;
242         for (int i = 0; i < path.size(); ++i) {
243             QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(path.at(i));
244             point = point - origin; // (0,0) if point == geoLeftBound_
245 
246             if (i == 0) {
247                 srcPath_.moveTo(point.toPointF());
248                 lastAddedPoint = point;
249             } else {
250                 if ((point - lastAddedPoint).manhattanLength() > 3 ||
251                         i == path.size() - 1) {
252                     srcPath_.lineTo(point.toPointF());
253                     lastAddedPoint = point;
254                 }
255             }
256         }
257         srcPath_.closeSubpath();
258     }
259 
260     if (!assumeSimple_)
261         srcPath_ = srcPath_.simplified();
262 
263     sourceBounds_ = srcPath_.boundingRect();
264 }
265 
266 /*!
267     \internal
268 */
updateScreenPoints(const QGeoMap & map,qreal strokeWidth)269 void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map, qreal strokeWidth)
270 {
271     if (!screenDirty_)
272         return;
273 
274     if (map.viewportWidth() == 0 || map.viewportHeight() == 0) {
275         clear();
276         return;
277     }
278 
279     // The geometry has already been clipped against the visible region projection in wrapped mercator space.
280     QPainterPath ppi = srcPath_;
281     clear();
282 
283     // a polygon requires at least 3 points;
284     if (ppi.elementCount() < 3)
285         return;
286 
287     // translate the path into top-left-centric coordinates
288     QRectF bb = ppi.boundingRect();
289     ppi.translate(-bb.left(), -bb.top());
290     firstPointOffset_ = -1 * bb.topLeft();
291 
292     ppi.closeSubpath();
293     screenOutline_ = ppi;
294 
295     using Coord = double;
296     using N = uint32_t;
297     using Point = std::array<Coord, 2>;
298 
299     std::vector<std::vector<Point>> polygon;
300     polygon.push_back(std::vector<Point>());
301     std::vector<Point> &poly = polygon.front();
302     // ... fill polygon structure with actual data
303 
304     for (int i = 0; i < ppi.elementCount(); ++i) {
305         const QPainterPath::Element e = ppi.elementAt(i);
306         if (e.isMoveTo() || i == ppi.elementCount() - 1
307                 || (qAbs(e.x - poly.front()[0]) < 0.1
308                     && qAbs(e.y - poly.front()[1]) < 0.1)) {
309             Point p = {{ e.x, e.y }};
310             poly.push_back( p );
311         } else if (e.isLineTo()) {
312             Point p = {{ e.x, e.y }};
313             poly.push_back( p );
314         } else {
315             qWarning("Unhandled element type in polygon painterpath");
316         }
317     }
318 
319     if (poly.size() > 2) {
320         // Run tessellation
321         // Returns array of indices that refer to the vertices of the input polygon.
322         // Three subsequent indices form a triangle.
323         screenVertices_.clear();
324         screenIndices_.clear();
325         for (const auto &p : poly)
326             screenVertices_ << QPointF(p[0], p[1]);
327         std::vector<N> indices = qt_mapbox::earcut<N>(polygon);
328         for (const auto &i: indices)
329             screenIndices_ << quint32(i);
330     }
331 
332     screenBounds_ = ppi.boundingRect();
333     if (strokeWidth != 0.0)
334         this->translate(QPointF(strokeWidth, strokeWidth));
335 }
336 
337 #if QT_CONFIG(opengl)
QGeoMapPolygonGeometryOpenGL()338 QGeoMapPolygonGeometryOpenGL::QGeoMapPolygonGeometryOpenGL(){
339 }
340 
updateSourcePoints(const QGeoMap & map,const QList<QDoubleVector2D> & path)341 void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QList<QDoubleVector2D> &path)
342 {
343     QList<QGeoCoordinate> geopath;
344     for (const auto &c: path)
345         geopath.append(QWebMercator::mercatorToCoord(c));
346     updateSourcePoints(map, geopath);
347 }
348 #endif
349 
350 // wrapPath always preserves the geometry
351 // This one handles holes
wrapPath(const QGeoPolygon & poly,const QGeoCoordinate & geoLeftBound,const QGeoProjectionWebMercator & p,QList<QList<QDoubleVector2D>> & wrappedPaths,QDoubleVector2D * leftBoundWrapped=nullptr)352 static void wrapPath(const QGeoPolygon &poly
353                      ,const QGeoCoordinate &geoLeftBound
354                      ,const QGeoProjectionWebMercator &p
355                      ,QList<QList<QDoubleVector2D> > &wrappedPaths
356                      ,QDoubleVector2D *leftBoundWrapped = nullptr)
357 {
358     QList<QList<QDoubleVector2D> > paths;
359     for (int i = 0; i < 1+poly.holesCount(); ++i) {
360         QList<QDoubleVector2D> path;
361         if (!i) {
362             for (const QGeoCoordinate &c : poly.path())
363                 path << p.geoToMapProjection(c);
364         } else {
365             for (const QGeoCoordinate &c : poly.holePath(i-1))
366                 path << p.geoToMapProjection(c);
367         }
368         paths.append(path);
369     }
370 
371     const QDoubleVector2D leftBound = p.geoToMapProjection(geoLeftBound);
372     wrappedPaths.clear();
373 
374     QList<QDoubleVector2D> wrappedPath;
375     // compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
376     for (int j = 0; j < paths.size(); ++j) {
377         const QList<QDoubleVector2D> &path = paths.at(j);
378         wrappedPath.clear();
379         for (int i = 0; i < path.size(); ++i) {
380             QDoubleVector2D coord = path.at(i);
381 
382             // We can get NaN if the map isn't set up correctly, or the projection
383             // is faulty -- probably best thing to do is abort
384             if (!qIsFinite(coord.x()) || !qIsFinite(coord.y())) {
385                 wrappedPaths.clear();
386                 return;
387             }
388 
389             const bool isPointLessThanUnwrapBelowX = (coord.x() < leftBound.x());
390             // unwrap x to preserve geometry if moved to border of map
391             if (isPointLessThanUnwrapBelowX)
392                 coord.setX(coord.x() + 1.0);
393             wrappedPath.append(coord);
394         }
395         wrappedPaths.append(wrappedPath);
396     }
397 
398     if (leftBoundWrapped)
399         *leftBoundWrapped = leftBound;
400 }
401 
cutPathEars(const QList<QList<QDoubleVector2D>> & wrappedPaths,QVector<QDeclarativeGeoMapItemUtils::vec2> & screenVertices,QVector<quint32> & screenIndices)402 static void cutPathEars(const QList<QList<QDoubleVector2D>> &wrappedPaths,
403                         QVector<QDeclarativeGeoMapItemUtils::vec2> &screenVertices,
404                         QVector<quint32> &screenIndices)
405 {
406     using Coord = double;
407     using N = uint32_t;
408     using Point = std::array<Coord, 2>;
409     screenVertices.clear();
410     screenIndices.clear();
411 
412     std::vector<std::vector<Point>> polygon;
413     std::vector<Point> poly;
414 
415     for (const QList<QDoubleVector2D> &wrappedPath: wrappedPaths) {
416         poly.clear();
417         for (const QDoubleVector2D &v: wrappedPath) {
418             screenVertices << v;
419             Point pt = {{ v.x(), v.y() }};
420             poly.push_back( pt );
421         }
422         polygon.push_back(poly);
423     }
424 
425     std::vector<N> indices = qt_mapbox::earcut<N>(polygon);
426 
427     for (const auto &i: indices)
428         screenIndices << quint32(i);
429 }
430 
cutPathEars(const QList<QDoubleVector2D> & wrappedPath,QVector<QDeclarativeGeoMapItemUtils::vec2> & screenVertices,QVector<quint32> & screenIndices)431 static void cutPathEars(const QList<QDoubleVector2D> &wrappedPath,
432                         QVector<QDeclarativeGeoMapItemUtils::vec2> &screenVertices,
433                         QVector<quint32> &screenIndices)
434 {
435     using Coord = double;
436     using N = uint32_t;
437     using Point = std::array<Coord, 2>;
438     screenVertices.clear();
439     screenIndices.clear();
440 
441     std::vector<std::vector<Point>> polygon;
442     std::vector<Point> poly;
443 
444     for (const QDoubleVector2D &v: wrappedPath) {
445         screenVertices << v;
446         Point pt = {{ v.x(), v.y() }};
447         poly.push_back( pt );
448     }
449     polygon.push_back(poly);
450 
451     std::vector<N> indices = qt_mapbox::earcut<N>(polygon);
452 
453     for (const auto &i: indices)
454         screenIndices << quint32(i);
455 }
456 
457 #if QT_CONFIG(opengl)
458 /*!
459     \internal
460 */
461 // This one does only a perimeter
updateSourcePoints(const QGeoMap & map,const QList<QGeoCoordinate> & perimeter)462 void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map,
463                                                 const QList<QGeoCoordinate> &perimeter)
464 {
465     if (!sourceDirty_)
466         return;
467     const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
468 
469     // build the actual path
470     // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
471     srcOrigin_ = geoLeftBound_;
472 
473     QDoubleVector2D leftBoundWrapped;
474     // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
475     QList<QDoubleVector2D> wrappedPath;
476     QDeclarativeGeoMapItemUtils::wrapPath(perimeter, geoLeftBound_, p,
477              wrappedPath, &leftBoundWrapped);
478 
479     // 1.1) do the same for the bbox
480     QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1;
481     QGeoPolygon bbox(QGeoPath(perimeter).boundingGeoRectangle());
482     QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p,
483              wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped);
484 
485     // 2) Store the triangulated polygon, and the wrapped bbox paths.
486     //    the triangulations can be used as they are, as they "bypass" the QtQuick display chain
487     //    the bbox wraps have to be however clipped, and then projected, in order to figure out the geometry.
488     //    Note that this might still cause the geometryChanged method to fail under some extreme conditions.
489     cutPathEars(wrappedPath, m_screenVertices, m_screenIndices);
490 
491     m_wrappedPolygons.resize(3);
492     m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1;
493     m_wrappedPolygons[1].wrappedBboxes = wrappedBbox;
494     m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1;
495 }
496 
497 // This one handles whole QGeoPolygon w. holes
updateSourcePoints(const QGeoMap & map,const QGeoPolygon & poly)498 void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly)
499 {
500     if (!sourceDirty_)
501         return;
502     const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
503 
504     // build the actual path
505     // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
506     srcOrigin_ = geoLeftBound_;
507 
508     QDoubleVector2D leftBoundWrapped;
509     QList<QList<QDoubleVector2D>> wrappedPath;
510     // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0
511     wrapPath(poly, geoLeftBound_, p,
512              wrappedPath, &leftBoundWrapped);
513 
514     // 1.1) do the same for the bbox
515     QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1;
516     QGeoPolygon bbox(poly.boundingGeoRectangle());
517     QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p,
518              wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped);
519 
520     // 2) Store the triangulated polygon, and the wrapped bbox paths.
521     //    the triangulations can be used as they are, as they "bypass" the QtQuick display chain
522     //    the bbox wraps have to be however clipped, and then projected, in order to figure out the geometry.
523     //    Note that this might still cause the geometryChanged method to fail under some extreme conditions.
524     cutPathEars(wrappedPath, m_screenVertices, m_screenIndices);
525     m_wrappedPolygons.resize(3);
526     m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1;
527     m_wrappedPolygons[1].wrappedBboxes = wrappedBbox;
528     m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1;
529 }
530 
updateSourcePoints(const QGeoMap & map,const QGeoRectangle & rect)531 void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect)
532 {
533     if (!sourceDirty_)
534         return;
535     const QList<QGeoCoordinate> perimeter = QDeclarativeRectangleMapItemPrivateCPU::path(rect);
536     updateSourcePoints(map, perimeter);
537 }
538 
539 /*!
540     \internal
541 */
updateScreenPoints(const QGeoMap & map,qreal strokeWidth,const QColor & strokeColor)542 void QGeoMapPolygonGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth , const QColor &strokeColor)
543 {
544     if (map.viewportWidth() == 0 || map.viewportHeight() == 0) {
545         clear();
546         return;
547     }
548 
549     // 1) identify which set to use: std, +1 or -1
550     const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
551     const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(srcOrigin_);
552     m_wrapOffset = p.projectionWrapFactor(leftBoundMercator) + 1; // +1 to get the offset into QLists
553 
554     // 1.1) select geometry set
555     // This could theoretically be skipped for those polygons whose bbox is not even projectable.
556     // However, such optimization could only be introduced if not calculating bboxes lazily.
557     // Hence not doing it.
558     if (sourceDirty_) {
559         m_dataChanged = true;
560     }
561 
562     if (strokeWidth == 0.0 || strokeColor.alpha() == 0) // or else the geometry of the border is used, so no point in calculating 2 of them
563         updateQuickGeometry(p, strokeWidth);
564 }
565 
updateQuickGeometry(const QGeoProjectionWebMercator & p,qreal)566 void QGeoMapPolygonGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal /*strokeWidth*/)
567 {
568      // 2) clip bbox
569     // BBox handling --  this is related to the bounding box geometry
570     // that has to inevitably follow the old projection codepath
571     // As it needs to provide projected coordinates for QtQuick interaction.
572     // This could be futher optimized to be updated in a lazy fashion.
573     const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(m_wrapOffset).wrappedBboxes;
574     QList<QList<QDoubleVector2D> > clippedBbox;
575     QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped;
576     bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1));
577     QDeclarativeGeoMapItemUtils::clipPolygon(wrappedBbox, p, clippedBbox, &bboxLeftBoundWrapped);
578 
579     // 3) project bbox
580     QPainterPath ppi;
581     if (!clippedBbox.size() || clippedBbox.first().size() < 3) {
582         sourceBounds_ = screenBounds_ = QRectF();
583         firstPointOffset_ = QPointF();
584         screenOutline_ = ppi;
585         return;
586     }
587 
588     QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox.first(), p, ppi); // Using first because a clipped box should always result in one polygon
589     const QRectF brect = ppi.boundingRect();
590     firstPointOffset_ = QPointF(brect.topLeft());
591     screenOutline_ = ppi;
592 
593     // 4) Set Screen bbox
594     screenBounds_ = brect;
595     sourceBounds_.setX(0);
596     sourceBounds_.setY(0);
597     sourceBounds_.setWidth(brect.width());
598     sourceBounds_.setHeight(brect.height());
599 }
600 #endif // QT_CONFIG(opengl)
601 /*
602  * QDeclarativePolygonMapItem Private Implementations
603  */
604 
~QDeclarativePolygonMapItemPrivate()605 QDeclarativePolygonMapItemPrivate::~QDeclarativePolygonMapItemPrivate() {}
606 
~QDeclarativePolygonMapItemPrivateCPU()607 QDeclarativePolygonMapItemPrivateCPU::~QDeclarativePolygonMapItemPrivateCPU() {}
608 
609 #if QT_CONFIG(opengl)
~QDeclarativePolygonMapItemPrivateOpenGL()610 QDeclarativePolygonMapItemPrivateOpenGL::~QDeclarativePolygonMapItemPrivateOpenGL() {}
611 #endif
612 /*
613  * QDeclarativePolygonMapItem Implementation
614  */
615 
616 struct PolygonBackendSelector
617 {
PolygonBackendSelectorPolygonBackendSelector618     PolygonBackendSelector()
619     {
620         backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolygonMapItem::OpenGL : QDeclarativePolygonMapItem::Software;
621     }
622     QDeclarativePolygonMapItem::Backend backend = QDeclarativePolygonMapItem::Software;
623 };
624 
Q_GLOBAL_STATIC(PolygonBackendSelector,mapPolygonBackendSelector)625 Q_GLOBAL_STATIC(PolygonBackendSelector, mapPolygonBackendSelector)
626 
627 QDeclarativePolygonMapItem::QDeclarativePolygonMapItem(QQuickItem *parent)
628 :   QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true),
629     m_updatingGeometry(false)
630   , m_d(new QDeclarativePolygonMapItemPrivateCPU(*this))
631 
632 {
633     // ToDo: handle envvar, and switch implementation.
634     m_itemType = QGeoMap::MapPolygon;
635     m_geopoly = QGeoPolygonEager();
636     setFlag(ItemHasContents, true);
637     QObject::connect(&m_border, SIGNAL(colorChanged(QColor)),
638                      this, SLOT(onLinePropertiesChanged())); // ToDo: fix this, only flag material?
639     QObject::connect(&m_border, SIGNAL(widthChanged(qreal)),
640                      this, SLOT(onLinePropertiesChanged()));
641     setBackend(mapPolygonBackendSelector->backend);
642 }
643 
~QDeclarativePolygonMapItem()644 QDeclarativePolygonMapItem::~QDeclarativePolygonMapItem()
645 {
646 }
647 
648 /*!
649     \qmlpropertygroup Location::MapPolygon::border
650     \qmlproperty int MapPolygon::border.width
651     \qmlproperty color MapPolygon::border.color
652 
653     This property is part of the border property group. The border property
654     group holds the width and color used to draw the border of the polygon.
655 
656     The width is in pixels and is independent of the zoom level of the map.
657 
658     The default values correspond to a black border with a width of 1 pixel.
659     For no line, use a width of 0 or a transparent color.
660 */
661 
border()662 QDeclarativeMapLineProperties *QDeclarativePolygonMapItem::border()
663 {
664     return &m_border;
665 }
666 
667 /*!
668     \qmlproperty MapPolygon.Backend QtLocation::MapPolygon::backend
669 
670     This property holds which backend is in use to render the map item.
671     Valid values are \b MapPolygon.Software and \b{MapPolygon.OpenGL}.
672     The default value is \b{MapPolygon.Software}.
673 
674     \note \b{The release of this API with Qt 5.15 is a Technology Preview}.
675     Ideally, as the OpenGL backends for map items mature, there will be
676     no more need to also offer the legacy software-projection backend.
677     So this property will likely disappear at some later point.
678     To select OpenGL-accelerated item backends without using this property,
679     it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS
680     to \b{1}.
681     Also note that all current OpenGL backends won't work as expected when enabling
682     layers on the individual item, or when running on OpenGL core profiles greater than 2.x.
683 
684     \since 5.15
685 */
backend() const686 QDeclarativePolygonMapItem::Backend QDeclarativePolygonMapItem::backend() const
687 {
688     return m_backend;
689 }
690 
setBackend(QDeclarativePolygonMapItem::Backend b)691 void QDeclarativePolygonMapItem::setBackend(QDeclarativePolygonMapItem::Backend b)
692 {
693     if (b == m_backend)
694         return;
695     m_backend = b;
696     QScopedPointer<QDeclarativePolygonMapItemPrivate> d(
697             (m_backend == Software) ? static_cast<QDeclarativePolygonMapItemPrivate *>(
698                     new QDeclarativePolygonMapItemPrivateCPU(*this))
699 #if QT_CONFIG(opengl)
700                                     : static_cast<QDeclarativePolygonMapItemPrivate *>(
701                                             new QDeclarativePolygonMapItemPrivateOpenGL(*this)));
702 #else
703                                     : nullptr);
704     qFatal("Requested non software rendering backend, but source code is compiled wihtout opengl "
705            "support");
706 #endif
707     m_d.swap(d);
708     m_d->onGeoGeometryChanged();
709     emit backendChanged();
710 }
711 
712 /*!
713     \internal
714 */
setMap(QDeclarativeGeoMap * quickMap,QGeoMap * map)715 void QDeclarativePolygonMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
716 {
717     QDeclarativeGeoMapItemBase::setMap(quickMap,map);
718     if (map)
719         m_d->onMapSet();
720 }
721 
722 /*!
723     \qmlproperty list<coordinate> MapPolygon::path
724 
725     This property holds the ordered list of coordinates which
726     define the polygon.
727     Having less than 3 different coordinates in the path results in undefined behavior.
728 
729     \sa addCoordinate, removeCoordinate
730 */
path() const731 QJSValue QDeclarativePolygonMapItem::path() const
732 {
733     return fromList(this, m_geopoly.path());
734 }
735 
setPath(const QJSValue & value)736 void QDeclarativePolygonMapItem::setPath(const QJSValue &value)
737 {
738     if (!value.isArray())
739         return;
740 
741     QList<QGeoCoordinate> pathList = toList(this, value);
742 
743     // Equivalent to QDeclarativePolylineMapItem::setPathFromGeoList
744     if (m_geopoly.path() == pathList)
745         return;
746 
747     m_geopoly.setPath(pathList);
748 
749     m_d->onGeoGeometryChanged();
750     emit pathChanged();
751 }
752 
753 /*!
754     \qmlmethod void MapPolygon::addCoordinate(coordinate)
755 
756     Adds the specified \a coordinate to the path.
757 
758     \sa removeCoordinate, path
759 */
760 
addCoordinate(const QGeoCoordinate & coordinate)761 void QDeclarativePolygonMapItem::addCoordinate(const QGeoCoordinate &coordinate)
762 {
763     if (!coordinate.isValid())
764         return;
765 
766     m_geopoly.addCoordinate(coordinate);
767     m_d->onGeoGeometryUpdated();
768     emit pathChanged();
769 }
770 
771 /*!
772     \qmlmethod void MapPolygon::removeCoordinate(coordinate)
773 
774     Removes \a coordinate from the path. If there are multiple instances of the
775     same coordinate, the one added last is removed.
776 
777     If \a coordinate is not in the path this method does nothing.
778 
779     \sa addCoordinate, path
780 */
removeCoordinate(const QGeoCoordinate & coordinate)781 void QDeclarativePolygonMapItem::removeCoordinate(const QGeoCoordinate &coordinate)
782 {
783     int length = m_geopoly.path().length();
784     m_geopoly.removeCoordinate(coordinate);
785     if (m_geopoly.path().length() == length)
786         return;
787 
788     m_d->onGeoGeometryChanged();
789     emit pathChanged();
790 }
791 
792 /*!
793     \qmlproperty color MapPolygon::color
794 
795     This property holds the color used to fill the polygon.
796 
797     The default value is transparent.
798 */
799 
color() const800 QColor QDeclarativePolygonMapItem::color() const
801 {
802     return m_color;
803 }
804 
setColor(const QColor & color)805 void QDeclarativePolygonMapItem::setColor(const QColor &color)
806 {
807     if (m_color == color)
808         return;
809 
810     m_color = color;
811     m_dirtyMaterial = true;
812     polishAndUpdate(); // in case color was transparent and now is not or vice versa
813     emit colorChanged(m_color);
814 }
815 
816 /*!
817     \internal
818 */
updateMapItemPaintNode(QSGNode * oldNode,UpdatePaintNodeData * data)819 QSGNode *QDeclarativePolygonMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
820 {
821     return m_d->updateMapItemPaintNode(oldNode, data);
822 }
823 
824 /*!
825     \internal
826 */
updatePolish()827 void QDeclarativePolygonMapItem::updatePolish()
828 {
829     if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
830         return;
831     m_d->updatePolish();
832 }
833 
setMaterialDirty()834 void QDeclarativePolygonMapItem::setMaterialDirty()
835 {
836     m_dirtyMaterial = true;
837     update();
838 }
839 
markSourceDirtyAndUpdate()840 void QDeclarativePolygonMapItem::markSourceDirtyAndUpdate()
841 {
842     m_d->markSourceDirtyAndUpdate();
843 }
844 
onLinePropertiesChanged()845 void QDeclarativePolygonMapItem::onLinePropertiesChanged()
846 {
847     m_d->onLinePropertiesChanged();
848 }
849 
850 /*!
851     \internal
852 */
afterViewportChanged(const QGeoMapViewportChangeEvent & event)853 void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
854 {
855     if (event.mapSize.isEmpty())
856         return;
857 
858     m_d->afterViewportChanged();
859 }
860 
861 /*!
862     \internal
863 */
contains(const QPointF & point) const864 bool QDeclarativePolygonMapItem::contains(const QPointF &point) const
865 {
866     return m_d->contains(point);
867 }
868 
geoShape() const869 const QGeoShape &QDeclarativePolygonMapItem::geoShape() const
870 {
871     return m_geopoly;
872 }
873 
setGeoShape(const QGeoShape & shape)874 void QDeclarativePolygonMapItem::setGeoShape(const QGeoShape &shape)
875 {
876     if (shape == m_geopoly)
877         return;
878 
879     m_geopoly = QGeoPolygonEager(shape);
880     m_d->onGeoGeometryChanged();
881     emit pathChanged();
882 }
883 
884 /*!
885     \internal
886 */
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)887 void QDeclarativePolygonMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
888 {
889     if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopoly.isValid() || m_updatingGeometry) {
890         QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry);
891         return;
892     }
893     // TODO: change the algorithm to preserve the distances and size!
894     QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(newGeometry.center()), false);
895     QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(oldGeometry.center()), false);
896     if (!newCenter.isValid() || !oldCenter.isValid())
897         return;
898     double offsetLongi = newCenter.longitude() - oldCenter.longitude();
899     double offsetLati = newCenter.latitude() - oldCenter.latitude();
900     if (offsetLati == 0.0 && offsetLongi == 0.0)
901         return;
902 
903     m_geopoly.translate(offsetLati, offsetLongi);
904     m_d->onGeoGeometryChanged();
905     emit pathChanged();
906 
907     // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested
908     // call to this function.
909 }
910 
911 //////////////////////////////////////////////////////////////////////
912 
913 #if QT_CONFIG(opengl)
createShader() const914 QSGMaterialShader *MapPolygonMaterial::createShader() const
915 {
916     return new MapPolygonShader();
917 }
918 
compare(const QSGMaterial * other) const919 int MapPolygonMaterial::compare(const QSGMaterial *other) const
920 {
921     const MapPolygonMaterial &o = *static_cast<const MapPolygonMaterial *>(other);
922     if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset)
923         return QSGFlatColorMaterial::compare(other);
924     return -1;
925 }
926 
type() const927 QSGMaterialType *MapPolygonMaterial::type() const
928 {
929     static QSGMaterialType type;
930     return &type;
931 }
932 #endif
933 
MapPolygonNode()934 MapPolygonNode::MapPolygonNode() :
935     border_(new MapPolylineNode()),
936     geometry_(QSGGeometry::defaultAttributes_Point2D(), 0)
937 {
938     geometry_.setDrawingMode(QSGGeometry::DrawTriangles);
939     QSGGeometryNode::setMaterial(&fill_material_);
940     QSGGeometryNode::setGeometry(&geometry_);
941 
942     appendChildNode(border_);
943 }
944 
~MapPolygonNode()945 MapPolygonNode::~MapPolygonNode()
946 {
947 }
948 
949 /*!
950     \internal
951 */
update(const QColor & fillColor,const QColor & borderColor,const QGeoMapItemGeometry * fillShape,const QGeoMapItemGeometry * borderShape)952 void MapPolygonNode::update(const QColor &fillColor, const QColor &borderColor,
953                             const QGeoMapItemGeometry *fillShape,
954                             const QGeoMapItemGeometry *borderShape)
955 {
956     /* Do the border update first */
957     border_->update(borderColor, borderShape);
958 
959     /* If we have neither fill nor border with valid points, block the whole
960      * tree. We can't just block the fill without blocking the border too, so
961      * we're a little conservative here (maybe at the expense of rendering
962      * accuracy) */
963     if (fillShape->size() == 0 && borderShape->size() == 0) {
964             setSubtreeBlocked(true);
965             return;
966     }
967     setSubtreeBlocked(false);
968 
969 
970     // TODO: do this only if the geometry has changed!!
971     // No need to do this every frame.
972     // Then benchmark the difference!
973     QSGGeometry *fill = QSGGeometryNode::geometry();
974     fillShape->allocateAndFill(fill);
975     markDirty(DirtyGeometry);
976 
977     if (fillColor != fill_material_.color()) {
978         fill_material_.setColor(fillColor);
979         setMaterial(&fill_material_);
980         markDirty(DirtyMaterial);
981     }
982 }
983 
984 #if QT_CONFIG(opengl)
MapPolygonNodeGL()985 MapPolygonNodeGL::MapPolygonNodeGL() :
986     //fill_material_(this),
987     fill_material_(),
988     geometry_(QSGGeometry::defaultAttributes_Point2D(), 0)
989 {
990     geometry_.setDrawingMode(QSGGeometry::DrawTriangles);
991     QSGGeometryNode::setMaterial(&fill_material_);
992     QSGGeometryNode::setGeometry(&geometry_);
993 }
994 
~MapPolygonNodeGL()995 MapPolygonNodeGL::~MapPolygonNodeGL()
996 {
997 }
998 
999 /*!
1000     \internal
1001 */
update(const QColor & fillColor,const QGeoMapPolygonGeometryOpenGL * fillShape,const QMatrix4x4 & geoProjection,const QDoubleVector3D & center)1002 void MapPolygonNodeGL::update(const QColor &fillColor,
1003                             const QGeoMapPolygonGeometryOpenGL *fillShape,
1004                             const QMatrix4x4 &geoProjection,
1005                             const QDoubleVector3D &center)
1006 {
1007     if (fillShape->m_screenIndices.size() < 3 || fillColor.alpha() == 0) {
1008         setSubtreeBlocked(true);
1009         return;
1010     }
1011     setSubtreeBlocked(false);
1012 
1013     QSGGeometry *fill = QSGGeometryNode::geometry();
1014     if (fillShape->m_dataChanged || !fill->vertexCount()) {
1015         fillShape->allocateAndFillPolygon(fill);
1016         markDirty(DirtyGeometry);
1017         fillShape->m_dataChanged = false;
1018     }
1019 
1020     //if (fillColor != fill_material_.color()) // Any point in optimizing this?
1021     {
1022         fill_material_.setColor(fillColor);
1023         fill_material_.setGeoProjection(geoProjection);
1024         fill_material_.setCenter(center);
1025         fill_material_.setWrapOffset(fillShape->m_wrapOffset - 1);
1026         setMaterial(&fill_material_);
1027         markDirty(DirtyMaterial);
1028     }
1029 }
1030 
MapPolygonShader()1031 MapPolygonShader::MapPolygonShader() : QSGMaterialShader(*new QSGMaterialShaderPrivate)
1032 {
1033 
1034 }
1035 
updateState(const QSGMaterialShader::RenderState & state,QSGMaterial * newEffect,QSGMaterial * oldEffect)1036 void MapPolygonShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
1037 {
1038     Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type());
1039     MapPolygonMaterial *oldMaterial = static_cast<MapPolygonMaterial *>(oldEffect);
1040     MapPolygonMaterial *newMaterial = static_cast<MapPolygonMaterial *>(newEffect);
1041 
1042     const QColor &c = newMaterial->color();
1043     const QMatrix4x4 &geoProjection = newMaterial->geoProjection();
1044     const QDoubleVector3D &center = newMaterial->center();
1045 
1046     QVector3D vecCenter, vecCenter_lowpart;
1047     for (int i = 0; i < 3; i++)
1048         QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]);
1049 
1050     if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) {
1051         float opacity = state.opacity() * c.alphaF();
1052         QVector4D v(c.redF() * opacity,
1053                     c.greenF() *  opacity,
1054                     c.blueF() * opacity,
1055                     opacity);
1056         program()->setUniformValue(m_color_id, v);
1057     }
1058 
1059     if (state.isMatrixDirty())
1060     {
1061         program()->setUniformValue(m_matrix_id, state.projectionMatrix());
1062     }
1063 
1064     program()->setUniformValue(m_mapProjection_id, geoProjection);
1065 
1066     program()->setUniformValue(m_center_id, vecCenter);
1067     program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart);
1068     program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset()));
1069 }
1070 #endif // QT_CONFIG(opengl)
1071 QT_END_NAMESPACE
1072