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