| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2015 The Qt Company Ltd. |
| 4 | ** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com> |
| 5 | ** Contact: http://www.qt.io/licensing/ |
| 6 | ** |
| 7 | ** This file is part of the QtLocation module of the Qt Toolkit. |
| 8 | ** |
| 9 | ** $QT_BEGIN_LICENSE:LGPL3$ |
| 10 | ** Commercial License Usage |
| 11 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 12 | ** accordance with the commercial license agreement provided with the |
| 13 | ** Software or, alternatively, in accordance with the terms contained in |
| 14 | ** a written agreement between you and The Qt Company. For licensing terms |
| 15 | ** and conditions see http://www.qt.io/terms-conditions. For further |
| 16 | ** information use the contact form at http://www.qt.io/contact-us. |
| 17 | ** |
| 18 | ** GNU Lesser General Public License Usage |
| 19 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 20 | ** General Public License version 3 as published by the Free Software |
| 21 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| 22 | ** packaging of this file. Please review the following information to |
| 23 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 24 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| 25 | ** |
| 26 | ** GNU General Public License Usage |
| 27 | ** Alternatively, this file may be used under the terms of the GNU |
| 28 | ** General Public License version 2.0 or later as published by the Free |
| 29 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
| 30 | ** the packaging of this file. Please review the following information to |
| 31 | ** ensure the GNU General Public License version 2.0 requirements will be |
| 32 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| 33 | ** |
| 34 | ** $QT_END_LICENSE$ |
| 35 | ** |
| 36 | ****************************************************************************/ |
| 37 | #include "qgeotiledmapscene_p.h" |
| 38 | #include "qgeotiledmapscene_p_p.h" |
| 39 | #include "qgeocameradata_p.h" |
| 40 | #include "qabstractgeotilecache_p.h" |
| 41 | #include "qgeotilespec_p.h" |
| 42 | #include <QtPositioning/private/qdoublevector3d_p.h> |
| 43 | #include <QtPositioning/private/qwebmercator_p.h> |
| 44 | #include <QtCore/private/qobject_p.h> |
| 45 | #include <QtQuick/QQuickWindow> |
| 46 | #include <QtGui/QVector3D> |
| 47 | #include <cmath> |
| 48 | #include <QtPositioning/private/qlocationutils_p.h> |
| 49 | #include <QtPositioning/private/qdoublematrix4x4_p.h> |
| 50 | #include <QtPositioning/private/qwebmercator_p.h> |
| 51 | |
| 52 | static QVector3D toVector3D(const QDoubleVector3D& in) |
| 53 | { |
| 54 | return QVector3D(in.x(), in.y(), in.z()); |
| 55 | } |
| 56 | |
| 57 | QT_BEGIN_NAMESPACE |
| 58 | |
| 59 | QGeoTiledMapScene::QGeoTiledMapScene(QObject *parent) |
| 60 | : QObject(*new QGeoTiledMapScenePrivate(),parent) |
| 61 | { |
| 62 | } |
| 63 | |
| 64 | QGeoTiledMapScene::~QGeoTiledMapScene() |
| 65 | { |
| 66 | } |
| 67 | |
| 68 | void QGeoTiledMapScene::setScreenSize(const QSize &size) |
| 69 | { |
| 70 | Q_D(QGeoTiledMapScene); |
| 71 | d->m_screenSize = size; |
| 72 | } |
| 73 | |
| 74 | void QGeoTiledMapScene::updateSceneParameters() |
| 75 | { |
| 76 | Q_D(QGeoTiledMapScene); |
| 77 | d->m_intZoomLevel = static_cast<int>(std::floor(x: d->m_cameraData.zoomLevel())); |
| 78 | const float delta = d->m_cameraData.zoomLevel() - d->m_intZoomLevel; |
| 79 | d->m_linearScaling = qAbs(t: delta) > 0.05 || d->isTiltedOrRotated(); |
| 80 | d->m_sideLength = 1 << d->m_intZoomLevel; |
| 81 | d->m_mapEdgeSize = std::pow(x: 2.0, y: d->m_cameraData.zoomLevel()) * d->m_tileSize; |
| 82 | } |
| 83 | |
| 84 | void QGeoTiledMapScene::setTileSize(int tileSize) |
| 85 | { |
| 86 | Q_D(QGeoTiledMapScene); |
| 87 | if (d->m_tileSize == tileSize) |
| 88 | return; |
| 89 | |
| 90 | d->m_tileSize = tileSize; |
| 91 | updateSceneParameters(); |
| 92 | } |
| 93 | |
| 94 | void QGeoTiledMapScene::setCameraData(const QGeoCameraData &cameraData) |
| 95 | { |
| 96 | Q_D(QGeoTiledMapScene); |
| 97 | d->m_cameraData = cameraData; |
| 98 | updateSceneParameters(); |
| 99 | } |
| 100 | |
| 101 | void QGeoTiledMapScene::setVisibleArea(const QRectF &visibleArea) |
| 102 | { |
| 103 | Q_D(QGeoTiledMapScene); |
| 104 | if (d->m_visibleArea == visibleArea) |
| 105 | return; |
| 106 | d->m_visibleArea = visibleArea; |
| 107 | updateSceneParameters(); |
| 108 | } |
| 109 | |
| 110 | void QGeoTiledMapScene::setVisibleTiles(const QSet<QGeoTileSpec> &tiles) |
| 111 | { |
| 112 | Q_D(QGeoTiledMapScene); |
| 113 | d->setVisibleTiles(tiles); |
| 114 | } |
| 115 | |
| 116 | const QSet<QGeoTileSpec> &QGeoTiledMapScene::visibleTiles() const |
| 117 | { |
| 118 | Q_D(const QGeoTiledMapScene); |
| 119 | return d->m_visibleTiles; |
| 120 | } |
| 121 | |
| 122 | void QGeoTiledMapScene::addTile(const QGeoTileSpec &spec, QSharedPointer<QGeoTileTexture> texture) |
| 123 | { |
| 124 | Q_D(QGeoTiledMapScene); |
| 125 | d->addTile(spec, texture); |
| 126 | } |
| 127 | |
| 128 | QSet<QGeoTileSpec> QGeoTiledMapScene::texturedTiles() |
| 129 | { |
| 130 | Q_D(QGeoTiledMapScene); |
| 131 | QSet<QGeoTileSpec> textured; |
| 132 | for (auto it = d->m_textures.cbegin(); it != d->m_textures.cend(); ++it) |
| 133 | textured += it.value()->spec; |
| 134 | |
| 135 | return textured; |
| 136 | } |
| 137 | |
| 138 | void QGeoTiledMapScene::clearTexturedTiles() |
| 139 | { |
| 140 | Q_D(QGeoTiledMapScene); |
| 141 | d->m_textures.clear(); |
| 142 | d->m_dropTextures = true; |
| 143 | } |
| 144 | |
| 145 | QGeoTiledMapScenePrivate::QGeoTiledMapScenePrivate() |
| 146 | : QObjectPrivate(), |
| 147 | m_tileSize(0), |
| 148 | #ifdef QT_LOCATION_DEBUG |
| 149 | m_scaleFactor(1.0), |
| 150 | #else |
| 151 | m_scaleFactor(10.0), |
| 152 | #endif |
| 153 | m_intZoomLevel(0), |
| 154 | m_sideLength(0), |
| 155 | m_minTileX(-1), |
| 156 | m_minTileY(-1), |
| 157 | m_maxTileX(-1), |
| 158 | m_maxTileY(-1), |
| 159 | m_tileXWrapsBelow(0), |
| 160 | m_linearScaling(false), |
| 161 | m_dropTextures(false) |
| 162 | { |
| 163 | } |
| 164 | |
| 165 | QGeoTiledMapScenePrivate::~QGeoTiledMapScenePrivate() |
| 166 | { |
| 167 | } |
| 168 | |
| 169 | bool QGeoTiledMapScenePrivate::buildGeometry(const QGeoTileSpec &spec, QSGImageNode *imageNode, bool &overzooming) |
| 170 | { |
| 171 | overzooming = false; |
| 172 | int x = spec.x(); |
| 173 | |
| 174 | if (x < m_tileXWrapsBelow) |
| 175 | x += m_sideLength; |
| 176 | |
| 177 | if ((x < m_minTileX) |
| 178 | || (m_maxTileX < x) |
| 179 | || (spec.y() < m_minTileY) |
| 180 | || (m_maxTileY < spec.y()) |
| 181 | || (spec.zoom() != m_intZoomLevel)) { |
| 182 | return false; |
| 183 | } |
| 184 | |
| 185 | double edge = m_scaleFactor * m_tileSize; |
| 186 | |
| 187 | double x1 = (x - m_minTileX); |
| 188 | double x2 = x1 + 1.0; |
| 189 | |
| 190 | double y1 = (m_minTileY - spec.y()); |
| 191 | double y2 = y1 - 1.0; |
| 192 | |
| 193 | x1 *= edge; |
| 194 | x2 *= edge; |
| 195 | y1 *= edge; |
| 196 | y2 *= edge; |
| 197 | |
| 198 | imageNode->setRect(QRectF(QPointF(x1, y2), QPointF(x2, y1))); |
| 199 | imageNode->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically); |
| 200 | |
| 201 | // Calculate the texture mapping, in case we are magnifying some lower ZL tile |
| 202 | const auto it = m_textures.find(akey: spec); // This should be always found, but apparently sometimes it isn't, possibly due to memory shortage |
| 203 | if (it != m_textures.end()) { |
| 204 | if (it.value()->spec.zoom() < spec.zoom()) { |
| 205 | // Currently only using lower ZL tiles for the overzoom. |
| 206 | const int tilesPerTexture = 1 << (spec.zoom() - it.value()->spec.zoom()); |
| 207 | const int mappedSize = imageNode->texture()->textureSize().width() / tilesPerTexture; |
| 208 | const int x = (spec.x() % tilesPerTexture) * mappedSize; |
| 209 | const int y = (spec.y() % tilesPerTexture) * mappedSize; |
| 210 | imageNode->setSourceRect(QRectF(x, y, mappedSize, mappedSize)); |
| 211 | overzooming = true; |
| 212 | } else { |
| 213 | imageNode->setSourceRect(QRectF(QPointF(0,0), imageNode->texture()->textureSize())); |
| 214 | } |
| 215 | } else { |
| 216 | qWarning() << "!! buildGeometry: tileSpec not present in m_textures !!" ; |
| 217 | imageNode->setSourceRect(QRectF(QPointF(0,0), imageNode->texture()->textureSize())); |
| 218 | } |
| 219 | |
| 220 | return true; |
| 221 | } |
| 222 | |
| 223 | void QGeoTiledMapScenePrivate::addTile(const QGeoTileSpec &spec, QSharedPointer<QGeoTileTexture> texture) |
| 224 | { |
| 225 | if (!m_visibleTiles.contains(value: spec)) // Don't add the geometry if it isn't visible |
| 226 | return; |
| 227 | |
| 228 | if (m_textures.contains(akey: spec)) |
| 229 | m_updatedTextures.append(t: spec); |
| 230 | m_textures.insert(akey: spec, avalue: texture); |
| 231 | } |
| 232 | |
| 233 | void QGeoTiledMapScenePrivate::setVisibleTiles(const QSet<QGeoTileSpec> &visibleTiles) |
| 234 | { |
| 235 | // work out the tile bounds for the new scene |
| 236 | updateTileBounds(tiles: visibleTiles); |
| 237 | |
| 238 | // set up the gl camera for the new scene |
| 239 | setupCamera(); |
| 240 | |
| 241 | QSet<QGeoTileSpec> toRemove = m_visibleTiles - visibleTiles; |
| 242 | if (!toRemove.isEmpty()) |
| 243 | removeTiles(oldTiles: toRemove); |
| 244 | |
| 245 | m_visibleTiles = visibleTiles; |
| 246 | } |
| 247 | |
| 248 | void QGeoTiledMapScenePrivate::removeTiles(const QSet<QGeoTileSpec> &oldTiles) |
| 249 | { |
| 250 | typedef QSet<QGeoTileSpec>::const_iterator iter; |
| 251 | iter i = oldTiles.constBegin(); |
| 252 | iter end = oldTiles.constEnd(); |
| 253 | |
| 254 | for (; i != end; ++i) { |
| 255 | QGeoTileSpec tile = *i; |
| 256 | m_textures.remove(akey: tile); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | void QGeoTiledMapScenePrivate::updateTileBounds(const QSet<QGeoTileSpec> &tiles) |
| 261 | { |
| 262 | if (tiles.isEmpty()) { |
| 263 | m_minTileX = -1; |
| 264 | m_minTileY = -1; |
| 265 | m_maxTileX = -1; |
| 266 | m_maxTileY = -1; |
| 267 | return; |
| 268 | } |
| 269 | |
| 270 | typedef QSet<QGeoTileSpec>::const_iterator iter; |
| 271 | iter i = tiles.constBegin(); |
| 272 | iter end = tiles.constEnd(); |
| 273 | |
| 274 | // determine whether the set of map tiles crosses the dateline. |
| 275 | // A gap in the tiles indicates dateline crossing |
| 276 | bool hasFarLeft = false; |
| 277 | bool hasFarRight = false; |
| 278 | bool hasMidLeft = false; |
| 279 | bool hasMidRight = false; |
| 280 | |
| 281 | for (; i != end; ++i) { |
| 282 | if ((*i).zoom() != m_intZoomLevel) |
| 283 | continue; |
| 284 | int x = (*i).x(); |
| 285 | if (x == 0) |
| 286 | hasFarLeft = true; |
| 287 | else if (x == (m_sideLength - 1)) |
| 288 | hasFarRight = true; |
| 289 | else if (x == ((m_sideLength / 2) - 1)) { |
| 290 | hasMidLeft = true; |
| 291 | } else if (x == (m_sideLength / 2)) { |
| 292 | hasMidRight = true; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | // if dateline crossing is detected we wrap all x pos of tiles |
| 297 | // that are in the left half of the map. |
| 298 | m_tileXWrapsBelow = 0; |
| 299 | |
| 300 | if (hasFarLeft && hasFarRight) { |
| 301 | if (!hasMidRight) { |
| 302 | m_tileXWrapsBelow = m_sideLength / 2; |
| 303 | } else if (!hasMidLeft) { |
| 304 | m_tileXWrapsBelow = (m_sideLength / 2) - 1; |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | // finally, determine the min and max bounds |
| 309 | i = tiles.constBegin(); |
| 310 | |
| 311 | QGeoTileSpec tile = *i; |
| 312 | |
| 313 | int x = tile.x(); |
| 314 | if (tile.x() < m_tileXWrapsBelow) |
| 315 | x += m_sideLength; |
| 316 | |
| 317 | m_minTileX = x; |
| 318 | m_maxTileX = x; |
| 319 | m_minTileY = tile.y(); |
| 320 | m_maxTileY = tile.y(); |
| 321 | |
| 322 | ++i; |
| 323 | |
| 324 | for (; i != end; ++i) { |
| 325 | tile = *i; |
| 326 | if (tile.zoom() != m_intZoomLevel) |
| 327 | continue; |
| 328 | |
| 329 | int x = tile.x(); |
| 330 | if (tile.x() < m_tileXWrapsBelow) |
| 331 | x += m_sideLength; |
| 332 | |
| 333 | m_minTileX = qMin(a: m_minTileX, b: x); |
| 334 | m_maxTileX = qMax(a: m_maxTileX, b: x); |
| 335 | m_minTileY = qMin(a: m_minTileY, b: tile.y()); |
| 336 | m_maxTileY = qMax(a: m_maxTileY, b: tile.y()); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | void QGeoTiledMapScenePrivate::setupCamera() |
| 341 | { |
| 342 | // NOTE: The following instruction is correct only because WebMercator is a square projection! |
| 343 | double f = m_screenSize.height(); |
| 344 | |
| 345 | // Using fraction of zoom level, z varies between [ m_tileSize , 2 * m_tileSize [ |
| 346 | double z = std::pow(x: 2.0, y: m_cameraData.zoomLevel() - m_intZoomLevel) * m_tileSize; |
| 347 | |
| 348 | // calculate altitude that allows the visible map tiles |
| 349 | // to fit in the screen correctly (note that a larger f will cause |
| 350 | // the camera be higher, resulting in gray areas displayed around |
| 351 | // the tiles) |
| 352 | double altitude = f / (2.0 * z); |
| 353 | |
| 354 | // calculate center |
| 355 | double edge = m_scaleFactor * m_tileSize; |
| 356 | |
| 357 | // first calculate the camera center in map space in the range of 0 <-> sideLength (2^z) |
| 358 | QDoubleVector2D camCenterMercator = QWebMercator::coordToMercator(coord: m_cameraData.center()); |
| 359 | QDoubleVector3D center = (m_sideLength * camCenterMercator); |
| 360 | |
| 361 | // wrap the center if necessary (due to dateline crossing) |
| 362 | if (center.x() < m_tileXWrapsBelow) |
| 363 | center.setX(center.x() + 1.0 * m_sideLength); |
| 364 | |
| 365 | // work out where the camera center is w.r.t minimum tile bounds |
| 366 | center.setX(center.x() - 1.0 * m_minTileX); |
| 367 | center.setY(1.0 * m_minTileY - center.y()); |
| 368 | |
| 369 | // apply necessary scaling to the camera center |
| 370 | center *= edge; |
| 371 | |
| 372 | // calculate eye |
| 373 | double apertureSize = 1.0; |
| 374 | if (m_cameraData.fieldOfView() != 90.0) //aperture(90 / 2) = 1 |
| 375 | apertureSize = tan(x: QLocationUtils::radians(degrees: m_cameraData.fieldOfView()) * 0.5); |
| 376 | QDoubleVector3D eye = center; |
| 377 | eye.setZ(altitude * edge / apertureSize); |
| 378 | |
| 379 | // calculate up |
| 380 | |
| 381 | QDoubleVector3D view = eye - center; |
| 382 | QDoubleVector3D side = QDoubleVector3D::normal(v1: view, v2: QDoubleVector3D(0.0, 1.0, 0.0)); |
| 383 | QDoubleVector3D up = QDoubleVector3D::normal(v1: side, v2: view); |
| 384 | |
| 385 | // old bearing, tilt and roll code. |
| 386 | // Now using double matrices until distilling the transformation to QMatrix4x4 |
| 387 | QDoubleMatrix4x4 mBearing; |
| 388 | // -1.0 * bearing removed, now map north goes in the bearing direction |
| 389 | mBearing.rotate(angle: -1.0 * m_cameraData.bearing(), vector: view); |
| 390 | up = mBearing * up; |
| 391 | |
| 392 | QDoubleVector3D side2 = QDoubleVector3D::normal(v1: up, v2: view); |
| 393 | if (m_cameraData.tilt() > 0.01) { |
| 394 | QDoubleMatrix4x4 mTilt; |
| 395 | mTilt.rotate(angle: m_cameraData.tilt(), vector: side2); |
| 396 | eye = mTilt * view + center; |
| 397 | } |
| 398 | |
| 399 | view = eye - center; |
| 400 | view.normalize(); |
| 401 | side = QDoubleVector3D::normal(v1: view, v2: QDoubleVector3D(0.0, 1.0, 0.0)); |
| 402 | up = QDoubleVector3D::normal(v1: view, v2: side2); |
| 403 | |
| 404 | // QMatrix4x4 mRoll; |
| 405 | // mRoll.rotate(camera.roll(), view); |
| 406 | // up = mRoll * up; |
| 407 | |
| 408 | // near plane and far plane |
| 409 | |
| 410 | double nearPlane = 1.0; |
| 411 | // Clip plane. Used to be (altitude + 1.0) * edge. This does not affect the perspective. minimum value would be > 0.0 |
| 412 | // Since, for some reasons possibly related to how QSG works, this clipping plane is unable to clip part of tiles, |
| 413 | // Instead of farPlane = (altitude + m_cameraData.clipDistance()) * edge , we use a fixed large clipDistance, and |
| 414 | // leave the clipping only in QGeoCameraTiles::createFrustum |
| 415 | double farPlane = (altitude + 10000.0) * edge; |
| 416 | |
| 417 | m_cameraUp = up; |
| 418 | m_cameraCenter = center; |
| 419 | m_cameraEye = eye; |
| 420 | |
| 421 | double aspectRatio = 1.0 * m_screenSize.width() / m_screenSize.height(); |
| 422 | float halfWidth = 1 * apertureSize; |
| 423 | float halfHeight = 1 * apertureSize; |
| 424 | halfWidth *= aspectRatio; |
| 425 | |
| 426 | // m_projectionMatrix.setToIdentity(); |
| 427 | // m_projectionMatrix.frustum(-halfWidth, halfWidth, -halfHeight, halfHeight, nearPlane, farPlane); |
| 428 | |
| 429 | QRectF va = m_visibleArea; |
| 430 | if (va.isNull()) |
| 431 | va = QRectF(0, 0, m_screenSize.width(), m_screenSize.height()); |
| 432 | |
| 433 | QRectF screen = QRectF(QPointF(0,0),m_screenSize); |
| 434 | QPointF vaCenter = va.center(); |
| 435 | |
| 436 | QPointF screenCenter = screen.center(); |
| 437 | QPointF diff = screenCenter - vaCenter; |
| 438 | float xdiffpct = diff.x() / m_screenSize.width(); |
| 439 | float ydiffpct = -(diff.y() / m_screenSize.height()); |
| 440 | |
| 441 | m_projectionMatrix.setToIdentity(); |
| 442 | float l = -halfWidth + (2 * halfWidth) * xdiffpct; |
| 443 | float r = halfWidth + (2 * halfWidth) * xdiffpct; |
| 444 | float t = halfHeight + (2 * halfHeight) * ydiffpct; |
| 445 | float b = -halfHeight + (2 * halfHeight) * ydiffpct; |
| 446 | |
| 447 | m_projectionMatrix.frustum(left: l, |
| 448 | right: r, |
| 449 | bottom: b, |
| 450 | top: t, |
| 451 | nearPlane, farPlane); |
| 452 | } |
| 453 | |
| 454 | static bool qgeotiledmapscene_isTileInViewport_Straight(const QRectF &tileRect, const QMatrix4x4 &matrix) |
| 455 | { |
| 456 | const QRectF boundingRect = QRectF(matrix * tileRect.topLeft(), matrix * tileRect.bottomRight()); |
| 457 | return QRectF(-1, -1, 2, 2).intersects(r: boundingRect); |
| 458 | } |
| 459 | |
| 460 | static bool qgeotiledmapscene_isTileInViewport_rotationTilt(const QRectF &tileRect, const QMatrix4x4 &matrix) |
| 461 | { |
| 462 | // Transformed corners |
| 463 | const QPointF tlt = matrix * tileRect.topLeft(); |
| 464 | const QPointF trt = matrix * tileRect.topRight(); |
| 465 | const QPointF blt = matrix * tileRect.bottomLeft(); |
| 466 | const QPointF brt = matrix * tileRect.bottomRight(); |
| 467 | |
| 468 | const QRectF boundingRect = QRectF(QPointF(qMin(a: qMin(a: qMin(a: tlt.x(), b: trt.x()), b: blt.x()), b: brt.x()) |
| 469 | ,qMax(a: qMax(a: qMax(a: tlt.y(), b: trt.y()), b: blt.y()), b: brt.y())) |
| 470 | ,QPointF(qMax(a: qMax(a: qMax(a: tlt.x(), b: trt.x()), b: blt.x()), b: brt.x()) |
| 471 | ,qMin(a: qMin(a: qMin(a: tlt.y(), b: trt.y()), b: blt.y()), b: brt.y())) |
| 472 | ); |
| 473 | return QRectF(-1, -1, 2, 2).intersects(r: boundingRect); |
| 474 | } |
| 475 | |
| 476 | static bool qgeotiledmapscene_isTileInViewport(const QRectF &tileRect, const QMatrix4x4 &matrix, const bool straight) |
| 477 | { |
| 478 | if (straight) |
| 479 | return qgeotiledmapscene_isTileInViewport_Straight(tileRect, matrix); |
| 480 | return qgeotiledmapscene_isTileInViewport_rotationTilt(tileRect, matrix); |
| 481 | } |
| 482 | |
| 483 | void QGeoTiledMapRootNode::updateTiles(QGeoTiledMapTileContainerNode *root, |
| 484 | QGeoTiledMapScenePrivate *d, |
| 485 | double camAdjust, |
| 486 | QQuickWindow *window, |
| 487 | bool ogl) |
| 488 | { |
| 489 | // Set up the matrix... |
| 490 | QDoubleVector3D eye = d->m_cameraEye; |
| 491 | eye.setX(eye.x() + camAdjust); |
| 492 | QDoubleVector3D center = d->m_cameraCenter; |
| 493 | center.setX(center.x() + camAdjust); |
| 494 | QMatrix4x4 cameraMatrix; |
| 495 | cameraMatrix.lookAt(eye: toVector3D(in: eye), center: toVector3D(in: center), up: toVector3D(in: d->m_cameraUp)); |
| 496 | root->setMatrix(d->m_projectionMatrix * cameraMatrix); |
| 497 | |
| 498 | QSet<QGeoTileSpec> tilesInSG; |
| 499 | for (auto it = root->tiles.cbegin(), end = root->tiles.cend(); it != end; ++it) |
| 500 | tilesInSG.insert(value: it.key()); |
| 501 | const QSet<QGeoTileSpec> toRemove = tilesInSG - d->m_visibleTiles; |
| 502 | const QSet<QGeoTileSpec> toAdd = d->m_visibleTiles - tilesInSG; |
| 503 | |
| 504 | for (const QGeoTileSpec &s : toRemove) |
| 505 | delete root->tiles.take(akey: s); |
| 506 | bool straight = !d->isTiltedOrRotated(); |
| 507 | bool overzooming; |
| 508 | qreal pixelRatio = window->effectiveDevicePixelRatio(); |
| 509 | #ifdef QT_LOCATION_DEBUG |
| 510 | QList<QGeoTileSpec> droppedTiles; |
| 511 | #endif |
| 512 | for (QHash<QGeoTileSpec, QSGImageNode *>::iterator it = root->tiles.begin(); |
| 513 | it != root->tiles.end(); ) { |
| 514 | QSGImageNode *node = it.value(); |
| 515 | bool ok = d->buildGeometry(spec: it.key(), imageNode: node, overzooming) |
| 516 | && qgeotiledmapscene_isTileInViewport(tileRect: node->rect(), matrix: root->matrix(), straight); |
| 517 | |
| 518 | QSGNode::DirtyState dirtyBits = {}; |
| 519 | |
| 520 | if (!ok) { |
| 521 | #ifdef QT_LOCATION_DEBUG |
| 522 | droppedTiles.append(it.key()); |
| 523 | #endif |
| 524 | it = root->tiles.erase(it); |
| 525 | delete node; |
| 526 | } else { |
| 527 | if (isTextureLinear != d->m_linearScaling) { |
| 528 | if (node->texture()->textureSize().width() > d->m_tileSize * pixelRatio) { |
| 529 | node->setFiltering(QSGTexture::Linear); // With mipmapping QSGTexture::Nearest generates artifacts |
| 530 | node->setMipmapFiltering(QSGTexture::Linear); |
| 531 | } else { |
| 532 | node->setFiltering((d->m_linearScaling || overzooming) ? QSGTexture::Linear : QSGTexture::Nearest); |
| 533 | } |
| 534 | #if QT_CONFIG(opengl) |
| 535 | if (ogl) |
| 536 | static_cast<QSGDefaultImageNode *>(node)->setAnisotropyLevel(QSGTexture::Anisotropy16x); |
| 537 | #else |
| 538 | Q_UNUSED(ogl); |
| 539 | #endif |
| 540 | dirtyBits |= QSGNode::DirtyMaterial; |
| 541 | } |
| 542 | if (dirtyBits != 0) |
| 543 | node->markDirty(bits: dirtyBits); |
| 544 | it++; |
| 545 | } |
| 546 | } |
| 547 | |
| 548 | for (const QGeoTileSpec &s : toAdd) { |
| 549 | QGeoTileTexture *tileTexture = d->m_textures.value(akey: s).data(); |
| 550 | if (!tileTexture || tileTexture->image.isNull()) { |
| 551 | #ifdef QT_LOCATION_DEBUG |
| 552 | droppedTiles.append(s); |
| 553 | #endif |
| 554 | continue; |
| 555 | } |
| 556 | QSGImageNode *tileNode = window->createImageNode(); |
| 557 | // note: setTexture will update coordinates so do it here, before we buildGeometry |
| 558 | tileNode->setTexture(textures.value(akey: s)); |
| 559 | if (d->buildGeometry(spec: s, imageNode: tileNode, overzooming) |
| 560 | && qgeotiledmapscene_isTileInViewport(tileRect: tileNode->rect(), matrix: root->matrix(), straight)) { |
| 561 | if (tileNode->texture()->textureSize().width() > d->m_tileSize * pixelRatio) { |
| 562 | tileNode->setFiltering(QSGTexture::Linear); // with mipmapping QSGTexture::Nearest generates artifacts |
| 563 | tileNode->setMipmapFiltering(QSGTexture::Linear); |
| 564 | } else { |
| 565 | tileNode->setFiltering((d->m_linearScaling || overzooming) ? QSGTexture::Linear : QSGTexture::Nearest); |
| 566 | } |
| 567 | #if QT_CONFIG(opengl) |
| 568 | if (ogl) |
| 569 | static_cast<QSGDefaultImageNode *>(tileNode)->setAnisotropyLevel(QSGTexture::Anisotropy16x); |
| 570 | #endif |
| 571 | root->addChild(spec: s, node: tileNode); |
| 572 | } else { |
| 573 | #ifdef QT_LOCATION_DEBUG |
| 574 | droppedTiles.append(s); |
| 575 | #endif |
| 576 | delete tileNode; |
| 577 | } |
| 578 | } |
| 579 | |
| 580 | #ifdef QT_LOCATION_DEBUG |
| 581 | m_droppedTiles[camAdjust] = droppedTiles; |
| 582 | #endif |
| 583 | } |
| 584 | |
| 585 | QSGNode *QGeoTiledMapScene::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) |
| 586 | { |
| 587 | Q_D(QGeoTiledMapScene); |
| 588 | float w = d->m_screenSize.width(); |
| 589 | float h = d->m_screenSize.height(); |
| 590 | if (w <= 0 || h <= 0) { |
| 591 | delete oldNode; |
| 592 | return 0; |
| 593 | } |
| 594 | |
| 595 | bool isOpenGL = (window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL); |
| 596 | QGeoTiledMapRootNode *mapRoot = static_cast<QGeoTiledMapRootNode *>(oldNode); |
| 597 | if (!mapRoot) |
| 598 | mapRoot = new QGeoTiledMapRootNode(); |
| 599 | |
| 600 | #ifdef QT_LOCATION_DEBUG |
| 601 | mapRoot->m_droppedTiles.clear(); |
| 602 | d->m_mapRoot = mapRoot; |
| 603 | #endif |
| 604 | |
| 605 | // Setting clip rect to fullscreen, as now the map can never be smaller than the viewport. |
| 606 | mapRoot->setClipRect(QRect(0, 0, w, h)); |
| 607 | |
| 608 | QMatrix4x4 itemSpaceMatrix; |
| 609 | itemSpaceMatrix.scale(x: w / 2, y: h / 2); |
| 610 | itemSpaceMatrix.translate(x: 1, y: 1); |
| 611 | itemSpaceMatrix.scale(x: 1, y: -1); |
| 612 | mapRoot->root->setMatrix(itemSpaceMatrix); |
| 613 | |
| 614 | if (d->m_dropTextures) { |
| 615 | for (const QGeoTileSpec &s : mapRoot->tiles->tiles.keys()) |
| 616 | delete mapRoot->tiles->tiles.take(akey: s); |
| 617 | for (const QGeoTileSpec &s : mapRoot->wrapLeft->tiles.keys()) |
| 618 | delete mapRoot->wrapLeft->tiles.take(akey: s); |
| 619 | for (const QGeoTileSpec &s : mapRoot->wrapRight->tiles.keys()) |
| 620 | delete mapRoot->wrapRight->tiles.take(akey: s); |
| 621 | for (const QGeoTileSpec &spec : mapRoot->textures.keys()) |
| 622 | mapRoot->textures.take(akey: spec)->deleteLater(); |
| 623 | d->m_dropTextures = false; |
| 624 | } |
| 625 | |
| 626 | // Evicting loZL tiles temporarily used in place of hiZL ones |
| 627 | if (d->m_updatedTextures.size()) { |
| 628 | const QVector<QGeoTileSpec> &toRemove = d->m_updatedTextures; |
| 629 | for (const QGeoTileSpec &s : toRemove) { |
| 630 | if (mapRoot->tiles->tiles.contains(akey: s)) |
| 631 | delete mapRoot->tiles->tiles.take(akey: s); |
| 632 | |
| 633 | if (mapRoot->wrapLeft->tiles.contains(akey: s)) |
| 634 | delete mapRoot->wrapLeft->tiles.take(akey: s); |
| 635 | |
| 636 | if (mapRoot->wrapRight->tiles.contains(akey: s)) |
| 637 | delete mapRoot->wrapRight->tiles.take(akey: s); |
| 638 | |
| 639 | if (mapRoot->textures.contains(akey: s)) |
| 640 | mapRoot->textures.take(akey: s)->deleteLater(); |
| 641 | } |
| 642 | d->m_updatedTextures.clear(); |
| 643 | } |
| 644 | |
| 645 | QSet<QGeoTileSpec> textures; |
| 646 | for (auto it = mapRoot->textures.cbegin(), end = mapRoot->textures.cend(); it != end; ++it) |
| 647 | textures.insert(value: it.key()); |
| 648 | const QSet<QGeoTileSpec> toRemove = textures - d->m_visibleTiles; |
| 649 | const QSet<QGeoTileSpec> toAdd = d->m_visibleTiles - textures; |
| 650 | |
| 651 | for (const QGeoTileSpec &spec : toRemove) |
| 652 | mapRoot->textures.take(akey: spec)->deleteLater(); |
| 653 | for (const QGeoTileSpec &spec : toAdd) { |
| 654 | QGeoTileTexture *tileTexture = d->m_textures.value(akey: spec).data(); |
| 655 | if (!tileTexture || tileTexture->image.isNull()) |
| 656 | continue; |
| 657 | mapRoot->textures.insert(akey: spec, avalue: window->createTextureFromImage(image: tileTexture->image)); |
| 658 | } |
| 659 | |
| 660 | double sideLength = d->m_scaleFactor * d->m_tileSize * d->m_sideLength; |
| 661 | #ifdef QT_LOCATION_DEBUG |
| 662 | d->m_sideLengthPixel = sideLength; |
| 663 | #endif |
| 664 | mapRoot->updateTiles(root: mapRoot->tiles, d, camAdjust: 0, window, ogl: isOpenGL); |
| 665 | mapRoot->updateTiles(root: mapRoot->wrapLeft, d, camAdjust: +sideLength, window, ogl: isOpenGL); |
| 666 | mapRoot->updateTiles(root: mapRoot->wrapRight, d, camAdjust: -sideLength, window, ogl: isOpenGL); |
| 667 | |
| 668 | mapRoot->isTextureLinear = d->m_linearScaling; |
| 669 | |
| 670 | return mapRoot; |
| 671 | } |
| 672 | |
| 673 | QT_END_NAMESPACE |
| 674 | |