source: subversion/applications/utils/import/srtm2wayinfo/srtm.cpp

Last change on this file was 17072, checked in by herm, 8 years ago

Improved the memory managment a bit.

File size: 8.2 KB
Line 
1/* Copyright (c) 2009 Hermann Kraus
2 * This software is available under a "MIT Style" license
3 * (see COPYING).
4 */
5/** \file
6  * SRTM data downloader and tile handler.
7  */
8#include "srtm.h"
9#include "zip.h"
10
11#include <math.h>
12#include <QDir>
13#include <QStringList>
14#include <QString>
15#include <QProcess>
16#include <QDebug>
17#include <qendian.h>
18
19/** Standard error tile that is returned when something goes wrong. */
20SrtmTile errorTile("error", -1000, -1000); //TODO
21
22/** Callback from curl for data that is not stored in a file but kept in memory. */
23size_t curl_data_callback(void *ptr, size_t size, size_t nmemb, void *stream)
24{
25    SrtmDownloader *downloader = static_cast<SrtmDownloader*>(stream);
26    downloader->curlAddData(ptr, size*nmemb);
27    return size*nmemb;
28}
29
30/** Callback from curl for data that is stored in a file. */
31size_t curl_file_callback(char *ptr, size_t size, size_t nmemb, void *stream)
32{
33    QFile *file = static_cast<QFile *>(stream);
34    int size_left = size * nmemb;
35    while (size_left > 0) {
36        int result = file->write(ptr, size_left);
37        if (result == -1) {
38            qCritical() << "Error while writing to file!" << file->errorString();
39        } else {
40            ptr += result;
41            size_left -= result;
42        }
43    }
44    return size*nmemb;
45}
46
47/** Constructor for SrtmDownloader.
48  * \note This downloader currently only supports SRTM3 data.
49  * \param url URL to the SRTM data.
50  * \param cachedir Directory in which the downloaded files are stored.
51  */
52SrtmDownloader::SrtmDownloader(QString url, QString cachedir)
53{
54    this->url = url;
55    this->cachedir = cachedir+"/";
56    regex.setPattern("<a href=\"([NS])(\\d{2})([EW])(\\d{3})\\.hgt\\.zip");
57    QDir dir;
58    if (!dir.exists(cachedir)) {
59        dir.mkpath(cachedir);
60    }
61    curl = curl_easy_init();
62    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
63    curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); //Make sure error reporting works
64    loadFileList();
65}
66
67/** One line helper function. */
68void SrtmDownloader::curlAddData(void *ptr, int size)
69{
70    curlData += QString::fromAscii(static_cast<char*>(ptr), size);
71}
72
73/** Create a new file list by getting directory contents from server. */
74void SrtmDownloader::createFileList()
75{
76    //Store data in memory
77    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_callback);
78    curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
79   
80    QStringList continents;
81    continents << "Africa" << "Australia" << "Eurasia" << "Islands" << "North_America" << "South_America";
82    foreach (QString continent, continents) {
83        qDebug() << "Downloading data from" << url+continent+"/";
84        curlData.clear();
85        curl_easy_setopt(curl, CURLOPT_URL, QString(url+continent+"/").toAscii().constData());
86        CURLcode error = curl_easy_perform(curl);
87        if (error) {
88            qCritical() << "Error downloading data for" << continent << "(" << curl_easy_strerror(error) << ")";
89        }
90        int index = -1;
91        while ((index = curlData.indexOf(regex, index+1)) != -1) {
92            int lat = regex.cap(2).toInt();
93            int lon = regex.cap(4).toInt();
94            if (regex.cap(1) == "S") {
95                lat = -lat;
96            }
97            if (regex.cap(3) == "W") {
98                lon = - lon;
99            }
100            //S00E000.hgt.zip
101            //123456789012345 => 15 bytes long
102            fileList[latLonToIndex(lat, lon)] = continent+"/"+regex.cap().right(15);
103        }
104    }
105    curlData.clear(); //Free mem
106
107    if (fileList.size() != SRTM_FILE_COUNT) {
108        qCritical() << "Could not download complete list of tiles from SRTM server. Got" << fileList.size() << "tiles but" << SRTM_FILE_COUNT << "were expected.";
109        exit(1);
110    }
111   
112    QFile file(cachedir+"filelist");
113    if (!file.open(QIODevice::WriteOnly)) {
114        qCritical() << "Could not open file" << cachedir+"filelist";
115        //Not a fatal error. We just can't cache the list.
116        return;
117    }
118    QDataStream stream(&file);
119    stream << fileList;
120    file.close();
121}
122
123/** Load a file list or create a new one if it doesn't exist. */
124void SrtmDownloader::loadFileList()
125{
126    QFile file(cachedir+"filelist");
127    if (!file.open(QIODevice::ReadOnly)) {
128        createFileList();
129        return;
130    }
131    QDataStream stream(&file);
132    stream >> fileList;
133    file.close();
134    if (fileList.size() != SRTM_FILE_COUNT) {
135        createFileList();
136    }
137}
138
139/** Get tile for a specified location.
140  * \note The tile object returned is owned by this SrtmDownloader instance. It
141  *       _must_ _not_ be deleted by the user. However it _may_ be deleted by
142  *       SrtmDownloader during any later call to getTile()
143  */
144SrtmTile *SrtmDownloader::getTile(float lat, float lon)
145{
146    int intlat = int(floor(lat)), intlon = int(floor(lon));
147    int index = latLonToIndex(intlat, intlon);
148    SrtmTile *tile = tileCache[index];
149
150    if (tile) return tile;
151
152    if (fileList.contains(index)) {
153        QStringList splitted = fileList[index].split("/", QString::SkipEmptyParts);
154        if (!QFile(cachedir + splitted[1]).exists()) {
155            downloadTile(fileList[index]);
156        }
157        tile = new SrtmTile(cachedir + splitted[1], intlat, intlon);
158        tileCache.insert(index, tile);
159        Q_ASSERT(tileCache[index] != 0);
160        return tile;
161    } else {
162       return &errorTile;
163    }
164}
165
166/** Download a tile from the server.
167    \param filename must be in the format continent/tilename.hgt.zip
168    \note You should _not_ call this function when you need tile data. Use getTile instead. */
169void SrtmDownloader::downloadTile(QString filename)
170{
171    qDebug() << "Downloading" << filename;
172    QStringList splitted = filename.split("/", QString::SkipEmptyParts);
173   
174    QFile file(cachedir+splitted[1]);
175    if (!file.open(QIODevice::WriteOnly)) {
176        qCritical() << "Could not create file" << cachedir+splitted[1];
177        return;
178    }
179    curlData.clear();
180    curl_easy_setopt(curl, CURLOPT_URL, QString(url+filename).toAscii().constData());
181    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_file_callback);
182    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file);
183    CURLcode error;
184    if ((error = curl_easy_perform(curl))) {
185        qCritical() << "Could not download" << filename << "("<< curl_easy_strerror(error) << ")";
186    }
187    //File is closed automatically
188}
189
190/** Get altitude and download necessary tiles automatically. */
191float SrtmDownloader::getAltitudeFromLatLon(float lat, float lon)
192{
193    SrtmTile *tile = getTile(lat, lon);
194    if (tile)
195        return tile->getAltitudeFromLatLon(lat, lon);
196    else
197        return SRTM_DATA_VOID;
198}
199
200/** Create a new tile object. Unzips the tile data if necessary.
201  * \note Special filename: "error" returns an invalid tile.
202  */
203SrtmTile::SrtmTile(QString filename, int lat, int lon)
204{
205    this->lat = lat;
206    this->lon = lon;
207    valid = false;
208    if (filename == "error") return;
209    buffer = 0;
210    size = SrtmZipFile::getData(filename, &buffer);
211    Q_ASSERT(size == 1201 || size == 3601);
212    Q_ASSERT(buffer != 0);
213    valid = true;
214}
215
216/** Free resources. */
217SrtmTile::~SrtmTile()
218{
219    if (buffer) delete buffer;
220}
221
222/** Get the value of a pixel from the data using a coordinate system
223  * starting in the upper left (NW) edge growing to the lower right
224  * egde (SE) instead of the SRTM coordinate system.
225  */
226int SrtmTile::getPixelValue(int x, int y)
227{
228    Q_ASSERT(x >= 0 && x < size && y >= 0 && y < size);
229    int offset = x + size * (size - y - 1);
230    qint16 value;
231    value = qFromBigEndian(buffer[offset]);
232    return value;
233}
234
235/** Gets the altitude in meters for a given coordinate. */
236float SrtmTile::getAltitudeFromLatLon(float lat, float lon)
237{
238    if (!valid) return SRTM_DATA_VOID;
239    lat -= this->lat;
240    lon -= this->lon;
241    Q_ASSERT(lat >= 0.0 && lat < 1.0 && lon >= 0.0 && lon < 1.0);
242    float x = lon * (size - 1);
243    float y = lat * (size - 1);
244    /* Variable names:
245        valueXY with X,Y as offset from calculated value, _ for average
246    */
247    float value00 = getPixelValue(x, y);
248    float value10 = getPixelValue(x+1, y);
249    float value01 = getPixelValue(x, y+1);
250    float value11 = getPixelValue(x+1, y+1);
251    float value_0 = avg(value00, value10, x-int(x));
252    float value_1 = avg(value01, value11, x-int(x));
253    float value__ = avg(value_0, value_1, y-int(y));
254    return value__;
255}
Note: See TracBrowser for help on using the repository browser.