diff options
Diffstat (limited to 'src/imports')
-rw-r--r-- | src/imports/location/location.pro | 3 | ||||
-rw-r--r-- | src/imports/location/qdeclarativecirclemapitem.cpp | 340 | ||||
-rw-r--r-- | src/imports/location/qdeclarativecirclemapitem_p.h | 2 | ||||
-rw-r--r-- | src/imports/location/qdeclarativegeomapitembase.cpp | 7 | ||||
-rw-r--r-- | src/imports/location/qdeclarativepolygonmapitem.cpp | 171 | ||||
-rw-r--r-- | src/imports/location/qdeclarativepolylinemapitem.cpp | 215 | ||||
-rw-r--r-- | src/imports/location/qdeclarativepolylinemapitem_p.h | 13 | ||||
-rw-r--r-- | src/imports/location/qdeclarativerectanglemapitem.cpp | 128 | ||||
-rw-r--r-- | src/imports/location/qdeclarativerectanglemapitem_p.h | 15 | ||||
-rw-r--r-- | src/imports/location/qgeomapitemgeometry.cpp | 10 |
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 ¢er, qreal distance) @@ -267,8 +333,7 @@ static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance) static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate ¢er, 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 ¢er, 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())); |