summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/3rdparty/clip2tri/clip2tri.cpp99
-rw-r--r--src/3rdparty/clip2tri/clip2tri.h33
-rw-r--r--src/3rdparty/clip2tri/clip2tri.pro3
-rw-r--r--src/imports/location/location.pro3
-rw-r--r--src/imports/location/qdeclarativecirclemapitem.cpp340
-rw-r--r--src/imports/location/qdeclarativecirclemapitem_p.h2
-rw-r--r--src/imports/location/qdeclarativegeomapitembase.cpp7
-rw-r--r--src/imports/location/qdeclarativepolygonmapitem.cpp171
-rw-r--r--src/imports/location/qdeclarativepolylinemapitem.cpp215
-rw-r--r--src/imports/location/qdeclarativepolylinemapitem_p.h13
-rw-r--r--src/imports/location/qdeclarativerectanglemapitem.cpp128
-rw-r--r--src/imports/location/qdeclarativerectanglemapitem_p.h15
-rw-r--r--src/imports/location/qgeomapitemgeometry.cpp10
-rw-r--r--src/location/location.pro6
-rw-r--r--src/location/maps/qgeoprojection.cpp237
-rw-r--r--src/location/maps/qgeoprojection_p.h67
-rw-r--r--src/positioning/positioning.pro12
-rw-r--r--src/positioning/qclipperutils.cpp100
-rw-r--r--src/positioning/qclipperutils_p.h86
-rw-r--r--src/positioning/qgeocircle.cpp8
-rw-r--r--src/positioning/qlocationutils_p.h47
-rw-r--r--src/positioning/qwebmercator.cpp24
-rw-r--r--src/positioning/qwebmercator_p.h1
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 &center, qreal distance)
@@ -267,8 +333,7 @@ static bool crossEarthPole(const QGeoCoordinate &center, qreal distance)
static void calculatePeripheralPoints(QList<QGeoCoordinate> &path,
const QGeoCoordinate &center,
qreal distance,
- int steps,
- QGeoCoordinate &leftBound )
+ int steps)
{
// Calculate points based on great-circle distance
// Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function
@@ -277,38 +342,27 @@ static void calculatePeripheralPoints(QList<QGeoCoordinate> &path,
// pre-calculations
steps = qMax(steps, 3);
qreal centerLon = center.longitude();
- qreal minLon = centerLon;
- qreal latRad = qgeocoordinate_degToRad(center.latitude());
- qreal lonRad = qgeocoordinate_degToRad(centerLon);
+ qreal latRad = QLocationUtils::radians(center.latitude());
+ qreal lonRad = QLocationUtils::radians(centerLon);
qreal cosLatRad = std::cos(latRad);
qreal sinLatRad = std::sin(latRad);
- qreal ratio = (distance / (qgeocoordinate_EARTH_MEAN_RADIUS * 1000.0));
+ qreal ratio = (distance / (QLocationUtils::earthMeanRadius()));
qreal cosRatio = std::cos(ratio);
qreal sinRatio = std::sin(ratio);
qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio;
qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio;
- int idx = 0;
+
for (int i = 0; i < steps; ++i) {
qreal azimuthRad = 2 * M_PI * i / steps;
qreal resultLatRad = std::asin(sinLatRad_x_cosRatio
+ cosLatRad_x_sinRatio * std::cos(azimuthRad));
qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio,
cosRatio - sinLatRad * std::sin(resultLatRad));
- qreal lat2 = qgeocoordinate_radToDeg(resultLatRad);
- qreal lon2 = QLocationUtils::wrapLong(qgeocoordinate_radToDeg(resultLonRad));
+ qreal lat2 = QLocationUtils::degrees(resultLatRad);
+ qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad));
path << QGeoCoordinate(lat2, lon2, center.altitude());
- // Consider only points in the left half of the circle for the left bound.
- if (azimuthRad > M_PI) {
- if (lon2 > centerLon) // if point and center are on different hemispheres
- lon2 -= 360;
- if (lon2 < minLon) {
- minLon = lon2;
- idx = i;
- }
- }
}
- leftBound = path.at(idx);
}
QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent)
@@ -476,37 +530,71 @@ void QDeclarativeCircleMapItem::updatePolish()
if (geometry_.isSourceDirty()) {
circlePath_.clear();
- calculatePeripheralPoints(circlePath_, center_, radius_, CircleSamples, geoLeftBound_);
+ calculatePeripheralPoints(circlePath_, center_, radius_, CircleSamples);
+ geoLeftBound_ = QGeoCircle(center(), radius()).boundingGeoRectangle().topLeft();
}
+ QList<QGeoCoordinate> originalCirclePath = circlePath_;
+
int pathCount = circlePath_.size();
bool preserve = preserveCircleGeometry(circlePath_, center_, radius_);
+ geometry_.setPreserveGeometry(true, geoLeftBound_); // to set the geoLeftBound_
geometry_.setPreserveGeometry(preserve, geoLeftBound_);
- geometry_.updateSourcePoints(*map(), circlePath_);
- if (crossEarthPole(center_, radius_) && circlePath_.size() == pathCount)
- geometry_.updateScreenPointsInvert(*map()); // invert fill area for really huge circles
- else geometry_.updateScreenPoints(*map());
+
+ bool invertedCircle = false;
+ if (crossEarthPole(center_, radius_) && circlePath_.size() == pathCount) {
+ geometry_.updateScreenPointsInvert(circlePath_, *map()); // invert fill area for really huge circles
+ invertedCircle = true;
+ } else {
+ geometry_.updateSourcePoints(*map(), circlePath_);
+ geometry_.updateScreenPoints(*map());
+ }
+
+ borderGeometry_.clear();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
if (border_.color() != Qt::transparent && border_.width() > 0) {
QList<QGeoCoordinate> closedPath = circlePath_;
closedPath << closedPath.first();
+
+ QGeoCoordinate lb = geoLeftBound_;
+ if (invertedCircle) {
+ closedPath = originalCirclePath;
+ closedPath << closedPath.first();
+ std::reverse(closedPath.begin(), closedPath.end());
+
+ double circumferenceRadius = QLocationUtils::earthMeanDiameter() * 0.5 - radius();
+ QGeoCoordinate circumferenceCenter = QLocationUtils::antipodalPoint(center());
+ lb = QGeoCircle(circumferenceCenter, circumferenceRadius).boundingGeoRectangle().topLeft();
+ }
+
+ borderGeometry_.setPreserveGeometry(true, geoLeftBound_); // to set the geoLeftBound_
borderGeometry_.setPreserveGeometry(preserve, geoLeftBound_);
- borderGeometry_.updateSourcePoints(*map(), closedPath, geoLeftBound_);
- borderGeometry_.updateScreenPoints(*map(), border_.width());
- QList<QGeoMapItemGeometry *> geoms;
- geoms << &geometry_ << &borderGeometry_;
- QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail.
+ const QGeoCoordinate &geometryOrigin = geometry_.origin();
- setWidth(combined.width());
- setHeight(combined.height());
- } else {
- borderGeometry_.clear();
- setWidth(geometry_.screenBoundingBox().width());
- setHeight(geometry_.screenBoundingBox().height());
+ borderGeometry_.srcPoints_.clear();
+ borderGeometry_.srcPointTypes_.clear();
+
+ QDoubleVector2D borderLeftBoundWrapped;
+ QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped);
+ if (clippedPaths.size()) {
+ borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
}
- setPositionOnMap(geoLeftBound_, geometry_.firstPointOffset());
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ setWidth(combined.width());
+ setHeight(combined.height());
+
+ setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset());
}
/*!
@@ -517,25 +605,8 @@ void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChange
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- if (event.centerChanged && crossEarthPole(center_, radius_)) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- geometry_.markScreenDirty();
- borderGeometry_.markScreenDirty();
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
polishAndUpdate();
}
@@ -578,8 +649,23 @@ bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QGeoCoordinate> &p
}
-
-// A workaround for circle path to be drawn correctly using a polygon geometry
+/*
+ * A workaround for circle path to be drawn correctly using a polygon geometry
+ * This method generates a polygon like
+ * _____________
+ * | |
+ * \ /
+ * | |
+ * / \
+ * | |
+ * -------------
+ *
+ * or a polygon like
+ *
+ * ______________
+ * | ____ |
+ * \__/ \__/
+ */
void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QGeoCoordinate> &path,
const QGeoCoordinate &center,
qreal distance)
@@ -589,24 +675,24 @@ void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QGeoCoordinat
qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0));
bool crossNorthPole = distanceToNorthPole < distance;
bool crossSouthPole = distanceToSouthPole < distance;
- if (!crossNorthPole && !crossSouthPole)
- return;
+
QList<int> wrapPathIndex;
- // calculate actual width of map on screen in pixels
- QDoubleVector2D midPoint = map()->geoProjection().coordinateToItemPosition(map()->cameraData().center(), false);
- QDoubleVector2D midPointPlusOne(midPoint.x() + 1.0, midPoint.y());
- QGeoCoordinate coord1 = map()->geoProjection().itemPositionToCoordinate(midPointPlusOne, false);
- qreal geoDistance = coord1.longitude() - map()->cameraData().center().longitude();
- if ( geoDistance < 0 )
- geoDistance += 360;
- qreal mapWidth = 360.0 / geoDistance;
- mapWidth = qMin(static_cast<int>(mapWidth), map()->viewportWidth());
- QDoubleVector2D prev = map()->geoProjection().coordinateToItemPosition(path.at(0), false);
+ QDoubleVector2D prev = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(0)));
+
+ for (int i = 1; i <= path.count(); ++i) {
+ int index = i % path.count();
+ QDoubleVector2D point = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(index)));
+ double diff = qAbs(point.x() - prev.x());
+ if (diff > 0.5) {
+ continue;
+ }
+ }
+
// find the points in path where wrapping occurs
for (int i = 1; i <= path.count(); ++i) {
int index = i % path.count();
- QDoubleVector2D point = map()->geoProjection().coordinateToItemPosition(path.at(index), false);
- if ( (qAbs(point.x() - prev.x())) >= mapWidth/2.0 ) {
+ QDoubleVector2D point = map()->geoProjection().wrapMapProjection(map()->geoProjection().geoToMapProjection(path.at(index)));
+ if ( (qAbs(point.x() - prev.x())) >= 0.5 ) { // TODO: Add a projectionWidth to GeoProjection, perhaps?
wrapPathIndex << index;
if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole))
break;
diff --git a/src/imports/location/qdeclarativecirclemapitem_p.h b/src/imports/location/qdeclarativecirclemapitem_p.h
index c91d1606..0305000d 100644
--- a/src/imports/location/qdeclarativecirclemapitem_p.h
+++ b/src/imports/location/qdeclarativecirclemapitem_p.h
@@ -61,7 +61,7 @@ class QGeoMapCircleGeometry : public QGeoMapPolygonGeometry
public:
QGeoMapCircleGeometry();
- void updateScreenPointsInvert(const QGeoMap &map);
+ void updateScreenPointsInvert(const QList<QGeoCoordinate> &circlePath, const QGeoMap &map);
};
class QDeclarativeCircleMapItem : public QDeclarativeGeoMapItemBase
diff --git a/src/imports/location/qdeclarativegeomapitembase.cpp b/src/imports/location/qdeclarativegeomapitembase.cpp
index 84bf757d..8e25e853 100644
--- a/src/imports/location/qdeclarativegeomapitembase.cpp
+++ b/src/imports/location/qdeclarativegeomapitembase.cpp
@@ -177,7 +177,12 @@ void QDeclarativeGeoMapItemBase::setPositionOnMap(const QGeoCoordinate &coordina
if (!map_ || !quickMap_)
return;
- QPointF topLeft = map_->geoProjection().coordinateToItemPosition(coordinate, false).toPointF() - offset;
+ QDoubleVector2D wrappedProjection = map_->geoProjection().geoToWrappedMapProjection(coordinate);
+ if (! map_->geoProjection().isProjectable(wrappedProjection))
+ return;
+
+ QDoubleVector2D pos = map_->geoProjection().wrappedMapProjectionToItemPosition(wrappedProjection);
+ QPointF topLeft = pos.toPointF() - offset;
setPosition(topLeft);
}
diff --git a/src/imports/location/qdeclarativepolygonmapitem.cpp b/src/imports/location/qdeclarativepolygonmapitem.cpp
index 19d68e5c..bfd57e99 100644
--- a/src/imports/location/qdeclarativepolygonmapitem.cpp
+++ b/src/imports/location/qdeclarativepolygonmapitem.cpp
@@ -48,10 +48,11 @@
#include <QPainterPath>
#include <qnumeric.h>
-#include "qdoublevector2d_p.h"
+#include <QtPositioning/private/qdoublevector2d_p.h>
+#include <QtPositioning/private/qclipperutils_p.h>
/* poly2tri triangulator includes */
-#include "../../3rdparty/clip2tri/clip2tri.h"
+#include <clip2tri.h>
QT_BEGIN_NAMESPACE
@@ -131,11 +132,6 @@ QT_BEGIN_NAMESPACE
\image api-mappolygon.png
*/
-struct Vertex
-{
- QVector2D position;
-};
-
QGeoMapPolygonGeometry::QGeoMapPolygonGeometry()
: assumeSimple_(false)
{
@@ -150,55 +146,100 @@ void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map,
if (!sourceDirty_)
return;
- bool foundValid = false;
-
- // build the actual path
- QDoubleVector2D lastAddedPoint;
srcPath_ = QPainterPath();
+ // build the actual path
+ // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints
srcOrigin_ = geoLeftBound_;
- QDoubleVector2D origin = map.geoProjection().coordinateToItemPosition(geoLeftBound_, false);
double unwrapBelowX = 0;
- if (preserveGeometry_ )
- unwrapBelowX = origin.x();
-
+ QDoubleVector2D leftBoundWrapped = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(geoLeftBound_));
+ if (preserveGeometry_)
+ unwrapBelowX = leftBoundWrapped.x();
+
+ QList<QDoubleVector2D> wrappedPath;
+ wrappedPath.reserve(path.size());
+ QDoubleVector2D wrappedLeftBound(qInf(), qInf());
+ // 1)
for (int i = 0; i < path.size(); ++i) {
const QGeoCoordinate &coord = path.at(i);
-
if (!coord.isValid())
continue;
- QDoubleVector2D point = map.geoProjection().coordinateToItemPosition(coord, false);
+ QDoubleVector2D wrappedProjection = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(coord));
// We can get NaN if the map isn't set up correctly, or the projection
// is faulty -- probably best thing to do is abort
- if (!qIsFinite(point.x()) || !qIsFinite(point.y()))
+ if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y()))
return;
+ const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x());
// unwrap x to preserve geometry if moved to border of map
- if (preserveGeometry_ && point.x() < unwrapBelowX
- && !qFuzzyCompare(point.x(), unwrapBelowX)
- && !qFuzzyCompare(geoLeftBound_.longitude(), coord.longitude()))
- point.setX(unwrapBelowX + geoDistanceToScreenWidth(map, geoLeftBound_, coord));
+ if (preserveGeometry_ && isPointLessThanUnwrapBelowX) {
+ double distance = wrappedProjection.x() - unwrapBelowX;
+ if (distance < 0.0)
+ distance += 1.0;
+ wrappedProjection.setX(unwrapBelowX + distance);
+ }
+ if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) {
+ wrappedLeftBound = wrappedProjection;
+ }
+ wrappedPath.append(wrappedProjection);
+ }
+
+ // 2)
+ QList<QList<QDoubleVector2D> > clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion();
+ if (visibleRegion.size()) {
+ c2t::clip2tri clipper;
+ clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), true);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
+ Paths res = clipper.execute(c2t::clip2tri::Intersection, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
+ clippedPaths = QClipperUtils::pathsToQList(res);
+
+ // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X
+ QDoubleVector2D lb(qInf(), qInf());
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ for (const QDoubleVector2D &p: path) {
+ if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) {
+ // y-minimization needed to find the same point on polygon and border
+ lb = p;
+ }
+ }
+ }
+ if (qIsInf(lb.x())) // e.g., when the polygon is clipped entirely
+ return;
+ // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which
+ // in turn will make the geometry wrap around.
+ lb.setX(qMax(wrappedLeftBound.x(), lb.x()));
+ leftBoundWrapped = lb;
+ srcOrigin_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(lb));
+ } else {
+ clippedPaths.append(wrappedPath);
+ }
- if (!foundValid) {
- foundValid = true;
- point = point - origin;
- srcPath_.moveTo(point.toPointF());
- lastAddedPoint = point;
- } else {
- point -= origin;
- if ((point - lastAddedPoint).manhattanLength() > 3 ||
- i == path.size() - 1) {
- srcPath_.lineTo(point.toPointF());
+ // 3)
+ QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(leftBoundWrapped);
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (int i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i));
+ point = point - origin; // (0,0) if point == geoLeftBound_
+
+ if (i == 0) {
+ srcPath_.moveTo(point.toPointF());
lastAddedPoint = point;
+ } else {
+ if ((point - lastAddedPoint).manhattanLength() > 3 ||
+ i == path.size() - 1) {
+ srcPath_.lineTo(point.toPointF());
+ lastAddedPoint = point;
+ }
}
}
+ srcPath_.closeSubpath();
}
- srcPath_.closeSubpath();
-
if (!assumeSimple_)
srcPath_ = srcPath_.simplified();
@@ -228,17 +269,17 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map)
QPainterPath vpPath;
vpPath.addRect(viewport);
- QPainterPath ppi;
- if (clipToViewport_)
- ppi = srcPath_.intersected(vpPath); // get the clipped version of the path
- else ppi = srcPath_;
-
+ // The geometry has already been clipped against the visible region projection in wrapped mercator space.
+ QPainterPath ppi = srcPath_;
clear();
// a polygon requires at least 3 points;
if (ppi.elementCount() < 3)
return;
+ // TODO: move this to clip2tri, and remove the code below.
+ // For clip2tri use the intersection between the the viewport AND the map as clipping region.
+
// Intersection between the viewport and a concave polygon can create multiple polygons
// joined by a line at the viewport border, and poly2tri does not triangulate this very well
// so use the full src path if the resulting polygon is concave.
@@ -273,7 +314,7 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map)
screenOutline_ = ppi;
-#if 1
+#if 0 // TODO: This code appears to crash seldomly in presence of tilt. Requires further investigation
std::vector<std::vector<c2t::Point>> clipperPoints;
clipperPoints.push_back(std::vector<c2t::Point>());
std::vector<c2t::Point> &curPts = clipperPoints.front();
@@ -545,22 +586,39 @@ void QDeclarativePolygonMapItem::updatePolish()
geometry_.updateSourcePoints(*map(), path_);
geometry_.updateScreenPoints(*map());
- QList<QGeoCoordinate> closedPath = path_;
- closedPath << closedPath.first();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
borderGeometry_.clear();
- borderGeometry_.updateSourcePoints(*map(), closedPath, geoLeftBound_);
- if (border_.color() != Qt::transparent && border_.width() > 0)
- borderGeometry_.updateScreenPoints(*map(), border_.width());
+ if (border_.color() != Qt::transparent && border_.width() > 0) {
+ QList<QGeoCoordinate> closedPath = path_;
+ closedPath << closedPath.first();
- QList<QGeoMapItemGeometry *> geoms;
- geoms << &geometry_ << &borderGeometry_;
- QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ borderGeometry_.setPreserveGeometry(true, geoLeftBound_);
+
+ const QGeoCoordinate &geometryOrigin = geometry_.origin();
+
+ borderGeometry_.srcPoints_.clear();
+ borderGeometry_.srcPointTypes_.clear();
+
+ QDoubleVector2D borderLeftBoundWrapped;
+ QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped);
+ if (clippedPaths.size()) {
+ borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
+ }
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
setWidth(combined.width());
setHeight(combined.height());
- setPositionOnMap(geoLeftBound_, -1 * geometry_.sourceBoundingBox().topLeft());
+ setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft());
}
/*!
@@ -571,21 +629,10 @@ void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChang
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
geometry_.setPreserveGeometry(true, geometry_.geoLeftBound());
borderGeometry_.setPreserveGeometry(true, borderGeometry_.geoLeftBound());
- geometry_.markScreenDirty();
- borderGeometry_.markScreenDirty();
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
polishAndUpdate();
}
diff --git a/src/imports/location/qdeclarativepolylinemapitem.cpp b/src/imports/location/qdeclarativepolylinemapitem.cpp
index 486b2c6f..2dea1464 100644
--- a/src/imports/location/qdeclarativepolylinemapitem.cpp
+++ b/src/imports/location/qdeclarativepolylinemapitem.cpp
@@ -53,6 +53,8 @@
#include <QtGui/private/qtriangulatingstroker_p.h>
#include <QtGui/private/qtriangulator_p.h>
+#include <QtPositioning/private/qclipperutils_p.h>
+
QT_BEGIN_NAMESPACE
/*!
@@ -176,92 +178,126 @@ QGeoMapPolylineGeometry::QGeoMapPolylineGeometry()
{
}
-/*!
- \internal
-*/
-void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
- const QList<QGeoCoordinate> &path,
- const QGeoCoordinate geoLeftBound)
+QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap &map,
+ const QList<QGeoCoordinate> &path,
+ QDoubleVector2D &leftBoundWrapped)
{
- bool foundValid = false;
- double minX = -1.0;
- double minY = -1.0;
- double maxX = -1.0;
- double maxY = -1.0;
-
- if (!sourceDirty_)
- return;
-
- geoLeftBound_ = geoLeftBound;
-
- // clear the old data and reserve enough memory
- srcPoints_.clear();
- srcPoints_.reserve(path.size() * 2);
- srcPointTypes_.clear();
- srcPointTypes_.reserve(path.size());
-
- QDoubleVector2D lastAddedPoint;
- const double mapWidthHalf = map.viewportWidth()/2.0;
- QDoubleVector2D origin = map.geoProjection().coordinateToItemPosition(geoLeftBound_, false);
+ /*
+ * Approach:
+ * 1) project coordinates to wrapped web mercator, and do unwrapBelowX
+ * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons)
+ * 2.1) recalculate the origin and geoLeftBound to prevent these parameters from ending in unprojectable areas
+ * 2.2) ensure the left bound does not wrap around due to QGeoCoordinate <-> clipper conversions
+ */
srcOrigin_ = geoLeftBound_;
double unwrapBelowX = 0;
+ leftBoundWrapped = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(geoLeftBound_));
if (preserveGeometry_)
- unwrapBelowX = origin.x();
+ unwrapBelowX = leftBoundWrapped.x();
+ QList<QDoubleVector2D> wrappedPath;
+ wrappedPath.reserve(path.size());
+ QDoubleVector2D wrappedLeftBound(qInf(), qInf());
+ // 1)
for (int i = 0; i < path.size(); ++i) {
const QGeoCoordinate &coord = path.at(i);
-
if (!coord.isValid())
continue;
- QDoubleVector2D point = map.geoProjection().coordinateToItemPosition(coord, false);
+ QDoubleVector2D wrappedProjection = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(coord));
// We can get NaN if the map isn't set up correctly, or the projection
// is faulty -- probably best thing to do is abort
- if (!qIsFinite(point.x()) || !qIsFinite(point.y()))
- return;
-
- bool isPointLessThanUnwrapBelowX = (point.x() < unwrapBelowX);
- bool isCoordNotLeftBound = !qFuzzyCompare(geoLeftBound_.longitude(), coord.longitude());
- bool isPointNotUnwrapBelowX = !qFuzzyCompare(point.x(), unwrapBelowX);
- bool isPointNotMapWidthHalf = !qFuzzyCompare(mapWidthHalf, point.x());
+ if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y()))
+ return QList<QList<QDoubleVector2D> >();
+ const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x());
// unwrap x to preserve geometry if moved to border of map
- if (preserveGeometry_ && isPointLessThanUnwrapBelowX
- && isCoordNotLeftBound
- && isPointNotUnwrapBelowX
- && isPointNotMapWidthHalf) {
- double distance = geoDistanceToScreenWidth(map, geoLeftBound_, coord);
- point.setX(unwrapBelowX + distance);
+ if (preserveGeometry_ && isPointLessThanUnwrapBelowX) {
+ double distance = wrappedProjection.x() - unwrapBelowX;
+ if (distance < 0.0)
+ distance += 1.0;
+ wrappedProjection.setX(unwrapBelowX + distance);
+ }
+ if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) {
+ wrappedLeftBound = wrappedProjection;
}
+ wrappedPath.append(wrappedProjection);
+ }
- if (!foundValid) {
- foundValid = true;
+ // 2)
+ QList<QList<QDoubleVector2D> > clippedPaths;
+ const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion();
+ if (visibleRegion.size()) {
+ c2t::clip2tri clipper;
+ clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), false);
+ clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion));
+ Paths res = clipper.execute(c2t::clip2tri::Intersection);
+ clippedPaths = QClipperUtils::pathsToQList(res);
+
+ // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X
+ QDoubleVector2D lb(qInf(), qInf());
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ for (const QDoubleVector2D &p: path) {
+ if (p == leftBoundWrapped) {
+ lb = p;
+ break;
+ } else if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) {
+ // y-minimization needed to find the same point on polygon and border
+ lb = p;
+ }
+ }
+ }
+ if (qIsInf(lb.x()))
+ return QList<QList<QDoubleVector2D> >();
- point = point - origin; // (0,0) if point == geoLeftBound_
+ // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which
+ // in turn will make the geometry wrap around.
+ lb.setX(qMax(wrappedLeftBound.x(), lb.x()));
+ leftBoundWrapped = lb;
+ } else {
+ clippedPaths.append(wrappedPath);
+ }
+
+ return clippedPaths;
+}
- minX = point.x();
- maxX = minX;
- minY = point.y();
- maxY = minY;
+void QGeoMapPolylineGeometry::pathToScreen(const QGeoMap &map,
+ const QList<QList<QDoubleVector2D> > &clippedPaths,
+ const QDoubleVector2D &leftBoundWrapped)
+{
+ // 3) project the resulting geometry to screen position and calculate screen bounds
+ double minX = qInf();
+ double minY = qInf();
+ double maxX = -qInf();
+ double maxY = -qInf();
+
+ srcOrigin_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(leftBoundWrapped));
+ QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(leftBoundWrapped);
+ for (const QList<QDoubleVector2D> &path: clippedPaths) {
+ QDoubleVector2D lastAddedPoint;
+ for (int i = 0; i < path.size(); ++i) {
+ QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i));
- srcPoints_ << point.x() << point.y();
- srcPointTypes_ << QPainterPath::MoveToElement;
- lastAddedPoint = point;
- } else {
- point -= origin;
+ point = point - origin; // (0,0) if point == geoLeftBound_
minX = qMin(point.x(), minX);
minY = qMin(point.y(), minY);
maxX = qMax(point.x(), maxX);
maxY = qMax(point.y(), maxY);
- if ((point - lastAddedPoint).manhattanLength() > 3 ||
- i == path.size() - 1) {
+ if (i == 0) {
srcPoints_ << point.x() << point.y();
- srcPointTypes_ << QPainterPath::LineToElement;
+ srcPointTypes_ << QPainterPath::MoveToElement;
lastAddedPoint = point;
+ } else {
+ if ((point - lastAddedPoint).manhattanLength() > 3 ||
+ i == path.size() - 1) {
+ srcPoints_ << point.x() << point.y();
+ srcPointTypes_ << QPainterPath::LineToElement;
+ lastAddedPoint = point;
+ }
}
}
}
@@ -269,7 +305,41 @@ void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
}
+/*!
+ \internal
+*/
+void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map,
+ const QList<QGeoCoordinate> &path,
+ const QGeoCoordinate geoLeftBound)
+{
+ if (!sourceDirty_)
+ return;
+
+ geoLeftBound_ = geoLeftBound;
+
+ // clear the old data and reserve enough memory
+ srcPoints_.clear();
+ srcPoints_.reserve(path.size() * 2);
+ srcPointTypes_.clear();
+ srcPointTypes_.reserve(path.size());
+
+ /*
+ * Approach:
+ * 1) project coordinates to wrapped web mercator, and do unwrapBelowX
+ * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons)
+ * 3) project the resulting geometry to screen position and calculate screen bounds
+ */
+
+ QDoubleVector2D leftBoundWrapped;
+ // 1, 2)
+ const QList<QList<QDoubleVector2D> > &clippedPaths = clipPath(map, path, leftBoundWrapped);
+
+ // 3)
+ pathToScreen(map, clippedPaths, leftBoundWrapped);
+}
+
////////////////////////////////////////////////////////////////////////////
+#if 0 // Old polyline to viewport clipping code. Retaining it for now.
/* Polyline clip */
enum ClipPointType {
@@ -382,7 +452,7 @@ static void clipPathToRect(const QVector<qreal> &points,
lastY = points[i * 2 + 1];
}
}
-
+#endif
/*!
\internal
*/
@@ -394,7 +464,7 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
QPointF origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false).toPointF();
- if (!qIsFinite(origin.x()) || !qIsFinite(origin.y())) {
+ if (!qIsFinite(origin.x()) || !qIsFinite(origin.y()) || srcPointTypes_.size() < 2) { // the line might have been clipped away.
clear();
return;
}
@@ -405,19 +475,13 @@ void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map,
viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth);
viewport.translate(-1 * origin);
- // Perform clipping to the viewport limits
- QVector<qreal> points;
- QVector<QPainterPath::ElementType> types;
-
- if (clipToViewport_) {
- clipPathToRect(srcPoints_, srcPointTypes_, viewport, points, types);
- } else {
- points = srcPoints_;
- types = srcPointTypes_;
- }
+ // The geometry has already been clipped against the visible region projection in wrapped mercator space.
+ QVector<qreal> points = srcPoints_;
+ QVector<QPainterPath::ElementType> types = srcPointTypes_;
QVectorPath vp(points.data(), types.size(), types.data());
QTriangulatingStroker ts;
+ // viewport is not used in the call below.
ts.process(vp, QPen(QBrush(Qt::black), strokeWidth), viewport, QPainter::Qt4CompatiblePainting);
clear();
@@ -873,17 +937,8 @@ void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChan
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- }
geometry_.setPreserveGeometry(true, geometry_.geoLeftBound());
- geometry_.markScreenDirty();
+ geometry_.markSourceDirty();
polishAndUpdate();
}
@@ -904,7 +959,7 @@ void QDeclarativePolylineMapItem::updatePolish()
setWidth(geometry_.sourceBoundingBox().width());
setHeight(geometry_.sourceBoundingBox().height());
- setPositionOnMap(geoLeftBound_, -1 * geometry_.sourceBoundingBox().topLeft());
+ setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft());
}
/*!
diff --git a/src/imports/location/qdeclarativepolylinemapitem_p.h b/src/imports/location/qdeclarativepolylinemapitem_p.h
index 8c827b6f..c744bf7f 100644
--- a/src/imports/location/qdeclarativepolylinemapitem_p.h
+++ b/src/imports/location/qdeclarativepolylinemapitem_p.h
@@ -95,11 +95,22 @@ public:
void updateScreenPoints(const QGeoMap &map,
qreal strokeWidth);
+protected:
+ QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map,
+ const QList<QGeoCoordinate> &path,
+ QDoubleVector2D &leftBoundWrapped);
+
+ void pathToScreen(const QGeoMap &map,
+ const QList<QList<QDoubleVector2D> > &clippedPaths,
+ const QDoubleVector2D &leftBoundWrapped);
+
private:
QVector<qreal> srcPoints_;
QVector<QPainterPath::ElementType> srcPointTypes_;
-
+ friend class QDeclarativeCircleMapItem;
+ friend class QDeclarativePolygonMapItem;
+ friend class QDeclarativeRectangleMapItem;
};
class QDeclarativePolylineMapItem : public QDeclarativeGeoMapItemBase
diff --git a/src/imports/location/qdeclarativerectanglemapitem.cpp b/src/imports/location/qdeclarativerectanglemapitem.cpp
index a3b8db90..5b6a8914 100644
--- a/src/imports/location/qdeclarativerectanglemapitem.cpp
+++ b/src/imports/location/qdeclarativerectanglemapitem.cpp
@@ -112,65 +112,6 @@ QT_BEGIN_NAMESPACE
\image api-maprectangle.png
*/
-struct Vertex
-{
- QVector2D position;
-};
-
-QGeoMapRectangleGeometry::QGeoMapRectangleGeometry()
-{
-}
-
-/*!
- \internal
-*/
-void QGeoMapRectangleGeometry::updatePoints(const QGeoMap &map,
- const QGeoCoordinate &topLeft,
- const QGeoCoordinate &bottomRight)
-{
- if (!screenDirty_ && !sourceDirty_)
- return;
-
- QDoubleVector2D tl = map.geoProjection().coordinateToItemPosition(topLeft, false);
- QDoubleVector2D br = map.geoProjection().coordinateToItemPosition(bottomRight, false);
-
- // We can get NaN if the map isn't set up correctly, or the projection
- // is faulty -- probably best thing to do is abort
- if (!qIsFinite(tl.x()) || !qIsFinite(tl.y()))
- return;
- if (!qIsFinite(br.x()) || !qIsFinite(br.y()))
- return;
-
- if ( preserveGeometry_ ) {
- double unwrapBelowX = map.geoProjection().coordinateToItemPosition(geoLeftBound_, false).x();
- if (br.x() < unwrapBelowX)
- br.setX(tl.x() + screenBounds_.width());
- }
-
- QRectF re(tl.toPointF(), br.toPointF());
- re.translate(-1 * tl.toPointF());
-
- clear();
- screenVertices_.reserve(6);
-
- screenVertices_ << re.topLeft();
- screenVertices_ << re.topRight();
- screenVertices_ << re.bottomLeft();
-
- screenVertices_ << re.topRight();
- screenVertices_ << re.bottomLeft();
- screenVertices_ << re.bottomRight();
-
- firstPointOffset_ = QPointF(0,0);
- srcOrigin_ = topLeft;
- screenBounds_ = re;
-
- screenOutline_ = QPainterPath();
- screenOutline_.addRect(re);
-
- geoLeftBound_ = topLeft;
-}
-
QDeclarativeRectangleMapItem::QDeclarativeRectangleMapItem(QQuickItem *parent)
: QDeclarativeGeoMapItemBase(parent), color_(Qt::transparent), dirtyMaterial_(true),
updatingGeometry_(false)
@@ -334,33 +275,49 @@ void QDeclarativeRectangleMapItem::updatePolish()
QScopedValueRollback<bool> rollback(updatingGeometry_);
updatingGeometry_ = true;
- geometry_.updatePoints(*map(), topLeft_, bottomRight_);
+ QList<QGeoCoordinate> path;
+ path << topLeft_;
+ path << QGeoCoordinate(topLeft_.latitude(), bottomRight_.longitude());
+ path << bottomRight_;
+ path << QGeoCoordinate(bottomRight_.latitude(), topLeft_.longitude());
+
+ geometry_.setPreserveGeometry(true, topLeft_);
+ geometry_.updateSourcePoints(*map(), path);
+ geometry_.updateScreenPoints(*map());
- QList<QGeoCoordinate> pathClosed;
- pathClosed << topLeft_;
- pathClosed << QGeoCoordinate(topLeft_.latitude(), bottomRight_.longitude());
- pathClosed << bottomRight_;
- pathClosed << QGeoCoordinate(bottomRight_.latitude(), topLeft_.longitude());
- pathClosed << pathClosed.first();
+ QList<QGeoMapItemGeometry *> geoms;
+ geoms << &geometry_;
+ borderGeometry_.clear();
if (border_.color() != Qt::transparent && border_.width() > 0) {
- borderGeometry_.updateSourcePoints(*map(), pathClosed, topLeft_);
- borderGeometry_.updateScreenPoints(*map(), border_.width());
+ QList<QGeoCoordinate> closedPath = path;
+ closedPath << closedPath.first();
+
+ borderGeometry_.setPreserveGeometry(true, topLeft_);
- QList<QGeoMapItemGeometry *> geoms;
- geoms << &geometry_ << &borderGeometry_;
- QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ const QGeoCoordinate &geometryOrigin = geometry_.origin();
- setWidth(combined.width());
- setHeight(combined.height());
- } else {
- borderGeometry_.clear();
+ borderGeometry_.srcPoints_.clear();
+ borderGeometry_.srcPointTypes_.clear();
- setWidth(geometry_.screenBoundingBox().width());
- setHeight(geometry_.screenBoundingBox().height());
+ QDoubleVector2D borderLeftBoundWrapped;
+ QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped);
+ if (clippedPaths.size()) {
+ borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin);
+ borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped);
+ borderGeometry_.updateScreenPoints(*map(), border_.width());
+
+ geoms << &borderGeometry_;
+ } else {
+ borderGeometry_.clear();
+ }
}
- setPositionOnMap(pathClosed.at(0), geometry_.firstPointOffset());
+ QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms);
+ setWidth(combined.width());
+ setHeight(combined.height());
+
+ setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset());
}
/*!
@@ -371,21 +328,10 @@ void QDeclarativeRectangleMapItem::afterViewportChanged(const QGeoMapViewportCha
if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
return;
- // if the scene is tilted, we must regenerate our geometry every frame
- if ((event.cameraData.tilt() > 0.0 || event.tiltChanged) && map()->cameraCapabilities().supportsTilting()) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
-
- // otherwise, only regen on rotate, resize and zoom
- if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
- geometry_.markSourceDirty();
- borderGeometry_.markSourceDirty();
- }
geometry_.setPreserveGeometry(true, topLeft_);
borderGeometry_.setPreserveGeometry(true, topLeft_);
- geometry_.markScreenDirty();
- borderGeometry_.markScreenDirty();
+ geometry_.markSourceDirty();
+ borderGeometry_.markSourceDirty();
polishAndUpdate();
}
diff --git a/src/imports/location/qdeclarativerectanglemapitem_p.h b/src/imports/location/qdeclarativerectanglemapitem_p.h
index fb9936b0..3c55b7ba 100644
--- a/src/imports/location/qdeclarativerectanglemapitem_p.h
+++ b/src/imports/location/qdeclarativerectanglemapitem_p.h
@@ -51,23 +51,12 @@
#include "qdeclarativegeomapitembase_p.h"
#include "qgeomapitemgeometry_p.h"
#include "qdeclarativepolylinemapitem_p.h"
+#include "qdeclarativepolygonmapitem_p.h"
#include <QSGGeometryNode>
#include <QSGFlatColorMaterial>
QT_BEGIN_NAMESPACE
-class QGeoMapRectangleGeometry : public QGeoMapItemGeometry
-{
-public:
- QGeoMapRectangleGeometry();
-
- void updatePoints(const QGeoMap &map,
- const QGeoCoordinate &topLeft,
- const QGeoCoordinate &bottomRight);
-};
-
-class MapRectangleNode;
-
class QDeclarativeRectangleMapItem: public QDeclarativeGeoMapItemBase
{
Q_OBJECT
@@ -117,7 +106,7 @@ private:
QDeclarativeMapLineProperties border_;
QColor color_;
bool dirtyMaterial_;
- QGeoMapRectangleGeometry geometry_;
+ QGeoMapPolygonGeometry geometry_;
QGeoMapPolylineGeometry borderGeometry_;
bool updatingGeometry_;
};
diff --git a/src/imports/location/qgeomapitemgeometry.cpp b/src/imports/location/qgeomapitemgeometry.cpp
index ab90d0dd..1b7e7d17 100644
--- a/src/imports/location/qgeomapitemgeometry.cpp
+++ b/src/imports/location/qgeomapitemgeometry.cpp
@@ -100,7 +100,15 @@ QRectF QGeoMapItemGeometry::translateToCommonOrigin(const QList<QGeoMapItemGeome
// first get max offset
QPointF maxOffset = geoms.at(0)->firstPointOffset();
foreach (QGeoMapItemGeometry *g, geoms) {
- Q_ASSERT(g->origin() == origin);
+#ifndef QT_NO_DEBUG
+ //Q_ASSERT(g->origin() == origin); // this might fail on clipper clipping inaccuracies, so better to remove it in production
+ if (!qFuzzyCompare(origin.latitude(), g->origin().latitude())) {
+ qWarning("translateToCommonOrigin: Origins differ!");
+ }
+ if (!qFuzzyCompare(origin.longitude(), g->origin().longitude())) {
+ qWarning("translateToCommonOrigin: Origins differ!");
+ }
+#endif
QPointF o = g->firstPointOffset();
maxOffset.setX(qMax(o.x(), maxOffset.x()));
maxOffset.setY(qMax(o.y(), maxOffset.y()));
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: