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
68QT_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
157QGeoMapPolygonGeometry::QGeoMapPolygonGeometry()
158: assumeSimple_(false)
159{
160}
161
162/*!
163 \internal
164*/
165void 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(projection: p.geoToMapProjection(coordinate: geoLeftBound_));
178 if (preserveGeometry_)
179 unwrapBelowX = leftBoundWrapped.x();
180
181 QList<QDoubleVector2D> wrappedPath;
182 wrappedPath.reserve(alloc: 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(projection: 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(d: wrappedProjection.x()) || !qIsFinite(d: 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(t: 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(path: QClipperUtils::qListToPath(list: wrappedPath), closed: true);
214 clipper.addClipPolygon(path: QClipperUtils::qListToPath(list: visibleRegion));
215 Paths res = clipper.execute(op: c2t::clip2tri::Intersection, subjFillType: QtClipperLib::pftEvenOdd, clipFillType: QtClipperLib::pftEvenOdd);
216 clippedPaths = QClipperUtils::pathsToQList(paths: 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(d: 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(a: wrappedLeftBound.x(), b: lb.x()));
232 leftBoundWrapped = lb;
233 srcOrigin_ = p.mapProjectionToGeo(projection: p.unwrapMapProjection(wrappedProjection: lb));
234 } else {
235 clippedPaths.append(t: wrappedPath);
236 }
237
238 // 3)
239 QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(wrappedProjection: 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(wrappedProjection: path.at(i));
244 point = point - origin; // (0,0) if point == geoLeftBound_
245
246 if (i == 0) {
247 srcPath_.moveTo(p: point.toPointF());
248 lastAddedPoint = point;
249 } else {
250 if ((point - lastAddedPoint).manhattanLength() > 3 ||
251 i == path.size() - 1) {
252 srcPath_.lineTo(p: 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*/
269void 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(dx: -bb.left(), dy: -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(x: 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(t: e.x - poly.front()[0]) < 0.1
308 && qAbs(t: e.y - poly.front()[1]) < 0.1)) {
309 Point p = {._M_elems: { e.x, e.y }};
310 poly.push_back( x: p );
311 } else if (e.isLineTo()) {
312 Point p = {._M_elems: { e.x, e.y }};
313 poly.push_back( x: p );
314 } else {
315 qWarning(msg: "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>(poly: 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(offset: QPointF(strokeWidth, strokeWidth));
335}
336
337#if QT_CONFIG(opengl)
338QGeoMapPolygonGeometryOpenGL::QGeoMapPolygonGeometryOpenGL(){
339}
340
341void QGeoMapPolygonGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QList<QDoubleVector2D> &path)
342{
343 QList<QGeoCoordinate> geopath;
344 for (const auto &c: path)
345 geopath.append(t: QWebMercator::mercatorToCoord(mercator: c));
346 updateSourcePoints(map, perimeter: geopath);
347}
348#endif
349
350// wrapPath always preserves the geometry
351// This one handles holes
352static 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(coordinate: c);
364 } else {
365 for (const QGeoCoordinate &c : poly.holePath(index: i-1))
366 path << p.geoToMapProjection(coordinate: c);
367 }
368 paths.append(t: path);
369 }
370
371 const QDoubleVector2D leftBound = p.geoToMapProjection(coordinate: 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(i: 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(d: coord.x()) || !qIsFinite(d: 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(t: coord);
394 }
395 wrappedPaths.append(t: wrappedPath);
396 }
397
398 if (leftBoundWrapped)
399 *leftBoundWrapped = leftBound;
400}
401
402static 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 = {._M_elems: { v.x(), v.y() }};
420 poly.push_back( x: pt );
421 }
422 polygon.push_back(x: poly);
423 }
424
425 std::vector<N> indices = qt_mapbox::earcut<N>(poly: polygon);
426
427 for (const auto &i: indices)
428 screenIndices << quint32(i);
429}
430
431static 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 = {._M_elems: { v.x(), v.y() }};
447 poly.push_back( x: pt );
448 }
449 polygon.push_back(x: poly);
450
451 std::vector<N> indices = qt_mapbox::earcut<N>(poly: 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
462void 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: geoLeftBound_, p,
477 wrappedPath, leftBoundWrapped: &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(perimeter: bbox.path(), geoLeftBound: bbox.boundingGeoRectangle().topLeft(), p,
483 wrappedPath&: wrappedBbox, wrappedPathMinus1&: wrappedBboxMinus1, wrappedPathPlus1&: wrappedBboxPlus1, leftBoundWrapped: &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, screenVertices&: m_screenVertices, screenIndices&: m_screenIndices);
490
491 m_wrappedPolygons.resize(asize: 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
498void 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: geoLeftBound_, p,
512 wrappedPaths&: wrappedPath, leftBoundWrapped: &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(perimeter: bbox.path(), geoLeftBound: bbox.boundingGeoRectangle().topLeft(), p,
518 wrappedPath&: wrappedBbox, wrappedPathMinus1&: wrappedBboxMinus1, wrappedPathPlus1&: wrappedBboxPlus1, leftBoundWrapped: &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(wrappedPaths: wrappedPath, screenVertices&: m_screenVertices, screenIndices&: m_screenIndices);
525 m_wrappedPolygons.resize(asize: 3);
526 m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1;
527 m_wrappedPolygons[1].wrappedBboxes = wrappedBbox;
528 m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1;
529}
530
531void 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*/
542void 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(coordinate: srcOrigin_);
552 m_wrapOffset = p.projectionWrapFactor(projection: 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
566void 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(i: 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(wrappedPath: wrappedBbox, p, clippedPaths&: clippedBbox, leftBoundWrapped: &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: clippedBbox.first(), p, projectedBbox&: 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
605QDeclarativePolygonMapItemPrivate::~QDeclarativePolygonMapItemPrivate() {}
606
607QDeclarativePolygonMapItemPrivateCPU::~QDeclarativePolygonMapItemPrivateCPU() {}
608
609#if QT_CONFIG(opengl)
610QDeclarativePolygonMapItemPrivateOpenGL::~QDeclarativePolygonMapItemPrivateOpenGL() {}
611#endif
612/*
613 * QDeclarativePolygonMapItem Implementation
614 */
615
616struct PolygonBackendSelector
617{
618 PolygonBackendSelector()
619 {
620 backend = (qgetenv(varName: "QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolygonMapItem::OpenGL : QDeclarativePolygonMapItem::Software;
621 }
622 QDeclarativePolygonMapItem::Backend backend = QDeclarativePolygonMapItem::Software;
623};
624
625Q_GLOBAL_STATIC(PolygonBackendSelector, mapPolygonBackendSelector)
626
627QDeclarativePolygonMapItem::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(flag: ItemHasContents, enabled: true);
637 QObject::connect(sender: &m_border, SIGNAL(colorChanged(QColor)),
638 receiver: this, SLOT(onLinePropertiesChanged())); // ToDo: fix this, only flag material?
639 QObject::connect(sender: &m_border, SIGNAL(widthChanged(qreal)),
640 receiver: this, SLOT(onLinePropertiesChanged()));
641 setBackend(mapPolygonBackendSelector->backend);
642}
643
644QDeclarativePolygonMapItem::~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
662QDeclarativeMapLineProperties *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*/
686QDeclarativePolygonMapItem::Backend QDeclarativePolygonMapItem::backend() const
687{
688 return m_backend;
689}
690
691void 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(other&: d);
708 m_d->onGeoGeometryChanged();
709 emit backendChanged();
710}
711
712/*!
713 \internal
714*/
715void 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*/
731QJSValue QDeclarativePolygonMapItem::path() const
732{
733 return fromList(object: this, list: m_geopoly.path());
734}
735
736void QDeclarativePolygonMapItem::setPath(const QJSValue &value)
737{
738 if (!value.isArray())
739 return;
740
741 QList<QGeoCoordinate> pathList = toList(object: 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
761void 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*/
781void 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
800QColor QDeclarativePolygonMapItem::color() const
801{
802 return m_color;
803}
804
805void 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(color: m_color);
814}
815
816/*!
817 \internal
818*/
819QSGNode *QDeclarativePolygonMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
820{
821 return m_d->updateMapItemPaintNode(oldNode, data);
822}
823
824/*!
825 \internal
826*/
827void QDeclarativePolygonMapItem::updatePolish()
828{
829 if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
830 return;
831 m_d->updatePolish();
832}
833
834void QDeclarativePolygonMapItem::setMaterialDirty()
835{
836 m_dirtyMaterial = true;
837 update();
838}
839
840void QDeclarativePolygonMapItem::markSourceDirtyAndUpdate()
841{
842 m_d->markSourceDirtyAndUpdate();
843}
844
845void QDeclarativePolygonMapItem::onLinePropertiesChanged()
846{
847 m_d->onLinePropertiesChanged();
848}
849
850/*!
851 \internal
852*/
853void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
854{
855 if (event.mapSize.isEmpty())
856 return;
857
858 m_d->afterViewportChanged();
859}
860
861/*!
862 \internal
863*/
864bool QDeclarativePolygonMapItem::contains(const QPointF &point) const
865{
866 return m_d->contains(point);
867}
868
869const QGeoShape &QDeclarativePolygonMapItem::geoShape() const
870{
871 return m_geopoly;
872}
873
874void 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*/
887void 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(pos: QDoubleVector2D(newGeometry.center()), clipToViewport: false);
895 QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(pos: QDoubleVector2D(oldGeometry.center()), clipToViewport: 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(degreesLatitude: offsetLati, degreesLongitude: 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)
914QSGMaterialShader *MapPolygonMaterial::createShader() const
915{
916 return new MapPolygonShader();
917}
918
919int 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
927QSGMaterialType *MapPolygonMaterial::type() const
928{
929 static QSGMaterialType type;
930 return &type;
931}
932#endif
933
934MapPolygonNode::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(node: border_);
943}
944
945MapPolygonNode::~MapPolygonNode()
946{
947}
948
949/*!
950 \internal
951*/
952void 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(fillColor: borderColor, shape: 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(geom: fill);
975 markDirty(bits: DirtyGeometry);
976
977 if (fillColor != fill_material_.color()) {
978 fill_material_.setColor(fillColor);
979 setMaterial(&fill_material_);
980 markDirty(bits: DirtyMaterial);
981 }
982}
983
984#if QT_CONFIG(opengl)
985MapPolygonNodeGL::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
995MapPolygonNodeGL::~MapPolygonNodeGL()
996{
997}
998
999/*!
1000 \internal
1001*/
1002void 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(geom: fill);
1016 markDirty(bits: 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(bits: DirtyMaterial);
1028 }
1029}
1030
1031MapPolygonShader::MapPolygonShader() : QSGMaterialShader(*new QSGMaterialShaderPrivate)
1032{
1033
1034}
1035
1036void 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(input: center.get(i), hipart: &vecCenter[i], lopart: &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(location: m_color_id, value: v);
1057 }
1058
1059 if (state.isMatrixDirty())
1060 {
1061 program()->setUniformValue(location: m_matrix_id, value: state.projectionMatrix());
1062 }
1063
1064 program()->setUniformValue(location: m_mapProjection_id, value: geoProjection);
1065
1066 program()->setUniformValue(location: m_center_id, value: vecCenter);
1067 program()->setUniformValue(location: m_center_lowpart_id, value: vecCenter_lowpart);
1068 program()->setUniformValue(location: m_wrapOffset_id, value: float(newMaterial->wrapOffset()));
1069}
1070#endif // QT_CONFIG(opengl)
1071QT_END_NAMESPACE
1072

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