1/****************************************************************************
2**
3** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com>
4** Copyright (C) 2020 The Qt Company Ltd.
5** Contact: http://www.qt.io/licensing/
6**
7** This file is part of the QtLocation module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL3$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see http://www.qt.io/terms-conditions. For further
16** information use the contact form at http://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPLv3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or later as published by the Free
29** Software Foundation and appearing in the file LICENSE.GPL included in
30** the packaging of this file. Please review the following information to
31** ensure the GNU General Public License version 2.0 requirements will be
32** met: http://www.gnu.org/licenses/gpl-2.0.html.
33**
34** $QT_END_LICENSE$
35**
36****************************************************************************/
37
38#ifndef QDECLARATIVECIRCLEMAPITEM_P_P_H
39#define QDECLARATIVECIRCLEMAPITEM_P_P_H
40
41//
42// W A R N I N G
43// -------------
44//
45// This file is not part of the Qt API. It exists purely as an
46// implementation detail. This header file may change from version to
47// version without notice, or even be removed.
48//
49// We mean it.
50//
51
52#include <QtLocation/private/qlocationglobal_p.h>
53#include <QtLocation/private/qdeclarativepolygonmapitem_p_p.h>
54#include <QtLocation/private/qdeclarativecirclemapitem_p.h>
55
56QT_BEGIN_NAMESPACE
57
58class Q_LOCATION_PRIVATE_EXPORT QGeoMapCircleGeometry : public QGeoMapPolygonGeometry
59{
60public:
61 QGeoMapCircleGeometry();
62
63 void updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map);
64};
65
66class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivate
67{
68public:
69 static const int CircleSamples = 128; // ToDo: make this radius && ZL dependent?
70
71 QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItem &circle) : m_circle(circle)
72 {
73
74 }
75 QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItemPrivate &other) : m_circle(other.m_circle)
76 {
77 }
78
79 virtual ~QDeclarativeCircleMapItemPrivate();
80 virtual void onLinePropertiesChanged() = 0;
81 virtual void markSourceDirtyAndUpdate() = 0;
82 virtual void onMapSet() = 0;
83 virtual void onGeoGeometryChanged() = 0;
84 virtual void onItemGeometryChanged() = 0;
85 virtual void updatePolish() = 0;
86 virtual void afterViewportChanged() = 0;
87 virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) = 0;
88 virtual bool contains(const QPointF &point) const = 0;
89
90 void updateCirclePath()
91 {
92 if (!m_circle.map() || m_circle.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
93 return;
94
95 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection());
96 QList<QGeoCoordinate> path;
97 calculatePeripheralPoints(path, center: m_circle.center(), distance: m_circle.radius(), steps: CircleSamples, leftBound&: m_leftBound);
98 m_circlePath.clear();
99 for (const QGeoCoordinate &c : path)
100 m_circlePath << p.geoToMapProjection(coordinate: c);
101 }
102
103 static bool crossEarthPole(const QGeoCoordinate &center, qreal distance);
104
105 static bool preserveCircleGeometry(QList<QDoubleVector2D> &path, const QGeoCoordinate &center,
106 qreal distance, const QGeoProjectionWebMercator &p);
107 static void updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate &center,
108 qreal distance, const QGeoProjectionWebMercator &p);
109
110 static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate &center,
111 qreal distance, int steps, QGeoCoordinate &leftBound);
112
113 QDeclarativeCircleMapItem &m_circle;
114 QList<QDoubleVector2D> m_circlePath;
115 QGeoCoordinate m_leftBound;
116};
117
118class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateCPU: public QDeclarativeCircleMapItemPrivate
119{
120public:
121
122 QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle)
123 {
124 }
125
126 QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItemPrivate &other)
127 : QDeclarativeCircleMapItemPrivate(other)
128 {
129 }
130
131 ~QDeclarativeCircleMapItemPrivateCPU() override;
132
133 void onLinePropertiesChanged() override
134 {
135 // mark dirty just in case we're a width change
136 markSourceDirtyAndUpdate();
137 }
138 void markSourceDirtyAndUpdate() override
139 {
140 // preserveGeometry is cleared in updateMapItemPaintNode
141 m_geometry.markSourceDirty();
142 m_borderGeometry.markSourceDirty();
143 m_circle.polishAndUpdate();
144 }
145 void onMapSet() override
146 {
147 updateCirclePath();
148 markSourceDirtyAndUpdate();
149 }
150 void onGeoGeometryChanged() override
151 {
152 updateCirclePath();
153 markSourceDirtyAndUpdate();
154 }
155 void onItemGeometryChanged() override
156 {
157 onGeoGeometryChanged();
158 }
159 void afterViewportChanged() override
160 {
161 markSourceDirtyAndUpdate();
162 }
163 void updatePolish() override
164 {
165 if (!m_circle.m_circle.isValid()) {
166 m_geometry.clear();
167 m_borderGeometry.clear();
168 m_circle.setWidth(0);
169 m_circle.setHeight(0);
170 return;
171 }
172
173 const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection());
174 QScopedValueRollback<bool> rollback(m_circle.m_updatingGeometry);
175 m_circle.m_updatingGeometry = true;
176
177 QList<QDoubleVector2D> circlePath = m_circlePath;
178
179 int pathCount = circlePath.size();
180 bool preserve = preserveCircleGeometry(path&: circlePath, center: m_circle.m_circle.center(), distance: m_circle.m_circle.radius(), p);
181 // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft());
182 // to fix QTBUG-62154
183 m_geometry.setPreserveGeometry(value: true, geoLeftBound: m_leftBound); // to set the geoLeftBound_
184 m_geometry.setPreserveGeometry(value: preserve, geoLeftBound: m_leftBound);
185
186 bool invertedCircle = false;
187 if (crossEarthPole(center: m_circle.m_circle.center(), distance: m_circle.m_circle.radius()) && circlePath.size() == pathCount) {
188 m_geometry.updateScreenPointsInvert(circlePath, map: *m_circle.map()); // invert fill area for really huge circles
189 invertedCircle = true;
190 } else {
191 m_geometry.updateSourcePoints(map: *m_circle.map(), path: circlePath);
192 m_geometry.updateScreenPoints(map: *m_circle.map(), strokeWidth: m_circle.m_border.width());
193 }
194
195 m_borderGeometry.clear();
196 QList<QGeoMapItemGeometry *> geoms;
197 geoms << &m_geometry;
198
199 if (m_circle.m_border.color() != Qt::transparent && m_circle.m_border.width() > 0) {
200 QList<QDoubleVector2D> closedPath = circlePath;
201 closedPath << closedPath.first();
202
203 if (invertedCircle) {
204 closedPath = m_circlePath;
205 closedPath << closedPath.first();
206 std::reverse(first: closedPath.begin(), last: closedPath.end());
207 }
208
209 m_borderGeometry.setPreserveGeometry(value: true, geoLeftBound: m_leftBound);
210 m_borderGeometry.setPreserveGeometry(value: preserve, geoLeftBound: m_leftBound);
211
212 // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail.
213 const QGeoCoordinate &geometryOrigin = m_geometry.origin();
214
215 m_borderGeometry.srcPoints_.clear();
216 m_borderGeometry.srcPointTypes_.clear();
217
218 QDoubleVector2D borderLeftBoundWrapped;
219 QList<QList<QDoubleVector2D > > clippedPaths = m_borderGeometry.clipPath(map: *m_circle.map(), path: closedPath, leftBoundWrapped&: borderLeftBoundWrapped);
220 if (clippedPaths.size()) {
221 borderLeftBoundWrapped = p.geoToWrappedMapProjection(coordinate: geometryOrigin);
222 m_borderGeometry.pathToScreen(map: *m_circle.map(), clippedPaths, leftBoundWrapped: borderLeftBoundWrapped);
223 m_borderGeometry.updateScreenPoints(map: *m_circle.map(), strokeWidth: m_circle.m_border.width());
224 geoms << &m_borderGeometry;
225 } else {
226 m_borderGeometry.clear();
227 }
228 }
229
230 QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
231
232 if (invertedCircle || !preserve) {
233 m_circle.setWidth(combined.width());
234 m_circle.setHeight(combined.height());
235 } else {
236 m_circle.setWidth(combined.width() + 2 * m_circle.m_border.width()); // ToDo: Fix this!
237 m_circle.setHeight(combined.height() + 2 * m_circle.m_border.width());
238 }
239
240 // No offsetting here, even in normal case, because first point offset is already translated
241 m_circle.setPositionOnMap(coordinate: m_geometry.origin(), offset: m_geometry.firstPointOffset());
242 }
243
244 QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override
245 {
246 Q_UNUSED(data);
247 if (!m_node || !oldNode) { // Apparently the QSG might delete the nodes if they become invisible
248 m_node = new MapPolygonNode();
249 if (oldNode) {
250 delete oldNode;
251 oldNode = nullptr;
252 }
253 } else {
254 m_node = static_cast<MapPolygonNode *>(oldNode);
255 }
256
257 //TODO: update only material
258 if (m_geometry.isScreenDirty() || m_borderGeometry.isScreenDirty() || m_circle.m_dirtyMaterial) {
259 m_node->update(fillColor: m_circle.m_color, borderColor: m_circle.m_border.color(), fillShape: &m_geometry, borderShape: &m_borderGeometry);
260 m_geometry.setPreserveGeometry(value: false);
261 m_borderGeometry.setPreserveGeometry(value: false);
262 m_geometry.markClean();
263 m_borderGeometry.markClean();
264 m_circle.m_dirtyMaterial = false;
265 }
266 return m_node;
267 }
268 bool contains(const QPointF &point) const override
269 {
270 return (m_geometry.contains(screenPoint: point) || m_borderGeometry.contains(point));
271 }
272
273 QGeoMapCircleGeometry m_geometry;
274 QGeoMapPolylineGeometry m_borderGeometry;
275 MapPolygonNode *m_node = nullptr;
276};
277
278#if QT_CONFIG(opengl)
279class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateOpenGL: public QDeclarativeCircleMapItemPrivate
280{
281public:
282 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle)
283 {
284 }
285
286 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate &other)
287 : QDeclarativeCircleMapItemPrivate(other)
288 {
289 }
290
291 ~QDeclarativeCircleMapItemPrivateOpenGL() override;
292
293 void onLinePropertiesChanged() override
294 {
295 m_circle.m_dirtyMaterial = true;
296 afterViewportChanged();
297 }
298 void markScreenDirtyAndUpdate()
299 {
300 // preserveGeometry is cleared in updateMapItemPaintNode
301 m_geometry.markScreenDirty();
302 m_borderGeometry.markScreenDirty();
303 m_circle.polishAndUpdate();
304 }
305 virtual void markSourceDirtyAndUpdate() override
306 {
307 updateCirclePath();
308 preserveGeometry();
309 m_geometry.markSourceDirty();
310 m_borderGeometry.markSourceDirty();
311 m_circle.polishAndUpdate();
312 }
313 void preserveGeometry()
314 {
315 m_geometry.setPreserveGeometry(value: true, geoLeftBound: m_leftBound);
316 m_borderGeometry.setPreserveGeometry(value: true, geoLeftBound: m_leftBound);
317 }
318 virtual void onMapSet() override
319 {
320 markSourceDirtyAndUpdate();
321 }
322 virtual void onGeoGeometryChanged() override
323 {
324
325 markSourceDirtyAndUpdate();
326 }
327 virtual void onItemGeometryChanged() override
328 {
329 onGeoGeometryChanged();
330 }
331 virtual void afterViewportChanged() override
332 {
333 preserveGeometry();
334 markScreenDirtyAndUpdate();
335 }
336 virtual void updatePolish() override
337 {
338 if (m_circle.m_circle.isEmpty()) {
339 m_geometry.clear();
340 m_borderGeometry.clear();
341 m_circle.setWidth(0);
342 m_circle.setHeight(0);
343 return;
344 }
345
346 QScopedValueRollback<bool> rollback(m_circle.m_updatingGeometry);
347 m_circle.m_updatingGeometry = true;
348 const qreal lineWidth = m_circle.m_border.width();
349 const QColor &lineColor = m_circle.m_border.color();
350 const QColor &fillColor = m_circle.color();
351 if (fillColor.alpha() != 0) {
352 m_geometry.updateSourcePoints(map: *m_circle.map(), path: m_circlePath);
353 m_geometry.markScreenDirty();
354 m_geometry.updateScreenPoints(map: *m_circle.map(), strokeWidth: lineWidth, strokeColor: lineColor);
355 } else {
356 m_geometry.clearBounds();
357 }
358
359 QGeoMapItemGeometry * geom = &m_geometry;
360 m_borderGeometry.clearScreen();
361 if (lineColor.alpha() != 0 && lineWidth > 0) {
362 m_borderGeometry.updateSourcePoints(map: *m_circle.map(), circle: m_circle.m_circle);
363 m_borderGeometry.markScreenDirty();
364 m_borderGeometry.updateScreenPoints(map: *m_circle.map(), strokeWidth: lineWidth);
365 geom = &m_borderGeometry;
366 }
367 m_circle.setWidth(geom->sourceBoundingBox().width());
368 m_circle.setHeight(geom->sourceBoundingBox().height());
369 m_circle.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5));
370 }
371
372 virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override
373 {
374 Q_UNUSED(data);
375
376 if (!m_rootNode || !oldNode) {
377 m_rootNode = new QDeclarativePolygonMapItemPrivateOpenGL::RootNode();
378 m_node = new MapPolygonNodeGL();
379 m_rootNode->appendChildNode(node: m_node);
380 m_polylinenode = new MapPolylineNodeOpenGLExtruded();
381 m_rootNode->appendChildNode(node: m_polylinenode);
382 m_rootNode->markDirty(bits: QSGNode::DirtyNodeAdded);
383 if (oldNode)
384 delete oldNode;
385 } else {
386 m_rootNode = static_cast<QDeclarativePolygonMapItemPrivateOpenGL::RootNode *>(oldNode);
387 }
388
389 const QGeoMap *map = m_circle.map();
390 const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform();
391 const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator();
392
393 if (m_borderGeometry.isScreenDirty()) {
394 /* Do the border update first */
395 m_polylinenode->update(fillColor: m_circle.m_border.color(),
396 lineWidth: float(m_circle.m_border.width()),
397 shape: &m_borderGeometry,
398 geoProjection: combinedMatrix,
399 center: cameraCenter,
400 capStyle: Qt::SquareCap,
401 closed: true,
402 zoom: 30); // No LOD for circles
403 m_borderGeometry.setPreserveGeometry(value: false);
404 m_borderGeometry.markClean();
405 } else {
406 m_polylinenode->setSubtreeBlocked(true);
407 }
408 if (m_geometry.isScreenDirty()) {
409 m_node->update(fillColor: m_circle.m_color,
410 fillShape: &m_geometry,
411 geoProjection: combinedMatrix,
412 center: cameraCenter);
413 m_geometry.setPreserveGeometry(value: false);
414 m_geometry.markClean();
415 } else {
416 m_node->setSubtreeBlocked(true);
417 }
418
419 m_rootNode->setSubtreeBlocked(false);
420 return m_rootNode;
421 }
422 virtual bool contains(const QPointF &point) const override
423 {
424 const qreal lineWidth = m_circle.m_border.width();
425 const QColor &lineColor = m_circle.m_border.color();
426 const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox();
427 if (bounds.contains(p: point)) {
428 QDeclarativeGeoMap *m = m_circle.quickMap();
429 if (m) {
430 const QGeoCoordinate crd = m->toCoordinate(position: m->mapFromItem(item: &m_circle, point));
431 return m_circle.m_circle.contains(coordinate: crd) || m_borderGeometry.contains(point: m_circle.mapToItem(item: m_circle.quickMap(), point),
432 lineWidth: m_circle.border()->width(),
433 p: static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection()));
434 } else {
435 return true;
436 }
437 }
438 return false;
439 }
440
441 QGeoMapPolygonGeometryOpenGL m_geometry;
442 QGeoMapPolylineGeometryOpenGL m_borderGeometry;
443 QDeclarativePolygonMapItemPrivateOpenGL::RootNode *m_rootNode = nullptr;
444 MapPolygonNodeGL *m_node = nullptr;
445 MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr;
446};
447#endif // QT_CONFIG(opengl)
448
449QT_END_NAMESPACE
450
451#endif // QDECLARATIVECIRCLEMAPITEM_P_P_H
452

source code of qtlocation/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h