File indexing completed on 2024-04-21 03:50:00

0001 /*
0002     SPDX-FileCopyrightText: 2010 Jens-Michael Hoffmann <jmho@c-xx.com>
0003     SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "TileLoader.h"
0009 
0010 #include <QDateTime>
0011 #include <QFileInfo>
0012 #include <QMetaType>
0013 #include <QImage>
0014 #include <QUrl>
0015 
0016 #include "GeoSceneTextureTileDataset.h"
0017 #include "GeoSceneTileDataset.h"
0018 #include "GeoSceneTypes.h"
0019 #include "GeoSceneVectorTileDataset.h"
0020 #include "GeoDataDocument.h"
0021 #include "HttpDownloadManager.h"
0022 #include "MarbleDebug.h"
0023 #include "MarbleDirs.h"
0024 #include "TileId.h"
0025 #include "TileLoaderHelper.h"
0026 #include "ParseRunnerPlugin.h"
0027 #include "ParsingRunner.h"
0028 
0029 Q_DECLARE_METATYPE( Marble::DownloadUsage )
0030 
0031 namespace Marble
0032 {
0033 
0034 TileLoader::TileLoader(HttpDownloadManager * const downloadManager, const PluginManager *pluginManager) :
0035     m_pluginManager(pluginManager)
0036 {
0037     qRegisterMetaType<DownloadUsage>( "DownloadUsage" );
0038     connect( this, SIGNAL(downloadTile(QUrl,QString,QString,DownloadUsage)),
0039              downloadManager, SLOT(addJob(QUrl,QString,QString,DownloadUsage)));
0040     connect( downloadManager, SIGNAL(downloadComplete(QString,QString)),
0041              SLOT(updateTile(QString,QString)));
0042     connect( downloadManager, SIGNAL(downloadComplete(QByteArray,QString)),
0043              SLOT(updateTile(QByteArray,QString)));
0044 }
0045 
0046 TileLoader::~TileLoader()
0047 {
0048     // nothing to do
0049 }
0050 
0051 // If the tile image file is locally available:
0052 //     - if not expired: create ImageTile, set state to "uptodate", return it => done
0053 //     - if expired: create TextureTile, state is set to Expired by default, trigger dl,
0054 QImage TileLoader::loadTileImage( GeoSceneTextureTileDataset const *textureLayer, TileId const & tileId, DownloadUsage const usage )
0055 {
0056     QString const fileName = tileFileName( textureLayer, tileId );
0057 
0058     TileStatus status = tileStatus( textureLayer, tileId );
0059     if ( status != Missing ) {
0060         // check if an update should be triggered
0061 
0062         if ( status == Available ) {
0063             mDebug() << tileId << "StateUptodate";
0064         } else {
0065             Q_ASSERT( status == Expired );
0066             mDebug() << tileId << "StateExpired";
0067             triggerDownload( textureLayer, tileId, usage );
0068         }
0069 
0070         QImage const image( fileName );
0071         if ( !image.isNull() ) {
0072             // file is there, so create and return a tile object in any case
0073             return image;
0074         }
0075     }
0076 
0077     // tile was not locally available => trigger download and look for tiles in other levels
0078     // for scaling
0079 
0080     QImage replacementTile = scaledLowerLevelTile( textureLayer, tileId );
0081     Q_ASSERT( !replacementTile.isNull() );
0082 
0083     triggerDownload( textureLayer, tileId, usage );
0084 
0085     return replacementTile;
0086 }
0087 
0088 
0089 GeoDataDocument *TileLoader::loadTileVectorData( GeoSceneVectorTileDataset const *textureLayer, TileId const & tileId, DownloadUsage const usage )
0090 {
0091     // FIXME: textureLayer->fileFormat() could be used in the future for use just that parser, instead of all available parsers
0092 
0093     QString const fileName = tileFileName( textureLayer, tileId );
0094 
0095     TileStatus status = tileStatus( textureLayer, tileId );
0096     if ( status != Missing ) {
0097         // check if an update should be triggered
0098 
0099         if ( status == Available ) {
0100             mDebug() << tileId << "StateUptodate";
0101         } else {
0102             Q_ASSERT( status == Expired );
0103             mDebug() << tileId << "StateExpired";
0104             triggerDownload( textureLayer, tileId, usage );
0105         }
0106 
0107         QFile file ( fileName );
0108         if ( file.exists() ) {
0109 
0110             // File is ready, so parse and return the vector data in any case
0111             GeoDataDocument* document = openVectorFile(fileName);
0112             if (document) {
0113                 return document;
0114             }
0115         }
0116     } else {
0117         // tile was not locally available => trigger download
0118         triggerDownload( textureLayer, tileId, usage );
0119     }
0120 
0121     return nullptr;
0122 }
0123 
0124 // This method triggers a download of the given tile (without checking
0125 // expiration). It is called by upper layer (StackedTileLoader) when the tile
0126 // that should be reloaded is currently loaded in memory.
0127 //
0128 // post condition
0129 //     - download is triggered
0130 void TileLoader::downloadTile( GeoSceneTileDataset const *tileData, TileId const &tileId, DownloadUsage const usage )
0131 {
0132     triggerDownload( tileData, tileId, usage );
0133 }
0134 
0135 int TileLoader::maximumTileLevel( GeoSceneTileDataset const & tileData )
0136 {
0137     // if maximum tile level is configured in the DGML files,
0138     // then use it, otherwise use old detection code.
0139     if ( tileData.maximumTileLevel() >= 0 ) {
0140         return tileData.maximumTileLevel();
0141     }
0142 
0143     int maximumTileLevel = -1;
0144     const QFileInfo themeStr( tileData.themeStr() );
0145     const QString tilepath = themeStr.isAbsolute() ? themeStr.absoluteFilePath() : MarbleDirs::path( tileData.themeStr() );
0146     //    mDebug() << "StackedTileLoader::maxPartialTileLevel tilepath" << tilepath;
0147     QStringList leveldirs = QDir( tilepath ).entryList( QDir::AllDirs | QDir::NoSymLinks
0148                                                         | QDir::NoDotAndDotDot );
0149 
0150     QStringList::const_iterator it = leveldirs.constBegin();
0151     QStringList::const_iterator const end = leveldirs.constEnd();
0152     for (; it != end; ++it ) {
0153         bool ok = true;
0154         const int value = (*it).toInt( &ok, 10 );
0155 
0156         if ( ok && value > maximumTileLevel )
0157             maximumTileLevel = value;
0158     }
0159 
0160     //    mDebug() << "Detected maximum tile level that contains data: "
0161     //             << maxtilelevel;
0162     return maximumTileLevel + 1;
0163 }
0164 
0165 bool TileLoader::baseTilesAvailable( GeoSceneTileDataset const & tileData )
0166 {
0167     const int  levelZeroColumns = tileData.levelZeroColumns();
0168     const int  levelZeroRows    = tileData.levelZeroRows();
0169 
0170     bool result = true;
0171 
0172     // Check whether the tiles from the lowest texture level are available
0173     //
0174     for ( int column = 0; result && column < levelZeroColumns; ++column ) {
0175         for ( int row = 0; result && row < levelZeroRows; ++row ) {
0176             const TileId id( 0, 0, column, row );
0177             const QString tilepath = tileFileName( &tileData, id );
0178             result &= QFile::exists( tilepath );
0179             if (!result) {
0180                 mDebug() << "Base tile " << tileData.relativeTileFileName( id ) << " is missing for source dir " << tileData.sourceDir();
0181             }
0182         }
0183     }
0184 
0185     return result;
0186 }
0187 
0188 TileLoader::TileStatus TileLoader::tileStatus( GeoSceneTileDataset const *tileData, const TileId &tileId )
0189 {
0190     QString const fileName = tileFileName( tileData, tileId );
0191     QFileInfo fileInfo( fileName );
0192     if ( !fileInfo.exists() ) {
0193         return Missing;
0194     }
0195 
0196     const QDateTime lastModified = fileInfo.lastModified();
0197     const int expireSecs = tileData->expire();
0198     const bool isExpired = lastModified.secsTo( QDateTime::currentDateTime() ) >= expireSecs;
0199     return isExpired ? Expired : Available;
0200 }
0201 
0202 void TileLoader::updateTile( QByteArray const & data, QString const & idStr )
0203 {
0204     QStringList const components = idStr.split(QLatin1Char(':'), QString::SkipEmptyParts);
0205     Q_ASSERT( components.size() == 5 );
0206 
0207     QString const origin = components[0];
0208     QString const sourceDir = components[ 1 ];
0209     int const zoomLevel = components[ 2 ].toInt();
0210     int const tileX = components[ 3 ].toInt();
0211     int const tileY = components[ 4 ].toInt();
0212 
0213     TileId const id = TileId( sourceDir, zoomLevel, tileX, tileY );
0214 
0215     if (origin == GeoSceneTypes::GeoSceneTextureTileType) {
0216         QImage const tileImage = QImage::fromData( data );
0217         if ( tileImage.isNull() )
0218             return;
0219 
0220         emit tileCompleted( id, tileImage );
0221     }
0222 }
0223 
0224 void TileLoader::updateTile(const QString &fileName, const QString &idStr)
0225 {
0226     QStringList const components = idStr.split(QLatin1Char(':'), QString::SkipEmptyParts);
0227     Q_ASSERT( components.size() == 5 );
0228 
0229     QString const origin = components[0];
0230     QString const sourceDir = components[ 1 ];
0231     int const zoomLevel = components[ 2 ].toInt();
0232     int const tileX = components[ 3 ].toInt();
0233     int const tileY = components[ 4 ].toInt();
0234 
0235     TileId const id = TileId( sourceDir, zoomLevel, tileX, tileY );
0236     if (origin == GeoSceneTypes::GeoSceneVectorTileType) {
0237         GeoDataDocument* document = openVectorFile(MarbleDirs::path(fileName));
0238         if (document) {
0239             emit tileCompleted(id,  document);
0240         }
0241     }
0242 }
0243 
0244 QString TileLoader::tileFileName( GeoSceneTileDataset const * tileData, TileId const & tileId )
0245 {
0246     QString const fileName = tileData->relativeTileFileName( tileId );
0247     QFileInfo const dirInfo( fileName );
0248     return dirInfo.isAbsolute() ? fileName : MarbleDirs::path( fileName );
0249 }
0250 
0251 void TileLoader::triggerDownload( GeoSceneTileDataset const *tileData, TileId const &id, DownloadUsage const usage )
0252 {
0253     if (id.zoomLevel() > 0) {
0254         int minValue = tileData->maximumTileLevel() == -1 ? id.zoomLevel() : qMin( id.zoomLevel(), tileData->maximumTileLevel() );
0255         if (id.zoomLevel() != qMax(tileData->minimumTileLevel(), minValue) ) {
0256             // Download only level 0 tiles and tiles between minimum and maximum tile level
0257             return;
0258         }
0259     }
0260 
0261     QUrl const sourceUrl = tileData->downloadUrl( id );
0262     QString const destFileName = tileData->relativeTileFileName( id );
0263     QString const idStr = QString( "%1:%2:%3:%4:%5" ).arg( tileData->nodeType(), tileData->sourceDir() ).arg( id.zoomLevel() ).arg( id.x() ).arg( id.y() );
0264     emit downloadTile( sourceUrl, destFileName, idStr, usage );
0265 }
0266 
0267 QImage TileLoader::scaledLowerLevelTile( const GeoSceneTextureTileDataset * textureData, TileId const & id )
0268 {
0269     mDebug() << id;
0270 
0271     int const minimumLevel = textureData->minimumTileLevel();
0272     for ( int level = qMax<int>( 0, id.zoomLevel() - 1 ); level >= 0; --level ) {
0273         if (level > 0 && level < minimumLevel) {
0274             continue;
0275         }
0276         int const deltaLevel = id.zoomLevel() - level;
0277 
0278         TileId const replacementTileId( id.mapThemeIdHash(), level,
0279                                         id.x() >> deltaLevel, id.y() >> deltaLevel );
0280         QString const fileName = tileFileName( textureData, replacementTileId );
0281         mDebug() << "TileLoader::scaledLowerLevelTile" << "trying" << fileName;
0282         QImage toScale = (!fileName.isEmpty() && QFile::exists(fileName)) ? QImage(fileName) : QImage();
0283 
0284         if ( level == 0 && toScale.isNull() ) {
0285             mDebug() << "No level zero tile installed in map theme dir. Falling back to a transparent image for now.";
0286             QSize tileSize = textureData->tileSize();
0287             Q_ASSERT( !tileSize.isEmpty() ); // assured by textureLayer
0288             toScale = QImage( tileSize, QImage::Format_ARGB32_Premultiplied );
0289             toScale.fill( qRgba( 0, 0, 0, 0 ) );
0290         }
0291 
0292         if ( !toScale.isNull() ) {
0293             // which rect to scale?
0294             int const restTileX = id.x() % ( 1 << deltaLevel );
0295             int const restTileY = id.y() % ( 1 << deltaLevel );
0296             int const partWidth = qMax(1, toScale.width() >> deltaLevel);
0297             int const partHeight = qMax(1, toScale.height() >> deltaLevel);
0298             int const startX = restTileX * partWidth;
0299             int const startY = restTileY * partHeight;
0300             mDebug() << "QImage::copy:" << startX << startY << partWidth << partHeight;
0301             QImage const part = toScale.copy( startX, startY, partWidth, partHeight );
0302             mDebug() << "QImage::scaled:" << toScale.size();
0303             return part.scaled( toScale.size() );
0304         }
0305     }
0306 
0307     Q_ASSERT_X( false, "scaled image", "level zero image missing" ); // not reached
0308     return QImage();
0309 }
0310 
0311 GeoDataDocument *TileLoader::openVectorFile(const QString &fileName) const
0312 {
0313     QList<const ParseRunnerPlugin*> plugins = m_pluginManager->parsingRunnerPlugins();
0314     const QFileInfo fileInfo( fileName );
0315     const QString suffix = fileInfo.suffix().toLower();
0316     const QString completeSuffix = fileInfo.completeSuffix().toLower();
0317 
0318     for( const ParseRunnerPlugin *plugin: plugins ) {
0319         QStringList const extensions = plugin->fileExtensions();
0320         if ( extensions.contains( suffix ) || extensions.contains( completeSuffix ) ) {
0321             ParsingRunner* runner = plugin->newRunner();
0322             QString error;
0323             GeoDataDocument* document = runner->parseFile(fileName, UserDocument, error);
0324             if (!document && !error.isEmpty()) {
0325                 mDebug() << QString("Failed to open vector tile %1: %2").arg(fileName, error);
0326             }
0327             delete runner;
0328             return document;
0329         }
0330     }
0331 
0332     mDebug() << "Unable to open vector tile " << fileName << ": No suitable plugin registered to parse this file format";
0333     return nullptr;
0334 }
0335 
0336 }
0337 
0338 #include "moc_TileLoader.cpp"