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 | |
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 | |
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 | |
81 | static QPointF marginsOffset(const QSizeF &screenSize, const QRectF &visibleArea) |
82 | { |
83 | QPointF diff = centerOffset(screenSize, visibleArea); |
84 | qreal xdiffpct = diff.x() / qMax<double>(a: screenSize.width() - 1, b: 1); |
85 | qreal ydiffpct = diff.y() / qMax<double>(a: screenSize.height() - 1, b: 1); |
86 | |
87 | return QPointF(-xdiffpct, -ydiffpct); |
88 | } |
89 | |
90 | QT_BEGIN_NAMESPACE |
91 | |
92 | QGeoProjection::QGeoProjection() |
93 | { |
94 | |
95 | } |
96 | |
97 | QGeoProjection::~QGeoProjection() |
98 | { |
99 | |
100 | } |
101 | |
102 | QGeoCoordinate QGeoProjection::anchorCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &anchorPoint) const |
103 | { |
104 | Q_UNUSED(coordinate); |
105 | Q_UNUSED(anchorPoint); |
106 | return QGeoCoordinate(); |
107 | } |
108 | |
109 | QGeoShape QGeoProjection::visibleRegion() const |
110 | { |
111 | return QGeoShape(); |
112 | } |
113 | |
114 | bool QGeoProjection::setBearing(qreal bearing, const QGeoCoordinate &coordinate) |
115 | { |
116 | Q_UNUSED(bearing); |
117 | Q_UNUSED(coordinate); |
118 | return false; |
119 | } |
120 | |
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 | |
129 | QTransform QGeoProjection::itemToWindowTransform() const |
130 | { |
131 | return m_itemToWindowTransform; |
132 | } |
133 | |
134 | |
135 | /* |
136 | * QGeoProjectionWebMercator implementation |
137 | */ |
138 | |
139 | 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(coordinate: cameraData().center()); |
143 | QDoubleVector2D coordProj = geoToWrappedMapProjection(coordinate); |
144 | |
145 | QDoubleVector2D anchorProj = itemPositionToWrappedMapProjection(itemPosition: QDoubleVector2D(anchorPoint)); |
146 | // Y-clamping done in mercatorToCoord |
147 | return wrappedMapProjectionToGeo(wrappedProjection: centerProj + coordProj - anchorProj); |
148 | } |
149 | |
150 | bool QGeoProjectionWebMercator::setBearing(qreal bearing, const QGeoCoordinate &coordinate) |
151 | { |
152 | const QDoubleVector2D coordWrapped = geoToWrappedMapProjection(coordinate); |
153 | if (!isProjectable(wrappedProjection: coordWrapped)) |
154 | return false; |
155 | const QPointF rotationPoint = wrappedMapProjectionToItemPosition(wrappedProjection: coordWrapped).toPointF(); |
156 | |
157 | QGeoCameraData camera = cameraData(); |
158 | // first set bearing |
159 | camera.setBearing(bearing); |
160 | setCameraData(cameraData: camera); |
161 | camera = cameraData(); |
162 | |
163 | // then reanchor |
164 | const QGeoCoordinate center = anchorCoordinateToPoint(coordinate, anchorPoint: rotationPoint); |
165 | camera.setCenter(center); |
166 | setCameraData(cameraData: camera); |
167 | return true; |
168 | } |
169 | |
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 | |
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. |
199 | double QGeoProjectionWebMercator::minimumZoom() const |
200 | { |
201 | return m_minimumZoom; |
202 | } |
203 | |
204 | QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation() const |
205 | { |
206 | return toMatrix4x4(m: m_transformation); |
207 | } |
208 | |
209 | QMatrix4x4 QGeoProjectionWebMercator::projectionTransformation_centered() const |
210 | { |
211 | return toMatrix4x4(m: m_transformation0); |
212 | } |
213 | |
214 | const QMatrix4x4 &QGeoProjectionWebMercator::qsgTransform() const |
215 | { |
216 | if (m_qsgTransformDirty) { |
217 | m_qsgTransformDirty = false; |
218 | m_qsgTransform = QMatrix4x4(m_itemToWindowTransform) * toMatrix4x4(m: m_transformation0); |
219 | // qDebug() << "QGeoProjectionWebMercator::qsgTransform" << m_itemToWindowTransform << toMatrix4x4(m_transformation0); |
220 | } |
221 | return m_qsgTransform; |
222 | } |
223 | |
224 | QDoubleVector3D QGeoProjectionWebMercator::centerMercator() const |
225 | { |
226 | return geoToMapProjection(coordinate: m_cameraData.center()).toVector3D(); |
227 | } |
228 | |
229 | // This method recalculates the "no-trespassing" limits for the map center. |
230 | // This has to be used when: |
231 | // 1) the map is resized, because the meters per pixel remain the same, but |
232 | // the amount of pixels between the center and the borders changes |
233 | // 2) when the zoom level changes, because the amount of pixels between the center |
234 | // and the borders stays the same, but the meters per pixel change |
235 | double QGeoProjectionWebMercator::maximumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const |
236 | { |
237 | double mapEdgeSize = std::pow(x: 2.0, y: cameraData.zoomLevel()) * defaultTileSize; |
238 | |
239 | // At init time weird things happen |
240 | int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight; |
241 | QPointF offsetPct = centerOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea); |
242 | double hpct = offsetPct.y() / qMax<double>(a: m_viewportHeight - 1, b: 1); |
243 | |
244 | // Use the window height divided by 2 as the topmost allowed center, with respect to the map size in pixels |
245 | double mercatorTopmost = (clampedWindowHeight * (0.5 - hpct)) / mapEdgeSize ; |
246 | QGeoCoordinate topMost = QWebMercator::mercatorToCoord(mercator: QDoubleVector2D(0.0, mercatorTopmost)); |
247 | return topMost.latitude(); |
248 | } |
249 | |
250 | double QGeoProjectionWebMercator::minimumCenterLatitudeAtZoom(const QGeoCameraData &cameraData) const |
251 | { |
252 | double mapEdgeSize = std::pow(x: 2.0, y: cameraData.zoomLevel()) * defaultTileSize; |
253 | |
254 | // At init time weird things happen |
255 | int clampedWindowHeight = (m_viewportHeight > mapEdgeSize) ? mapEdgeSize : m_viewportHeight; |
256 | QPointF offsetPct = centerOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea); |
257 | double hpct = offsetPct.y() / qMax<double>(a: m_viewportHeight - 1, b: 1); |
258 | |
259 | // Use the window height divided by 2 as the topmost allowed center, with respect to the map size in pixels |
260 | double mercatorTopmost = (clampedWindowHeight * (0.5 + hpct)) / mapEdgeSize ; |
261 | QGeoCoordinate topMost = QWebMercator::mercatorToCoord(mercator: QDoubleVector2D(0.0, mercatorTopmost)); |
262 | return -topMost.latitude(); |
263 | } |
264 | |
265 | void QGeoProjectionWebMercator::setVisibleArea(const QRectF &visibleArea) |
266 | { |
267 | m_visibleArea = visibleArea; |
268 | setupCamera(); |
269 | } |
270 | |
271 | double QGeoProjectionWebMercator::mapWidth() const |
272 | { |
273 | return m_mapEdgeSize; |
274 | } |
275 | |
276 | double QGeoProjectionWebMercator::mapHeight() const |
277 | { |
278 | return m_mapEdgeSize; |
279 | } |
280 | |
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(x: qMax(a: m_viewportWidth, b: m_viewportHeight) / defaultTileSize) / std::log(x: 2.0); |
291 | setupCamera(); |
292 | } |
293 | |
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(x: 2.0, y: cameraData.zoomLevel()) * defaultTileSize; |
301 | setupCamera(); |
302 | } |
303 | |
304 | QDoubleVector2D QGeoProjectionWebMercator::geoToMapProjection(const QGeoCoordinate &coordinate) const |
305 | { |
306 | return QWebMercator::coordToMercator(coord: coordinate); |
307 | } |
308 | |
309 | QGeoCoordinate QGeoProjectionWebMercator::mapProjectionToGeo(const QDoubleVector2D &projection) const |
310 | { |
311 | return QWebMercator::mercatorToCoord(mercator: projection); |
312 | } |
313 | |
314 | 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 |
328 | QDoubleVector2D QGeoProjectionWebMercator::wrapMapProjection(const QDoubleVector2D &projection) const |
329 | { |
330 | return QDoubleVector2D(projection.x() + double(projectionWrapFactor(projection)), projection.y()); |
331 | } |
332 | |
333 | 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 | |
343 | QDoubleVector2D QGeoProjectionWebMercator::wrappedMapProjectionToItemPosition(const QDoubleVector2D &wrappedProjection) const |
344 | { |
345 | return (m_transformation * wrappedProjection).toVector2D(); |
346 | } |
347 | |
348 | QDoubleVector2D QGeoProjectionWebMercator::itemPositionToWrappedMapProjection(const QDoubleVector2D &itemPosition) const |
349 | { |
350 | const QPointF centerOff = centerOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea); |
351 | QDoubleVector2D pos = itemPosition + QDoubleVector2D(centerOff); |
352 | pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight); |
353 | pos *= 2.0; |
354 | pos -= QDoubleVector2D(1.0,1.0); |
355 | |
356 | double s; |
357 | QDoubleVector2D res = viewportToWrappedMapProjection(itemPosition: pos, s); |
358 | |
359 | // a positive s means a point behind the camera. So do it again, after clamping Y. See QTBUG-61813 |
360 | if (s > 0.0) { |
361 | pos = itemPosition; |
362 | // when the camera is tilted, picking a point above the horizon returns a coordinate behind the camera |
363 | pos.setY(m_minimumUnprojectableY); |
364 | pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight); |
365 | pos *= 2.0; |
366 | pos -= QDoubleVector2D(1.0,1.0); |
367 | res = viewportToWrappedMapProjection(itemPosition: pos, s); |
368 | } |
369 | |
370 | return res; |
371 | } |
372 | |
373 | /* Default implementations */ |
374 | QGeoCoordinate QGeoProjectionWebMercator::itemPositionToCoordinate(const QDoubleVector2D &pos, bool clipToViewport) const |
375 | { |
376 | if (qIsNaN(d: pos.x()) || qIsNaN(d: pos.y())) |
377 | return QGeoCoordinate(); |
378 | |
379 | if (clipToViewport) { |
380 | int w = m_viewportWidth; |
381 | int h = m_viewportHeight; |
382 | |
383 | if ((pos.x() < 0) || (w < pos.x()) || (pos.y() < 0) || (h < pos.y())) |
384 | return QGeoCoordinate(); |
385 | } |
386 | |
387 | QDoubleVector2D wrappedMapProjection = itemPositionToWrappedMapProjection(itemPosition: pos); |
388 | // With rotation/tilting, a screen position might end up outside the projection space. |
389 | if (!isProjectable(wrappedProjection: wrappedMapProjection)) |
390 | return QGeoCoordinate(); |
391 | return mapProjectionToGeo(projection: unwrapMapProjection(wrappedProjection: wrappedMapProjection)); |
392 | } |
393 | |
394 | QDoubleVector2D QGeoProjectionWebMercator::coordinateToItemPosition(const QGeoCoordinate &coordinate, bool clipToViewport) const |
395 | { |
396 | if (!coordinate.isValid()) |
397 | return QDoubleVector2D(qQNaN(), qQNaN()); |
398 | |
399 | QDoubleVector2D wrappedProjection = wrapMapProjection(projection: geoToMapProjection(coordinate)); |
400 | if (!isProjectable(wrappedProjection)) |
401 | return QDoubleVector2D(qQNaN(), qQNaN()); |
402 | |
403 | QDoubleVector2D pos = wrappedMapProjectionToItemPosition(wrappedProjection); |
404 | |
405 | if (clipToViewport) { |
406 | int w = m_viewportWidth; |
407 | int h = m_viewportHeight; |
408 | double x = pos.x(); |
409 | double y = pos.y(); |
410 | if ((x < -0.5) || (x > w + 0.5) || (y < -0.5) || (y > h + 0.5) || qIsNaN(d: x) || qIsNaN(d: y)) |
411 | return QDoubleVector2D(qQNaN(), qQNaN()); |
412 | } |
413 | return pos; |
414 | } |
415 | |
416 | QDoubleVector2D QGeoProjectionWebMercator::geoToWrappedMapProjection(const QGeoCoordinate &coordinate) const |
417 | { |
418 | return wrapMapProjection(projection: geoToMapProjection(coordinate)); |
419 | } |
420 | |
421 | QGeoCoordinate QGeoProjectionWebMercator::wrappedMapProjectionToGeo(const QDoubleVector2D &wrappedProjection) const |
422 | { |
423 | return mapProjectionToGeo(projection: unwrapMapProjection(wrappedProjection)); |
424 | } |
425 | |
426 | QMatrix4x4 QGeoProjectionWebMercator::quickItemTransformation(const QGeoCoordinate &coordinate, const QPointF &anchorPoint, qreal zoomLevel) const |
427 | { |
428 | const QDoubleVector2D coordWrapped = geoToWrappedMapProjection(coordinate); |
429 | double scale = std::pow(x: 0.5, y: zoomLevel - m_cameraData.zoomLevel()); |
430 | const QDoubleVector2D anchorScaled = QDoubleVector2D(anchorPoint.x(), anchorPoint.y()) * scale; |
431 | const QDoubleVector2D anchorMercator = anchorScaled / mapWidth(); |
432 | |
433 | const QDoubleVector2D coordAnchored = coordWrapped - anchorMercator; |
434 | const QDoubleVector2D coordAnchoredScaled = coordAnchored * m_sideLengthPixels; |
435 | QDoubleMatrix4x4 matTranslateScale; |
436 | matTranslateScale.translate(x: coordAnchoredScaled.x(), y: coordAnchoredScaled.y(), z: 0.0); |
437 | |
438 | scale = std::pow(x: 0.5, y: (zoomLevel - std::floor(x: zoomLevel)) + |
439 | (std::floor(x: zoomLevel) - std::floor(x: m_cameraData.zoomLevel()))); |
440 | matTranslateScale.scale(factor: scale); |
441 | |
442 | /* |
443 | * The full transformation chain for quickItemTransformation() would be: |
444 | * matScreenShift * m_quickItemTransformation * matTranslate * matScale |
445 | * where: |
446 | * matScreenShift = translate(-coordOnScreen.x(), -coordOnScreen.y(), 0) |
447 | * matTranslate = translate(coordAnchoredScaled.x(), coordAnchoredScaled.y(), 0.0) |
448 | * matScale = scale(scale) |
449 | * |
450 | * However, matScreenShift is removed, as setPosition(0,0) is used in place of setPositionOnScreen. |
451 | */ |
452 | |
453 | return toMatrix4x4(m: m_quickItemTransformation * matTranslateScale); |
454 | } |
455 | |
456 | 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(v1: p , v2: m_viewNormalized); |
465 | |
466 | if (dot < 0.0) // behind the near plane |
467 | return false; |
468 | return true; |
469 | } |
470 | |
471 | QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleGeometry() const |
472 | { |
473 | if (m_visibleRegionDirty) |
474 | const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion(); |
475 | return m_visibleRegion; |
476 | } |
477 | |
478 | QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleGeometryExpanded() const |
479 | { |
480 | if (m_visibleRegionDirty) |
481 | const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion(); |
482 | return m_visibleRegionExpanded; |
483 | } |
484 | |
485 | QList<QDoubleVector2D> QGeoProjectionWebMercator::projectableGeometry() const |
486 | { |
487 | if (m_visibleRegionDirty) |
488 | const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion(); |
489 | return m_projectableRegion; |
490 | } |
491 | |
492 | 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(t: visibleRegion.at(i: i-1).x() - c.x()) >= 0.5) { // This assumes a segment is never >= 1.0 (whole map span) |
500 | QDoubleVector2D = (visibleRegion.at(i: i-1) + c) * 0.5; |
501 | poly.addCoordinate(coordinate: wrappedMapProjectionToGeo(wrappedProjection: extraPoint)); |
502 | } |
503 | poly.addCoordinate(coordinate: wrappedMapProjectionToGeo(wrappedProjection: c)); |
504 | } |
505 | if (visibleRegion.size() >= 2 && qAbs(t: visibleRegion.last().x() - visibleRegion.first().x()) >= 0.5) { |
506 | QDoubleVector2D = (visibleRegion.last() + visibleRegion.first()) * 0.5; |
507 | poly.addCoordinate(coordinate: wrappedMapProjectionToGeo(wrappedProjection: extraPoint)); |
508 | } |
509 | |
510 | return poly; |
511 | } |
512 | |
513 | 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 | */ |
522 | 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(linePoint: m_eye, lineDirection: ray, s) / m_sideLengthPixels).toVector2D(); |
537 | } |
538 | |
539 | /* |
540 | Returns a pair of <newCenter, newZoom> |
541 | */ |
542 | 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(coordinate: rectangle.topLeft()); |
551 | QDoubleVector2D bottomRightPoint = geoToMapProjection(coordinate: rectangle.bottomRight()); |
552 | if (bottomRightPoint.x() < topLeftPoint.x()) // crossing the dateline |
553 | bottomRightPoint.setX(bottomRightPoint.x() + 1.0); |
554 | |
555 | // find center of the bounding box |
556 | QDoubleVector2D center = (topLeftPoint + bottomRightPoint) * 0.5; |
557 | center.setX(center.x() > 1.0 ? center.x() - 1.0 : center.x()); |
558 | res.first = mapProjectionToGeo(projection: center); |
559 | |
560 | // if the shape is empty we just change center position, not zoom |
561 | double bboxWidth = (bottomRightPoint.x() - topLeftPoint.x()) * mapWidth(); |
562 | double bboxHeight = (bottomRightPoint.y() - topLeftPoint.y()) * mapHeight(); |
563 | |
564 | if (bboxHeight == 0.0 && bboxWidth == 0.0) |
565 | return res; |
566 | |
567 | double zoomRatio = qMax(a: bboxWidth / (m_viewportWidth - m.left() - m.right()), |
568 | b: bboxHeight / (m_viewportHeight - m.top() - m.bottom())); |
569 | zoomRatio = std::log(x: zoomRatio) / std::log(x: 2.0); |
570 | res.second = m_cameraData.zoomLevel() - zoomRatio; |
571 | |
572 | return res; |
573 | } |
574 | |
575 | QGeoProjection::ProjectionGroup QGeoProjectionWebMercator::projectionGroup() const |
576 | { |
577 | return QGeoProjection::ProjectionCylindrical; |
578 | } |
579 | |
580 | QGeoProjection::Datum QGeoProjectionWebMercator::datum() const |
581 | { |
582 | return QGeoProjection::DatumWGS84; |
583 | } |
584 | |
585 | QGeoProjection::ProjectionType QGeoProjectionWebMercator::projectionType() const |
586 | { |
587 | return QGeoProjection::ProjectionWebMercator; |
588 | } |
589 | |
590 | void QGeoProjectionWebMercator::setupCamera() |
591 | { |
592 | m_qsgTransformDirty = true; |
593 | m_centerMercator = geoToMapProjection(coordinate: m_cameraData.center()); |
594 | m_cameraCenterXMercator = m_centerMercator.x(); |
595 | m_cameraCenterYMercator = m_centerMercator.y(); |
596 | |
597 | int intZoomLevel = static_cast<int>(std::floor(x: m_cameraData.zoomLevel())); |
598 | m_sideLengthPixels = (1 << intZoomLevel) * defaultTileSize; |
599 | m_center = m_centerMercator * m_sideLengthPixels; |
600 | //aperture(90 / 2) = 1 |
601 | m_aperture = tan(x: QLocationUtils::radians(degrees: m_cameraData.fieldOfView()) * 0.5); |
602 | |
603 | double f = m_viewportHeight; |
604 | double z = std::pow(x: 2.0, y: m_cameraData.zoomLevel() - intZoomLevel) * defaultTileSize; |
605 | double altitude = f / (2.0 * z); |
606 | // Also in mercator space |
607 | double z_mercator = std::pow(x: 2.0, y: m_cameraData.zoomLevel()) * defaultTileSize; |
608 | double altitude_mercator = f / (2.0 * z_mercator); |
609 | |
610 | // calculate eye |
611 | m_eye = m_center; |
612 | m_eye.setZ(altitude * defaultTileSize / m_aperture); |
613 | |
614 | // And in mercator space |
615 | m_eyeMercator = m_centerMercator; |
616 | m_eyeMercator.setZ(altitude_mercator / m_aperture); |
617 | m_eyeMercator0 = QDoubleVector3D(0,0,0); |
618 | m_eyeMercator0.setZ(altitude_mercator / m_aperture); |
619 | QDoubleVector3D eye0(0,0,0); |
620 | eye0.setZ(altitude * defaultTileSize / m_aperture); |
621 | |
622 | m_view = m_eye - m_center; |
623 | QDoubleVector3D side = QDoubleVector3D::normal(v1: m_view, v2: QDoubleVector3D(0.0, 1.0, 0.0)); |
624 | m_up = QDoubleVector3D::normal(v1: side, v2: m_view); |
625 | |
626 | // In mercator space too |
627 | m_viewMercator = m_eyeMercator - m_centerMercator; |
628 | QDoubleVector3D sideMercator = QDoubleVector3D::normal(v1: m_viewMercator, v2: QDoubleVector3D(0.0, 1.0, 0.0)); |
629 | m_upMercator = QDoubleVector3D::normal(v1: sideMercator, v2: m_viewMercator); |
630 | |
631 | if (m_cameraData.bearing() > 0.0) { |
632 | QDoubleMatrix4x4 mBearing; |
633 | mBearing.rotate(angle: m_cameraData.bearing(), vector: m_view); |
634 | m_up = mBearing * m_up; |
635 | |
636 | // In mercator space too |
637 | QDoubleMatrix4x4 mBearingMercator; |
638 | mBearingMercator.rotate(angle: m_cameraData.bearing(), vector: m_viewMercator); |
639 | m_upMercator = mBearingMercator * m_upMercator; |
640 | } |
641 | |
642 | m_side = QDoubleVector3D::normal(v1: m_up, v2: m_view); |
643 | m_sideMercator = QDoubleVector3D::normal(v1: m_upMercator, v2: m_viewMercator); |
644 | |
645 | if (m_cameraData.tilt() > 0.0) { // tilt has been already thresholded by QGeoCameraData::setTilt |
646 | QDoubleMatrix4x4 mTilt; |
647 | mTilt.rotate(angle: -m_cameraData.tilt(), vector: m_side); |
648 | m_eye = mTilt * m_view + m_center; |
649 | eye0 = mTilt * m_view; |
650 | |
651 | // In mercator space too |
652 | QDoubleMatrix4x4 mTiltMercator; |
653 | mTiltMercator.rotate(angle: -m_cameraData.tilt(), vector: m_sideMercator); |
654 | m_eyeMercator = mTiltMercator * m_viewMercator + m_centerMercator; |
655 | m_eyeMercator0 = mTiltMercator * m_viewMercator; |
656 | } |
657 | |
658 | m_view = m_eye - m_center; // ToDo: this should be inverted (center - eye), and the rest should follow |
659 | m_viewNormalized = m_view.normalized(); |
660 | m_up = QDoubleVector3D::normal(v1: m_view, v2: m_side); |
661 | |
662 | m_nearPlane = 1.0; |
663 | // At ZL 20 the map has 2^20 tiles per side. That is 1048576. |
664 | // Placing the camera on one corner of the map, rotated toward the opposite corner, and tilted |
665 | // at almost 90 degrees would require a frustum that can span the whole size of this map. |
666 | // For this reason, the far plane is set to 2 * 2^20 * defaultTileSize. |
667 | // That is, in order to make sure that the whole map would fit in the frustum at this ZL. |
668 | // Since we are using a double matrix, and since the largest value in the matrix is going to be |
669 | // 2 * m_farPlane (as near plane is 1.0), there should be sufficient precision left. |
670 | // |
671 | // TODO: extend this to support clip distance. |
672 | m_farPlane = (altitude + 2097152.0) * defaultTileSize; |
673 | |
674 | m_viewMercator = m_eyeMercator - m_centerMercator; |
675 | m_upMercator = QDoubleVector3D::normal(v1: m_viewMercator, v2: m_sideMercator); |
676 | m_nearPlaneMercator = 0.000002; // this value works until ZL 18. Above that, a better progressive formula is needed, or |
677 | // else, this clips too much. |
678 | |
679 | double aspectRatio = 1.0 * m_viewportWidth / m_viewportHeight; |
680 | |
681 | m_halfWidth = m_aperture * aspectRatio; |
682 | m_halfHeight = m_aperture; |
683 | |
684 | double verticalHalfFOV = QLocationUtils::degrees(radians: atan(x: m_aperture)); |
685 | |
686 | m_cameraMatrix.setToIdentity(); |
687 | m_cameraMatrix.lookAt(eye: m_eye, center: m_center, up: m_up); |
688 | m_cameraMatrix0.setToIdentity(); |
689 | m_cameraMatrix0.lookAt(eye: eye0, center: QDoubleVector3D(0,0,0), up: m_up); |
690 | |
691 | QDoubleMatrix4x4 projectionMatrix; |
692 | projectionMatrix.frustum(left: -m_halfWidth, right: m_halfWidth, bottom: -m_halfHeight, top: m_halfHeight, nearPlane: m_nearPlane, farPlane: m_farPlane); |
693 | |
694 | /* |
695 | * The full transformation chain for m_transformation is: |
696 | * matScreen * matScreenFit * matShift * projectionMatrix * cameraMatrix * matZoomLevelScale |
697 | * where: |
698 | * matZoomLevelScale = scale(m_sideLength, m_sideLength, 1.0) |
699 | * matShift = translate(1.0, 1.0, 0.0) |
700 | * matScreenFit = scale(0.5, 0.5, 1.0) |
701 | * matScreen = scale(m_viewportWidth, m_viewportHeight, 1.0) |
702 | */ |
703 | |
704 | QPointF offsetPct = marginsOffset(screenSize: QSizeF(m_viewportWidth, m_viewportHeight), visibleArea: m_visibleArea); |
705 | QDoubleMatrix4x4 matScreenTransformation; |
706 | matScreenTransformation.scale(x: 0.5 * m_viewportWidth, y: 0.5 * m_viewportHeight, z: 1.0); |
707 | matScreenTransformation(0,3) = (0.5 + offsetPct.x()) * m_viewportWidth; |
708 | matScreenTransformation(1,3) = (0.5 + offsetPct.y()) * m_viewportHeight; |
709 | |
710 | m_transformation = matScreenTransformation * projectionMatrix * m_cameraMatrix; |
711 | m_quickItemTransformation = m_transformation; |
712 | m_transformation.scale(x: m_sideLengthPixels, y: m_sideLengthPixels, z: 1.0); |
713 | |
714 | m_transformation0 = matScreenTransformation * projectionMatrix * m_cameraMatrix0; |
715 | m_transformation0.scale(x: m_sideLengthPixels, y: m_sideLengthPixels, z: 1.0); |
716 | |
717 | m_centerNearPlane = m_eye - m_viewNormalized; |
718 | m_centerNearPlaneMercator = m_eyeMercator - m_viewNormalized * m_nearPlaneMercator; |
719 | |
720 | // The method does not support tilting angles >= 90.0 or < 0. |
721 | |
722 | // The following formula is used to have a growing epsilon with the zoom level, |
723 | // in order not to have too large values at low zl, which would overflow when converted to Clipper::cInt. |
724 | const double upperBoundEpsilon = 1.0 / std::pow(x: 10, y: 1.0 + m_cameraData.zoomLevel() / 5.0); |
725 | const double elevationUpperBound = 90.0 - upperBoundEpsilon; |
726 | const double maxRayElevation = qMin(a: elevationUpperBound - m_cameraData.tilt(), b: verticalHalfFOV); |
727 | double maxHalfAperture = 0; |
728 | m_verticalEstateToSkip = 0; |
729 | if (maxRayElevation < verticalHalfFOV) { |
730 | maxHalfAperture = tan(x: QLocationUtils::radians(degrees: maxRayElevation)); |
731 | m_verticalEstateToSkip = 1.0 - maxHalfAperture / m_aperture; |
732 | } |
733 | |
734 | m_minimumUnprojectableY = m_verticalEstateToSkip * 0.5 * m_viewportHeight; // m_verticalEstateToSkip is relative to half aperture |
735 | m_visibleRegionDirty = true; |
736 | } |
737 | |
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>(a: -viewportHalfHeight, b: -1 + m_verticalEstateToSkip); |
746 | double bottom = viewportHalfHeight; |
747 | double left = -viewportHalfWidth; |
748 | double right = viewportHalfWidth; |
749 | |
750 | QDoubleVector2D tl = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(left, top )); |
751 | QDoubleVector2D tr = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(right, top )); |
752 | QDoubleVector2D bl = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(left, bottom )); |
753 | QDoubleVector2D br = viewportToWrappedMapProjection(itemPosition: QDoubleVector2D(right, bottom )); |
754 | |
755 | // To make sure that what is returned can be safely converted back to lat/lon without risking overlaps |
756 | double mapLeftLongitude = QLocationUtils::mapLeftLongitude(centerLongitude: m_cameraData.center().longitude()); |
757 | double mapRightLongitude = QLocationUtils::mapRightLongitude(centerLongitude: m_cameraData.center().longitude()); |
758 | double leftX = geoToWrappedMapProjection(coordinate: QGeoCoordinate(0, mapLeftLongitude)).x(); |
759 | double rightX = geoToWrappedMapProjection(coordinate: QGeoCoordinate(0, mapRightLongitude)).x(); |
760 | |
761 | QList<QDoubleVector2D> mapRect; |
762 | mapRect.push_back(t: QDoubleVector2D(leftX, 1.0)); |
763 | mapRect.push_back(t: QDoubleVector2D(rightX, 1.0)); |
764 | mapRect.push_back(t: QDoubleVector2D(rightX, 0.0)); |
765 | mapRect.push_back(t: QDoubleVector2D(leftX, 0.0)); |
766 | |
767 | QList<QDoubleVector2D> viewportRect; |
768 | viewportRect.push_back(t: bl); |
769 | viewportRect.push_back(t: br); |
770 | viewportRect.push_back(t: tr); |
771 | viewportRect.push_back(t: tl); |
772 | |
773 | c2t::clip2tri clipper; |
774 | clipper.clearClipper(); |
775 | clipper.addSubjectPath(path: QClipperUtils::qListToPath(list: mapRect), closed: true); |
776 | clipper.addClipPolygon(path: QClipperUtils::qListToPath(list: viewportRect)); |
777 | |
778 | Paths res = clipper.execute(op: c2t::clip2tri::Intersection); |
779 | m_visibleRegion.clear(); |
780 | if (res.size()) |
781 | m_visibleRegion = QClipperUtils::pathToQList(path: res[0]); // Intersection between two convex quadrilaterals should always be a single polygon |
782 | |
783 | m_projectableRegion.clear(); |
784 | mapRect.clear(); |
785 | // The full map rectangle in extended mercator space |
786 | mapRect.push_back(t: QDoubleVector2D(-1.0, 1.0)); |
787 | mapRect.push_back(t: QDoubleVector2D( 2.0, 1.0)); |
788 | mapRect.push_back(t: QDoubleVector2D( 2.0, 0.0)); |
789 | mapRect.push_back(t: QDoubleVector2D(-1.0, 0.0)); |
790 | if (m_cameraData.tilt() == 0) { |
791 | m_projectableRegion = mapRect; |
792 | } else { |
793 | QGeoProjectionWebMercator::Plane nearPlane(m_centerNearPlaneMercator, m_viewNormalized); |
794 | Line2D nearPlaneXYIntersection = nearPlane.planeXYIntersection(); |
795 | double squareHalfSide = qMax(a: 5.0, b: nearPlaneXYIntersection.m_point.length()); |
796 | QDoubleVector2D viewDirectionProjected = -m_viewNormalized.toVector2D().normalized(); |
797 | |
798 | |
799 | QDoubleVector2D tl = nearPlaneXYIntersection.m_point |
800 | - squareHalfSide * nearPlaneXYIntersection.m_direction |
801 | + 2 * squareHalfSide * viewDirectionProjected; |
802 | QDoubleVector2D tr = nearPlaneXYIntersection.m_point |
803 | + squareHalfSide * nearPlaneXYIntersection.m_direction |
804 | + 2 * squareHalfSide * viewDirectionProjected; |
805 | QDoubleVector2D bl = nearPlaneXYIntersection.m_point |
806 | - squareHalfSide * nearPlaneXYIntersection.m_direction; |
807 | QDoubleVector2D br = nearPlaneXYIntersection.m_point |
808 | + squareHalfSide * nearPlaneXYIntersection.m_direction; |
809 | |
810 | QList<QDoubleVector2D> projectableRect; |
811 | projectableRect.push_back(t: bl); |
812 | projectableRect.push_back(t: br); |
813 | projectableRect.push_back(t: tr); |
814 | projectableRect.push_back(t: tl); |
815 | |
816 | |
817 | c2t::clip2tri clipperProjectable; |
818 | clipperProjectable.clearClipper(); |
819 | clipperProjectable.addSubjectPath(path: QClipperUtils::qListToPath(list: mapRect), closed: true); |
820 | clipperProjectable.addClipPolygon(path: QClipperUtils::qListToPath(list: projectableRect)); |
821 | |
822 | Paths resProjectable = clipperProjectable.execute(op: c2t::clip2tri::Intersection); |
823 | if (resProjectable.size()) |
824 | m_projectableRegion = QClipperUtils::pathToQList(path: resProjectable[0]); // Intersection between two convex quadrilaterals should always be a single polygon |
825 | else |
826 | m_projectableRegion = viewportRect; |
827 | } |
828 | |
829 | // Compute m_visibleRegionExpanded as a clipped expanded version of m_visibleRegion |
830 | QDoubleVector2D centroid; |
831 | for (const QDoubleVector2D &v: qAsConst(t&: m_visibleRegion)) |
832 | centroid += v; |
833 | centroid /= m_visibleRegion.size(); |
834 | |
835 | m_visibleRegionExpanded.clear(); |
836 | for (const QDoubleVector2D &v: qAsConst(t&: m_visibleRegion)) { |
837 | const QDoubleVector2D vc = v - centroid; |
838 | m_visibleRegionExpanded.push_back(t: centroid + vc * 1.2); // fixing expansion factor to 1.2 |
839 | } |
840 | |
841 | c2t::clip2tri clipperExpanded; |
842 | clipperExpanded.clearClipper(); |
843 | clipperExpanded.addSubjectPath(path: QClipperUtils::qListToPath(list: m_visibleRegionExpanded), closed: true); |
844 | clipperExpanded.addClipPolygon(path: QClipperUtils::qListToPath(list: m_projectableRegion)); |
845 | Paths resVisibleExpanded = clipperExpanded.execute(op: c2t::clip2tri::Intersection); |
846 | if (resVisibleExpanded.size()) |
847 | m_visibleRegionExpanded = QClipperUtils::pathToQList(path: resVisibleExpanded[0]); // Intersection between two convex quadrilaterals should always be a single polygon |
848 | else |
849 | m_visibleRegionExpanded = m_visibleRegion; |
850 | } |
851 | |
852 | QGeoCameraData QGeoProjectionWebMercator::cameraData() const |
853 | { |
854 | return m_cameraData; |
855 | } |
856 | |
857 | /* |
858 | * |
859 | * Line implementation |
860 | * |
861 | */ |
862 | |
863 | QGeoProjectionWebMercator::Line2D::Line2D() |
864 | { |
865 | |
866 | } |
867 | |
868 | QGeoProjectionWebMercator::Line2D::Line2D(const QDoubleVector2D &linePoint, const QDoubleVector2D &lineDirection) |
869 | : m_point(linePoint), m_direction(lineDirection.normalized()) |
870 | { |
871 | |
872 | } |
873 | |
874 | bool QGeoProjectionWebMercator::Line2D::isValid() const |
875 | { |
876 | return (m_direction.length() > 0.5); |
877 | } |
878 | |
879 | /* |
880 | * |
881 | * Plane implementation |
882 | * |
883 | */ |
884 | |
885 | QGeoProjectionWebMercator::Plane::Plane() |
886 | { |
887 | |
888 | } |
889 | |
890 | QGeoProjectionWebMercator::Plane::Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal) |
891 | : m_point(planePoint), m_normal(planeNormal.normalized()) { } |
892 | |
893 | QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const |
894 | { |
895 | double s; |
896 | return lineIntersection(linePoint, lineDirection, s); |
897 | } |
898 | |
899 | 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(v1: -m_normal, v2: w) / QDoubleVector3D::dotProduct(v1: m_normal, v2: lineDirection); |
904 | return linePoint + lineDirection * s; |
905 | } |
906 | |
907 | QGeoProjectionWebMercator::Line2D QGeoProjectionWebMercator::Plane::planeXYIntersection() const |
908 | { |
909 | // cross product of the two normals for the line direction |
910 | QDoubleVector3D lineDirection = QDoubleVector3D::crossProduct(v1: m_normal, v2: xyNormal); |
911 | lineDirection.setZ(0.0); |
912 | lineDirection.normalize(); |
913 | |
914 | // cross product of the line direction and the plane normal to find the direction on the plane |
915 | // intersecting the xy plane |
916 | QDoubleVector3D directionToXY = QDoubleVector3D::crossProduct(v1: m_normal, v2: lineDirection); |
917 | QDoubleVector3D p = xyPlane.lineIntersection(linePoint: m_point, lineDirection: directionToXY); |
918 | return Line2D(p.toVector2D(), lineDirection.toVector2D()); |
919 | } |
920 | |
921 | bool QGeoProjectionWebMercator::Plane::isValid() const |
922 | { |
923 | return (m_normal.length() > 0.5); |
924 | } |
925 | |
926 | QT_END_NAMESPACE |
927 | |