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
47namespace {
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
58static 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
66static 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
81static QPointF marginsOffset(const QSizeF &screenSize, const QRectF &visibleArea)
82{
83 QPointF diff = centerOffset(screenSize, visibleArea);
84 qreal xdiffpct = diff.x() / qMax<double>(a: screenSize.width() - 1, b: 1);
85 qreal ydiffpct = diff.y() / qMax<double>(a: screenSize.height() - 1, b: 1);
86
87 return QPointF(-xdiffpct, -ydiffpct);
88}
89
90QT_BEGIN_NAMESPACE
91
92QGeoProjection::QGeoProjection()
93{
94
95}
96
97QGeoProjection::~QGeoProjection()
98{
99
100}
101
102QGeoCoordinate QGeoProjection::anchorCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &anchorPoint) const
103{
104 Q_UNUSED(coordinate);
105 Q_UNUSED(anchorPoint);
106 return QGeoCoordinate();
107}
108
109QGeoShape QGeoProjection::visibleRegion() const
110{
111 return QGeoShape();
112}
113
114bool QGeoProjection::setBearing(qreal bearing, const QGeoCoordinate &coordinate)
115{
116 Q_UNUSED(bearing);
117 Q_UNUSED(coordinate);
118 return false;
119}
120
121void QGeoProjection::setItemToWindowTransform(const QTransform &itemToWindowTransform)
122{
123 if (m_itemToWindowTransform == itemToWindowTransform)
124 return;
125 m_qsgTransformDirty = true;
126 m_itemToWindowTransform = itemToWindowTransform;
127}
128
129QTransform QGeoProjection::itemToWindowTransform() const
130{
131 return m_itemToWindowTransform;
132}
133
134
135/*
136 * QGeoProjectionWebMercator implementation
137*/
138
139QGeoCoordinate 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(coordinate: cameraData().center());
143 QDoubleVector2D coordProj = geoToWrappedMapProjection(coordinate);
144
145 QDoubleVector2D anchorProj = itemPositionToWrappedMapProjection(itemPosition: QDoubleVector2D(anchorPoint));
146 // Y-clamping done in mercatorToCoord
147 return wrappedMapProjectionToGeo(wrappedProjection: centerProj + coordProj - anchorProj);
148}
149
150bool QGeoProjectionWebMercator::setBearing(qreal bearing, const QGeoCoordinate &coordinate)
151{
152 const QDoubleVector2D coordWrapped = geoToWrappedMapProjection(coordinate);
153 if (!isProjectable(wrappedProjection: coordWrapped))
154 return false;
155 const QPointF rotationPoint = wrappedMapProjectionToItemPosition(wrappedProjection: coordWrapped).toPointF();
156
157 QGeoCameraData camera = cameraData();
158 // first set bearing
159 camera.setBearing(bearing);
160 setCameraData(cameraData: camera);
161 camera = cameraData();
162
163 // then reanchor
164 const QGeoCoordinate center = anchorCoordinateToPoint(coordinate, anchorPoint: rotationPoint);
165 camera.setCenter(center);
166 setCameraData(cameraData: camera);
167 return true;
168}
169
170QGeoProjectionWebMercator::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
192QGeoProjectionWebMercator::~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.
199double QGeoProjectionWebMercator::minimumZoom() const
200{
201 return m_minimumZoom;
202}
203
204QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation() const
205{
206 return toMatrix4x4(m: m_transformation);
207}
208
209QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation_centered() const
210{
211 return toMatrix4x4(m: m_transformation0);
212}
213
214const QMatrix4x4 &QGeoProjectionWebMercator::qsgTransform() const
215{
216 if (m_qsgTransformDirty) {
217 m_qsgTransformDirty = false;
218 m_qsgTransform = QMatrix4x4(m_itemToWindowTransform) * toMatrix4x4(m: m_transformation0);
219// qDebug() << "QGeoProjectionWebMercator::qsgTransform" << m_itemToWindowTransform << toMatrix4x4(m_transformation0);
220 }
221 return m_qsgTransform;
222}
223
224QDoubleVector3D QGeoProjectionWebMercator::centerMercator() const
225{
226 return geoToMapProjection(coordinate: 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
235double QGeoProjectionWebMercator::maximumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const
236{
237 double mapEdgeSize = std::pow(x: 2.0, y: cameraData.zoomLevel()) * defaultTileSize;
238
239 // At init time weird things happen
240 int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight;
241 QPointF offsetPct = centerOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea);
242 double hpct = offsetPct.y() / qMax<double>(a: m_viewportHeight - 1, b: 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(mercator: QDoubleVector2D(0.0, mercatorTopmost));
247 return topMost.latitude();
248}
249
250double QGeoProjectionWebMercator::minimumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const
251{
252 double mapEdgeSize = std::pow(x: 2.0, y: cameraData.zoomLevel()) * defaultTileSize;
253
254 // At init time weird things happen
255 int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight;
256 QPointF offsetPct = centerOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea);
257 double hpct = offsetPct.y() / qMax<double>(a: m_viewportHeight - 1, b: 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(mercator: QDoubleVector2D(0.0, mercatorTopmost));
262 return -topMost.latitude();
263}
264
265void QGeoProjectionWebMercator::setVisibleArea(const QRectF &visibleArea)
266{
267 m_visibleArea = visibleArea;
268 setupCamera();
269}
270
271double QGeoProjectionWebMercator::mapWidth() const
272{
273 return m_mapEdgeSize;
274}
275
276double QGeoProjectionWebMercator::mapHeight() const
277{
278 return m_mapEdgeSize;
279}
280
281void 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(x: qMax(a: m_viewportWidth, b: m_viewportHeight) / defaultTileSize) / std::log(x: 2.0);
291 setupCamera();
292}
293
294void 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(x: 2.0, y: cameraData.zoomLevel()) * defaultTileSize;
301 setupCamera();
302}
303
304QDoubleVector2D QGeoProjectionWebMercator::geoToMapProjection(const QGeoCoordinate &coordinate) const
305{
306 return QWebMercator::coordToMercator(coord: coordinate);
307}
308
309QGeoCoordinate QGeoProjectionWebMercator::mapProjectionToGeo(const QDoubleVector2D &projection) const
310{
311 return QWebMercator::mercatorToCoord(mercator: projection);
312}
313
314int 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
328QDoubleVector2D QGeoProjectionWebMercator::wrapMapProjection(const QDoubleVector2D &projection) const
329{
330 return QDoubleVector2D(projection.x() + double(projectionWrapFactor(projection)), projection.y());
331}
332
333QDoubleVector2D 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
343QDoubleVector2D QGeoProjectionWebMercator::wrappedMapProjectionToItemPosition(const QDoubleVector2D &wrappedProjection) const
344{
345 return (m_transformation * wrappedProjection).toVector2D();
346}
347
348QDoubleVector2D QGeoProjectionWebMercator::itemPositionToWrappedMapProjection(const QDoubleVector2D &itemPosition) const
349{
350 const QPointF centerOff = centerOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: 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(itemPosition: 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(itemPosition: pos, s);
368 }
369
370 return res;
371}
372
373/* Default implementations */
374QGeoCoordinate QGeoProjectionWebMercator::itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport) const
375{
376 if (qIsNaN(d: pos.x()) || qIsNaN(d: 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(itemPosition: pos);
388 // With rotation/tilting, a screen position might end up outside the projection space.
389 if (!isProjectable(wrappedProjection: wrappedMapProjection))
390 return QGeoCoordinate();
391 return mapProjectionToGeo(projection: unwrapMapProjection(wrappedProjection: wrappedMapProjection));
392}
393
394QDoubleVector2D QGeoProjectionWebMercator::coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport) const
395{
396 if (!coordinate.isValid())
397 return QDoubleVector2D(qQNaN(), qQNaN());
398
399 QDoubleVector2D wrappedProjection = wrapMapProjection(projection: 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(d: x) || qIsNaN(d: y))
411 return QDoubleVector2D(qQNaN(), qQNaN());
412 }
413 return pos;
414}
415
416QDoubleVector2D QGeoProjectionWebMercator::geoToWrappedMapProjection(const QGeoCoordinate &coordinate) const
417{
418 return wrapMapProjection(projection: geoToMapProjection(coordinate));
419}
420
421QGeoCoordinate QGeoProjectionWebMercator::wrappedMapProjectionToGeo(const QDoubleVector2D &wrappedProjection) const
422{
423 return mapProjectionToGeo(projection: unwrapMapProjection(wrappedProjection));
424}
425
426QMatrix4x4 QGeoProjectionWebMercator::quickItemTransformation(const QGeoCoordinate &coordinate, const QPointF &anchorPoint, qreal zoomLevel) const
427{
428 const QDoubleVector2D coordWrapped = geoToWrappedMapProjection(coordinate);
429 double scale = std::pow(x: 0.5, y: 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(x: coordAnchoredScaled.x(), y: coordAnchoredScaled.y(), z: 0.0);
437
438 scale = std::pow(x: 0.5, y: (zoomLevel - std::floor(x: zoomLevel)) +
439 (std::floor(x: zoomLevel) - std::floor(x: m_cameraData.zoomLevel())));
440 matTranslateScale.scale(factor: 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: m_quickItemTransformation * matTranslateScale);
454}
455
456bool 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(v1: p , v2: m_viewNormalized);
465
466 if (dot < 0.0) // behind the near plane
467 return false;
468 return true;
469}
470
471QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleGeometry() const
472{
473 if (m_visibleRegionDirty)
474 const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion();
475 return m_visibleRegion;
476}
477
478QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleGeometryExpanded() const
479{
480 if (m_visibleRegionDirty)
481 const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion();
482 return m_visibleRegionExpanded;
483}
484
485QList<QDoubleVector2D> QGeoProjectionWebMercator::projectableGeometry() const
486{
487 if (m_visibleRegionDirty)
488 const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion();
489 return m_projectableRegion;
490}
491
492QGeoShape 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(t: visibleRegion.at(i: i-1).x() - c.x()) >= 0.5) { // This assumes a segment is never >= 1.0 (whole map span)
500 QDoubleVector2D extraPoint = (visibleRegion.at(i: i-1) + c) * 0.5;
501 poly.addCoordinate(coordinate: wrappedMapProjectionToGeo(wrappedProjection: extraPoint));
502 }
503 poly.addCoordinate(coordinate: wrappedMapProjectionToGeo(wrappedProjection: c));
504 }
505 if (visibleRegion.size() >= 2 && qAbs(t: visibleRegion.last().x() - visibleRegion.first().x()) >= 0.5) {
506 QDoubleVector2D extraPoint = (visibleRegion.last() + visibleRegion.first()) * 0.5;
507 poly.addCoordinate(coordinate: wrappedMapProjectionToGeo(wrappedProjection: extraPoint));
508 }
509
510 return poly;
511}
512
513QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const
514{
515 double s;
516 return viewportToWrappedMapProjection(itemPosition, s);
517}
518
519/*
520 actual implementation of itemPositionToWrappedMapProjection
521*/
522QDoubleVector2D 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(linePoint: m_eye, lineDirection: ray, s) / m_sideLengthPixels).toVector2D();
537}
538
539/*
540 Returns a pair of <newCenter, newZoom>
541*/
542QPair<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(coordinate: rectangle.topLeft());
551 QDoubleVector2D bottomRightPoint = geoToMapProjection(coordinate: 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(projection: 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(a: bboxWidth / (m_viewportWidth - m.left() - m.right()),
568 b: bboxHeight / (m_viewportHeight - m.top() - m.bottom()));
569 zoomRatio = std::log(x: zoomRatio) / std::log(x: 2.0);
570 res.second = m_cameraData.zoomLevel() - zoomRatio;
571
572 return res;
573}
574
575QGeoProjection::ProjectionGroup QGeoProjectionWebMercator::projectionGroup() const
576{
577 return QGeoProjection::ProjectionCylindrical;
578}
579
580QGeoProjection::Datum QGeoProjectionWebMercator::datum() const
581{
582 return QGeoProjection::DatumWGS84;
583}
584
585QGeoProjection::ProjectionType QGeoProjectionWebMercator::projectionType() const
586{
587 return QGeoProjection::ProjectionWebMercator;
588}
589
590void QGeoProjectionWebMercator::setupCamera()
591{
592 m_qsgTransformDirty = true;
593 m_centerMercator = geoToMapProjection(coordinate: m_cameraData.center());
594 m_cameraCenterXMercator = m_centerMercator.x();
595 m_cameraCenterYMercator = m_centerMercator.y();
596
597 int intZoomLevel = static_cast<int>(std::floor(x: 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(x: QLocationUtils::radians(degrees: m_cameraData.fieldOfView()) * 0.5);
602
603 double f = m_viewportHeight;
604 double z = std::pow(x: 2.0, y: m_cameraData.zoomLevel() - intZoomLevel) * defaultTileSize;
605 double altitude = f / (2.0 * z);
606 // Also in mercator space
607 double z_mercator = std::pow(x: 2.0, y: 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(v1: m_view, v2: QDoubleVector3D(0.0, 1.0, 0.0));
624 m_up = QDoubleVector3D::normal(v1: side, v2: m_view);
625
626 // In mercator space too
627 m_viewMercator = m_eyeMercator - m_centerMercator;
628 QDoubleVector3D sideMercator = QDoubleVector3D::normal(v1: m_viewMercator, v2: QDoubleVector3D(0.0, 1.0, 0.0));
629 m_upMercator = QDoubleVector3D::normal(v1: sideMercator, v2: m_viewMercator);
630
631 if (m_cameraData.bearing() > 0.0) {
632 QDoubleMatrix4x4 mBearing;
633 mBearing.rotate(angle: m_cameraData.bearing(), vector: m_view);
634 m_up = mBearing * m_up;
635
636 // In mercator space too
637 QDoubleMatrix4x4 mBearingMercator;
638 mBearingMercator.rotate(angle: m_cameraData.bearing(), vector: m_viewMercator);
639 m_upMercator = mBearingMercator * m_upMercator;
640 }
641
642 m_side = QDoubleVector3D::normal(v1: m_up, v2: m_view);
643 m_sideMercator = QDoubleVector3D::normal(v1: m_upMercator, v2: m_viewMercator);
644
645 if (m_cameraData.tilt() > 0.0) { // tilt has been already thresholded by QGeoCameraData::setTilt
646 QDoubleMatrix4x4 mTilt;
647 mTilt.rotate(angle: -m_cameraData.tilt(), vector: 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(angle: -m_cameraData.tilt(), vector: 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(v1: m_view, v2: 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(v1: m_viewMercator, v2: 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(radians: atan(x: m_aperture));
685
686 m_cameraMatrix.setToIdentity();
687 m_cameraMatrix.lookAt(eye: m_eye, center: m_center, up: m_up);
688 m_cameraMatrix0.setToIdentity();
689 m_cameraMatrix0.lookAt(eye: eye0, center: QDoubleVector3D(0,0,0), up: m_up);
690
691 QDoubleMatrix4x4 projectionMatrix;
692 projectionMatrix.frustum(left: -m_halfWidth, right: m_halfWidth, bottom: -m_halfHeight, top: m_halfHeight, nearPlane: m_nearPlane, farPlane: 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(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea);
705 QDoubleMatrix4x4 matScreenTransformation;
706 matScreenTransformation.scale(x: 0.5 * m_viewportWidth, y: 0.5 * m_viewportHeight, z: 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(x: m_sideLengthPixels, y: m_sideLengthPixels, z: 1.0);
713
714 m_transformation0 = matScreenTransformation * projectionMatrix * m_cameraMatrix0;
715 m_transformation0.scale(x: m_sideLengthPixels, y: m_sideLengthPixels, z: 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(x: 10, y: 1.0 + m_cameraData.zoomLevel() / 5.0);
725 const double elevationUpperBound = 90.0 - upperBoundEpsilon;
726 const double maxRayElevation = qMin(a: elevationUpperBound - m_cameraData.tilt(), b: verticalHalfFOV);
727 double maxHalfAperture = 0;
728 m_verticalEstateToSkip = 0;
729 if (maxRayElevation < verticalHalfFOV) {
730 maxHalfAperture = tan(x: QLocationUtils::radians(degrees: 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
738void 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>(a: -viewportHalfHeight, b: -1 + m_verticalEstateToSkip);
746 double bottom = viewportHalfHeight;
747 double left = -viewportHalfWidth;
748 double right = viewportHalfWidth;
749
750 QDoubleVector2D tl = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(left, top ));
751 QDoubleVector2D tr = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(right, top ));
752 QDoubleVector2D bl = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(left, bottom ));
753 QDoubleVector2D br = viewportToWrappedMapProjection(itemPosition: 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(centerLongitude: m_cameraData.center().longitude());
757 double mapRightLongitude = QLocationUtils::mapRightLongitude(centerLongitude: m_cameraData.center().longitude());
758 double leftX = geoToWrappedMapProjection(coordinate: QGeoCoordinate(0, mapLeftLongitude)).x();
759 double rightX = geoToWrappedMapProjection(coordinate: QGeoCoordinate(0, mapRightLongitude)).x();
760
761 QList<QDoubleVector2D> mapRect;
762 mapRect.push_back(t: QDoubleVector2D(leftX, 1.0));
763 mapRect.push_back(t: QDoubleVector2D(rightX, 1.0));
764 mapRect.push_back(t: QDoubleVector2D(rightX, 0.0));
765 mapRect.push_back(t: QDoubleVector2D(leftX, 0.0));
766
767 QList<QDoubleVector2D> viewportRect;
768 viewportRect.push_back(t: bl);
769 viewportRect.push_back(t: br);
770 viewportRect.push_back(t: tr);
771 viewportRect.push_back(t: tl);
772
773 c2t::clip2tri clipper;
774 clipper.clearClipper();
775 clipper.addSubjectPath(path: QClipperUtils::qListToPath(list: mapRect), closed: true);
776 clipper.addClipPolygon(path: QClipperUtils::qListToPath(list: viewportRect));
777
778 Paths res = clipper.execute(op: c2t::clip2tri::Intersection);
779 m_visibleRegion.clear();
780 if (res.size())
781 m_visibleRegion = QClipperUtils::pathToQList(path: 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(t: QDoubleVector2D(-1.0, 1.0));
787 mapRect.push_back(t: QDoubleVector2D( 2.0, 1.0));
788 mapRect.push_back(t: QDoubleVector2D( 2.0, 0.0));
789 mapRect.push_back(t: 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(a: 5.0, b: 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(t: bl);
812 projectableRect.push_back(t: br);
813 projectableRect.push_back(t: tr);
814 projectableRect.push_back(t: tl);
815
816
817 c2t::clip2tri clipperProjectable;
818 clipperProjectable.clearClipper();
819 clipperProjectable.addSubjectPath(path: QClipperUtils::qListToPath(list: mapRect), closed: true);
820 clipperProjectable.addClipPolygon(path: QClipperUtils::qListToPath(list: projectableRect));
821
822 Paths resProjectable = clipperProjectable.execute(op: c2t::clip2tri::Intersection);
823 if (resProjectable.size())
824 m_projectableRegion = QClipperUtils::pathToQList(path: 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(t&: m_visibleRegion))
832 centroid += v;
833 centroid /= m_visibleRegion.size();
834
835 m_visibleRegionExpanded.clear();
836 for (const QDoubleVector2D &v: qAsConst(t&: m_visibleRegion)) {
837 const QDoubleVector2D vc = v - centroid;
838 m_visibleRegionExpanded.push_back(t: centroid + vc * 1.2); // fixing expansion factor to 1.2
839 }
840
841 c2t::clip2tri clipperExpanded;
842 clipperExpanded.clearClipper();
843 clipperExpanded.addSubjectPath(path: QClipperUtils::qListToPath(list: m_visibleRegionExpanded), closed: true);
844 clipperExpanded.addClipPolygon(path: QClipperUtils::qListToPath(list: m_projectableRegion));
845 Paths resVisibleExpanded = clipperExpanded.execute(op: c2t::clip2tri::Intersection);
846 if (resVisibleExpanded.size())
847 m_visibleRegionExpanded = QClipperUtils::pathToQList(path: resVisibleExpanded[0]); // Intersection between two convex quadrilaterals should always be a single polygon
848 else
849 m_visibleRegionExpanded = m_visibleRegion;
850}
851
852QGeoCameraData QGeoProjectionWebMercator::cameraData() const
853{
854 return m_cameraData;
855}
856
857/*
858 *
859 * Line implementation
860 *
861 */
862
863QGeoProjectionWebMercator::Line2D::Line2D()
864{
865
866}
867
868QGeoProjectionWebMercator::Line2D::Line2D(const QDoubleVector2D &linePoint, const QDoubleVector2D &lineDirection)
869 : m_point(linePoint), m_direction(lineDirection.normalized())
870{
871
872}
873
874bool QGeoProjectionWebMercator::Line2D::isValid() const
875{
876 return (m_direction.length() > 0.5);
877}
878
879/*
880 *
881 * Plane implementation
882 *
883 */
884
885QGeoProjectionWebMercator::Plane::Plane()
886{
887
888}
889
890QGeoProjectionWebMercator::Plane::Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal)
891 : m_point(planePoint), m_normal(planeNormal.normalized()) { }
892
893QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const
894{
895 double s;
896 return lineIntersection(linePoint, lineDirection, s);
897}
898
899QDoubleVector3D 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(v1: -m_normal, v2: w) / QDoubleVector3D::dotProduct(v1: m_normal, v2: lineDirection);
904 return linePoint + lineDirection * s;
905}
906
907QGeoProjectionWebMercator::Line2D QGeoProjectionWebMercator::Plane::planeXYIntersection() const
908{
909 // cross product of the two normals for the line direction
910 QDoubleVector3D lineDirection = QDoubleVector3D::crossProduct(v1: m_normal, v2: 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(v1: m_normal, v2: lineDirection);
917 QDoubleVector3D p = xyPlane.lineIntersection(linePoint: m_point, lineDirection: directionToXY);
918 return Line2D(p.toVector2D(), lineDirection.toVector2D());
919}
920
921bool QGeoProjectionWebMercator::Plane::isValid() const
922{
923 return (m_normal.length() > 0.5);
924}
925
926QT_END_NAMESPACE
927

source code of qtlocation/src/location/maps/qgeoprojection.cpp