| 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 |  |