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