1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qgeoprojection_p.h"
38 #include <QtPositioning/private/qwebmercator_p.h>
39 #include <QtPositioning/private/qlocationutils_p.h>
40 #include <QtPositioning/private/qclipperutils_p.h>
41 #include <QtPositioning/QGeoPolygon>
42 #include <QtPositioning/QGeoRectangle>
43 #include <QSize>
44 #include <QtGui/QMatrix4x4>
45 #include <cmath>
46 
47 namespace {
48     static const double defaultTileSize = 256.0;
49     static const QDoubleVector3D xyNormal(0.0, 0.0, 1.0);
50     static const QGeoProjectionWebMercator::Plane xyPlane(QDoubleVector3D(0,0,0), QDoubleVector3D(0,0,1));
51     static const QList<QDoubleVector2D> mercatorGeometry = {
52                                                 QDoubleVector2D(-1.0,0.0),
53                                                 QDoubleVector2D( 2.0,0.0),
54                                                 QDoubleVector2D( 2.0,1.0),
55                                                 QDoubleVector2D(-1.0,1.0) };
56 }
57 
toMatrix4x4(const QDoubleMatrix4x4 & m)58 static QMatrix4x4 toMatrix4x4(const QDoubleMatrix4x4 &m)
59 {
60     return QMatrix4x4(m(0,0), m(0,1), m(0,2), m(0,3),
61                       m(1,0), m(1,1), m(1,2), m(1,3),
62                       m(2,0), m(2,1), m(2,2), m(2,3),
63                       m(3,0), m(3,1), m(3,2), m(3,3));
64 }
65 
centerOffset(const QSizeF & screenSize,const QRectF & visibleArea)66 static QPointF centerOffset(const QSizeF &screenSize, const QRectF &visibleArea)
67 {
68     QRectF va = visibleArea;
69     if (va.isNull())
70         va = QRectF(0, 0, screenSize.width(), screenSize.height());
71 
72     QRectF screen = QRectF(QPointF(0,0),screenSize);
73     QPointF vaCenter = va.center();
74 
75     QPointF screenCenter = screen.center();
76     QPointF diff = screenCenter - vaCenter;
77 
78     return diff;
79 }
80 
marginsOffset(const QSizeF & screenSize,const QRectF & visibleArea)81 static QPointF marginsOffset(const QSizeF &screenSize, const QRectF &visibleArea)
82 {
83     QPointF diff = centerOffset(screenSize, visibleArea);
84     qreal xdiffpct = diff.x() / qMax<double>(screenSize.width() - 1, 1);
85     qreal ydiffpct = diff.y() / qMax<double>(screenSize.height() - 1, 1);
86 
87     return QPointF(-xdiffpct, -ydiffpct);
88 }
89 
90 QT_BEGIN_NAMESPACE
91 
QGeoProjection()92 QGeoProjection::QGeoProjection()
93 {
94 
95 }
96 
~QGeoProjection()97 QGeoProjection::~QGeoProjection()
98 {
99 
100 }
101 
anchorCoordinateToPoint(const QGeoCoordinate & coordinate,const QPointF & anchorPoint) const102 QGeoCoordinate QGeoProjection::anchorCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &anchorPoint) const
103 {
104     Q_UNUSED(coordinate);
105     Q_UNUSED(anchorPoint);
106     return QGeoCoordinate();
107 }
108 
visibleRegion() const109 QGeoShape QGeoProjection::visibleRegion() const
110 {
111     return QGeoShape();
112 }
113 
setBearing(qreal bearing,const QGeoCoordinate & coordinate)114 bool QGeoProjection::setBearing(qreal bearing, const QGeoCoordinate &coordinate)
115 {
116     Q_UNUSED(bearing);
117     Q_UNUSED(coordinate);
118     return false;
119 }
120 
setItemToWindowTransform(const QTransform & itemToWindowTransform)121 void QGeoProjection::setItemToWindowTransform(const QTransform &itemToWindowTransform)
122 {
123     if (m_itemToWindowTransform == itemToWindowTransform)
124         return;
125     m_qsgTransformDirty = true;
126     m_itemToWindowTransform = itemToWindowTransform;
127 }
128 
itemToWindowTransform() const129 QTransform QGeoProjection::itemToWindowTransform() const
130 {
131     return m_itemToWindowTransform;
132 }
133 
134 
135 /*
136  * QGeoProjectionWebMercator implementation
137 */
138 
anchorCoordinateToPoint(const QGeoCoordinate & coordinate,const QPointF & anchorPoint) const139 QGeoCoordinate QGeoProjectionWebMercator::anchorCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &anchorPoint) const
140 {
141     // Approach: find the displacement in (wrapped) mercator space, and apply that to the center
142     QDoubleVector2D centerProj = geoToWrappedMapProjection(cameraData().center());
143     QDoubleVector2D coordProj  = geoToWrappedMapProjection(coordinate);
144 
145     QDoubleVector2D anchorProj = itemPositionToWrappedMapProjection(QDoubleVector2D(anchorPoint));
146     // Y-clamping done in mercatorToCoord
147     return wrappedMapProjectionToGeo(centerProj + coordProj - anchorProj);
148 }
149 
setBearing(qreal bearing,const QGeoCoordinate & coordinate)150 bool QGeoProjectionWebMercator::setBearing(qreal bearing, const QGeoCoordinate &coordinate)
151 {
152     const QDoubleVector2D coordWrapped = geoToWrappedMapProjection(coordinate);
153     if (!isProjectable(coordWrapped))
154         return false;
155     const QPointF rotationPoint = wrappedMapProjectionToItemPosition(coordWrapped).toPointF();
156 
157     QGeoCameraData camera = cameraData();
158     // first set bearing
159     camera.setBearing(bearing);
160     setCameraData(camera);
161     camera = cameraData();
162 
163     // then reanchor
164     const QGeoCoordinate center = anchorCoordinateToPoint(coordinate, rotationPoint);
165     camera.setCenter(center);
166     setCameraData(camera);
167     return true;
168 }
169 
QGeoProjectionWebMercator()170 QGeoProjectionWebMercator::QGeoProjectionWebMercator()
171     : QGeoProjection(),
172       m_mapEdgeSize(256), // at zl 0
173       m_minimumZoom(0),
174       m_cameraCenterXMercator(0),
175       m_cameraCenterYMercator(0),
176       m_viewportWidth(1),
177       m_viewportHeight(1),
178       m_1_viewportWidth(0),
179       m_1_viewportHeight(0),
180       m_sideLengthPixels(256),
181       m_aperture(0.0),
182       m_nearPlane(0.0),
183       m_farPlane(0.0),
184       m_halfWidth(0.0),
185       m_halfHeight(0.0),
186       m_minimumUnprojectableY(0.0),
187       m_verticalEstateToSkip(0.0),
188       m_visibleRegionDirty(false)
189 {
190 }
191 
~QGeoProjectionWebMercator()192 QGeoProjectionWebMercator::~QGeoProjectionWebMercator()
193 {
194 
195 }
196 
197 // This method returns the minimum zoom level that this specific qgeomap type allows
198 // at the current viewport size and for the default tile size of 256^2.
minimumZoom() const199 double QGeoProjectionWebMercator::minimumZoom() const
200 {
201     return m_minimumZoom;
202 }
203 
projectionTransformation() const204 QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation() const
205 {
206     return toMatrix4x4(m_transformation);
207 }
208 
projectionTransformation_centered() const209 QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation_centered() const
210 {
211     return toMatrix4x4(m_transformation0);
212 }
213 
qsgTransform() const214 const QMatrix4x4 &QGeoProjectionWebMercator::qsgTransform() const
215 {
216     if (m_qsgTransformDirty) {
217         m_qsgTransformDirty = false;
218         m_qsgTransform = QMatrix4x4(m_itemToWindowTransform) * toMatrix4x4(m_transformation0);
219 //        qDebug() << "QGeoProjectionWebMercator::qsgTransform" << m_itemToWindowTransform << toMatrix4x4(m_transformation0);
220     }
221     return m_qsgTransform;
222 }
223 
centerMercator() const224 QDoubleVector3D QGeoProjectionWebMercator::centerMercator() const
225 {
226     return geoToMapProjection(m_cameraData.center()).toVector3D();
227 }
228 
229 // This method recalculates the "no-trespassing" limits for the map center.
230 // This has to be used when:
231 // 1) the map is resized, because the meters per pixel remain the same, but
232 //    the amount of pixels between the center and the borders changes
233 // 2) when the zoom level changes, because the amount of pixels between the center
234 //    and the borders stays the same, but the meters per pixel change
maximumCenterLatitudeAtZoom(const QGeoCameraData & cameraData) const235 double QGeoProjectionWebMercator::maximumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const
236 {
237     double mapEdgeSize = std::pow(2.0, cameraData.zoomLevel()) * defaultTileSize;
238 
239     // At init time weird things happen
240     int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight;
241     QPointF offsetPct = centerOffset(QSizeF(m_viewportWidth, m_viewportHeight), m_visibleArea);
242     double hpct = offsetPct.y() / qMax<double>(m_viewportHeight - 1, 1);
243 
244     // Use the window height divided by 2 as the topmost allowed center, with respect to the map size in pixels
245     double mercatorTopmost = (clampedWindowHeight * (0.5 - hpct)) /  mapEdgeSize ;
246     QGeoCoordinate topMost = QWebMercator::mercatorToCoord(QDoubleVector2D(0.0, mercatorTopmost));
247     return topMost.latitude();
248 }
249 
minimumCenterLatitudeAtZoom(const QGeoCameraData & cameraData) const250 double QGeoProjectionWebMercator::minimumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const
251 {
252     double mapEdgeSize = std::pow(2.0, cameraData.zoomLevel()) * defaultTileSize;
253 
254     // At init time weird things happen
255     int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight;
256     QPointF offsetPct = centerOffset(QSizeF(m_viewportWidth, m_viewportHeight), m_visibleArea);
257     double hpct = offsetPct.y() / qMax<double>(m_viewportHeight - 1, 1);
258 
259     // Use the window height divided by 2 as the topmost allowed center, with respect to the map size in pixels
260     double mercatorTopmost = (clampedWindowHeight * (0.5 + hpct)) /  mapEdgeSize ;
261     QGeoCoordinate topMost = QWebMercator::mercatorToCoord(QDoubleVector2D(0.0, mercatorTopmost));
262     return -topMost.latitude();
263 }
264 
setVisibleArea(const QRectF & visibleArea)265 void QGeoProjectionWebMercator::setVisibleArea(const QRectF &visibleArea)
266 {
267     m_visibleArea = visibleArea;
268     setupCamera();
269 }
270 
mapWidth() const271 double QGeoProjectionWebMercator::mapWidth() const
272 {
273     return m_mapEdgeSize;
274 }
275 
mapHeight() const276 double QGeoProjectionWebMercator::mapHeight() const
277 {
278     return m_mapEdgeSize;
279 }
280 
setViewportSize(const QSize & size)281 void QGeoProjectionWebMercator::setViewportSize(const QSize &size)
282 {
283     if (int(m_viewportWidth) ==  size.width() && int(m_viewportHeight) == size.height())
284         return;
285 
286     m_viewportWidth = size.width();
287     m_viewportHeight = size.height();
288     m_1_viewportWidth = 1.0 / m_viewportWidth;
289     m_1_viewportHeight = 1.0 / m_viewportHeight;
290     m_minimumZoom =  std::log(qMax(m_viewportWidth, m_viewportHeight) / defaultTileSize) / std::log(2.0);
291     setupCamera();
292 }
293 
setCameraData(const QGeoCameraData & cameraData,bool force)294 void QGeoProjectionWebMercator::setCameraData(const QGeoCameraData &cameraData, bool force)
295 {
296     if (m_cameraData == cameraData && !force)
297         return;
298 
299     m_cameraData = cameraData;
300     m_mapEdgeSize = std::pow(2.0, cameraData.zoomLevel()) * defaultTileSize;
301     setupCamera();
302 }
303 
geoToMapProjection(const QGeoCoordinate & coordinate) const304 QDoubleVector2D QGeoProjectionWebMercator::geoToMapProjection(const QGeoCoordinate &coordinate) const
305 {
306     return QWebMercator::coordToMercator(coordinate);
307 }
308 
mapProjectionToGeo(const QDoubleVector2D & projection) const309 QGeoCoordinate QGeoProjectionWebMercator::mapProjectionToGeo(const QDoubleVector2D &projection) const
310 {
311     return QWebMercator::mercatorToCoord(projection);
312 }
313 
projectionWrapFactor(const QDoubleVector2D & projection) const314 int QGeoProjectionWebMercator::projectionWrapFactor(const QDoubleVector2D &projection) const
315 {
316     const double &x = projection.x();
317     if (m_cameraCenterXMercator < 0.5) {
318         if (x - m_cameraCenterXMercator > 0.5 )
319             return -1;
320     } else if (m_cameraCenterXMercator > 0.5) {
321         if (x - m_cameraCenterXMercator < -0.5 )
322             return 1;
323     }
324     return 0;
325 }
326 
327 //wraps around center
wrapMapProjection(const QDoubleVector2D & projection) const328 QDoubleVector2D QGeoProjectionWebMercator::wrapMapProjection(const QDoubleVector2D &projection) const
329 {
330     return QDoubleVector2D(projection.x() + double(projectionWrapFactor(projection)), projection.y());
331 }
332 
unwrapMapProjection(const QDoubleVector2D & wrappedProjection) const333 QDoubleVector2D QGeoProjectionWebMercator::unwrapMapProjection(const QDoubleVector2D &wrappedProjection) const
334 {
335     double x = wrappedProjection.x();
336     if (x > 1.0)
337         return QDoubleVector2D(x - 1.0, wrappedProjection.y());
338     if (x <= 0.0)
339         return QDoubleVector2D(x + 1.0, wrappedProjection.y());
340     return wrappedProjection;
341 }
342 
wrappedMapProjectionToItemPosition(const QDoubleVector2D & wrappedProjection) const343 QDoubleVector2D QGeoProjectionWebMercator::wrappedMapProjectionToItemPosition(const QDoubleVector2D &wrappedProjection) const
344 {
345     return (m_transformation * wrappedProjection).toVector2D();
346 }
347 
itemPositionToWrappedMapProjection(const QDoubleVector2D & itemPosition) const348 QDoubleVector2D QGeoProjectionWebMercator::itemPositionToWrappedMapProjection(const QDoubleVector2D &itemPosition) const
349 {
350     const QPointF centerOff = centerOffset(QSizeF(m_viewportWidth, m_viewportHeight), m_visibleArea);
351     QDoubleVector2D pos = itemPosition + QDoubleVector2D(centerOff);
352     pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight);
353     pos *= 2.0;
354     pos -= QDoubleVector2D(1.0,1.0);
355 
356     double s;
357     QDoubleVector2D res = viewportToWrappedMapProjection(pos, s);
358 
359     // a positive s means a point behind the camera. So do it again, after clamping Y. See QTBUG-61813
360     if (s > 0.0) {
361         pos = itemPosition;
362         // when the camera is tilted, picking a point above the horizon returns a coordinate behind the camera
363         pos.setY(m_minimumUnprojectableY);
364         pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight);
365         pos *= 2.0;
366         pos -= QDoubleVector2D(1.0,1.0);
367         res = viewportToWrappedMapProjection(pos, s);
368     }
369 
370     return res;
371 }
372 
373 /* Default implementations */
itemPositionToCoordinate(const QDoubleVector2D & pos,bool clipToViewport) const374 QGeoCoordinate QGeoProjectionWebMercator::itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport) const
375 {
376     if (qIsNaN(pos.x()) || qIsNaN(pos.y()))
377         return QGeoCoordinate();
378 
379     if (clipToViewport) {
380         int w = m_viewportWidth;
381         int h = m_viewportHeight;
382 
383         if ((pos.x() < 0) || (w < pos.x()) || (pos.y() < 0) || (h < pos.y()))
384             return QGeoCoordinate();
385     }
386 
387     QDoubleVector2D wrappedMapProjection = itemPositionToWrappedMapProjection(pos);
388     // With rotation/tilting, a screen position might end up outside the projection space.
389     if (!isProjectable(wrappedMapProjection))
390         return QGeoCoordinate();
391     return mapProjectionToGeo(unwrapMapProjection(wrappedMapProjection));
392 }
393 
coordinateToItemPosition(const QGeoCoordinate & coordinate,bool clipToViewport) const394 QDoubleVector2D QGeoProjectionWebMercator::coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport) const
395 {
396     if (!coordinate.isValid())
397         return QDoubleVector2D(qQNaN(), qQNaN());
398 
399     QDoubleVector2D wrappedProjection = wrapMapProjection(geoToMapProjection(coordinate));
400     if (!isProjectable(wrappedProjection))
401         return QDoubleVector2D(qQNaN(), qQNaN());
402 
403     QDoubleVector2D pos = wrappedMapProjectionToItemPosition(wrappedProjection);
404 
405     if (clipToViewport) {
406         int w = m_viewportWidth;
407         int h = m_viewportHeight;
408         double x = pos.x();
409         double y = pos.y();
410         if ((x < -0.5) || (x > w + 0.5) || (y < -0.5) || (y > h + 0.5) || qIsNaN(x) || qIsNaN(y))
411             return QDoubleVector2D(qQNaN(), qQNaN());
412     }
413     return pos;
414 }
415 
geoToWrappedMapProjection(const QGeoCoordinate & coordinate) const416 QDoubleVector2D QGeoProjectionWebMercator::geoToWrappedMapProjection(const QGeoCoordinate &coordinate) const
417 {
418     return wrapMapProjection(geoToMapProjection(coordinate));
419 }
420 
wrappedMapProjectionToGeo(const QDoubleVector2D & wrappedProjection) const421 QGeoCoordinate QGeoProjectionWebMercator::wrappedMapProjectionToGeo(const QDoubleVector2D &wrappedProjection) const
422 {
423     return mapProjectionToGeo(unwrapMapProjection(wrappedProjection));
424 }
425 
quickItemTransformation(const QGeoCoordinate & coordinate,const QPointF & anchorPoint,qreal zoomLevel) const426 QMatrix4x4 QGeoProjectionWebMercator::quickItemTransformation(const QGeoCoordinate &coordinate, const QPointF &anchorPoint, qreal zoomLevel) const
427 {
428     const QDoubleVector2D coordWrapped = geoToWrappedMapProjection(coordinate);
429     double scale = std::pow(0.5, zoomLevel - m_cameraData.zoomLevel());
430     const QDoubleVector2D anchorScaled = QDoubleVector2D(anchorPoint.x(), anchorPoint.y()) * scale;
431     const QDoubleVector2D anchorMercator = anchorScaled / mapWidth();
432 
433     const QDoubleVector2D coordAnchored = coordWrapped - anchorMercator;
434     const QDoubleVector2D coordAnchoredScaled = coordAnchored * m_sideLengthPixels;
435     QDoubleMatrix4x4 matTranslateScale;
436     matTranslateScale.translate(coordAnchoredScaled.x(), coordAnchoredScaled.y(), 0.0);
437 
438     scale = std::pow(0.5, (zoomLevel - std::floor(zoomLevel)) +
439                      (std::floor(zoomLevel) - std::floor(m_cameraData.zoomLevel())));
440     matTranslateScale.scale(scale);
441 
442     /*
443      *  The full transformation chain for quickItemTransformation() would be:
444      *  matScreenShift * m_quickItemTransformation * matTranslate * matScale
445      *  where:
446      *  matScreenShift = translate(-coordOnScreen.x(), -coordOnScreen.y(), 0)
447      *  matTranslate = translate(coordAnchoredScaled.x(), coordAnchoredScaled.y(), 0.0)
448      *  matScale = scale(scale)
449      *
450      *  However, matScreenShift is removed, as setPosition(0,0) is used in place of setPositionOnScreen.
451      */
452 
453     return toMatrix4x4(m_quickItemTransformation * matTranslateScale);
454 }
455 
isProjectable(const QDoubleVector2D & wrappedProjection) const456 bool QGeoProjectionWebMercator::isProjectable(const QDoubleVector2D &wrappedProjection) const
457 {
458     if (m_cameraData.tilt() == 0.0)
459         return true;
460 
461     QDoubleVector3D pos = wrappedProjection * m_sideLengthPixels;
462     // use m_centerNearPlane in order to add an offset to m_eye.
463     QDoubleVector3D p = m_centerNearPlane - pos;
464     double dot = QDoubleVector3D::dotProduct(p , m_viewNormalized);
465 
466     if (dot < 0.0) // behind the near plane
467         return false;
468     return true;
469 }
470 
visibleGeometry() const471 QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleGeometry() const
472 {
473     if (m_visibleRegionDirty)
474         const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion();
475     return m_visibleRegion;
476 }
477 
visibleGeometryExpanded() const478 QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleGeometryExpanded() const
479 {
480     if (m_visibleRegionDirty)
481         const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion();
482     return m_visibleRegionExpanded;
483 }
484 
projectableGeometry() const485 QList<QDoubleVector2D> QGeoProjectionWebMercator::projectableGeometry() const
486 {
487     if (m_visibleRegionDirty)
488         const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion();
489     return m_projectableRegion;
490 }
491 
visibleRegion() const492 QGeoShape QGeoProjectionWebMercator::visibleRegion() const
493 {
494     const QList<QDoubleVector2D> &visibleRegion = visibleGeometry();
495     QGeoPolygon poly;
496     for (int i = 0; i < visibleRegion.size(); ++i) {
497          const QDoubleVector2D &c = visibleRegion.at(i);
498         // If a segment spans more than half of the map longitudinally, split in 2.
499         if (i && qAbs(visibleRegion.at(i-1).x() - c.x()) >= 0.5) { // This assumes a segment is never >= 1.0 (whole map span)
500             QDoubleVector2D extraPoint = (visibleRegion.at(i-1) + c) * 0.5;
501             poly.addCoordinate(wrappedMapProjectionToGeo(extraPoint));
502         }
503         poly.addCoordinate(wrappedMapProjectionToGeo(c));
504     }
505     if (visibleRegion.size() >= 2 && qAbs(visibleRegion.last().x() - visibleRegion.first().x()) >= 0.5) {
506         QDoubleVector2D extraPoint = (visibleRegion.last() + visibleRegion.first()) * 0.5;
507         poly.addCoordinate(wrappedMapProjectionToGeo(extraPoint));
508     }
509 
510     return poly;
511 }
512 
viewportToWrappedMapProjection(const QDoubleVector2D & itemPosition) const513 QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const
514 {
515     double s;
516     return viewportToWrappedMapProjection(itemPosition, s);
517 }
518 
519 /*
520     actual implementation of itemPositionToWrappedMapProjection
521 */
viewportToWrappedMapProjection(const QDoubleVector2D & itemPosition,double & s) const522 QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition, double &s) const
523 {
524     QDoubleVector2D pos = itemPosition;
525     pos *= QDoubleVector2D(m_halfWidth, m_halfHeight);
526 
527     // determine itemPosition on the near plane
528     QDoubleVector3D p = m_centerNearPlane;
529     p += m_up * pos.y();
530     p += m_side * pos.x();
531 
532     // compute the ray using the eye position
533     QDoubleVector3D ray = m_eye - p;
534     ray.normalize();
535 
536     return (xyPlane.lineIntersection(m_eye, ray, s) / m_sideLengthPixels).toVector2D();
537 }
538 
539 /*
540     Returns a pair of <newCenter, newZoom>
541 */
fitViewportToGeoRectangle(const QGeoRectangle & rectangle,const QMargins & m) const542 QPair<QGeoCoordinate, qreal> QGeoProjectionWebMercator::fitViewportToGeoRectangle(const QGeoRectangle &rectangle,
543                                                                                   const QMargins &m) const
544 {
545     QPair<QGeoCoordinate, qreal> res;
546     res.second = qQNaN();
547     if (m_viewportWidth <= m.left() + m.right() || m_viewportHeight <= m.top() + m.bottom())
548         return res;
549 
550     QDoubleVector2D topLeftPoint = geoToMapProjection(rectangle.topLeft());
551     QDoubleVector2D bottomRightPoint = geoToMapProjection(rectangle.bottomRight());
552     if (bottomRightPoint.x() < topLeftPoint.x()) // crossing the dateline
553         bottomRightPoint.setX(bottomRightPoint.x() + 1.0);
554 
555     // find center of the bounding box
556     QDoubleVector2D center = (topLeftPoint + bottomRightPoint) * 0.5;
557     center.setX(center.x() > 1.0 ? center.x() - 1.0 : center.x());
558     res.first = mapProjectionToGeo(center);
559 
560     // if the shape is empty we just change center position, not zoom
561     double bboxWidth  = (bottomRightPoint.x() - topLeftPoint.x()) * mapWidth();
562     double bboxHeight = (bottomRightPoint.y() - topLeftPoint.y()) * mapHeight();
563 
564     if (bboxHeight == 0.0 && bboxWidth == 0.0)
565         return res;
566 
567     double zoomRatio = qMax(bboxWidth / (m_viewportWidth - m.left() - m.right()),
568                             bboxHeight / (m_viewportHeight - m.top() - m.bottom()));
569     zoomRatio = std::log(zoomRatio) / std::log(2.0);
570     res.second = m_cameraData.zoomLevel() - zoomRatio;
571 
572     return  res;
573 }
574 
projectionGroup() const575 QGeoProjection::ProjectionGroup QGeoProjectionWebMercator::projectionGroup() const
576 {
577     return QGeoProjection::ProjectionCylindrical;
578 }
579 
datum() const580 QGeoProjection::Datum QGeoProjectionWebMercator::datum() const
581 {
582     return QGeoProjection::DatumWGS84;
583 }
584 
projectionType() const585 QGeoProjection::ProjectionType QGeoProjectionWebMercator::projectionType() const
586 {
587     return QGeoProjection::ProjectionWebMercator;
588 }
589 
setupCamera()590 void QGeoProjectionWebMercator::setupCamera()
591 {
592     m_qsgTransformDirty = true;
593     m_centerMercator = geoToMapProjection(m_cameraData.center());
594     m_cameraCenterXMercator = m_centerMercator.x();
595     m_cameraCenterYMercator = m_centerMercator.y();
596 
597     int intZoomLevel = static_cast<int>(std::floor(m_cameraData.zoomLevel()));
598     m_sideLengthPixels = (1 << intZoomLevel) * defaultTileSize;
599     m_center = m_centerMercator * m_sideLengthPixels;
600     //aperture(90 / 2) = 1
601     m_aperture = tan(QLocationUtils::radians(m_cameraData.fieldOfView()) * 0.5);
602 
603     double f = m_viewportHeight;
604     double z = std::pow(2.0, m_cameraData.zoomLevel() - intZoomLevel) * defaultTileSize;
605     double altitude = f / (2.0 * z);
606     // Also in mercator space
607     double z_mercator = std::pow(2.0, m_cameraData.zoomLevel()) * defaultTileSize;
608     double altitude_mercator = f / (2.0 * z_mercator);
609 
610     // calculate eye
611     m_eye = m_center;
612     m_eye.setZ(altitude * defaultTileSize / m_aperture);
613 
614     // And in mercator space
615     m_eyeMercator = m_centerMercator;
616     m_eyeMercator.setZ(altitude_mercator  / m_aperture);
617     m_eyeMercator0 = QDoubleVector3D(0,0,0);
618     m_eyeMercator0.setZ(altitude_mercator  / m_aperture);
619     QDoubleVector3D eye0(0,0,0);
620     eye0.setZ(altitude * defaultTileSize / m_aperture);
621 
622     m_view = m_eye - m_center;
623     QDoubleVector3D side = QDoubleVector3D::normal(m_view, QDoubleVector3D(0.0, 1.0, 0.0));
624     m_up = QDoubleVector3D::normal(side, m_view);
625 
626     // In mercator space too
627     m_viewMercator = m_eyeMercator - m_centerMercator;
628     QDoubleVector3D sideMercator = QDoubleVector3D::normal(m_viewMercator, QDoubleVector3D(0.0, 1.0, 0.0));
629     m_upMercator = QDoubleVector3D::normal(sideMercator, m_viewMercator);
630 
631     if (m_cameraData.bearing() > 0.0) {
632         QDoubleMatrix4x4 mBearing;
633         mBearing.rotate(m_cameraData.bearing(), m_view);
634         m_up = mBearing * m_up;
635 
636         // In mercator space too
637         QDoubleMatrix4x4 mBearingMercator;
638         mBearingMercator.rotate(m_cameraData.bearing(), m_viewMercator);
639         m_upMercator = mBearingMercator * m_upMercator;
640     }
641 
642     m_side = QDoubleVector3D::normal(m_up, m_view);
643     m_sideMercator = QDoubleVector3D::normal(m_upMercator, m_viewMercator);
644 
645     if (m_cameraData.tilt() > 0.0) { // tilt has been already thresholded by QGeoCameraData::setTilt
646         QDoubleMatrix4x4 mTilt;
647         mTilt.rotate(-m_cameraData.tilt(), m_side);
648         m_eye = mTilt * m_view + m_center;
649         eye0 = mTilt * m_view;
650 
651         // In mercator space too
652         QDoubleMatrix4x4 mTiltMercator;
653         mTiltMercator.rotate(-m_cameraData.tilt(), m_sideMercator);
654         m_eyeMercator = mTiltMercator * m_viewMercator + m_centerMercator;
655         m_eyeMercator0 = mTiltMercator * m_viewMercator;
656     }
657 
658     m_view = m_eye - m_center; // ToDo: this should be inverted (center - eye), and the rest should follow
659     m_viewNormalized = m_view.normalized();
660     m_up = QDoubleVector3D::normal(m_view, m_side);
661 
662     m_nearPlane = 1.0;
663     // At ZL 20 the map has 2^20 tiles per side. That is 1048576.
664     // Placing the camera on one corner of the map, rotated toward the opposite corner, and tilted
665     // at almost 90 degrees would  require a frustum that can span the whole size of this map.
666     // For this reason, the far plane is set to 2 * 2^20 * defaultTileSize.
667     // That is, in order to make sure that the whole map would fit in the frustum at this ZL.
668     // Since we are using a double matrix, and since the largest value in the matrix is going to be
669     // 2 * m_farPlane (as near plane is 1.0), there should be sufficient precision left.
670     //
671     // TODO: extend this to support clip distance.
672     m_farPlane =  (altitude + 2097152.0) * defaultTileSize;
673 
674     m_viewMercator = m_eyeMercator - m_centerMercator;
675     m_upMercator = QDoubleVector3D::normal(m_viewMercator, m_sideMercator);
676     m_nearPlaneMercator = 0.000002; // this value works until ZL 18. Above that, a better progressive formula is needed, or
677                                     // else, this clips too much.
678 
679     double aspectRatio = 1.0 * m_viewportWidth / m_viewportHeight;
680 
681     m_halfWidth = m_aperture * aspectRatio;
682     m_halfHeight = m_aperture;
683 
684     double verticalHalfFOV = QLocationUtils::degrees(atan(m_aperture));
685 
686     m_cameraMatrix.setToIdentity();
687     m_cameraMatrix.lookAt(m_eye, m_center, m_up);
688     m_cameraMatrix0.setToIdentity();
689     m_cameraMatrix0.lookAt(eye0, QDoubleVector3D(0,0,0), m_up);
690 
691     QDoubleMatrix4x4 projectionMatrix;
692     projectionMatrix.frustum(-m_halfWidth, m_halfWidth, -m_halfHeight, m_halfHeight, m_nearPlane, m_farPlane);
693 
694     /*
695      * The full transformation chain for m_transformation is:
696      * matScreen * matScreenFit * matShift *  projectionMatrix * cameraMatrix * matZoomLevelScale
697      * where:
698      * matZoomLevelScale = scale(m_sideLength, m_sideLength, 1.0)
699      * matShift = translate(1.0, 1.0, 0.0)
700      * matScreenFit = scale(0.5, 0.5, 1.0)
701      * matScreen = scale(m_viewportWidth, m_viewportHeight, 1.0)
702      */
703 
704     QPointF offsetPct = marginsOffset(QSizeF(m_viewportWidth, m_viewportHeight), m_visibleArea);
705     QDoubleMatrix4x4 matScreenTransformation;
706     matScreenTransformation.scale(0.5 * m_viewportWidth, 0.5 * m_viewportHeight, 1.0);
707     matScreenTransformation(0,3) = (0.5 + offsetPct.x()) * m_viewportWidth;
708     matScreenTransformation(1,3) = (0.5 + offsetPct.y()) * m_viewportHeight;
709 
710     m_transformation = matScreenTransformation *  projectionMatrix * m_cameraMatrix;
711     m_quickItemTransformation = m_transformation;
712     m_transformation.scale(m_sideLengthPixels, m_sideLengthPixels, 1.0);
713 
714     m_transformation0 = matScreenTransformation *  projectionMatrix * m_cameraMatrix0;
715     m_transformation0.scale(m_sideLengthPixels, m_sideLengthPixels, 1.0);
716 
717     m_centerNearPlane = m_eye - m_viewNormalized;
718     m_centerNearPlaneMercator = m_eyeMercator - m_viewNormalized * m_nearPlaneMercator;
719 
720     // The method does not support tilting angles >= 90.0 or < 0.
721 
722     // The following formula is used to have a growing epsilon with the zoom level,
723     // in order not to have too large values at low zl, which would overflow when converted to Clipper::cInt.
724     const double upperBoundEpsilon = 1.0 / std::pow(10, 1.0 + m_cameraData.zoomLevel() / 5.0);
725     const double elevationUpperBound = 90.0 - upperBoundEpsilon;
726     const double maxRayElevation = qMin(elevationUpperBound - m_cameraData.tilt(), verticalHalfFOV);
727     double maxHalfAperture = 0;
728     m_verticalEstateToSkip = 0;
729     if (maxRayElevation < verticalHalfFOV) {
730         maxHalfAperture = tan(QLocationUtils::radians(maxRayElevation));
731         m_verticalEstateToSkip = 1.0 - maxHalfAperture / m_aperture;
732     }
733 
734     m_minimumUnprojectableY = m_verticalEstateToSkip * 0.5 * m_viewportHeight; // m_verticalEstateToSkip is relative to half aperture
735     m_visibleRegionDirty = true;
736 }
737 
updateVisibleRegion()738 void QGeoProjectionWebMercator::updateVisibleRegion()
739 {
740     m_visibleRegionDirty = false;
741 
742     double viewportHalfWidth  = (!m_visibleArea.isEmpty()) ? m_visibleArea.width() / m_viewportWidth : 1.0;
743     double viewportHalfHeight = (!m_visibleArea.isEmpty()) ? m_visibleArea.height() / m_viewportHeight : 1.0;
744 
745     double top = qMax<double>(-viewportHalfHeight, -1 + m_verticalEstateToSkip);
746     double bottom = viewportHalfHeight;
747     double left = -viewportHalfWidth;
748     double right = viewportHalfWidth;
749 
750     QDoubleVector2D tl = viewportToWrappedMapProjection(QDoubleVector2D(left, top ));
751     QDoubleVector2D tr = viewportToWrappedMapProjection(QDoubleVector2D(right, top ));
752     QDoubleVector2D bl = viewportToWrappedMapProjection(QDoubleVector2D(left,  bottom ));
753     QDoubleVector2D br = viewportToWrappedMapProjection(QDoubleVector2D(right, bottom ));
754 
755     // To make sure that what is returned can be safely converted back to lat/lon without risking overlaps
756     double mapLeftLongitude = QLocationUtils::mapLeftLongitude(m_cameraData.center().longitude());
757     double mapRightLongitude = QLocationUtils::mapRightLongitude(m_cameraData.center().longitude());
758     double leftX = geoToWrappedMapProjection(QGeoCoordinate(0, mapLeftLongitude)).x();
759     double rightX = geoToWrappedMapProjection(QGeoCoordinate(0, mapRightLongitude)).x();
760 
761     QList<QDoubleVector2D> mapRect;
762     mapRect.push_back(QDoubleVector2D(leftX, 1.0));
763     mapRect.push_back(QDoubleVector2D(rightX, 1.0));
764     mapRect.push_back(QDoubleVector2D(rightX, 0.0));
765     mapRect.push_back(QDoubleVector2D(leftX, 0.0));
766 
767     QList<QDoubleVector2D> viewportRect;
768     viewportRect.push_back(bl);
769     viewportRect.push_back(br);
770     viewportRect.push_back(tr);
771     viewportRect.push_back(tl);
772 
773     c2t::clip2tri clipper;
774     clipper.clearClipper();
775     clipper.addSubjectPath(QClipperUtils::qListToPath(mapRect), true);
776     clipper.addClipPolygon(QClipperUtils::qListToPath(viewportRect));
777 
778     Paths res = clipper.execute(c2t::clip2tri::Intersection);
779     m_visibleRegion.clear();
780     if (res.size())
781         m_visibleRegion = QClipperUtils::pathToQList(res[0]); // Intersection between two convex quadrilaterals should always be a single polygon
782 
783     m_projectableRegion.clear();
784     mapRect.clear();
785     // The full map rectangle in extended mercator space
786     mapRect.push_back(QDoubleVector2D(-1.0, 1.0));
787     mapRect.push_back(QDoubleVector2D( 2.0, 1.0));
788     mapRect.push_back(QDoubleVector2D( 2.0, 0.0));
789     mapRect.push_back(QDoubleVector2D(-1.0, 0.0));
790     if (m_cameraData.tilt() == 0) {
791         m_projectableRegion = mapRect;
792     } else {
793         QGeoProjectionWebMercator::Plane nearPlane(m_centerNearPlaneMercator, m_viewNormalized);
794         Line2D nearPlaneXYIntersection = nearPlane.planeXYIntersection();
795         double squareHalfSide = qMax(5.0, nearPlaneXYIntersection.m_point.length());
796         QDoubleVector2D viewDirectionProjected = -m_viewNormalized.toVector2D().normalized();
797 
798 
799         QDoubleVector2D tl = nearPlaneXYIntersection.m_point
800                             - squareHalfSide * nearPlaneXYIntersection.m_direction
801                             + 2 * squareHalfSide * viewDirectionProjected;
802         QDoubleVector2D tr = nearPlaneXYIntersection.m_point
803                             + squareHalfSide * nearPlaneXYIntersection.m_direction
804                             + 2 * squareHalfSide * viewDirectionProjected;
805         QDoubleVector2D bl = nearPlaneXYIntersection.m_point
806                             - squareHalfSide * nearPlaneXYIntersection.m_direction;
807         QDoubleVector2D br = nearPlaneXYIntersection.m_point
808                             + squareHalfSide * nearPlaneXYIntersection.m_direction;
809 
810         QList<QDoubleVector2D> projectableRect;
811         projectableRect.push_back(bl);
812         projectableRect.push_back(br);
813         projectableRect.push_back(tr);
814         projectableRect.push_back(tl);
815 
816 
817         c2t::clip2tri clipperProjectable;
818         clipperProjectable.clearClipper();
819         clipperProjectable.addSubjectPath(QClipperUtils::qListToPath(mapRect), true);
820         clipperProjectable.addClipPolygon(QClipperUtils::qListToPath(projectableRect));
821 
822         Paths resProjectable = clipperProjectable.execute(c2t::clip2tri::Intersection);
823         if (resProjectable.size())
824             m_projectableRegion = QClipperUtils::pathToQList(resProjectable[0]); // Intersection between two convex quadrilaterals should always be a single polygon
825         else
826             m_projectableRegion = viewportRect;
827     }
828 
829     // Compute m_visibleRegionExpanded as a clipped expanded version of m_visibleRegion
830     QDoubleVector2D centroid;
831     for (const QDoubleVector2D &v: qAsConst(m_visibleRegion))
832         centroid += v;
833     centroid /= m_visibleRegion.size();
834 
835     m_visibleRegionExpanded.clear();
836     for (const QDoubleVector2D &v: qAsConst(m_visibleRegion)) {
837         const QDoubleVector2D vc = v - centroid;
838         m_visibleRegionExpanded.push_back(centroid + vc * 1.2); // fixing expansion factor to 1.2
839     }
840 
841     c2t::clip2tri clipperExpanded;
842     clipperExpanded.clearClipper();
843     clipperExpanded.addSubjectPath(QClipperUtils::qListToPath(m_visibleRegionExpanded), true);
844     clipperExpanded.addClipPolygon(QClipperUtils::qListToPath(m_projectableRegion));
845     Paths resVisibleExpanded = clipperExpanded.execute(c2t::clip2tri::Intersection);
846     if (resVisibleExpanded.size())
847         m_visibleRegionExpanded = QClipperUtils::pathToQList(resVisibleExpanded[0]); // Intersection between two convex quadrilaterals should always be a single polygon
848     else
849         m_visibleRegionExpanded = m_visibleRegion;
850 }
851 
cameraData() const852 QGeoCameraData QGeoProjectionWebMercator::cameraData() const
853 {
854     return m_cameraData;
855 }
856 
857 /*
858  *
859  *  Line implementation
860  *
861  */
862 
Line2D()863 QGeoProjectionWebMercator::Line2D::Line2D()
864 {
865 
866 }
867 
Line2D(const QDoubleVector2D & linePoint,const QDoubleVector2D & lineDirection)868 QGeoProjectionWebMercator::Line2D::Line2D(const QDoubleVector2D &linePoint, const QDoubleVector2D &lineDirection)
869     :   m_point(linePoint), m_direction(lineDirection.normalized())
870 {
871 
872 }
873 
isValid() const874 bool QGeoProjectionWebMercator::Line2D::isValid() const
875 {
876     return (m_direction.length() > 0.5);
877 }
878 
879 /*
880  *
881  *  Plane implementation
882  *
883  */
884 
Plane()885 QGeoProjectionWebMercator::Plane::Plane()
886 {
887 
888 }
889 
Plane(const QDoubleVector3D & planePoint,const QDoubleVector3D & planeNormal)890 QGeoProjectionWebMercator::Plane::Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal)
891     :   m_point(planePoint), m_normal(planeNormal.normalized()) { }
892 
lineIntersection(const QDoubleVector3D & linePoint,const QDoubleVector3D & lineDirection) const893 QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const
894 {
895     double s;
896     return lineIntersection(linePoint, lineDirection, s);
897 }
898 
lineIntersection(const QDoubleVector3D & linePoint,const QDoubleVector3D & lineDirection,double & s) const899 QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection, double &s) const
900 {
901     QDoubleVector3D w = linePoint - m_point;
902     // s = -n.dot(w) / n.dot(u).  p = p0 + su; u is lineDirection
903     s = QDoubleVector3D::dotProduct(-m_normal, w) / QDoubleVector3D::dotProduct(m_normal, lineDirection);
904     return linePoint + lineDirection * s;
905 }
906 
planeXYIntersection() const907 QGeoProjectionWebMercator::Line2D QGeoProjectionWebMercator::Plane::planeXYIntersection() const
908 {
909     // cross product of the two normals for the line direction
910     QDoubleVector3D lineDirection = QDoubleVector3D::crossProduct(m_normal, xyNormal);
911     lineDirection.setZ(0.0);
912     lineDirection.normalize();
913 
914     // cross product of the line direction and the plane normal to find the direction on the plane
915     // intersecting the xy plane
916     QDoubleVector3D directionToXY = QDoubleVector3D::crossProduct(m_normal, lineDirection);
917     QDoubleVector3D p = xyPlane.lineIntersection(m_point, directionToXY);
918     return Line2D(p.toVector2D(), lineDirection.toVector2D());
919 }
920 
isValid() const921 bool QGeoProjectionWebMercator::Plane::isValid() const
922 {
923     return (m_normal.length() > 0.5);
924 }
925 
926 QT_END_NAMESPACE
927