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