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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Niko Sams <niko.sams@gmail.com>
0004 //
0005 
0006 #include "ElevationModel.h"
0007 
0008 #include <QCache>
0009 #include <QImage>
0010 #include <qmath.h>
0011 
0012 #include "GeoSceneHead.h"
0013 #include "GeoSceneLayer.h"
0014 #include "GeoSceneMap.h"
0015 #include "GeoSceneDocument.h"
0016 #include "GeoSceneTextureTileDataset.h"
0017 #include "HttpDownloadManager.h"
0018 #include "Tile.h"
0019 #include "TileLoader.h"
0020 #include "TileLoaderHelper.h"
0021 #include "MapThemeManager.h"
0022 #include "TileId.h"
0023 #include "PluginManager.h"
0024 
0025 #include "digikam_debug.h"
0026 
0027 namespace Marble
0028 {
0029 
0030 class ElevationModelPrivate
0031 {
0032 public:
0033     ElevationModelPrivate( ElevationModel *_q, HttpDownloadManager *downloadManager, PluginManager* pluginManager )
0034         : q( _q ),
0035           m_tileLoader( downloadManager, pluginManager ),
0036           m_textureLayer( nullptr ),
0037           m_srtmTheme(nullptr)
0038     {
0039         m_cache.setMaxCost( 10 ); //keep 10 tiles in memory (~17MB)
0040 
0041         m_srtmTheme = MapThemeManager::loadMapTheme( QString::fromUtf8("earth/srtm2/srtm2.dgml" ));
0042         if ( !m_srtmTheme ) {
0043             qCDebug(DIGIKAM_MARBLE_LOG) << "Failed to load map theme earth/srtm2/srtm2.dgml. Check your installation. No elevation will be returned.";
0044             return;
0045         }
0046 
0047         const GeoSceneHead *head = m_srtmTheme->head();
0048         Q_ASSERT( head );
0049 
0050         const GeoSceneMap *map = m_srtmTheme->map();
0051         Q_ASSERT( map );
0052 
0053         const GeoSceneLayer *sceneLayer = map->layer( head->theme() );
0054         if (!sceneLayer) {
0055             qCDebug(DIGIKAM_MARBLE_LOG) << "Failed to instantiate elevation map. No elevation will be returned.";
0056             return;
0057         }
0058         Q_ASSERT( sceneLayer );
0059 
0060         m_textureLayer = dynamic_cast<GeoSceneTextureTileDataset*>( sceneLayer->datasets().first() );
0061         Q_ASSERT( m_textureLayer );
0062     }
0063 
0064     ~ElevationModelPrivate()
0065     {
0066        delete m_srtmTheme;
0067     }
0068 
0069     void tileCompleted( const TileId & tileId, const QImage &image )
0070     {
0071         m_cache.insert( tileId, new QImage( image ) );
0072         Q_EMIT q->updateAvailable();
0073     }
0074 
0075 public:
0076     ElevationModel *q;
0077 
0078     TileLoader m_tileLoader;
0079     const GeoSceneTextureTileDataset *m_textureLayer;
0080     QCache<TileId, const QImage> m_cache;
0081     GeoSceneDocument *m_srtmTheme;
0082 };
0083 
0084 ElevationModel::ElevationModel( HttpDownloadManager *downloadManager, PluginManager* pluginManager, QObject *parent ) :
0085     QObject( parent ),
0086     d( new ElevationModelPrivate( this, downloadManager, pluginManager ) )
0087 {
0088     connect( &d->m_tileLoader, SIGNAL(tileCompleted(TileId,QImage)),
0089              this, SLOT(tileCompleted(TileId,QImage)) );
0090 }
0091 
0092 ElevationModel::~ElevationModel()
0093 {
0094     delete d;
0095 }
0096 
0097 
0098 qreal ElevationModel::height( qreal lon, qreal lat ) const
0099 {
0100     if ( !d->m_textureLayer ) {
0101         return invalidElevationData;
0102     }
0103 
0104     const int tileZoomLevel = TileLoader::maximumTileLevel( *( d->m_textureLayer ) );
0105     Q_ASSERT( tileZoomLevel == 9 );
0106 
0107     const int width = d->m_textureLayer->tileSize().width();
0108     const int height = d->m_textureLayer->tileSize().height();
0109 
0110     const int numTilesX = TileLoaderHelper::levelToColumn( d->m_textureLayer->levelZeroColumns(), tileZoomLevel );
0111     const int numTilesY = TileLoaderHelper::levelToRow( d->m_textureLayer->levelZeroRows(), tileZoomLevel );
0112     Q_ASSERT( numTilesX > 0 );
0113     Q_ASSERT( numTilesY > 0 );
0114 
0115     qreal textureX = 180 + lon;
0116     textureX *= numTilesX * width / 360;
0117 
0118     qreal textureY = 90 - lat;
0119     textureY *= numTilesY * height / 180;
0120 
0121     qreal ret = 0;
0122     bool hasHeight = false;
0123     qreal noData = 0;
0124 
0125     for ( int i = 0; i < 4; ++i ) {
0126         const int x = static_cast<int>( textureX + ( i % 2 ) );
0127         const int y = static_cast<int>( textureY + ( i / 2 ) );
0128 
0129         //qCDebug(DIGIKAM_MARBLE_LOG) << "x" << x << ( x / width );
0130         //qCDebug(DIGIKAM_MARBLE_LOG) << "y" << y << ( y / height );
0131 
0132         const TileId id( 0, tileZoomLevel, ( x % ( numTilesX * width ) ) / width, ( y % ( numTilesY * height ) ) / height );
0133         //qCDebug(DIGIKAM_MARBLE_LOG) << "LAT" << lat << "LON" << lon << "tile" << ( x % ( numTilesX * width ) ) / width << ( y % ( numTilesY * height ) ) / height;
0134 
0135         const QImage *image = d->m_cache[id];
0136         if ( image == nullptr ) {
0137             image = new QImage( d->m_tileLoader.loadTileImage( d->m_textureLayer, id, DownloadBrowse ) );
0138             d->m_cache.insert( id, image );
0139         }
0140         Q_ASSERT( image );
0141         Q_ASSERT( !image->isNull() );
0142         Q_ASSERT( width == image->width() );
0143         Q_ASSERT( height == image->height() );
0144 
0145         const qreal dx = ( textureX > ( qreal )x ) ? textureX - ( qreal )x : ( qreal )x - textureX;
0146         const qreal dy = ( textureY > ( qreal )y ) ? textureY - ( qreal )y : ( qreal )y - textureY;
0147 
0148         Q_ASSERT( 0 <= dx && dx <= 1 );
0149         Q_ASSERT( 0 <= dy && dy <= 1 );
0150         unsigned int pixel = image->pixel( x % width, y % height ) & 0xffff; // 16 valid bits
0151         short int elevation = (short int) pixel; // and signed type, so just cast it
0152         //qCDebug(DIGIKAM_MARBLE_LOG) << "(1-dx)" << (1-dx) << "(1-dy)" << (1-dy);
0153         if ( pixel != invalidElevationData ) { //no data?
0154             //qCDebug(DIGIKAM_MARBLE_LOG) << "got at x" << x % width << "y" << y % height << "a height of" << pixel << "** RGB" << qRed(pixel) << qGreen(pixel) << qBlue(pixel);
0155             ret += ( qreal )elevation * ( 1 - dx ) * ( 1 - dy );
0156             hasHeight = true;
0157         } else {
0158             //qCDebug(DIGIKAM_MARBLE_LOG) << "no data at" <<  x % width << "y" << y % height;
0159             noData += ( 1 - dx ) * ( 1 - dy );
0160         }
0161     }
0162 
0163     if ( !hasHeight ) {
0164         ret = invalidElevationData; //no data
0165     } else {
0166         if ( noData ) {
0167             //qCDebug(DIGIKAM_MARBLE_LOG) << "NO DATA" << noData;
0168             ret += ( ret / ( 1 - noData ) ) * noData;
0169         }
0170     }
0171 
0172     //qCDebug(DIGIKAM_MARBLE_LOG) << ">>>" << lat << lon << "returning an elevation of" << ret;
0173     return ret;
0174 }
0175 
0176 QVector<GeoDataCoordinates> ElevationModel::heightProfile( qreal fromLon, qreal fromLat, qreal toLon, qreal toLat ) const
0177 {
0178     if ( !d->m_textureLayer ) {
0179         return QVector<GeoDataCoordinates>();
0180     }
0181 
0182     const int tileZoomLevel = TileLoader::maximumTileLevel( *( d->m_textureLayer ) );
0183     const int width = d->m_textureLayer->tileSize().width();
0184     const int numTilesX = TileLoaderHelper::levelToColumn( d->m_textureLayer->levelZeroColumns(), tileZoomLevel );
0185 
0186     qreal distPerPixel = ( qreal )360 / ( width * numTilesX );
0187     //qCDebug(DIGIKAM_MARBLE_LOG) << "heightProfile" << fromLat << fromLon << toLat << toLon << "distPerPixel" << distPerPixel;
0188 
0189     qreal lat = fromLat;
0190     qreal lon = fromLon;
0191     char dirLat = fromLat < toLat ? 1 : -1;
0192     char dirLon = fromLon < toLon ? 1 : -1;
0193     qreal k = qAbs( ( fromLat - toLat ) / ( fromLon - toLon ) );
0194     //qCDebug(DIGIKAM_MARBLE_LOG) << "fromLon" << fromLon << "fromLat" << fromLat;
0195     //qCDebug(DIGIKAM_MARBLE_LOG) << "diff lon" << ( fromLon - toLon ) << "diff lat" << ( fromLat - toLat );
0196     //qCDebug(DIGIKAM_MARBLE_LOG) << "dirLon" << QString::number(dirLon) << "dirLat" << QString::number(dirLat) << "k" << k;
0197     QVector<GeoDataCoordinates> ret;
0198     while ( lat*dirLat <= toLat*dirLat && lon*dirLon <= toLon * dirLon ) {
0199         //qCDebug(DIGIKAM_MARBLE_LOG) << lat << lon;
0200         qreal h = height( lon, lat );
0201         if ( h < 32000 ) {
0202             ret << GeoDataCoordinates( lon, lat, h, GeoDataCoordinates::Degree );
0203         }
0204         if ( k < 0.5 ) {
0205             //qCDebug(DIGIKAM_MARBLE_LOG) << "lon(x) += distPerPixel";
0206             lat += distPerPixel * k * dirLat;
0207             lon += distPerPixel * dirLon;
0208         } else {
0209             //qCDebug(DIGIKAM_MARBLE_LOG) << "lat(y) += distPerPixel";
0210             lat += distPerPixel * dirLat;
0211             lon += distPerPixel / k * dirLon;
0212         }
0213     }
0214     //qCDebug(DIGIKAM_MARBLE_LOG) << ret;
0215     return ret;
0216 }
0217 
0218 }
0219 
0220 
0221 
0222 #include "moc_ElevationModel.cpp"