File indexing completed on 2024-04-21 07:36:04

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"