diff options
21 files changed, 1067 insertions, 44 deletions
diff --git a/examples/location/geojson_viewer/GeoJsonDelegateMapObject.qml b/examples/location/geojson_viewer/GeoJsonDelegateMapObject.qml new file mode 100644 index 00000000..d1a144c8 --- /dev/null +++ b/examples/location/geojson_viewer/GeoJsonDelegateMapObject.qml @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtPositioning 5.12 +import Qt.labs.location 1.0 +import Qt.labs.qmlmodels 1.0 + +DelegateChooser { + id: dc + role: "type" + property color defaultColor: "grey" + + DelegateChoice { + roleValue: "Point" + delegate: MapCircleObject { + property string geojsonType: "Point" + property var props: modelData.properties + geoShape: modelData.data + radius: 20*1000 + border.width: 3 + /* The expression below is equivalent to: + ((props !== undefined && props["color"] !== undefined) ? props["color"] : + ((parent && parent.props !== undefined && parent.props["color"] !== undefined) ? parent.props["color"] : dc.defaultColor)) + */ + color: (props && props.color) || (parent && parent.props && parent.props.color) || dc.defaultColor + } + } + + DelegateChoice { + roleValue: "LineString" + delegate: MapPolylineObject { + property string geojsonType: "LineString" + property var props: modelData.properties + geoShape: modelData.data + line.width: 4 + line.color: (props && props.color) || (parent && parent.props && parent.props.color) || dc.defaultColor + } + } + + DelegateChoice { + roleValue: "Polygon" + delegate: MapPolygonObject { + property string geojsonType: "Polygon" + property var props: modelData.properties + geoShape: modelData.data + color: (props && props.color) || (parent && parent.props && parent.props.color) || dc.defaultColor + border.width: 4 + border.color: 'black' + } + } + + DelegateChoice { + roleValue: "MultiPoint" + delegate: MapObjectView { + property string geojsonType: "MultiPoint" + property var props: modelData.properties + model: modelData.data + delegate: dc + } + } + + DelegateChoice { + roleValue: "MultiLineString" + delegate: MapObjectView { + property string geojsonType: "MultiLineString" + property var props: modelData.properties + model: modelData.data + delegate: dc + } + } + + DelegateChoice { + roleValue: "MultiPolygon" + delegate: MapObjectView { + property string geojsonType: "MultiPolygon" + property var props: modelData.properties + model: modelData.data + delegate: dc + } + } + + DelegateChoice { + roleValue: "GeometryCollection" + delegate: MapObjectView { + property string geojsonType: "GeometryCollection" + property var props: modelData.properties + model: modelData.data + delegate: dc + } + } + + // Features are explicitly not generated by the parser, but converted straight to their content + the properties. + + DelegateChoice { + roleValue: "FeatureCollection" + delegate: MapObjectView { + property string geojsonType: "FeatureCollection" + property var props: modelData.properties + model: modelData.data + delegate: dc + } + } +} diff --git a/examples/location/geojson_viewer/main.qml b/examples/location/geojson_viewer/main.qml index b2fc3524..1b55ef8b 100644 --- a/examples/location/geojson_viewer/main.qml +++ b/examples/location/geojson_viewer/main.qml @@ -58,6 +58,7 @@ import QtQuick.Window 2.11 import QtPositioning 5.12 import QtLocation 5.12 import Qt.labs.qmlmodels 1.0 +import Qt.labs.location 1.0 import Qt.GeoJson 1.0 C1.ApplicationWindow { @@ -144,11 +145,32 @@ C1.ApplicationWindow { } } C1.MenuItem { - text: "&OpenGL Item backends" + text: "OpenGL Item backends" id: glBackendSelector checkable: true checked: false } + + C1.MenuItem { + text: "Map Object Delegates" + id: mapObjectsSelector + checkable: true + checked: false + + onCheckedChanged: { + if (checked) { + miv.model = undefined + map.removeMapItemView(miv) + rootMoV.addMapObject(mov) + mov.model = geoJsoner.model + } else { + mov.model = undefined + rootMoV.removeMapObject(mov) + map.addMapItemView(miv) + miv.model = geoJsoner.model + } + } + } } } @@ -166,6 +188,11 @@ C1.ApplicationWindow { } } + MapObjectView { + id: mov + delegate: GeoJsonDelegateMapObject {} + } + Map { id: map anchors.fill: parent @@ -173,6 +200,10 @@ C1.ApplicationWindow { plugin: Plugin { name: "osm" } zoomLevel: 4 + MapObjectView { + id: rootMoV + } + MapItemView { id: miv model: geoJsoner.model diff --git a/examples/location/geojson_viewer/qml.qrc b/examples/location/geojson_viewer/qml.qrc index ea443bb0..794a2093 100644 --- a/examples/location/geojson_viewer/qml.qrc +++ b/examples/location/geojson_viewer/qml.qrc @@ -2,5 +2,6 @@ <qresource prefix="/"> <file>main.qml</file> <file>GeoJsonDelegate.qml</file> + <file>GeoJsonDelegateMapObject.qml</file> </qresource> </RCC> diff --git a/src/3rdparty/geosimplify.js/LICENSE b/src/3rdparty/geosimplify.js/LICENSE new file mode 100644 index 00000000..bd04cc24 --- /dev/null +++ b/src/3rdparty/geosimplify.js/LICENSE @@ -0,0 +1,27 @@ +Qt port of geosimplify.js, https://github.com/mapbox/geosimplify-js + +Copyright (c) 2017, Daniel Patterson +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------- +Based on simplify-js by Vladimir Agafonkin - http://mourner.github.io/simplify-js/ diff --git a/src/3rdparty/geosimplify.js/qt_attribution.json b/src/3rdparty/geosimplify.js/qt_attribution.json new file mode 100644 index 00000000..bc8046e0 --- /dev/null +++ b/src/3rdparty/geosimplify.js/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "geosimplify-js", + "Name": "geosimplify-js polyline simplification library", + "QDocModule": "qtlocation", + "QtUsage": "Used in the QML plugin of Qt Location.", + + "Description": "Based on https://github.com/mourner/simplify-js, geosimplify-js fixes the problem that the simple pythagorean measure used in simplify-js changes size if you simply give it longitude/latitude sequences to simplify.", + "Homepage": "https://github.com/mapbox/geosimplify-js", + "LicenseId": "geosimplify-js", + "License": "geosimplify-js License", + "LicenseFile": "LICENSE", + "Copyright": "Copyright (c) 2017 Daniel Patterson" +} diff --git a/src/imports/location/location.cpp b/src/imports/location/location.cpp index 7876e45a..01b6fb62 100644 --- a/src/imports/location/location.cpp +++ b/src/imports/location/location.cpp @@ -211,6 +211,8 @@ public: qmlRegisterType<QDeclarativePolygonMapItem, 15>(uri, major, minor, "MapPolygon"); qmlRegisterType<QDeclarativeRectangleMapItem, 15>(uri, major, minor, "MapRectangle"); qmlRegisterType<QDeclarativeCircleMapItem, 15>(uri, major, minor, "MapCircle"); + qmlRegisterUncreatableType<QDeclarativeGeoMapItemBase, 15>(uri, major, minor, "GeoMapItemBase", + QStringLiteral("GeoMapItemBase is not intended instantiable by developer.")); // Register the latest Qt version as QML type version qmlRegisterModule(uri, QT_VERSION_MAJOR, QT_VERSION_MINOR); diff --git a/src/location/declarativemaps/declarativemaps.pri b/src/location/declarativemaps/declarativemaps.pri index c2afe9a4..e2a922f4 100644 --- a/src/location/declarativemaps/declarativemaps.pri +++ b/src/location/declarativemaps/declarativemaps.pri @@ -34,6 +34,7 @@ PRIVATE_HEADERS += \ declarativemaps/qdeclarativepolygonmapitem_p_p.h \ declarativemaps/qdeclarativerectanglemapitem_p_p.h \ declarativemaps/qdeclarativecirclemapitem_p_p.h \ + declarativemaps/qgeosimplify_p.h \ declarativemaps/qquickgeomapgesturearea_p.h SOURCES += \ @@ -63,6 +64,7 @@ SOURCES += \ declarativemaps/qgeomapobject.cpp \ declarativemaps/qdeclarativegeomapitemutils.cpp \ declarativemaps/qparameterizableobject.cpp \ + declarativemaps/qgeosimplify.cpp \ declarativemaps/qquickgeomapgesturearea.cpp load(qt_build_paths) diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h b/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h index 71a6b6a4..4cf42173 100644 --- a/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h +++ b/src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h @@ -397,7 +397,8 @@ public: combinedMatrix, cameraCenter, Qt::SquareCap, - true); + true, + 30); // No LOD for circles m_borderGeometry.setPreserveGeometry(false); m_borderGeometry.markClean(); } else { diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp index d1a34f1e..70b67828 100644 --- a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp +++ b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp @@ -218,6 +218,33 @@ void QDeclarativeGeoMapItemBase::setAutoFadeIn(bool fadeIn) polishAndUpdate(); } +int QDeclarativeGeoMapItemBase::lodThreshold() const +{ + return m_lodThreshold; +} + +void QDeclarativeGeoMapItemBase::setLodThreshold(int lt) +{ + if (lt == m_lodThreshold) + return; + m_lodThreshold = lt; + update(); +} + +/*! + \internal + + This returns the zoom level to be used when requesting the LOD. + Essentially it clamps to m_lodThreshold, and if above, it selects + a ZL higher than the maximum LODable level. +*/ +unsigned int QDeclarativeGeoMapItemBase::zoomForLOD(int zoom) const +{ + if (zoom >= m_lodThreshold) + return 30; // some arbitrarily large zoom + return uint(zoom); +} + /*! \internal */ diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase_p.h b/src/location/declarativemaps/qdeclarativegeomapitembase_p.h index 65f111f6..61a67f59 100644 --- a/src/location/declarativemaps/qdeclarativegeomapitembase_p.h +++ b/src/location/declarativemaps/qdeclarativegeomapitembase_p.h @@ -85,6 +85,8 @@ class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapItemBase : public QQuickItem Q_PROPERTY(QGeoShape geoShape READ geoShape WRITE setGeoShape STORED false ) Q_PROPERTY(bool autoFadeIn READ autoFadeIn WRITE setAutoFadeIn REVISION 14) + Q_PROPERTY(int lodThreshold READ lodThreshold WRITE setLodThreshold NOTIFY lodThresholdChanged REVISION 15) + public: explicit QDeclarativeGeoMapItemBase(QQuickItem *parent = 0); virtual ~QDeclarativeGeoMapItemBase(); @@ -100,6 +102,10 @@ public: bool autoFadeIn() const; void setAutoFadeIn(bool fadeIn); + int lodThreshold() const; + void setLodThreshold(int lt); + unsigned int zoomForLOD(int zoom) const; + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *); @@ -129,6 +135,7 @@ Q_SIGNALS: void mapItemOpacityChanged(); Q_REVISION(12) void addTransitionFinished(); Q_REVISION(12) void removeTransitionFinished(); + void lodThresholdChanged(); protected Q_SLOTS: virtual void afterChildrenChanged(); @@ -158,6 +165,7 @@ private: QScopedPointer<QDeclarativeGeoMapItemTransitionManager> m_transitionManager; bool m_autoFadeIn = true; + int m_lodThreshold = 0; friend class QDeclarativeGeoMap; friend class QDeclarativeGeoMapItemView; diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h b/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h index 83f1100e..8d566e69 100644 --- a/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h @@ -116,6 +116,8 @@ public: void allocateAndFillPolygon(QSGGeometry *geom) const { + + const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = m_screenVertices; const QVector<quint32> &ix = m_screenIndices; @@ -613,7 +615,9 @@ public: combinedMatrix, cameraCenter, Qt::SquareCap, - true); + true, + 30); // No LOD for polygons just yet. + // First figure out what to do with holes. m_borderGeometry.setPreserveGeometry(false); m_borderGeometry.markClean(); } else { diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp index 7e484122..f914b36d 100644 --- a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp @@ -62,10 +62,30 @@ #include <QtPositioning/private/qgeopath_p.h> #include <QtQuick/private/qsgmaterialshader_p.h> #include <array> +#include <QThreadPool> +#include <QRunnable> #include <QtLocation/private/qgeomapparameter_p.h> +#include "qgeosimplify_p.h" QT_BEGIN_NAMESPACE +struct ThreadPool // to have a thread pool with max 1 thread for geometry processing +{ + ThreadPool () + { + m_threadPool.setMaxThreadCount(1); + } + + void start(QRunnable *runnable, int priority = 0) + { + m_threadPool.start(runnable, priority); + } + + QThreadPool m_threadPool; +}; + +Q_GLOBAL_STATIC(ThreadPool, threadPool) + static const double kClipperScaleFactor = 281474976710656.0; // 48 bits of precision @@ -774,6 +794,7 @@ void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QList<QDoubleVector2D> wrappedPath; QDeclarativeGeoMapItemUtils::wrapPath(poly.path(), geoLeftBound_, p, wrappedPath, &leftBoundWrapped); + const QGeoRectangle &boundingRectangle = poly.boundingGeoRectangle(); updateSourcePoints(p, wrappedPath, boundingRectangle); } @@ -802,8 +823,10 @@ void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoProjectionWebMe QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p, wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped); - m_screenVertices.clear(); - for (const auto &v: qAsConst(wrappedPath)) m_screenVertices << v; + // New pointers, some old LOD task might still be running and operating on the old pointers. + resetLOD(); + + for (const auto &v: qAsConst(wrappedPath)) m_screenVertices->append(v); m_wrappedPolygons.resize(3); m_wrappedPolygons[0].wrappedBboxes = wrappedBboxMinus1; @@ -1474,7 +1497,7 @@ void MapPolylineNodeOpenGLLineStrip::update(const QColor &fillColor, const QDoubleVector3D ¢er, const Qt::PenCapStyle /*capStyle*/) { - if (shape->m_screenVertices.size() < 2) { + if (shape->m_screenVertices->size() < 2) { setSubtreeBlocked(true); return; } else { @@ -1585,13 +1608,27 @@ MapPolylineNodeOpenGLExtruded::~MapPolylineNodeOpenGLExtruded() } -void QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom, bool closed) const +bool QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom, + bool closed, + unsigned int zoom) const { - // ToDo: add dirty flag. - const QVector<QDeclarativeGeoMapItemUtils::vec2> &v = m_screenVertices; + // Select LOD. Generate if not present. Assign it to m_screenVertices; + if (m_dataChanged) { + // it means that the data really changed. + // So synchronously produce LOD 1, and enqueue the requested one if != 0 or 1. + // Select 0 if 0 is requested, or 1 in all other cases. + selectLODOnDataChanged(zoom, m_bboxLeftBoundWrapped.x()); + } else { + // Data has not changed, but active LOD != requested LOD. + // So, if there are no active tasks, try to change to the correct one. + if (!selectLODOnLODMismatch(zoom, m_bboxLeftBoundWrapped.x(), closed)) + return false; + } + + const QVector<QDeclarativeGeoMapItemUtils::vec2> &v = *m_screenVertices; if (v.size() < 2) { geom->allocate(0, 0); - return; + return true; } const int numSegments = (v.size() - 1); @@ -1646,11 +1683,16 @@ void QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom, bo } } } + return true; } -void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom) const +void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom, + int lod) const { - const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = m_screenVertices; + // Select LOD. Generate if not present. Assign it to m_screenVertices; + Q_UNUSED(lod) + + const QVector<QDeclarativeGeoMapItemUtils::vec2> &vx = *m_screenVertices; geom->allocate(vx.size()); QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D(); @@ -1664,10 +1706,11 @@ void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor, const QMatrix4x4 geoProjection, const QDoubleVector3D center, const Qt::PenCapStyle capStyle, - bool closed) + bool closed, + unsigned int zoom) { // shape->size() == number of triangles - if (shape->m_screenVertices.size() < 2 + if (shape->m_screenVertices->size() < 2 || lineWidth < 0.5 || fillColor.alpha() == 0) { // number of points setSubtreeBlocked(true); return; @@ -1676,10 +1719,11 @@ void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor, } QSGGeometry *fill = QSGGeometryNode::geometry(); - if (shape->m_dataChanged || !fill->vertexCount()) { // fill->vertexCount for when node gets destroyed by MapItemBase bcoz of opacity, then recreated. - shape->allocateAndFillEntries(fill, closed); - markDirty(DirtyGeometry); - shape->m_dataChanged = false; + if (shape->m_dataChanged || !shape->isLODActive(zoom) || !fill->vertexCount()) { // fill->vertexCount for when node gets destroyed by MapItemBase bcoz of opacity, then recreated. + if (shape->allocateAndFillEntries(fill, closed, zoom)) { + markDirty(DirtyGeometry); + shape->m_dataChanged = false; + } } // Update this @@ -1874,5 +1918,136 @@ const char *MapPolylineShaderExtruded::vertexShaderMiteredSegments() const "}\n"; } -QT_END_NAMESPACE +QVector<QDeclarativeGeoMapItemUtils::vec2> QGeoMapItemLODGeometry::getSimplified( + QVector<QDeclarativeGeoMapItemUtils::vec2> &wrappedPath, // reference as it gets copied in the nested call + double leftBoundWrapped, + unsigned int zoom) +{ + // Try a simplify step + QList<QDoubleVector2D> data; + for (auto e: wrappedPath) + data << e.toDoubleVector2D(); + const QList<QDoubleVector2D> simplified = QGeoSimplify::geoSimplifyZL(data, + leftBoundWrapped, + zoom); + + data.clear(); + QVector<QDeclarativeGeoMapItemUtils::vec2> simple; + for (auto e: simplified) + simple << e; + return simple; +} + +bool QGeoMapItemLODGeometry::isLODActive(unsigned int lod) const +{ + return m_screenVertices == m_verticesLOD[zoomToLOD(lod)].data(); +} + +class PolylineSimplifyTask : public QRunnable +{ +public: + PolylineSimplifyTask(QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call + QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output, + double leftBound, + unsigned int zoom, + QSharedPointer<unsigned int> &working) + : m_zoom(zoom) + , m_leftBound(leftBound) + , m_input(input) + , m_output(output) + , m_working(working) + { + } + + ~PolylineSimplifyTask() override; + + void run() override + { + // Skip sending notifications for now. Updated data will be picked up eventually. + // ToDo: figure out how to connect a signal from here to a slot in the item. + *m_working = QGeoMapPolylineGeometryOpenGL::zoomToLOD(m_zoom); + *m_output = QGeoMapPolylineGeometryOpenGL::getSimplified( *m_input, + m_leftBound, + QGeoMapPolylineGeometryOpenGL::zoomForLOD(m_zoom)); + *m_working = 0; + } + + unsigned int m_zoom; + double m_leftBound; + QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > m_input, m_output; + QSharedPointer<unsigned int> m_working; +}; + +void QGeoMapItemLODGeometry::enqueueSimplificationTask(QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, + QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output, + double leftBound, + unsigned int zoom, + QSharedPointer<unsigned int> &working) +{ + PolylineSimplifyTask *task = new PolylineSimplifyTask(input, + output, + leftBound, + zoom, + working); + threadPool->start(task); +} + +PolylineSimplifyTask::~PolylineSimplifyTask() {} + +void QGeoMapItemLODGeometry::selectLOD(unsigned int zoom, double leftBound, bool /* closed */) // closed to tell if this is a polygon or a polyline. +{ + unsigned int requestedLod = zoomToLOD(zoom); + if (!m_verticesLOD[requestedLod].isNull()) { + m_screenVertices = m_verticesLOD[requestedLod].data(); + } else if (!m_verticesLOD.at(0)->isEmpty()) { + // if here, zoomToLOD != 0 and no current working task. + // So select the last filled LOD != m_working (lower-bounded by 1, + // guaranteed to exist), and enqueue the right one + m_verticesLOD[requestedLod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( + new QVector<QDeclarativeGeoMapItemUtils::vec2>); + + for (unsigned int i = requestedLod - 1; i >= 1; i--) { + if (*m_working != i && !m_verticesLOD[i].isNull()) { + m_screenVertices = m_verticesLOD[i].data(); + break; + } else if (i == 1) { + // get 1 synchronously if not computed already + m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( + new QVector<QDeclarativeGeoMapItemUtils::vec2>); + *m_verticesLOD[1] = getSimplified( *m_verticesLOD[0], + leftBound, + zoomForLOD(0)); + if (requestedLod == 1) + return; + } + } + + enqueueSimplificationTask( m_verticesLOD.at(0), + m_verticesLOD[requestedLod], + leftBound, + zoom, + m_working); + + } +} + +unsigned int QGeoMapItemLODGeometry::zoomToLOD(unsigned int zoom) +{ + unsigned int res; + if (zoom > 20) + res = 0; + else + res = qBound<unsigned int>(3, zoom, 20) / 3; // bound LOD'ing between ZL 3 and 20. Every 3 ZoomLevels + return res; +} + +unsigned int QGeoMapItemLODGeometry::zoomForLOD(unsigned int zoom) +{ + unsigned int res = (qBound<unsigned int>(3, zoom, 20) / 3) * 3; + if (zoom < 6) + return res; + return res + 1; // give more resolution when closing in +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h b/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h index d37f77ad..35d52790 100644 --- a/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h @@ -62,6 +62,8 @@ #include <QtPositioning/QGeoCircle> #include <QtPositioning/private/qdoublevector2d_p.h> #include <QtCore/QScopedValueRollback> +#include <QSharedPointer> +#include <array> QT_BEGIN_NAMESPACE @@ -211,14 +213,93 @@ protected: QSGGeometry geometry_; }; -class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometryOpenGL : public QGeoMapItemGeometry +class Q_LOCATION_PRIVATE_EXPORT QGeoMapItemLODGeometry +{ +public: + mutable std::array<QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>, 7> m_verticesLOD; // fix it to 7, + // do not allow simplifications beyond ZL 20. This could actually be limited even further + mutable QVector<QDeclarativeGeoMapItemUtils::vec2> *m_screenVertices; + mutable QSharedPointer<unsigned int> m_working; + + QGeoMapItemLODGeometry() + { + resetLOD(); + } + + void resetLOD() + { + // New pointer, some old LOD task might still be running and operating on the old pointers. + m_verticesLOD[0] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( + new QVector<QDeclarativeGeoMapItemUtils::vec2>); + for (unsigned int i = 1; i < m_verticesLOD.size(); ++i) + m_verticesLOD[i] = nullptr; // allocate on first use + m_screenVertices = m_verticesLOD.front().data(); // resetting pointer to data to be LOD 0 + } + + static unsigned int zoomToLOD(unsigned int zoom); + + static unsigned int zoomForLOD(unsigned int zoom); + + bool isLODActive(unsigned int lod) const; + + void selectLOD(unsigned int zoom, double leftBound, bool /*closed*/); + + static QVector<QDeclarativeGeoMapItemUtils::vec2> getSimplified ( + QVector<QDeclarativeGeoMapItemUtils::vec2> &wrappedPath, + double leftBoundWrapped, + unsigned int zoom); + + static void enqueueSimplificationTask(QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call + QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output, + double leftBound, + unsigned int zoom, + QSharedPointer<unsigned int> &working); + + void selectLODOnDataChanged(unsigned int zoom, double leftBound) const + { + unsigned int lod = zoomToLOD(zoom); + if (lod > 0) { + // Generate ZL 1 as fallback for all cases != 0. Do not do if 0 is requested + // (= old behavior, LOD disabled) + m_verticesLOD[1] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( + new QVector<QDeclarativeGeoMapItemUtils::vec2>); + *m_verticesLOD[1] = getSimplified( *m_verticesLOD[0], + leftBound, + zoomForLOD(0)); + } + if (lod > 1) { + enqueueSimplificationTask( m_verticesLOD.at(0), + m_verticesLOD[zoomToLOD(zoom)], + leftBound, + zoom, + m_working); + } + m_screenVertices = m_verticesLOD[qMin<unsigned int>(lod, 1)].data(); + } + + bool selectLODOnLODMismatch(unsigned int zoom, double leftBound, bool closed) const + { + if (*m_working > 0) { + return false; + } + const_cast<QGeoMapItemLODGeometry *>(this)->selectLOD(zoom, + leftBound, + closed); + return true; + } +}; + +class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometryOpenGL : public QGeoMapItemGeometry, public QGeoMapItemLODGeometry { public: typedef struct { QList<QDoubleVector2D> wrappedBboxes; } WrappedPolyline; - QGeoMapPolylineGeometryOpenGL() {} + QGeoMapPolylineGeometryOpenGL() + { + m_working = QSharedPointer<unsigned int>(new unsigned int(0)); + } void updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly); @@ -242,8 +323,11 @@ public: void updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth = 0.0); - void allocateAndFillEntries(QSGGeometry *geom, bool closed = false) const; - void allocateAndFillLineStrip(QSGGeometry *geom) const; + bool allocateAndFillEntries(QSGGeometry *geom, + bool closed = false, + unsigned int zoom = 0) const; + void allocateAndFillLineStrip(QSGGeometry *geom, + int lod = 0) const; bool contains(const QPointF &point) const override { @@ -270,16 +354,17 @@ public: const double lineHalfWidth = lineWidth * 0.5; const QDoubleVector2D pt(point); QDoubleVector2D a; - if (m_screenVertices.size()) a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices.first().toDoubleVector2D())); + if (m_screenVertices->size()) + a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices->first().toDoubleVector2D())); QDoubleVector2D b; - for (int i = 1; i < m_screenVertices.size(); ++i) + for (int i = 1; i < m_screenVertices->size(); ++i) { if (!a.isFinite()) { - a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices.at(i).toDoubleVector2D())); + a = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices->at(i).toDoubleVector2D())); continue; } - b = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices.at(i).toDoubleVector2D())); + b = p.wrappedMapProjectionToItemPosition(p.wrapMapProjection(m_screenVertices->at(i).toDoubleVector2D())); if (!b.isFinite()) { a = b; continue; @@ -299,7 +384,6 @@ public: } public: - QVector<QDeclarativeGeoMapItemUtils::vec2> m_screenVertices; QDoubleVector2D m_bboxLeftBoundWrapped; QVector<WrappedPolyline> m_wrappedPolygons; int m_wrapOffset; @@ -493,7 +577,8 @@ public: const QMatrix4x4 geoProjection, const QDoubleVector3D center, const Qt::PenCapStyle capStyle = Qt::FlatCap, - bool closed = false); + bool closed = false, + unsigned int zoom = 30); static const QSGGeometry::AttributeSet &attributesMapPolylineTriangulated(); @@ -806,7 +891,9 @@ public: &m_geometry, combinedMatrix, cameraCenter, - m_penCapStyle); + m_penCapStyle, + false, + m_poly.zoomForLOD(int(map->cameraData().zoomLevel()))); m_geometry.setPreserveGeometry(false); m_geometry.markClean(); m_poly.m_dirtyMaterial = false; diff --git a/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h b/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h index 520abdf3..65d2f618 100644 --- a/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h +++ b/src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h @@ -364,7 +364,8 @@ public: combinedMatrix, cameraCenter, Qt::SquareCap, - true); + true, + 30); // No LOD for rectangles m_borderGeometry.setPreserveGeometry(false); m_borderGeometry.markClean(); } else { diff --git a/src/location/declarativemaps/qgeosimplify.cpp b/src/location/declarativemaps/qgeosimplify.cpp new file mode 100644 index 00000000..9414a1cf --- /dev/null +++ b/src/location/declarativemaps/qgeosimplify.cpp @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Qt adaptation of geosimplify-js +** Copyright (C) 2017 Daniel Patterson +** See 3rdParty/geosimplify.js for the original license. +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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$ +** +****************************************************************************/ + +#include "qgeosimplify_p.h" +#include <QtPositioning/private/qlocationutils_p.h> + +QT_BEGIN_NAMESPACE + +double QGeoSimplify::getDist(const QGeoCoordinate &p1, const QGeoCoordinate &p2) +{ + return p1.distanceTo(p2); +} + +QDoubleVector2D QGeoSimplify::closestPoint(const QDoubleVector2D &p, const QDoubleVector2D &a, const QDoubleVector2D &b) +{ + if (a == b) + return a; + + const double u = ((p.x() - a.x()) * (b.x() - a.x()) + (p.y() - a.y()) * (b.y() - a.y()) ) / (b - a).lengthSquared(); + const QDoubleVector2D intersection(a.x() + u * (b.x() - a.x()) , a.y() + u * (b.y() - a.y()) ); + QDoubleVector2D candidate = ( (p-a).length() < (p-b).length() ) ? a : b; + if (u > 0 && u < 1 + && (p-intersection).length() < (p-candidate).length() ) // And it falls in the segment + candidate = intersection; + return candidate; +} + +QGeoCoordinate QGeoSimplify::closestPoint(const QGeoCoordinate &pc, const QGeoCoordinate &ac, const QGeoCoordinate &bc, const double &leftBound) +{ + QDoubleVector2D p = QWebMercator::coordToMercator(pc); + if (p.x() < leftBound) + p.setX(p.x() + leftBound); // unwrap X + + QDoubleVector2D a = QWebMercator::coordToMercator(ac); + if (a.x() < leftBound) + a.setX(a.x() + leftBound); // unwrap X + + QDoubleVector2D b = QWebMercator::coordToMercator(bc); + if (b.x() < leftBound) + b.setX(b.x() + leftBound); // unwrap X + + QDoubleVector2D intersection = closestPoint(p, a, b); + if (intersection.x() > 1.0) + intersection.setX(intersection.x() - leftBound); // wrap X + + const QGeoCoordinate closest = QWebMercator::mercatorToCoord(intersection); + return closest; +} + +double QGeoSimplify::getSegDist(const QGeoCoordinate &pc, const QGeoCoordinate &ac, const QGeoCoordinate &bc, const double &leftBound) +{ + const QGeoCoordinate closest = closestPoint(pc, ac, bc, leftBound); + const double distanceMeters = pc.distanceTo(closest); + return distanceMeters; +} + +double QGeoSimplify::getSegDist(const QDoubleVector2D &p, const QDoubleVector2D &a, const QDoubleVector2D &b, const double &leftBound) +{ + QDoubleVector2D intersection = closestPoint(p, a, b); + return getDist(intersection, p, leftBound); +} + +void QGeoSimplify::simplifyDPStep(const QList<QGeoCoordinate> &points, const double &leftBound, int first, int last, double offsetTolerance, QList<QGeoCoordinate> &simplified) +{ + double maxDistanceFound = offsetTolerance; + int index = 0; + + for (int i = first + 1; i < last; i++) { + const double distance = getSegDist(points.at(i), + points.at(first), + points.at(last), + leftBound); + + if (distance > maxDistanceFound) { + index = i; + maxDistanceFound = distance; + } + } + + if (index > 0) { + if (index - first > 1) + simplifyDPStep(points, + leftBound, + first, + index, + offsetTolerance, + simplified); + simplified.append(points.at(index)); + if (last - index > 1) + simplifyDPStep(points, + leftBound, + index, + last, + offsetTolerance, + simplified); + } +} + +double QGeoSimplify::getDist(QDoubleVector2D a, QDoubleVector2D b, const double &leftBound) +{ + if (a.x() > 1.0) + a.setX(a.x() - leftBound); // wrap X + if (b.x() > 1.0) + b.setX(b.x() - leftBound); // wrap X + return QWebMercator::mercatorToCoord(a).distanceTo( + QWebMercator::mercatorToCoord(b)); +} + +void QGeoSimplify::simplifyDPStep(const QList<QDoubleVector2D> &points, + const double &leftBound, + int first, + int last, + double offsetTolerance, + QList<QDoubleVector2D> &simplified) +{ + double maxDistanceFound = offsetTolerance; + int index = 0; + + for (int i = first + 1; i < last; i++) { + const double distance = getSegDist(points.at(i), + points.at(first), + points.at(last), + leftBound); + + if (distance > maxDistanceFound) { + index = i; + maxDistanceFound = distance; + } + } + + if (index > 0) { + if (index - first > 1) + simplifyDPStep(points, + leftBound, + first, + index, + offsetTolerance, + simplified); + simplified.append(points.at(index)); + if (last - index > 1) + simplifyDPStep(points, + leftBound, + index, + last, + offsetTolerance, + simplified); + } +} + +static double pixelDistanceAtZoomAndLatitude(int zoom, double latitude) +{ + const double den = double((1 << (zoom + 8))); + const double pixelDist = (QLocationUtils::earthMeanCircumference() * + std::cos(QLocationUtils::radians(latitude))) / den; + return pixelDist; +} + +static QGeoCoordinate unwrappedToGeo(QDoubleVector2D p, double leftBound) +{ + if (p.x() > 1.0) + p.setX(p.x() - leftBound); + return QWebMercator::mercatorToCoord(p); +} + +void QGeoSimplify::simplifyDPStepZL(const QList<QDoubleVector2D> &points, + const double &leftBound, + int first, + int last, + int zoomLevel, + QList<QDoubleVector2D> &simplified) +{ + const QGeoCoordinate firstC = unwrappedToGeo(points.at(first), leftBound); + const QGeoCoordinate lastC = unwrappedToGeo(points.at(last), leftBound); + double maxDistanceFound = (pixelDistanceAtZoomAndLatitude(zoomLevel, firstC.latitude()) + + pixelDistanceAtZoomAndLatitude(zoomLevel, lastC.latitude())) * 0.5; + int index = 0; + + for (int i = first + 1; i < last; i++) { + const double distance = getSegDist(points.at(i), + points.at(first), + points.at(last), + leftBound); + + if (distance > maxDistanceFound) { + index = i; + maxDistanceFound = distance; + } + } + + if (index > 0) { + if (index - first > 1) + simplifyDPStepZL(points, + leftBound, + first, + index, + zoomLevel, + simplified); + simplified.append(points.at(index)); + if (last - index > 1) + simplifyDPStepZL(points, + leftBound, + index, + last, + zoomLevel, + simplified); + } +} + +QList<QGeoCoordinate> QGeoSimplify::simplifyDouglasPeucker(const QList<QGeoCoordinate> &points, + const double &leftBound, + double offsetTolerance) { + const int last = points.size() - 1; + QList<QGeoCoordinate> simplified { points.first() }; + simplifyDPStep(points, leftBound, 0, last, offsetTolerance, simplified); + simplified.append(points.at(last)); + return simplified; +} + +QList<QDoubleVector2D> QGeoSimplify::simplifyDouglasPeucker(const QList<QDoubleVector2D> &points, + const double &leftBound, + double offsetTolerance) { + const int last = points.size() - 1; + QList<QDoubleVector2D> simplified { points.first() }; + simplifyDPStep(points, leftBound, 0, last, offsetTolerance, simplified); + simplified.append(points.at(last)); + return simplified; +} + +QList<QDoubleVector2D> QGeoSimplify::simplifyDouglasPeuckerZL(const QList<QDoubleVector2D> &points, + const double &leftBound, + int zoomLevel) +{ + const int last = points.size() - 1; + QList<QDoubleVector2D> simplified { points.first() }; + simplifyDPStepZL(points, leftBound, 0, last, zoomLevel, simplified); + simplified.append(points.at(last)); + return simplified; +} + +QList<QGeoCoordinate> QGeoSimplify::geoSimplify(const QList<QGeoCoordinate> &points, + const double &leftBound, + double offsetTolerance) // also in meters +{ + if (points.size() <= 2) + return points; + return simplifyDouglasPeucker(points, + leftBound, + offsetTolerance); +} + +QList<QDoubleVector2D> QGeoSimplify::geoSimplify(const QList<QDoubleVector2D> &points, + const double &leftBound, + double offsetTolerance) // also in meters +{ + if (points.size() <= 2) + return points; + return simplifyDouglasPeucker(points, + leftBound, + offsetTolerance); +} + +QList<QDoubleVector2D> QGeoSimplify::geoSimplifyZL(const QList<QDoubleVector2D> &points, + const double &leftBound, + int zoomLevel) +{ + if (points.size() <= 2) + return points; + return simplifyDouglasPeuckerZL(points, + leftBound, + zoomLevel); +} + + +QT_END_NAMESPACE + diff --git a/src/location/declarativemaps/qgeosimplify_p.h b/src/location/declarativemaps/qgeosimplify_p.h new file mode 100644 index 00000000..19445552 --- /dev/null +++ b/src/location/declarativemaps/qgeosimplify_p.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Qt adaptation of geosimplify.js, https://github.com/mapbox/geosimplify-js, (c) 2017, Mapbox +** +** Copyright (C) 2020 Paolo Angelelli <paolo.angelelli@gmail.com> +** 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 QGEOSIMPLIFY_P_H +#define QGEOSIMPLIFY_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 <QtPositioning/QGeoCoordinate> +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QtPositioning/private/qwebmercator_p.h> + +QT_BEGIN_NAMESPACE + +class QGeoSimplify { +protected: + // Distance between two points in metres + static double getDist(const QGeoCoordinate &p1, const QGeoCoordinate &p2); + + // p, a and b are intended as "unwrapped" around the left bound + static QDoubleVector2D closestPoint( const QDoubleVector2D &p, + const QDoubleVector2D &a, + const QDoubleVector2D &b); + + static QGeoCoordinate closestPoint( const QGeoCoordinate &pc, + const QGeoCoordinate &ac, + const QGeoCoordinate &bc, + const double &leftBound); + + // Distance from a point to a segment (line between two points) in metres + static double getSegDist(const QGeoCoordinate &pc, + const QGeoCoordinate &ac, + const QGeoCoordinate &bc, + const double &leftBound); + + // doublevectors Intended as wrapped + static double getSegDist(const QDoubleVector2D &p, + const QDoubleVector2D &a, + const QDoubleVector2D &b, + const double &leftBound); + + static void simplifyDPStep(const QList<QGeoCoordinate> &points, + const double &leftBound, + int first, + int last, + double offsetTolerance, + QList<QGeoCoordinate> &simplified); + + static double getDist(QDoubleVector2D a, + QDoubleVector2D b, + const double &leftBound); + + static void simplifyDPStep(const QList<QDoubleVector2D> &points, + const double &leftBound, + int first, + int last, + double offsetTolerance, + QList<QDoubleVector2D> &simplified); + + static void simplifyDPStepZL(const QList<QDoubleVector2D> &points, + const double &leftBound, + int first, + int last, + int zoomLevel, + QList<QDoubleVector2D> &simplified); + + // simplification using Ramer-Douglas-Peucker algorithm + static QList<QGeoCoordinate> simplifyDouglasPeucker(const QList<QGeoCoordinate> &points, + const double &leftBound, + double offsetTolerance); + + static QList<QDoubleVector2D> simplifyDouglasPeucker(const QList<QDoubleVector2D> &points, + const double &leftBound, + double offsetTolerance); + + static QList<QDoubleVector2D> simplifyDouglasPeuckerZL(const QList<QDoubleVector2D> &points, + const double &leftBound, + int zoomLevel); + +public: + /* + offsetTolerance - how far outside the straight line a point + needs to be for it to be "kept" + */ + static QList<QGeoCoordinate> geoSimplify(const QList<QGeoCoordinate> &points, + const double &leftBound, + double offsetTolerance); // in meters + + static QList<QDoubleVector2D> geoSimplify(const QList<QDoubleVector2D> &points, + const double &leftBound, + double offsetTolerance); // in meters + + // This overload tries to be adaptive in the offsetTolerance across latitudes, + // and return a simplification adequate for the given zoomLevel. + static QList<QDoubleVector2D> geoSimplifyZL(const QList<QDoubleVector2D> &points, + const double &leftBound, + int zoomLevel); // in meters +}; + +QT_END_NAMESPACE + +#endif // QGEOSIMPLIFY_P_H diff --git a/src/location/labs/qsg/qmapcircleobjectqsg.cpp b/src/location/labs/qsg/qmapcircleobjectqsg.cpp index 5b1f5361..750b20f2 100644 --- a/src/location/labs/qsg/qmapcircleobjectqsg.cpp +++ b/src/location/labs/qsg/qmapcircleobjectqsg.cpp @@ -297,7 +297,7 @@ QSGNode *QMapCircleObjectPrivateQSG::updateMapObjectNodeGL(QSGNode *oldNode, combinedMatrix, cameraCenter, Qt::SquareCap, - true); + true); // No LOD for circles ATM m_dataGL->m_borderGeometry.setPreserveGeometry(false); m_dataGL->m_borderGeometry.markClean(); } diff --git a/src/location/labs/qsg/qmappolylineobjectqsg.cpp b/src/location/labs/qsg/qmappolylineobjectqsg.cpp index 817d4df5..8efbfc2f 100644 --- a/src/location/labs/qsg/qmappolylineobjectqsg.cpp +++ b/src/location/labs/qsg/qmappolylineobjectqsg.cpp @@ -102,6 +102,18 @@ void QMapPolylineObjectPrivateQSG::updateGeometry() m_borderGeometry.m_wrapOffset = p.projectionWrapFactor(m_leftBoundMercator) + 1; } +/*! + \internal +*/ +unsigned int QMapPolylineObjectPrivateQSG::zoomForLOD(int zoom) const +{ + // LOD Threshold currently fixed to 12 for MapPolylineObject(QSG). + // ToDo: Consider allowing to change this via DynamicParameter. + if (zoom >= 12) + return 30; + return uint(zoom); +} + QSGNode *QMapPolylineObjectPrivateQSG::updateMapObjectNode(QSGNode *oldNode, VisibleNode **visibleNode, QSGNode *root, @@ -127,7 +139,8 @@ QSGNode *QMapPolylineObjectPrivateQSG::updateMapObjectNode(QSGNode *oldNode, combinedMatrix, cameraCenter, Qt::SquareCap, - true); + true, + zoomForLOD(int(m_map->cameraData().zoomLevel()))); m_borderGeometry.setPreserveGeometry(false); m_borderGeometry.markClean(); } diff --git a/src/location/labs/qsg/qmappolylineobjectqsg_p_p.h b/src/location/labs/qsg/qmappolylineobjectqsg_p_p.h index 63ebcde9..8bba2703 100644 --- a/src/location/labs/qsg/qmappolylineobjectqsg_p_p.h +++ b/src/location/labs/qsg/qmappolylineobjectqsg_p_p.h @@ -85,6 +85,8 @@ public: QGeoMapObjectPrivate *clone() override; virtual QGeoShape geoShape() const override; + unsigned int zoomForLOD(int zoom) const; + // Data Members QDoubleVector2D m_leftBoundMercator; QGeoMapPolylineGeometryOpenGL m_borderGeometry; diff --git a/tests/manual/mapobjects_tester/main.qml b/tests/manual/mapobjects_tester/main.qml index 4695df3a..1e758ace 100644 --- a/tests/manual/mapobjects_tester/main.qml +++ b/tests/manual/mapobjects_tester/main.qml @@ -39,7 +39,7 @@ Window { visible: true width: 1440 height: 720 - title: qsTr("MapItems backends") + title: qsTr("MapObjects tester") property real initialZL: 5 @@ -365,6 +365,12 @@ Window { } } + LongPolyline { + id: longPoly + backend: polylineBackend() + Component.onCompleted: longPolyPath = path + } + MapCircle { center: QtPositioning.coordinate(52, 0) radius: sliRadius.value @@ -391,8 +397,8 @@ Window { center: QtPositioning.coordinate(17, 44); radius: 200*1000 color: "firebrick" - layer.enabled: (backend == MapCircle.Software) - layer.samples: 4 +// layer.enabled: (backend == MapCircle.Software) +// layer.samples: 4 } } } @@ -486,17 +492,27 @@ Window { border.color: 'firebrick' } -// MapCircleObject { -// id: circle1 -// border.color: 'deepskyblue' -// border.width: 26 -// center: QtPositioning.coordinate(17, 44); -// radius: 200*1000 -// color: "firebrick" -// } + MapPolylineObject { + id: longPolyline + line.color: "firebrick" + objectName: parent.objectName + "longPolyline" + line.width: 10 + path: longPolyPath + } + + MapCircleObject { + id: circle1 + border.color: 'deepskyblue' + border.width: 26 + center: QtPositioning.coordinate(17, 44); + radius: 200*1000 + color: "firebrick" + } } } + property var longPolyPath + C2.Slider { id: sliRadius orientation: Qt.Vertical diff --git a/tests/manual/mapobjects_tester/qml.qrc b/tests/manual/mapobjects_tester/qml.qrc index 5f6483ac..3a8a697d 100644 --- a/tests/manual/mapobjects_tester/qml.qrc +++ b/tests/manual/mapobjects_tester/qml.qrc @@ -1,5 +1,6 @@ <RCC> <qresource prefix="/"> <file>main.qml</file> + <file alias="LongPolyline.qml">../mappolyline_tester/LongPolyline.qml</file> </qresource> </RCC> |