summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Rauter <matthias.rauter@qt.io>2023-01-26 17:18:50 +0100
committerMatthias Rauter <matthias.rauter@qt.io>2023-02-14 16:31:13 +0100
commit8b9ecad4bed0150adcbdf91db3f5f9507a156fd6 (patch)
tree6cd08872c366a32ed041701654d15559325fd464
parentb842a6cdcce0ee43a48ec084180d9dc065b599a1 (diff)
downloadqtlocation-8b9ecad4bed0150adcbdf91db3f5f9507a156fd6.tar.gz
Correct and improve the rendering of QuickMapItems
Various MapItems were not rendered correctly, especially in corner cases. This change ensures that MapItems are rendered correctly in the vast majority of cases. All MapItems are shown correctly if they wrap around the globe and appear twice on the map. Circles that span around the globe or are located near poles are shown correclty and filled all the way to the border of the map. Polygons are shown correctly including their holes. The code was simplified and some artefacts of previous implementations were removed. Fixes: QTBUG-110701 Fixes: QTBUG-110511 Pick-to: 6.5 Change-Id: I1110659989436cd5a93f6ec26f75caa06d5f2b71 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
-rw-r--r--src/location/quickmapitems/qdeclarativecirclemapitem.cpp343
-rw-r--r--src/location/quickmapitems/qdeclarativecirclemapitem_p.h1
-rw-r--r--src/location/quickmapitems/qdeclarativecirclemapitem_p_p.h19
-rw-r--r--src/location/quickmapitems/qdeclarativegeomap.cpp20
-rw-r--r--src/location/quickmapitems/qdeclarativegeomapitembase.cpp8
-rw-r--r--src/location/quickmapitems/qdeclarativegeomapitembase_p.h3
-rw-r--r--src/location/quickmapitems/qdeclarativegeomapitemutils.cpp41
-rw-r--r--src/location/quickmapitems/qdeclarativegeomapitemutils_p.h10
-rw-r--r--src/location/quickmapitems/qdeclarativepolygonmapitem.cpp168
-rw-r--r--src/location/quickmapitems/qdeclarativepolygonmapitem_p.h3
-rw-r--r--src/location/quickmapitems/qdeclarativepolygonmapitem_p_p.h35
-rw-r--r--src/location/quickmapitems/qdeclarativepolylinemapitem.cpp490
-rw-r--r--src/location/quickmapitems/qdeclarativepolylinemapitem_p.h1
-rw-r--r--src/location/quickmapitems/qdeclarativepolylinemapitem_p_p.h43
-rw-r--r--src/location/quickmapitems/qdeclarativerectanglemapitem.cpp8
-rw-r--r--src/location/quickmapitems/qdeclarativerectanglemapitem_p.h1
-rw-r--r--src/location/quickmapitems/qdeclarativerectanglemapitem_p_p.h2
-rw-r--r--src/location/quickmapitems/qgeomapitemgeometry_p.h6
18 files changed, 353 insertions, 849 deletions
diff --git a/src/location/quickmapitems/qdeclarativecirclemapitem.cpp b/src/location/quickmapitems/qdeclarativecirclemapitem.cpp
index 0e45d439..34aa4fb5 100644
--- a/src/location/quickmapitems/qdeclarativecirclemapitem.cpp
+++ b/src/location/quickmapitems/qdeclarativecirclemapitem.cpp
@@ -11,7 +11,6 @@
#include <QtGui/private/qtriangulator_p.h>
#include <QtLocation/private/qgeomap_p.h>
#include <QtPositioning/private/qlocationutils_p.h>
-#include <QtPositioning/private/qclipperutils_p.h>
#include <qmath.h>
#include <algorithm>
@@ -108,136 +107,8 @@ QGeoMapCircleGeometry::QGeoMapCircleGeometry()
{
}
-/*!
- \internal
-*/
-void QGeoMapCircleGeometry::updateSourceAndScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map)
-{
- const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
- // Not checking for !screenDirty anymore, as everything is now recalculated.
- clear();
- srcPath_ = QPainterPath();
- if (map.viewportWidth() == 0 || map.viewportHeight() == 0 || circlePath.size() < 3) // a circle requires at least 3 points;
- return;
-
- /*
- * No special case for no tilting as these items are very rare, and usually at most one per map.
- *
- * Approach:
- * 1) subtract the circle from a rectangle filling the whole map, *in wrapped mercator space*
- * 2) clip the resulting geometries against the visible region, *in wrapped mercator space*
- * 3) create a QPainterPath with each of the resulting polygons projected to screen
- * 4) use qTriangulate() to triangulate the painter path
- */
-
- // 1)
- const double topLati = QLocationUtils::mercatorMaxLatitude();
- const double bottomLati = -(QLocationUtils::mercatorMaxLatitude());
- const double leftLongi = QLocationUtils::mapLeftLongitude(map.cameraData().center().longitude());
- const double rightLongi = QLocationUtils::mapRightLongitude(map.cameraData().center().longitude());
-
- srcOrigin_ = QGeoCoordinate(topLati,leftLongi);
- const QDoubleVector2D tl = p.geoToWrappedMapProjection(QGeoCoordinate(topLati,leftLongi));
- const QDoubleVector2D tr = p.geoToWrappedMapProjection(QGeoCoordinate(topLati,rightLongi));
- const QDoubleVector2D br = p.geoToWrappedMapProjection(QGeoCoordinate(bottomLati,rightLongi));
- const QDoubleVector2D bl = p.geoToWrappedMapProjection(QGeoCoordinate(bottomLati,leftLongi));
-
- QList<QDoubleVector2D> fill;
- fill << tl << tr << br << bl;
-
- QList<QDoubleVector2D> hole;
- for (const QDoubleVector2D &c: circlePath)
- hole << p.wrapMapProjection(c);
-
- QClipperUtils clipper;
- clipper.addSubjectPath(fill, true);
- clipper.addClipPolygon(hole);
- auto difference = clipper.execute(QClipperUtils::Difference, QClipperUtils::pftEvenOdd,
- QClipperUtils::pftEvenOdd);
-
- // 2)
- QDoubleVector2D lb = p.geoToWrappedMapProjection(srcOrigin_);
- QList<QList<QDoubleVector2D> > clippedPaths;
- const QList<QDoubleVector2D> &visibleRegion = p.visibleGeometry();
- if (visibleRegion.size()) {
- clipper.clearClipper();
- for (const auto &p: difference)
- clipper.addSubjectPath(p, true);
- clipper.addClipPolygon(visibleRegion);
- clippedPaths = clipper.execute(QClipperUtils::Intersection, QClipperUtils::pftEvenOdd,
- QClipperUtils::pftEvenOdd);
-
- // 2.1) update srcOrigin_ with the point with minimum X/Y
- lb = QDoubleVector2D(qInf(), qInf());
- for (const QList<QDoubleVector2D> &path: clippedPaths) {
- for (const QDoubleVector2D &p: path) {
- if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) {
- lb = p;
- }
- }
- }
- if (qIsInf(lb.x()))
- return;
-
- // Prevent the conversion to and from clipper from introducing negative offsets which
- // in turn will make the geometry wrap around.
- lb.setX(qMax(tl.x(), lb.x()));
- srcOrigin_ = p.mapProjectionToGeo(p.unwrapMapProjection(lb));
- } else {
- clippedPaths = difference;
- }
-
- //3)
- const QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(lb);
-
- for (const QList<QDoubleVector2D> &path: clippedPaths) {
- QDoubleVector2D lastAddedPoint;
- for (qsizetype i = 0; i < path.size(); ++i) {
- QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(path.at(i));
- //point = point - origin; // Do this using ppi.translate()
-
- const QDoubleVector2D pt = point - origin;
- if (qMax(pt.x(), pt.y()) > maxCoord_)
- maxCoord_ = qMax(pt.x(), pt.y());
-
- if (i == 0) {
- srcPath_.moveTo(point.toPointF());
- lastAddedPoint = point;
- } else if ((point - lastAddedPoint).manhattanLength() > 3 || i == path.size() - 1) {
- srcPath_.lineTo(point.toPointF());
- lastAddedPoint = point;
- }
- }
- srcPath_.closeSubpath();
- }
-
- QPainterPath ppi = srcPath_;
- ppi.translate(-1 * origin.toPointF());
-
- QTriangleSet ts = qTriangulate(ppi);
- qreal *vx = ts.vertices.data();
-
- screenIndices_.reserve(ts.indices.size());
- screenVertices_.reserve(ts.vertices.size());
-
- if (ts.indices.type() == QVertexIndexVector::UnsignedInt) {
- const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data());
- for (qsizetype i = 0; i < (ts.indices.size()/3*3); ++i)
- screenIndices_ << ix[i];
- } else {
- const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data());
- for (qsizetype i = 0; i < (ts.indices.size()/3*3); ++i)
- screenIndices_ << ix[i];
- }
- for (qsizetype i = 0; i < (ts.vertices.size()/2*2); i += 2)
- screenVertices_ << QPointF(vx[i], vx[i + 1]);
-
- screenBounds_ = ppi.boundingRect();
- sourceBounds_ = srcPath_.boundingRect();
-}
-
QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent)
-: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true),
+: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent),
m_updatingGeometry(false)
, m_d(new QDeclarativeCircleMapItemPrivateCPU(*this))
{
@@ -248,12 +119,6 @@ QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent)
this, &QDeclarativeCircleMapItem::onLinePropertiesChanged);
QObject::connect(&m_border, &QDeclarativeMapLineProperties::widthChanged,
this, &QDeclarativeCircleMapItem::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);
}
QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem()
@@ -326,9 +191,9 @@ void QDeclarativeCircleMapItem::setColor(const QColor &color)
{
if (m_color == color)
return;
+
m_color = color;
- m_dirtyMaterial = true;
- update();
+ polishAndUpdate(); // in case color was transparent and now is not or vice versa
emit colorChanged(m_color);
}
@@ -404,7 +269,6 @@ void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChange
bool QDeclarativeCircleMapItem::contains(const QPointF &point) const
{
return m_d->contains(point);
- //
}
const QGeoShape &QDeclarativeCircleMapItem::geoShape() const
@@ -475,94 +339,59 @@ QDeclarativeCircleMapItemPrivateCPU::~QDeclarativeCircleMapItemPrivateCPU()
delete m_shape;
}
-bool QDeclarativeCircleMapItemPrivate::preserveCircleGeometry (QList<QDoubleVector2D> &path,
- const QGeoCoordinate &center, qreal distance, const QGeoProjectionWebMercator &p)
-{
- // if circle crosses north/south pole, then don't preserve circular shape,
- if ( crossEarthPole(center, distance)) {
- updateCirclePathForRendering(path, center, distance, p);
- return false;
- }
- return true;
-}
-
/*
* A workaround for circle path to be drawn correctly using a polygon geometry
* This method generates a polygon like
- * _____________
- * | |
- * \ /
- * | |
- * / \
- * | |
- * -------------
- *
- * or a polygon like
- *
* ______________
* | ____ |
* \__/ \__/
*/
-void QDeclarativeCircleMapItemPrivate::updateCirclePathForRendering(QList<QDoubleVector2D> &path,
+void QDeclarativeCircleMapItemPrivate::includeOnePoleInPath(QList<QDoubleVector2D> &path,
const QGeoCoordinate &center,
qreal distance, const QGeoProjectionWebMercator &p)
{
const qreal poleLat = 90;
const qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0));
const qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0));
- bool crossNorthPole = distanceToNorthPole < distance;
- bool crossSouthPole = distanceToSouthPole < distance;
-
- QList<qsizetype> wrapPathIndex;
- QDoubleVector2D prev = p.wrapMapProjection(path.at(0));
-
- for (qsizetype i = 1; i <= path.count(); ++i) {
- const auto index = i % path.count();
- const QDoubleVector2D point = p.wrapMapProjection(path.at(index));
- double diff = qAbs(point.x() - prev.x());
- if (diff > 0.5) {
- continue;
- }
- }
+ const bool crossNorthPole = distanceToNorthPole < distance;
+ const bool crossSouthPole = distanceToSouthPole < distance;
- // find the points in path where wrapping occurs
- for (qsizetype i = 1; i <= path.count(); ++i) {
- const auto index = i % path.count();
- const QDoubleVector2D point = p.wrapMapProjection(path.at(index));
- if ((qAbs(point.x() - prev.x())) >= 0.5) {
- wrapPathIndex << index;
- if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole))
- break;
- }
- prev = point;
- }
- // insert two additional coords at top/bottom map corners of the map for shape
- // to be drawn correctly
- if (wrapPathIndex.size() > 0) {
- qreal newPoleLat = 0; // 90 latitude
- QDoubleVector2D wrapCoord = path.at(wrapPathIndex[0]);
- if (wrapPathIndex.size() == 2) {
- QDoubleVector2D wrapCoord2 = path.at(wrapPathIndex[1]);
- if (wrapCoord2.y() < wrapCoord.y())
- newPoleLat = 1; // -90 latitude
- } else if (center.latitude() < 0) {
- newPoleLat = 1; // -90 latitude
- }
- for (qsizetype i = 0; i < wrapPathIndex.size(); ++i) {
- const qsizetype index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2;
- const qsizetype prevIndex = (index - 1) < 0 ? (path.count() - 1) : index - 1;
- QDoubleVector2D coord0 = path.at(prevIndex);
- QDoubleVector2D coord1 = path.at(index);
- coord0.setY(newPoleLat);
- coord1.setY(newPoleLat);
- path.insert(index ,coord1);
- path.insert(index, coord0);
- newPoleLat = 1.0 - newPoleLat;
- }
+ if (!crossNorthPole && !crossSouthPole)
+ return;
+
+ if (crossNorthPole && crossSouthPole)
+ return;
+
+ const QRectF cameraRect = QDeclarativeGeoMapItemUtils::boundingRectangleFromList(p.visibleGeometry());
+ const qreal xAtBorder = cameraRect.left();
+
+ // The strategy is to order the points from left to right as they appear on the screen.
+ // Then add the 3 missing sides that form the box for painting at the front and at the end of the list.
+ // We ensure that the box aligns with the cameraRect in order to avoid rendering it twice (wrap around).
+ // Notably, this leads to outlines at the right side of the map.
+ // Set xAtBorder to 0.0 to avoid this, however, for an increased rendering cost.
+ for (auto &c : path) {
+ c.setX(c.x());
+ while (c.x() - xAtBorder > 1.0)
+ c.setX(c.x() - 1.0);
+ while (c.x() - xAtBorder < 0.0)
+ c.setX(c.x() + 1.0);
}
+
+ std::sort(path.begin(), path.end(),
+ [](const QDoubleVector2D &a, const QDoubleVector2D &b) -> bool
+ {return a.x() < b.x();});
+
+ const qreal newPoleLat = crossNorthPole ? 0.0 : 1.0;
+ const QDoubleVector2D P1 = path.first() + QDoubleVector2D(1.0, 0.0);
+ const QDoubleVector2D P2 = path.last() - QDoubleVector2D(1.0, 0.0);
+ path.push_front(P2);
+ path.push_front(QDoubleVector2D(P2.x(), newPoleLat));
+ path.append(P1);
+ path.append(QDoubleVector2D(P1.x(), newPoleLat));
}
-bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate &center, qreal distance)
+int QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate &center, qreal distance)
{
qreal poleLat = 90;
QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude());
@@ -570,16 +399,15 @@ bool QDeclarativeCircleMapItemPrivate::crossEarthPole(const QGeoCoordinate &cent
// approximate using great circle distance
qreal distanceToNorthPole = center.distanceTo(northPole);
qreal distanceToSouthPole = center.distanceTo(southPole);
- if (distanceToNorthPole < distance || distanceToSouthPole < distance)
- return true;
- return false;
+ return (distanceToNorthPole < distance? 1 : 0) +
+ (distanceToSouthPole < distance? 1 : 0);
}
-void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoordinate> &path,
+void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QDoubleVector2D> &path,
const QGeoCoordinate &center,
qreal distance,
- int steps,
- QGeoCoordinate &leftBound)
+ const QGeoProjectionWebMercator &p,
+ int steps)
{
// Calculate points based on great-circle distance
// Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function
@@ -588,7 +416,6 @@ void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoord
// 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);
@@ -598,7 +425,6 @@ void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoord
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) {
const qreal azimuthRad = 2 * M_PI * i / steps;
const qreal resultLatRad = std::asin(sinLatRad_x_cosRatio
@@ -606,20 +432,20 @@ void QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(QList<QGeoCoord
const qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio,
cosRatio - sinLatRad * std::sin(resultLatRad));
const 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;
- }
+ qreal lon2 = QLocationUtils::degrees(resultLonRad);
+
+ //Workaround as QGeoCoordinate does not take Longitudes outside [-180,180]
+ qreal offset = 0.0;
+ while (lon2 > 180.0) {
+ offset += 1.0;
+ lon2 -= 360.0;
}
+ while (lon2 < -180.0) {
+ offset -= 1.0;
+ lon2 += 360.0;
+ }
+ path << p.geoToMapProjection(QGeoCoordinate(lat2, lon2, center.altitude())) + QDoubleVector2D(offset, 0.0);
}
- leftBound = path.at(idx);
}
//////////////////////////////////////////////////////////////////////
@@ -640,22 +466,41 @@ void QDeclarativeCircleMapItemPrivateCPU::updatePolish()
QList<QDoubleVector2D> circlePath = m_circlePath;
- int pathCount = circlePath.size();
- bool preserve = preserveCircleGeometry(circlePath, m_circle.m_circle.center(),
- m_circle.m_circle.radius(), p);
- // using leftBound_ instead of the analytically calculated
- // circle_.boundingGeoRectangle().topLeft());
- // to fix QTBUG-62154
- m_geometry.setPreserveGeometry(true, m_leftBound); // to set the geoLeftBound_
- m_geometry.setPreserveGeometry(preserve, m_leftBound);
-
- bool invertedCircle = false;
- if (crossEarthPole(m_circle.m_circle.center(), m_circle.m_circle.radius()) && circlePath.size() == pathCount) {
- // invert fill area for really huge circles
- m_geometry.updateSourceAndScreenPointsInvert(circlePath, *m_circle.map());
- invertedCircle = true;
+ const QGeoCoordinate &center = m_circle.m_circle.center();
+ const qreal &radius = m_circle.m_circle.radius();
+
+ // if circle crosses north/south pole, then don't preserve circular shape,
+ int crossingPoles = crossEarthPole(center, radius);
+ if (crossingPoles == 1) { // If the circle crosses both poles, we will remove it from a rectangle
+ includeOnePoleInPath(circlePath, center, radius, p);
+ m_geometry.updateSourcePoints(*m_circle.map(), QList<QList<QDoubleVector2D>>{circlePath}, QGeoMapPolygonGeometry::DrawOnce);
+ }
+ else if (crossingPoles == 2) { // If the circle crosses both poles, we will remove it from a rectangle
+ // The circle covers both poles. This appears on the map as a total fill with a hole on the opposite side of the planet
+ // This can be represented by a rectangle that spans the entire planet with a hole defined by the calculated points.
+ // The points on one side have to be wraped around the globe
+ const qreal centerX = p.geoToMapProjection(center).x();
+ for (int i = 0; i < circlePath.count(); i++) {
+ if (circlePath.at(i).x() > centerX)
+ circlePath[i].setX(circlePath.at(i).x() - 1.0);
+ }
+ QRectF cameraRect = QDeclarativeGeoMapItemUtils::boundingRectangleFromList(p.visibleGeometry());
+ const QRectF circleRect = QDeclarativeGeoMapItemUtils::boundingRectangleFromList(circlePath);
+ QGeoMapPolygonGeometry::MapBorderBehaviour wrappingMode = QGeoMapPolygonGeometry::DrawOnce;
+ QList<QDoubleVector2D> surroundingRect;
+ if (cameraRect.contains(circleRect)){
+ cameraRect = cameraRect.adjusted(-0.1, 0.0, 0.2, 0.0);
+ surroundingRect = {{cameraRect.left(), cameraRect.top()}, {cameraRect.right(), cameraRect.top()},
+ {cameraRect.right(), cameraRect.bottom()}, {cameraRect.left() , cameraRect.bottom()}};
+ } else {
+ const qreal anchorRect = centerX;
+ surroundingRect = {{anchorRect, 0.0}, {anchorRect + 1.0, 0.0},
+ {anchorRect + 1.0, 1.0}, {anchorRect, 1.0}};
+ wrappingMode = QGeoMapPolygonGeometry::WrapAround;
+ }
+ m_geometry.updateSourcePoints(*m_circle.map(), {surroundingRect, circlePath}, wrappingMode);
} else {
- m_geometry.updateSourcePoints(*m_circle.map(), circlePath);
+ m_geometry.updateSourcePoints(*m_circle.map(), QList<QList<QDoubleVector2D>>{circlePath});
}
m_circle.setShapeTriangulationScale(m_shape, m_geometry.maxCoord());
@@ -672,9 +517,7 @@ void QDeclarativeCircleMapItemPrivateCPU::updatePolish()
path.closeSubpath();
m_painterPath->setPath(path);
- m_circle.setSize(invertedCircle || !preserve
- ? bb.size()
- : bb.size() + QSize(2 * borderWidth, 2 * borderWidth));
+ m_circle.setSize(bb.size());
m_shape->setSize(m_circle.size());
m_shape->setOpacity(m_circle.zoomLevelOpacity());
m_shape->setVisible(true);
@@ -687,10 +530,8 @@ QSGNode *QDeclarativeCircleMapItemPrivateCPU::updateMapItemPaintNode(QSGNode *ol
{
Q_UNUSED(data);
delete oldNode;
- if (m_geometry.isScreenDirty() || m_circle.m_dirtyMaterial) {
- m_geometry.setPreserveGeometry(false);
+ if (m_geometry.isScreenDirty()) {
m_geometry.markClean();
- m_circle.m_dirtyMaterial = false;
}
return nullptr;
}
diff --git a/src/location/quickmapitems/qdeclarativecirclemapitem_p.h b/src/location/quickmapitems/qdeclarativecirclemapitem_p.h
index 211cd914..305fd719 100644
--- a/src/location/quickmapitems/qdeclarativecirclemapitem_p.h
+++ b/src/location/quickmapitems/qdeclarativecirclemapitem_p.h
@@ -79,7 +79,6 @@ private:
QGeoCircle m_circle;
QDeclarativeMapLineProperties m_border;
QColor m_color;
- bool m_dirtyMaterial;
bool m_updatingGeometry;
std::unique_ptr<QDeclarativeCircleMapItemPrivate> m_d;
diff --git a/src/location/quickmapitems/qdeclarativecirclemapitem_p_p.h b/src/location/quickmapitems/qdeclarativecirclemapitem_p_p.h
index 03b4f1b7..117dcb63 100644
--- a/src/location/quickmapitems/qdeclarativecirclemapitem_p_p.h
+++ b/src/location/quickmapitems/qdeclarativecirclemapitem_p_p.h
@@ -60,27 +60,21 @@ public:
if (!m_circle.map() || m_circle.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
return;
- const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection());
- QList<QGeoCoordinate> path;
- calculatePeripheralPoints(path, m_circle.center(), m_circle.radius(), CircleSamples, m_leftBound);
m_circlePath.clear();
- for (const QGeoCoordinate &c : path)
- m_circlePath << p.geoToMapProjection(c);
+ const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_circle.map()->geoProjection());
+ calculatePeripheralPoints(m_circlePath, m_circle.center(), m_circle.radius(), p, CircleSamples);
}
- static bool crossEarthPole(const QGeoCoordinate &center, qreal distance);
+ static int crossEarthPole(const QGeoCoordinate &center, qreal distance);
- static bool preserveCircleGeometry(QList<QDoubleVector2D> &path, const QGeoCoordinate &center,
- qreal distance, const QGeoProjectionWebMercator &p);
- static void updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate &center,
+ static void includeOnePoleInPath(QList<QDoubleVector2D> &path, const QGeoCoordinate &center,
qreal distance, const QGeoProjectionWebMercator &p);
- static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate &center,
- qreal distance, int steps, QGeoCoordinate &leftBound);
+ static void calculatePeripheralPoints(QList<QDoubleVector2D> &path, const QGeoCoordinate &center,
+ qreal distance, const QGeoProjectionWebMercator &p, int steps);
QDeclarativeCircleMapItem &m_circle;
QList<QDoubleVector2D> m_circlePath;
- QGeoCoordinate m_leftBound;
};
class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItemPrivateCPU: public QDeclarativeCircleMapItemPrivate
@@ -96,7 +90,6 @@ public:
}
void markSourceDirtyAndUpdate() override
{
- // preserveGeometry is cleared in updateMapItemPaintNode
m_geometry.markSourceDirty();
m_circle.polishAndUpdate();
}
diff --git a/src/location/quickmapitems/qdeclarativegeomap.cpp b/src/location/quickmapitems/qdeclarativegeomap.cpp
index 0b4fcde9..c232010b 100644
--- a/src/location/quickmapitems/qdeclarativegeomap.cpp
+++ b/src/location/quickmapitems/qdeclarativegeomap.cpp
@@ -1868,23 +1868,12 @@ void QDeclarativeGeoMap::updateItemToWindowTransform()
return;
// Update itemToWindowTransform into QGeoProjection
- const QTransform item2WindowOld = m_map->geoProjection().itemToWindowTransform();
QTransform item2Window = QQuickItemPrivate::get(this)->itemToWindowTransform();
if (!property("layer").isNull() && property("layer").value<QObject *>()->property("enabled").toBool())
item2Window.reset(); // When layer is enabled, the item is rendered offscreen with no transformation, then the layer is applied
m_map->setItemToWindowTransform(item2Window);
- // This method is called at every redraw, including those redraws not generated by
- // sgNodeChanged.
- // In these cases, *if* the item2windowTransform has changed (e.g., if transformation of
- // the item or one of its ancestors changed), a forced update of the map items using accelerated
- // GL implementation has to be performed in order to have them pulling the updated itemToWindowTransform.
- if (!m_sgNodeHasChanged && item2WindowOld != item2Window) {
- for (auto i: std::as_const(m_mapItems))
- i->setMaterialDirty();
- }
-
m_sgNodeHasChanged = false;
}
@@ -2112,10 +2101,11 @@ void QDeclarativeGeoMap::fitViewportToMapItemsRefine(const QList<QPointer<QDecla
bottomRightX = topLeftX + brect.width();
bottomRightY = topLeftY + brect.height();
} else {
- topLeftX = item->position().x();
- topLeftY = item->position().y();
- bottomRightX = topLeftX + item->width();
- bottomRightY = topLeftY + item->height();
+ QGeoRectangle brect = item->geoShape().boundingGeoRectangle();
+ topLeftX = fromCoordinate(brect.topLeft(), false).x();
+ topLeftY = fromCoordinate(brect.topLeft(), false).y();
+ bottomRightX = fromCoordinate(brect.bottomRight(), false).x();
+ bottomRightY = fromCoordinate(brect.bottomRight(), false).y();
}
minX = qMin(minX, topLeftX);
diff --git a/src/location/quickmapitems/qdeclarativegeomapitembase.cpp b/src/location/quickmapitems/qdeclarativegeomapitembase.cpp
index c5b1cc8c..233ea82d 100644
--- a/src/location/quickmapitems/qdeclarativegeomapitembase.cpp
+++ b/src/location/quickmapitems/qdeclarativegeomapitembase.cpp
@@ -77,7 +77,7 @@ void QDeclarativeGeoMapItemBase::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *m
// For performance reasons we're not connecting map_'s and quickMap_'s signals to this.
// Rather, the handling of cameraDataChanged, visibleAreaChanged, heightChanged and widthChanged is done explicitly in QDeclarativeGeoMap by directly calling methods on the items.
// See QTBUG-76950
- lastSize_ = QSizeF(quickMap_->width(), quickMap_->height());
+ lastMapSize_ = QSizeF(quickMap_->width(), quickMap_->height());
lastCameraData_ = map_->cameraData();
}
}
@@ -91,7 +91,7 @@ void QDeclarativeGeoMapItemBase::baseCameraDataChanged(const QGeoCameraData &cam
evt.cameraData = cameraData;
evt.mapSize = QSizeF(quickMap_->width(), quickMap_->height());
- if (evt.mapSize != lastSize_)
+ if (evt.mapSize != lastMapSize_)
evt.mapSizeChanged = true;
if (cameraData.bearing() != lastCameraData_.bearing())
@@ -105,7 +105,7 @@ void QDeclarativeGeoMapItemBase::baseCameraDataChanged(const QGeoCameraData &cam
if (cameraData.zoomLevel() != lastCameraData_.zoomLevel())
evt.zoomLevelChanged = true;
- lastSize_ = evt.mapSize;
+ lastMapSize_ = evt.mapSize;
lastCameraData_ = cameraData;
afterViewportChanged(evt);
@@ -286,8 +286,6 @@ bool QDeclarativeGeoMapItemBase::isPolishScheduled() const
return QQuickItemPrivate::get(this)->polishScheduled;
}
-void QDeclarativeGeoMapItemBase::setMaterialDirty() {}
-
void QDeclarativeGeoMapItemBase::polishAndUpdate()
{
polish();
diff --git a/src/location/quickmapitems/qdeclarativegeomapitembase_p.h b/src/location/quickmapitems/qdeclarativegeomapitembase_p.h
index 6888596a..38eef2db 100644
--- a/src/location/quickmapitems/qdeclarativegeomapitembase_p.h
+++ b/src/location/quickmapitems/qdeclarativegeomapitembase_p.h
@@ -111,7 +111,6 @@ protected Q_SLOTS:
protected:
float zoomLevelOpacity() const;
bool isPolishScheduled() const;
- virtual void setMaterialDirty();
QGeoMap::ItemType m_itemType = QGeoMap::NoItem;
@@ -123,7 +122,7 @@ private:
QPointer<QGeoMap> map_;
QDeclarativeGeoMap *quickMap_ = nullptr;
- QSizeF lastSize_;
+ QSizeF lastMapSize_;
QGeoCameraData lastCameraData_;
QDeclarativeGeoMapItemGroup *parentGroup_ = nullptr;
diff --git a/src/location/quickmapitems/qdeclarativegeomapitemutils.cpp b/src/location/quickmapitems/qdeclarativegeomapitemutils.cpp
index 6a79b33b..a1501dd6 100644
--- a/src/location/quickmapitems/qdeclarativegeomapitemutils.cpp
+++ b/src/location/quickmapitems/qdeclarativegeomapitemutils.cpp
@@ -16,6 +16,31 @@ QT_BEGIN_NAMESPACE
namespace QDeclarativeGeoMapItemUtils {
+double distanceSqrPointLine(double p0_x
+ , double p0_y
+ , double p1_x
+ , double p1_y
+ , double p2_x
+ , double p2_y)
+{
+ const double t_x = p2_x - p1_x;
+ const double t_y = p2_y - p1_y;
+ const double p_x = p0_x - p1_x;
+ const double p_y = p0_y - p1_y;
+ const double tsqr = t_x * t_x + t_y * t_y;
+
+ if (tsqr == 0)
+ return qInf();
+
+ double alpha = (p_x * t_x + p_y * t_y) / tsqr;
+ alpha = qBound<double>(0, alpha, 1);
+
+ const double dx = p_x - t_x * alpha;
+ const double dy = p_y - t_y * alpha;
+
+ return dx * dx + dy * dy;
+}
+
void wrapPath(const QList<QGeoCoordinate> &perimeter,
const QGeoCoordinate &geoLeftBound,
const QGeoProjectionWebMercator &p,
@@ -150,6 +175,22 @@ void projectBbox(const QList<QDoubleVector2D> &clippedBbox,
projectedBbox.closeSubpath();
}
+
+QRectF boundingRectangleFromList(const QList<QDoubleVector2D> &list)
+{
+ double xMin = qInf();
+ double xMax = -qInf();
+ double yMin = qInf();
+ double yMax = -qInf();
+ for (const auto &coord : list) {
+ xMin = qMin(xMin, coord.x());
+ xMax = qMax(xMax, coord.x());
+ yMin = qMin(yMin, coord.y());
+ yMax = qMax(yMax, coord.y());
+ }
+ return QRectF(xMin, yMin, xMax - xMin, yMax - yMin);
+}
+
} // namespace QDeclarativeGeoMapItemUtils
QT_END_NAMESPACE
diff --git a/src/location/quickmapitems/qdeclarativegeomapitemutils_p.h b/src/location/quickmapitems/qdeclarativegeomapitemutils_p.h
index 2f117520..50ece528 100644
--- a/src/location/quickmapitems/qdeclarativegeomapitemutils_p.h
+++ b/src/location/quickmapitems/qdeclarativegeomapitemutils_p.h
@@ -52,6 +52,14 @@ namespace QDeclarativeGeoMapItemUtils
}
};
+
+ double distanceSqrPointLine(double p0_x
+ , double p0_y
+ , double p1_x
+ , double p1_y
+ , double p2_x
+ , double p2_y);
+
void wrapPath(const QList<QGeoCoordinate> &perimeter
, const QGeoCoordinate &geoLeftBound
, const QGeoProjectionWebMercator &p
@@ -80,6 +88,8 @@ namespace QDeclarativeGeoMapItemUtils
, const QGeoProjectionWebMercator &p
, QPainterPath &projectedBbox);
+ QRectF boundingRectangleFromList(const QList<QDoubleVector2D> &list);
+
};
QT_END_NAMESPACE
diff --git a/src/location/quickmapitems/qdeclarativepolygonmapitem.cpp b/src/location/quickmapitems/qdeclarativepolygonmapitem.cpp
index 1f34d0c1..1eee3eb0 100644
--- a/src/location/quickmapitems/qdeclarativepolygonmapitem.cpp
+++ b/src/location/quickmapitems/qdeclarativepolygonmapitem.cpp
@@ -115,98 +115,105 @@ QGeoMapPolygonGeometry::QGeoMapPolygonGeometry() = default;
\internal
*/
void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map,
- const QList<QDoubleVector2D> &path)
+ const QList<QList <QDoubleVector2D>> &paths,
+ MapBorderBehaviour wrapping)
{
+ // A polygon consists of mutliple paths. This is usually a perimeter and multiple holes
+ // We move all paths into a single QPainterPath. The filling rule EvenOdd will then ensure that the paths are shown correctly
if (!sourceDirty_)
return;
const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
srcPath_ = QPainterPath();
+ srcOrigin_ = p.mapProjectionToGeo(QDoubleVector2D(0.0, 0.0)); //avoid warning of NaN values if function is returned early
+
+ //1 The bounding rectangle of the polygon and camera view are compared to determine if the polygon is visible
+ // The viewport is periodic in x-direction in the interval [-1; 1].
+ // The polygon (maybe) has to be ploted periodically too by shifting it by -1 or +1;
+ const QRectF cameraRect = QDeclarativeGeoMapItemUtils::boundingRectangleFromList(p.visibleGeometry());
+ QRectF itemRect;
+ for (const auto &path : paths)
+ itemRect |= QDeclarativeGeoMapItemUtils::boundingRectangleFromList(path);
+ QList<QList<QDoubleVector2D>> wrappedPaths;
+
+ if (wrapping == WrapAround) {
+ for (double xoffset : {-1.0, 0.0, 1.0}) {
+ if (!cameraRect.intersects(itemRect.translated(QPointF(xoffset, 0.0))))
+ continue;
+ for (const auto &path : paths) {
+ wrappedPaths.append(QList<QDoubleVector2D>());
+ QList<QDoubleVector2D> &wP = wrappedPaths.last();
+ wP.reserve(path.size());
+ for (const QDoubleVector2D &coord : path)
+ wP.append(coord+QDoubleVector2D(xoffset, 0.0));
+ }
+ }
+ } else
+ wrappedPaths = paths;
+
+ if (wrappedPaths.isEmpty()) // the polygon boundary rectangle does not overlap with the viewport rectangle
+ return;
- // build the actual path
- // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
- srcOrigin_ = geoLeftBound_;
- double unwrapBelowX = 0;
- QDoubleVector2D leftBoundWrapped = p.wrapMapProjection(p.geoToMapProjection(geoLeftBound_));
- if (preserveGeometry_)
- unwrapBelowX = leftBoundWrapped.x();
-
- QList<QDoubleVector2D> wrappedPath;
- wrappedPath.reserve(path.size());
- QDoubleVector2D wrappedLeftBound(qInf(), qInf());
- // 1)
- for (const auto &coord : path) {
- QDoubleVector2D wrappedProjection = p.wrapMapProjection(coord);
-
- // We can get NaN if the map isn't set up correctly, or the projection
- // is faulty -- probably best thing to do is abort
- if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y()))
- return;
-
- const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x());
- // unwrap x to preserve geometry if moved to border of map
- if (preserveGeometry_ && isPointLessThanUnwrapBelowX) {
- double distance = wrappedProjection.x() - unwrapBelowX;
- if (distance < 0.0)
- distance += 1.0;
- wrappedProjection.setX(unwrapBelowX + distance);
+
+ //2 The polygons that are at least partially in the viewport are cliped to reduce their size
+ QList<QList<QDoubleVector2D>> clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = p.visibleGeometryExpanded();
+ for (const auto &path : wrappedPaths) {
+ if (visibleRegion.size()) {
+ QClipperUtils clipper;
+ clipper.addSubjectPath(path, true);
+ clipper.addClipPolygon(visibleRegion);
+ clippedPaths << clipper.execute(QClipperUtils::Intersection, QClipperUtils::pftEvenOdd,
+ QClipperUtils::pftEvenOdd);
}
- if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) {
- wrappedLeftBound = wrappedProjection;
+ else {
+ clippedPaths.append(path); //Do we really need this if there are no visible regions??
}
- wrappedPath.append(wrappedProjection);
- }
-
- // 2)
- QList<QList<QDoubleVector2D> > clippedPaths;
- const QList<QDoubleVector2D> &visibleRegion = p.projectableGeometry();
- if (visibleRegion.size()) {
- QClipperUtils clipper;
- clipper.addSubjectPath(wrappedPath, true);
- clipper.addClipPolygon(visibleRegion);
- clippedPaths = clipper.execute(QClipperUtils::Intersection, QClipperUtils::pftEvenOdd,
- QClipperUtils::pftEvenOdd);
-
- // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X
- QDoubleVector2D lb(qInf(), qInf());
- for (const QList<QDoubleVector2D> &path: clippedPaths)
- for (const QDoubleVector2D &p: path)
- if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y()))
- // y-minimization needed to find the same point on polygon and border
- lb = p;
-
- if (qIsInf(lb.x())) // e.g., when the polygon is clipped entirely
- return;
-
- // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which
- // in turn will make the geometry wrap around.
- lb.setX(qMax(wrappedLeftBound.x(), lb.x()));
- leftBoundWrapped = lb;
- srcOrigin_ = p.mapProjectionToGeo(p.unwrapMapProjection(lb));
- } else {
- clippedPaths.append(wrappedPath);
}
+ if (clippedPaths.isEmpty()) //the polygon is entirely outside visibleRegion
+ return;
- // 3)
+ QRectF bb;
+ for (const auto &path: clippedPaths)
+ bb |= QDeclarativeGeoMapItemUtils::boundingRectangleFromList(path);
+ //Offset by origin, find the maximum coordinate
+ srcOrigin_ = p.mapProjectionToGeo(QDoubleVector2D(bb.left(), bb.top()));
+ QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(p.geoToWrappedMapProjection(srcOrigin_)); //save way: redo all projections
maxCoord_ = 0.0;
- QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(leftBoundWrapped);
- for (const QList<QDoubleVector2D> &path: clippedPaths) {
- QDoubleVector2D lastAddedPoint;
- for (qsizetype i = 0; i < path.size(); ++i) {
- QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(path.at(i));
- point = point - origin; // (0,0) if point == geoLeftBound_
+ for (const auto &path: clippedPaths) {
+ QDoubleVector2D prevPoint = p.wrappedMapProjectionToItemPosition(path.at(0)) - origin;
+ QDoubleVector2D nextPoint = p.wrappedMapProjectionToItemPosition(path.at(1)) - origin;
+ srcPath_.moveTo(prevPoint.toPointF());
+ maxCoord_ = qMax(maxCoord_, qMax(prevPoint.x(), prevPoint.y()));
+ qsizetype pointsAdded = 1;
+ for (qsizetype i = 1; i < path.size(); ++i) {
+ const QDoubleVector2D point = nextPoint;
if (qMax(point.x(), point.y()) > maxCoord_)
maxCoord_ = qMax(point.x(), point.y());
- if (i == 0) {
- srcPath_.moveTo(point.toPointF());
- lastAddedPoint = point;
+ if (i == path.size() - 1) {
+ srcPath_.lineTo(point.toPointF()); //close the path
} else {
- if ((point - lastAddedPoint).manhattanLength() > 3 ||
- i == path.size() - 1) {
+ nextPoint = p.wrappedMapProjectionToItemPosition(path.at(i+1)) - origin;
+
+ bool addPoint = ( i > pointsAdded * 10 || //make sure that at least every 10th point is drawn
+ path.size() < 10 ); //draw small paths completely
+
+ const double tolerance = 0.1;
+ if (!addPoint) { //add the point to the shape if it deflects the boundary by more than the tolerance
+ const double dsqr = QDeclarativeGeoMapItemUtils::distanceSqrPointLine(
+ point.x(), point.y(),
+ nextPoint.x(), nextPoint.y(),
+ prevPoint.x(), prevPoint.y());
+ addPoint = addPoint || (dsqr > (tolerance*tolerance));
+ }
+
+ if (addPoint) {
srcPath_.lineTo(point.toPointF());
- lastAddedPoint = point;
+ pointsAdded++;
+ prevPoint = point;
}
+
}
}
srcPath_.closeSubpath();
@@ -292,10 +299,8 @@ QSGNode *QDeclarativePolygonMapItemPrivateCPU::updateMapItemPaintNode(QSGNode *o
{
Q_UNUSED(data);
delete oldNode;
- if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial) {
- m_geometry.setPreserveGeometry(false);
+ if (m_geometry.isScreenDirty()) {
m_geometry.markClean();
- m_poly.m_dirtyMaterial = false;
}
return nullptr;
}
@@ -310,7 +315,7 @@ bool QDeclarativePolygonMapItemPrivateCPU::contains(const QPointF &point) const
*/
QDeclarativePolygonMapItem::QDeclarativePolygonMapItem(QQuickItem *parent)
-: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent), m_dirtyMaterial(true),
+: QDeclarativeGeoMapItemBase(parent), m_border(this), m_color(Qt::transparent),
m_updatingGeometry(false)
, m_d(new QDeclarativePolygonMapItemPrivateCPU(*this))
@@ -443,7 +448,6 @@ void QDeclarativePolygonMapItem::setColor(const QColor &color)
return;
m_color = color;
- m_dirtyMaterial = true;
polishAndUpdate(); // in case color was transparent and now is not or vice versa
emit colorChanged(m_color);
}
@@ -466,12 +470,6 @@ void QDeclarativePolygonMapItem::updatePolish()
m_d->updatePolish();
}
-void QDeclarativePolygonMapItem::setMaterialDirty()
-{
- m_dirtyMaterial = true;
- update();
-}
-
void QDeclarativePolygonMapItem::markSourceDirtyAndUpdate()
{
m_d->markSourceDirtyAndUpdate();
diff --git a/src/location/quickmapitems/qdeclarativepolygonmapitem_p.h b/src/location/quickmapitems/qdeclarativepolygonmapitem_p.h
index 1462bca4..1a99983d 100644
--- a/src/location/quickmapitems/qdeclarativepolygonmapitem_p.h
+++ b/src/location/quickmapitems/qdeclarativepolygonmapitem_p.h
@@ -68,7 +68,6 @@ protected Q_SLOTS:
protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
void updatePolish() override;
- void setMaterialDirty() override;
#ifdef QT_LOCATION_DEBUG
public:
@@ -76,8 +75,6 @@ public:
QGeoPolygon m_geopoly;
QDeclarativeMapLineProperties m_border;
QColor m_color;
- bool m_dirtyMaterial;
-// bool m_dirtyGeometry = false;
bool m_updatingGeometry;
std::unique_ptr<QDeclarativePolygonMapItemPrivate> m_d;
diff --git a/src/location/quickmapitems/qdeclarativepolygonmapitem_p_p.h b/src/location/quickmapitems/qdeclarativepolygonmapitem_p_p.h
index 449a1a1d..ce0ed18e 100644
--- a/src/location/quickmapitems/qdeclarativepolygonmapitem_p_p.h
+++ b/src/location/quickmapitems/qdeclarativepolygonmapitem_p_p.h
@@ -38,12 +38,18 @@ class QQuickShapePath;
class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolygonGeometry : public QGeoMapItemGeometry
{
public:
+ enum MapBorderBehaviour {
+ DrawOnce,
+ WrapAround
+ };
+
QGeoMapPolygonGeometry();
inline void setAssumeSimple(bool value) { assumeSimple_ = value; }
void updateSourcePoints(const QGeoMap &map,
- const QList<QDoubleVector2D> &path);
+ const QList<QList<QDoubleVector2D>> &path,
+ MapBorderBehaviour wrapping = WrapAround);
QPainterPath srcPath() const { return srcPath_; }
qreal maxCoord() const { return maxCoord_; }
@@ -91,7 +97,6 @@ public:
}
void markSourceDirtyAndUpdate() override
{
- // preserveGeometry is cleared in updateMapItemPaintNode
m_geometry.markSourceDirty();
m_poly.polishAndUpdate();
}
@@ -101,25 +106,29 @@ public:
return;
const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection());
m_geopathProjected.clear();
- m_geopathProjected.reserve(m_poly.m_geopoly.size());
+ m_geopathProjected << QList<QDoubleVector2D>();
+ QList<QDoubleVector2D> &pP = m_geopathProjected.last();
+ pP.reserve(m_poly.m_geopoly.perimeter().size());
for (const QGeoCoordinate &c : m_poly.m_geopoly.perimeter())
- m_geopathProjected << p.geoToMapProjection(c);
+ pP << p.geoToMapProjection(c);
+ for (int i = 0; i < m_poly.m_geopoly.holesCount(); i++) {
+ m_geopathProjected << QList<QDoubleVector2D>();
+ QList<QDoubleVector2D> &pH = m_geopathProjected.last();
+ pH.reserve(m_poly.m_geopoly.holePath(i).size());
+ for (const QGeoCoordinate &c : m_poly.m_geopoly.holePath(i))
+ pH << p.geoToMapProjection(c);
+ }
}
void updateCache()
{
if (!m_poly.map() || m_poly.map()->geoProjection().projectionType() != QGeoProjection::ProjectionWebMercator)
return;
const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection());
- m_geopathProjected << p.geoToMapProjection(m_poly.m_geopoly.perimeter().last());
- }
- void preserveGeometry()
- {
- m_geometry.setPreserveGeometry(true, m_poly.m_geopoly.boundingGeoRectangle().topLeft());
+ QList<QDoubleVector2D> &pP = m_geopathProjected.first();
+ pP << p.geoToMapProjection(m_poly.m_geopoly.perimeter().last());
}
void afterViewportChanged() override
{
- // preserveGeometry is cleared in updateMapItemPaintNode
- preserveGeometry();
markSourceDirtyAndUpdate();
}
void onMapSet() override
@@ -130,13 +139,11 @@ public:
void onGeoGeometryChanged() override
{
regenerateCache();
- preserveGeometry();
markSourceDirtyAndUpdate();
}
void onGeoGeometryUpdated() override
{
updateCache();
- preserveGeometry();
markSourceDirtyAndUpdate();
}
void onItemGeometryChanged() override
@@ -147,7 +154,7 @@ public:
QSGNode *updateMapItemPaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data) override;
bool contains(const QPointF &point) const override;
- QList<QDoubleVector2D> m_geopathProjected;
+ QList<QList<QDoubleVector2D>> m_geopathProjected;
QGeoMapPolygonGeometry m_geometry;
QQuickShape *m_shape = nullptr;
QQuickShapePath *m_shapePath = nullptr;
diff --git a/src/location/quickmapitems/qdeclarativepolylinemapitem.cpp b/src/location/quickmapitems/qdeclarativepolylinemapitem.cpp
index 2477a4d8..e80afbee 100644
--- a/src/location/quickmapitems/qdeclarativepolylinemapitem.cpp
+++ b/src/location/quickmapitems/qdeclarativepolylinemapitem.cpp
@@ -3,7 +3,6 @@
#include "qdeclarativepolylinemapitem_p.h"
#include "qdeclarativepolylinemapitem_p_p.h"
-#include "qdeclarativegeomapitemutils_p.h"
#include <QtCore/QScopedValueRollback>
#include <qnumeric.h>
@@ -296,421 +295,97 @@ void QDeclarativeMapLineProperties::setWidth(qreal width)
emit widthChanged(width_);
}
-QGeoMapPolylineGeometry::QGeoMapPolylineGeometry()
-{
-}
-
-QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap &map,
- const QList<QDoubleVector2D> &path,
- QDoubleVector2D &leftBoundWrapped)
-{
- /*
- * Approach:
- * 1) project coordinates to wrapped web mercator, and do unwrapBelowX
- * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons)
- * 2.1) recalculate the origin and geoLeftBound to prevent these parameters from ending in unprojectable areas
- * 2.2) ensure the left bound does not wrap around due to QGeoCoordinate <-> clipper conversions
- */
- const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
- srcOrigin_ = geoLeftBound_;
-
- double unwrapBelowX = 0;
- leftBoundWrapped = p.wrapMapProjection(p.geoToMapProjection(geoLeftBound_));
- if (preserveGeometry_)
- unwrapBelowX = leftBoundWrapped.x();
-
- QList<QDoubleVector2D> wrappedPath;
- wrappedPath.reserve(path.size());
- QDoubleVector2D wrappedLeftBound(qInf(), qInf());
- // 1)
- for (const auto &coord : path) {
- QDoubleVector2D wrappedProjection = p.wrapMapProjection(coord);
-
- // We can get NaN if the map isn't set up correctly, or the projection
- // is faulty -- probably best thing to do is abort
- if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y()))
- return QList<QList<QDoubleVector2D> >();
-
- const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x());
- // unwrap x to preserve geometry if moved to border of map
- if (preserveGeometry_ && isPointLessThanUnwrapBelowX) {
- double distance = wrappedProjection.x() - unwrapBelowX;
- if (distance < 0.0)
- distance += 1.0;
- wrappedProjection.setX(unwrapBelowX + distance);
- }
- if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) {
- wrappedLeftBound = wrappedProjection;
- }
- wrappedPath.append(wrappedProjection);
- }
-
-#ifdef QT_LOCATION_DEBUG
- m_wrappedPath = wrappedPath;
-#endif
-
- // 2)
- QList<QList<QDoubleVector2D> > clippedPaths;
- const QList<QDoubleVector2D> &visibleRegion = p.projectableGeometry();
- if (visibleRegion.size()) {
- clippedPaths = clipLine(wrappedPath, visibleRegion);
-
- // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X
- QDoubleVector2D lb(qInf(), qInf());
- for (const QList<QDoubleVector2D> &path: clippedPaths) {
- for (const QDoubleVector2D &p: path) {
- if (p == leftBoundWrapped) {
- lb = p;
- break;
- } else if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) {
- // y-minimization needed to find the same point on polygon and border
- lb = p;
- }
- }
- }
- if (qIsInf(lb.x()))
- return QList<QList<QDoubleVector2D> >();
-
- // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which
- // in turn will make the geometry wrap around.
- lb.setX(qMax(wrappedLeftBound.x(), lb.x()));
- leftBoundWrapped = lb;
- } else {
- clippedPaths.append(wrappedPath);
- }
-
-#ifdef QT_LOCATION_DEBUG
- m_clippedPaths = clippedPaths;
-#endif
-
- return clippedPaths;
-}
-
-void QGeoMapPolylineGeometry::pathToScreen(const QGeoMap &map,
- const QList<QList<QDoubleVector2D> > &clippedPaths,
- const QDoubleVector2D &leftBoundWrapped)
-{
- const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
- // 3) project the resulting geometry to screen position and calculate screen bounds
- double minX = qInf();
- double minY = qInf();
- double maxX = -qInf();
- double maxY = -qInf();
- srcOrigin_ = p.mapProjectionToGeo(p.unwrapMapProjection(leftBoundWrapped));
- QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(leftBoundWrapped);
- for (const QList<QDoubleVector2D> &path: clippedPaths) {
- QDoubleVector2D lastAddedPoint;
- for (qsizetype i = 0; i < path.size(); ++i) {
- QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(path.at(i));
- point = point - origin; // (0,0) if point == geoLeftBound_
-
- minX = qMin(point.x(), minX);
- minY = qMin(point.y(), minY);
- maxX = qMax(point.x(), maxX);
- maxY = qMax(point.y(), maxY);
-
- if (i == 0) {
- srcPoints_ << point.x() << point.y();
- srcPointTypes_ << QPainterPath::MoveToElement;
- lastAddedPoint = point;
- } else {
- if ((point - lastAddedPoint).manhattanLength() > 3 ||
- i == path.size() - 1) {
- srcPoints_ << point.x() << point.y();
- srcPointTypes_ << QPainterPath::LineToElement;
- lastAddedPoint = point;
- }
- }
- }
- }
-
- sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
-}
-
/*!
\internal
*/
void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
- const QList<QDoubleVector2D> &path,
- const QGeoCoordinate geoLeftBound)
+ const QList<QDoubleVector2D> &basePath)
{
+ // A polygon consists of mutliple paths. This is usually a perimeter and multiple holes
+ // We move all paths into a single QPainterPath. The filling rule EvenOdd will then ensure that the paths are shown correctly
if (!sourceDirty_)
return;
-
- geoLeftBound_ = geoLeftBound;
-
- // clear the old data and reserve enough memory
- srcPoints_.clear();
- srcPoints_.reserve(path.size() * 2);
- srcPointTypes_.clear();
- srcPointTypes_.reserve(path.size());
-
- /*
- * Approach:
- * 1) project coordinates to wrapped web mercator, and do unwrapBelowX
- * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons)
- * 3) project the resulting geometry to screen position and calculate screen bounds
- */
-
- QDoubleVector2D leftBoundWrapped;
- // 1, 2)
- const QList<QList<QDoubleVector2D> > &clippedPaths = clipPath(map, path, leftBoundWrapped);
-
- // 3)
- pathToScreen(map, clippedPaths, leftBoundWrapped);
-
+ const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(map.geoProjection());
srcPath_ = QPainterPath();
- maxCoord_ = 0.0;
- const int elemCount = srcPointTypes_.count();
- for (int i = 0; i < elemCount; ++i) {
- switch (srcPointTypes_[i]) {
- case QPainterPath::MoveToElement:
- {
- const qreal x = srcPoints_[2 * i];
- const qreal y = srcPoints_[2 * i + 1];
- if (qMax(x, y) > maxCoord_)
- maxCoord_ = qMax(x, y);
- srcPath_.moveTo(x, y);
- }
- break;
- case QPainterPath::LineToElement:
- {
- const qreal x = srcPoints_[2 * i];
- const qreal y = srcPoints_[2 * i + 1];
- if (qMax(x, y) > maxCoord_)
- maxCoord_ = qMax(x, y);
- srcPath_.lineTo(x, y);
- }
- break;
- default:
- break;
- }
+ srcOrigin_ = p.mapProjectionToGeo(QDoubleVector2D(0, 0)); //avoid warning of NaN values if function is returned early
+
+ //0 Wrap the points around the globe if the path makes more sense that way.
+ // Ultimately, this is done if it is closer to walk around the day-border than the other direction
+ QVarLengthArray<QList<QDoubleVector2D>, 3> wrappedPaths;
+ wrappedPaths << QList<QDoubleVector2D>({basePath[0]});
+ wrappedPaths.last().reserve(basePath.size());
+ for (int i = 1; i < basePath.size(); i++) {
+ if (basePath[i].x() > wrappedPaths.last().last().x() + 0.5)
+ wrappedPaths.last() << basePath[i] - QDoubleVector2D(1.0, 0.0);
+ else if (basePath[i].x() < wrappedPaths.last().last().x() - 0.5)
+ wrappedPaths.last() << basePath[i] + QDoubleVector2D(1.0, 0.0);
+ else
+ wrappedPaths.last() << basePath[i];
}
-}
-
-// *** SCREEN CLIPPING *** //
-
-enum ClipPointType {
- InsidePoint = 0x00,
- LeftPoint = 0x01,
- RightPoint = 0x02,
- BottomPoint = 0x04,
- TopPoint = 0x08
-};
-static inline int clipPointType(qreal x, qreal y, const QRectF &rect)
-{
- int type = InsidePoint;
- if (x < rect.left())
- type |= LeftPoint;
- else if (x > rect.right())
- type |= RightPoint;
- if (y < rect.top())
- type |= TopPoint;
- else if (y > rect.bottom())
- type |= BottomPoint;
- return type;
-}
-
-static void clipSegmentToRect(qreal x0, qreal y0, qreal x1, qreal y1, const QRectF &clipRect,
- QList<qreal> &outPoints, QList<QPainterPath::ElementType> &outTypes)
-{
- int type0 = clipPointType(x0, y0, clipRect);
- int type1 = clipPointType(x1, y1, clipRect);
- bool accept = false;
-
- while (true) {
- if (!(type0 | type1)) {
- accept = true;
- break;
- } else if (type0 & type1) {
- break;
- } else {
- qreal x = 0.0;
- qreal y = 0.0;
- int outsideType = type0 ? type0 : type1;
-
- if (outsideType & BottomPoint) {
- x = x0 + (x1 - x0) * (clipRect.bottom() - y0) / (y1 - y0);
- y = clipRect.bottom() - 0.1;
- } else if (outsideType & TopPoint) {
- x = x0 + (x1 - x0) * (clipRect.top() - y0) / (y1 - y0);
- y = clipRect.top() + 0.1;
- } else if (outsideType & RightPoint) {
- y = y0 + (y1 - y0) * (clipRect.right() - x0) / (x1 - x0);
- x = clipRect.right() - 0.1;
- } else if (outsideType & LeftPoint) {
- y = y0 + (y1 - y0) * (clipRect.left() - x0) / (x1 - x0);
- x = clipRect.left() + 0.1;
- }
-
- if (outsideType == type0) {
- x0 = x;
- y0 = y;
- type0 = clipPointType(x0, y0, clipRect);
- } else {
- x1 = x;
- y1 = y;
- type1 = clipPointType(x1, y1, clipRect);
- }
- }
+ //1 The bounding rectangle of the polygon and camera view are compared to determine if the polygon is visible
+ // The viewport is periodic in x-direction in the interval [-1; 1].
+ // The polygon (maybe) has to be ploted periodically too by shifting it by -1 or +1;
+ const QRectF cameraRect = QDeclarativeGeoMapItemUtils::boundingRectangleFromList(p.visibleGeometry());
+ QRectF itemRect;
+ for (const auto &path : wrappedPaths)
+ itemRect |= QDeclarativeGeoMapItemUtils::boundingRectangleFromList(path).adjusted(-1e-6, -1e-6, 2e-6, 2e-6); //TODO: Maybe use linewidth?
+ for (double xoffset : {-1.0, 1.0}) {
+ if (!cameraRect.intersects(itemRect.translated(QPointF(xoffset,0))))
+ continue;
+ wrappedPaths.append(QList<QDoubleVector2D>());
+ QList<QDoubleVector2D> &wP = wrappedPaths.last();
+ wP.reserve(wrappedPaths.first().size());
+ for (const QDoubleVector2D &coord : wrappedPaths.first())
+ wP.append(coord + QDoubleVector2D(xoffset, 0.0));
}
+ if (wrappedPaths.isEmpty()) // the polygon boundary rectangle does not overlap with the viewport rectangle
+ return;
- if (accept) {
- if (outPoints.size() >= 2) {
- qreal lastX, lastY;
- lastY = outPoints.at(outPoints.size() - 1);
- lastX = outPoints.at(outPoints.size() - 2);
-
- if (!qFuzzyCompare(lastY, y0) || !qFuzzyCompare(lastX, x0)) {
- outTypes << QPainterPath::MoveToElement;
- outPoints << x0 << y0;
- }
+ //2 The polygons that are at least partially in the viewport are cliped to reduce their size
+ QList<QList<QDoubleVector2D>> clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = p.visibleGeometryExpanded();
+ for (const auto &path : wrappedPaths) {
+ if (visibleRegion.size()) {
+ clippedPaths << clipLine(path, visibleRegion);
+ //TODO: Replace clipping with Clipper lib, similar to QPolygonMapItem
} else {
- outTypes << QPainterPath::MoveToElement;
- outPoints << x0 << y0;
- }
-
- outTypes << QPainterPath::LineToElement;
- outPoints << x1 << y1;
- }
-}
-
-static void clipPathToRect(const QList<qreal> &points,
- const QList<QPainterPath::ElementType> &types, const QRectF &clipRect,
- QList<qreal> &outPoints, QList<QPainterPath::ElementType> &outTypes)
-{
- outPoints.clear();
- outPoints.reserve(points.size());
- outTypes.clear();
- outTypes.reserve(types.size());
-
- qreal lastX = 0;
- qreal lastY = 0; // or else used uninitialized
- for (qsizetype i = 0; i < types.size(); ++i) {
- if (i > 0 && types[i] != QPainterPath::MoveToElement) {
- qreal x = points[i * 2], y = points[i * 2 + 1];
- clipSegmentToRect(lastX, lastY, x, y, clipRect, outPoints, outTypes);
+ clippedPaths.append(path); //Do we really need this if there are no visible regions??
}
-
- lastX = points[i * 2];
- lastY = points[i * 2 + 1];
- }
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-/*!
- \internal
-*/
-void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
- qreal strokeWidth,
- bool adjustTranslation)
-{
- if (!screenDirty_)
- return;
-
- QPointF origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false).toPointF();
-
- if (!qIsFinite(origin.x()) || !qIsFinite(origin.y()) || srcPointTypes_.size() < 2) { // the line might have been clipped away.
- clear();
- return;
- }
-
- // 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 * 2, strokeWidth * 2);
- viewport.translate(-1 * origin);
-
- QList<qreal> points;
- QList<QPainterPath::ElementType> types;
-
- if (clipToViewport_) {
- // Although the geometry has already been clipped against the visible region in wrapped mercator space.
- // This is currently still needed to prevent a number of artifacts deriving from QTriangulatingStroker processing
- // very large lines (that is, polylines that span many pixels in screen space)
- clipPathToRect(srcPoints_, srcPointTypes_, viewport, points, types);
- } else {
- points = srcPoints_;
- types = srcPointTypes_;
}
-
- QVectorPath vp(points.data(), types.size(), types.data());
- QTriangulatingStroker ts;
- // As of Qt5.11, the clip argument is not actually used, in the call below.
- ts.process(vp, QPen(QBrush(Qt::black), strokeWidth), QRectF(), QPainter::Antialiasing);
-
- clear();
-
- // Nothing is on the screen
- if (ts.vertexCount() == 0)
+ if (clippedPaths.isEmpty()) //the polygon is entirely outside visibleRegion
return;
- // QTriangulatingStroker#vertexCount is actually the length of the array,
- // not the number of vertices
- screenVertices_.reserve(ts.vertexCount());
-
QRectF bb;
+ for (const auto &path: clippedPaths)
+ bb |= QDeclarativeGeoMapItemUtils::boundingRectangleFromList(path);
+ //Offset by origin, find the maximum coordinate
+ maxCoord_ = 0.0;
+ srcOrigin_ = p.mapProjectionToGeo(QDoubleVector2D(bb.left(), bb.top()));
+ QDoubleVector2D origin = p.wrappedMapProjectionToItemPosition(p.geoToWrappedMapProjection(srcOrigin_)); //save way: redo all projections
+ for (const auto &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (qsizetype i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = p.wrappedMapProjectionToItemPosition(path.at(i));
+ point = point - origin; // (0,0) if point == origin
- QPointF pt;
- const float *vs = ts.vertices();
- for (int i = 0; i < (ts.vertexCount()/2*2); i += 2) {
- pt = QPointF(vs[i], vs[i + 1]);
- screenVertices_ << pt;
-
- if (!qIsFinite(pt.x()) || !qIsFinite(pt.y()))
- break;
-
- if (!bb.contains(pt)) {
- if (pt.x() < bb.left())
- bb.setLeft(pt.x());
-
- if (pt.x() > bb.right())
- bb.setRight(pt.x());
-
- if (pt.y() < bb.top())
- bb.setTop(pt.y());
-
- if (pt.y() > bb.bottom())
- bb.setBottom(pt.y());
- }
- }
-
- screenBounds_ = bb;
-
- const QPointF strokeOffset = (adjustTranslation) ? QPointF(strokeWidth, strokeWidth) * 0.5: QPointF();
- const QPointF offset = -1 * sourceBounds_.topLeft() + strokeOffset;
- for (qsizetype i = 0; i < screenVertices_.size(); ++i)
- screenVertices_[i] += offset;
-
- firstPointOffset_ += offset;
- screenOutline_.translate(offset);
- screenBounds_.translate(offset);
-}
-
-void QGeoMapPolylineGeometry::clearSource()
-{
- srcPoints_.clear();
- srcPointTypes_.clear();
-}
+ if (qMax(point.x(), point.y()) > maxCoord_)
+ maxCoord_ = qMax(point.x(), point.y());
-bool QGeoMapPolylineGeometry::contains(const QPointF &point) const
-{
- // screenOutline_.contains(screenPoint) doesn't work, as, it appears, that
- // screenOutline_ for QGeoMapPolylineGeometry is empty (QRectF(0,0 0x0))
- const QList<QPointF> &verts = vertices();
- QPolygonF tri;
- for (const auto &v : verts) {
- tri << v;
- if (tri.size() == 3) {
- if (tri.containsPoint(point, Qt::OddEvenFill))
- return true;
- tri.remove(0);
+ if (i == 0) {
+ srcPath_.moveTo(point.toPointF());
+ lastAddedPoint = point;
+ } else {
+ if ((point - lastAddedPoint).manhattanLength() > 3 ||
+ i == path.size() - 1) {
+ srcPath_.lineTo(point.toPointF());
+ lastAddedPoint = point;
+ }
+ }
}
}
- return false;
+ sourceBounds_ = srcPath_.boundingRect();
}
/*
@@ -750,7 +425,7 @@ void QDeclarativePolylineMapItemPrivateCPU::regenerateCache()
return;
const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(m_poly.map()->geoProjection());
m_geopathProjected.clear();
- m_geopathProjected.reserve(m_poly.m_geopath.size());
+ m_geopathProjected.reserve(m_poly.m_geopath.path().size());
for (const QGeoCoordinate &c : m_poly.m_geopath.path())
m_geopathProjected << p.geoToMapProjection(c);
}
@@ -778,16 +453,14 @@ void QDeclarativePolylineMapItemPrivateCPU::updatePolish()
const QGeoMap *map = m_poly.map();
const qreal borderWidth = m_poly.m_line.width();
- m_geometry.updateSourcePoints(*map, m_geopathProjected, m_poly.m_geopath.boundingGeoRectangle().topLeft());
-
- // still needed even with Shapes, due to contains()
- m_geometry.updateScreenPoints(*map, borderWidth);
+ m_geometry.updateSourcePoints(*map, m_geopathProjected);
const QRectF bb = m_geometry.sourceBoundingBox();
m_poly.setSize(bb.size() + QSizeF(borderWidth, borderWidth));
// it has to be shifted so that the center of the line is on the correct geocoord
m_poly.setPositionOnMap(m_geometry.origin(), -1 * bb.topLeft() + QPointF(borderWidth, borderWidth) * 0.5);
+
m_poly.setShapeTriangulationScale(m_shape, m_geometry.maxCoord_);
m_shapePath->setStrokeColor(m_poly.m_line.color());
@@ -808,11 +481,8 @@ QSGNode *QDeclarativePolylineMapItemPrivateCPU::updateMapItemPaintNode(QSGNode *
{
delete oldNode;
- //TODO: update only material
- if (m_geometry.isScreenDirty() || m_poly.m_dirtyMaterial || !oldNode) {
- m_geometry.setPreserveGeometry(false);
+ if (m_geometry.isScreenDirty() || !oldNode) {
m_geometry.markClean();
- m_poly.m_dirtyMaterial = false;
}
return nullptr;
}
@@ -822,7 +492,21 @@ bool QDeclarativePolylineMapItemPrivateCPU::contains(const QPointF &point) const
// With Shapes, do not just call
// m_shape->contains(m_poly.mapToItem(m_shape, point)) because that can
// only do FillContains at best, whereas the polyline relies on stroking.
- return m_geometry.contains(point);
+
+ const QPainterPath &path = m_geometry.srcPath_;
+ const double &lineWidth = m_poly.m_line.width();
+ const QPointF p = m_poly.mapToItem(m_shape, point) - QPointF(lineWidth, lineWidth) * 0.5;
+
+ for (int i = 1; i < path.elementCount(); i++) {
+ if (path.elementAt(i).type == QPainterPath::MoveToElement)
+ continue;
+ const double dsqr = QDeclarativeGeoMapItemUtils::distanceSqrPointLine(p.x(), p.y(),
+ path.elementAt(i - 1).x, path.elementAt(i - 1).y,
+ path.elementAt(i).x, path.elementAt(i).y);
+ if (dsqr < 0.25 * lineWidth * lineWidth)
+ return true;
+ }
+ return false;
}
/*
diff --git a/src/location/quickmapitems/qdeclarativepolylinemapitem_p.h b/src/location/quickmapitems/qdeclarativepolylinemapitem_p.h
index 377fb239..ce5d29cc 100644
--- a/src/location/quickmapitems/qdeclarativepolylinemapitem_p.h
+++ b/src/location/quickmapitems/qdeclarativepolylinemapitem_p.h
@@ -109,7 +109,6 @@ public:
QGeoPath m_geopath;
QDeclarativeMapLineProperties m_line;
- bool m_dirtyMaterial = true;
bool m_updatingGeometry = false;
std::unique_ptr<QDeclarativePolylineMapItemPrivate> m_d;
diff --git a/src/location/quickmapitems/qdeclarativepolylinemapitem_p_p.h b/src/location/quickmapitems/qdeclarativepolylinemapitem_p_p.h
index 4ade0fba..ef482f3c 100644
--- a/src/location/quickmapitems/qdeclarativepolylinemapitem_p_p.h
+++ b/src/location/quickmapitems/qdeclarativepolylinemapitem_p_p.h
@@ -31,47 +31,15 @@ QT_BEGIN_NAMESPACE
class QQuickShape;
class QQuickShapePath;
-class Q_LOCATION_PRIVATE_EXPORT QGeoMapPolylineGeometry : public QGeoMapItemGeometry
+struct QGeoMapPolylineGeometry : QGeoMapItemGeometry
{
-public:
- QGeoMapPolylineGeometry();
-
void updateSourcePoints(const QGeoMap &map,
- const QList<QDoubleVector2D> &path,
- const QGeoCoordinate geoLeftBound);
-
- void updateScreenPoints(const QGeoMap &map,
- qreal strokeWidth,
- bool adjustTranslation = true);
-
- void clearSource();
-
- bool contains(const QPointF &point) const override;
-
- QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map,
- const QList<QDoubleVector2D> &path,
- QDoubleVector2D &leftBoundWrapped);
-
- void pathToScreen(const QGeoMap &map,
- const QList<QList<QDoubleVector2D> > &clippedPaths,
- const QDoubleVector2D &leftBoundWrapped);
+ const QList<QDoubleVector2D> &basePath);
QPainterPath srcPath() const { return srcPath_; }
-public:
- QList<qreal> srcPoints_;
- QList<QPainterPath::ElementType> srcPointTypes_;
QPainterPath srcPath_;
qreal maxCoord_ = 0.0;
-
-#ifdef QT_LOCATION_DEBUG
- QList<QDoubleVector2D> m_wrappedPath;
- QList<QList<QDoubleVector2D>> m_clippedPaths;
-#endif
-
- friend class QDeclarativeCircleMapItem;
- friend class QDeclarativePolygonMapItem;
- friend class QDeclarativeRectangleMapItem;
};
class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItemPrivate
@@ -116,14 +84,9 @@ public:
}
void regenerateCache();
void updateCache();
- void preserveGeometry()
- {
- m_geometry.setPreserveGeometry(true, m_poly.m_geopath.boundingGeoRectangle().topLeft());
- }
void afterViewportChanged() override
{
// preserveGeometry is cleared in updateMapItemPaintNode
- preserveGeometry();
markSourceDirtyAndUpdate();
}
void onMapSet() override
@@ -134,13 +97,11 @@ public:
void onGeoGeometryChanged() override
{
regenerateCache();
- preserveGeometry();
markSourceDirtyAndUpdate();
}
void onGeoGeometryUpdated() override
{
updateCache();
- preserveGeometry();
markSourceDirtyAndUpdate();
}
void onItemGeometryChanged() override
diff --git a/src/location/quickmapitems/qdeclarativerectanglemapitem.cpp b/src/location/quickmapitems/qdeclarativerectanglemapitem.cpp
index 1fbc9215..0e4e9e02 100644
--- a/src/location/quickmapitems/qdeclarativerectanglemapitem.cpp
+++ b/src/location/quickmapitems/qdeclarativerectanglemapitem.cpp
@@ -211,7 +211,6 @@ void QDeclarativeRectangleMapItem::setColor(const QColor &color)
if (m_color == color)
return;
m_color = color;
- m_dirtyMaterial = true;
polishAndUpdate();
emit colorChanged(m_color);
}
@@ -359,8 +358,7 @@ void QDeclarativeRectangleMapItemPrivateCPU::updatePolish()
const QList<QGeoCoordinate> perimeter = QGeoMapItemGeometry::path(m_rect.m_rectangle);
const QList<QDoubleVector2D> pathMercator_ = QGeoMapItemGeometry::pathMercator(perimeter);
- m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft());
- m_geometry.updateSourcePoints(*m_rect.map(), pathMercator_);
+ m_geometry.updateSourcePoints(*m_rect.map(), QList<QList<QDoubleVector2D>>{pathMercator_});
m_rect.setShapeTriangulationScale(m_shape, m_geometry.maxCoord());
@@ -389,10 +387,8 @@ QSGNode *QDeclarativeRectangleMapItemPrivateCPU::updateMapItemPaintNode(QSGNode
{
Q_UNUSED(data);
delete oldNode;
- if (m_geometry.isScreenDirty() || m_rect.m_dirtyMaterial) {
- m_geometry.setPreserveGeometry(false);
+ if (m_geometry.isScreenDirty()) {
m_geometry.markClean();
- m_rect.m_dirtyMaterial = false;
}
return nullptr;
}
diff --git a/src/location/quickmapitems/qdeclarativerectanglemapitem_p.h b/src/location/quickmapitems/qdeclarativerectanglemapitem_p.h
index 883b261e..467857fe 100644
--- a/src/location/quickmapitems/qdeclarativerectanglemapitem_p.h
+++ b/src/location/quickmapitems/qdeclarativerectanglemapitem_p.h
@@ -80,7 +80,6 @@ private:
QGeoRectangle m_rectangle;
QDeclarativeMapLineProperties m_border;
QColor m_color = Qt::transparent;
- bool m_dirtyMaterial = true;
bool m_updatingGeometry = false;
diff --git a/src/location/quickmapitems/qdeclarativerectanglemapitem_p_p.h b/src/location/quickmapitems/qdeclarativerectanglemapitem_p_p.h
index 3f914dd0..2df6767d 100644
--- a/src/location/quickmapitems/qdeclarativerectanglemapitem_p_p.h
+++ b/src/location/quickmapitems/qdeclarativerectanglemapitem_p_p.h
@@ -72,12 +72,10 @@ public:
}
void onItemGeometryChanged() override
{
- m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft());
markSourceDirtyAndUpdate();
}
void afterViewportChanged() override
{
- m_geometry.setPreserveGeometry(true, m_rect.m_rectangle.topLeft());
markSourceDirtyAndUpdate();
}
void updatePolish() override;
diff --git a/src/location/quickmapitems/qgeomapitemgeometry_p.h b/src/location/quickmapitems/qgeomapitemgeometry_p.h
index c7fd2f57..0fa90ae4 100644
--- a/src/location/quickmapitems/qgeomapitemgeometry_p.h
+++ b/src/location/quickmapitems/qgeomapitemgeometry_p.h
@@ -74,12 +74,6 @@ public:
inline void markClean() { screenDirty_ = (sourceDirty_ = false); clipToViewport_ = true;}
inline void clearScreen() { screenDirty_ = false; }
- inline void setPreserveGeometry(bool value, const QGeoCoordinate &geoLeftBound = QGeoCoordinate())
- {
- preserveGeometry_ = value;
- if (preserveGeometry_)
- geoLeftBound_ = geoLeftBound;
- }
inline QGeoCoordinate geoLeftBound() { return geoLeftBound_; }
inline QRectF sourceBoundingBox() const { return sourceBounds_; }