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 QDECLARATIVERECTANGLEMAPITEM_P_P_H |
39 | #define QDECLARATIVERECTANGLEMAPITEM_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/qdeclarativerectanglemapitem_p.h> |
55 | #include <QtPositioning/private/qwebmercator_p.h> |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItemPrivate |
60 | { |
61 | public: |
62 | QDeclarativeRectangleMapItemPrivate(QDeclarativeRectangleMapItem &rect) : m_rect(rect) |
63 | { |
64 | |
65 | } |
66 | QDeclarativeRectangleMapItemPrivate(QDeclarativeRectangleMapItemPrivate &other) : m_rect(other.m_rect) |
67 | { |
68 | } |
69 | |
70 | virtual ~QDeclarativeRectangleMapItemPrivate(); |
71 | virtual void onLinePropertiesChanged() = 0; |
72 | virtual void markSourceDirtyAndUpdate() = 0; |
73 | virtual void onMapSet() = 0; |
74 | virtual void onGeoGeometryChanged() = 0; |
75 | virtual void onItemGeometryChanged() = 0; |
76 | virtual void updatePolish() = 0; |
77 | virtual void afterViewportChanged() = 0; |
78 | virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) = 0; |
79 | virtual bool contains(const QPointF &point) const = 0; |
80 | |
81 | QDeclarativeRectangleMapItem &m_rect; |
82 | }; |
83 | |
84 | class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItemPrivateCPU: public QDeclarativeRectangleMapItemPrivate |
85 | { |
86 | public: |
87 | QDeclarativeRectangleMapItemPrivateCPU(QDeclarativeRectangleMapItem &rect) : QDeclarativeRectangleMapItemPrivate(rect) |
88 | { |
89 | } |
90 | |
91 | QDeclarativeRectangleMapItemPrivateCPU(QDeclarativeRectangleMapItemPrivate &other) |
92 | : QDeclarativeRectangleMapItemPrivate(other) |
93 | { |
94 | } |
95 | |
96 | ~QDeclarativeRectangleMapItemPrivateCPU() override; |
97 | |
98 | void onLinePropertiesChanged() override |
99 | { |
100 | // mark dirty just in case we're a width change |
101 | markSourceDirtyAndUpdate(); |
102 | } |
103 | virtual void markSourceDirtyAndUpdate() override |
104 | { |
105 | m_geometry.markSourceDirty(); |
106 | m_borderGeometry.markSourceDirty(); |
107 | m_rect.polishAndUpdate(); |
108 | } |
109 | virtual void onMapSet() override |
110 | { |
111 | markSourceDirtyAndUpdate(); |
112 | } |
113 | virtual void onGeoGeometryChanged() override |
114 | { |
115 | markSourceDirtyAndUpdate(); |
116 | } |
117 | virtual void onItemGeometryChanged() override |
118 | { |
119 | m_geometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
120 | m_borderGeometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
121 | markSourceDirtyAndUpdate(); |
122 | } |
123 | virtual void afterViewportChanged() override |
124 | { |
125 | m_geometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
126 | m_borderGeometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
127 | markSourceDirtyAndUpdate(); |
128 | } |
129 | virtual void updatePolish() override |
130 | { |
131 | if (!m_rect.topLeft().isValid() || !m_rect.bottomRight().isValid()) { |
132 | m_geometry.clear(); |
133 | m_borderGeometry.clear(); |
134 | m_rect.setWidth(0); |
135 | m_rect.setHeight(0); |
136 | return; |
137 | } |
138 | |
139 | const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_rect.map()->geoProjection()); |
140 | |
141 | QScopedValueRollback<bool> rollback(m_rect.m_updatingGeometry); |
142 | m_rect.m_updatingGeometry = true; |
143 | |
144 | const QList<QGeoCoordinate> perimeter = path(rect: m_rect.m_rectangle); |
145 | const QList<QDoubleVector2D> pathMercator_ = pathMercator(p: perimeter); |
146 | m_geometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
147 | m_geometry.updateSourcePoints(map: *m_rect.map(), path: pathMercator_); |
148 | m_geometry.updateScreenPoints(map: *m_rect.map(), strokeWidth: m_rect.m_border.width()); |
149 | |
150 | QList<QGeoMapItemGeometry *> geoms; |
151 | geoms << &m_geometry; |
152 | m_borderGeometry.clear(); |
153 | |
154 | if (m_rect.m_border.color().alpha() != 0 && m_rect.m_border.width() > 0) { |
155 | QList<QDoubleVector2D> closedPath = pathMercator_; |
156 | closedPath << closedPath.first(); |
157 | |
158 | m_borderGeometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
159 | const QGeoCoordinate &geometryOrigin = m_geometry.origin(); |
160 | |
161 | m_borderGeometry.srcPoints_.clear(); |
162 | m_borderGeometry.srcPointTypes_.clear(); |
163 | |
164 | QDoubleVector2D borderLeftBoundWrapped; |
165 | QList<QList<QDoubleVector2D > > clippedPaths = m_borderGeometry.clipPath(map: *m_rect.map(), path: closedPath, leftBoundWrapped&: borderLeftBoundWrapped); |
166 | if (clippedPaths.size()) { |
167 | borderLeftBoundWrapped = p.geoToWrappedMapProjection(coordinate: geometryOrigin); |
168 | m_borderGeometry.pathToScreen(map: *m_rect.map(), clippedPaths, leftBoundWrapped: borderLeftBoundWrapped); |
169 | m_borderGeometry.updateScreenPoints(map: *m_rect.map(), strokeWidth: m_rect.m_border.width()); |
170 | |
171 | geoms << &m_borderGeometry; |
172 | } else { |
173 | m_borderGeometry.clear(); |
174 | } |
175 | } |
176 | |
177 | QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); |
178 | m_rect.setWidth(combined.width() + 2 * m_rect.m_border.width()); // ToDo: fix this! 2 is incorrect |
179 | m_rect.setHeight(combined.height() + 2 * m_rect.m_border.width()); |
180 | |
181 | m_rect.setPositionOnMap(coordinate: m_geometry.origin(), offset: m_geometry.firstPointOffset()); |
182 | } |
183 | |
184 | virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override |
185 | { |
186 | Q_UNUSED(data); |
187 | if (!m_node || !oldNode) { |
188 | m_node = new MapPolygonNode(); |
189 | if (oldNode) { |
190 | delete oldNode; |
191 | oldNode = nullptr; |
192 | } |
193 | } else { |
194 | m_node = static_cast<MapPolygonNode *>(oldNode); |
195 | } |
196 | |
197 | //TODO: update only material |
198 | if (m_geometry.isScreenDirty() || m_borderGeometry.isScreenDirty() || m_rect.m_dirtyMaterial) { |
199 | m_node->update(fillColor: m_rect.m_color, borderColor: m_rect.m_border.color(), fillShape: &m_geometry, borderShape: &m_borderGeometry); |
200 | m_geometry.setPreserveGeometry(value: false); |
201 | m_borderGeometry.setPreserveGeometry(value: false); |
202 | m_geometry.markClean(); |
203 | m_borderGeometry.markClean(); |
204 | m_rect.m_dirtyMaterial = false; |
205 | } |
206 | return m_node; |
207 | } |
208 | virtual bool contains(const QPointF &point) const override |
209 | { |
210 | return (m_geometry.contains(screenPoint: point) || m_borderGeometry.contains(point)); |
211 | } |
212 | |
213 | static QList<QGeoCoordinate> path(const QGeoRectangle &rect) |
214 | { |
215 | QList<QGeoCoordinate> res; |
216 | res << rect.topLeft(); |
217 | res << QGeoCoordinate(rect.topLeft().latitude(), rect.bottomRight().longitude()); |
218 | res << rect.bottomRight(); |
219 | res << QGeoCoordinate(rect.bottomRight().latitude(), rect.topLeft().longitude()); |
220 | return res; |
221 | } |
222 | |
223 | static QList<QGeoCoordinate> perimeter(const QGeoRectangle &rect) |
224 | { |
225 | QList<QGeoCoordinate> res; |
226 | res << rect.topLeft(); |
227 | res << QGeoCoordinate(rect.topLeft().latitude(), rect.bottomRight().longitude()); |
228 | res << rect.bottomRight(); |
229 | res << QGeoCoordinate(rect.bottomRight().latitude(), rect.topLeft().longitude()); |
230 | res << res.first(); |
231 | return res; |
232 | } |
233 | |
234 | static QList<QDoubleVector2D> pathMercator(const QList<QGeoCoordinate> &p) |
235 | { |
236 | QList<QDoubleVector2D> res; |
237 | for (const auto &c: p) |
238 | res << QWebMercator::coordToMercator(coord: c); |
239 | return res; |
240 | } |
241 | |
242 | QGeoMapPolygonGeometry m_geometry; |
243 | QGeoMapPolylineGeometry m_borderGeometry; |
244 | MapPolygonNode *m_node = nullptr; |
245 | }; |
246 | |
247 | #if QT_CONFIG(opengl) |
248 | class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItemPrivateOpenGL: public QDeclarativeRectangleMapItemPrivate |
249 | { |
250 | public: |
251 | QDeclarativeRectangleMapItemPrivateOpenGL(QDeclarativeRectangleMapItem &rect) : QDeclarativeRectangleMapItemPrivate(rect) |
252 | { |
253 | } |
254 | |
255 | QDeclarativeRectangleMapItemPrivateOpenGL(QDeclarativeRectangleMapItemPrivate &other) |
256 | : QDeclarativeRectangleMapItemPrivate(other) |
257 | { |
258 | } |
259 | |
260 | ~QDeclarativeRectangleMapItemPrivateOpenGL() override; |
261 | |
262 | void markScreenDirtyAndUpdate() |
263 | { |
264 | // preserveGeometry is cleared in updateMapItemPaintNode |
265 | m_geometry.markScreenDirty(); |
266 | m_borderGeometry.markScreenDirty(); |
267 | m_rect.polishAndUpdate(); |
268 | } |
269 | void onLinePropertiesChanged() override |
270 | { |
271 | m_rect.m_dirtyMaterial = true; |
272 | afterViewportChanged(); |
273 | } |
274 | virtual void markSourceDirtyAndUpdate() override |
275 | { |
276 | m_geometry.markSourceDirty(); |
277 | m_borderGeometry.markSourceDirty(); |
278 | m_rect.polishAndUpdate(); |
279 | } |
280 | void preserveGeometry() |
281 | { |
282 | m_geometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
283 | m_borderGeometry.setPreserveGeometry(value: true, geoLeftBound: m_rect.m_rectangle.topLeft()); |
284 | } |
285 | virtual void onMapSet() override |
286 | { |
287 | markSourceDirtyAndUpdate(); |
288 | } |
289 | virtual void onGeoGeometryChanged() override |
290 | { |
291 | preserveGeometry(); |
292 | markSourceDirtyAndUpdate(); |
293 | } |
294 | virtual void onItemGeometryChanged() override |
295 | { |
296 | onGeoGeometryChanged(); |
297 | } |
298 | virtual void afterViewportChanged() override |
299 | { |
300 | preserveGeometry(); |
301 | markScreenDirtyAndUpdate(); |
302 | } |
303 | virtual void updatePolish() override |
304 | { |
305 | if (!m_rect.topLeft().isValid() || !m_rect.bottomRight().isValid()) { |
306 | m_geometry.clear(); |
307 | m_borderGeometry.clear(); |
308 | m_rect.setWidth(0); |
309 | m_rect.setHeight(0); |
310 | return; |
311 | } |
312 | |
313 | QScopedValueRollback<bool> rollback(m_rect.m_updatingGeometry); |
314 | m_rect.m_updatingGeometry = true; |
315 | const qreal lineWidth = m_rect.m_border.width(); |
316 | const QColor &lineColor = m_rect.m_border.color(); |
317 | const QColor &fillColor = m_rect.color(); |
318 | if (fillColor.alpha() != 0) { |
319 | m_geometry.updateSourcePoints(map: *m_rect.map(), rect: m_rect.m_rectangle); |
320 | m_geometry.markScreenDirty(); |
321 | m_geometry.updateScreenPoints(map: *m_rect.map(), strokeWidth: lineWidth, strokeColor: lineColor); |
322 | } else { |
323 | m_geometry.clearBounds(); |
324 | } |
325 | |
326 | QGeoMapItemGeometry * geom = &m_geometry; |
327 | m_borderGeometry.clearScreen(); |
328 | if (lineColor.alpha() != 0 && lineWidth > 0) { |
329 | m_borderGeometry.updateSourcePoints(map: *m_rect.map(), rect: m_rect.m_rectangle); |
330 | m_borderGeometry.markScreenDirty(); |
331 | m_borderGeometry.updateScreenPoints(map: *m_rect.map(), strokeWidth: lineWidth); |
332 | geom = &m_borderGeometry; |
333 | } |
334 | m_rect.setWidth(geom->sourceBoundingBox().width()); |
335 | m_rect.setHeight(geom->sourceBoundingBox().height()); |
336 | m_rect.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); |
337 | } |
338 | |
339 | virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override |
340 | { |
341 | Q_UNUSED(data); |
342 | |
343 | if (!m_rootNode || !oldNode) { |
344 | m_rootNode = new QDeclarativePolygonMapItemPrivateOpenGL::RootNode(); |
345 | m_node = new MapPolygonNodeGL(); |
346 | m_rootNode->appendChildNode(node: m_node); |
347 | m_polylinenode = new MapPolylineNodeOpenGLExtruded(); |
348 | m_rootNode->appendChildNode(node: m_polylinenode); |
349 | m_rootNode->markDirty(bits: QSGNode::DirtyNodeAdded); |
350 | if (oldNode) |
351 | delete oldNode; |
352 | } else { |
353 | m_rootNode = static_cast<QDeclarativePolygonMapItemPrivateOpenGL::RootNode *>(oldNode); |
354 | } |
355 | |
356 | const QGeoMap *map = m_rect.map(); |
357 | const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform(); |
358 | const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator(); |
359 | |
360 | if (m_borderGeometry.isScreenDirty()) { |
361 | /* Do the border update first */ |
362 | m_polylinenode->update(fillColor: m_rect.m_border.color(), |
363 | lineWidth: float(m_rect.m_border.width()), |
364 | shape: &m_borderGeometry, |
365 | geoProjection: combinedMatrix, |
366 | center: cameraCenter, |
367 | capStyle: Qt::SquareCap, |
368 | closed: true, |
369 | zoom: 30); // No LOD for rectangles |
370 | m_borderGeometry.setPreserveGeometry(value: false); |
371 | m_borderGeometry.markClean(); |
372 | } else { |
373 | m_polylinenode->setSubtreeBlocked(true); |
374 | } |
375 | if (m_geometry.isScreenDirty()) { |
376 | m_node->update(fillColor: m_rect.m_color, |
377 | fillShape: &m_geometry, |
378 | geoProjection: combinedMatrix, |
379 | center: cameraCenter); |
380 | m_geometry.setPreserveGeometry(value: false); |
381 | m_geometry.markClean(); |
382 | } else { |
383 | m_node->setSubtreeBlocked(true); |
384 | } |
385 | |
386 | m_rootNode->setSubtreeBlocked(false); |
387 | return m_rootNode; |
388 | } |
389 | virtual bool contains(const QPointF &point) const override |
390 | { |
391 | const qreal lineWidth = m_rect.m_border.width(); |
392 | const QColor &lineColor = m_rect.m_border.color(); |
393 | const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox(); |
394 | if (bounds.contains(p: point)) { |
395 | QDeclarativeGeoMap *m = m_rect.quickMap(); |
396 | if (m) { |
397 | const QGeoCoordinate crd = m->toCoordinate(position: m->mapFromItem(item: &m_rect, point)); |
398 | return m_rect.m_rectangle.contains(coordinate: crd) || m_borderGeometry.contains(point: m_rect.mapToItem(item: m_rect.quickMap(), point), |
399 | lineWidth: m_rect.border()->width(), |
400 | p: static_cast<const QGeoProjectionWebMercator&>(m_rect.map()->geoProjection())); |
401 | } else { |
402 | return true; |
403 | } |
404 | } |
405 | return false; |
406 | } |
407 | |
408 | QGeoMapPolygonGeometryOpenGL m_geometry; |
409 | QGeoMapPolylineGeometryOpenGL m_borderGeometry; |
410 | QDeclarativePolygonMapItemPrivateOpenGL::RootNode *m_rootNode = nullptr; |
411 | MapPolygonNodeGL *m_node = nullptr; |
412 | MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr; |
413 | }; |
414 | #endif // QT_CONFIG(opengl) |
415 | |
416 | QT_END_NAMESPACE |
417 | |
418 | #endif // QDECLARATIVERECTANGLEMAPITEM_P_P_H |
419 | |
420 | |