/**************************************************************************** ** ** 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 "qgeotilerequestmanager_p.h" #include "qgeotilespec_p.h" #include "qgeotiledmap_p.h" #include "qgeotiledmappingmanagerengine_p.h" #include "qabstractgeotilecache_p.h" #include QT_BEGIN_NAMESPACE class RetryFuture; class QGeoTileRequestManagerPrivate { public: explicit QGeoTileRequestManagerPrivate(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine); ~QGeoTileRequestManagerPrivate(); QGeoTiledMap *m_map; QPointer m_engine; QMap > requestTiles(const QSet &tiles); void tileError(const QGeoTileSpec &tile, const QString &errorString); QHash m_retries; QHash > m_futures; QSet m_requested; void tileFetched(const QGeoTileSpec &spec); }; QGeoTileRequestManager::QGeoTileRequestManager(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine) : d_ptr(new QGeoTileRequestManagerPrivate(map, engine)) { } QGeoTileRequestManager::~QGeoTileRequestManager() { } QMap > QGeoTileRequestManager::requestTiles(const QSet &tiles) { return d_ptr->requestTiles(tiles); } void QGeoTileRequestManager::tileFetched(const QGeoTileSpec &spec) { d_ptr->tileFetched(spec); } QSharedPointer QGeoTileRequestManager::tileTexture(const QGeoTileSpec &spec) { if (d_ptr->m_engine) return d_ptr->m_engine->getTileTexture(spec); else return QSharedPointer(); } void QGeoTileRequestManager::tileError(const QGeoTileSpec &tile, const QString &errorString) { d_ptr->tileError(tile, errorString); } QGeoTileRequestManagerPrivate::QGeoTileRequestManagerPrivate(QGeoTiledMap *map,QGeoTiledMappingManagerEngine *engine) : m_map(map), m_engine(engine) { } QGeoTileRequestManagerPrivate::~QGeoTileRequestManagerPrivate() { } QMap > QGeoTileRequestManagerPrivate::requestTiles(const QSet &tiles) { QSet cancelTiles = m_requested - tiles; QSet requestTiles = tiles - m_requested; QSet cached; // int tileSize = tiles.size(); // int newTiles = requestTiles.size(); typedef QSet::const_iterator iter; QMap > cachedTex; // remove tiles in cache from request tiles if (!m_engine.isNull()) { iter i = requestTiles.constBegin(); iter end = requestTiles.constEnd(); for (; i != end; ++i) { QGeoTileSpec tile = *i; QSharedPointer tex = m_engine->getTileTexture(tile); if (tex) { if (!tex->image.isNull()) cachedTex.insert(tile, tex); cached.insert(tile); } else { // Try to use textures from lower zoom levels, but still request the proper tile QGeoTileSpec spec = tile; const int endRange = qMax(0, tile.zoom() - 4); // Using up to 4 zoom levels up. 4 is arbitrary. for (int z = tile.zoom() - 1; z >= endRange; z--) { int denominator = 1 << (tile.zoom() - z); spec.setZoom(z); spec.setX(tile.x() / denominator); spec.setY(tile.y() / denominator); QSharedPointer t = m_engine->getTileTexture(spec); if (t && !t->image.isNull()) { cachedTex.insert(tile, t); break; } } } } } requestTiles -= cached; m_requested -= cancelTiles; m_requested += requestTiles; // qDebug() << "required # tiles: " << tileSize << ", new tiles: " << newTiles << ", total server requests: " << requested_.size(); if (!requestTiles.isEmpty() || !cancelTiles.isEmpty()) { if (!m_engine.isNull()) { // qDebug() << "new server requests: " << requestTiles.size() << ", server cancels: " << cancelTiles.size(); m_engine->updateTileRequests(m_map, requestTiles, cancelTiles); // Remove any cancelled tiles from the error retry hash to avoid // re-using the numbers for a totally different request cycle. iter i = cancelTiles.constBegin(); iter end = cancelTiles.constEnd(); for (; i != end; ++i) { m_retries.remove(*i); m_futures.remove(*i); } } } return cachedTex; } void QGeoTileRequestManagerPrivate::tileFetched(const QGeoTileSpec &spec) { m_map->updateTile(spec); m_requested.remove(spec); m_retries.remove(spec); m_futures.remove(spec); } // Represents a tile that needs to be retried after a certain period of time class RetryFuture : public QObject { Q_OBJECT public: RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent = 0); public Q_SLOTS: void retry(); private: QGeoTileSpec m_tile; QGeoTiledMap *m_map; QPointer m_engine; }; RetryFuture::RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent) : QObject(parent), m_tile(tile), m_map(map), m_engine(engine) {} void RetryFuture::retry() { QSet requestTiles; QSet cancelTiles; requestTiles.insert(m_tile); if (!m_engine.isNull()) m_engine->updateTileRequests(m_map, requestTiles, cancelTiles); } void QGeoTileRequestManagerPrivate::tileError(const QGeoTileSpec &tile, const QString &errorString) { if (m_requested.contains(tile)) { int count = m_retries.value(tile, 0); m_retries.insert(tile, count + 1); if (count >= 5) { qWarning("QGeoTileRequestManager: Failed to fetch tile (%d,%d,%d) 5 times, giving up. " "Last error message was: '%s'", tile.x(), tile.y(), tile.zoom(), qPrintable(errorString)); m_requested.remove(tile); m_retries.remove(tile); m_futures.remove(tile); } else { // Exponential time backoff when retrying int delay = (1 << count) * 500; QSharedPointer future(new RetryFuture(tile,m_map,m_engine)); m_futures.insert(tile, future); QTimer::singleShot(delay, future.data(), SLOT(retry())); // Passing .data() to singleShot is ok -- Qt will clean up the // connection if the target qobject is deleted } } } #include "qgeotilerequestmanager.moc" QT_END_NAMESPACE