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 "MarbleDebug.h"
14#include "MarbleGlobal.h"
15#include "MathHelper.h"
16#include "TileLoader.h"
17
18#include <QThreadPool>
19#include <qmath.h>
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 Q_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, &VectorTileModel::tileAdded, treeModel, &GeoDataTreeModel::addDocument);
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 QList<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 Q_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 const auto tiles = m_documents.keys();
164 for (auto const &tile : tiles) {
165 m_loader->downloadTile(m_layer, tile, DownloadBrowse);
166 }
167}
168
169void VectorTileModel::updateTile(const TileId &idWithMapThemeHash, GeoDataDocument *document)
170{
171 TileId const id(0, idWithMapThemeHash.zoomLevel(), idWithMapThemeHash.x(), idWithMapThemeHash.y());
172 m_pendingDocuments.removeAll(id);
173 if (!document) {
174 return;
175 }
176
177 if (m_tileLoadLevel != id.zoomLevel()) {
178 delete document;
179 return;
180 }
181
182 document->setName(QStringLiteral("%1/%2/%3").arg(id.zoomLevel()).arg(id.x()).arg(id.y()));
183 m_garbageQueue << document;
184 if (m_documents.contains(id)) {
185 m_documents.remove(id);
186 }
187 if (m_deleteDocumentsLater) {
188 m_deleteDocumentsLater = false;
189 m_documents.clear();
190 }
191 const GeoDataLatLonBox boundingBox = m_layer->tileProjection()->geoCoordinates(id);
192 m_documents[id] = QSharedPointer<CacheDocument>(new CacheDocument(document, this, boundingBox));
193 Q_EMIT tileAdded(document);
194}
195
196void VectorTileModel::clear()
197{
198 m_documents.clear();
199}
200
201void VectorTileModel::queryTiles(int tileZoomLevel, const QRect &rect)
202{
203 // Download all the tiles inside the given indexes
204 for (int x = rect.left(); x <= rect.right(); ++x) {
205 for (int y = rect.top(); y <= rect.bottom(); ++y) {
206 const TileId tileId = TileId(0, tileZoomLevel, x, y);
207 if (!m_documents.contains(tileId) && !m_pendingDocuments.contains(tileId)) {
208 m_pendingDocuments << tileId;
209 auto job = new TileRunner(m_loader, m_layer, tileId);
210 connect(job, SIGNAL(documentLoaded(TileId, GeoDataDocument *)), this, SLOT(updateTile(TileId, GeoDataDocument *)));
211 m_threadPool->start(job);
212 }
213 }
214 }
215}
216
217void VectorTileModel::cleanupTile(GeoDataObject *object)
218{
219 if (auto document = geodata_cast<GeoDataDocument>(object)) {
220 if (m_garbageQueue.contains(document)) {
221 m_garbageQueue.removeAll(document);
222 delete document;
223 }
224 }
225}
226
227}
228
229#include "moc_VectorTileModel.cpp"
QStringView level(QStringView ifopt)
Binds a QML item to a specific geodetic location in screen coordinates.
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 Sat Dec 21 2024 17:04:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.