diff options
23 files changed, 1118 insertions, 509 deletions
diff --git a/src/3rdparty/clip2tri/clip2tri.cpp b/src/3rdparty/clip2tri/clip2tri.cpp index 16b1b86b..2f502667 100644 --- a/src/3rdparty/clip2tri/clip2tri.cpp +++ b/src/3rdparty/clip2tri/clip2tri.cpp @@ -27,10 +27,12 @@ */ #include "clip2tri.h" -#include "../poly2tri/poly2tri.h" +#include <poly2tri.h> #include <cstdio> +static const double clipperScaleFactor = 1073741822.0; +static const double clipperScaleFactorInv = 1.0 / 1073741822.0; using namespace p2t; @@ -41,9 +43,6 @@ namespace c2t static const F32 CLIPPER_SCALE_FACT = 1000.0f; static const F32 CLIPPER_SCALE_FACT_INVERSE = 0.001f; -static const F64 CLIPPER_SCALE_FACT_D = double(1<<31); -static const F64 CLIPPER_SCALE_FACT_INVERSE_D = 1.0 / double(1<<31); - ///////////////////////////////// Point::Point() @@ -61,7 +60,7 @@ Point::Point(const Point& pt) ///////////////////////////////// -clip2tri::clip2tri() +clip2tri::clip2tri() : openSubject(false) { // Do nothing! } @@ -85,79 +84,95 @@ void clip2tri::triangulate(const vector<vector<Point> > &inputPolygons, vector<P triangulateComplex(outputTriangles, bounds, solution); } -IntPoint clip2tri::intPoint(double x, double y) -{ - return IntPoint(S64(x * CLIPPER_SCALE_FACT_D), S64(y * CLIPPER_SCALE_FACT_D)); -} - -PointD clip2tri::pointD(IntPoint p) -{ - return PointD(F64(p.X) * CLIPPER_SCALE_FACT_INVERSE_D, F64(p.Y) * CLIPPER_SCALE_FACT_INVERSE_D); -} - -void clip2tri::addClipPolygon(const std::vector<IntPoint> &path) +void clip2tri::addClipPolygon(const Path &path) { try // prevent any exception to spill into Qt { - if (path.front() != path.back()) - return; // Clip polygons must be closed. clipper.AddPath(path, ptClip, true); } - catch(...) + catch(ClipperLib::clipperException &e) { - printf("addClipPolygon: clipper.AddPath, something went wrong\n"); + printf("addClipPolygon: %s\n", e.what()); } } -void clip2tri::addSubjectPath(const std::vector<IntPoint> &path, bool closed) +void clip2tri::addSubjectPath(const Path &path, bool closed) { try // prevent any exception to spill into Qt { - if (path.front() != path.back() && closed) - return; // Clip polygons must be closed. clipper.AddPath(path, ptSubject, closed); } - catch(...) + catch(ClipperLib::clipperException &e) { - printf("addSubjectPath: clipper.AddPath, something went wrong\n"); + printf("addSubjectPath: %s\n", e.what()); + return; } + if (!closed) + openSubject = true; } void clip2tri::clearClipper() { // clear doesn't throw clipper.Clear(); + openSubject = false; } -Paths clip2tri::executeUnion(PolyFillType subjFillType, PolyFillType clipFillType) +static ClipperLib::ClipType operation(const clip2tri::Operation &op) { - Paths solution; - try // prevent any exception to spill into Qt - { - clipper.Execute(ctUnion, solution, subjFillType, subjFillType); + switch (op) { + case clip2tri::Intersection: + return ClipperLib::ctIntersection; + case clip2tri::Union: + return ClipperLib::ctUnion; + case clip2tri::Difference: + return ClipperLib::ctDifference; + case clip2tri::Xor: + return ClipperLib::ctXor; } - catch(...) - { - printf("executeUnion: clipper.Execute, something went wrong\n"); + return ctIntersection; +} + +static std::string operationName(const clip2tri::Operation &op) +{ + switch (op) { + case clip2tri::Intersection: + return std::string("Intersection"); + case clip2tri::Union: + return std::string("Union"); + case clip2tri::Difference: + return std::string("Difference"); + case clip2tri::Xor: + return std::string("Xor"); } - return solution; + return std::string("Intersection"); } -Paths clip2tri::executeIntersection(PolyFillType subjFillType, PolyFillType clipFillType) +Paths clip2tri::execute(const clip2tri::Operation op, const PolyFillType subjFillType, const PolyFillType clipFillType) { Paths solution; - try // prevent any exception to spill into Qt + try // prevent any exception from spilling into Qt { - clipper.Execute(ctIntersection, solution, subjFillType, subjFillType); + if (!openSubject) { + clipper.Execute(operation(op), solution, subjFillType, clipFillType); + } else { + PolyTree res; + clipper.Execute(operation(op), res, subjFillType, clipFillType); + PolyNode *n = res.GetFirst(); + if (n) { + solution.push_back(n->Contour); + while ((n = n->GetNext())) + solution.push_back(n->Contour); + } + } } - catch(...) + catch(ClipperLib::clipperException &e) { - printf("executeIntersection: clipper.Execute, something went wrong\n"); + printf("executing %s: %s\n", operationName(op).c_str(), e.what()); } return solution; } - Path clip2tri::upscaleClipperPoints(const vector<Point> &inputPolygon) { Path outputPolygon; @@ -222,9 +237,9 @@ bool clip2tri::mergePolysToPolyTree(const vector<vector<Point> > &inputPolygons, { clipper.AddPaths(input, ptSubject, true); } - catch(...) + catch(ClipperLib::clipperException &e) { - printf("clipper.AddPaths, something went wrong\n"); + printf("mergePolysToPolyTree: %s\n", e.what()); } return clipper.Execute(ctUnion, solution, pftNonZero, pftNonZero); diff --git a/src/3rdparty/clip2tri/clip2tri.h b/src/3rdparty/clip2tri/clip2tri.h index a94bb6a1..37b563bb 100644 --- a/src/3rdparty/clip2tri/clip2tri.h +++ b/src/3rdparty/clip2tri/clip2tri.h @@ -30,7 +30,7 @@ #define CLIP2TRI_H_ #include <vector> -#include "../clipper/clipper.h" +#include <clipper.h> using namespace std; using namespace ClipperLib; @@ -57,18 +57,6 @@ struct Point Point(T in_x, U in_y) { x = static_cast<F32>(in_x); y = static_cast<F32>(in_y); } }; -struct PointD -{ - F64 x; - F64 y; - - PointD(); - PointD(const PointD &pt); - - template<class T, class U> - PointD(T in_x, U in_y) { x = static_cast<F64>(in_x); y = static_cast<F64>(in_y); } -}; - class clip2tri { private: @@ -85,29 +73,26 @@ private: const PolyTree &polyTree, bool ignoreFills = true, bool ignoreHoles = false); public: + enum Operation { Union, Intersection, Difference, Xor }; clip2tri(); virtual ~clip2tri(); void triangulate(const vector<vector<Point> > &inputPolygons, vector<Point> &outputTriangles, const vector<Point> &boundingPolygon); - inline static IntPoint intPoint(double x, double y); - inline static PointD pointD(IntPoint p); - - // Clip polygons MUST be closed. Meaning path[0] == path[path.size()-1] - void addClipPolygon(const std::vector<IntPoint> &path); + // Clip polygons are intended as closed, even if the first and last vertex aren't the same. + void addClipPolygon(const Path &path); // Closed means the path has to be effectively closed. Meaning path[0] == path[path.size()-1] - void addSubjectPath(const std::vector<IntPoint> &path, bool closed); + void addSubjectPath(const Path &path, bool closed); void clearClipper(); - Paths executeUnion(PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); - - Paths executeIntersection(PolyFillType subjFillType = pftEvenOdd, - PolyFillType clipFillType = pftEvenOdd); + Paths execute(const Operation op, + const PolyFillType subjFillType = pftNonZero, + const PolyFillType clipFillType = pftNonZero); Clipper clipper; + bool openSubject; }; } /* namespace c2t */ diff --git a/src/3rdparty/clip2tri/clip2tri.pro b/src/3rdparty/clip2tri/clip2tri.pro index 50901c06..4ae7a799 100644 --- a/src/3rdparty/clip2tri/clip2tri.pro +++ b/src/3rdparty/clip2tri/clip2tri.pro @@ -2,6 +2,9 @@ TARGET = clip2tri CONFIG += staticlib exceptions +INCLUDEPATH += ../poly2tri +INCLUDEPATH += ../clipper + load(qt_helper_lib) # workaround for QTBUG-31586 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())); diff --git a/src/location/location.pro b/src/location/location.pro index 225df556..3669188c 100644 --- a/src/location/location.pro +++ b/src/location/location.pro @@ -1,6 +1,10 @@ TARGET = QtLocation QT = core-private positioning-private +#INCLUDEPATH += ../3rdparty/poly2tri +INCLUDEPATH += ../3rdparty/clipper +INCLUDEPATH += ../3rdparty/clip2tri + android { # adding qtconcurrent dependency here for the osm plugin QT += concurrent @@ -28,3 +32,5 @@ include(places/places.pri) HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS load(qt_module) + +LIBS_PRIVATE += -L$$MODULE_BASE_OUTDIR/lib -lclip2tri$$qtPlatformTargetSuffix() diff --git a/src/location/maps/qgeoprojection.cpp b/src/location/maps/qgeoprojection.cpp index a54eee54..77a91a15 100644 --- a/src/location/maps/qgeoprojection.cpp +++ b/src/location/maps/qgeoprojection.cpp @@ -37,12 +37,23 @@ #include "qgeoprojection_p.h" #include <QtPositioning/private/qwebmercator_p.h> #include <QtPositioning/private/qlocationutils_p.h> +#include <QtPositioning/private/qclipperutils_p.h> #include <QSize> #include <cmath> -QT_BEGIN_NAMESPACE +namespace { + static const double defaultTileSize = 256.0; + static const QDoubleVector3D xyNormal(0.0, 0.0, 1.0); + static const QDoubleVector3D xyPoint(0.0, 0.0, 0.0); + static const QGeoProjectionWebMercator::Plane xyPlane(QDoubleVector3D(0,0,0), QDoubleVector3D(0,0,1)); + static const QList<QDoubleVector2D> mercatorGeometry = { + QDoubleVector2D(-1.0,0.0), + QDoubleVector2D( 2.0,0.0), + QDoubleVector2D( 2.0,1.0), + QDoubleVector2D(-1.0,1.0) }; +} -static const double defaultTileSize = 256.0; +QT_BEGIN_NAMESPACE QGeoProjection::QGeoProjection() { @@ -58,7 +69,6 @@ QGeoProjection::~QGeoProjection() * QGeoProjectionWebMercator implementation */ - QGeoProjectionWebMercator::QGeoProjectionWebMercator() : QGeoProjection(), m_mapEdgeSize(256), // at zl 0 @@ -74,10 +84,8 @@ QGeoProjectionWebMercator::QGeoProjectionWebMercator() m_nearPlane(0.0), m_farPlane(0.0), m_halfWidth(0.0), - m_halfHeight(0.0), - m_plane(QDoubleVector3D(0,0,0), QDoubleVector3D(0,0,1)) + m_halfHeight(0.0) { - } QGeoProjectionWebMercator::~QGeoProjectionWebMercator() @@ -190,16 +198,8 @@ QDoubleVector2D QGeoProjectionWebMercator::itemPositionToWrappedMapProjection(co pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight); pos *= 2.0; pos -= QDoubleVector2D(1.0,1.0); - pos *= QDoubleVector2D(m_halfWidth, m_halfHeight); - - QDoubleVector3D p = m_centerNearPlane; - p -= m_up * pos.y(); - p -= m_side * pos.x(); - QDoubleVector3D ray = p - m_eye; - ray.normalize(); - - return (m_plane.lineIntersection(m_eye, ray) / m_sideLength).toVector2D(); + return viewportToWrappedMapProjection(pos); } /* Default implementations */ @@ -235,32 +235,67 @@ QDoubleVector2D QGeoProjectionWebMercator::coordinateToItemPosition(const QGeoCo return pos; } +QDoubleVector2D QGeoProjectionWebMercator::geoToWrappedMapProjection(const QGeoCoordinate &coordinate) const +{ + return wrapMapProjection(geoToMapProjection(coordinate)); +} + +QGeoCoordinate QGeoProjectionWebMercator::wrappedMapProjectionToGeo(const QDoubleVector2D &wrappedProjection) const +{ + return mapProjectionToGeo(unwrapMapProjection(wrappedProjection)); +} + bool QGeoProjectionWebMercator::isProjectable(const QDoubleVector2D &wrappedProjection) const { - QDoubleVector3D pos = wrappedProjection * m_sideLength; + if (m_cameraData.tilt() == 0.0) + return true; - // TODO: add an offset to the eye - QDoubleVector3D p = m_eye - pos; + QDoubleVector3D pos = wrappedProjection * m_sideLength; + // use m_centerNearPlane in order to add an offset to m_eye. + QDoubleVector3D p = m_centerNearPlane - pos; double dot = QDoubleVector3D::dotProduct(p , m_viewNormalized); - if (dot < 0.0) // behind the eye + if (dot < 0.0) // behind the near plane return false; return true; } +QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleRegion() const +{ + return m_visibleRegion; +} + +QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const +{ + QDoubleVector2D pos = itemPosition; + pos *= QDoubleVector2D(m_halfWidth, m_halfHeight); + + QDoubleVector3D p = m_centerNearPlane; + p -= m_up * pos.y(); + p -= m_side * pos.x(); + + QDoubleVector3D ray = p - m_eye; + ray.normalize(); + + return (xyPlane.lineIntersection(m_eye, ray) / m_sideLength).toVector2D(); +} + void QGeoProjectionWebMercator::setupCamera() { - QDoubleVector2D camCenterMercator = geoToMapProjection(m_cameraData.center()); - m_cameraCenterXMercator = camCenterMercator.x(); - m_cameraCenterYMercator = camCenterMercator.y(); + m_centerMercator = geoToMapProjection(m_cameraData.center()); + m_cameraCenterXMercator = m_centerMercator.x(); + m_cameraCenterYMercator = m_centerMercator.y(); int intZoomLevel = static_cast<int>(std::floor(m_cameraData.zoomLevel())); m_sideLength = (1 << intZoomLevel) * defaultTileSize; - m_center = camCenterMercator * m_sideLength; + m_center = m_centerMercator * m_sideLength; double f = 1.0 * qMin(m_viewportWidth, m_viewportHeight); double z = std::pow(2.0, m_cameraData.zoomLevel() - intZoomLevel) * defaultTileSize; - double altitude = f / (2.0 * z) ; + double altitude = f / (2.0 * z); + // Also in mercator space + double z_mercator = std::pow(2.0, m_cameraData.zoomLevel()) * defaultTileSize; + double altitude_mercator = f / (2.0 * z_mercator); //aperture(90 / 2) = 1 m_aperture = tan(QLocationUtils::radians(m_cameraData.fieldOfView()) * 0.5); @@ -268,44 +303,76 @@ void QGeoProjectionWebMercator::setupCamera() m_eye = m_center; m_eye.setZ(altitude * defaultTileSize / m_aperture); + // And in mercator space + m_eyeMercator = m_centerMercator; + m_eyeMercator.setZ(altitude_mercator); + m_view = m_eye - m_center; QDoubleVector3D side = QDoubleVector3D::normal(m_view, QDoubleVector3D(0.0, 1.0, 0.0)); m_up = QDoubleVector3D::normal(side, m_view); + // In mercator space too + m_viewMercator = m_eyeMercator - m_centerMercator; + QDoubleVector3D sideMercator = QDoubleVector3D::normal(m_viewMercator, QDoubleVector3D(0.0, 1.0, 0.0)); + m_upMercator = QDoubleVector3D::normal(sideMercator, m_viewMercator); + if (m_cameraData.bearing() > 0.0) { - // old bearing, tilt and roll code QDoubleMatrix4x4 mBearing; mBearing.rotate(m_cameraData.bearing(), m_view); m_up = mBearing * m_up; + + // In mercator space too + QDoubleMatrix4x4 mBearingMercator; + mBearingMercator.rotate(m_cameraData.bearing(), m_viewMercator); + m_upMercator = mBearingMercator * m_upMercator; } m_side = QDoubleVector3D::normal(m_up, m_view); + m_sideMercator = QDoubleVector3D::normal(m_upMercator, m_viewMercator); - if (m_cameraData.tilt() > 0.0) { + if (m_cameraData.tilt() > 0.0) { // tilt has been already thresholded by QGeoCameraData::setTilt QDoubleMatrix4x4 mTilt; mTilt.rotate(-m_cameraData.tilt(), m_side); m_eye = mTilt * m_view + m_center; + + // In mercator space too + QDoubleMatrix4x4 mTiltMercator; + mTiltMercator.rotate(-m_cameraData.tilt(), m_sideMercator); + m_eyeMercator = mTiltMercator * m_viewMercator + m_centerMercator; } m_view = m_eye - m_center; m_viewNormalized = m_view.normalized(); m_up = QDoubleVector3D::normal(m_view, m_side); - m_nearPlane = 1; - // 10000.0 to make sure that everything fits in the frustum even at high zoom levels - // (that is, with a very large map) - // TODO: extend this to support clip distance - m_farPlane = (altitude + 10000.0) * defaultTileSize; + m_nearPlane = 1.0; + // At ZL 20 the map has 2^20 tiles per side. That is 1048576. + // Placing the camera on one corner of the map, rotated toward the opposite corner, and tilted + // at almost 90 degrees would require a frustum that can span the whole size of this map. + // For this reason, the far plane is set to 2 * 2^20 * defaultTileSize. + // That is, in order to make sure that the whole map would fit in the frustum at this ZL. + // Since we are using a double matrix, and since the largest value in the matrix is going to be + // 2 * m_farPlane (as near plane is 1.0), there should be sufficient precision left. + // + // TODO: extend this to support clip distance. + m_farPlane = (altitude + 2097152.0) * defaultTileSize; + + m_viewMercator = m_eyeMercator - m_centerMercator; + m_upMercator = QDoubleVector3D::normal(m_viewMercator, m_sideMercator); + m_nearPlaneMercator = 1.0 / m_sideLength; double aspectRatio = 1.0 * m_viewportWidth / m_viewportHeight; m_halfWidth = m_aperture; m_halfHeight = m_aperture; + double verticalAperture = m_aperture; if (aspectRatio > 1.0) { m_halfWidth *= aspectRatio; } else if (aspectRatio > 0.0 && aspectRatio < 1.0) { m_halfHeight /= aspectRatio; + verticalAperture /= aspectRatio; } + double verticalHalfFOV = QLocationUtils::degrees(atan(verticalAperture)); QDoubleMatrix4x4 cameraMatrix; cameraMatrix.lookAt(m_eye, m_center, m_up); @@ -314,7 +381,113 @@ void QGeoProjectionWebMercator::setupCamera() projectionMatrix.frustum(-m_halfWidth, m_halfWidth, -m_halfHeight, m_halfHeight, m_nearPlane, m_farPlane); m_transformation = projectionMatrix * cameraMatrix; - m_centerNearPlane = m_eye + m_view.normalized(); + m_centerNearPlane = m_eye + m_viewNormalized; + m_centerNearPlaneMercator = m_eyeMercator + m_viewNormalized * m_nearPlaneMercator; + + // TODO: support tilting angles > 90.0, if desired. And flip the bottom corners with the top corners, if needed + // by clipper. + + // The following formula is used to have a growing epsilon with the zoom level, + // in order not to have too large values at low zl, which would overflow when converted to Clipper::cInt. + const double upperBoundEpsilon = 1.0 / std::pow(10, 1.0 + m_cameraData.zoomLevel() / 5.0); + const double elevationUpperBound = 90.0 - upperBoundEpsilon; + const double maxRayElevation = qMin(elevationUpperBound - m_cameraData.tilt(), verticalHalfFOV); + double maxHalfAperture = 0; + double verticalEstateToSkip = 0; + if (maxRayElevation < verticalHalfFOV) { + maxHalfAperture = tan(QLocationUtils::radians(maxRayElevation)); + verticalEstateToSkip = 1.0 - maxHalfAperture / verticalAperture; + } + + QDoubleVector2D tl = viewportToWrappedMapProjection(QDoubleVector2D(-1, -1 + verticalEstateToSkip )); + QDoubleVector2D tr = viewportToWrappedMapProjection(QDoubleVector2D( 1, -1 + verticalEstateToSkip )); + QDoubleVector2D bl = viewportToWrappedMapProjection(QDoubleVector2D(-1, 1 )); + QDoubleVector2D br = viewportToWrappedMapProjection(QDoubleVector2D( 1, 1 )); + + QList<QDoubleVector2D> mapRect; + mapRect.push_back(QDoubleVector2D(-1.0, 1.0)); + mapRect.push_back(QDoubleVector2D( 2.0, 1.0)); + mapRect.push_back(QDoubleVector2D( 2.0, 0.0)); + mapRect.push_back(QDoubleVector2D(-1.0, 0.0)); + + QList<QDoubleVector2D> viewportRect; + viewportRect.push_back(bl); + viewportRect.push_back(br); + viewportRect.push_back(tr); + viewportRect.push_back(tl); + + c2t::clip2tri clipper; + clipper.clearClipper(); + clipper.addSubjectPath(QClipperUtils::qListToPath(mapRect), true); + clipper.addClipPolygon(QClipperUtils::qListToPath(viewportRect)); + + Paths res = clipper.execute(c2t::clip2tri::Intersection); + m_visibleRegion.clear(); + if (res.size()) + m_visibleRegion = QClipperUtils::pathToQList(res[0]); // Intersection between two convex quadrilaterals should always be a single polygon +} + +/* + * + * Line implementation + * + */ + +QGeoProjectionWebMercator::Line2D::Line2D() +{ + +} + +QGeoProjectionWebMercator::Line2D::Line2D(const QDoubleVector2D &linePoint, const QDoubleVector2D &lineDirection) + : m_point(linePoint), m_direction(lineDirection.normalized()) +{ + +} + +bool QGeoProjectionWebMercator::Line2D::isValid() const +{ + return (m_direction.length() > 0.5); +} + +/* + * + * Plane implementation + * + */ + +QGeoProjectionWebMercator::Plane::Plane() +{ + +} + +QGeoProjectionWebMercator::Plane::Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal) + : m_point(planePoint), m_normal(planeNormal.normalized()) { } + +QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const +{ + QDoubleVector3D w = linePoint - m_point; + // s = -n.dot(w) / n.dot(u). p = p0 + su; u is lineDirection + double s = QDoubleVector3D::dotProduct(-m_normal, w) / QDoubleVector3D::dotProduct(m_normal, lineDirection); + return linePoint + lineDirection * s; +} + +QGeoProjectionWebMercator::Line2D QGeoProjectionWebMercator::Plane::planeXYIntersection() const +{ + // cross product of the two normals for the line direction + QDoubleVector3D lineDirection = QDoubleVector3D::crossProduct(m_normal, xyNormal); + lineDirection.setZ(0.0); + lineDirection.normalize(); + + // cross product of the line direction and the plane normal to find the direction on the plane + // intersecting the xy plane + QDoubleVector3D directionToXY = QDoubleVector3D::crossProduct(m_normal, lineDirection); + QDoubleVector3D p = xyPlane.lineIntersection(m_point, directionToXY); + return Line2D(p.toVector2D(), lineDirection.toVector2D()); +} + +bool QGeoProjectionWebMercator::Plane::isValid() const +{ + return (m_normal.length() > 0.5); } QT_END_NAMESPACE diff --git a/src/location/maps/qgeoprojection_p.h b/src/location/maps/qgeoprojection_p.h index c31f806b..d8508578 100644 --- a/src/location/maps/qgeoprojection_p.h +++ b/src/location/maps/qgeoprojection_p.h @@ -71,6 +71,9 @@ public: virtual double mapWidth() const = 0; virtual double mapHeight() const = 0; + virtual bool isProjectable(const QDoubleVector2D &wrappedProjection) const = 0; + virtual QList<QDoubleVector2D> visibleRegion() const = 0; + // Conversion methods for QGeoCoordinate <-> screen. // This currently assumes that the "MapProjection" space is [0, 1][0, 1] for every type of possibly supported map projection virtual QDoubleVector2D geoToMapProjection(const QGeoCoordinate &coordinate) const = 0; @@ -83,9 +86,10 @@ public: virtual QDoubleVector2D itemPositionToWrappedMapProjection(const QDoubleVector2D &itemPosition) const = 0; // Convenience methods to avoid the chain itemPositionToWrappedProjection(wrapProjection(geoToProjection())) - // These also come with a default implementation that can, however, be overridden. virtual QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const = 0; virtual QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const = 0; + virtual QDoubleVector2D geoToWrappedMapProjection(const QGeoCoordinate &coordinate) const = 0; + virtual QGeoCoordinate wrappedMapProjectionToGeo(const QDoubleVector2D &wrappedProjection) const = 0; }; class Q_LOCATION_PRIVATE_EXPORT QGeoProjectionWebMercator : public QGeoProjection @@ -115,11 +119,42 @@ public: QGeoCoordinate itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport = true) const Q_DECL_OVERRIDE; QDoubleVector2D coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport = true) const Q_DECL_OVERRIDE; + QDoubleVector2D geoToWrappedMapProjection(const QGeoCoordinate &coordinate) const Q_DECL_OVERRIDE; + QGeoCoordinate wrappedMapProjectionToGeo(const QDoubleVector2D &wrappedProjection) const Q_DECL_OVERRIDE; + + bool isProjectable(const QDoubleVector2D &wrappedProjection) const Q_DECL_OVERRIDE; + QList<QDoubleVector2D> visibleRegion() const Q_DECL_OVERRIDE; + + inline QDoubleVector2D viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const; - bool isProjectable(const QDoubleVector2D &wrappedProjection) const; private: void setupCamera(); +public: + struct Line2D + { + Line2D(); + Line2D(const QDoubleVector2D &linePoint, const QDoubleVector2D &lineDirection); + + bool isValid() const; + + QDoubleVector2D m_point; + QDoubleVector2D m_direction; + }; + + struct Plane + { + Plane(); + Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal); + + QDoubleVector3D lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const; + Line2D planeXYIntersection() const; + bool isValid() const; + + QDoubleVector3D m_point; + QDoubleVector3D m_normal; + }; + private: QGeoCameraData m_cameraData; double m_mapEdgeSize; @@ -149,23 +184,19 @@ private: double m_halfWidth; double m_halfHeight; - struct Plane - { - Plane() {} - Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal) - : m_point(planePoint), m_normal(planeNormal) { } - - QDoubleVector3D lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const - { - QDoubleVector3D w = linePoint - m_point; - // s = -n.dot(w) / n.dot(u). p = p0 + su; u is lineDirection - double s = QDoubleVector3D::dotProduct(-m_normal, w) / QDoubleVector3D::dotProduct(m_normal, lineDirection); - return linePoint + lineDirection * s; - } + // For the clipping region + QDoubleVector3D m_centerMercator; + QDoubleVector3D m_eyeMercator; + QDoubleVector3D m_viewMercator; + QDoubleVector3D m_upMercator; + QDoubleVector3D m_sideMercator; + QDoubleVector3D m_centerNearPlaneMercator; + double m_nearPlaneMercator; + Line2D m_nearPlaneMapIntersection; - QDoubleVector3D m_point; - QDoubleVector3D m_normal; - } m_plane; + QList<QDoubleVector2D> m_visibleRegion; + + Q_DISABLE_COPY(QGeoProjectionWebMercator) }; QT_END_NAMESPACE diff --git a/src/positioning/positioning.pro b/src/positioning/positioning.pro index c4c9cc6c..0df56a71 100644 --- a/src/positioning/positioning.pro +++ b/src/positioning/positioning.pro @@ -1,6 +1,10 @@ TARGET = QtPositioning QT = core-private +#INCLUDEPATH += ../3rdparty/poly2tri +INCLUDEPATH += ../3rdparty/clipper +INCLUDEPATH += ../3rdparty/clip2tri + QMAKE_DOCS = $$PWD/doc/qtpositioning.qdocconf OTHER_FILES += doc/src/*.qdoc # show .qdoc files in Qt Creator @@ -53,7 +57,8 @@ PRIVATE_HEADERS += \ qpositioningglobal_p.h \ qlocationdata_simulator_p.h \ qdoublematrix4x4_p.h \ - qgeopath_p.h + qgeopath_p.h \ + qclipperutils_p.h SOURCES += \ qgeoaddress.cpp \ @@ -78,9 +83,12 @@ SOURCES += \ qgeopath.cpp \ qlocationdata_simulator.cpp \ qwebmercator.cpp \ - qdoublematrix4x4.cpp + qdoublematrix4x4.cpp \ + qclipperutils.cpp HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS load(qt_module) + +LIBS_PRIVATE += -L$$MODULE_BASE_OUTDIR/lib -lclip2tri$$qtPlatformTargetSuffix() diff --git a/src/positioning/qclipperutils.cpp b/src/positioning/qclipperutils.cpp new file mode 100644 index 00000000..08e65712 --- /dev/null +++ b/src/positioning/qclipperutils.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipperutils_p.h" + +QT_BEGIN_NAMESPACE + +static const double kClipperScaleFactor = 281474976710656.0; // 48 bits of precision +static const double kClipperScaleFactorInv = 1.0 / kClipperScaleFactor; + +double QClipperUtils::clipperScaleFactor() +{ + return kClipperScaleFactor; +} + +QDoubleVector2D QClipperUtils::toVector2D(const IntPoint &p) +{ + return QDoubleVector2D(double(p.X) * kClipperScaleFactorInv, double(p.Y) * kClipperScaleFactorInv); +} + +IntPoint QClipperUtils::toIntPoint(const QDoubleVector2D &p) +{ + return IntPoint(cInt(p.x() * kClipperScaleFactor), cInt(p.y() * kClipperScaleFactor)); +} + +QList<QDoubleVector2D> QClipperUtils::pathToQList(const Path &path) +{ + QList<QDoubleVector2D> res; + res.reserve(path.size()); + for (const IntPoint &ip: path) + res.append(toVector2D(ip)); + return res; +} + +QList<QList<QDoubleVector2D> > QClipperUtils::pathsToQList(const Paths &paths) +{ + QList<QList<QDoubleVector2D> > res; + res.reserve(paths.size()); + for (const Path &p: paths) { + res.append(pathToQList(p)); + } + return res; +} + +Path QClipperUtils::qListToPath(const QList<QDoubleVector2D> &list) +{ + Path res; + res.reserve(list.size()); + for (const QDoubleVector2D &p: list) + res.push_back(toIntPoint(p)); + return res; +} + +Paths QClipperUtils::qListToPaths(const QList<QList<QDoubleVector2D> > &lists) +{ + Paths res; + res.reserve(lists.size()); + for (const QList<QDoubleVector2D> &l: lists) { + res.push_back(qListToPath(l)); + } + return res; +} + +QT_END_NAMESPACE diff --git a/src/positioning/qclipperutils_p.h b/src/positioning/qclipperutils_p.h new file mode 100644 index 00000000..f05d9838 --- /dev/null +++ b/src/positioning/qclipperutils_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QCLIPPERUTILS_P_H +#define QCLIPPERUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +/* + * This file is intended to be include only in source files of + * QtPositioning/QtLocation. It is in QtPositioning to enable manipulation + * of geo polygons + */ + +#include <QtPositioning/private/qpositioningglobal_p.h> +#include <QtCore/QtGlobal> +#include <QtCore/QList> +#include <cmath> +/* clip2tri triangulator includes */ +#include <clip2tri.h> +#include <QtPositioning/private/qdoublevector2d_p.h> + +QT_BEGIN_NAMESPACE + +class Q_POSITIONING_PRIVATE_EXPORT QClipperUtils +{ +public: + static double clipperScaleFactor(); + + static QDoubleVector2D toVector2D(const IntPoint &p); + static IntPoint toIntPoint(const QDoubleVector2D &p); + + static QList<QDoubleVector2D> pathToQList(const Path &path); + static QList<QList<QDoubleVector2D> > pathsToQList(const Paths &paths); + + static Path qListToPath(const QList<QDoubleVector2D> &list); + static Paths qListToPaths(const QList<QList<QDoubleVector2D> > &lists); +}; + +QT_END_NAMESPACE + +#endif // QCLIPPERUTILS_P_H diff --git a/src/positioning/qgeocircle.cpp b/src/positioning/qgeocircle.cpp index 4b4370bf..6fccb8af 100644 --- a/src/positioning/qgeocircle.cpp +++ b/src/positioning/qgeocircle.cpp @@ -316,11 +316,11 @@ void QGeoCirclePrivate::updateBoundingBox() )); QGeoCoordinate topLeft; - topLeft.setLatitude(m_center.latitude() + lat_delta_in_deg); - topLeft.setLongitude(m_center.longitude() - lon_delta_in_deg); + topLeft.setLatitude(QLocationUtils::clipLat(m_center.latitude() + lat_delta_in_deg)); + topLeft.setLongitude(QLocationUtils::wrapLong(m_center.longitude() - lon_delta_in_deg)); QGeoCoordinate bottomRight; - bottomRight.setLatitude(m_center.latitude() - lat_delta_in_deg); - bottomRight.setLongitude(m_center.longitude() + lon_delta_in_deg); + bottomRight.setLatitude(QLocationUtils::clipLat(m_center.latitude() - lat_delta_in_deg)); + bottomRight.setLongitude(QLocationUtils::wrapLong(m_center.longitude() + lon_delta_in_deg)); m_bbox = QGeoRectangle(topLeft, bottomRight); } diff --git a/src/positioning/qlocationutils_p.h b/src/positioning/qlocationutils_p.h index 704a57f8..c73a2c3b 100644 --- a/src/positioning/qlocationutils_p.h +++ b/src/positioning/qlocationutils_p.h @@ -52,12 +52,16 @@ #include <QtCore/QtGlobal> #include <math.h> +#include <QtPositioning/QGeoCoordinate> static const double M_PID = 3.14159265358979323846264338327950288; // to get more precision than float static const double M_1_180D = 0.0055555555555555555555555555555555555555556; static const double M_1_PID = 1.0 / M_PID; static const double M_PI_180D = M_PID / 180.0; //0.0174532925199432954743716805978692718781530857086181640625; static const double M_180_PID = 180.0 / M_PID; // 57.29577951308232286464772187173366546630859375 +static const double offsetEpsilon = 0.0000000000001; +static const double leftOffset = -180.0 + offsetEpsilon; +static const double rightOffset = 180.0 - offsetEpsilon; QT_BEGIN_NAMESPACE class QTime; @@ -87,25 +91,25 @@ public: }; inline static bool isValidLat(double lat) { - return lat >= -90 && lat <= 90; + return lat >= -90.0 && lat <= 90.0; } inline static bool isValidLong(double lng) { - return lng >= -180 && lng <= 180; + return lng >= -180.0 && lng <= 180.0; } inline static double clipLat(double lat) { - if (lat > 90) - lat = 90; - else if (lat < -90) - lat = -90; + if (lat > 90.0) + lat = 90.0; + else if (lat < -90.0) + lat = -90.0; return lat; } inline static double wrapLong(double lng) { - if (lng > 180) - lng -= 360; - else if (lng < -180) - lng += 360; + if (lng > 180.0) + lng -= 360.0; + else if (lng < -180.0) + lng += 360.0; return lng; } @@ -212,16 +216,39 @@ public: { return radians * M_180_PID; } + inline static double earthMeanRadius() { return 6371007.2; } + inline static double earthMeanDiameter() + { + return earthMeanRadius() * 2.0 * M_PID; + } + inline static double mercatorMaxLatitude() { return 85.05113; } + inline static QGeoCoordinate antipodalPoint(const QGeoCoordinate &p) + { + return QGeoCoordinate(-p.latitude(), wrapLong(p.longitude() + 180.0)); + } + + // Leftmost longitude before wrapping kicks in + inline static double mapLeftLongitude(double centerLongitude) + { + return wrapLong(centerLongitude + leftOffset); + } + + // Rightmost longitude before wrapping kicks in + inline static double mapRightLongitude(double centerLongitude) + { + return wrapLong(centerLongitude - leftOffset); + } + /* Creates a QGeoPositionInfo from a GGA, GLL, RMC, VTG or ZDA sentence. diff --git a/src/positioning/qwebmercator.cpp b/src/positioning/qwebmercator.cpp index d22258a7..da35c7d7 100644 --- a/src/positioning/qwebmercator.cpp +++ b/src/positioning/qwebmercator.cpp @@ -101,6 +101,30 @@ QGeoCoordinate QWebMercator::mercatorToCoord(const QDoubleVector2D &mercator) return QGeoCoordinate(lat, lng, 0.0); } +QGeoCoordinate QWebMercator::mercatorToCoordClamped(const QDoubleVector2D &mercator) +{ + double fx = mercator.x(); + double fy = mercator.y(); + + if (fy < 0.0) + fy = 0.0; + else if (fy > 1.0) + fy = 1.0; + + double lat = (180.0 / M_PI) * (2.0 * std::atan(std::exp(M_PI * (1.0 - 2.0 * fy))) - (M_PI / 2.0)); + + double lng; + if (fx >= 0) { + lng = realmod(fx, 1.0); + } else { + lng = realmod(1.0 - realmod(-1.0 * fx, 1.0), 1.0); + } + + lng = lng * 360.0 - 180.0; + + return QGeoCoordinate(lat, lng, 0.0); +} + QGeoCoordinate QWebMercator::coordinateInterpolation(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress) { QDoubleVector2D s = QWebMercator::coordToMercator(from); diff --git a/src/positioning/qwebmercator_p.h b/src/positioning/qwebmercator_p.h index 8c324710..2b8e9564 100644 --- a/src/positioning/qwebmercator_p.h +++ b/src/positioning/qwebmercator_p.h @@ -65,6 +65,7 @@ class Q_POSITIONING_PRIVATE_EXPORT QWebMercator public: static QDoubleVector2D coordToMercator(const QGeoCoordinate &coord); static QGeoCoordinate mercatorToCoord(const QDoubleVector2D &mercator); + static QGeoCoordinate mercatorToCoordClamped(const QDoubleVector2D &mercator); static QGeoCoordinate coordinateInterpolation(const QGeoCoordinate &from, const QGeoCoordinate &to, qreal progress); private: |