1/****************************************************************************
2**
3** Copyright (C) 2015 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#include "qgeotilerequestmanager_p.h"
37#include "qgeotilespec_p.h"
38#include "qgeotiledmap_p.h"
39#include "qgeotiledmappingmanagerengine_p.h"
40#include "qabstractgeotilecache_p.h"
41#include <QtCore/QPointer>
42
43QT_BEGIN_NAMESPACE
44
45class RetryFuture;
46
47class QGeoTileRequestManagerPrivate
48{
49public:
50 explicit QGeoTileRequestManagerPrivate(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine);
51 ~QGeoTileRequestManagerPrivate();
52
53 QGeoTiledMap *m_map;
54 QPointer<QGeoTiledMappingManagerEngine> m_engine;
55
56 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > requestTiles(const QSet<QGeoTileSpec> &tiles);
57 void tileError(const QGeoTileSpec &tile, const QString &errorString);
58
59 QHash<QGeoTileSpec, int> m_retries;
60 QHash<QGeoTileSpec, QSharedPointer<RetryFuture> > m_futures;
61 QSet<QGeoTileSpec> m_requested;
62
63 void tileFetched(const QGeoTileSpec &spec);
64};
65
66QGeoTileRequestManager::QGeoTileRequestManager(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine)
67 : d_ptr(new QGeoTileRequestManagerPrivate(map, engine))
68{
69
70}
71
72QGeoTileRequestManager::~QGeoTileRequestManager()
73{
74
75}
76
77QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > QGeoTileRequestManager::requestTiles(const QSet<QGeoTileSpec> &tiles)
78{
79 return d_ptr->requestTiles(tiles);
80}
81
82void QGeoTileRequestManager::tileFetched(const QGeoTileSpec &spec)
83{
84 d_ptr->tileFetched(spec);
85}
86
87QSharedPointer<QGeoTileTexture> QGeoTileRequestManager::tileTexture(const QGeoTileSpec &spec)
88{
89 if (d_ptr->m_engine)
90 return d_ptr->m_engine->getTileTexture(spec);
91 else
92 return QSharedPointer<QGeoTileTexture>();
93}
94
95void QGeoTileRequestManager::tileError(const QGeoTileSpec &tile, const QString &errorString)
96{
97 d_ptr->tileError(tile, errorString);
98}
99
100QGeoTileRequestManagerPrivate::QGeoTileRequestManagerPrivate(QGeoTiledMap *map,QGeoTiledMappingManagerEngine *engine)
101 : m_map(map),
102 m_engine(engine)
103{
104}
105
106QGeoTileRequestManagerPrivate::~QGeoTileRequestManagerPrivate()
107{
108}
109
110QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > QGeoTileRequestManagerPrivate::requestTiles(const QSet<QGeoTileSpec> &tiles)
111{
112 QSet<QGeoTileSpec> cancelTiles = m_requested - tiles;
113 QSet<QGeoTileSpec> requestTiles = tiles - m_requested;
114 QSet<QGeoTileSpec> cached;
115// int tileSize = tiles.size();
116// int newTiles = requestTiles.size();
117
118 typedef QSet<QGeoTileSpec>::const_iterator iter;
119
120 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > cachedTex;
121
122 // remove tiles in cache from request tiles
123 if (!m_engine.isNull()) {
124 iter i = requestTiles.constBegin();
125 iter end = requestTiles.constEnd();
126 for (; i != end; ++i) {
127 QGeoTileSpec tile = *i;
128 QSharedPointer<QGeoTileTexture> tex = m_engine->getTileTexture(spec: tile);
129 if (tex) {
130 if (!tex->image.isNull())
131 cachedTex.insert(akey: tile, avalue: tex);
132 cached.insert(value: tile);
133 } else {
134 // Try to use textures from lower zoom levels, but still request the proper tile
135 QGeoTileSpec spec = tile;
136 const int endRange = qMax(a: 0, b: tile.zoom() - 4); // Using up to 4 zoom levels up. 4 is arbitrary.
137 for (int z = tile.zoom() - 1; z >= endRange; z--) {
138 int denominator = 1 << (tile.zoom() - z);
139 spec.setZoom(z);
140 spec.setX(tile.x() / denominator);
141 spec.setY(tile.y() / denominator);
142 QSharedPointer<QGeoTileTexture> t = m_engine->getTileTexture(spec);
143 if (t && !t->image.isNull()) {
144 cachedTex.insert(akey: tile, avalue: t);
145 break;
146 }
147 }
148 }
149 }
150 }
151
152 requestTiles -= cached;
153
154 m_requested -= cancelTiles;
155 m_requested += requestTiles;
156
157// qDebug() << "required # tiles: " << tileSize << ", new tiles: " << newTiles << ", total server requests: " << requested_.size();
158
159 if (!requestTiles.isEmpty() || !cancelTiles.isEmpty()) {
160 if (!m_engine.isNull()) {
161// qDebug() << "new server requests: " << requestTiles.size() << ", server cancels: " << cancelTiles.size();
162 m_engine->updateTileRequests(map: m_map, tilesAdded: requestTiles, tilesRemoved: cancelTiles);
163
164 // Remove any cancelled tiles from the error retry hash to avoid
165 // re-using the numbers for a totally different request cycle.
166 iter i = cancelTiles.constBegin();
167 iter end = cancelTiles.constEnd();
168 for (; i != end; ++i) {
169 m_retries.remove(akey: *i);
170 m_futures.remove(akey: *i);
171 }
172 }
173 }
174
175 return cachedTex;
176}
177
178void QGeoTileRequestManagerPrivate::tileFetched(const QGeoTileSpec &spec)
179{
180 m_map->updateTile(spec);
181 m_requested.remove(value: spec);
182 m_retries.remove(akey: spec);
183 m_futures.remove(akey: spec);
184}
185
186// Represents a tile that needs to be retried after a certain period of time
187class RetryFuture : public QObject
188{
189 Q_OBJECT
190public:
191 RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent = 0);
192
193public Q_SLOTS:
194 void retry();
195
196private:
197 QGeoTileSpec m_tile;
198 QGeoTiledMap *m_map;
199 QPointer<QGeoTiledMappingManagerEngine> m_engine;
200};
201
202RetryFuture::RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent)
203 : QObject(parent), m_tile(tile), m_map(map), m_engine(engine)
204{}
205
206void RetryFuture::retry()
207{
208 QSet<QGeoTileSpec> requestTiles;
209 QSet<QGeoTileSpec> cancelTiles;
210 requestTiles.insert(value: m_tile);
211 if (!m_engine.isNull())
212 m_engine->updateTileRequests(map: m_map, tilesAdded: requestTiles, tilesRemoved: cancelTiles);
213}
214
215void QGeoTileRequestManagerPrivate::tileError(const QGeoTileSpec &tile, const QString &errorString)
216{
217 if (m_requested.contains(value: tile)) {
218 int count = m_retries.value(akey: tile, adefaultValue: 0);
219 m_retries.insert(akey: tile, avalue: count + 1);
220
221 if (count >= 5) {
222 qWarning(msg: "QGeoTileRequestManager: Failed to fetch tile (%d,%d,%d) 5 times, giving up. "
223 "Last error message was: '%s'",
224 tile.x(), tile.y(), tile.zoom(), qPrintable(errorString));
225 m_requested.remove(value: tile);
226 m_retries.remove(akey: tile);
227 m_futures.remove(akey: tile);
228
229 } else {
230 // Exponential time backoff when retrying
231 int delay = (1 << count) * 500;
232
233 QSharedPointer<RetryFuture> future(new RetryFuture(tile,m_map,m_engine));
234 m_futures.insert(akey: tile, avalue: future);
235
236 QTimer::singleShot(msec: delay, receiver: future.data(), SLOT(retry()));
237 // Passing .data() to singleShot is ok -- Qt will clean up the
238 // connection if the target qobject is deleted
239 }
240 }
241}
242
243QT_END_NAMESPACE
244
245#include "qgeotilerequestmanager.moc"
246

source code of qtlocation/src/location/maps/qgeotilerequestmanager.cpp