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