2020-12-31 14:03:30 +01:00
|
|
|
|
/*
|
|
|
|
|
WARNING: This code uses internal Qt API - the QZipReader class for reading
|
|
|
|
|
ZIP files - and things may break if Qt changes the API. For Qt5 this is not
|
|
|
|
|
a problem as we can "see the future" now and there are no changes in all
|
|
|
|
|
the supported Qt5 versions up to the last one (5.15). In Qt6 the class
|
|
|
|
|
might change or even disappear in the future, but this is very unlikely
|
|
|
|
|
as there were no changes for several years and The Qt Company's policy
|
|
|
|
|
is: "do not invest any resources into any desktop related stuff unless
|
|
|
|
|
absolutely necessary". There is an issue (QTBUG-3897) since the year 2009 to
|
|
|
|
|
include the ZIP reader into the public API, which aptly illustrates the
|
|
|
|
|
effort The Qt Company is willing to make about anything desktop related...
|
|
|
|
|
*/
|
|
|
|
|
|
2019-01-06 18:45:09 +01:00
|
|
|
|
#include <QtEndian>
|
2019-10-31 17:47:58 +01:00
|
|
|
|
#include <QtMath>
|
2019-01-17 00:47:44 +01:00
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QFile>
|
2021-09-23 22:44:21 +02:00
|
|
|
|
#include <QRegularExpression>
|
2021-09-28 15:25:56 +02:00
|
|
|
|
#include <QLocale>
|
2020-12-31 14:03:30 +01:00
|
|
|
|
#include <private/qzipreader_p.h>
|
2021-08-30 20:31:33 +02:00
|
|
|
|
#include "common/rectc.h"
|
2019-01-06 18:45:09 +01:00
|
|
|
|
#include "dem.h"
|
|
|
|
|
|
2019-01-22 23:00:02 +01:00
|
|
|
|
|
2019-01-06 18:45:09 +01:00
|
|
|
|
#define SRTM3_SAMPLES 1201
|
|
|
|
|
#define SRTM1_SAMPLES 3601
|
|
|
|
|
|
|
|
|
|
#define SRTM_SIZE(samples) \
|
|
|
|
|
((samples) * (samples) * 2)
|
|
|
|
|
|
2019-01-06 19:59:58 +01:00
|
|
|
|
static qreal interpolate(qreal dx, qreal dy, qreal p0, qreal p1, qreal p2,
|
|
|
|
|
qreal p3)
|
|
|
|
|
{
|
|
|
|
|
return p0 * (1 - dx) * (1 - dy) + p1 * dx * (1 - dy) + p2 * dy * (1 - dx)
|
|
|
|
|
+ p3 * dx * dy;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-22 23:00:02 +01:00
|
|
|
|
static qreal value(int col, int row, int samples, const QByteArray *data)
|
2019-01-06 19:59:58 +01:00
|
|
|
|
{
|
|
|
|
|
int pos = ((samples - 1 - row) * samples + col) * 2;
|
2019-01-22 23:00:02 +01:00
|
|
|
|
qint16 val = qFromBigEndian(*((const qint16*)(data->constData() + pos)));
|
2019-01-06 19:59:58 +01:00
|
|
|
|
|
|
|
|
|
return (val == -32768) ? NAN : val;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-22 23:00:02 +01:00
|
|
|
|
static qreal height(const Coordinates &c, const QByteArray *data)
|
2019-01-06 18:45:09 +01:00
|
|
|
|
{
|
|
|
|
|
int samples;
|
|
|
|
|
|
2019-01-22 23:00:02 +01:00
|
|
|
|
if (data->size() == SRTM_SIZE(SRTM3_SAMPLES))
|
2019-01-06 18:45:09 +01:00
|
|
|
|
samples = SRTM3_SAMPLES;
|
2019-01-22 23:00:02 +01:00
|
|
|
|
else if (data->size() == SRTM_SIZE(SRTM1_SAMPLES))
|
2019-01-06 18:45:09 +01:00
|
|
|
|
samples = SRTM1_SAMPLES;
|
|
|
|
|
else
|
|
|
|
|
return NAN;
|
|
|
|
|
|
2019-10-31 17:47:58 +01:00
|
|
|
|
int row = (int)((c.lat() - qFloor(c.lat())) * (samples - 1));
|
|
|
|
|
int col = (int)((c.lon() - qFloor(c.lon())) * (samples - 1));
|
|
|
|
|
qreal dx = ((c.lon() - qFloor(c.lon())) * (samples - 1)) - col;
|
|
|
|
|
qreal dy = ((c.lat() - qFloor(c.lat())) * (samples - 1)) - row;
|
2019-01-06 18:45:09 +01:00
|
|
|
|
|
2019-01-06 19:59:58 +01:00
|
|
|
|
qreal p0 = value(col, row, samples, data);
|
|
|
|
|
qreal p1 = value(col + 1, row, samples, data);
|
|
|
|
|
qreal p2 = value(col, row + 1, samples, data);
|
|
|
|
|
qreal p3 = value(col + 1, row + 1, samples, data);
|
2019-01-06 18:45:09 +01:00
|
|
|
|
|
2019-01-06 19:59:58 +01:00
|
|
|
|
return interpolate(dx, dy, p0, p1, p2, p3);
|
2019-01-06 18:45:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
QString DEM::Tile::latStr() const
|
|
|
|
|
{
|
|
|
|
|
const char ns = (_lat >= 0) ? 'N' : 'S';
|
|
|
|
|
return QString("%1%2").arg(ns).arg(qAbs(_lat), 2, 10, QChar('0'));
|
|
|
|
|
}
|
2019-01-17 00:47:44 +01:00
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
QString DEM::Tile::lonStr() const
|
2019-01-06 18:45:09 +01:00
|
|
|
|
{
|
2021-08-30 20:31:33 +02:00
|
|
|
|
const char ew = (_lon >= 0) ? 'E' : 'W';
|
|
|
|
|
return QString("%1%2").arg(ew).arg(qAbs(_lon), 3, 10, QChar('0'));
|
|
|
|
|
}
|
2019-01-06 18:45:09 +01:00
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
QString DEM::Tile::baseName() const
|
|
|
|
|
{
|
|
|
|
|
return QString("%1%2.hgt").arg(latStr(), lonStr());
|
2020-12-31 14:03:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
QString DEM::_dir;
|
|
|
|
|
QCache<DEM::Tile, QByteArray> DEM::_data;
|
|
|
|
|
|
2020-12-31 14:03:30 +01:00
|
|
|
|
QString DEM::fileName(const QString &baseName)
|
|
|
|
|
{
|
|
|
|
|
return QDir(_dir).absoluteFilePath(baseName);
|
2019-01-06 18:45:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-17 00:47:44 +01:00
|
|
|
|
void DEM::setDir(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
_dir = path;
|
2021-09-01 13:08:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DEM::clearCache()
|
|
|
|
|
{
|
2021-08-30 20:31:33 +02:00
|
|
|
|
_data.clear();
|
2019-01-17 00:47:44 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-06 18:45:09 +01:00
|
|
|
|
qreal DEM::elevation(const Coordinates &c)
|
|
|
|
|
{
|
2019-01-07 22:07:03 +01:00
|
|
|
|
if (_dir.isEmpty())
|
|
|
|
|
return NAN;
|
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
Tile tile(qFloor(c.lon()), qFloor(c.lat()));
|
2019-01-06 18:45:09 +01:00
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
QByteArray *ba = _data[tile];
|
2019-01-22 23:00:02 +01:00
|
|
|
|
if (!ba) {
|
2021-08-30 20:31:33 +02:00
|
|
|
|
QString bn(tile.baseName());
|
2020-12-31 14:03:30 +01:00
|
|
|
|
QString fn(fileName(bn));
|
|
|
|
|
QString zn(fn + ".zip");
|
|
|
|
|
|
|
|
|
|
if (QFileInfo::exists(zn)) {
|
|
|
|
|
QZipReader zip(zn, QIODevice::ReadOnly);
|
|
|
|
|
ba = new QByteArray(zip.fileData(bn));
|
2019-03-05 20:43:05 +01:00
|
|
|
|
qreal ele = height(c, ba);
|
2021-08-30 20:31:33 +02:00
|
|
|
|
_data.insert(tile, ba);
|
2019-03-05 20:43:05 +01:00
|
|
|
|
return ele;
|
2020-12-31 14:03:30 +01:00
|
|
|
|
} else {
|
|
|
|
|
QFile file(fn);
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
|
|
|
qWarning("%s: %s", qPrintable(file.fileName()),
|
|
|
|
|
qPrintable(file.errorString()));
|
2021-08-30 20:31:33 +02:00
|
|
|
|
_data.insert(tile, new QByteArray());
|
2020-12-31 14:03:30 +01:00
|
|
|
|
return NAN;
|
|
|
|
|
} else {
|
|
|
|
|
ba = new QByteArray(file.readAll());
|
|
|
|
|
qreal ele = height(c, ba);
|
2021-08-30 20:31:33 +02:00
|
|
|
|
_data.insert(tile, ba);
|
2020-12-31 14:03:30 +01:00
|
|
|
|
return ele;
|
|
|
|
|
}
|
2019-01-06 18:45:09 +01:00
|
|
|
|
}
|
|
|
|
|
} else
|
2019-01-22 23:00:02 +01:00
|
|
|
|
return height(c, ba);
|
2019-01-06 18:45:09 +01:00
|
|
|
|
}
|
2021-08-30 20:31:33 +02:00
|
|
|
|
|
2021-09-23 22:44:21 +02:00
|
|
|
|
QList<Area> DEM::tiles()
|
|
|
|
|
{
|
|
|
|
|
QDir dir(_dir);
|
|
|
|
|
QFileInfoList files(dir.entryInfoList(QDir::Files | QDir::Readable));
|
|
|
|
|
QRegularExpression re("([NS])([0-9]{2})([EW])([0-9]{3})");
|
2021-09-28 15:25:56 +02:00
|
|
|
|
QLocale l(QLocale::system());
|
2021-09-23 22:44:21 +02:00
|
|
|
|
QList<Area> list;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < files.size(); i++) {
|
|
|
|
|
QString basename(files.at(i).baseName());
|
|
|
|
|
QRegularExpressionMatch match(re.match(basename));
|
|
|
|
|
if (!match.hasMatch())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
int lat = match.captured(2).toInt();
|
|
|
|
|
int lon = match.captured(4).toInt();
|
|
|
|
|
if (match.captured(1) == "S")
|
|
|
|
|
lat = -lat;
|
|
|
|
|
if (match.captured(3) == "W")
|
|
|
|
|
lon = -lon;
|
|
|
|
|
|
|
|
|
|
Area area(RectC(Coordinates(lon, lat + 1), Coordinates(lon + 1, lat)));
|
|
|
|
|
area.setName(basename);
|
2021-09-28 15:25:56 +02:00
|
|
|
|
area.setDescription(files.at(i).suffix().toUpper() + ", "
|
|
|
|
|
+ l.formattedDataSize(files.at(i).size()));
|
2021-09-23 22:44:21 +02:00
|
|
|
|
|
|
|
|
|
list.append(area);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
|
|
|
QDebug operator<<(QDebug dbg, const DEM::Tile &tile)
|
|
|
|
|
{
|
|
|
|
|
dbg.nospace() << "Tile(" << tile.baseName() << ")";
|
|
|
|
|
return dbg.space();
|
|
|
|
|
}
|
|
|
|
|
#endif // QT_NO_DEBUG
|