1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2011 Niko Sams <niko.sams@gmail.com>
4 //
5 
6 #include "tccore.h"
7 #include <MarbleGlobal.h>
8 #include <TileLoaderHelper.h>
9 #include <QFile>
10 #include <cmath>
11 #include <QPainter>
12 #include <QProcess>
13 #include <QFileInfo>
14 #include <QDir>
15 #include <QCache>
16 
17 using namespace Marble;
18 
19 class TileCreatorSourceSrtm : public TileCreatorSource
20 {
21 public:
TileCreatorSourceSrtm(const QString & sourceDir)22     TileCreatorSourceSrtm( const QString &sourceDir )
23         : m_sourceDir( sourceDir )
24     {
25     }
26 
fullImageSize() const27     QSize fullImageSize() const override
28     {
29         return QSize( 512*c_defaultTileSize*2, 512*c_defaultTileSize ); //512: 2**9 (9 zoom levels)
30     }
31 
tile(int n,int m,int maxTileLevel)32     QImage tile( int n, int m, int maxTileLevel ) override
33     {
34         Q_ASSERT( maxTileLevel == 9 );
35 
36         int  nmax = TileLoaderHelper::levelToRow( defaultLevelZeroRows, maxTileLevel );
37         Q_ASSERT( nmax == 512 );
38         int  mmax = TileLoaderHelper::levelToColumn( defaultLevelZeroColumns, maxTileLevel );
39 
40         qreal startLat = ( ( ( (qreal)n * 180 ) / nmax ) - 90 ) * -1;
41         qreal startLng = ( ( (qreal)m * 360 ) / mmax ) - 180;
42 
43         int startLngPxResized = c_defaultTileSize * m;
44         int startLatPxResized = c_defaultTileSize * n;
45 
46 
47         if (hgtFileName(std::floor(startLng), std::floor(startLat)).isNull()
48             && hgtFileName(std::floor(startLng)+1, std::floor(startLat)).isNull()
49             && hgtFileName(std::floor(startLng), std::floor(startLat)-1).isNull()
50             && hgtFileName(std::floor(startLng)+1, std::floor(startLat)-1).isNull()
51         ) {
52             QImage ret( c_defaultTileSize, c_defaultTileSize, QImage::Format_ARGB32  );
53             QPainter painter( &ret );
54             painter.fillRect( 0, 0, c_defaultTileSize, c_defaultTileSize, QColor( Qt::black ) );
55             return ret;
56         }
57 
58         QImage image( 2400, 2400, QImage::Format_ARGB32  );
59         {
60             QPainter painter( &image );
61             painter.fillRect( 0, 0, 2400, 2400, QColor( Qt::black ) );
62             QImage i = readHgt(std::floor(startLng), std::floor(startLat));
63             painter.drawImage( 0, 0, i );
64             painter.drawImage( 1200, 0, readHgt( std::floor(startLng)+1, std::floor(startLat) ) );
65             painter.drawImage( 0, 1200, readHgt( std::floor(startLng), std::floor(startLat)-1 ) );
66             painter.drawImage( 1200, 1200, readHgt( std::floor(startLng)+1, std::floor(startLat)-1 ) );
67         }
68 
69         int imageSizeResized = 2400 * (512 * c_defaultTileSize) / (1200 * 180);
70 
71         // Pick the current row and smooth scale it
72         // to make it match the expected size
73         image = image.scaled( QSize(imageSizeResized, imageSizeResized),
74                         Qt::IgnoreAspectRatio );
75 
76 
77         //startL??Px: position in px of what we are looking for
78         int startLngPx = startLng * 1200;
79         startLngPx = ( 180 * 1200 ) + startLngPx;
80         //qDebug() << "startLngPx(/1200)" << (qreal)startLngPx / 1200;
81 
82         int startLatPx = startLat * 1200;
83         startLatPx = ( 90 * 1200 ) - startLatPx;
84         //qDebug() << "startLatPx(/1200)" << (qreal)startLatPx / 1200;
85 
86 
87         //imageL??Px: position in px of image
88         int imageLngPx = std::floor(startLng);
89         imageLngPx = 180 + imageLngPx;
90         //qDebug() << "imageLngPx(/1200)" << imageLngPx << "*1200" << imageLngPx*1200;
91         imageLngPx *= 1200;
92 
93         int imageLatPx = std::floor(90 - startLat);
94         //qDebug() << "imageLatPx(/1200)" << imageLatPx;
95         imageLatPx *= 1200;
96 
97         //qDebug() << "lng" << imageLngPx << startLngPx << "offset" << startLngPx - imageLngPx;
98         //qDebug() << "lat" << imageLatPx << startLatPx << "offset" << startLatPx - imageLatPx;
99         Q_ASSERT(1200*2 - (startLngPx - imageLngPx) >= 675);
100         Q_ASSERT(1200*2 - (startLatPx - imageLatPx) >= 675);
101         Q_ASSERT(startLngPx - imageLngPx >= 0);
102         Q_ASSERT(startLatPx - imageLatPx >= 0);
103 
104         int imageLngPxResized = imageLngPx * 1.6; //(512 * c_defaultTileSize) / (1200 * 180);
105         int imageLatPxResized = imageLatPx * 1.6; //(512 * c_defaultTileSize) / (1200 * 180);
106         //qDebug() << "lng(m)" << startLngPxResized << imageLngPxResized << "diff" << startLngPxResized - imageLngPxResized;
107         //qDebug() << "lat(n)" << startLatPxResized << imageLngPxResized << "diff" << startLatPxResized - imageLatPxResized;
108         Q_ASSERT(startLngPxResized - imageLngPxResized < imageSizeResized);
109         Q_ASSERT(startLatPxResized - imageLatPxResized < imageSizeResized);
110         Q_ASSERT(startLngPxResized - imageLngPxResized >= 0);
111         Q_ASSERT(startLatPxResized - imageLatPxResized >= 0);
112 
113         QImage croppedImage = image.copy(startLngPx - imageLngPx, startLatPx - imageLatPx, 675, 675);
114         QImage ret = image.copy(startLngPxResized - imageLngPxResized, startLatPxResized - imageLatPxResized, c_defaultTileSize, c_defaultTileSize);
115         //qDebug() << image.size() << ret.size();
116         return ret;
117     }
118 
119 private:
hgtFileName(int lng,int lat) const120     QString hgtFileName( int lng, int lat ) const
121     {
122         QChar EW(QLatin1Char(lng >= 0 ? 'E' : 'W'));
123         QChar NS(QLatin1Char(lat >= 0 ? 'N' : 'S'));
124 
125         QStringList dirs;
126         dirs << "Africa" << "Australia" << "Eurasia" << "Silands" << "North_America" << "South_America";
127         for( const QString &dir: dirs) {
128             QString fileName = m_sourceDir + QLatin1Char('/') + dir + QLatin1Char('/');
129             if ( lat < 0 ) lat *= -1;
130             fileName += QString( "%1%2%3%4.hgt" ).arg( NS ).arg( lat<0 ? lat*-1 : lat, 2, 10, QLatin1Char('0') )
131                                         .arg( EW ).arg( lng<0 ? lng*-1 : lng, 3, 10, QLatin1Char('0' ) );
132             //qDebug() << fileName;
133 
134             if (!QFile::exists(fileName) && QFile::exists(fileName + QLatin1String(".zip"))) {
135                 qDebug() << "zip found, unzipping";
136                 QProcess p;
137                 p.execute("unzip", QStringList() << fileName + QLatin1String(".zip"));
138                 p.waitForFinished();
139                 QFile(QDir::currentPath() + QLatin1Char('/') + QFileInfo(fileName).fileName()).rename(fileName);
140             }
141             if ( QFile::exists( fileName ) ) {
142                 return fileName;
143             }
144         }
145 
146         return QString();
147     }
148 
readHgt(int lng,int lat)149     QImage readHgt( int lng, int lat )
150     {
151         static QCache<QPair<int, int>, QImage > cache( 10 );
152         if ( cache.contains( qMakePair( lng, lat ) ) ) {
153             return *cache[ qMakePair( lng, lat ) ];
154         }
155         QString fileName = hgtFileName( lng, lat );
156         if ( fileName.isNull() ) {
157             //qDebug() << lng << lat << "hgt file does not exist, returning null image";
158             return QImage();
159         } else {
160             //qDebug() << lng << lat << "reading hgt file" << fileName;
161         }
162 
163         QFile file( fileName );
164 
165         file.open( QIODevice::ReadOnly );
166         int iLat = 0;
167         int iLng = 0;
168 
169         //hgt file is 1201px large, but the last px is overlapping
170         QImage image( 1200, 1200, QImage::Format_ARGB32 );
171         while(true) {
172             QByteArray data = file.read( 2 );
173 
174             if ( iLng < 1200 ) {
175                 unsigned short height =  *(unsigned short*)data.data();
176                 height = ( height << 8 | height >> 8 );
177                 unsigned int pixel;
178                 pixel = height;
179                 pixel += 0xFF000000; //fully opaque
180                 image.setPixel( iLng, iLat, pixel );
181             }
182 
183             if ( iLat >= 1199 && iLng >= 1199 ) break;
184 
185             iLng++;
186             if ( iLng > 1200 ) { //here not 1199 but one more, because of overlapping px at the end of the line
187                 iLng = 0;
188                 iLat++;
189             }
190         }
191         file.close();
192 
193         cache.insert( qMakePair( lng, lat ), new QImage( image ) );
194 
195         return image;
196     }
197 
198     QString m_sourceDir;
199 };
200 
TCCoreApplication(int argc,char ** argv)201 TCCoreApplication::TCCoreApplication( int argc, char ** argv ) : QCoreApplication( argc, argv )
202 {
203 
204     if( !(argc < 2) )
205     {
206         TileCreatorSource *source = new TileCreatorSourceSrtm( argv[1] );
207         m_tilecreator = new TileCreator( source, "false", argv[2] );
208         m_tilecreator->setTileFormat( "png" );
209         m_tilecreator->setTileQuality( 25 );
210         m_tilecreator->setResume( true );
211         m_tilecreator->setVerifyExactResult( true );
212         connect( m_tilecreator, SIGNAL(finished()), this, SLOT(quit()) );
213         m_tilecreator->start();
214     }
215 }
216