summaryrefslogtreecommitdiff
path: root/src/imports
diff options
context:
space:
mode:
authorPaolo Angelelli <paolo.angelelli@qt.io>2016-12-11 20:44:17 +0100
committerPaolo Angelelli <paolo.angelelli@qt.io>2017-01-26 14:45:48 +0000
commitc57d42b47004623db9b934d0688180ec6dc1a73e (patch)
treeba4cd0bb2f332db2ad2ad4d144cc2fa3227f3fc2 /src/imports
parenta33f9131a3f5b07831ea9565cb0dc22e078f9475 (diff)
downloadqtlocation-c57d42b47004623db9b934d0688180ec6dc1a73e.tar.gz
Add clipping for rotated/tilted Map Items
This patch adds proper rotation/tilting support to Map Items. To do so, clipping is now performed in wrapped mercator space instead of screen space. This prevents projection of geo coordinates that ended behind the camera, and that would be projected incorrectly by the projection transformation. This patch therefore does not use the screen clipping code any longer (clipPathToRect), since the geometry has already been clipped. The downside is that updateSourcePoints is now necessary for any viewport change. This would be necessary anyway in presence of tilt or rotation. NB: Handling of MapQuickItems with zoomLevel set is still TODO. Future work: 1) Optimize updateSourcePoints by pre-computing the mercator projection of the geometry, and let updateSourcePoints do only the wrapping/clipping/projection-to-screen operations. 2) Remove updateScreenPoints altogether Change-Id: Ie0d3dbef68d48ac97a596d40240d0ac126c0efaf Reviewed-by: Alex Blasche <alexander.blasche@qt.io> Reviewed-by: Paolo Angelelli <paolo.angelelli@qt.io> Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Diffstat (limited to 'src/imports')
-rw-r--r--src/imports/location/location.pro3
-rw-r--r--src/imports/location/qdeclarativecirclemapitem.cpp340
-rw-r--r--src/imports/location/qdeclarativecirclemapitem_p.h2
-rw-r--r--src/imports/location/qdeclarativegeomapitembase.cpp7
-rw-r--r--src/imports/location/qdeclarativepolygonmapitem.cpp171
-rw-r--r--src/imports/location/qdeclarativepolylinemapitem.cpp215
-rw-r--r--src/imports/location/qdeclarativepolylinemapitem_p.h13
-rw-r--r--src/imports/location/qdeclarativerectanglemapitem.cpp128
-rw-r--r--src/imports/location/qdeclarativerectanglemapitem_p.h15
-rw-r--r--src/imports/location/qgeomapitemgeometry.cpp10
10 files changed, 527 insertions, 377 deletions
diff --git a/src/imports/location/location.pro b/src/imports/location/location.pro
index e733a768..1f4f2822 100644
--- a/src/imports/location/location.pro
+++ b/src/imports/location/location.pro
@@ -4,6 +4,9 @@ INCLUDEPATH += ../../location
INCLUDEPATH += ../../location/maps
INCLUDEPATH += ../../positioning
INCLUDEPATH += ../positioning
+INCLUDEPATH += ../../3rdparty/clip2tri
+INCLUDEPATH += ../../3rdparty/clipper
+INCLUDEPATH += ../../3rdparty/poly2tri
INCLUDEPATH *= $$PWD
HEADERS += \
diff --git a/src/imports/location/qdeclarativecirclemapitem.cpp b/src/imports/location/qdeclarativecirclemapitem.cpp
index 5e139bd5..f5520e4b 100644
--- a/src/imports/location/qdeclarativecirclemapitem.cpp
+++ b/src/imports/location/qdeclarativecirclemapitem.cpp
@@ -40,17 +40,22 @@
#include "qwebmercator_p.h"
#include <cmath>
+#include <algorithm>
#include <QtCore/QScopedValueRollback>
#include <QPen>
#include <QPainter>
+#include <QtGui/private/qtriangulator_p.h>
#include "qdoublevector2d_p.h"
#include "qlocationutils_p.h"
+#include "qgeocircle.h"
/* poly2tri triangulator includes */
-#include "../../3rdparty/poly2tri/common/shapes.h"
-#include "../../3rdparty/poly2tri/sweep/cdt.h"
+#include <common/shapes.h>
+#include <sweep/cdt.h>
+
+#include <QtPositioning/private/qclipperutils_p.h>
QT_BEGIN_NAMESPACE
@@ -122,9 +127,10 @@ QT_BEGIN_NAMESPACE
\image api-mapcircle.png
*/
-#ifndef M_PI
-#define M_PI 3.14159265358979323846
+#ifdef M_PI
+#undef M_PI
#endif
+#define M_PI 3.14159265358979323846264338327950288
static const int CircleSamples = 128;
@@ -140,53 +146,105 @@ QGeoMapCircleGeometry::QGeoMapCircleGeometry()
/*!
\internal
*/
-void QGeoMapCircleGeometry::updateScreenPointsInvert(const QGeoMap &map)
+void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QGeoCoordinate> &circlePath, const QGeoMap &map)
{
- if (!screenDirty_)
- return;
-
- if (map.viewportWidth() == 0 || map.viewportHeight() == 0) {
- clear();
+ // Not checking for !screenDirty anymore, as everything is now recalculated.
+ clear();
+ if (map.viewportWidth() == 0 || map.viewportHeight() == 0 || circlePath.size() < 3) // a circle requires at least 3 points;
return;
- }
-
- QPointF origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false).toPointF();
- QPainterPath ppi = srcPath_;
-
- clear();
+ /*
+ * 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)
+ double topLati = QLocationUtils::mercatorMaxLatitude();
+ double bottomLati = -(QLocationUtils::mercatorMaxLatitude());
+ double leftLongi = QLocationUtils::mapLeftLongitude(map.cameraData().center().longitude());
+ double rightLongi = QLocationUtils::mapRightLongitude(map.cameraData().center().longitude());
+
+ srcOrigin_ = QGeoCoordinate(topLati,leftLongi);
+ QDoubleVector2D tl = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(topLati,leftLongi));
+ QDoubleVector2D tr = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(topLati,rightLongi));
+ QDoubleVector2D br = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(bottomLati,rightLongi));
+ QDoubleVector2D bl = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(bottomLati,leftLongi));
+
+ QList<QDoubleVector2D> fill;
+ fill << tl << tr << br << bl;
+
+ QList<QDoubleVector2D> hole;
+ for (const QGeoCoordinate &c: circlePath)
+ hole << map.geoProjection().geoToWrappedMapProjection(c);
+
+ c2t::clip2tri clipper;
+ clipper.addSubjectPath(QClipperUtils::qListToPath(fill), true);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(hole));
+ Paths difference = clipper.execute(c2t::clip2tri::Difference, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+
+ // 2)
+ QDoubleVector2D lb = map.geoProjection().geoToWrappedMapProjection(srcOrigin_);
+ QList<QList<QDoubleVector2D> > clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion();
+ if (visibleRegion.size()) {
+ clipper.clearClipper();
+ for (const Path &p: difference)
+ clipper.addSubjectPath(p, true);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
+ Paths res = clipper.execute(c2t::clip2tri::Intersection, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+ clippedPaths = QClipperUtils::pathsToQList(res);
+
+ // 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;
- // a circle requires at least 3 points;
- if (ppi.elementCount() < 3)
- 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_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(lb));
+ } else {
+ clippedPaths = QClipperUtils::pathsToQList(difference);
+ }
- // translate the path into top-left-centric coordinates
- QRectF bb = ppi.boundingRect();
- ppi.translate(-bb.left(), -bb.top());
- firstPointOffset_ = -1 * bb.topLeft();
-
- ppi.closeSubpath();
-
- // calculate actual width of map on screen in pixels
- QGeoCoordinate mapCenter(0, map.cameraData().center().longitude());
- QDoubleVector2D midPoint = map.geoProjection().coordinateToItemPosition(mapCenter, false);
- QDoubleVector2D midPointPlusOne = QDoubleVector2D(midPoint.x() + 1.0, midPoint.y());
- QGeoCoordinate coord1 = map.geoProjection().itemPositionToCoordinate(midPointPlusOne, false);
- double geoDistance = coord1.longitude() - map.cameraData().center().longitude();
- if ( geoDistance < 0 )
- geoDistance += 360.0;
- double mapWidth = 360.0 / geoDistance;
-
- qreal leftOffset = origin.x() - (map.viewportWidth()/2.0 - mapWidth/2.0) - firstPointOffset_.x();
- qreal topOffset = origin.y() - (midPoint.y() - mapWidth/2.0) - firstPointOffset_.y();
- QPainterPath ppiBorder;
- ppiBorder.moveTo(QPointF(-leftOffset, -topOffset));
- ppiBorder.lineTo(QPointF(mapWidth - leftOffset, -topOffset));
- ppiBorder.lineTo(QPointF(mapWidth - leftOffset, mapWidth - topOffset));
- ppiBorder.lineTo(QPointF(-leftOffset, mapWidth - topOffset));
-
- screenOutline_ = ppiBorder;
+ //3)
+ QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(lb);
+
+ QPainterPath ppi;
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (int i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i));
+ //point = point - origin; // Do this using ppi.translate()
+
+ if (i == 0) {
+ ppi.moveTo(point.toPointF());
+ lastAddedPoint = point;
+ } else {
+ if ((point - lastAddedPoint).manhattanLength() > 3 ||
+ i == path.size() - 1) {
+ ppi.lineTo(point.toPointF());
+ lastAddedPoint = point;
+ }
+ }
+ }
+ ppi.closeSubpath();
+ }
+ ppi.translate(-1 * origin.toPointF());
+#if 0 // old poly2tri code, has to be ported to clip2tri in order to work with tilted projections
std::vector<p2t::Point*> borderPts;
borderPts.reserve(4);
@@ -235,20 +293,28 @@ void QGeoMapCircleGeometry::updateScreenPointsInvert(const QGeoMap &map)
qDeleteAll(borderPts.begin(), borderPts.end());
borderPts.clear();
}
+#else // Using qTriangulate as this case is not frequent, and not many circles including both poles are usually used
+ QTriangleSet ts = qTriangulate(ppi);
+ qreal *vx = ts.vertices.data();
- screenBounds_ = ppiBorder.boundingRect();
-
-}
+ screenIndices_.reserve(ts.indices.size());
+ screenVertices_.reserve(ts.vertices.size());
-static const qreal qgeocoordinate_EARTH_MEAN_RADIUS = 6371.0072;
+ if (ts.indices.type() == QVertexIndexVector::UnsignedInt) {
+ const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data());
+ for (int i = 0; i < (ts.indices.size()/3*3); ++i)
+ screenIndices_ << ix[i];
+ } else {
+ const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data());
+ for (int i = 0; i < (ts.indices.size()/3*3); ++i)
+ screenIndices_ << ix[i];
+ }
+ for (int i = 0; i < (ts.vertices.size()/2*2); i += 2)
+ screenVertices_ << QPointF(vx[i], vx[i + 1]);
+#endif
-inline static qreal qgeocoordinate_degToRad(qreal deg)
-{
- return deg * M_PI / 180;
-}
-inline static qreal qgeocoordinate_radToDeg(qreal rad)
-{
- return rad * 180 / M_PI;
+ screenBounds_ = ppi.boundingRect();
+ sourceBounds_ = screenBounds_;
}
static bool crossEarthPole(const QGeoCoordinate &center, qreal distance)
@@ -267,8 +333,7 @@ static bool crossEarthPole(const QGeoCoordinate &center, qreal distance)
static void calculatePeripheralPoints(QList<QGeoCoordinate> &path,
const QGeoCoordinate &center,
qreal distance,
- int steps,
- QGeoCoordinate &leftBound )
+ int steps)
{
// Calculate points based on great-circle distance
// Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function
@@ -277,38 +342,27 @@ static void calculatePeripheralPoints(QList<QGeoCoordinate> &path,
// pre-calculations
steps = qMax(steps, 3);
qreal centerLon = center.longitude();
- qreal minLon = centerLon;
- qreal latRad = qgeocoordinate_degToRad(center.latitude());
- qreal lonRad = qgeocoordinate_degToRad(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 / (qgeocoordinate_EARTH_MEAN_RADIUS * 1000.0));
+ 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 = qgeocoordinate_radToDeg(resultLatRad);
- qreal lon2 = QLocationUtils::wrapLong(qgeocoordinate_radToDeg(resultLonRad));
+ 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);
}
QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent)
@@ -476,37 +530,71 @@ void QDeclarativeCircleMapItem::updatePolish()
if (geometry_.isSourceDirty()) {
circlePath_.clear();
- calculatePeripheralPoints(circlePath_, center_, radius_, CircleSamples, geoLeftBound_);
+ calculatePeripheralPoints(circlePath_, center_, radius_, CircleSamples);
+ geoLeftBound_ = QGeoCircle(center(), radius()).boundingGeoRectangle().topLeft();
}
+ QList<QGeoCoordinate> originalCirclePath = circlePath_;
+
int pathCount = circlePath_.size();
bool preserve = preserveCircleGeometry(circlePath_, center_, radius_);
+ geometry_.setPreserveGeometry(true, geoLeftBound_); // to set the geoLeftBound_
geometry_.setPreserveGeometry(preserve, geoLeftBound_);
- geometry_.updateSourcePoints(*map(), circlePath_);
- if (crossEarthPole(center_, radius_) && circlePath_.size() == pathCount)
- geometry_.updateScreenPointsInvert(*map()); // invert fill area for really huge circles
- else geometry_.updateScreenPoints(*map());
+
+ bool invertedCircle = false;
+ if (crossEarthPole(center_, 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());
+ }
+
+ borderGeometry_.clear();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
if (border_.color() != Qt::transparent && border_.width() > 0) {
QList<QGeoCoordinate> closedPath = circlePath_;
closedPath << closedPath.first();
+
+ QGeoCoordinate lb = geoLeftBound_;
+ if (invertedCircle) {
+ closedPath = originalCirclePath;
+ closedPath << closedPath.first();
+ std::reverse(closedPath.begin(), closedPath.end());
+
+ double circumferenceRadius = QLocationUtils::earthMeanDiameter() * 0.5 - radius();
+ QGeoCoordinate circumferenceCenter = QLocationUtils::antipodalPoint(center());
+ lb = QGeoCircle(circumferenceCenter, circumferenceRadius).boundingGeoRectangle().topLeft();
+ }
+
+ borderGeometry_.setPreserveGeometry(true, geoLeftBound_); // to set the geoLeftBound_
borderGeometry_.setPreserveGeometry(preserve, geoLeftBound_);
- borderGeometry_.updateSourcePoints(*map(), closedPath, geoLeftBound_);
- borderGeometry_.updateScreenPoints(*map(), border_.width());
- QList<QGeoMapItemGeometry *> geoms;
- geoms << &geometry_ << &borderGeometry_;
- QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail.
+ const QGeoCoordinate &geometryOrigin = geometry_.origin();
- setWidth(combined.width());
- setHeight(combined.height());
- } else {
- borderGeometry_.clear();
- setWidth(geometry_.screenBoundingBox().width());
- setHeight(geometry_.screenBoundingBox().height());
+ borderGeometry_.srcPoints_.clear();
+ borderGeometry_.srcPointTypes_.clear();
+
+ QDoubleVector2D borderLeftBoundWrapped;
+ QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped);
+ if (clippedPaths.size()) {
+ borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
}
- setPositionOnMap(geoLeftBound_, geometry_.firstPointOffset());
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ setWidth(combined.width());
+ setHeight(combined.height());
+
+ setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset());
}
/*!
@@ -517,25 +605,8 @@ void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChange
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- if (event.centerChanged && crossEarthPole(center_, radius_)) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- geometry_.markScreenDirty();
- borderGeometry_.markScreenDirty();
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
polishAndUpdate();
}
@@ -578,8 +649,23 @@ bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QGeoCoordinate> &p
}
-
-// A workaround for circle path to be drawn correctly using a polygon geometry
+/*
+ * A workaround for circle path to be drawn correctly using a polygon geometry
+ * This method generates a polygon like
+ * _____________
+ * | |
+ * \ /
+ * | |
+ * / \
+ * | |
+ * -------------
+ *
+ * or a polygon like
+ *
+ * ______________
+ * | ____ |
+ * \__/ \__/
+ */
void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QGeoCoordinate> &path,
const QGeoCoordinate &center,
qreal distance)
@@ -589,24 +675,24 @@ void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QGeoCoordinat
qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0));
bool crossNorthPole = distanceToNorthPole < distance;
bool crossSouthPole = distanceToSouthPole < distance;
- if (!crossNorthPole && !crossSouthPole)
- return;
+
QList<int> wrapPathIndex;
- // calculate actual width of map on screen in pixels
- QDoubleVector2D midPoint = map()->geoProjection().coordinateToItemPosition(map()->cameraData().center(), false);
- QDoubleVector2D midPointPlusOne(midPoint.x() + 1.0, midPoint.y());
- QGeoCoordinate coord1 = map()->geoProjection().itemPositionToCoordinate(midPointPlusOne, false);
- qreal geoDistance = coord1.longitude() - map()->cameraData().center().longitude();
- if ( geoDistance < 0 )
- geoDistance += 360;
- qreal mapWidth = 360.0 / geoDistance;
- mapWidth = qMin(static_cast<int>(mapWidth), map()->viewportWidth());
- QDoubleVector2D prev = map()->geoProjection().coordinateToItemPosition(path.at(0), false);
+ QDoubleVector2D prev = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(0)));
+
+ for (int i = 1; i <= path.count(); ++i) {
+ int index = i % path.count();
+ QDoubleVector2D point = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(index)));
+ double diff = qAbs(point.x() - prev.x());
+ if (diff > 0.5) {
+ continue;
+ }
+ }
+
// find the points in path where wrapping occurs
for (int i = 1; i <= path.count(); ++i) {
int index = i % path.count();
- QDoubleVector2D point = map()->geoProjection().coordinateToItemPosition(path.at(index), false);
- if ( (qAbs(point.x() - prev.x())) >= mapWidth/2.0 ) {
+ QDoubleVector2D point = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(index)));
+ if ( (qAbs(point.x() - prev.x())) >= 0.5 ) { // TODO: Add a projectionWidth to GeoProjection, perhaps?
wrapPathIndex << index;
if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole))
break;
diff --git a/src/imports/location/qdeclarativecirclemapitem_p.h b/src/imports/location/qdeclarativecirclemapitem_p.h
index c91d1606..0305000d 100644
--- a/src/imports/location/qdeclarativecirclemapitem_p.h
+++ b/src/imports/location/qdeclarativecirclemapitem_p.h
@@ -61,7 +61,7 @@ class QGeoMapCircleGeometry : public QGeoMapPolygonGeometry
public:
QGeoMapCircleGeometry();
- void updateScreenPointsInvert(const QGeoMap &map);
+ void updateScreenPointsInvert(const QList<QGeoCoordinate> &circlePath, const QGeoMap &map);
};
class QDeclarativeCircleMapItem : public QDeclarativeGeoMapItemBase
diff --git a/src/imports/location/qdeclarativegeomapitembase.cpp b/src/imports/location/qdeclarativegeomapitembase.cpp
index 84bf757d..8e25e853 100644
--- a/src/imports/location/qdeclarativegeomapitembase.cpp
+++ b/src/imports/location/qdeclarativegeomapitembase.cpp
@@ -177,7 +177,12 @@ void QDeclarativeGeoMapItemBase::setPositionOnMap(const QGeoCoordinate &coordina
if (!map_ || !quickMap_)
return;
- QPointF topLeft = map_->geoProjection().coordinateToItemPosition(coordinate, false).toPointF() - offset;
+ QDoubleVector2D wrappedProjection = map_->geoProjection().geoToWrappedMapProjection(coordinate);
+ if (! map_->geoProjection().isProjectable(wrappedProjection))
+ return;
+
+ QDoubleVector2D pos = map_->geoProjection().wrappedMapProjectionToItemPosition(wrappedProjection);
+ QPointF topLeft = pos.toPointF() - offset;
setPosition(topLeft);
}
diff --git a/src/imports/location/qdeclarativepolygonmapitem.cpp b/src/imports/location/qdeclarativepolygonmapitem.cpp
index 19d68e5c..bfd57e99 100644
--- a/src/imports/location/qdeclarativepolygonmapitem.cpp
+++ b/src/imports/location/qdeclarativepolygonmapitem.cpp
@@ -48,10 +48,11 @@
#include <QPainterPath>
#include <qnumeric.h>
-#include "qdoublevector2d_p.h"
+#include <QtPositioning/private/qdoublevector2d_p.h>
+#include <QtPositioning/private/qclipperutils_p.h>
/* poly2tri triangulator includes */
-#include "../../3rdparty/clip2tri/clip2tri.h"
+#include <clip2tri.h>
QT_BEGIN_NAMESPACE
@@ -131,11 +132,6 @@ QT_BEGIN_NAMESPACE
\image api-mappolygon.png
*/
-struct Vertex
-{
- QVector2D position;
-};
-
QGeoMapPolygonGeometry::QGeoMapPolygonGeometry()
: assumeSimple_(false)
{
@@ -150,55 +146,100 @@ void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map,
if (!sourceDirty_)
return;
- bool foundValid = false;
-
- // build the actual path
- QDoubleVector2D lastAddedPoint;
srcPath_ = QPainterPath();
+ // build the actual path
+ // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
srcOrigin_ = geoLeftBound_;
- QDoubleVector2D origin = map.geoProjection().coordinateToItemPosition(geoLeftBound_, false);
double unwrapBelowX = 0;
- if (preserveGeometry_ )
- unwrapBelowX = origin.x();
-
+ QDoubleVector2D leftBoundWrapped = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(geoLeftBound_));
+ if (preserveGeometry_)
+ unwrapBelowX = leftBoundWrapped.x();
+
+ QList<QDoubleVector2D> wrappedPath;
+ wrappedPath.reserve(path.size());
+ QDoubleVector2D wrappedLeftBound(qInf(), qInf());
+ // 1)
for (int i = 0; i < path.size(); ++i) {
const QGeoCoordinate &coord = path.at(i);
-
if (!coord.isValid())
continue;
- QDoubleVector2D point = map.geoProjection().coordinateToItemPosition(coord, false);
+ QDoubleVector2D wrappedProjection = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(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(point.x()) || !qIsFinite(point.y()))
+ 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_ && point.x() < unwrapBelowX
- && !qFuzzyCompare(point.x(), unwrapBelowX)
- && !qFuzzyCompare(geoLeftBound_.longitude(), coord.longitude()))
- point.setX(unwrapBelowX + geoDistanceToScreenWidth(map, geoLeftBound_, coord));
+ 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);
+ }
+
+ // 2)
+ QList<QList<QDoubleVector2D> > clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion();
+ if (visibleRegion.size()) {
+ c2t::clip2tri clipper;
+ clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), true);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
+ Paths res = clipper.execute(c2t::clip2tri::Intersection, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+ clippedPaths = QClipperUtils::pathsToQList(res);
+
+ // 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_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(lb));
+ } else {
+ clippedPaths.append(wrappedPath);
+ }
- if (!foundValid) {
- foundValid = true;
- point = point - origin;
- srcPath_.moveTo(point.toPointF());
- lastAddedPoint = point;
- } else {
- point -= origin;
- if ((point - lastAddedPoint).manhattanLength() > 3 ||
- i == path.size() - 1) {
- srcPath_.lineTo(point.toPointF());
+ // 3)
+ QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(leftBoundWrapped);
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (int i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i));
+ point = point - origin; // (0,0) if point == geoLeftBound_
+
+ 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();
}
- srcPath_.closeSubpath();
-
if (!assumeSimple_)
srcPath_ = srcPath_.simplified();
@@ -228,17 +269,17 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map)
QPainterPath vpPath;
vpPath.addRect(viewport);
- QPainterPath ppi;
- if (clipToViewport_)
- ppi = srcPath_.intersected(vpPath); // get the clipped version of the path
- else ppi = srcPath_;
-
+ // The geometry has already been clipped against the visible region projection in wrapped mercator space.
+ QPainterPath ppi = srcPath_;
clear();
// a polygon requires at least 3 points;
if (ppi.elementCount() < 3)
return;
+ // TODO: move this to clip2tri, and remove the code below.
+ // For clip2tri use the intersection between the the viewport AND the map as clipping region.
+
// Intersection between the viewport and a concave polygon can create multiple polygons
// joined by a line at the viewport border, and poly2tri does not triangulate this very well
// so use the full src path if the resulting polygon is concave.
@@ -273,7 +314,7 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map)
screenOutline_ = ppi;
-#if 1
+#if 0 // TODO: This code appears to crash seldomly in presence of tilt. Requires further investigation
std::vector<std::vector<c2t::Point>> clipperPoints;
clipperPoints.push_back(std::vector<c2t::Point>());
std::vector<c2t::Point> &curPts = clipperPoints.front();
@@ -545,22 +586,39 @@ void QDeclarativePolygonMapItem::updatePolish()
geometry_.updateSourcePoints(*map(), path_);
geometry_.updateScreenPoints(*map());
- QList<QGeoCoordinate> closedPath = path_;
- closedPath << closedPath.first();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
borderGeometry_.clear();
- borderGeometry_.updateSourcePoints(*map(), closedPath, geoLeftBound_);
- if (border_.color() != Qt::transparent && border_.width() > 0)
- borderGeometry_.updateScreenPoints(*map(), border_.width());
+ if (border_.color() != Qt::transparent && border_.width() > 0) {
+ QList<QGeoCoordinate> closedPath = path_;
+ closedPath << closedPath.first();
- QList<QGeoMapItemGeometry *> geoms;
- geoms << &geometry_ << &borderGeometry_;
- QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ borderGeometry_.setPreserveGeometry(true, geoLeftBound_);
+
+ 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 = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
+ }
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
setWidth(combined.width());
setHeight(combined.height());
- setPositionOnMap(geoLeftBound_, -1 * geometry_.sourceBoundingBox().topLeft());
+ setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft());
}
/*!
@@ -571,21 +629,10 @@ void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChang
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
geometry_.setPreserveGeometry(true, geometry_.geoLeftBound());
borderGeometry_.setPreserveGeometry(true, borderGeometry_.geoLeftBound());
- geometry_.markScreenDirty();
- borderGeometry_.markScreenDirty();
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
polishAndUpdate();
}
diff --git a/src/imports/location/qdeclarativepolylinemapitem.cpp b/src/imports/location/qdeclarativepolylinemapitem.cpp
index 486b2c6f..2dea1464 100644
--- a/src/imports/location/qdeclarativepolylinemapitem.cpp
+++ b/src/imports/location/qdeclarativepolylinemapitem.cpp
@@ -53,6 +53,8 @@
#include <QtGui/private/qtriangulatingstroker_p.h>
#include <QtGui/private/qtriangulator_p.h>
+#include <QtPositioning/private/qclipperutils_p.h>
+
QT_BEGIN_NAMESPACE
/*!
@@ -176,92 +178,126 @@ QGeoMapPolylineGeometry::QGeoMapPolylineGeometry()
{
}
-/*!
- \internal
-*/
-void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
- const QList<QGeoCoordinate> &path,
- const QGeoCoordinate geoLeftBound)
+QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap &map,
+ const QList<QGeoCoordinate> &path,
+ QDoubleVector2D &leftBoundWrapped)
{
- bool foundValid = false;
- double minX = -1.0;
- double minY = -1.0;
- double maxX = -1.0;
- double maxY = -1.0;
-
- 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());
-
- QDoubleVector2D lastAddedPoint;
- const double mapWidthHalf = map.viewportWidth()/2.0;
- QDoubleVector2D origin = map.geoProjection().coordinateToItemPosition(geoLeftBound_, false);
+ /*
+ * 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
+ */
srcOrigin_ = geoLeftBound_;
double unwrapBelowX = 0;
+ leftBoundWrapped = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(geoLeftBound_));
if (preserveGeometry_)
- unwrapBelowX = origin.x();
+ unwrapBelowX = leftBoundWrapped.x();
+ QList<QDoubleVector2D> wrappedPath;
+ wrappedPath.reserve(path.size());
+ QDoubleVector2D wrappedLeftBound(qInf(), qInf());
+ // 1)
for (int i = 0; i < path.size(); ++i) {
const QGeoCoordinate &coord = path.at(i);
-
if (!coord.isValid())
continue;
- QDoubleVector2D point = map.geoProjection().coordinateToItemPosition(coord, false);
+ QDoubleVector2D wrappedProjection = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(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(point.x()) || !qIsFinite(point.y()))
- return;
-
- bool isPointLessThanUnwrapBelowX = (point.x() < unwrapBelowX);
- bool isCoordNotLeftBound = !qFuzzyCompare(geoLeftBound_.longitude(), coord.longitude());
- bool isPointNotUnwrapBelowX = !qFuzzyCompare(point.x(), unwrapBelowX);
- bool isPointNotMapWidthHalf = !qFuzzyCompare(mapWidthHalf, point.x());
+ 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
- && isCoordNotLeftBound
- && isPointNotUnwrapBelowX
- && isPointNotMapWidthHalf) {
- double distance = geoDistanceToScreenWidth(map, geoLeftBound_, coord);
- point.setX(unwrapBelowX + distance);
+ 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);
+ }
- if (!foundValid) {
- foundValid = true;
+ // 2)
+ QList<QList<QDoubleVector2D> > clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion();
+ if (visibleRegion.size()) {
+ c2t::clip2tri clipper;
+ clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), false);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
+ Paths res = clipper.execute(c2t::clip2tri::Intersection);
+ clippedPaths = QClipperUtils::pathsToQList(res);
+
+ // 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> >();
- point = point - origin; // (0,0) if point == geoLeftBound_
+ // 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);
+ }
+
+ return clippedPaths;
+}
- minX = point.x();
- maxX = minX;
- minY = point.y();
- maxY = minY;
+void QGeoMapPolylineGeometry::pathToScreen(const QGeoMap &map,
+ const QList<QList<QDoubleVector2D> > &clippedPaths,
+ const QDoubleVector2D &leftBoundWrapped)
+{
+ // 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_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(leftBoundWrapped));
+ QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(leftBoundWrapped);
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (int i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i));
- srcPoints_ << point.x() << point.y();
- srcPointTypes_ << QPainterPath::MoveToElement;
- lastAddedPoint = point;
- } else {
- point -= origin;
+ 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 ((point - lastAddedPoint).manhattanLength() > 3 ||
- i == path.size() - 1) {
+ if (i == 0) {
srcPoints_ << point.x() << point.y();
- srcPointTypes_ << QPainterPath::LineToElement;
+ 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;
+ }
}
}
}
@@ -269,7 +305,41 @@ void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
}
+/*!
+ \internal
+*/
+void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
+ const QList<QGeoCoordinate> &path,
+ const QGeoCoordinate geoLeftBound)
+{
+ 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);
+}
+
////////////////////////////////////////////////////////////////////////////
+#if 0 // Old polyline to viewport clipping code. Retaining it for now.
/* Polyline clip */
enum ClipPointType {
@@ -382,7 +452,7 @@ static void clipPathToRect(const QVector<qreal> &points,
lastY = points[i * 2 + 1];
}
}
-
+#endif
/*!
\internal
*/
@@ -394,7 +464,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
QPointF origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false).toPointF();
- if (!qIsFinite(origin.x()) || !qIsFinite(origin.y())) {
+ if (!qIsFinite(origin.x()) || !qIsFinite(origin.y()) || srcPointTypes_.size() < 2) { // the line might have been clipped away.
clear();
return;
}
@@ -405,19 +475,13 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth);
viewport.translate(-1 * origin);
- // Perform clipping to the viewport limits
- QVector<qreal> points;
- QVector<QPainterPath::ElementType> types;
-
- if (clipToViewport_) {
- clipPathToRect(srcPoints_, srcPointTypes_, viewport, points, types);
- } else {
- points = srcPoints_;
- types = srcPointTypes_;
- }
+ // The geometry has already been clipped against the visible region projection in wrapped mercator space.
+ QVector<qreal> points = srcPoints_;
+ QVector<QPainterPath::ElementType> types = srcPointTypes_;
QVectorPath vp(points.data(), types.size(), types.data());
QTriangulatingStroker ts;
+ // viewport is not used in the call below.
ts.process(vp, QPen(QBrush(Qt::black), strokeWidth), viewport, QPainter::Qt4CompatiblePainting);
clear();
@@ -873,17 +937,8 @@ void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChan
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- }
geometry_.setPreserveGeometry(true, geometry_.geoLeftBound());
- geometry_.markScreenDirty();
+ geometry_.markSourceDirty();
polishAndUpdate();
}
@@ -904,7 +959,7 @@ void QDeclarativePolylineMapItem::updatePolish()
setWidth(geometry_.sourceBoundingBox().width());
setHeight(geometry_.sourceBoundingBox().height());
- setPositionOnMap(geoLeftBound_, -1 * geometry_.sourceBoundingBox().topLeft());
+ setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft());
}
/*!
diff --git a/src/imports/location/qdeclarativepolylinemapitem_p.h b/src/imports/location/qdeclarativepolylinemapitem_p.h
index 8c827b6f..c744bf7f 100644
--- a/src/imports/location/qdeclarativepolylinemapitem_p.h
+++ b/src/imports/location/qdeclarativepolylinemapitem_p.h
@@ -95,11 +95,22 @@ public:
void updateScreenPoints(const QGeoMap &map,
qreal strokeWidth);
+protected:
+ QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map,
+ const QList<QGeoCoordinate> &path,
+ QDoubleVector2D &leftBoundWrapped);
+
+ void pathToScreen(const QGeoMap &map,
+ const QList<QList<QDoubleVector2D> > &clippedPaths,
+ const QDoubleVector2D &leftBoundWrapped);
+
private:
QVector<qreal> srcPoints_;
QVector<QPainterPath::ElementType> srcPointTypes_;
-
+ friend class QDeclarativeCircleMapItem;
+ friend class QDeclarativePolygonMapItem;
+ friend class QDeclarativeRectangleMapItem;
};
class QDeclarativePolylineMapItem : public QDeclarativeGeoMapItemBase
diff --git a/src/imports/location/qdeclarativerectanglemapitem.cpp b/src/imports/location/qdeclarativerectanglemapitem.cpp
index a3b8db90..5b6a8914 100644
--- a/src/imports/location/qdeclarativerectanglemapitem.cpp
+++ b/src/imports/location/qdeclarativerectanglemapitem.cpp
@@ -112,65 +112,6 @@ QT_BEGIN_NAMESPACE
\image api-maprectangle.png
*/
-struct Vertex
-{
- QVector2D position;
-};
-
-QGeoMapRectangleGeometry::QGeoMapRectangleGeometry()
-{
-}
-
-/*!
- \internal
-*/
-void QGeoMapRectangleGeometry::updatePoints(const QGeoMap &map,
- const QGeoCoordinate &topLeft,
- const QGeoCoordinate &bottomRight)
-{
- if (!screenDirty_ && !sourceDirty_)
- return;
-
- QDoubleVector2D tl = map.geoProjection().coordinateToItemPosition(topLeft, false);
- QDoubleVector2D br = map.geoProjection().coordinateToItemPosition(bottomRight, false);
-
- // 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(tl.x()) || !qIsFinite(tl.y()))
- return;
- if (!qIsFinite(br.x()) || !qIsFinite(br.y()))
- return;
-
- if ( preserveGeometry_ ) {
- double unwrapBelowX = map.geoProjection().coordinateToItemPosition(geoLeftBound_, false).x();
- if (br.x() < unwrapBelowX)
- br.setX(tl.x() + screenBounds_.width());
- }
-
- QRectF re(tl.toPointF(), br.toPointF());
- re.translate(-1 * tl.toPointF());
-
- clear();
- screenVertices_.reserve(6);
-
- screenVertices_ << re.topLeft();
- screenVertices_ << re.topRight();
- screenVertices_ << re.bottomLeft();
-
- screenVertices_ << re.topRight();
- screenVertices_ << re.bottomLeft();
- screenVertices_ << re.bottomRight();
-
- firstPointOffset_ = QPointF(0,0);
- srcOrigin_ = topLeft;
- screenBounds_ = re;
-
- screenOutline_ = QPainterPath();
- screenOutline_.addRect(re);
-
- geoLeftBound_ = topLeft;
-}
-
QDeclarativeRectangleMapItem::QDeclarativeRectangleMapItem(QQuickItem *parent)
: QDeclarativeGeoMapItemBase(parent), color_(Qt::transparent), dirtyMaterial_(true),
updatingGeometry_(false)
@@ -334,33 +275,49 @@ void QDeclarativeRectangleMapItem::updatePolish()
QScopedValueRollback<bool> rollback(updatingGeometry_);
updatingGeometry_ = true;
- geometry_.updatePoints(*map(), topLeft_, bottomRight_);
+ QList<QGeoCoordinate> path;
+ path << topLeft_;
+ path << QGeoCoordinate(topLeft_.latitude(), bottomRight_.longitude());
+ path << bottomRight_;
+ path << QGeoCoordinate(bottomRight_.latitude(), topLeft_.longitude());
+
+ geometry_.setPreserveGeometry(true, topLeft_);
+ geometry_.updateSourcePoints(*map(), path);
+ geometry_.updateScreenPoints(*map());
- QList<QGeoCoordinate> pathClosed;
- pathClosed << topLeft_;
- pathClosed << QGeoCoordinate(topLeft_.latitude(), bottomRight_.longitude());
- pathClosed << bottomRight_;
- pathClosed << QGeoCoordinate(bottomRight_.latitude(), topLeft_.longitude());
- pathClosed << pathClosed.first();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
+ borderGeometry_.clear();
if (border_.color() != Qt::transparent && border_.width() > 0) {
- borderGeometry_.updateSourcePoints(*map(), pathClosed, topLeft_);
- borderGeometry_.updateScreenPoints(*map(), border_.width());
+ QList<QGeoCoordinate> closedPath = path;
+ closedPath << closedPath.first();
+
+ borderGeometry_.setPreserveGeometry(true, topLeft_);
- QList<QGeoMapItemGeometry *> geoms;
- geoms << &geometry_ << &borderGeometry_;
- QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ const QGeoCoordinate &geometryOrigin = geometry_.origin();
- setWidth(combined.width());
- setHeight(combined.height());
- } else {
- borderGeometry_.clear();
+ borderGeometry_.srcPoints_.clear();
+ borderGeometry_.srcPointTypes_.clear();
- setWidth(geometry_.screenBoundingBox().width());
- setHeight(geometry_.screenBoundingBox().height());
+ QDoubleVector2D borderLeftBoundWrapped;
+ QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped);
+ if (clippedPaths.size()) {
+ borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
}
- setPositionOnMap(pathClosed.at(0), geometry_.firstPointOffset());
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ setWidth(combined.width());
+ setHeight(combined.height());
+
+ setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset());
}
/*!
@@ -371,21 +328,10 @@ void QDeclarativeRectangleMapItem::afterViewportChanged(const QGeoMapViewportCha
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
geometry_.setPreserveGeometry(true, topLeft_);
borderGeometry_.setPreserveGeometry(true, topLeft_);
- geometry_.markScreenDirty();
- borderGeometry_.markScreenDirty();
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
polishAndUpdate();
}
diff --git a/src/imports/location/qdeclarativerectanglemapitem_p.h b/src/imports/location/qdeclarativerectanglemapitem_p.h
index fb9936b0..3c55b7ba 100644
--- a/src/imports/location/qdeclarativerectanglemapitem_p.h
+++ b/src/imports/location/qdeclarativerectanglemapitem_p.h
@@ -51,23 +51,12 @@
#include "qdeclarativegeomapitembase_p.h"
#include "qgeomapitemgeometry_p.h"
#include "qdeclarativepolylinemapitem_p.h"
+#include "qdeclarativepolygonmapitem_p.h"
#include <QSGGeometryNode>
#include <QSGFlatColorMaterial>
QT_BEGIN_NAMESPACE
-class QGeoMapRectangleGeometry : public QGeoMapItemGeometry
-{
-public:
- QGeoMapRectangleGeometry();
-
- void updatePoints(const QGeoMap &map,
- const QGeoCoordinate &topLeft,
- const QGeoCoordinate &bottomRight);
-};
-
-class MapRectangleNode;
-
class QDeclarativeRectangleMapItem: public QDeclarativeGeoMapItemBase
{
Q_OBJECT
@@ -117,7 +106,7 @@ private:
QDeclarativeMapLineProperties border_;
QColor color_;
bool dirtyMaterial_;
- QGeoMapRectangleGeometry geometry_;
+ QGeoMapPolygonGeometry geometry_;
QGeoMapPolylineGeometry borderGeometry_;
bool updatingGeometry_;
};
diff --git a/src/imports/location/qgeomapitemgeometry.cpp b/src/imports/location/qgeomapitemgeometry.cpp
index ab90d0dd..1b7e7d17 100644
--- a/src/imports/location/qgeomapitemgeometry.cpp
+++ b/src/imports/location/qgeomapitemgeometry.cpp
@@ -100,7 +100,15 @@ QRectF QGeoMapItemGeometry::translateToCommonOrigin(const QList<QGeoMapItemGeome
// first get max offset
QPointF maxOffset = geoms.at(0)->firstPointOffset();
foreach (QGeoMapItemGeometry *g, geoms) {
- Q_ASSERT(g->origin() == origin);
+#ifndef QT_NO_DEBUG
+ //Q_ASSERT(g->origin() == origin); // this might fail on clipper clipping inaccuracies, so better to remove it in production
+ if (!qFuzzyCompare(origin.latitude(), g->origin().latitude())) {
+ qWarning("translateToCommonOrigin: Origins differ!");
+ }
+ if (!qFuzzyCompare(origin.longitude(), g->origin().longitude())) {
+ qWarning("translateToCommonOrigin: Origins differ!");
+ }
+#endif
QPointF o = g->firstPointOffset();
maxOffset.setX(qMax(o.x(), maxOffset.x()));
maxOffset.setY(qMax(o.y(), maxOffset.y()));