diff options
Diffstat (limited to 'src/location/declarativemaps/qdeclarativepolylinemapitem.cpp')
-rw-r--r-- | src/location/declarativemaps/qdeclarativepolylinemapitem.cpp | 1075 |
1 files changed, 951 insertions, 124 deletions
diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp index d20d175f..d59704dc 100644 --- a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp @@ -35,11 +35,16 @@ ****************************************************************************/ #include "qdeclarativepolylinemapitem_p.h" +#include "qdeclarativepolylinemapitem_p_p.h" +#include "qdeclarativerectanglemapitem_p_p.h" +#include "qdeclarativecirclemapitem_p_p.h" #include "qlocationutils_p.h" +#include "qdeclarativegeomapitemutils_p.h" #include "error_messages_p.h" #include "locationvaluetypehelper_p.h" #include "qdoublevector2d_p.h" #include <QtLocation/private/qgeomap_p.h> +#include <QtPositioning/private/qwebmercator_p.h> #include <QtCore/QScopedValueRollback> #include <QtQml/QQmlInfo> @@ -55,10 +60,32 @@ #include <QtPositioning/private/qclipperutils_p.h> #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 @@ -125,7 +152,7 @@ static QList<QList<QDoubleVector2D> > clipLine( const QList<QDoubleVector2D> &poly) { QList<QList<QDoubleVector2D> > res; - if (poly.size() < 3 || l.size() < 2) + if (poly.size() < 2 || l.size() < 2) return res; // Step 1: build edges @@ -355,11 +382,6 @@ void QDeclarativeMapLineProperties::setWidth(qreal width) emit widthChanged(width_); } -struct Vertex -{ - QVector2D position; -}; - QGeoMapPolylineGeometry::QGeoMapPolylineGeometry() { } @@ -661,7 +683,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, // Create the viewport rect in the same coordinate system // as the actual points QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); - viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth); + viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth * 2, strokeWidth * 2); viewport.translate(-1 * origin); QVector<qreal> points; @@ -719,7 +741,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, } screenBounds_ = bb; - const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) : QPointF(); + const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) * 0.5: QPointF(); this->translate( -1 * sourceBounds_.topLeft() + strokeOffset); } @@ -747,16 +769,202 @@ bool QGeoMapPolylineGeometry::contains(const QPointF &point) const return false; } +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPolygon &poly) +{ + if (!sourceDirty_) + return; + QGeoPath p(poly.path()); + if (poly.path().size() && poly.path().last() != poly.path().first()) + p.addCoordinate(poly.path().first()); + updateSourcePoints(map, p); +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoPath &poly) +{ + if (!sourceDirty_) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + + // build the actual path + // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints + + + QDoubleVector2D leftBoundWrapped; + // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + QList<QDoubleVector2D> wrappedPath; + QDeclarativeGeoMapItemUtils::wrapPath(poly.path(), geoLeftBound_, p, + wrappedPath, &leftBoundWrapped); + + const QGeoRectangle &boundingRectangle = poly.boundingGeoRectangle(); + updateSourcePoints(p, wrappedPath, boundingRectangle); +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoProjectionWebMercator &p, + const QList<QDoubleVector2D> &wrappedPath, + const QGeoRectangle &boundingRectangle) { + if (!sourceDirty_) + return; + // 1.1) do the same for the bbox + // Beware: vertical lines (or horizontal lines) might have an "empty" bbox. Check for that + + QGeoCoordinate topLeft = boundingRectangle.topLeft(); + QGeoCoordinate bottomRight = boundingRectangle.bottomRight(); + const qreal epsilon = 0.000001; + if (qFuzzyCompare(topLeft.latitude(), bottomRight.latitude())) { + topLeft.setLatitude(qBound(-90.0, topLeft.latitude() + epsilon ,90.0)); + bottomRight.setLatitude(qBound(-90.0, bottomRight.latitude() - epsilon ,90.0)); + } + if (qFuzzyCompare(topLeft.longitude(), bottomRight.longitude())) { + topLeft.setLongitude(QLocationUtils::wrapLong(topLeft.longitude() - epsilon)); + bottomRight.setLongitude(QLocationUtils::wrapLong(bottomRight.longitude() + epsilon)); + } + QGeoPolygon bbox(QGeoRectangle(topLeft, bottomRight)); + QList<QDoubleVector2D> wrappedBbox, wrappedBboxPlus1, wrappedBboxMinus1; + QDeclarativeGeoMapItemUtils::wrapPath(bbox.path(), bbox.boundingGeoRectangle().topLeft(), p, + wrappedBbox, wrappedBboxMinus1, wrappedBboxPlus1, &m_bboxLeftBoundWrapped); + + // 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; + m_wrappedPolygons[1].wrappedBboxes = wrappedBbox; + m_wrappedPolygons[2].wrappedBboxes = wrappedBboxPlus1; + srcOrigin_ = geoLeftBound_; +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoRectangle &rect) +{ + const QGeoPath path(QDeclarativeRectangleMapItemPrivateCPU::perimeter(rect)); + updateSourcePoints(map, path); +} + +void QGeoMapPolylineGeometryOpenGL::updateSourcePoints(const QGeoMap &map, const QGeoCircle &circle) +{ + if (!sourceDirty_) + return; + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + + QDoubleVector2D leftBoundWrapped; + // 1) pre-compute 3 sets of "wrapped" coordinates: one w regular mercator, one w regular mercator +- 1.0 + QList<QGeoCoordinate> path; + QGeoCoordinate leftBound; + QList<QDoubleVector2D> wrappedPath; + QDeclarativeCircleMapItemPrivateCPU::calculatePeripheralPoints(path, circle.center(), circle.radius(), QDeclarativeCircleMapItemPrivateCPU::CircleSamples, leftBound); + path << path.first(); + geoLeftBound_ = leftBound; + QDeclarativeGeoMapItemUtils::wrapPath(path, leftBound, p, wrappedPath, &leftBoundWrapped); + const QGeoRectangle &boundingRectangle = circle.boundingGeoRectangle(); + updateSourcePoints(p, wrappedPath, boundingRectangle); +} + +void QGeoMapPolylineGeometryOpenGL::updateScreenPoints(const QGeoMap &map, qreal strokeWidth, bool /*adjustTranslation*/) +{ + if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { + clear(); + return; + } + + // 1) identify which set to use: std, +1 or -1 + const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection()); + const QDoubleVector2D leftBoundMercator = p.geoToMapProjection(srcOrigin_); + m_wrapOffset = p.projectionWrapFactor(leftBoundMercator) + 1; // +1 to get the offset into QLists + + if (sourceDirty_) { + // 1.1) select geometry set + // This could theoretically be skipped for those polylines whose bbox is not even projectable. + // However, such optimization could only be introduced if not calculating bboxes lazily. + // Hence not doing it. +// if (m_screenVertices.size() > 1) + m_dataChanged = true; + } + + updateQuickGeometry(p, strokeWidth); +} + +void QGeoMapPolylineGeometryOpenGL::updateQuickGeometry(const QGeoProjectionWebMercator &p, qreal strokeWidth) +{ + // 2) clip bbox + // BBox handling -- this is related to the bounding box geometry + // that has to inevitably follow the old projection codepath + // As it needs to provide projected coordinates for QtQuick interaction. + // This could be futher optimized to be updated in a lazy fashion. + const QList<QDoubleVector2D> &wrappedBbox = m_wrappedPolygons.at(m_wrapOffset).wrappedBboxes; + QList<QList<QDoubleVector2D> > clippedBbox; + QDoubleVector2D bboxLeftBoundWrapped = m_bboxLeftBoundWrapped; + bboxLeftBoundWrapped.setX(bboxLeftBoundWrapped.x() + double(m_wrapOffset - 1)); + QDeclarativeGeoMapItemUtils::clipPolygon(wrappedBbox, p, clippedBbox, &bboxLeftBoundWrapped, false); + + // 3) project bbox + QPainterPath ppi; + + if ( !clippedBbox.size() || + clippedBbox.first().size() < 3) { + sourceBounds_ = screenBounds_ = QRectF(); + firstPointOffset_ = QPointF(); + screenOutline_ = ppi; + return; + } + + QDeclarativeGeoMapItemUtils::projectBbox(clippedBbox.first(), p, ppi); // Using first because a clipped box should always result in one polygon + const QRectF brect = ppi.boundingRect(); + firstPointOffset_ = QPointF(brect.topLeft()); + sourceBounds_ = brect; + screenOutline_ = ppi; + + // 4) Set Screen bbox + screenBounds_ = brect; + sourceBounds_.setX(0); + sourceBounds_.setY(0); + sourceBounds_.setWidth(brect.width() + strokeWidth); + sourceBounds_.setHeight(brect.height() + strokeWidth); +} + +/* + * QDeclarativePolygonMapItem Private Implementations + */ + +QDeclarativePolylineMapItemPrivate::~QDeclarativePolylineMapItemPrivate() {} + + +QDeclarativePolylineMapItemPrivateCPU::~QDeclarativePolylineMapItemPrivateCPU() {} + +QDeclarativePolylineMapItemPrivateOpenGLLineStrip::~QDeclarativePolylineMapItemPrivateOpenGLLineStrip() {} + +QDeclarativePolylineMapItemPrivateOpenGLExtruded::~QDeclarativePolylineMapItemPrivateOpenGLExtruded() {} + +/* + * QDeclarativePolygonMapItem Implementation + */ + +struct PolylineBackendSelector +{ + PolylineBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativePolylineMapItem::OpenGLExtruded : QDeclarativePolylineMapItem::Software; + } + QDeclarativePolylineMapItem::Backend backend = QDeclarativePolylineMapItem::Software; +}; + +Q_GLOBAL_STATIC(PolylineBackendSelector, mapPolylineBackendSelector) + QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent) -: QDeclarativeGeoMapItemBase(parent), line_(this), dirtyMaterial_(true), updatingGeometry_(false) +: QDeclarativeGeoMapItemBase(parent), + m_line(this), + m_dirtyMaterial(true), + m_updatingGeometry(false), + m_d(new QDeclarativePolylineMapItemPrivateCPU(*this)) { m_itemType = QGeoMap::MapPolyline; - geopath_ = QGeoPathEager(); + m_geopath = QGeoPathEager(); setFlag(ItemHasContents, true); - QObject::connect(&line_, SIGNAL(colorChanged(QColor)), + QObject::connect(&m_line, SIGNAL(colorChanged(QColor)), this, SLOT(updateAfterLinePropertiesChanged())); - QObject::connect(&line_, SIGNAL(widthChanged(qreal)), + QObject::connect(&m_line, SIGNAL(widthChanged(qreal)), this, SLOT(updateAfterLinePropertiesChanged())); + setBackend(mapPolylineBackendSelector->backend); } QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() @@ -768,9 +976,7 @@ QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() */ void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() { - // mark dirty just in case we're a width change - geometry_.markSourceDirty(); - polishAndUpdate(); + m_d->onLinePropertiesChanged(); } /*! @@ -779,11 +985,8 @@ void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) { QDeclarativeGeoMapItemBase::setMap(quickMap,map); - if (map) { - regenerateCache(); - geometry_.markSourceDirty(); - polishAndUpdate(); - } + if (map) + m_d->onMapSet(); } /*! @@ -795,7 +998,7 @@ void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap * QJSValue QDeclarativePolylineMapItem::path() const { - return fromList(this, geopath_.path()); + return fromList(this, m_geopath.path()); } void QDeclarativePolylineMapItem::setPath(const QJSValue &value) @@ -817,13 +1020,11 @@ void QDeclarativePolylineMapItem::setPath(const QJSValue &value) */ void QDeclarativePolylineMapItem::setPath(const QGeoPath &path) { - if (geopath_.path() == path.path()) + if (m_geopath.path() == path.path()) return; - geopath_ = QGeoPathEager(path); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopath = QGeoPathEager(path); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -832,14 +1033,12 @@ void QDeclarativePolylineMapItem::setPath(const QGeoPath &path) */ void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> &path) { - if (geopath_.path() == path) + if (m_geopath.path() == path) return; - geopath_.setPath(path); + m_geopath.setPath(path); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -854,7 +1053,7 @@ void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> */ int QDeclarativePolylineMapItem::pathLength() const { - return geopath_.path().length(); + return m_geopath.path().length(); } /*! @@ -869,11 +1068,9 @@ void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate if (!coordinate.isValid()) return; - geopath_.addCoordinate(coordinate); + m_geopath.addCoordinate(coordinate); - updateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryUpdated(); emit pathChanged(); } @@ -888,14 +1085,12 @@ void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate */ void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate) { - if (index < 0 || index > geopath_.path().length()) + if (index < 0 || index > m_geopath.path().length()) return; - geopath_.insertCoordinate(index, coordinate); + m_geopath.insertCoordinate(index, coordinate); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -911,14 +1106,12 @@ void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordina */ void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate) { - if (index < 0 || index >= geopath_.path().length()) + if (index < 0 || index >= m_geopath.path().length()) return; - geopath_.replaceCoordinate(index, coordinate); + m_geopath.replaceCoordinate(index, coordinate); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -933,10 +1126,10 @@ void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordin */ QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const { - if (index < 0 || index >= geopath_.path().length()) + if (index < 0 || index >= m_geopath.path().length()) return QGeoCoordinate(); - return geopath_.coordinateAt(index); + return m_geopath.coordinateAt(index); } /*! @@ -948,7 +1141,7 @@ QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const */ bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate) { - return geopath_.containsCoordinate(coordinate); + return m_geopath.containsCoordinate(coordinate); } /*! @@ -963,13 +1156,12 @@ bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coord */ void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate) { - int length = geopath_.path().length(); - geopath_.removeCoordinate(coordinate); - if (geopath_.path().length() == length) + int length = m_geopath.path().length(); + m_geopath.removeCoordinate(coordinate); + if (m_geopath.path().length() == length) return; - regenerateCache(); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -986,14 +1178,12 @@ void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordin */ void QDeclarativePolylineMapItem::removeCoordinate(int index) { - if (index < 0 || index >= geopath_.path().length()) + if (index < 0 || index >= m_geopath.path().length()) return; - geopath_.removeCoordinate(index); + m_geopath.removeCoordinate(index); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); emit pathChanged(); } @@ -1013,7 +1203,47 @@ void QDeclarativePolylineMapItem::removeCoordinate(int index) QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() { - return &line_; + return &m_line; +} + +/*! + \qmlproperty MapPolyline.Backend QtLocation::MapPolyline::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapPolyline.Software and \b{MapPolyline.OpenGLLineStrip} + and \b{MapPolyline.OpenGLExtruded}. + The default value is \b{MapPolyline.Software}. + + \note \b{The release of this API with Qt 5.15 is a Technology Preview}. + Ideally, as the OpenGL backends for map items mature, there will be + no more need to also offer the legacy software-projection backend. + So this property will likely disappear at some later point. + To select OpenGL-accelerated item backends without using this property, + it is also possible to set the environment variable \b QTLOCATION_OPENGL_ITEMS + to \b{1}. + Also note that all current OpenGL backends won't work as expected when enabling + layers on the individual item, or when running on OpenGL core profiles greater than 2.x. + + \since 5.15 +*/ +QDeclarativePolylineMapItem::Backend QDeclarativePolylineMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativePolylineMapItem::setBackend(QDeclarativePolylineMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + QScopedPointer<QDeclarativePolylineMapItemPrivate> d((m_backend == Software) + ? static_cast<QDeclarativePolylineMapItemPrivate *>(new QDeclarativePolylineMapItemPrivateCPU(*this)) + : ((m_backend == OpenGLExtruded) + ? static_cast<QDeclarativePolylineMapItemPrivate * >(new QDeclarativePolylineMapItemPrivateOpenGLExtruded(*this)) + : static_cast<QDeclarativePolylineMapItemPrivate * >(new QDeclarativePolylineMapItemPrivateOpenGLLineStrip(*this)))); + m_d.swap(d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); } /*! @@ -1021,7 +1251,7 @@ QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() */ void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!map() || !geopath_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + if (newGeometry.topLeft() == oldGeometry.topLeft() || !map() || !m_geopath.isValid() || m_updatingGeometry) { QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); return; } @@ -1035,10 +1265,8 @@ void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, con if (offsetLati == 0.0 && offsetLongi == 0.0) return; - geopath_.translate(offsetLati, offsetLongi); - regenerateCache(); - geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); - markSourceDirtyAndUpdate(); + m_geopath.translate(offsetLati, offsetLongi); + m_d->onGeoGeometryChanged(); emit pathChanged(); // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested @@ -1050,68 +1278,78 @@ void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, con */ void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) { - if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + if (event.mapSize.isEmpty()) return; - geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); - markSourceDirtyAndUpdate(); + m_d->afterViewportChanged(); } /*! \internal */ -void QDeclarativePolylineMapItem::regenerateCache() +void QDeclarativePolylineMapItem::updatePolish() { if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - geopathProjected_.clear(); - geopathProjected_.reserve(geopath_.path().size()); - for (const QGeoCoordinate &c : geopath_.path()) - geopathProjected_ << p.geoToMapProjection(c); + m_d->updatePolish(); } -/*! - \internal -*/ -void QDeclarativePolylineMapItem::updateCache() +void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, + const char *propertyName, + bool update) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) - return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - geopathProjected_ << p.geoToMapProjection(geopath_.path().last()); + static const QByteArrayList acceptedParameterTypes = QByteArrayList() + << QByteArrayLiteral("lineCap") + << QByteArrayLiteral("pen"); + switch (acceptedParameterTypes.indexOf(QByteArray(propertyName))) { + case -1: + qWarning() << "Invalid property " << QLatin1String(propertyName) << " for parameter lineStyle"; + break; + case 0: // lineCap + { + const QVariant lineCap = p->property("lineCap"); + m_d->m_penCapStyle = lineCap.value<Qt::PenCapStyle>(); // if invalid, will return 0 == FlatCap + if (update) + markSourceDirtyAndUpdate(); + break; + } + case 1: // penStyle + { + const QVariant penStyle = p->property("pen"); + m_d->m_penStyle = penStyle.value<Qt::PenStyle>(); + if (m_d->m_penStyle == Qt::NoPen) + m_d->m_penStyle = Qt::SolidLine; + if (update) + markSourceDirtyAndUpdate(); + break; + } + } } -/*! - \internal -*/ -void QDeclarativePolylineMapItem::updatePolish() +void QDeclarativePolylineMapItem::updateLineStyleParameter(QGeoMapParameter *p, const char *propertyName) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) - return; - if (geopath_.path().length() == 0) { // Possibly cleared - geometry_.clear(); - setWidth(0); - setHeight(0); - return; - } - - QScopedValueRollback<bool> rollback(updatingGeometry_); - updatingGeometry_ = true; - - geometry_.updateSourcePoints(*map(), geopathProjected_, geopath_.boundingGeoRectangle().topLeft()); - geometry_.updateScreenPoints(*map(), line_.width()); - - setWidth(geometry_.sourceBoundingBox().width() + 2 * line_.width()); - setHeight(geometry_.sourceBoundingBox().height() + 2 * line_.width()); + updateLineStyleParameter(p, propertyName, true); +} - setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft() + QPointF(line_.width(), line_.width())); +void QDeclarativePolylineMapItem::componentComplete() +{ + QQuickItem::componentComplete(); + // Set up Dynamic Parameters + QList<QGeoMapParameter *> dynamicParameters = quickChildren<QGeoMapParameter>(); + for (QGeoMapParameter *p : qAsConst(dynamicParameters)) { + if (p->type() == QLatin1String("lineStyle")) { + updateLineStyleParameter(p, "lineCap", false); + updateLineStyleParameter(p, "pen", false); + connect(p, &QGeoMapParameter::propertyUpdated, + this, static_cast<void (QDeclarativePolylineMapItem::*)(QGeoMapParameter *, const char *)>(&QDeclarativePolylineMapItem::updateLineStyleParameter)); + markSourceDirtyAndUpdate(); + } + } } void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate() { - geometry_.markSourceDirty(); - polishAndUpdate(); + m_d->markSourceDirtyAndUpdate(); } /*! @@ -1119,32 +1357,17 @@ void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate() */ QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - Q_UNUSED(data); - - MapPolylineNode *node = static_cast<MapPolylineNode *>(oldNode); - - if (!node) { - node = new MapPolylineNode(); - } - - //TODO: update only material - if (geometry_.isScreenDirty() || dirtyMaterial_ || !oldNode) { - node->update(line_.color(), &geometry_); - geometry_.setPreserveGeometry(false); - geometry_.markClean(); - dirtyMaterial_ = false; - } - return node; + return m_d->updateMapItemPaintNode(oldNode, data); } bool QDeclarativePolylineMapItem::contains(const QPointF &point) const { - return geometry_.contains(point); + return m_d->contains(point); } const QGeoShape &QDeclarativePolylineMapItem::geoShape() const { - return geopath_; + return m_geopath; } void QDeclarativePolylineMapItem::setGeoShape(const QGeoShape &shape) @@ -1254,4 +1477,608 @@ void MapPolylineNode::update(const QColor &fillColor, } } +MapPolylineNodeOpenGLLineStrip::MapPolylineNodeOpenGLLineStrip() +: geometry_(QSGGeometry::defaultAttributes_Point2D(), 0) +{ + geometry_.setDrawingMode(QSGGeometry::DrawLineStrip); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); +} + +MapPolylineNodeOpenGLLineStrip::~MapPolylineNodeOpenGLLineStrip() +{ + +} + +void MapPolylineNodeOpenGLLineStrip::update(const QColor &fillColor, + const qreal lineWidth, + const QGeoMapPolylineGeometryOpenGL *shape, + const QMatrix4x4 &geoProjection, + const QDoubleVector3D ¢er, + const Qt::PenCapStyle /*capStyle*/) +{ + if (shape->m_screenVertices->size() < 2) { + setSubtreeBlocked(true); + return; + } else { + setSubtreeBlocked(false); + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + if (shape->m_dataChanged) { + shape->allocateAndFillLineStrip(fill); + markDirty(DirtyGeometry); + shape->m_dataChanged = false; + } + fill->setLineWidth(lineWidth); + fill_material_.setLineWidth(lineWidth); // to make the material not compare equal if linewidth changes + +// if (fillColor != fill_material_.color()) + { + fill_material_.setWrapOffset(shape->m_wrapOffset - 1); + fill_material_.setColor(fillColor); + fill_material_.setGeoProjection(geoProjection); + fill_material_.setCenter(center); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +MapPolylineShaderLineStrip::MapPolylineShaderLineStrip() : QSGMaterialShader(*new QSGMaterialShaderPrivate) +{ + +} + +void MapPolylineShaderLineStrip::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) +{ + Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); + MapPolylineMaterial *oldMaterial = static_cast<MapPolylineMaterial *>(oldEffect); + MapPolylineMaterial *newMaterial = static_cast<MapPolylineMaterial *>(newEffect); + + const QColor &c = newMaterial->color(); + const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); + const QDoubleVector3D ¢er = newMaterial->center(); + + QVector3D vecCenter, vecCenter_lowpart; + for (int i = 0; i < 3; i++) + QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]); + + if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { + float opacity = state.opacity() * c.alphaF(); + QVector4D v(c.redF() * opacity, + c.greenF() * opacity, + c.blueF() * opacity, + opacity); + program()->setUniformValue(m_color_id, v); + } + + if (state.isMatrixDirty()) + { + program()->setUniformValue(m_matrix_id, state.projectionMatrix()); + } + + program()->setUniformValue(m_mapProjection_id, geoProjection); + + program()->setUniformValue(m_center_id, vecCenter); + program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart); + program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset())); +} + +const char * const *MapPolylineShaderLineStrip::attributeNames() const +{ + static char const *const attr[] = { "vertex", nullptr }; + return attr; +} + +QSGMaterialShader *MapPolylineMaterial::createShader() const +{ + return new MapPolylineShaderLineStrip(); +} + +QSGMaterialType *MapPolylineMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + +int MapPolylineMaterial::compare(const QSGMaterial *other) const +{ + const MapPolylineMaterial &o = *static_cast<const MapPolylineMaterial *>(other); + if (o.m_center == m_center && o.m_geoProjection == m_geoProjection && o.m_wrapOffset == m_wrapOffset && o.m_lineWidth == m_lineWidth) + return QSGFlatColorMaterial::compare(other); + return -1; +} + +const QSGGeometry::AttributeSet &MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated() +{ + return MapPolylineEntry::attributes(); +} + +MapPolylineNodeOpenGLExtruded::MapPolylineNodeOpenGLExtruded() +: m_geometryTriangulating(MapPolylineNodeOpenGLExtruded::attributesMapPolylineTriangulated(), + 0 /* vtx cnt */, 0 /* index cnt */, QSGGeometry::UnsignedIntType /* index type */) +{ + m_geometryTriangulating.setDrawingMode(QSGGeometry::DrawTriangles); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&m_geometryTriangulating); +} + +MapPolylineNodeOpenGLExtruded::~MapPolylineNodeOpenGLExtruded() +{ + +} + +bool QGeoMapPolylineGeometryOpenGL::allocateAndFillEntries(QSGGeometry *geom, + bool closed, + unsigned int zoom) const +{ + // 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 true; + } + const int numSegments = (v.size() - 1); + + const int numIndices = numSegments * 6; // six vertices per line segment + geom->allocate(numIndices); + MapPolylineNodeOpenGLExtruded::MapPolylineEntry *vertices = + static_cast<MapPolylineNodeOpenGLExtruded::MapPolylineEntry *>(geom->vertexData()); + + for (int i = 0; i < numSegments; ++i) { + MapPolylineNodeOpenGLExtruded::MapPolylineEntry e; + const QDeclarativeGeoMapItemUtils::vec2 &cur = v[i]; + const QDeclarativeGeoMapItemUtils::vec2 &next = v[i+1]; + e.triangletype = 1.0; + e.next = next; + e.prev = cur; + e.pos = cur; + e.direction = 1.0; + e.vertextype = -1.0; + vertices[i*6] = e; + e.direction = -1.0; + vertices[i*6+1] = e; + e.pos = next; + e.vertextype = 1.0; + vertices[i*6+2] = e; + + // Second tri + e.triangletype = -1.0; + e.direction = -1.0; + vertices[i*6+3] = e; + e.direction = 1.0; + vertices[i*6+4] = e; + e.pos = cur; + e.vertextype = -1.0; + vertices[i*6+5] = e; + + if (i != 0) { + vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[i-1]; + } else { + if (closed) { + vertices[i*6].prev = vertices[i*6+1].prev = vertices[i*6+5].prev = v[numSegments - 1]; + } else { + vertices[i*6].triangletype = vertices[i*6+1].triangletype = vertices[i*6+5].triangletype = 2.0; + } + } + if (i != numSegments - 1) { + vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[i+2]; + } else { + if (closed) { + vertices[i*6+2].next = vertices[i*6+3].next = vertices[i*6+4].next = v[1]; + } else { + vertices[i*6+2].triangletype = vertices[i*6+3].triangletype = vertices[i*6+4].triangletype = 3.0; + } + } + } + return true; +} + +void QGeoMapPolylineGeometryOpenGL::allocateAndFillLineStrip(QSGGeometry *geom, + int lod) const +{ + // 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(); + for (int i = 0; i < vx.size(); ++i) + pts[i].set(vx[i].x, vx[i].y); +} + +void MapPolylineNodeOpenGLExtruded::update(const QColor &fillColor, + const float lineWidth, + const QGeoMapPolylineGeometryOpenGL *shape, + const QMatrix4x4 geoProjection, + const QDoubleVector3D center, + const Qt::PenCapStyle capStyle, + bool closed, + unsigned int zoom) +{ + // shape->size() == number of triangles + if (shape->m_screenVertices->size() < 2 + || lineWidth < 0.5 || fillColor.alpha() == 0) { // number of points + setSubtreeBlocked(true); + return; + } else { + setSubtreeBlocked(false); + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + 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 +// if (fillColor != fill_material_.color()) + { + fill_material_.setWrapOffset(shape->m_wrapOffset - 1); + fill_material_.setColor(fillColor); + fill_material_.setGeoProjection(geoProjection); + fill_material_.setCenter(center); + fill_material_.setLineWidth(lineWidth); + fill_material_.setMiter(capStyle != Qt::FlatCap); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +MapPolylineShaderExtruded::MapPolylineShaderExtruded() : QSGMaterialShader(*new QSGMaterialShaderPrivate) +{ + +} + +void MapPolylineShaderExtruded::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) +{ + Q_ASSERT(oldEffect == nullptr || newEffect->type() == oldEffect->type()); + MapPolylineMaterialExtruded *oldMaterial = static_cast<MapPolylineMaterialExtruded *>(oldEffect); + MapPolylineMaterialExtruded *newMaterial = static_cast<MapPolylineMaterialExtruded *>(newEffect); + + const QColor &c = newMaterial->color(); + const QMatrix4x4 &geoProjection = newMaterial->geoProjection(); + const QDoubleVector3D ¢er = newMaterial->center(); + + QVector3D vecCenter, vecCenter_lowpart; + for (int i = 0; i < 3; i++) + QLocationUtils::split_double(center.get(i), &vecCenter[i], &vecCenter_lowpart[i]); + + if (oldMaterial == nullptr || c != oldMaterial->color() || state.isOpacityDirty()) { + float opacity = state.opacity() * c.alphaF(); + QVector4D v(c.redF() * opacity, + c.greenF() * opacity, + c.blueF() * opacity, + opacity); + program()->setUniformValue(m_color_id, v); + } + + if (state.isMatrixDirty()) + { + program()->setUniformValue(m_matrix_id, state.projectionMatrix()); + } + + // ToDo: dirty-flag all this + program()->setUniformValue(m_mapProjection_id, geoProjection); + + program()->setUniformValue(m_center_id, vecCenter); + program()->setUniformValue(m_center_lowpart_id, vecCenter_lowpart); + program()->setUniformValue(m_miter_id, newMaterial->miter()); + program()->setUniformValue(m_lineWidth_id, newMaterial->lineWidth()); + program()->setUniformValue(m_wrapOffset_id, float(newMaterial->wrapOffset())); + + const QRectF viewportRect = state.viewportRect(); + const float aspect = float(viewportRect.width() / viewportRect.height()); + program()->setUniformValue(m_aspect_id, aspect); +} + +const char * const *MapPolylineShaderExtruded::attributeNames() const +{ + return MapPolylineNodeOpenGLExtruded::MapPolylineEntry::attributeNames(); +} + +QSGMaterialShader *MapPolylineMaterialExtruded::createShader() const +{ + return new MapPolylineShaderExtruded(); +} + +QSGMaterialType *MapPolylineMaterialExtruded::type() const +{ + static QSGMaterialType type; + return &type; +} + +int MapPolylineMaterialExtruded::compare(const QSGMaterial *other) const +{ + const MapPolylineMaterialExtruded &o = *static_cast<const MapPolylineMaterialExtruded *>(other); + if (o.m_miter == m_miter) + return MapPolylineMaterial::compare(other); + return -1; +} + +const char *MapPolylineShaderExtruded::vertexShaderMiteredSegments() const +{ + return + "attribute highp vec4 vertex;\n" + "attribute highp vec4 previous;\n" + "attribute highp vec4 next;\n" + "attribute lowp float direction;\n" + "attribute lowp float triangletype;\n" + "attribute lowp float vertextype;\n" // -1.0 if it is the "left" end of the segment, 1.0 if it is the "right" end. + "\n" + "uniform highp mat4 qt_Matrix;\n" + "uniform highp mat4 mapProjection;\n" + "uniform highp vec3 center;\n" + "uniform highp vec3 center_lowpart;\n" + "uniform lowp float lineWidth;\n" + "uniform lowp float aspect;\n" + "uniform lowp int miter;\n" // currently unused + "uniform lowp vec4 color;\n" + "uniform lowp float wrapOffset;\n" + "\n" + "varying vec4 primitivecolor;\n" + "\n" + " \n" + "vec4 wrapped(in vec4 v) { return vec4(v.x + wrapOffset, v.y, 0.0, 1.0); }\n" + "void main() {\n" // ln 22 + " primitivecolor = color;\n" + " vec2 aspectVec = vec2(aspect, 1.0);\n" + " mat4 projViewModel = qt_Matrix * mapProjection;\n" + " vec4 cur = wrapped(vertex) - vec4(center, 0.0);\n" + " cur = cur - vec4(center_lowpart, 0.0);\n" + " vec4 prev = wrapped(previous) - vec4(center, 0.0);\n" + " prev = prev - vec4(center_lowpart, 0.0);\n" + " vec4 nex = wrapped(next) - vec4(center, 0.0);\n" + " nex = nex - vec4(center_lowpart, 0.0);\n" + "\n" + " vec4 centerProjected = projViewModel * vec4(center, 1.0);\n" + " vec4 previousProjected = projViewModel * prev;\n" + " vec4 currentProjected = projViewModel * cur;\n" + " vec4 nextProjected = projViewModel * nex;\n" + "\n" + " //get 2D screen space with W divide and aspect correction\n" + " vec2 currentScreen = (currentProjected.xy / currentProjected.w) * aspectVec;\n" + " vec2 previousScreen = (previousProjected.xy / previousProjected.w) * aspectVec;\n" + " vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n" + " float len = (lineWidth);\n" + " float orientation = direction;\n" + " bool clipped = false;\n" + " bool otherEndBelowFrustum = false;\n" + " //starting point uses (next - current)\n" + " vec2 dir = vec2(0.0);\n" + " if (vertextype < 0.0) {\n" + " dir = normalize(nextScreen - currentScreen);\n" + " if (nextProjected.z < 0.0) dir = -dir;\n" + " } else { \n" + " dir = normalize(currentScreen - previousScreen);\n" + " if (previousProjected.z < 0.0) dir = -dir;\n" + " }\n" + // first, clip current, and make sure currentProjected.z is > 0 + " if (currentProjected.z < 0.0) {\n" + " if ((nextProjected.z > 0.0 && vertextype < 0.0) || (vertextype > 0.0 && previousProjected.z > 0.0)) {\n" + " dir = -dir;\n" + " clipped = true;\n" + " if (vertextype < 0.0 && nextProjected.y / nextProjected.w < -1.0) otherEndBelowFrustum = true;\n" + " else if (vertextype > 0.0 && previousProjected.y / previousProjected.w < -1.0) otherEndBelowFrustum = true;\n" + " } else {\n" + " primitivecolor = vec4(0.0,0.0,0.0,0.0);\n" + " gl_Position = vec4(-10000000.0, -1000000000.0, -1000000000.0, 1);\n" // get the vertex out of the way if the segment is fully invisible + " return;\n" + " }\n" + " } else if (triangletype < 2.0) {\n" // vertex in the view, try to miter + " //get directions from (C - B) and (B - A)\n" + " vec2 dirA = normalize((currentScreen - previousScreen));\n" + " if (previousProjected.z < 0.0) dirA = -dirA;\n" + " vec2 dirB = normalize((nextScreen - currentScreen));\n" + " //now compute the miter join normal and length\n" + " if (nextProjected.z < 0.0) dirB = -dirB;\n" + " vec2 tangent = normalize(dirA + dirB);\n" + " vec2 perp = vec2(-dirA.y, dirA.x);\n" + " vec2 vmiter = vec2(-tangent.y, tangent.x);\n" + " len = lineWidth / dot(vmiter, perp);\n" + // The following is an attempt to have a segment-length based miter threshold. + // A mediocre workaround until better mitering will be added. + " float lenTreshold = clamp( min(length((currentProjected.xy - previousProjected.xy) / aspectVec)," + " length((nextProjected.xy - currentProjected.xy) / aspectVec)), 3.0, 6.0 ) * 0.5;\n" + " if (len < lineWidth * lenTreshold && len > -lineWidth * lenTreshold \n" + " ) {\n" + " dir = tangent;\n" + " } else {\n" + " len = lineWidth;\n" + " }\n" + " }\n" + " vec4 offset;\n" + " if (!clipped) {\n" + " vec2 normal = normalize(vec2(-dir.y, dir.x));\n" + " normal *= len;\n" // fracZL apparently was needed before the (-2.0 / qt_Matrix[1][1]) factor was introduced + " normal /= aspectVec;\n" // straighten the normal up again + " float scaleFactor = currentProjected.w / centerProjected.w;\n" + " offset = vec4(normal * orientation * scaleFactor * (centerProjected.w / (-2.0 / qt_Matrix[1][1])), 0.0, 0.0);\n" // ToDo: figure out why (-2.0 / qt_Matrix[1][1]), that is empirically what works + " gl_Position = currentProjected + offset;\n" + " } else {\n" + " if (otherEndBelowFrustum) offset = vec4((dir * 1.0) / aspectVec, 0.0, 0.0);\n" // the if is necessary otherwise it seems the direction vector still flips in some obscure cases. + " else offset = vec4((dir * 500000000000.0) / aspectVec, 0.0, 0.0);\n" // Hack alert: just 1 triangle, long enough to look like a rectangle. + " if (vertextype < 0.0) gl_Position = nextProjected - offset; else gl_Position = previousProjected + offset;\n" + " }\n" + "}\n"; +} + +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(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, // reference as it gets copied in the nested call + const 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) + { + Q_ASSERT(!input.isNull()); + Q_ASSERT(!output.isNull()); + } + + ~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); + const QVector<QDeclarativeGeoMapItemUtils::vec2> res = + QGeoMapPolylineGeometryOpenGL::getSimplified( *m_input, + m_leftBound, + QGeoMapPolylineGeometryOpenGL::zoomForLOD(m_zoom)); + *m_output = res; + *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(const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &input, + const QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2> > &output, + double leftBound, + unsigned int zoom, + QSharedPointer<unsigned int> &working) +{ + Q_ASSERT(!input.isNull()); + Q_ASSERT(!output.isNull()); + 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); + + } +} + +void QGeoMapItemLODGeometry::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) { + if (!m_verticesLOD[lod]) + m_verticesLOD[lod] = QSharedPointer<QVector<QDeclarativeGeoMapItemUtils::vec2>>( + new QVector<QDeclarativeGeoMapItemUtils::vec2>); + enqueueSimplificationTask( m_verticesLOD.at(0), + m_verticesLOD[lod], + leftBound, + zoom, + m_working); + } + m_screenVertices = m_verticesLOD[qMin<unsigned int>(lod, 1)].data(); // return only 0,1 synchronously +} + +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 |