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 | |
43 | QT_BEGIN_NAMESPACE |
44 | |
45 | class RetryFuture; |
46 | |
47 | class QGeoTileRequestManagerPrivate |
48 | { |
49 | public: |
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 | |
66 | QGeoTileRequestManager::QGeoTileRequestManager(QGeoTiledMap *map, QGeoTiledMappingManagerEngine *engine) |
67 | : d_ptr(new QGeoTileRequestManagerPrivate(map, engine)) |
68 | { |
69 | |
70 | } |
71 | |
72 | QGeoTileRequestManager::~QGeoTileRequestManager() |
73 | { |
74 | |
75 | } |
76 | |
77 | QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > QGeoTileRequestManager::requestTiles(const QSet<QGeoTileSpec> &tiles) |
78 | { |
79 | return d_ptr->requestTiles(tiles); |
80 | } |
81 | |
82 | void QGeoTileRequestManager::tileFetched(const QGeoTileSpec &spec) |
83 | { |
84 | d_ptr->tileFetched(spec); |
85 | } |
86 | |
87 | QSharedPointer<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 | |
95 | void QGeoTileRequestManager::tileError(const QGeoTileSpec &tile, const QString &errorString) |
96 | { |
97 | d_ptr->tileError(tile, errorString); |
98 | } |
99 | |
100 | QGeoTileRequestManagerPrivate::QGeoTileRequestManagerPrivate(QGeoTiledMap *map,QGeoTiledMappingManagerEngine *engine) |
101 | : m_map(map), |
102 | m_engine(engine) |
103 | { |
104 | } |
105 | |
106 | QGeoTileRequestManagerPrivate::~QGeoTileRequestManagerPrivate() |
107 | { |
108 | } |
109 | |
110 | QMap<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 | |
178 | void 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 |
187 | class RetryFuture : public QObject |
188 | { |
189 | Q_OBJECT |
190 | public: |
191 | RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent = 0); |
192 | |
193 | public Q_SLOTS: |
194 | void retry(); |
195 | |
196 | private: |
197 | QGeoTileSpec m_tile; |
198 | QGeoTiledMap *m_map; |
199 | QPointer<QGeoTiledMappingManagerEngine> m_engine; |
200 | }; |
201 | |
202 | RetryFuture::RetryFuture(const QGeoTileSpec &tile, QGeoTiledMap *map, QGeoTiledMappingManagerEngine* engine, QObject *parent) |
203 | : QObject(parent), m_tile(tile), m_map(map), m_engine(engine) |
204 | {} |
205 | |
206 | void 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 | |
215 | void 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 | |
243 | QT_END_NAMESPACE |
244 | |
245 | #include "qgeotilerequestmanager.moc" |
246 | |