/**************************************************************************** ** ** Copyright (C) 2020 Paolo Angelelli ** Copyright (C) 2020 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtLocation module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QDECLARATIVECIRCLEMAPITEM_P_P_H #define QDECLARATIVECIRCLEMAPITEM_P_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include QT_BEGIN_NAMESPACE class Q_LOCATION_PRIVATE_EXPORT QGeoMapCircleGeometry : public QGeoMapPolygonGeometry { public: QGeoMapCircleGeometry(); void updateScreenPointsInvert(const QList &circlePath, const QGeoMap &map); }; class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivate { public: static const int CircleSamples = 128; // ToDo: make this radius && ZL dependent? QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItem &circle) : m_circle(circle) { } QDeclarativeCircleMapItemPrivate(QDeclarativeCircleMapItemPrivate &other) : m_circle(other.m_circle) { } virtual ~QDeclarativeCircleMapItemPrivate(); virtual void onLinePropertiesChanged() = 0; virtual void markSourceDirtyAndUpdate() = 0; virtual void onMapSet() = 0; virtual void onGeoGeometryChanged() = 0; virtual void onItemGeometryChanged() = 0; virtual void updatePolish() = 0; virtual void afterViewportChanged() = 0; virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) = 0; virtual bool contains(const QPointF &point) const = 0; void updateCirclePath() { if (!m_circle.map() || m_circle.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; const QGeoProjectionWebMercator &p = static_cast(m_circle.map()->geoProjection()); QList path; calculatePeripheralPoints(path, m_circle.center(), m_circle.radius(), CircleSamples, m_leftBound); m_circlePath.clear(); for (const QGeoCoordinate &c : path) m_circlePath << p.geoToMapProjection(c); } static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance); static bool preserveCircleGeometry(QList &path, const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p); static void updateCirclePathForRendering(QList &path, const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p); static void calculatePeripheralPoints(QList &path, const QGeoCoordinate ¢er, qreal distance, int steps, QGeoCoordinate &leftBound); QDeclarativeCircleMapItem &m_circle; QList m_circlePath; QGeoCoordinate m_leftBound; }; class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateCPU: public QDeclarativeCircleMapItemPrivate { public: QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) { } QDeclarativeCircleMapItemPrivateCPU(QDeclarativeCircleMapItemPrivate &other) : QDeclarativeCircleMapItemPrivate(other) { } ~QDeclarativeCircleMapItemPrivateCPU() override; void onLinePropertiesChanged() override { // mark dirty just in case we're a width change markSourceDirtyAndUpdate(); } void markSourceDirtyAndUpdate() override { // preserveGeometry is cleared in updateMapItemPaintNode m_geometry.markSourceDirty(); m_borderGeometry.markSourceDirty(); m_circle.polishAndUpdate(); } void onMapSet() override { updateCirclePath(); markSourceDirtyAndUpdate(); } void onGeoGeometryChanged() override { updateCirclePath(); markSourceDirtyAndUpdate(); } void onItemGeometryChanged() override { onGeoGeometryChanged(); } void afterViewportChanged() override { markSourceDirtyAndUpdate(); } void updatePolish() override { if (!m_circle.m_circle.isValid()) { m_geometry.clear(); m_borderGeometry.clear(); m_circle.setWidth(0); m_circle.setHeight(0); return; } const QGeoProjectionWebMercator &p = static_cast(m_circle.map()->geoProjection()); QScopedValueRollback rollback(m_circle.m_updatingGeometry); m_circle.m_updatingGeometry = true; QList circlePath = m_circlePath; int pathCount = circlePath.size(); bool preserve = preserveCircleGeometry(circlePath, m_circle.m_circle.center(), m_circle.m_circle.radius(), p); // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft()); // to fix QTBUG-62154 m_geometry.setPreserveGeometry(true, m_leftBound); // to set the geoLeftBound_ m_geometry.setPreserveGeometry(preserve, m_leftBound); bool invertedCircle = false; if (crossEarthPole(m_circle.m_circle.center(), m_circle.m_circle.radius()) && circlePath.size() == pathCount) { m_geometry.updateScreenPointsInvert(circlePath, *m_circle.map()); // invert fill area for really huge circles invertedCircle = true; } else { m_geometry.updateSourcePoints(*m_circle.map(), circlePath); m_geometry.updateScreenPoints(*m_circle.map(), m_circle.m_border.width()); } m_borderGeometry.clear(); QList geoms; geoms << &m_geometry; if (m_circle.m_border.color() != Qt::transparent && m_circle.m_border.width() > 0) { QList closedPath = circlePath; closedPath << closedPath.first(); if (invertedCircle) { closedPath = m_circlePath; closedPath << closedPath.first(); std::reverse(closedPath.begin(), closedPath.end()); } m_borderGeometry.setPreserveGeometry(true, m_leftBound); m_borderGeometry.setPreserveGeometry(preserve, m_leftBound); // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail. const QGeoCoordinate &geometryOrigin = m_geometry.origin(); m_borderGeometry.srcPoints_.clear(); m_borderGeometry.srcPointTypes_.clear(); QDoubleVector2D borderLeftBoundWrapped; QList > clippedPaths = m_borderGeometry.clipPath(*m_circle.map(), closedPath, borderLeftBoundWrapped); if (clippedPaths.size()) { borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); m_borderGeometry.pathToScreen(*m_circle.map(), clippedPaths, borderLeftBoundWrapped); m_borderGeometry.updateScreenPoints(*m_circle.map(), m_circle.m_border.width()); geoms << &m_borderGeometry; } else { m_borderGeometry.clear(); } } QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); if (invertedCircle || !preserve) { m_circle.setWidth(combined.width()); m_circle.setHeight(combined.height()); } else { m_circle.setWidth(combined.width() + 2 * m_circle.m_border.width()); // ToDo: Fix this! m_circle.setHeight(combined.height() + 2 * m_circle.m_border.width()); } // No offsetting here, even in normal case, because first point offset is already translated m_circle.setPositionOnMap(m_geometry.origin(), m_geometry.firstPointOffset()); } QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override { Q_UNUSED(data); if (!m_node || !oldNode) { // Apparently the QSG might delete the nodes if they become invisible m_node = new MapPolygonNode(); if (oldNode) { delete oldNode; oldNode = nullptr; } } else { m_node = static_cast(oldNode); } //TODO: update only material if (m_geometry.isScreenDirty() || m_borderGeometry.isScreenDirty() || m_circle.m_dirtyMaterial) { m_node->update(m_circle.m_color, m_circle.m_border.color(), &m_geometry, &m_borderGeometry); m_geometry.setPreserveGeometry(false); m_borderGeometry.setPreserveGeometry(false); m_geometry.markClean(); m_borderGeometry.markClean(); m_circle.m_dirtyMaterial = false; } return m_node; } bool contains(const QPointF &point) const override { return (m_geometry.contains(point) || m_borderGeometry.contains(point)); } QGeoMapCircleGeometry m_geometry; QGeoMapPolylineGeometry m_borderGeometry; MapPolygonNode *m_node = nullptr; }; class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateOpenGL: public QDeclarativeCircleMapItemPrivate { public: QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItem &circle) : QDeclarativeCircleMapItemPrivate(circle) { } QDeclarativeCircleMapItemPrivateOpenGL(QDeclarativeCircleMapItemPrivate &other) : QDeclarativeCircleMapItemPrivate(other) { } ~QDeclarativeCircleMapItemPrivateOpenGL() override; void onLinePropertiesChanged() override { m_circle.m_dirtyMaterial = true; afterViewportChanged(); } void markScreenDirtyAndUpdate() { // preserveGeometry is cleared in updateMapItemPaintNode m_geometry.markScreenDirty(); m_borderGeometry.markScreenDirty(); m_circle.polishAndUpdate(); } virtual void markSourceDirtyAndUpdate() override { updateCirclePath(); preserveGeometry(); m_geometry.markSourceDirty(); m_borderGeometry.markSourceDirty(); m_circle.polishAndUpdate(); } void preserveGeometry() { m_geometry.setPreserveGeometry(true, m_leftBound); m_borderGeometry.setPreserveGeometry(true, m_leftBound); } virtual void onMapSet() override { markSourceDirtyAndUpdate(); } virtual void onGeoGeometryChanged() override { markSourceDirtyAndUpdate(); } virtual void onItemGeometryChanged() override { onGeoGeometryChanged(); } virtual void afterViewportChanged() override { preserveGeometry(); markScreenDirtyAndUpdate(); } virtual void updatePolish() override { if (m_circle.m_circle.isEmpty()) { m_geometry.clear(); m_borderGeometry.clear(); m_circle.setWidth(0); m_circle.setHeight(0); return; } QScopedValueRollback rollback(m_circle.m_updatingGeometry); m_circle.m_updatingGeometry = true; const qreal lineWidth = m_circle.m_border.width(); const QColor &lineColor = m_circle.m_border.color(); const QColor &fillColor = m_circle.color(); if (fillColor.alpha() != 0) { m_geometry.updateSourcePoints(*m_circle.map(), m_circlePath); m_geometry.markScreenDirty(); m_geometry.updateScreenPoints(*m_circle.map(), lineWidth, lineColor); } else { m_geometry.clearBounds(); } QGeoMapItemGeometry * geom = &m_geometry; m_borderGeometry.clearScreen(); if (lineColor.alpha() != 0 && lineWidth > 0) { m_borderGeometry.updateSourcePoints(*m_circle.map(), m_circle.m_circle); m_borderGeometry.markScreenDirty(); m_borderGeometry.updateScreenPoints(*m_circle.map(), lineWidth); geom = &m_borderGeometry; } m_circle.setWidth(geom->sourceBoundingBox().width()); m_circle.setHeight(geom->sourceBoundingBox().height()); m_circle.setPosition(1.0 * geom->firstPointOffset() - QPointF(lineWidth * 0.5,lineWidth * 0.5)); } virtual QSGNode * updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override { Q_UNUSED(data); if (!m_rootNode || !oldNode) { m_rootNode = new QDeclarativePolygonMapItemPrivateOpenGL::RootNode(); m_node = new MapPolygonNodeGL(); m_rootNode->appendChildNode(m_node); m_polylinenode = new MapPolylineNodeOpenGLExtruded(); m_rootNode->appendChildNode(m_polylinenode); m_rootNode->markDirty(QSGNode::DirtyNodeAdded); if (oldNode) delete oldNode; } else { m_rootNode = static_cast(oldNode); } const QGeoMap *map = m_circle.map(); const QMatrix4x4 &combinedMatrix = map->geoProjection().qsgTransform(); const QDoubleVector3D &cameraCenter = map->geoProjection().centerMercator(); if (m_borderGeometry.isScreenDirty()) { /* Do the border update first */ m_polylinenode->update(m_circle.m_border.color(), float(m_circle.m_border.width()), &m_borderGeometry, combinedMatrix, cameraCenter, Qt::SquareCap, true, 30); // No LOD for circles m_borderGeometry.setPreserveGeometry(false); m_borderGeometry.markClean(); } else { m_polylinenode->setSubtreeBlocked(true); } if (m_geometry.isScreenDirty()) { m_node->update(m_circle.m_color, &m_geometry, combinedMatrix, cameraCenter); m_geometry.setPreserveGeometry(false); m_geometry.markClean(); } else { m_node->setSubtreeBlocked(true); } m_rootNode->setSubtreeBlocked(false); return m_rootNode; } virtual bool contains(const QPointF &point) const override { const qreal lineWidth = m_circle.m_border.width(); const QColor &lineColor = m_circle.m_border.color(); const QRectF &bounds = (lineColor.alpha() != 0 && lineWidth > 0) ? m_borderGeometry.sourceBoundingBox() : m_geometry.sourceBoundingBox(); if (bounds.contains(point)) { QDeclarativeGeoMap *m = m_circle.quickMap(); if (m) { const QGeoCoordinate crd = m->toCoordinate(m->mapFromItem(&m_circle, point)); return m_circle.m_circle.contains(crd) || m_borderGeometry.contains(m_circle.mapToItem(m_circle.quickMap(), point), m_circle.border()->width(), static_cast(m_circle.map()->geoProjection())); } else { return true; } } return false; } QGeoMapPolygonGeometryOpenGL m_geometry; QGeoMapPolylineGeometryOpenGL m_borderGeometry; QDeclarativePolygonMapItemPrivateOpenGL::RootNode *m_rootNode = nullptr; MapPolygonNodeGL *m_node = nullptr; MapPolylineNodeOpenGLExtruded *m_polylinenode = nullptr; }; QT_END_NAMESPACE #endif // QDECLARATIVECIRCLEMAPITEM_P_P_H