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"