Revision control
++ // Copyright (C) 2015 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++ #include "qgeotiledmap_p.h"
++ #include "qgeotiledmap_p_p.h"
++ #include "qgeotiledmappingmanagerengine_p.h"
++ #include "qabstractgeotilecache_p.h"
++ #include "qgeotilespec_p.h"
++ #include "qgeoprojection_p.h"
++
++ #include "qgeocameratiles_p.h"
++ #include "qgeotilerequestmanager_p.h"
++ #include "qgeotiledmapscene_p.h"
++ #include "qgeocameracapabilities_p.h"
++ #include <cmath>
++
++ QT_BEGIN_NAMESPACE
++ #define PREFETCH_FRUSTUM_SCALE 2.0
++
++ QGeoTiledMap::QGeoTiledMap(QGeoTiledMappingManagerEngine *engine, QObject *parent)
++ : QGeoMap(*new QGeoTiledMapPrivate(engine), parent)
++ {
++ Q_D(QGeoTiledMap);
++
++ d->m_tileRequests = new QGeoTileRequestManager(this, engine);
++
++ QObject::connect(engine,&QGeoTiledMappingManagerEngine::tileVersionChanged,
++ this,&QGeoTiledMap::handleTileVersionChanged);
++ QObject::connect(this, &QGeoMap::cameraCapabilitiesChanged, this,
++ [d](const QGeoCameraCapabilities &oldCameraCapabilities) {
++ d->onCameraCapabilitiesChanged(oldCameraCapabilities);
++ });
++ }
++
++ QGeoTiledMap::QGeoTiledMap(QGeoTiledMapPrivate &dd, QGeoTiledMappingManagerEngine *engine, QObject *parent)
++ : QGeoMap(dd, parent)
++ {
++ Q_D(QGeoTiledMap);
++
++ d->m_tileRequests = new QGeoTileRequestManager(this, engine);
++
++ QObject::connect(engine,&QGeoTiledMappingManagerEngine::tileVersionChanged,
++ this,&QGeoTiledMap::handleTileVersionChanged);
++ QObject::connect(this, &QGeoMap::cameraCapabilitiesChanged, this,
++ [d](const QGeoCameraCapabilities &oldCameraCapabilities) {
++ d->onCameraCapabilitiesChanged(oldCameraCapabilities);
++ });
++ }
++
++ QGeoTiledMap::~QGeoTiledMap()
++ {
++ Q_D(QGeoTiledMap);
++ delete d->m_tileRequests;
++ d->m_tileRequests = nullptr;
++
++ if (!d->m_engine.isNull()) {
++ QGeoTiledMappingManagerEngine *engine = qobject_cast<QGeoTiledMappingManagerEngine*>(d->m_engine);
++ Q_ASSERT(engine);
++ engine->releaseMap(this);
++ }
++ }
++
++ QGeoTileRequestManager *QGeoTiledMap::requestManager()
++ {
++ Q_D(QGeoTiledMap);
++ return d->m_tileRequests;
++ }
++
++ void QGeoTiledMap::updateTile(const QGeoTileSpec &spec)
++ {
++ Q_D(QGeoTiledMap);
++ d->updateTile(spec);
++ }
++
++ void QGeoTiledMap::setPrefetchStyle(QGeoTiledMap::PrefetchStyle style)
++ {
++ Q_D(QGeoTiledMap);
++ d->m_prefetchStyle = style;
++ }
++
++ QAbstractGeoTileCache *QGeoTiledMap::tileCache()
++ {
++ Q_D(QGeoTiledMap);
++ return d->m_cache;
++ }
++
++ QSGNode *QGeoTiledMap::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window)
++ {
++ Q_D(QGeoTiledMap);
++ return d->updateSceneGraph(oldNode, window);
++ }
++
++ void QGeoTiledMap::prefetchData()
++ {
++ Q_D(QGeoTiledMap);
++ d->prefetchTiles();
++ }
++
++ void QGeoTiledMap::clearData()
++ {
++ Q_D(QGeoTiledMap);
++ d->m_cache->clearAll();
++ d->m_mapScene->clearTexturedTiles();
++ d->updateScene();
++ sgNodeChanged();
++ }
++
++ QGeoMap::Capabilities QGeoTiledMap::capabilities() const
++ {
++ return Capabilities(SupportsVisibleRegion
++ | SupportsSetBearing
++ | SupportsAnchoringCoordinate
++ | SupportsVisibleArea);
++ }
++
++ void QGeoTiledMap::setCopyrightVisible(bool visible)
++ {
++ Q_D(QGeoTiledMap);
++ if (visible == d->m_copyrightVisible)
++ return;
++
++ QGeoMap::setCopyrightVisible(visible);
++ if (visible)
++ evaluateCopyrights(d->m_mapScene->visibleTiles());
++ }
++
++ void QGeoTiledMap::clearScene(int mapId)
++ {
++ Q_D(QGeoTiledMap);
++ if (activeMapType().mapId() == mapId)
++ d->clearScene();
++ }
++
++ void QGeoTiledMap::handleTileVersionChanged()
++ {
++ Q_D(QGeoTiledMap);
++ if (!d->m_engine.isNull()) {
++ QGeoTiledMappingManagerEngine* engine = qobject_cast<QGeoTiledMappingManagerEngine*>(d->m_engine);
++ Q_ASSERT(engine);
++ d->changeTileVersion(engine->tileVersion());
++ }
++ }
++
++ void QGeoTiledMap::evaluateCopyrights(const QSet<QGeoTileSpec> &visibleTiles)
++ {
++ Q_UNUSED(visibleTiles);
++ }
++
++ QGeoTiledMapPrivate::QGeoTiledMapPrivate(QGeoTiledMappingManagerEngine *engine)
++ : QGeoMapPrivate(engine, new QGeoProjectionWebMercator),
++ m_cache(engine->tileCache()),
++ m_visibleTiles(new QGeoCameraTiles()),
++ m_prefetchTiles(new QGeoCameraTiles()),
++ m_mapScene(new QGeoTiledMapScene()),
++ m_tileRequests(nullptr),
++ m_maxZoomLevel(static_cast<int>(std::ceil(m_cameraCapabilities.maximumZoomLevel()))),
++ m_minZoomLevel(static_cast<int>(std::ceil(m_cameraCapabilities.minimumZoomLevel()))),
++ m_prefetchStyle(QGeoTiledMap::PrefetchTwoNeighbourLayers)
++ {
++ int tileSize = m_cameraCapabilities.tileSize();
++ QString pluginString(engine->managerName() + QLatin1Char('_') + QString::number(engine->managerVersion()));
++ m_visibleTiles->setTileSize(tileSize);
++ m_prefetchTiles->setTileSize(tileSize);
++ m_visibleTiles->setPluginString(pluginString);
++ m_prefetchTiles->setPluginString(pluginString);
++ m_mapScene->setTileSize(tileSize);
++ }
++
++ QGeoTiledMapPrivate::~QGeoTiledMapPrivate()
++ {
++ // controller_ is a child of map_, don't need to delete it here
++
++ delete m_mapScene;
++ delete m_visibleTiles;
++ delete m_prefetchTiles;
++
++ // TODO map items are not deallocated!
++ // However: how to ensure this is done in rendering thread?
++ }
++
++ void QGeoTiledMapPrivate::prefetchTiles()
++ {
++ if (m_tileRequests && m_prefetchStyle != QGeoTiledMap::NoPrefetching) {
++
++ QSet<QGeoTileSpec> tiles;
++ QGeoCameraData camera = m_visibleTiles->cameraData();
++ int currentIntZoom = static_cast<int>(std::floor(camera.zoomLevel()));
++
++ m_prefetchTiles->setCameraData(camera);
++ m_prefetchTiles->setViewExpansion(PREFETCH_FRUSTUM_SCALE);
++ tiles = m_prefetchTiles->createTiles();
++
++ switch (m_prefetchStyle) {
++
++ case QGeoTiledMap::PrefetchNeighbourLayer: {
++ double zoomFraction = camera.zoomLevel() - currentIntZoom;
++ int nearestNeighbourLayer = zoomFraction > 0.5 ? currentIntZoom + 1 : currentIntZoom - 1;
++ if (nearestNeighbourLayer <= m_maxZoomLevel && nearestNeighbourLayer >= m_minZoomLevel) {
++ camera.setZoomLevel(nearestNeighbourLayer);
++ // Approx heuristic, keeping total # prefetched tiles roughly independent of the
++ // fractional zoom level.
++ double neighbourScale = (1.0 + zoomFraction)/2.0;
++ m_prefetchTiles->setCameraData(camera);
++ m_prefetchTiles->setViewExpansion(PREFETCH_FRUSTUM_SCALE * neighbourScale);
++ tiles += m_prefetchTiles->createTiles();
++ }
++ }
++ break;
++
++ case QGeoTiledMap::PrefetchTwoNeighbourLayers: {
++ // This is a simpler strategy, we just prefetch from layer above and below
++ // for the layer below we only use half the size as this fills the screen
++ if (currentIntZoom > m_minZoomLevel) {
++ camera.setZoomLevel(currentIntZoom - 1);
++ m_prefetchTiles->setCameraData(camera);
++ m_prefetchTiles->setViewExpansion(0.5);
++ tiles += m_prefetchTiles->createTiles();
++ }
++
++ if (currentIntZoom < m_maxZoomLevel) {
++ camera.setZoomLevel(currentIntZoom + 1);
++ m_prefetchTiles->setCameraData(camera);
++ m_prefetchTiles->setViewExpansion(1.0);
++ tiles += m_prefetchTiles->createTiles();
++ }
++ }
++ break;
++
++ default:
++ break;
++ }
++
++ m_tileRequests->requestTiles(tiles - m_mapScene->texturedTiles());
++ }
++ }
++
++ QGeoMapType QGeoTiledMapPrivate::activeMapType() const
++ {
++ return m_visibleTiles->activeMapType();
++ }
++
++ // Called before changeCameraData
++ void QGeoTiledMapPrivate::onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities)
++ {
++ // Handle varying min/maxZoomLevel
++ if (oldCameraCapabilities.minimumZoomLevel() != m_cameraCapabilities.minimumZoomLevel())
++ m_minZoomLevel = static_cast<int>(std::ceil(m_cameraCapabilities.minimumZoomLevel()));
++ if (oldCameraCapabilities.maximumZoomLevel() != m_cameraCapabilities.maximumZoomLevel())
++ m_maxZoomLevel = static_cast<int>(std::ceil(m_cameraCapabilities.maximumZoomLevel()));
++
++ // Handle varying tile size
++ if (oldCameraCapabilities.tileSize() != m_cameraCapabilities.tileSize()) {
++ m_visibleTiles->setTileSize(oldCameraCapabilities.tileSize());
++ m_prefetchTiles->setTileSize(oldCameraCapabilities.tileSize());
++ m_mapScene->setTileSize(oldCameraCapabilities.tileSize());
++ }
++ }
++
++ void QGeoTiledMapPrivate::changeCameraData(const QGeoCameraData &cameraData)
++ {
++ Q_Q(QGeoTiledMap);
++
++ QGeoCameraData cam = cameraData;
++
++ // The incoming zoom level is intended for a tileSize of 256.
++ // Adapt it to the current tileSize
++ double zoomLevel = cameraData.zoomLevel();
++ if (m_visibleTiles->tileSize() != 256)
++ zoomLevel = std::log(std::pow(2.0, zoomLevel) * 256.0 / m_visibleTiles->tileSize()) * (1.0 / std::log(2.0));
++ cam.setZoomLevel(zoomLevel);
++
++ // For zoomlevel, "snap" 0.01 either side of a whole number.
++ // This is so that when we turn off bilinear scaling, we're
++ // snapped to the exact pixel size of the tiles
++ int izl = static_cast<int>(std::floor(cam.zoomLevel()));
++ float delta = cam.zoomLevel() - izl;
++
++ if (delta > 0.5) {
++ izl++;
++ delta -= 1.0;
++ }
++
++ // TODO: Don't do this if there's tilt or bearing.
++ if (qAbs(delta) < 0.01) {
++ cam.setZoomLevel(izl);
++ }
++
++ m_visibleTiles->setCameraData(cam);
++ m_mapScene->setCameraData(cam);
++
++ updateScene();
++ q->sgNodeChanged(); // ToDo: explain why emitting twice
++ }
++
++ void QGeoTiledMapPrivate::updateScene()
++ {
++ Q_Q(QGeoTiledMap);
++ // detect if new tiles introduced
++ const QSet<QGeoTileSpec>& tiles = m_visibleTiles->createTiles();
++ bool newTilesIntroduced = !m_mapScene->visibleTiles().contains(tiles);
++ m_mapScene->setVisibleTiles(tiles);
++
++ if (newTilesIntroduced && m_copyrightVisible)
++ q->evaluateCopyrights(tiles);
++
++ // don't request tiles that are already built and textured
++ QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > cachedTiles =
++ m_tileRequests->requestTiles(m_visibleTiles->createTiles() - m_mapScene->texturedTiles());
++
++ for (auto it = cachedTiles.cbegin(); it != cachedTiles.cend(); ++it)
++ m_mapScene->addTile(it.key(), it.value());
++
++ if (!cachedTiles.isEmpty())
++ emit q->sgNodeChanged();
++ }
++
++ void QGeoTiledMapPrivate::setVisibleArea(const QRectF &visibleArea)
++ {
++ Q_Q(QGeoTiledMap);
++ const QRectF va = clampVisibleArea(visibleArea);
++ if (va == m_visibleArea)
++ return;
++
++ m_visibleArea = va;
++ m_geoProjection->setVisibleArea(va);
++
++ m_visibleTiles->setVisibleArea(va);
++ m_prefetchTiles->setVisibleArea(va);
++ m_mapScene->setVisibleArea(va);
++
++ if (m_copyrightVisible)
++ q->evaluateCopyrights(m_mapScene->visibleTiles());
++ updateScene();
++ q->sgNodeChanged(); // ToDo: explain why emitting twice
++ }
++
++ QRectF QGeoTiledMapPrivate::visibleArea() const
++ {
++ return m_visibleArea;
++ }
++
++ void QGeoTiledMapPrivate::changeActiveMapType(const QGeoMapType &mapType)
++ {
++ m_visibleTiles->setTileSize(m_cameraCapabilities.tileSize());
++ m_prefetchTiles->setTileSize(m_cameraCapabilities.tileSize());
++ m_mapScene->setTileSize(m_cameraCapabilities.tileSize());
++ m_visibleTiles->setMapType(mapType);
++ m_prefetchTiles->setMapType(mapType);
++ changeCameraData(m_cameraData); // Updates the zoom level to the possibly new tile size
++ // updateScene called in changeCameraData()
++ }
++
++ void QGeoTiledMapPrivate::changeTileVersion(int version)
++ {
++ m_visibleTiles->setMapVersion(version);
++ m_prefetchTiles->setMapVersion(version);
++ updateScene();
++ }
++
++ void QGeoTiledMapPrivate::clearScene()
++ {
++ m_mapScene->clearTexturedTiles();
++ m_mapScene->setVisibleTiles(QSet<QGeoTileSpec>());
++ updateScene();
++ }
++
++ void QGeoTiledMapPrivate::changeViewportSize(const QSize& size)
++ {
++ Q_Q(QGeoTiledMap);
++
++ m_visibleTiles->setScreenSize(size);
++ m_prefetchTiles->setScreenSize(size);
++ m_mapScene->setScreenSize(size);
++
++
++ if (!size.isEmpty() && m_cache) {
++ // absolute minimum size: one tile each side of display, 32-bit colour
++ int texCacheSize = (size.width() + m_visibleTiles->tileSize() * 2) *
++ (size.height() + m_visibleTiles->tileSize() * 2) * 4;
++
++ // multiply by 3 so the 'recent' list in the cache is big enough for
++ // an entire display of tiles
++ texCacheSize *= 3;
++ // TODO: move this reasoning into the tilecache
++
++ int newSize = qMax(m_cache->minTextureUsage(), texCacheSize);
++ m_cache->setMinTextureUsage(newSize);
++ }
++
++ if (m_copyrightVisible)
++ q->evaluateCopyrights(m_mapScene->visibleTiles());
++ updateScene();
++ }
++
++ void QGeoTiledMapPrivate::updateTile(const QGeoTileSpec &spec)
++ {
++ Q_Q(QGeoTiledMap);
++ // Only promote the texture up to GPU if it is visible
++ if (m_visibleTiles->createTiles().contains(spec)){
++ QSharedPointer<QGeoTileTexture> tex = m_tileRequests->tileTexture(spec);
++ if (!tex.isNull() && !tex->image.isNull()) {
++ m_mapScene->addTile(spec, tex);
++ emit q->sgNodeChanged();
++ }
++ }
++ }
++
++ QSGNode *QGeoTiledMapPrivate::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window)
++ {
++ return m_mapScene->updateSceneGraph(oldNode, window);
++ }
++
++ QT_END_NAMESPACE