Marble

VectorTileModel.cpp
1/*
2 SPDX-License-Identifier: LGPL-2.1-or-later
3
4 SPDX-FileCopyrightText: 2012 Ander Pijoan <ander.pijoan@deusto.es>
5 SPDX-FileCopyrightText: 2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6*/
7
8#include "VectorTileModel.h"
9
10#include "GeoDataDocument.h"
11#include "GeoDataTreeModel.h"
12#include "GeoSceneVectorTileDataset.h"
13#include "MarbleGlobal.h"
14#include "MarbleDebug.h"
15#include "MathHelper.h"
16#include "TileLoader.h"
17
18#include <qmath.h>
19#include <QThreadPool>
20
21namespace Marble
22{
23
24TileRunner::TileRunner(TileLoader *loader, const GeoSceneVectorTileDataset *tileDataset, const TileId &id) :
25 m_loader(loader),
26 m_tileDataset(tileDataset),
27 m_id(id)
28{
29}
30
31void TileRunner::run()
32{
33 GeoDataDocument *const document = m_loader->loadTileVectorData(m_tileDataset, m_id, DownloadBrowse);
34
35 emit documentLoaded(m_id, document);
36}
37
38VectorTileModel::CacheDocument::CacheDocument(GeoDataDocument *doc, VectorTileModel *vectorTileModel, const GeoDataLatLonBox &boundingBox) :
39 m_document(doc),
40 m_vectorTileModel(vectorTileModel),
41 m_boundingBox(boundingBox)
42{
43 // nothing to do
44}
45
46VectorTileModel::CacheDocument::~CacheDocument()
47{
48 m_vectorTileModel->removeTile(m_document);
49}
50
51VectorTileModel::VectorTileModel(TileLoader *loader, const GeoSceneVectorTileDataset *layer, GeoDataTreeModel *treeModel, QThreadPool *threadPool) :
52 m_loader(loader),
53 m_layer(layer),
54 m_treeModel(treeModel),
55 m_threadPool(threadPool),
56 m_tileLoadLevel(-1),
57 m_tileZoomLevel(-1),
58 m_deleteDocumentsLater(false)
59{
60 connect(this, SIGNAL(tileAdded(GeoDataDocument*)), treeModel, SLOT(addDocument(GeoDataDocument*)));
61 connect(this, SIGNAL(tileRemoved(GeoDataDocument*)), treeModel, SLOT(removeDocument(GeoDataDocument*)));
62 connect(treeModel, SIGNAL(removed(GeoDataObject*)), this, SLOT(cleanupTile(GeoDataObject*)));
63}
64
65void VectorTileModel::setViewport(const GeoDataLatLonBox &latLonBox)
66{
67 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
68 int const nTiles = smallScreen ? 12 : 20;
69 qreal const viewportArea = latLonBox.width() * latLonBox.height();
70 qreal const level = log((nTiles * 2.0 * M_PI * M_PI) / viewportArea) / log(4);
71 m_tileZoomLevel = qFloor(level);
72 int tileLoadLevel = m_tileZoomLevel;
73
74 // Determine available tile levels in the layer and thereby
75 // select the tileZoomLevel that is actually used:
76 QVector<int> tileLevels = m_layer->tileLevels();
77 if (tileLevels.isEmpty() /* || tileZoomLevel < tileLevels.first() */) {
78 // if there is no (matching) tile level then show nothing
79 // and bail out.
80 m_documents.clear();
81 return;
82 }
83 int tileLevel = tileLevels.first();
84 for (int i = 1, n = tileLevels.size(); i < n; ++i) {
85 if (tileLevels[i] > tileLoadLevel) {
86 break;
87 }
88 tileLevel = tileLevels[i];
89 }
90 tileLoadLevel = tileLevel;
91
92 // if zoom level has changed, empty vectortile cache
93 if (tileLoadLevel != m_tileLoadLevel) {
94 m_deleteDocumentsLater = m_tileLoadLevel >= 0;
95 m_tileLoadLevel = tileLoadLevel;
96 }
97
98 /** LOGIC FOR DOWNLOADING ALL THE TILES THAT ARE INSIDE THE SCREEN AT THE CURRENT ZOOM LEVEL **/
99
100 // New tiles X and Y for moved screen coordinates
101 // More info: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Subtiles
102 // More info: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#C.2FC.2B.2B
103 const QRect rect = m_layer->tileProjection()->tileIndexes(latLonBox, tileLoadLevel);
104
105 // Download tiles and send them to VectorTileLayer
106 // When changing zoom, download everything inside the screen
107 // TODO: hardcodes assumption about tiles indexing also ends at dateline
108 // TODO: what about crossing things in y direction?
109 if (!latLonBox.crossesDateLine()) {
110 queryTiles(tileLoadLevel, rect);
111 }
112 // When only moving screen, just download the new tiles
113 else {
114 // TODO: maxTileX (calculation knowledge) should be a property of tileProjection or m_layer
115 const int maxTileX = (1 << tileLoadLevel) * m_layer->levelZeroColumns() - 1;
116
117 queryTiles(tileLoadLevel, QRect(QPoint(0, rect.top()), rect.bottomRight()));
118 queryTiles(tileLoadLevel, QRect(rect.topLeft(), QPoint(maxTileX, rect.bottom())));
119 }
120 removeTilesOutOfView(latLonBox);
121}
122
123void VectorTileModel::removeTilesOutOfView(const GeoDataLatLonBox &boundingBox)
124{
125 GeoDataLatLonBox const extendedViewport = boundingBox.scaled(2.0, 2.0);
126 for (auto iter = m_documents.begin(); iter != m_documents.end();) {
127 bool const isOutOfView = !extendedViewport.intersects(iter.value()->latLonBox());
128 if (isOutOfView) {
129 iter = m_documents.erase(iter);
130 } else {
131 ++iter;
132 }
133 }
134}
135
136QString VectorTileModel::name() const
137{
138 return m_layer->name();
139}
140
141const GeoSceneVectorTileDataset *VectorTileModel::layer() const
142{
143 return m_layer;
144}
145
146void VectorTileModel::removeTile(GeoDataDocument *document)
147{
148 emit tileRemoved(document);
149}
150
151int VectorTileModel::tileZoomLevel() const
152{
153 return m_tileZoomLevel;
154}
155
156int VectorTileModel::cachedDocuments() const
157{
158 return m_documents.size();
159}
160
161void VectorTileModel::reload()
162{
163 for (auto const &tile : m_documents.keys()) {
164 m_loader->downloadTile(m_layer, tile, DownloadBrowse);
165 }
166}
167
168void VectorTileModel::updateTile(const TileId &idWithMapThemeHash, GeoDataDocument *document)
169{
170 TileId const id(0, idWithMapThemeHash.zoomLevel(), idWithMapThemeHash.x(), idWithMapThemeHash.y());
171 m_pendingDocuments.removeAll(id);
172 if (!document) {
173 return;
174 }
175
176 if (m_tileLoadLevel != id.zoomLevel()) {
177 delete document;
178 return;
179 }
180
181 document->setName(QString("%1/%2/%3").arg(id.zoomLevel()).arg(id.x()).arg(id.y()));
182 m_garbageQueue << document;
183 if (m_documents.contains(id)) {
184 m_documents.remove(id);
185 }
186 if (m_deleteDocumentsLater) {
187 m_deleteDocumentsLater = false;
188 m_documents.clear();
189 }
190 const GeoDataLatLonBox boundingBox = m_layer->tileProjection()->geoCoordinates(id);
191 m_documents[id] = QSharedPointer<CacheDocument>(new CacheDocument(document, this, boundingBox));
192 emit tileAdded(document);
193}
194
195void VectorTileModel::clear()
196{
197 m_documents.clear();
198}
199
200void VectorTileModel::queryTiles(int tileZoomLevel, const QRect &rect)
201{
202 // Download all the tiles inside the given indexes
203 for (int x = rect.left(); x <= rect.right(); ++x) {
204 for (int y = rect.top(); y <= rect.bottom(); ++y) {
205 const TileId tileId = TileId(0, tileZoomLevel, x, y);
206 if (!m_documents.contains(tileId) && !m_pendingDocuments.contains(tileId)) {
207 m_pendingDocuments << tileId;
208 TileRunner *job = new TileRunner(m_loader, m_layer, tileId);
209 connect(job, SIGNAL(documentLoaded(TileId,GeoDataDocument*)), this, SLOT(updateTile(TileId,GeoDataDocument*)));
210 m_threadPool->start(job);
211 }
212 }
213 }
214}
215
216void VectorTileModel::cleanupTile(GeoDataObject *object)
217{
218 if (GeoDataDocument *document = geodata_cast<GeoDataDocument>(object)) {
219 if (m_garbageQueue.contains(document)) {
220 m_garbageQueue.removeAll(document);
221 delete document;
222 }
223 }
224}
225
226}
227
228#include "moc_VectorTileModel.cpp"
QStringView level(QStringView ifopt)
Binds a QML item to a specific geodetic location in screen coordinates.
void clear()
T & first()
bool isEmpty() const const
qsizetype size() const const
int bottom() const const
QPoint bottomRight() const const
int left() const const
int right() const const
int top() const const
QPoint topLeft() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.