File indexing completed on 2025-01-05 03:59:38
0001 /* 0002 SPDX-License-Identifier: LGPL-2.1-or-later 0003 0004 SPDX-FileCopyrightText: 2012 Ander Pijoan <ander.pijoan@deusto.es> 0005 SPDX-FileCopyrightText: 2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de> 0006 */ 0007 0008 #include "VectorTileModel.h" 0009 0010 #include <qmath.h> 0011 #include <QThreadPool> 0012 0013 #include "GeoDataDocument.h" 0014 #include "GeoDataTreeModel.h" 0015 #include "GeoSceneVectorTileDataset.h" 0016 #include "MarbleGlobal.h" 0017 #include "MathHelper.h" 0018 #include "TileLoader.h" 0019 0020 #include "digikam_debug.h" 0021 0022 namespace Marble 0023 { 0024 0025 TileRunner::TileRunner(TileLoader *loader, const GeoSceneVectorTileDataset *tileDataset, const TileId &id) : 0026 m_loader(loader), 0027 m_tileDataset(tileDataset), 0028 m_id(id) 0029 { 0030 } 0031 0032 void TileRunner::run() 0033 { 0034 GeoDataDocument *const document = m_loader->loadTileVectorData(m_tileDataset, m_id, DownloadBrowse); 0035 0036 Q_EMIT documentLoaded(m_id, document); 0037 } 0038 0039 VectorTileModel::CacheDocument::CacheDocument(GeoDataDocument *doc, VectorTileModel *vectorTileModel, const GeoDataLatLonBox &boundingBox) : 0040 m_document(doc), 0041 m_vectorTileModel(vectorTileModel), 0042 m_boundingBox(boundingBox) 0043 { 0044 // nothing to do 0045 } 0046 0047 VectorTileModel::CacheDocument::~CacheDocument() 0048 { 0049 m_vectorTileModel->removeTile(m_document); 0050 } 0051 0052 VectorTileModel::VectorTileModel(TileLoader *loader, const GeoSceneVectorTileDataset *layer, GeoDataTreeModel *treeModel, QThreadPool *threadPool) : 0053 m_loader(loader), 0054 m_layer(layer), 0055 m_treeModel(treeModel), 0056 m_threadPool(threadPool), 0057 m_tileLoadLevel(-1), 0058 m_tileZoomLevel(-1), 0059 m_deleteDocumentsLater(false) 0060 { 0061 connect(this, SIGNAL(tileAdded(GeoDataDocument*)), treeModel, SLOT(addDocument(GeoDataDocument*))); 0062 connect(this, SIGNAL(tileRemoved(GeoDataDocument*)), treeModel, SLOT(removeDocument(GeoDataDocument*))); 0063 connect(treeModel, SIGNAL(removed(GeoDataObject*)), this, SLOT(cleanupTile(GeoDataObject*))); 0064 } 0065 0066 void VectorTileModel::setViewport(const GeoDataLatLonBox &latLonBox) 0067 { 0068 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; 0069 int const nTiles = smallScreen ? 12 : 20; 0070 qreal const viewportArea = latLonBox.width() * latLonBox.height(); 0071 qreal const level = log((nTiles * 2.0 * M_PI * M_PI) / viewportArea) / log(4); 0072 m_tileZoomLevel = qFloor(level); 0073 int tileLoadLevel = m_tileZoomLevel; 0074 0075 // Determine available tile levels in the layer and thereby 0076 // select the tileZoomLevel that is actually used: 0077 QVector<int> tileLevels = m_layer->tileLevels(); 0078 if (tileLevels.isEmpty() /* || tileZoomLevel < tileLevels.first() */) { 0079 // if there is no (matching) tile level then show nothing 0080 // and bail out. 0081 m_documents.clear(); 0082 return; 0083 } 0084 int tileLevel = tileLevels.first(); 0085 for (int i = 1, n = tileLevels.size(); i < n; ++i) { 0086 if (tileLevels[i] > tileLoadLevel) { 0087 break; 0088 } 0089 tileLevel = tileLevels[i]; 0090 } 0091 tileLoadLevel = tileLevel; 0092 0093 // if zoom level has changed, empty vectortile cache 0094 if (tileLoadLevel != m_tileLoadLevel) { 0095 m_deleteDocumentsLater = m_tileLoadLevel >= 0; 0096 m_tileLoadLevel = tileLoadLevel; 0097 } 0098 0099 /** LOGIC FOR DOWNLOADING ALL THE TILES THAT ARE INSIDE THE SCREEN AT THE CURRENT ZOOM LEVEL **/ 0100 0101 // New tiles X and Y for moved screen coordinates 0102 // More info: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Subtiles 0103 // More info: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#C.2FC.2B.2B 0104 const QRect rect = m_layer->tileProjection()->tileIndexes(latLonBox, tileLoadLevel); 0105 0106 // Download tiles and send them to VectorTileLayer 0107 // When changing zoom, download everything inside the screen 0108 // TODO: hardcodes assumption about tiles indexing also ends at dateline 0109 // TODO: what about crossing things in y direction? 0110 if (!latLonBox.crossesDateLine()) { 0111 queryTiles(tileLoadLevel, rect); 0112 } 0113 // When only moving screen, just download the new tiles 0114 else { 0115 // TODO: maxTileX (calculation knowledge) should be a property of tileProjection or m_layer 0116 const int maxTileX = (1 << tileLoadLevel) * m_layer->levelZeroColumns() - 1; 0117 0118 queryTiles(tileLoadLevel, QRect(QPoint(0, rect.top()), rect.bottomRight())); 0119 queryTiles(tileLoadLevel, QRect(rect.topLeft(), QPoint(maxTileX, rect.bottom()))); 0120 } 0121 removeTilesOutOfView(latLonBox); 0122 } 0123 0124 void VectorTileModel::removeTilesOutOfView(const GeoDataLatLonBox &boundingBox) 0125 { 0126 GeoDataLatLonBox const extendedViewport = boundingBox.scaled(2.0, 2.0); 0127 for (auto iter = m_documents.begin(); iter != m_documents.end();) { 0128 bool const isOutOfView = !extendedViewport.intersects(iter.value()->latLonBox()); 0129 if (isOutOfView) { 0130 iter = m_documents.erase(iter); 0131 } else { 0132 ++iter; 0133 } 0134 } 0135 } 0136 0137 QString VectorTileModel::name() const 0138 { 0139 return m_layer->name(); 0140 } 0141 0142 const GeoSceneVectorTileDataset *VectorTileModel::layer() const 0143 { 0144 return m_layer; 0145 } 0146 0147 void VectorTileModel::removeTile(GeoDataDocument *document) 0148 { 0149 Q_EMIT tileRemoved(document); 0150 } 0151 0152 int VectorTileModel::tileZoomLevel() const 0153 { 0154 return m_tileZoomLevel; 0155 } 0156 0157 int VectorTileModel::cachedDocuments() const 0158 { 0159 return m_documents.size(); 0160 } 0161 0162 void VectorTileModel::reload() 0163 { 0164 for (auto const &tile : m_documents.keys()) { 0165 m_loader->downloadTile(m_layer, tile, DownloadBrowse); 0166 } 0167 } 0168 0169 void VectorTileModel::updateTile(const TileId &idWithMapThemeHash, GeoDataDocument *document) 0170 { 0171 TileId const id(0, idWithMapThemeHash.zoomLevel(), idWithMapThemeHash.x(), idWithMapThemeHash.y()); 0172 m_pendingDocuments.removeAll(id); 0173 if (!document) { 0174 return; 0175 } 0176 0177 if (m_tileLoadLevel != id.zoomLevel()) { 0178 delete document; 0179 return; 0180 } 0181 0182 document->setName(QString::fromUtf8("%1/%2/%3").arg(id.zoomLevel()).arg(id.x()).arg(id.y())); 0183 m_garbageQueue << document; 0184 if (m_documents.contains(id)) { 0185 m_documents.remove(id); 0186 } 0187 if (m_deleteDocumentsLater) { 0188 m_deleteDocumentsLater = false; 0189 m_documents.clear(); 0190 } 0191 const GeoDataLatLonBox boundingBox = m_layer->tileProjection()->geoCoordinates(id); 0192 m_documents[id] = QSharedPointer<CacheDocument>(new CacheDocument(document, this, boundingBox)); 0193 Q_EMIT tileAdded(document); 0194 } 0195 0196 void VectorTileModel::clear() 0197 { 0198 m_documents.clear(); 0199 } 0200 0201 void VectorTileModel::queryTiles(int tileZoomLevel, const QRect &rect) 0202 { 0203 // Download all the tiles inside the given indexes 0204 for (int x = rect.left(); x <= rect.right(); ++x) { 0205 for (int y = rect.top(); y <= rect.bottom(); ++y) { 0206 const TileId tileId = TileId(0, tileZoomLevel, x, y); 0207 if (!m_documents.contains(tileId) && !m_pendingDocuments.contains(tileId)) { 0208 m_pendingDocuments << tileId; 0209 TileRunner *job = new TileRunner(m_loader, m_layer, tileId); 0210 connect(job, SIGNAL(documentLoaded(TileId,GeoDataDocument*)), this, SLOT(updateTile(TileId,GeoDataDocument*))); 0211 m_threadPool->start(job); 0212 } 0213 } 0214 } 0215 } 0216 0217 void VectorTileModel::cleanupTile(GeoDataObject *object) 0218 { 0219 if (GeoDataDocument *document = geodata_cast<GeoDataDocument>(object)) { 0220 if (m_garbageQueue.contains(document)) { 0221 m_garbageQueue.removeAll(document); 0222 delete document; 0223 } 0224 } 0225 } 0226 0227 } 0228 0229 #include "moc_VectorTileModel.cpp"