diff options
Diffstat (limited to 'src/location/declarativemaps/qdeclarativecirclemapitem.cpp')
-rw-r--r-- | src/location/declarativemaps/qdeclarativecirclemapitem.cpp | 391 |
1 files changed, 182 insertions, 209 deletions
diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp index 846fccbf..f0ba122c 100644 --- a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp @@ -57,6 +57,7 @@ #include <sweep/cdt.h> #include <QtPositioning/private/qclipperutils_p.h> +#include "qdeclarativecirclemapitem_p_p.h" QT_BEGIN_NAMESPACE @@ -274,83 +275,36 @@ void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QDoubleVector2D sourceBounds_ = screenBounds_; } -bool QDeclarativeCircleMapItem::crossEarthPole(const QGeoCoordinate ¢er, qreal distance) +struct CircleBackendSelector { - qreal poleLat = 90; - QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); - QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); - // approximate using great circle distance - qreal distanceToNorthPole = center.distanceTo(northPole); - qreal distanceToSouthPole = center.distanceTo(southPole); - if (distanceToNorthPole < distance || distanceToSouthPole < distance) - return true; - return false; -} - -void QDeclarativeCircleMapItem::calculatePeripheralPoints(QList<QGeoCoordinate> &path, - const QGeoCoordinate ¢er, - qreal distance, - int steps, - QGeoCoordinate &leftBound) -{ - // Calculate points based on great-circle distance - // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function - // but tweaked here for computing multiple points - - // pre-calculations - steps = qMax(steps, 3); - qreal centerLon = center.longitude(); - qreal minLon = centerLon; - qreal latRad = QLocationUtils::radians(center.latitude()); - qreal lonRad = QLocationUtils::radians(centerLon); - qreal cosLatRad = std::cos(latRad); - qreal sinLatRad = std::sin(latRad); - qreal ratio = (distance / QLocationUtils::earthMeanRadius()); - qreal cosRatio = std::cos(ratio); - qreal sinRatio = std::sin(ratio); - qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; - qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; - int idx = 0; - for (int i = 0; i < steps; ++i) { - qreal azimuthRad = 2 * M_PI * i / steps; - qreal resultLatRad = std::asin(sinLatRad_x_cosRatio - + cosLatRad_x_sinRatio * std::cos(azimuthRad)); - qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, - cosRatio - sinLatRad * std::sin(resultLatRad)); - qreal lat2 = QLocationUtils::degrees(resultLatRad); - qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); - - path << QGeoCoordinate(lat2, lon2, center.altitude()); - // Consider only points in the left half of the circle for the left bound. - if (azimuthRad > M_PI) { - if (lon2 > centerLon) // if point and center are on different hemispheres - lon2 -= 360; - if (lon2 < minLon) { - minLon = lon2; - idx = i; - } - } + CircleBackendSelector() + { + backend = (qgetenv("QTLOCATION_OPENGL_ITEMS").toInt()) ? QDeclarativeCircleMapItem::OpenGL : QDeclarativeCircleMapItem::Software; } - leftBound = path.at(idx); -} + QDeclarativeCircleMapItem::Backend backend = QDeclarativeCircleMapItem::Software; +}; + +Q_GLOBAL_STATIC(CircleBackendSelector, mapCircleBackendSelector) QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) -: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), - updatingGeometry_(false) +: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true), + m_updatingGeometry(false) + , m_d(new QDeclarativeCircleMapItemPrivateCPU(*this)) { + // ToDo: handle envvar, and switch implementation. m_itemType = QGeoMap::MapCircle; setFlag(ItemHasContents, true); - QObject::connect(&border_, SIGNAL(colorChanged(QColor)), - this, SLOT(markSourceDirtyAndUpdate())); - QObject::connect(&border_, SIGNAL(widthChanged(qreal)), - this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&m_border, SIGNAL(colorChanged(QColor)), + this, SLOT(onLinePropertiesChanged())); + QObject::connect(&m_border, SIGNAL(widthChanged(qreal)), + this, SLOT(onLinePropertiesChanged())); // assume that circles are not self-intersecting // to speed up processing // FIXME: unfortunately they self-intersect at the poles due to current drawing method // so the line is commented out until fixed //geometry_.setAssumeSimple(true); - + setBackend(mapCircleBackendSelector->backend); } QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() @@ -371,23 +325,24 @@ QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() */ QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border() { - return &border_; + return &m_border; } void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate() { - geometry_.markSourceDirty(); - borderGeometry_.markSourceDirty(); - polishAndUpdate(); + m_d->markSourceDirtyAndUpdate(); +} + +void QDeclarativeCircleMapItem::onLinePropertiesChanged() +{ + m_d->onLinePropertiesChanged(); } void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) { QDeclarativeGeoMapItemBase::setMap(quickMap,map); - if (!map) - return; - updateCirclePath(); - markSourceDirtyAndUpdate(); + if (map) + m_d->onMapSet(); } /*! @@ -399,18 +354,18 @@ void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *ma */ void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate ¢er) { - if (circle_.center() == center) + if (m_circle.center() == center) return; - circle_.setCenter(center); - updateCirclePath(); - markSourceDirtyAndUpdate(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), center, m_circle.radius()); + m_circle.setCenter(center); + m_d->onGeoGeometryChanged(); emit centerChanged(center); } QGeoCoordinate QDeclarativeCircleMapItem::center() { - return circle_.center(); + return m_circle.center(); } /*! @@ -421,17 +376,17 @@ QGeoCoordinate QDeclarativeCircleMapItem::center() */ void QDeclarativeCircleMapItem::setColor(const QColor &color) { - if (color_ == color) + if (m_color == color) return; - color_ = color; - dirtyMaterial_ = true; + m_color = color; + m_dirtyMaterial = true; update(); - emit colorChanged(color_); + emit colorChanged(m_color); } QColor QDeclarativeCircleMapItem::color() const { - return color_; + return m_color; } /*! @@ -443,18 +398,18 @@ QColor QDeclarativeCircleMapItem::color() const */ void QDeclarativeCircleMapItem::setRadius(qreal radius) { - if (circle_.radius() == radius) + if (m_circle.radius() == radius) return; - circle_.setRadius(radius); - updateCirclePath(); - markSourceDirtyAndUpdate(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), m_circle.center(), radius); + m_circle.setRadius(radius); + m_d->onGeoGeometryChanged(); emit radiusChanged(radius); } qreal QDeclarativeCircleMapItem::radius() const { - return circle_.radius(); + return m_circle.radius(); } /*! @@ -472,23 +427,7 @@ qreal QDeclarativeCircleMapItem::radius() const */ QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - Q_UNUSED(data); - - MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); - - if (!node) - node = new MapPolygonNode(); - - //TODO: update only material - if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { - node->update(color_, border_.color(), &geometry_, &borderGeometry_); - geometry_.setPreserveGeometry(false); - borderGeometry_.setPreserveGeometry(false); - geometry_.markClean(); - borderGeometry_.markClean(); - dirtyMaterial_ = false; - } - return node; + return m_d->updateMapItemPaintNode(oldNode, data); } /*! @@ -498,111 +437,41 @@ void QDeclarativeCircleMapItem::updatePolish() { if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) return; - if (!circle_.isValid()) { - geometry_.clear(); - borderGeometry_.clear(); - setWidth(0); - setHeight(0); - return; - } - - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - QScopedValueRollback<bool> rollback(updatingGeometry_); - updatingGeometry_ = true; - - QList<QDoubleVector2D> circlePath = circlePath_; - - int pathCount = circlePath.size(); - bool preserve = preserveCircleGeometry(circlePath, circle_.center(), circle_.radius(), p); - // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft()); - // to fix QTBUG-62154 - geometry_.setPreserveGeometry(true, leftBound_); // to set the geoLeftBound_ - geometry_.setPreserveGeometry(preserve, leftBound_); - - bool invertedCircle = false; - if (crossEarthPole(circle_.center(), circle_.radius()) && circlePath.size() == pathCount) { - geometry_.updateScreenPointsInvert(circlePath, *map()); // invert fill area for really huge circles - invertedCircle = true; - } else { - geometry_.updateSourcePoints(*map(), circlePath); - geometry_.updateScreenPoints(*map(), border_.width()); - } - - borderGeometry_.clear(); - QList<QGeoMapItemGeometry *> geoms; - geoms << &geometry_; - - if (border_.color() != Qt::transparent && border_.width() > 0) { - QList<QDoubleVector2D> closedPath = circlePath; - closedPath << closedPath.first(); - - if (invertedCircle) { - closedPath = circlePath_; - closedPath << closedPath.first(); - std::reverse(closedPath.begin(), closedPath.end()); - } - - borderGeometry_.setPreserveGeometry(true, leftBound_); - borderGeometry_.setPreserveGeometry(preserve, leftBound_); - - // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail. - const QGeoCoordinate &geometryOrigin = geometry_.origin(); - - borderGeometry_.srcPoints_.clear(); - borderGeometry_.srcPointTypes_.clear(); - - QDoubleVector2D borderLeftBoundWrapped; - QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); - if (clippedPaths.size()) { - borderLeftBoundWrapped = p.geoToWrappedMapProjection(geometryOrigin); - borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); - borderGeometry_.updateScreenPoints(*map(), border_.width()); - geoms << &borderGeometry_; - } else { - borderGeometry_.clear(); - } - } - - QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); - - if (invertedCircle || !preserve) { - setWidth(combined.width()); - setHeight(combined.height()); - setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); - } else { - setWidth(combined.width() + 2 * border_.width()); - setHeight(combined.height() + 2 * border_.width()); - } - - // No offsetting here, even in normal case, because first point offset is already translated - setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); + m_d->updatePolish(); } /*! \internal + + The OpenGL backend doesn't do circles crossing poles yet. + So if that backend is selected and the circle crosses the poles, use the CPU backend instead. */ -void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +void QDeclarativeCircleMapItem::possiblySwitchBackend(const QGeoCoordinate &oldCenter, qreal oldRadius, const QGeoCoordinate &newCenter, qreal newRadius) { - if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + if (m_backend != QDeclarativeCircleMapItem::OpenGL) return; - markSourceDirtyAndUpdate(); + // if old does not cross and new crosses, move to CPU. + if (!QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius) + && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { + QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this))); + m_d.swap(d); + } else if (QDeclarativeCircleMapItemPrivate::crossEarthPole(oldCenter, oldRadius) + && !QDeclarativeCircleMapItemPrivate::crossEarthPole(newCenter, newRadius)) { // else if old crosses and new does not cross, move back to OpenGL + QScopedPointer<QDeclarativeCircleMapItemPrivate> d(static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateOpenGL(*this))); + m_d.swap(d); + } } /*! \internal */ -void QDeclarativeCircleMapItem::updateCirclePath() +void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) { - if (!map() || map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator) + if (event.mapSize.isEmpty()) return; - const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map()->geoProjection()); - QList<QGeoCoordinate> path; - calculatePeripheralPoints(path, circle_.center(), circle_.radius(), CircleSamples, leftBound_); - circlePath_.clear(); - for (const QGeoCoordinate &c : path) - circlePath_ << p.geoToMapProjection(c); + m_d->afterViewportChanged(); } /*! @@ -610,30 +479,69 @@ void QDeclarativeCircleMapItem::updateCirclePath() */ bool QDeclarativeCircleMapItem::contains(const QPointF &point) const { - return (geometry_.contains(point) || borderGeometry_.contains(point)); + return m_d->contains(point); + // } const QGeoShape &QDeclarativeCircleMapItem::geoShape() const { - return circle_; + return m_circle; } void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape) { - if (shape == circle_) + if (shape == m_circle) return; const QGeoCircle circle(shape); // if shape isn't a circle, circle will be created as a default-constructed circle - const bool centerHasChanged = circle.center() != circle_.center(); - const bool radiusHasChanged = circle.radius() != circle_.radius(); - circle_ = circle; + const bool centerHasChanged = circle.center() != m_circle.center(); + const bool radiusHasChanged = circle.radius() != m_circle.radius(); + possiblySwitchBackend(m_circle.center(), m_circle.radius(), circle.center(), circle.radius()); + m_circle = circle; - updateCirclePath(); - markSourceDirtyAndUpdate(); + m_d->onGeoGeometryChanged(); if (centerHasChanged) - emit centerChanged(circle_.center()); + emit centerChanged(m_circle.center()); if (radiusHasChanged) - emit radiusChanged(circle_.radius()); + emit radiusChanged(m_circle.radius()); +} + +/*! + \qmlproperty MapCircle.Backend QtLocation::MapCircle::backend + + This property holds which backend is in use to render the map item. + Valid values are \b MapCircle.Software and \b{MapCircle.OpenGL}. + The default value is \b{MapCircle.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 +*/ + +QDeclarativeCircleMapItem::Backend QDeclarativeCircleMapItem::backend() const +{ + return m_backend; +} + +void QDeclarativeCircleMapItem::setBackend(QDeclarativeCircleMapItem::Backend b) +{ + if (b == m_backend) + return; + m_backend = b; + QScopedPointer<QDeclarativeCircleMapItemPrivate> d((m_backend == Software) + ? static_cast<QDeclarativeCircleMapItemPrivate *>(new QDeclarativeCircleMapItemPrivateCPU(*this)) + : static_cast<QDeclarativeCircleMapItemPrivate * >(new QDeclarativeCircleMapItemPrivateOpenGL(*this))); + m_d.swap(d); + m_d->onGeoGeometryChanged(); + emit backendChanged(); } /*! @@ -641,21 +549,27 @@ void QDeclarativeCircleMapItem::setGeoShape(const QGeoShape &shape) */ void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { - if (!map() || !circle_.isValid() || updatingGeometry_ || newGeometry == oldGeometry) { + if (!map() || !m_circle.isValid() || m_updatingGeometry || newGeometry == oldGeometry) { QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); return; } - QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) / 2; + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) * 0.5; QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(newPoint, false); if (newCoordinate.isValid()) - setCenter(newCoordinate); + setCenter(newCoordinate); // ToDo: this is incorrect. setting such center might yield to another geometry changed. // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested // call to this function. } -bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> &path, +QDeclarativeCircleMapItemPrivate::~QDeclarativeCircleMapItemPrivate() {} + +QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU() {} + +QDeclarativeCircleMapItemPrivateOpenGL::~QDeclarativeCircleMapItemPrivateOpenGL() {} + +bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p) { // if circle crosses north/south pole, then don't preserve circular shape, @@ -664,7 +578,6 @@ bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> & return false; } return true; - } /* @@ -684,7 +597,7 @@ bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> & * | ____ | * \__/ \__/ */ -void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QDoubleVector2D> &path, +void QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, qreal distance, const QGeoProjectionWebMercator &p) { @@ -743,6 +656,66 @@ void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QDoubleVector } } +bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate ¢er, qreal distance) +{ + qreal poleLat = 90; + QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); + QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); + // approximate using great circle distance + qreal distanceToNorthPole = center.distanceTo(northPole); + qreal distanceToSouthPole = center.distanceTo(southPole); + if (distanceToNorthPole < distance || distanceToSouthPole < distance) + return true; + return false; +} + +void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path, + const QGeoCoordinate ¢er, + qreal distance, + int steps, + QGeoCoordinate &leftBound) +{ + // Calculate points based on great-circle distance + // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function + // but tweaked here for computing multiple points + + // pre-calculations + steps = qMax(steps, 3); + qreal centerLon = center.longitude(); + qreal minLon = centerLon; + qreal latRad = QLocationUtils::radians(center.latitude()); + qreal lonRad = QLocationUtils::radians(centerLon); + qreal cosLatRad = std::cos(latRad); + qreal sinLatRad = std::sin(latRad); + qreal ratio = (distance / QLocationUtils::earthMeanRadius()); + qreal cosRatio = std::cos(ratio); + qreal sinRatio = std::sin(ratio); + qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; + qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; + int idx = 0; + for (int i = 0; i < steps; ++i) { + qreal azimuthRad = 2 * M_PI * i / steps; + qreal resultLatRad = std::asin(sinLatRad_x_cosRatio + + cosLatRad_x_sinRatio * std::cos(azimuthRad)); + qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, + cosRatio - sinLatRad * std::sin(resultLatRad)); + qreal lat2 = QLocationUtils::degrees(resultLatRad); + qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); + + path << QGeoCoordinate(lat2, lon2, center.altitude()); + // Consider only points in the left half of the circle for the left bound. + if (azimuthRad > M_PI) { + if (lon2 > centerLon) // if point and center are on different hemispheres + lon2 -= 360; + if (lon2 < minLon) { + minLon = lon2; + idx = i; + } + } + } + leftBound = path.at(idx); +} + ////////////////////////////////////////////////////////////////////// QT_END_NAMESPACE |