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 "qgeotiledmap_p.h"
37#include "qgeotiledmap_p_p.h"
38#include <QtPositioning/private/qwebmercator_p.h>
39#include "qgeotiledmappingmanagerengine_p.h"
40#include "qabstractgeotilecache_p.h"
41#include "qgeotilespec_p.h"
42#include "qgeoprojection_p.h"
43
44#include "qgeocameratiles_p.h"
45#include "qgeotilerequestmanager_p.h"
46#include "qgeotiledmapscene_p.h"
47#include "qgeocameracapabilities_p.h"
48#include <cmath>
49
50QT_BEGIN_NAMESPACE
51#define PREFETCH_FRUSTUM_SCALE 2.0
52
53static const double invLog2 = 1.0 / std::log(x: 2.0);
54
55static double zoomLevelFrom256(double zoomLevelFor256, double tileSize)
56{
57 return std::log( x: std::pow(x: 2.0, y: zoomLevelFor256) * 256.0 / tileSize ) * invLog2;
58}
59
60QGeoTiledMap::QGeoTiledMap(QGeoTiledMappingManagerEngine *engine, QObject *parent)
61 : QGeoMap(*new QGeoTiledMapPrivate(engine), parent)
62{
63 Q_D(QGeoTiledMap);
64
65 d->m_tileRequests = new QGeoTileRequestManager(this, engine);
66
67 QObject::connect(sender: engine,signal: &QGeoTiledMappingManagerEngine::tileVersionChanged,
68 receiver: this,slot: &QGeoTiledMap::handleTileVersionChanged);
69 QObject::connect(sender: this, signal: &QGeoMap::cameraCapabilitiesChanged,
70 slot: [d](const QGeoCameraCapabilities &oldCameraCapabilities) {
71 d->onCameraCapabilitiesChanged(oldCameraCapabilities);
72 });
73}
74
75QGeoTiledMap::QGeoTiledMap(QGeoTiledMapPrivate &dd, QGeoTiledMappingManagerEngine *engine, QObject *parent)
76 : QGeoMap(dd, parent)
77{
78 Q_D(QGeoTiledMap);
79
80 d->m_tileRequests = new QGeoTileRequestManager(this, engine);
81
82 QObject::connect(sender: engine,signal: &QGeoTiledMappingManagerEngine::tileVersionChanged,
83 receiver: this,slot: &QGeoTiledMap::handleTileVersionChanged);
84 QObject::connect(sender: this, signal: &QGeoMap::cameraCapabilitiesChanged,
85 slot: [d](const QGeoCameraCapabilities &oldCameraCapabilities) {
86 d->onCameraCapabilitiesChanged(oldCameraCapabilities);
87 });
88}
89
90QGeoTiledMap::~QGeoTiledMap()
91{
92 Q_D(QGeoTiledMap);
93 delete d->m_tileRequests;
94 d->m_tileRequests = 0;
95
96 if (!d->m_engine.isNull()) {
97 QGeoTiledMappingManagerEngine *engine = qobject_cast<QGeoTiledMappingManagerEngine*>(object: d->m_engine);
98 Q_ASSERT(engine);
99 engine->releaseMap(map: this);
100 }
101}
102
103QGeoTileRequestManager *QGeoTiledMap::requestManager()
104{
105 Q_D(QGeoTiledMap);
106 return d->m_tileRequests;
107}
108
109void QGeoTiledMap::updateTile(const QGeoTileSpec &spec)
110{
111 Q_D(QGeoTiledMap);
112 d->updateTile(spec);
113}
114
115void QGeoTiledMap::setPrefetchStyle(QGeoTiledMap::PrefetchStyle style)
116{
117 Q_D(QGeoTiledMap);
118 d->m_prefetchStyle = style;
119}
120
121QAbstractGeoTileCache *QGeoTiledMap::tileCache()
122{
123 Q_D(QGeoTiledMap);
124 return d->m_cache;
125}
126
127QSGNode *QGeoTiledMap::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window)
128{
129 Q_D(QGeoTiledMap);
130 return d->updateSceneGraph(node: oldNode, window);
131}
132
133void QGeoTiledMap::prefetchData()
134{
135 Q_D(QGeoTiledMap);
136 d->prefetchTiles();
137}
138
139void QGeoTiledMap::clearData()
140{
141 Q_D(QGeoTiledMap);
142 d->m_cache->clearAll();
143 d->m_mapScene->clearTexturedTiles();
144 d->updateScene();
145 sgNodeChanged();
146}
147
148QGeoMap::Capabilities QGeoTiledMap::capabilities() const
149{
150 return Capabilities(SupportsVisibleRegion
151 | SupportsSetBearing
152 | SupportsAnchoringCoordinate
153 | SupportsVisibleArea);
154}
155
156void QGeoTiledMap::setCopyrightVisible(bool visible)
157{
158 Q_D(QGeoTiledMap);
159 if (visible == d->m_copyrightVisible)
160 return;
161
162 QGeoMap::setCopyrightVisible(visible);
163 if (visible)
164 evaluateCopyrights(visibleTiles: d->m_mapScene->visibleTiles());
165}
166
167void QGeoTiledMap::clearScene(int mapId)
168{
169 Q_D(QGeoTiledMap);
170 if (activeMapType().mapId() == mapId)
171 d->clearScene();
172}
173
174void QGeoTiledMap::handleTileVersionChanged()
175{
176 Q_D(QGeoTiledMap);
177 if (!d->m_engine.isNull()) {
178 QGeoTiledMappingManagerEngine* engine = qobject_cast<QGeoTiledMappingManagerEngine*>(object: d->m_engine);
179 Q_ASSERT(engine);
180 d->changeTileVersion(version: engine->tileVersion());
181 }
182}
183
184void QGeoTiledMap::evaluateCopyrights(const QSet<QGeoTileSpec> &visibleTiles)
185{
186 Q_UNUSED(visibleTiles);
187}
188
189QGeoTiledMapPrivate::QGeoTiledMapPrivate(QGeoTiledMappingManagerEngine *engine)
190 : QGeoMapPrivate(engine, new QGeoProjectionWebMercator),
191 m_cache(engine->tileCache()),
192 m_visibleTiles(new QGeoCameraTiles()),
193 m_prefetchTiles(new QGeoCameraTiles()),
194 m_mapScene(new QGeoTiledMapScene()),
195 m_tileRequests(0),
196 m_maxZoomLevel(static_cast<int>(std::ceil(x: m_cameraCapabilities.maximumZoomLevel()))),
197 m_minZoomLevel(static_cast<int>(std::ceil(x: m_cameraCapabilities.minimumZoomLevel()))),
198 m_prefetchStyle(QGeoTiledMap::PrefetchTwoNeighbourLayers)
199{
200 int tileSize = m_cameraCapabilities.tileSize();
201 QString pluginString(engine->managerName() + QLatin1Char('_') + QString::number(engine->managerVersion()));
202 m_visibleTiles->setTileSize(tileSize);
203 m_prefetchTiles->setTileSize(tileSize);
204 m_visibleTiles->setPluginString(pluginString);
205 m_prefetchTiles->setPluginString(pluginString);
206 m_mapScene->setTileSize(tileSize);
207}
208
209QGeoTiledMapPrivate::~QGeoTiledMapPrivate()
210{
211 // controller_ is a child of map_, don't need to delete it here
212
213 delete m_mapScene;
214 delete m_visibleTiles;
215 delete m_prefetchTiles;
216
217 // TODO map items are not deallocated!
218 // However: how to ensure this is done in rendering thread?
219}
220
221void QGeoTiledMapPrivate::prefetchTiles()
222{
223 if (m_tileRequests && m_prefetchStyle != QGeoTiledMap::NoPrefetching) {
224
225 QSet<QGeoTileSpec> tiles;
226 QGeoCameraData camera = m_visibleTiles->cameraData();
227 int currentIntZoom = static_cast<int>(std::floor(x: camera.zoomLevel()));
228
229 m_prefetchTiles->setCameraData(camera);
230 m_prefetchTiles->setViewExpansion(PREFETCH_FRUSTUM_SCALE);
231 tiles = m_prefetchTiles->createTiles();
232
233 switch (m_prefetchStyle) {
234
235 case QGeoTiledMap::PrefetchNeighbourLayer: {
236 double zoomFraction = camera.zoomLevel() - currentIntZoom;
237 int nearestNeighbourLayer = zoomFraction > 0.5 ? currentIntZoom + 1 : currentIntZoom - 1;
238 if (nearestNeighbourLayer <= m_maxZoomLevel && nearestNeighbourLayer >= m_minZoomLevel) {
239 camera.setZoomLevel(nearestNeighbourLayer);
240 // Approx heuristic, keeping total # prefetched tiles roughly independent of the
241 // fractional zoom level.
242 double neighbourScale = (1.0 + zoomFraction)/2.0;
243 m_prefetchTiles->setCameraData(camera);
244 m_prefetchTiles->setViewExpansion(PREFETCH_FRUSTUM_SCALE * neighbourScale);
245 tiles += m_prefetchTiles->createTiles();
246 }
247 }
248 break;
249
250 case QGeoTiledMap::PrefetchTwoNeighbourLayers: {
251 // This is a simpler strategy, we just prefetch from layer above and below
252 // for the layer below we only use half the size as this fills the screen
253 if (currentIntZoom > m_minZoomLevel) {
254 camera.setZoomLevel(currentIntZoom - 1);
255 m_prefetchTiles->setCameraData(camera);
256 m_prefetchTiles->setViewExpansion(0.5);
257 tiles += m_prefetchTiles->createTiles();
258 }
259
260 if (currentIntZoom < m_maxZoomLevel) {
261 camera.setZoomLevel(currentIntZoom + 1);
262 m_prefetchTiles->setCameraData(camera);
263 m_prefetchTiles->setViewExpansion(1.0);
264 tiles += m_prefetchTiles->createTiles();
265 }
266 }
267 break;
268
269 default:
270 break;
271 }
272
273 m_tileRequests->requestTiles(tiles: tiles - m_mapScene->texturedTiles());
274 }
275}
276
277QGeoMapType QGeoTiledMapPrivate::activeMapType()
278{
279 return m_visibleTiles->activeMapType();
280}
281
282// Called before changeCameraData
283void QGeoTiledMapPrivate::onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities)
284{
285 // Handle varying min/maxZoomLevel
286 if (oldCameraCapabilities.minimumZoomLevel() != m_cameraCapabilities.minimumZoomLevel())
287 m_minZoomLevel = static_cast<int>(std::ceil(x: m_cameraCapabilities.minimumZoomLevel()));
288 if (oldCameraCapabilities.maximumZoomLevel() != m_cameraCapabilities.maximumZoomLevel())
289 m_maxZoomLevel = static_cast<int>(std::ceil(x: m_cameraCapabilities.maximumZoomLevel()));
290
291 // Handle varying tile size
292 if (oldCameraCapabilities.tileSize() != m_cameraCapabilities.tileSize()) {
293 m_visibleTiles->setTileSize(oldCameraCapabilities.tileSize());
294 m_prefetchTiles->setTileSize(oldCameraCapabilities.tileSize());
295 m_mapScene->setTileSize(oldCameraCapabilities.tileSize());
296 }
297}
298
299void QGeoTiledMapPrivate::changeCameraData(const QGeoCameraData &cameraData)
300{
301 Q_Q(QGeoTiledMap);
302
303 QGeoCameraData cam = cameraData;
304
305 // The incoming zoom level is intended for a tileSize of 256.
306 // Adapt it to the current tileSize
307 double zoomLevel = cameraData.zoomLevel();
308 if (m_visibleTiles->tileSize() != 256)
309 zoomLevel = zoomLevelFrom256(zoomLevelFor256: zoomLevel, tileSize: m_visibleTiles->tileSize());
310 cam.setZoomLevel(zoomLevel);
311
312 // For zoomlevel, "snap" 0.01 either side of a whole number.
313 // This is so that when we turn off bilinear scaling, we're
314 // snapped to the exact pixel size of the tiles
315 int izl = static_cast<int>(std::floor(x: cam.zoomLevel()));
316 float delta = cam.zoomLevel() - izl;
317
318 if (delta > 0.5) {
319 izl++;
320 delta -= 1.0;
321 }
322
323 // TODO: Don't do this if there's tilt or bearing.
324 if (qAbs(t: delta) < 0.01) {
325 cam.setZoomLevel(izl);
326 }
327
328 m_visibleTiles->setCameraData(cam);
329 m_mapScene->setCameraData(cam);
330
331 updateScene();
332 q->sgNodeChanged(); // ToDo: explain why emitting twice
333}
334
335void QGeoTiledMapPrivate::updateScene()
336{
337 Q_Q(QGeoTiledMap);
338 // detect if new tiles introduced
339 const QSet<QGeoTileSpec>& tiles = m_visibleTiles->createTiles();
340 bool newTilesIntroduced = !m_mapScene->visibleTiles().contains(other: tiles);
341 m_mapScene->setVisibleTiles(tiles);
342
343 if (newTilesIntroduced && m_copyrightVisible)
344 q->evaluateCopyrights(visibleTiles: tiles);
345
346 // don't request tiles that are already built and textured
347 QMap<QGeoTileSpec, QSharedPointer<QGeoTileTexture> > cachedTiles =
348 m_tileRequests->requestTiles(tiles: m_visibleTiles->createTiles() - m_mapScene->texturedTiles());
349
350 for (auto it = cachedTiles.cbegin(); it != cachedTiles.cend(); ++it)
351 m_mapScene->addTile(spec: it.key(), texture: it.value());
352
353 if (!cachedTiles.isEmpty())
354 emit q->sgNodeChanged();
355}
356
357void QGeoTiledMapPrivate::setVisibleArea(const QRectF &visibleArea)
358{
359 Q_Q(QGeoTiledMap);
360 const QRectF va = clampVisibleArea(visibleArea);
361 if (va == m_visibleArea)
362 return;
363
364 m_visibleArea = va;
365 m_geoProjection->setVisibleArea(va);
366
367 m_visibleTiles->setVisibleArea(va);
368 m_prefetchTiles->setVisibleArea(va);
369 m_mapScene->setVisibleArea(va);
370
371 if (m_copyrightVisible)
372 q->evaluateCopyrights(visibleTiles: m_mapScene->visibleTiles());
373 updateScene();
374 q->sgNodeChanged(); // ToDo: explain why emitting twice
375}
376
377QRectF QGeoTiledMapPrivate::visibleArea() const
378{
379 return m_visibleArea;
380}
381
382void QGeoTiledMapPrivate::changeActiveMapType(const QGeoMapType mapType)
383{
384 m_visibleTiles->setTileSize(m_cameraCapabilities.tileSize());
385 m_prefetchTiles->setTileSize(m_cameraCapabilities.tileSize());
386 m_mapScene->setTileSize(m_cameraCapabilities.tileSize());
387 m_visibleTiles->setMapType(mapType);
388 m_prefetchTiles->setMapType(mapType);
389 changeCameraData(cameraData: m_cameraData); // Updates the zoom level to the possibly new tile size
390 // updateScene called in changeCameraData()
391}
392
393void QGeoTiledMapPrivate::changeTileVersion(int version)
394{
395 m_visibleTiles->setMapVersion(version);
396 m_prefetchTiles->setMapVersion(version);
397 updateScene();
398}
399
400void QGeoTiledMapPrivate::clearScene()
401{
402 m_mapScene->clearTexturedTiles();
403 m_mapScene->setVisibleTiles(QSet<QGeoTileSpec>());
404 updateScene();
405}
406
407void QGeoTiledMapPrivate::changeViewportSize(const QSize& size)
408{
409 Q_Q(QGeoTiledMap);
410
411 m_visibleTiles->setScreenSize(size);
412 m_prefetchTiles->setScreenSize(size);
413 m_mapScene->setScreenSize(size);
414
415
416 if (!size.isEmpty() && m_cache) {
417 // absolute minimum size: one tile each side of display, 32-bit colour
418 int texCacheSize = (size.width() + m_visibleTiles->tileSize() * 2) *
419 (size.height() + m_visibleTiles->tileSize() * 2) * 4;
420
421 // multiply by 3 so the 'recent' list in the cache is big enough for
422 // an entire display of tiles
423 texCacheSize *= 3;
424 // TODO: move this reasoning into the tilecache
425
426 int newSize = qMax(a: m_cache->minTextureUsage(), b: texCacheSize);
427 m_cache->setMinTextureUsage(newSize);
428 }
429
430 if (m_copyrightVisible)
431 q->evaluateCopyrights(visibleTiles: m_mapScene->visibleTiles());
432 updateScene();
433}
434
435void QGeoTiledMapPrivate::updateTile(const QGeoTileSpec &spec)
436{
437 Q_Q(QGeoTiledMap);
438 // Only promote the texture up to GPU if it is visible
439 if (m_visibleTiles->createTiles().contains(value: spec)){
440 QSharedPointer<QGeoTileTexture> tex = m_tileRequests->tileTexture(spec);
441 if (!tex.isNull() && !tex->image.isNull()) {
442 m_mapScene->addTile(spec, texture: tex);
443 emit q->sgNodeChanged();
444 }
445 }
446}
447
448QSGNode *QGeoTiledMapPrivate::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window)
449{
450 return m_mapScene->updateSceneGraph(oldNode, window);
451}
452
453QT_END_NAMESPACE
454

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