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"