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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "DownloadRegion.h"
0007 
0008 #include "MarbleModel.h"
0009 #include "MarbleMap.h"
0010 #include "MarbleMath.h"
0011 #include "TextureLayer.h"
0012 #include "GeoDataLatLonAltBox.h"
0013 #include "GeoDataLineString.h"
0014 #include "GeoSceneDocument.h"
0015 #include "GeoSceneMap.h"
0016 #include "GeoSceneLayer.h"
0017 #include "GeoSceneTileDataset.h"
0018 #include "GeoSceneAbstractTileProjection.h"
0019 #include "TileCoordsPyramid.h"
0020 
0021 #include "digikam_debug.h"
0022 
0023 namespace Marble {
0024 
0025 class DownloadRegionPrivate
0026 {
0027 public:
0028     MarbleModel* m_marbleModel;
0029 
0030     QPair<int,int> m_tileLevelRange;
0031 
0032     int m_visibleTileLevel;
0033 
0034     DownloadRegionPrivate();
0035 
0036     int rad2PixelX( qreal const lon, const TileLayer *tileLayer ) const;
0037 
0038     int rad2PixelY( qreal const lat, const TileLayer *tileLayer ) const;
0039 };
0040 
0041 DownloadRegionPrivate::DownloadRegionPrivate() : m_marbleModel( nullptr ),
0042     m_tileLevelRange( 0, 0 ), m_visibleTileLevel( 0 )
0043 {
0044     // nothing to do
0045 }
0046 
0047 // copied from AbstractScanlineTextureMapper and slightly adjusted
0048 int DownloadRegionPrivate::rad2PixelX( qreal const lon, const TileLayer *tileLayer ) const
0049 {
0050     qreal tileWidth = tileLayer && tileLayer->layerCount() > 0 ? tileLayer->tileSize().width() : 256;
0051     qreal const globalWidth = tileWidth * tileLayer->tileColumnCount( m_visibleTileLevel );
0052     return static_cast<int>(globalWidth * 0.5 * (1 + lon / M_PI));
0053 }
0054 
0055 // copied from AbstractScanlineTextureMapper and slightly adjusted
0056 int DownloadRegionPrivate::rad2PixelY( qreal const lat, const TileLayer *tileLayer ) const
0057 {
0058     qreal tileHeight = tileLayer && tileLayer->layerCount() > 0 ? tileLayer->tileSize().height() : 256;
0059     qreal const globalHeight = tileHeight * tileLayer->tileRowCount( m_visibleTileLevel );
0060 
0061     switch (tileLayer->tileProjection()->type()) {
0062     case GeoSceneAbstractTileProjection::Equirectangular:
0063         return static_cast<int>(globalHeight * (0.5 - lat / M_PI));
0064     case GeoSceneAbstractTileProjection::Mercator:
0065         if ( fabs( lat ) < 1.4835 )
0066             return static_cast<int>(globalHeight * 0.5 * (1 - gdInv(lat) / M_PI));
0067         if ( lat >= +1.4835 )
0068             return static_cast<int>(globalHeight * 0.5 * (1 - 3.1309587 / M_PI));
0069         if ( lat <= -1.4835 )
0070             return static_cast<int>(globalHeight * 0.5 * (1 + 3.1309587 / M_PI));
0071     }
0072 
0073     // Dummy value to avoid a warning.
0074     return 0;
0075 }
0076 
0077 DownloadRegion::DownloadRegion( QObject* parent ) : QObject( parent ),
0078     d( new DownloadRegionPrivate )
0079 {
0080     // nothing to do
0081 }
0082 
0083 void DownloadRegion::setMarbleModel( MarbleModel* model )
0084 {
0085     d->m_marbleModel = model;
0086 }
0087 
0088 DownloadRegion::~DownloadRegion()
0089 {
0090     delete d;
0091 }
0092 
0093 void DownloadRegion::setTileLevelRange( const int minimumTileLevel, const int maximumTileLevel )
0094 {
0095     Q_ASSERT( minimumTileLevel >= 0 );
0096     Q_ASSERT( maximumTileLevel >= 0 );
0097     Q_ASSERT( minimumTileLevel <= maximumTileLevel );
0098     d->m_tileLevelRange.first = minimumTileLevel;
0099     d->m_tileLevelRange.second = maximumTileLevel;
0100 }
0101 
0102 QVector<TileCoordsPyramid> DownloadRegion::region( const TileLayer *tileLayer, const GeoDataLatLonAltBox &downloadRegion ) const
0103 {
0104     Q_ASSERT( tileLayer );
0105 
0106     int tileLevelRangeFirst = d->m_tileLevelRange.first;
0107     int tileLevelRangeSecond = d->m_tileLevelRange.second;
0108 
0109     TileType tileType = dynamic_cast<const TextureLayer*>( tileLayer ) ? TextureTileType : VectorTileType;
0110 
0111     QVector<int> validLevels;
0112     validLevels = validTileLevels(tileType);
0113 
0114     // Align the tileLevelRangeSecond with the validTileLevels
0115     if (!validLevels.isEmpty()) {
0116         int lastIndex = validLevels.count() - 1;
0117         for ( int i = 0; i < validLevels.count(); ++i ) {
0118             if (validLevels.at(lastIndex - i) <= tileLevelRangeSecond
0119                 && validLevels.at(lastIndex - i) >= tileLevelRangeFirst) {
0120                 tileLevelRangeSecond = validLevels.at(lastIndex - i);
0121                 break;
0122             }
0123         }
0124     }
0125 
0126     int const westX = d->rad2PixelX( downloadRegion.west(), tileLayer );
0127     int const northY = d->rad2PixelY( downloadRegion.north(), tileLayer );
0128     int const eastX = d->rad2PixelX( downloadRegion.east(), tileLayer );
0129     int const southY = d->rad2PixelY( downloadRegion.south(), tileLayer );
0130 
0131     // FIXME: remove this stuff
0132     qCDebug(DIGIKAM_MARBLE_LOG) << "DownloadRegionDialog downloadRegion:"
0133              << "north:" << downloadRegion.north()
0134              << "south:" << downloadRegion.south()
0135              << "east:" << downloadRegion.east()
0136              << "west:" << downloadRegion.west();
0137     qCDebug(DIGIKAM_MARBLE_LOG) << "north/west (x/y):" << westX << northY;
0138     qCDebug(DIGIKAM_MARBLE_LOG) << "south/east (x/y):" << eastX << southY;
0139 
0140     int const tileWidth = tileLayer->tileSize().width();
0141     int const tileHeight = tileLayer->tileSize().height();
0142     qCDebug(DIGIKAM_MARBLE_LOG) << "DownloadRegionDialog downloadRegion: tileSize:" << tileWidth << tileHeight;
0143 
0144     int const visibleLevelX1 = qMin( westX, eastX );
0145     int const visibleLevelY1 = qMin( northY, southY );
0146     int const visibleLevelX2 = qMax( westX, eastX );
0147     int const visibleLevelY2 = qMax( northY, southY );
0148 
0149     qCDebug(DIGIKAM_MARBLE_LOG) << "visible level pixel coords (level/x1/y1/x2/y2):" << d->m_visibleTileLevel
0150              << visibleLevelX1 << visibleLevelY1 << visibleLevelX2 << visibleLevelY2;
0151 
0152     int bottomLevelX1, bottomLevelY1, bottomLevelX2, bottomLevelY2;
0153     // the pixel coords calculated above are referring to the visible tile level,
0154     // if the bottom level is a different level, we have to take it into account
0155     if ( d->m_visibleTileLevel > tileLevelRangeSecond ) {
0156         int const deltaLevel = d->m_visibleTileLevel - tileLevelRangeSecond;
0157         bottomLevelX1 = visibleLevelX1 >> deltaLevel;
0158         bottomLevelY1 = visibleLevelY1 >> deltaLevel;
0159         bottomLevelX2 = visibleLevelX2 >> deltaLevel;
0160         bottomLevelY2 = visibleLevelY2 >> deltaLevel;
0161     }
0162     else if ( d->m_visibleTileLevel < tileLevelRangeSecond ) {
0163         int const deltaLevel = tileLevelRangeSecond - d->m_visibleTileLevel;
0164         bottomLevelX1 = visibleLevelX1 << deltaLevel;
0165         bottomLevelY1 = visibleLevelY1 << deltaLevel;
0166         bottomLevelX2 = visibleLevelX2 << deltaLevel;
0167         bottomLevelY2 = visibleLevelY2 << deltaLevel;
0168     }
0169     else {
0170         bottomLevelX1 = visibleLevelX1;
0171         bottomLevelY1 = visibleLevelY1;
0172         bottomLevelX2 = visibleLevelX2;
0173         bottomLevelY2 = visibleLevelY2;
0174     }
0175     qCDebug(DIGIKAM_MARBLE_LOG) << "bottom level pixel coords (level/x1/y1/x2/y2):"
0176              << tileLevelRangeSecond
0177              << bottomLevelX1 << bottomLevelY1 << bottomLevelX2 << bottomLevelY2;
0178 
0179     TileCoordsPyramid coordsPyramid( tileLevelRangeFirst, tileLevelRangeSecond );
0180     coordsPyramid.setValidTileLevels(validLevels);
0181 
0182     QRect bottomLevelTileCoords;
0183     bottomLevelTileCoords.setCoords
0184             ( bottomLevelX1 / tileWidth,
0185               bottomLevelY1 / tileHeight,
0186               bottomLevelX2 / tileWidth + ( bottomLevelX2 % tileWidth > 0 ? 1 : 0 ) - 1,    // -1 needed for proper counting
0187               bottomLevelY2 / tileHeight + ( bottomLevelY2 % tileHeight > 0 ? 1 : 0 ) - 1); // -1 needed for proper counting
0188     qCDebug(DIGIKAM_MARBLE_LOG) << "bottom level tile coords: (x1/y1/size):" << bottomLevelTileCoords;
0189     coordsPyramid.setBottomLevelCoords( bottomLevelTileCoords );
0190 
0191     qCDebug(DIGIKAM_MARBLE_LOG) << "tiles count:" << coordsPyramid.tilesCount( );
0192     QVector<TileCoordsPyramid> pyramid;
0193     pyramid << coordsPyramid;
0194     return pyramid;
0195 }
0196 
0197 void DownloadRegion::setVisibleTileLevel(const int tileLevel)
0198 {
0199     d->m_visibleTileLevel = tileLevel;
0200 }
0201 
0202 QVector<TileCoordsPyramid> DownloadRegion::fromPath( const TileLayer *tileLayer, qreal offset, const GeoDataLineString &waypoints ) const
0203 {
0204     if ( !d->m_marbleModel ) {
0205         return QVector<TileCoordsPyramid>();
0206     }
0207 
0208     int tileLevelRangeFirst = d->m_tileLevelRange.first;
0209     int tileLevelRangeSecond = d->m_tileLevelRange.second;
0210 
0211     TileType tileType = dynamic_cast<const TextureLayer*>( tileLayer ) ? TextureTileType : VectorTileType;
0212 
0213     QVector<int> validLevels;
0214     validLevels = validTileLevels(tileType);
0215 
0216     // Align the tileLevelRangeSecond with the validTileLevels
0217     if (!validLevels.isEmpty()) {
0218         int lastIndex = validLevels.count() - 1;
0219         for ( int i = 0; i < validLevels.count(); ++i ) {
0220             if (validLevels.at(lastIndex - i) <= tileLevelRangeSecond
0221                 && validLevels.at(lastIndex - i) >= tileLevelRangeFirst) {
0222                 tileLevelRangeSecond = validLevels.at(lastIndex - i);
0223                 break;
0224             }
0225         }
0226     }
0227 
0228     TileCoordsPyramid coordsPyramid( tileLevelRangeFirst, tileLevelRangeSecond );
0229     coordsPyramid.setValidTileLevels(validLevels);
0230 
0231     int const tileWidth = tileLayer->tileSize().width();
0232     int const tileHeight = tileLayer->tileSize().height();
0233 
0234     qreal radius = d->m_marbleModel->planetRadius();
0235     QVector<TileCoordsPyramid> pyramid;
0236     qreal radianOffset = offset / radius;
0237 
0238     for( int i = 1; i < waypoints.size(); ++i ) {
0239         GeoDataCoordinates position = waypoints[i];
0240         qreal lonCenter = position.longitude();
0241         qreal latCenter = position.latitude();
0242 
0243         // coordinates of the of the vertices of the square(topleft and bottomright) at an offset distance from the waypoint
0244         qreal latNorth = asin( sin( latCenter ) *  cos( radianOffset ) +  cos( latCenter ) * sin( radianOffset )  * cos( 7*M_PI/4 ) );
0245         qreal dlonWest = atan2( sin( 7*M_PI/4 ) * sin( radianOffset ) * cos( latCenter ),  cos( radianOffset ) -  sin( latCenter ) * sin( latNorth ) );
0246         qreal lonWest  = fmod( lonCenter - dlonWest + M_PI, 2*M_PI ) - M_PI;
0247         qreal latSouth = asin( sin( latCenter ) * cos( radianOffset ) + cos( latCenter ) * sin( radianOffset ) * cos( 3*M_PI/4 ) );
0248         qreal dlonEast =  atan2( sin( 3*M_PI/4 ) * sin( radianOffset ) * cos( latCenter ),  cos( radianOffset ) -  sin( latCenter ) * sin( latSouth ) );
0249         qreal lonEast  = fmod( lonCenter - dlonEast+M_PI, 2*M_PI ) - M_PI;
0250 
0251         int const northY = d->rad2PixelY( latNorth, tileLayer );
0252         int const southY = d->rad2PixelY( latSouth, tileLayer );
0253         int const eastX =  d->rad2PixelX( lonEast, tileLayer );
0254         int const westX =  d->rad2PixelX( lonWest, tileLayer );
0255 
0256         int const west  = qMin( westX, eastX );
0257         int const north = qMin( northY, southY );
0258         int const east  = qMax( westX, eastX );
0259         int const south = qMax( northY, southY );
0260 
0261         int bottomLevelTileX1 = 0;
0262         int bottomLevelTileY1 = 0;
0263         int bottomLevelTileX2 = 0;
0264         int bottomLevelTileY2 = 0;
0265 
0266         if ( d->m_visibleTileLevel > tileLevelRangeSecond ) {
0267             int const deltaLevel = d->m_visibleTileLevel - tileLevelRangeSecond;
0268             bottomLevelTileX1 = west  >> deltaLevel;
0269             bottomLevelTileY1 = north >> deltaLevel;
0270             bottomLevelTileX2 = east  >> deltaLevel;
0271             bottomLevelTileY2 = south >> deltaLevel;
0272         }
0273         else if ( d->m_visibleTileLevel < tileLevelRangeSecond ) {
0274             int const deltaLevel = tileLevelRangeSecond - d->m_visibleTileLevel;
0275             bottomLevelTileX1 = west  << deltaLevel;
0276             bottomLevelTileY1 = north << deltaLevel;
0277             bottomLevelTileX2 = east  << deltaLevel;
0278             bottomLevelTileY2 = south << deltaLevel;
0279         }
0280         else {
0281             bottomLevelTileX1 = west;
0282             bottomLevelTileY1 = north;
0283             bottomLevelTileX2 = east;
0284             bottomLevelTileY2 = south;
0285         }
0286 
0287         QRect waypointRegion;
0288         //square region around the waypoint
0289         waypointRegion.setCoords( bottomLevelTileX1/tileWidth, bottomLevelTileY1/tileHeight,
0290                                   bottomLevelTileX2/tileWidth, bottomLevelTileY2/tileHeight );
0291         coordsPyramid.setBottomLevelCoords( waypointRegion );
0292         pyramid << coordsPyramid;
0293     }
0294 
0295     return pyramid;
0296 }
0297 
0298 QVector<int> DownloadRegion::validTileLevels( const TileType tileType ) const
0299 {
0300     QVector<int> validTileLevels;
0301 
0302     GeoSceneMap * map = d->m_marbleModel->mapTheme()->map();
0303     QVector<GeoSceneLayer*> layers = map->layers();
0304     for (auto layer : layers) {
0305         if ((layer->backend() == QString::fromUtf8("vectortile") && tileType == VectorTileType )
0306             || (layer->backend() == QString::fromUtf8("texture") && tileType == TextureTileType ) ) {
0307             GeoSceneTileDataset * dataset = dynamic_cast<GeoSceneTileDataset*>(layer->datasets().first());
0308             validTileLevels = dataset->tileLevels();
0309             break;
0310         }
0311     }
0312 
0313     return validTileLevels;
0314 }
0315 
0316 }
0317 
0318 #include "moc_DownloadRegion.cpp"