File indexing completed on 2025-01-05 03:59:11

0001 /*
0002     SPDX-FileCopyrightText: 2008 Torsten Rahn <rahn@kde.org>
0003     SPDX-FileCopyrightText: 2008 Jens-Michael Hoffmann <jensmh@gmx.de>
0004     SPDX-FileCopyrightText: 2012 Ander Pijoan <ander.pijoan@deusto.es>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "GeoSceneTileDataset.h"
0010 
0011 #include <QImage>
0012 #include <QUrl>
0013 
0014 #include "GeoSceneTypes.h"
0015 #include "GeoSceneEquirectTileProjection.h"
0016 #include "GeoSceneMercatorTileProjection.h"
0017 #include "DownloadPolicy.h"
0018 #include "MarbleDirs.h"
0019 #include "ServerLayout.h"
0020 #include "TileId.h"
0021 
0022 #include "digikam_debug.h"
0023 
0024 namespace Marble
0025 {
0026 
0027 GeoSceneTileDataset::GeoSceneTileDataset( const QString& name )
0028     : GeoSceneAbstractDataset( name ),
0029       m_sourceDir(),
0030       m_installMap(),
0031       m_storageLayoutMode(Marble),
0032       m_serverLayout( new MarbleServerLayout( this ) ),
0033       m_levelZeroColumns( defaultLevelZeroColumns ),
0034       m_levelZeroRows( defaultLevelZeroRows ),
0035       m_minimumTileLevel(0),
0036       m_maximumTileLevel( -1 ),
0037       m_tileProjection(new GeoSceneEquirectTileProjection()),
0038       m_blending(),
0039       m_downloadUrls(),
0040       m_nextUrl( m_downloadUrls.constEnd() )
0041 {
0042     m_tileProjection->setLevelZeroColumns(m_levelZeroColumns);
0043     m_tileProjection->setLevelZeroRows(m_levelZeroRows);
0044 }
0045 
0046 GeoSceneTileDataset::~GeoSceneTileDataset()
0047 {
0048     qDeleteAll( m_downloadPolicies );
0049     delete m_serverLayout;
0050     delete m_tileProjection;
0051 }
0052 
0053 const char* GeoSceneTileDataset::nodeType() const
0054 {
0055     return GeoSceneTypes::GeoSceneTileDatasetType;
0056 }
0057 
0058 QString GeoSceneTileDataset::sourceDir() const
0059 {
0060     return m_sourceDir;
0061 }
0062 
0063 void GeoSceneTileDataset::setSourceDir( const QString& sourceDir )
0064 {
0065     m_sourceDir = sourceDir;
0066 }
0067 
0068 QString GeoSceneTileDataset::installMap() const
0069 {
0070     return m_installMap;
0071 }
0072 
0073 void GeoSceneTileDataset::setInstallMap( const QString& installMap )
0074 {
0075     m_installMap = installMap;
0076 }
0077 
0078 GeoSceneTileDataset::StorageLayout GeoSceneTileDataset::storageLayout() const
0079 {
0080     return m_storageLayoutMode;
0081 }
0082 
0083 void GeoSceneTileDataset::setStorageLayout( const StorageLayout layout )
0084 {
0085     m_storageLayoutMode = layout;
0086 }
0087 
0088 void GeoSceneTileDataset::setServerLayout( const ServerLayout *layout )
0089 {
0090     delete m_serverLayout;
0091     m_serverLayout = layout;
0092 }
0093 
0094 const ServerLayout* GeoSceneTileDataset::serverLayout() const
0095 {
0096     return m_serverLayout;
0097 }
0098 
0099 int GeoSceneTileDataset::levelZeroColumns() const
0100 {
0101    return m_levelZeroColumns;
0102 }
0103 
0104 void GeoSceneTileDataset::setLevelZeroColumns( const int columns )
0105 {
0106     m_levelZeroColumns = columns;
0107     m_tileProjection->setLevelZeroColumns(m_levelZeroColumns);
0108 }
0109 
0110 int GeoSceneTileDataset::levelZeroRows() const
0111 {
0112     return m_levelZeroRows;
0113 }
0114 
0115 void GeoSceneTileDataset::setLevelZeroRows( const int rows )
0116 {
0117     m_levelZeroRows = rows;
0118     m_tileProjection->setLevelZeroRows(m_levelZeroRows);
0119 }
0120 
0121 int GeoSceneTileDataset::maximumTileLevel() const
0122 {
0123     return m_maximumTileLevel;
0124 }
0125 
0126 void GeoSceneTileDataset::setMaximumTileLevel( const int maximumTileLevel )
0127 {
0128     m_maximumTileLevel = maximumTileLevel;
0129 }
0130 
0131 int GeoSceneTileDataset::minimumTileLevel() const
0132 {
0133     return m_minimumTileLevel;
0134 }
0135 
0136 void GeoSceneTileDataset::setMinimumTileLevel(int level)
0137 {
0138     m_minimumTileLevel = level;
0139 }
0140 
0141 void GeoSceneTileDataset::setTileLevels(const QString &tileLevels)
0142 {
0143     if (tileLevels.isEmpty()) {
0144         m_tileLevels.clear();
0145         return;
0146     }
0147 
0148     const QStringList values = tileLevels.split(QLatin1Char(','));
0149     for(const QString &value: values) {
0150         bool canParse(false);
0151         int const tileLevel = value.trimmed().toInt(&canParse);
0152         if (canParse && tileLevel >= 0 && tileLevel < 100) {
0153             m_tileLevels << tileLevel;
0154         } else {
0155             qCDebug(DIGIKAM_MARBLE_LOG) << "Cannot parse tile level part " << value << " in " << tileLevels << ", ignoring it.";
0156         }
0157     }
0158 
0159     if (!m_tileLevels.isEmpty()) {
0160         std::sort(m_tileLevels.begin(), m_tileLevels.end());
0161         m_minimumTileLevel = m_tileLevels.first();
0162         m_maximumTileLevel = m_tileLevels.last();
0163     }
0164 }
0165 
0166 QVector<int> GeoSceneTileDataset::tileLevels() const
0167 {
0168     return m_tileLevels;
0169 }
0170 
0171 QVector<QUrl> GeoSceneTileDataset::downloadUrls() const
0172 {
0173     return m_downloadUrls;
0174 }
0175 
0176 const QSize GeoSceneTileDataset::tileSize() const
0177 {
0178     if ( m_tileSize.isEmpty() ) {
0179         const TileId id( 0, 0, 0, 0 );
0180         QString const fileName = relativeTileFileName( id );
0181         QFileInfo const dirInfo( fileName );
0182         QString const path = dirInfo.isAbsolute() ? fileName : MarbleDirs::path( fileName );
0183 
0184         QImage testTile( path );
0185 
0186         if ( testTile.isNull() ) {
0187             qCDebug(DIGIKAM_MARBLE_LOG) << "Tile size is missing in dgml and no base tile found in " << themeStr();
0188             qCDebug(DIGIKAM_MARBLE_LOG) << "Using default tile size " << c_defaultTileSize;
0189             m_tileSize = QSize( c_defaultTileSize, c_defaultTileSize );
0190         } else {
0191             m_tileSize = testTile.size();
0192         }
0193 
0194         if ( m_tileSize.isEmpty() ) {
0195             qCDebug(DIGIKAM_MARBLE_LOG) << "Tile width or height cannot be 0. Falling back to default tile size.";
0196             m_tileSize = QSize( c_defaultTileSize, c_defaultTileSize );
0197         }
0198     }
0199 
0200     Q_ASSERT( !m_tileSize.isEmpty() );
0201     return m_tileSize;
0202 }
0203 
0204 GeoDataLatLonBox GeoSceneTileDataset::latLonBox() const
0205 {
0206     return m_latLonBox;
0207 }
0208 
0209 void GeoSceneTileDataset::setLatLonBox( const GeoDataLatLonBox &box )
0210 {
0211     m_latLonBox = box;
0212 }
0213 
0214 void GeoSceneTileDataset::setTileSize( const QSize &tileSize )
0215 {
0216     if ( tileSize.isEmpty() ) {
0217         qCDebug(DIGIKAM_MARBLE_LOG) << "Ignoring invalid tile size " << tileSize;
0218     } else {
0219         m_tileSize = tileSize;
0220     }
0221 }
0222 
0223 void GeoSceneTileDataset::setTileProjection(GeoSceneAbstractTileProjection::Type projectionType)
0224 {
0225     if (m_tileProjection->type() == projectionType) {
0226         return;
0227     }
0228 
0229     delete m_tileProjection;
0230     if (projectionType == GeoSceneAbstractTileProjection::Mercator) {
0231         m_tileProjection = new GeoSceneMercatorTileProjection();
0232     } else {
0233         m_tileProjection = new GeoSceneEquirectTileProjection();
0234     }
0235 
0236     m_tileProjection->setLevelZeroColumns(m_levelZeroColumns);
0237     m_tileProjection->setLevelZeroRows(m_levelZeroRows);
0238 }
0239 
0240 const GeoSceneAbstractTileProjection * GeoSceneTileDataset::tileProjection() const
0241 {
0242     return m_tileProjection;
0243 }
0244 
0245 GeoSceneAbstractTileProjection::Type GeoSceneTileDataset::tileProjectionType() const
0246 {
0247     return m_tileProjection->type();
0248 }
0249 
0250 // Even though this method changes the internal state, it may be const
0251 // because the compiler is forced to invoke this method for different TileIds.
0252 QUrl GeoSceneTileDataset::downloadUrl( const TileId &id ) const
0253 {
0254     // default download url
0255     if ( m_downloadUrls.empty() ) {
0256         QUrl const defaultUrl = QUrl(QLatin1String("https://maps.kde.org/") + m_serverLayout->sourceDir());
0257         qCDebug(DIGIKAM_MARBLE_LOG) << "No download URL specified for tiles stored in "
0258                  << m_sourceDir << ", falling back to " << defaultUrl.toString();
0259         return m_serverLayout->downloadUrl(defaultUrl, id);
0260     } else if (m_downloadUrls.size() == 1) {
0261         return m_serverLayout->downloadUrl(*m_nextUrl, id);
0262     } else {
0263         if (m_nextUrl == m_downloadUrls.constEnd()) {
0264             m_nextUrl = m_downloadUrls.constBegin();
0265         }
0266         const QUrl url = m_serverLayout->downloadUrl(*m_nextUrl, id);
0267         ++m_nextUrl;
0268         return url;
0269     }
0270 }
0271 
0272 void GeoSceneTileDataset::addDownloadUrl( const QUrl & url )
0273 {
0274     m_downloadUrls.append( url );
0275     // FIXME: this could be done only once
0276     m_nextUrl = m_downloadUrls.constBegin();
0277 }
0278 
0279 QString GeoSceneTileDataset::relativeTileFileName( const TileId &id ) const
0280 {
0281     const QString suffix = fileFormat().toLower();
0282 
0283     QString relFileName;
0284 
0285     switch ( m_storageLayoutMode ) {
0286     case GeoSceneTileDataset::Marble:
0287         relFileName = QStringLiteral( "%1/%2/%3/%3_%4.%5" )
0288             .arg( themeStr() )
0289             .arg( id.zoomLevel() )
0290             .arg(id.y(), tileDigits, 10, QLatin1Char('0'))
0291             .arg(id.x(), tileDigits, 10, QLatin1Char('0'))
0292             .arg( suffix );
0293         break;
0294     case GeoSceneTileDataset::OpenStreetMap:
0295         relFileName = QStringLiteral( "%1/%2/%3/%4.%5" )
0296             .arg( themeStr() )
0297             .arg( id.zoomLevel() )
0298             .arg( id.x() )
0299             .arg( id.y() )
0300             .arg( suffix );
0301         break;
0302     case GeoSceneTileDataset::TileMapService:
0303         relFileName = QStringLiteral( "%1/%2/%3/%4.%5" )
0304             .arg( themeStr() )
0305             .arg( id.zoomLevel() )
0306             .arg( id.x() )
0307             .arg( ( 1<<id.zoomLevel() ) - id.y() - 1 )  //Y coord in TMS runs from bottom to top
0308             .arg( suffix );
0309         break;
0310     }
0311 
0312     return relFileName;
0313 }
0314 
0315 QString GeoSceneTileDataset::themeStr() const
0316 {
0317     QFileInfo const dirInfo( sourceDir() );
0318     return dirInfo.isAbsolute() ? sourceDir() : QLatin1String("maps/") + sourceDir();
0319 }
0320 
0321 QList<const DownloadPolicy *> GeoSceneTileDataset::downloadPolicies() const
0322 {
0323     return m_downloadPolicies;
0324 }
0325 
0326 void GeoSceneTileDataset::addDownloadPolicy( const DownloadUsage usage, const int maximumConnections )
0327 {
0328     DownloadPolicy * const policy = new DownloadPolicy( DownloadPolicyKey( hostNames(), usage ));
0329     policy->setMaximumConnections( maximumConnections );
0330     m_downloadPolicies.append( policy );
0331     qCDebug(DIGIKAM_MARBLE_LOG) << "added download policy" << hostNames() << usage << maximumConnections;
0332 }
0333 
0334 QStringList GeoSceneTileDataset::hostNames() const
0335 {
0336     QStringList result;
0337     result.reserve(m_downloadUrls.size());
0338 
0339     QVector<QUrl>::const_iterator pos = m_downloadUrls.constBegin();
0340     QVector<QUrl>::const_iterator const end = m_downloadUrls.constEnd();
0341     for (; pos != end; ++pos )
0342         result.append( (*pos).host() );
0343     return result;
0344 }
0345 
0346 }