summaryrefslogtreecommitdiff
path: root/src/location/maps/qgeoprojection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/location/maps/qgeoprojection.cpp')
-rw-r--r--src/location/maps/qgeoprojection.cpp237
1 files changed, 205 insertions, 32 deletions
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