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 #if QT_CONFIG(opengl) 279 class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateOpenGL: public QDeclarativeCircleMapItemPrivate 280 { 281 public: QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem & circle)282 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) 283 { 284 } 285 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate & other)286 QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate &other) 287 : QDeclarativeCircleMapItemPrivate(other) 288 { 289 } 290 291 ~QDeclarativeCircleMapItemPrivateOpenGL() override; 292 onLinePropertiesChanged()293 void onLinePropertiesChanged() override 294 { 295 m_circle.m_dirtyMaterial = true; 296 afterViewportChanged(); 297 } markScreenDirtyAndUpdate()298 void markScreenDirtyAndUpdate() 299 { 300 // preserveGeometry is cleared in updateMapItemPaintNode 301 m_geometry.markScreenDirty(); 302 m_borderGeometry.markScreenDirty(); 303 m_circle.polishAndUpdate(); 304 } markSourceDirtyAndUpdate()305 virtual void markSourceDirtyAndUpdate() override 306 { 307 updateCirclePath(); 308 preserveGeometry(); 309 m_geometry.markSourceDirty(); 310 m_borderGeometry.markSourceDirty(); 311 m_circle.polishAndUpdate(); 312 } preserveGeometry()313 void preserveGeometry() 314 { 315 m_geometry.setPreserveGeometry(true, m_leftBound); 316 m_borderGeometry.setPreserveGeometry(true, m_leftBound); 317 } onMapSet()318 virtual void onMapSet() override 319 { 320 markSourceDirtyAndUpdate(); 321 } onGeoGeometryChanged()322 virtual void onGeoGeometryChanged() override 323 { 324 325 markSourceDirtyAndUpdate(); 326 } onItemGeometryChanged()327 virtual void onItemGeometryChanged() override 328 { 329 onGeoGeometryChanged(); 330 } afterViewportChanged()331 virtual void afterViewportChanged() override 332 { 333 preserveGeometry(); 334 markScreenDirtyAndUpdate(); 335 } updatePolish()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(*m_circle.map(), m_circlePath); 353 m_geometry.markScreenDirty(); 354 m_geometry.updateScreenPoints(*m_circle.map(), lineWidth, 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(*m_circle.map(), m_circle.m_circle); 363 m_borderGeometry.markScreenDirty(); 364 m_borderGeometry.updateScreenPoints(*m_circle.map(), 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 updateMapItemPaintNode(QSGNode * oldNode,QQuickItem::UpdatePaintNodeData * data)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(m_node); 380 m_polylinenode = new MapPolylineNodeOpenGLExtruded(); 381 m_rootNode->appendChildNode(m_polylinenode); 382 m_rootNode->markDirty(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(m_circle.m_border.color(), 396 float(m_circle.m_border.width()), 397 &m_borderGeometry, 398 combinedMatrix, 399 cameraCenter, 400 Qt::SquareCap, 401 true, 402 30); // No LOD for circles 403 m_borderGeometry.setPreserveGeometry(false); 404 m_borderGeometry.markClean(); 405 } else { 406 m_polylinenode->setSubtreeBlocked(true); 407 } 408 if (m_geometry.isScreenDirty()) { 409 m_node->update(m_circle.m_color, 410 &m_geometry, 411 combinedMatrix, 412 cameraCenter); 413 m_geometry.setPreserveGeometry(false); 414 m_geometry.markClean(); 415 } else { 416 m_node->setSubtreeBlocked(true); 417 } 418 419 m_rootNode->setSubtreeBlocked(false); 420 return m_rootNode; 421 } contains(const QPointF & point)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(point)) { 428 QDeclarativeGeoMap *m = m_circle.quickMap(); 429 if (m) { 430 const QGeoCoordinate crd = m->toCoordinate(m->mapFromItem(&m_circle, point)); 431 return m_circle.m_circle.contains(crd) || m_borderGeometry.contains(m_circle.mapToItem(m_circle.quickMap(), point), 432 m_circle.border()->width(), 433 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 449 QT_END_NAMESPACE 450 451 #endif // QDECLARATIVECIRCLEMAPITEM_P_P_H 452