summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Angelelli <paolo.angelelli.qt@gmail.com>2019-12-30 13:27:29 +0100
committerPaolo Angelelli <paolo.angelelli.qt@gmail.com>2020-02-12 11:24:45 +0000
commitbe7cbed7411d024d178377bd327d5916c80e02a0 (patch)
treed5840e89206157770188c4472bcc752f793a6a5d
parenta4469cad4041f21e640efa9ca5d0b192dd702955 (diff)
downloadqtlocation-be7cbed7411d024d178377bd327d5916c80e02a0.tar.gz
Add geometry simplification to MapPolyline/MapPolylineObjectQSG
This change introduces a metric-based implementation of the Ramer-Douglas-Peucker line simplification algorithm to generate a LOD pyramid for the polyline geometries. This comes with a related property (in MapItemBase), lodThreshold, that can be used to change the threshold after which no simplification will be used. By default the value of this property is 0, meaning that the behavior will be unchanged and no LOD will be used. This change also introduces LOD on map polyine objects QSG, for which no property is introduced, and there's a default threshold set to zoom level 12 (which appear to produce acceptable results). Finally, this patch makes use of a threadpool with 1 thread to enqueue geometry simplification tasks, which would otherwise freeze the UI when computing for the first time. Support for geometry simplification is currently added only to polylines. It might be of interest extending it to polygons as well, once a proper strategy for handling the simplification of inner holes has been identified. Finally, extending it to circles could be of interest, while potentially bringing only minor benefits, as circle geometries are currently fixed to 128 vertices. Also adds a MapObject-based delegate to the geojson viewer example. Task-number: QTBUG-46652 Task-number: QTBUG-38459 Task-number: QTBUG-49303 Change-Id: I64b5db4577962db17e5388812909285c9356ef0d Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--examples/location/geojson_viewer/GeoJsonDelegateMapObject.qml152
-rw-r--r--examples/location/geojson_viewer/main.qml33
-rw-r--r--examples/location/geojson_viewer/qml.qrc1
-rw-r--r--src/3rdparty/geosimplify.js/LICENSE27
-rw-r--r--src/3rdparty/geosimplify.js/qt_attribution.json13
-rw-r--r--src/imports/location/location.cpp2
-rw-r--r--src/location/declarativemaps/declarativemaps.pri2
-rw-r--r--src/location/declarativemaps/qdeclarativecirclemapitem_p_p.h3
-rw-r--r--src/location/declarativemaps/qdeclarativegeomapitembase.cpp27
-rw-r--r--src/location/declarativemaps/qdeclarativegeomapitembase_p.h8
-rw-r--r--src/location/declarativemaps/qdeclarativepolygonmapitem_p_p.h6
-rw-r--r--src/location/declarativemaps/qdeclarativepolylinemapitem.cpp207
-rw-r--r--src/location/declarativemaps/qdeclarativepolylinemapitem_p_p.h109
-rw-r--r--src/location/declarativemaps/qdeclarativerectanglemapitem_p_p.h3
-rw-r--r--src/location/declarativemaps/qgeosimplify.cpp313
-rw-r--r--src/location/declarativemaps/qgeosimplify_p.h147
-rw-r--r--src/location/labs/qsg/qmapcircleobjectqsg.cpp2
-rw-r--r--src/location/labs/qsg/qmappolylineobjectqsg.cpp15
-rw-r--r--src/location/labs/qsg/qmappolylineobjectqsg_p_p.h2
-rw-r--r--tests/manual/mapobjects_tester/main.qml38
-rw-r--r--tests/manual/mapobjects_tester/qml.qrc1
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 &center,
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>