blob: e6c91042b1ebe85594620ec6dfeba74009178683 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtLocation module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgeotiledmap_p.h"
#include "qgeotiledmap_p_p.h"
#include <QtPositioning/private/qwebmercator_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
static const double invLog2 = 1.0 / std::log(2.0);
static double zoomLevelFrom256(double zoomLevelFor256, double tileSize)
{
return std::log( std::pow(2.0, zoomLevelFor256) * 256.0 / tileSize ) * invLog2;
}
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,
[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,
[d](const QGeoCameraCapabilities &oldCameraCapabilities) {
d->onCameraCapabilitiesChanged(oldCameraCapabilities);
});
}
QGeoTiledMap::~QGeoTiledMap()
{
Q_D(QGeoTiledMap);
delete d->m_tileRequests;
d->m_tileRequests = 0;
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(0),
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()
{
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 = zoomLevelFrom256(zoomLevel, m_visibleTiles->tileSize());
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