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"