summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Angelelli <paolo.angelelli@qt.io>2016-12-11 20:44:17 +0100
committerPaolo Angelelli <paolo.angelelli@qt.io>2017-01-26 14:45:48 +0000
commitc57d42b47004623db9b934d0688180ec6dc1a73e (patch)
treeba4cd0bb2f332db2ad2ad4d144cc2fa3227f3fc2
parenta33f9131a3f5b07831ea9565cb0dc22e078f9475 (diff)
downloadqtlocation-c57d42b47004623db9b934d0688180ec6dc1a73e.tar.gz
Add clipping for rotated/tilted Map Items
This patch adds proper rotation/tilting support to Map Items. To do so, clipping is now performed in wrapped mercator space instead of screen space. This prevents projection of geo coordinates that ended behind the camera, and that would be projected incorrectly by the projection transformation. This patch therefore does not use the screen clipping code any longer (clipPathToRect), since the geometry has already been clipped. The downside is that updateSourcePoints is now necessary for any viewport change. This would be necessary anyway in presence of tilt or rotation. NB: Handling of MapQuickItems with zoomLevel set is still TODO. Future work: 1) Optimize updateSourcePoints by pre-computing the mercator projection of the geometry, and let updateSourcePoints do only the wrapping/clipping/projection-to-screen operations. 2) Remove updateScreenPoints altogether Change-Id: Ie0d3dbef68d48ac97a596d40240d0ac126c0efaf Reviewed-by: Alex Blasche <alexander.blasche@qt.io> Reviewed-by: Paolo Angelelli <paolo.angelelli@qt.io> Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-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: