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 56 QT_BEGIN_NAMESPACE 57 58 class Q_LOCATION_PRIVATE_EXPORT QGeoMapCircleGeometry : public QGeoMapPolygonGeometry 59 { 60 public: 61 QGeoMapCircleGeometry(); 62 63 void updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map); 64 }; 65 66 class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivate 67 { 68 public: 69 static const int CircleSamples = 128; // ToDo: make this radius && ZL dependent? 70 QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItem & circle)71 QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItem &circle) : m_circle(circle) 72 { 73 74 } QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItemPrivate & other)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 updateCirclePath()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, m_circle.center(), m_circle.radius(), CircleSamples, m_leftBound); 98 m_circlePath.clear(); 99 for (const QGeoCoordinate &c : path) 100 m_circlePath << p.geoToMapProjection(c); 101 } 102 103 static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance); 104 105 static bool preserveCircleGeometry(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, 106 qreal distance, const QGeoProjectionWebMercator &p); 107 static void updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, 108 qreal distance, const QGeoProjectionWebMercator &p); 109 110 static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate ¢er, 111 qreal distance, int steps, QGeoCoordinate &leftBound); 112 113 QDeclarativeCircleMapItem &m_circle; 114 QList<QDoubleVector2D> m_circlePath; 115 QGeoCoordinate m_leftBound; 116 }; 117 118 class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateCPU: public QDeclarativeCircleMapItemPrivate 119 { 120 public: 121 QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItem & circle)122 QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) 123 { 124 } 125 QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItemPrivate & other)126 QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItemPrivate &other) 127 : QDeclarativeCircleMapItemPrivate(other) 128 { 129 } 130 131 ~QDeclarativeCircleMapItemPrivateCPU() override; 132 onLinePropertiesChanged()133 void onLinePropertiesChanged() override 134 { 135 // mark dirty just in case we're a width change 136 markSourceDirtyAndUpdate(); 137 } markSourceDirtyAndUpdate()138 void markSourceDirtyAndUpdate() override 139 { 140 // preserveGeometry is cleared in updateMapItemPaintNode 141 m_geometry.markSourceDirty(); 142 m_borderGeometry.markSourceDirty(); 143 m_circle.polishAndUpdate(); 144 } onMapSet()145 void onMapSet() override 146 { 147 updateCirclePath(); 148 markSourceDirtyAndUpdate(); 149 } onGeoGeometryChanged()150 void onGeoGeometryChanged() override 151 { 152 updateCirclePath(); 153 markSourceDirtyAndUpdate(); 154 } onItemGeometryChanged()155 void onItemGeometryChanged() override 156 { 157 onGeoGeometryChanged(); 158 } afterViewportChanged()159 void afterViewportChanged() override 160 { 161 markSourceDirtyAndUpdate(); 162 } updatePolish()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(circlePath, m_circle.m_circle.center(), 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(true, m_leftBound); // to set the geoLeftBound_ 184 m_geometry.setPreserveGeometry(preserve, m_leftBound); 185 186 bool invertedCircle = false; 187 if (crossEarthPole(m_circle.m_circle.center(), m_circle.m_circle.radius()) && circlePath.size() == pathCount) { 188 m_geometry.updateScreenPointsInvert(circlePath, *m_circle.map()); // invert fill area for really huge circles 189 invertedCircle = true; 190 } else { 191 m_geometry.updateSourcePoints(*m_circle.map(), circlePath); 192 m_geometry.updateScreenPoints(*m_circle.map(), 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(closedPath.begin(), closedPath.end()); 207 } 208 209 m_borderGeometry.setPreserveGeometry(true, m_leftBound); 210 m_borderGeometry.setPreserveGeometry(preserve, 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(*m_circle.map(), closedPath, borderLeftBoundWrapped); 220 if (clippedPaths.size()) { 221 borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); 222 m_borderGeometry.pathToScreen(*m_circle.map(), clippedPaths, borderLeftBoundWrapped); 223 m_borderGeometry.updateScreenPoints(*m_circle.map(), 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(m_geometry.origin(), m_geometry.firstPointOffset()); 242 } 243 updateMapItemPaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData * data)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(m_circle.m_color, m_circle.m_border.color(), &m_geometry, &m_borderGeometry); 260 m_geometry.setPreserveGeometry(false); 261 m_borderGeometry.setPreserveGeometry(false); 262 m_geometry.markClean(); 263 m_borderGeometry.markClean(); 264 m_circle.m_dirtyMaterial = false; 265 } 266 return m_node; 267 } contains(const QPointF & point)268 bool contains(const QPointF &point) const override 269 { 270 return (m_geometry.contains(point) || m_borderGeometry.contains(point)); 271 } 272 273 QGeoMapCircleGeometry m_geometry; 274 QGeoMapPolylineGeometry m_borderGeometry; 275 MapPolygonNode *m_node = nullptr; 276 }; 277 278 class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateOpenGL: public QDeclarativeCircleMapItemPrivate 279 { 280 public: QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem & circle)281 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) 282 { 283 } 284 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate & other)285 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate &other) 286 : QDeclarativeCircleMapItemPrivate(other) 287 { 288 } 289 290 ~QDeclarativeCircleMapItemPrivateOpenGL() override; 291 onLinePropertiesChanged()292 void onLinePropertiesChanged() override 293 { 294 m_circle.m_dirtyMaterial = true; 295 afterViewportChanged(); 296 } markScreenDirtyAndUpdate()297 void markScreenDirtyAndUpdate() 298 { 299 // preserveGeometry is cleared in updateMapItemPaintNode 300 m_geometry.markScreenDirty(); 301 m_borderGeometry.markScreenDirty(); 302 m_circle.polishAndUpdate(); 303 } markSourceDirtyAndUpdate()304 virtual void markSourceDirtyAndUpdate() override 305 { 306 updateCirclePath(); 307 preserveGeometry(); 308 m_geometry.markSourceDirty(); 309 m_borderGeometry.markSourceDirty(); 310 m_circle.polishAndUpdate(); 311 } preserveGeometry()312 void preserveGeometry() 313 { 314 m_geometry.setPreserveGeometry(true, m_leftBound); 315 m_borderGeometry.setPreserveGeometry(true, m_leftBound); 316 } onMapSet()317 virtual void onMapSet() override 318 { 319 markSourceDirtyAndUpdate(); 320 } onGeoGeometryChanged()321 virtual void onGeoGeometryChanged() override 322 { 323 324 markSourceDirtyAndUpdate(); 325 } onItemGeometryChanged()326 virtual void onItemGeometryChanged() override 327 { 328 onGeoGeometryChanged(); 329 } afterViewportChanged()330 virtual void afterViewportChanged() override 331 { 332 preserveGeometry(); 333 markScreenDirtyAndUpdate(); 334 } updatePolish()335 virtual void updatePolish() override 336 { 337 if (m_circle.m_circle.isEmpty()) { 338 m_geometry.clear(); 339 m_borderGeometry.clear(); 340 m_circle.setWidth(0); 341 m_circle.setHeight(0); 342 return; 343 } 344 345 QScopedValueRollback<bool> rollback(m_circle.m_updatingGeometry); 346 m_circle.m_updatingGeometry = true; 347 const qreal lineWidth = m_circle.m_border.width(); 348 const QColor &lineColor = m_circle.m_border.color(); 349 const QColor &fillColor = m_circle.color(); 350 if (fillColor.alpha() != 0) { 351 m_geometry.updateSourcePoints(*m_circle.map(), m_circlePath); 352 m_geometry.markScreenDirty(); 353 m_geometry.updateScreenPoints(*m_circle.map(), lineWidth, lineColor); 354 } else { 355 m_geometry.clearBounds(); 356 } 357 358 QGeoMapItemGeometry * geom = &m_geometry; 359 m_borderGeometry.clearScreen(); 360 if (lineColor.alpha() != 0 && lineWidth > 0) { 361 m_borderGeometry.updateSourcePoints(*m_circle.map(), m_circle.m_circle); 362 m_borderGeometry.markScreenDirty(); 363 m_borderGeometry.updateScreenPoints(*m_circle.map(), lineWidth); 364 geom = &m_borderGeometry; 365 } 366 m_circle.setWidth(geom->sourceBoundingBox().width()); 367 m_circle.setHeight(geom->sourceBoundingBox().height()); 368 m_circle.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); 369 } 370 updateMapItemPaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData * data)371 virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override 372 { 373 Q_UNUSED(data); 374 375 if (!m_rootNode || !oldNode) { 376 m_rootNode = new QDeclarativePolygonMapItemPrivateOpenGL::RootNode(); 377 m_node = new MapPolygonNodeGL(); 378 m_rootNode->appendChildNode(m_node); 379 m_polylinenode = new MapPolylineNodeOpenGLExtruded(); 380 m_rootNode->appendChildNode(m_polylinenode); 381 m_rootNode->markDirty(QSGNode::DirtyNodeAdded); 382 if (oldNode) 383 delete oldNode; 384 } else { 385 m_rootNode = static_cast<QDeclarativePolygonMapItemPrivateOpenGL::RootNode *>(oldNode); 386 } 387 388 const QGeoMap *map = m_circle.map(); 389 const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform(); 390 const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator(); 391 392 if (m_borderGeometry.isScreenDirty()) { 393 /* Do the border update first */ 394 m_polylinenode->update(m_circle.m_border.color(), 395 float(m_circle.m_border.width()), 396 &m_borderGeometry, 397 combinedMatrix, 398 cameraCenter, 399 Qt::SquareCap, 400 true, 401 30); // No LOD for circles 402 m_borderGeometry.setPreserveGeometry(false); 403 m_borderGeometry.markClean(); 404 } else { 405 m_polylinenode->setSubtreeBlocked(true); 406 } 407 if (m_geometry.isScreenDirty()) { 408 m_node->update(m_circle.m_color, 409 &m_geometry, 410 combinedMatrix, 411 cameraCenter); 412 m_geometry.setPreserveGeometry(false); 413 m_geometry.markClean(); 414 } else { 415 m_node->setSubtreeBlocked(true); 416 } 417 418 m_rootNode->setSubtreeBlocked(false); 419 return m_rootNode; 420 } contains(const QPointF & point)421 virtual bool contains(const QPointF &point) const override 422 { 423 const qreal lineWidth = m_circle.m_border.width(); 424 const QColor &lineColor = m_circle.m_border.color(); 425 const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox(); 426 if (bounds.contains(point)) { 427 QDeclarativeGeoMap *m = m_circle.quickMap(); 428 if (m) { 429 const QGeoCoordinate crd = m->toCoordinate(m->mapFromItem(&m_circle, point)); 430 return m_circle.m_circle.contains(crd) || m_borderGeometry.contains(m_circle.mapToItem(m_circle.quickMap(), point), 431 m_circle.border()->width(), 432 static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection())); 433 } else { 434 return true; 435 } 436 } 437 return false; 438 } 439 440 QGeoMapPolygonGeometryOpenGL m_geometry; 441 QGeoMapPolylineGeometryOpenGL m_borderGeometry; 442 QDeclarativePolygonMapItemPrivateOpenGL::RootNode *m_rootNode = nullptr; 443 MapPolygonNodeGL *m_node = nullptr; 444 MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr; 445 }; 446 447 QT_END_NAMESPACE 448 449 #endif // QDECLARATIVECIRCLEMAPITEM_P_P_H 450