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