1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-10-06 23:03:22 +02:00
GPXSee/src/map/rmap.cpp

469 lines
12 KiB
C++
Raw Normal View History

2019-03-02 16:51:14 +01:00
#include <QFileInfo>
#include <QDataStream>
#include <QPixmapCache>
#include <QPainter>
#include <QRegularExpression>
2019-03-07 22:58:43 +01:00
#include <QtEndian>
2019-03-02 16:51:14 +01:00
#include "common/rectc.h"
#include "common/wgs84.h"
2021-10-10 08:38:38 +02:00
#include "common/color.h"
#include "calibrationpoint.h"
2019-03-02 16:51:14 +01:00
#include "utm.h"
#include "pcs.h"
#include "rectd.h"
#include "rmap.h"
#define MAGIC "CompeGPSRasterImage"
static CalibrationPoint parseCalibrationPoint(const QString &str)
{
QStringList fields(str.split(","));
if (fields.size() != 5)
return CalibrationPoint();
bool ret1, ret2;
PointD xy(fields.at(0).toDouble(&ret1), fields.at(1).toDouble(&ret2));
if (!ret1 || !ret2)
return CalibrationPoint();
PointD pp(fields.at(3).toDouble(&ret1), fields.at(4).toDouble(&ret2));
if (!ret1 || !ret2)
return CalibrationPoint();
return (fields.at(2) == "A")
? CalibrationPoint(xy, Coordinates(pp.x(), pp.y()))
: CalibrationPoint(xy, pp);
}
2021-06-17 21:58:25 +02:00
static Projection parseProjection(const QString &str, const GCS &gcs)
2019-03-02 16:51:14 +01:00
{
QStringList fields(str.split(","));
if (fields.isEmpty())
return Projection();
bool ret;
int id = fields.at(0).toDouble(&ret);
if (!ret)
return Projection();
int zone;
switch (id) {
2019-03-07 22:58:43 +01:00
case 0: // UTM
2019-03-02 16:51:14 +01:00
if (fields.size() < 4)
return Projection();
zone = fields.at(2).toInt(&ret);
if (!ret)
return Projection();
2019-03-02 18:49:01 +01:00
if (fields.at(3) == "S")
zone = -zone;
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9807, UTM::setup(zone), 9001));
2019-03-07 22:58:43 +01:00
case 1: // LatLon
2019-03-02 16:51:14 +01:00
return Projection(gcs);
2019-03-07 22:58:43 +01:00
case 2: // Mercator
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 1024, Projection::Setup(), 9001));
2019-03-07 22:58:43 +01:00
case 3: // Transversal Mercator
if (fields.size() < 7)
return Projection();
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9807, Projection::Setup(
fields.at(3).toDouble(), fields.at(2).toDouble(),
fields.at(6).toDouble(), fields.at(5).toDouble(),
fields.at(4).toDouble(), NAN, NAN), 9001));
2019-03-07 22:58:43 +01:00
case 4: // Lambert 2SP
if (fields.size() < 8)
return Projection();
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9802, Projection::Setup(
fields.at(4).toDouble(), fields.at(5).toDouble(), NAN,
2019-03-07 22:58:43 +01:00
fields.at(6).toDouble(), fields.at(7).toDouble(),
2021-06-17 21:58:25 +02:00
fields.at(3).toDouble(), fields.at(2).toDouble()), 9001));
2019-03-07 22:58:43 +01:00
case 6: // BGN (British National Grid)
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9807, Projection::Setup(49, -2, 0.999601,
400000, -100000, NAN, NAN), 9001));
2019-03-07 22:58:43 +01:00
case 12: // France Lambert II etendu
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9801, Projection::Setup(52, 0,
0.99987742, 600000, 2200000, NAN, NAN), 9001));
2019-03-07 22:58:43 +01:00
case 14: // Swiss Grid
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9815, Projection::Setup(46.570866,
7.26225, 1.0, 600000, 200000, 90.0, 90.0), 9001));
case 108: // Dutch RD grid
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9809, Projection::Setup(52.15616055555555,
5.38763888888889, 0.9999079, 155000, 463000, NAN, NAN), 9001));
2019-03-07 22:58:43 +01:00
case 184: // Swedish Grid
2021-06-17 21:58:25 +02:00
return Projection(PCS(gcs, 9807, Projection::Setup(0, 15.808278, 1,
1500000, 0, NAN, NAN), 9001));
2019-03-02 16:51:14 +01:00
default:
return Projection();
}
}
bool RMap::parseIMP(const QByteArray &data)
{
QStringList lines = QString(data).split("\r\n");
2019-03-05 22:34:50 +01:00
QVector<CalibrationPoint> calibrationPoints;
2019-03-02 16:51:14 +01:00
QString projection, datum;
QRegularExpression re("^P[0-9]+=");
2019-03-02 16:51:14 +01:00
for (int i = 0; i < lines.count(); i++) {
const QString &line = lines.at(i);
if (line.startsWith("Projection="))
projection = line.split("=").at(1);
else if (line.startsWith("Datum="))
datum = line.split("=").at(1);
2019-03-04 22:29:53 +01:00
else if (line.contains(re)) {
2019-03-02 16:51:14 +01:00
QString point(line.split("=").at(1));
2019-03-05 20:44:10 +01:00
CalibrationPoint cp(parseCalibrationPoint(point));
if (cp.isValid())
calibrationPoints.append(cp);
else {
_errorString = point + ": invalid calibration point";
return false;
}
2019-03-02 16:51:14 +01:00
}
}
GCS gcs(GCS::gcs(datum));
2021-06-17 21:58:25 +02:00
if (gcs.isNull()) {
2019-03-05 20:44:10 +01:00
_errorString = datum + ": unknown/invalid datum";
2019-03-02 16:51:14 +01:00
return false;
}
_projection = parseProjection(projection, gcs);
if (!_projection.isValid()) {
2019-03-05 20:44:10 +01:00
_errorString = projection + ": unknown/invalid projection";
2019-03-02 16:51:14 +01:00
return false;
}
QList<ReferencePoint> rp;
2019-03-05 20:44:10 +01:00
for (int i = 0; i < calibrationPoints.size(); i++)
rp.append(calibrationPoints.at(i).rp(_projection));
2019-03-02 16:51:14 +01:00
_transform = Transform(rp);
if (!_transform.isValid()) {
_errorString = _transform.errorString();
return false;
}
return true;
}
bool RMap::readPalette(QDataStream &stream, quint32 paletteSize)
2019-03-02 16:51:14 +01:00
{
quint32 bgr;
if (paletteSize > 256)
return false;
_palette.resize(256);
for (int i = 0; i < (int)paletteSize; i++) {
stream >> bgr;
_palette[i] = Color::bgr2rgb(bgr);
2019-03-02 16:51:14 +01:00
}
return (stream.status() == QDataStream::Ok);
}
bool RMap::readZoomLevel(quint64 offset, const QSize &imageSize)
{
if (!_file.seek(offset))
return false;
QDataStream stream(&_file);
2019-03-02 16:51:14 +01:00
stream.setByteOrder(QDataStream::LittleEndian);
_zooms.append(Zoom());
Zoom &zoom = _zooms.last();
quint32 width, height;
stream >> width >> height;
zoom.size = QSize(width, -(int)height);
stream >> width >> height;
zoom.dim = QSize(width, height);
zoom.scale = QPointF((qreal)zoom.size.width() / (qreal)imageSize.width(),
(qreal)zoom.size.height() / (qreal)imageSize.height());
if (stream.status() != QDataStream::Ok)
return false;
zoom.tiles.resize(zoom.dim.width() * zoom.dim.height());
for (int j = 0; j < zoom.tiles.size(); j++)
stream >> zoom.tiles[j];
return (stream.status() == QDataStream::Ok);
}
bool RMap::readZooms(QDataStream &stream, const QSize &imageSize)
{
qint32 zoomCount;
stream >> zoomCount;
if (!(stream.status() == QDataStream::Ok && zoomCount))
return false;
QVector<quint64> zoomOffsets(zoomCount);
for (int i = 0; i < zoomCount; i++)
stream >> zoomOffsets[i];
if (stream.status() != QDataStream::Ok)
return false;
for (int i = 0; i < zoomOffsets.size(); i++)
if (!readZoomLevel(zoomOffsets.at(i), imageSize))
return false;
return true;
}
QByteArray RMap::readIMP(quint64 IMPOffset)
{
if (!_file.seek(IMPOffset))
return QByteArray();
QDataStream stream(&_file);
stream.setByteOrder(QDataStream::LittleEndian);
quint32 IMPSize, unknown;
stream >> unknown >> IMPSize;
if (stream.status() != QDataStream::Ok)
return QByteArray();
QByteArray IMP;
IMP.resize(IMPSize);
return (stream.readRawData(IMP.data(), IMP.size()) == IMP.size())
? IMP : QByteArray();
}
bool RMap::readHeader(QDataStream &stream, Header &hdr)
{
quint32 subtype, obfuscated;
2019-03-04 22:29:53 +01:00
char magic[sizeof(MAGIC) - 1];
2019-03-02 16:51:14 +01:00
if (stream.readRawData(magic, sizeof(magic)) != sizeof(magic)
|| memcmp(MAGIC, magic, sizeof(magic))) {
_errorString = "Not a raster RMap file";
return false;
2019-03-02 16:51:14 +01:00
}
stream >> hdr.type;
if (hdr.type > 5)
stream >> subtype;
if (hdr.type > 6)
stream >> obfuscated;
2019-03-07 22:58:43 +01:00
else
obfuscated = 0;
stream >> hdr.width >> hdr.height >> hdr.bpp >> hdr.unknown >> hdr.tileWidth
>> hdr.tileHeight >> hdr.IMPOffset >> hdr.paletteSize;
if (stream.status() != QDataStream::Ok) {
_errorString = "Error reading RMap header";
return false;
}
2019-03-07 22:58:43 +01:00
if (hdr.type < 4 || hdr.type > 10) {
_errorString = QString::number(hdr.type) + ": unsupported map type";
return false;
2019-03-07 22:58:43 +01:00
}
if (obfuscated) {
_errorString = "Obfuscated maps not supported";
return false;
2019-03-07 22:58:43 +01:00
}
return true;
}
2019-03-07 22:58:43 +01:00
RMap::RMap(const QString &fileName, QObject *parent)
: Map(fileName, parent), _file(fileName), _mapRatio(1.0), _zoom(0),
_valid(false)
{
if (!_file.open(QIODevice::ReadOnly)) {
_errorString = _file.errorString();
return;
2019-03-07 22:58:43 +01:00
}
QDataStream stream(&_file);
stream.setByteOrder(QDataStream::LittleEndian);
2019-03-02 16:51:14 +01:00
Header hdr;
if (!readHeader(stream, hdr))
return;
_tileSize = QSize(hdr.tileWidth, hdr.tileHeight);
if (hdr.paletteSize && !readPalette(stream, hdr.paletteSize)) {
_errorString = "Error reading color palette";
return;
2019-03-02 16:51:14 +01:00
}
if (!readZooms(stream, QSize(hdr.width, -(int)hdr.height))) {
_errorString = "Error reading zoom levels";
return;
}
2019-03-02 16:51:14 +01:00
QByteArray IMP(readIMP(hdr.IMPOffset));
if (IMP.isNull()) {
_errorString = "Error reading IMP data";
return;
}
2019-03-02 16:51:14 +01:00
_valid = parseIMP(IMP);
_file.close();
2019-03-02 16:51:14 +01:00
}
QRectF RMap::bounds()
{
return QRectF(QPointF(0, 0), _zooms.at(_zoom).size / _mapRatio);
2019-03-02 16:51:14 +01:00
}
int RMap::zoomFit(const QSize &size, const RectC &rect)
{
if (!rect.isValid())
_zoom = 0;
else {
RectD prect(rect, _projection);
QRectF sbr(_transform.proj2img(prect.topLeft()),
_transform.proj2img(prect.bottomRight()));
for (int i = 0; i < _zooms.size(); i++) {
_zoom = i;
const Zoom &z = _zooms.at(i);
if (sbr.size().width() * z.scale.x() <= size.width()
&& sbr.size().height() * z.scale.y() <= size.height())
break;
}
}
return _zoom;
}
int RMap::zoomIn()
{
_zoom = qMax(_zoom - 1, 0);
return _zoom;
}
int RMap::zoomOut()
{
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
return _zoom;
}
QPointF RMap::ll2xy(const Coordinates &c)
{
const QPointF &scale = _zooms.at(_zoom).scale;
QPointF p(_transform.proj2img(_projection.ll2xy(c)));
return QPointF(p.x() * scale.x(), p.y() * scale.y()) / _mapRatio;
2019-03-02 16:51:14 +01:00
}
Coordinates RMap::xy2ll(const QPointF &p)
{
const QPointF &scale = _zooms.at(_zoom).scale;
return _projection.xy2ll(_transform.img2proj(QPointF(p.x() / scale.x(),
p.y() / scale.y()) * _mapRatio));
2019-03-02 16:51:14 +01:00
}
void RMap::load()
{
_file.open(QIODevice::ReadOnly);
}
void RMap::unload()
{
_file.close();
}
QPixmap RMap::tile(int x, int y)
{
const Zoom &zoom = _zooms.at(_zoom);
qint32 index = y / _tileSize.height() * zoom.dim.width()
+ x / _tileSize.width();
if (index > zoom.tiles.size())
return QPixmap();
quint64 offset = zoom.tiles.at(index);
if (!_file.seek(offset))
return QPixmap();
QDataStream stream(&_file);
stream.setByteOrder(QDataStream::LittleEndian);
2019-03-07 22:58:43 +01:00
quint32 tag;
stream >> tag;
2019-03-02 16:51:14 +01:00
if (stream.status() != QDataStream::Ok)
return QPixmap();
2019-03-07 22:58:43 +01:00
if (tag == 2) {
if (_palette.isEmpty())
return QPixmap();
quint32 width, height, size;
stream >> width >> height >> size;
2019-03-09 14:36:15 +01:00
QSize tileSize(width, -(int)height);
2019-03-07 22:58:43 +01:00
quint32 bes = qToBigEndian(tileSize.width() * tileSize.height());
QByteArray ba;
ba.resize(sizeof(bes) + size);
memcpy(ba.data(), &bes, sizeof(bes));
if (stream.readRawData(ba.data() + sizeof(bes), size) != (int)size)
return QPixmap();
QByteArray uba = qUncompress(ba);
if (uba.size() < tileSize.width() * tileSize.height())
2019-03-07 22:58:43 +01:00
return QPixmap();
QImage img((const uchar*)uba.constData(), tileSize.width(),
tileSize.height(), QImage::Format_Indexed8);
img.setColorTable(_palette);
return QPixmap::fromImage(img);
} else if (tag == 7) {
quint32 len;
stream >> len;
QByteArray ba;
ba.resize(len);
if (stream.readRawData(ba.data(), ba.size()) != ba.size())
return QPixmap();
QImage img(QImage::fromData(ba, "JPG"));
2019-03-07 22:58:43 +01:00
return QPixmap::fromImage(img);
} else
2019-03-02 16:51:14 +01:00
return QPixmap();
}
void RMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
{
Q_UNUSED(flags);
QSizeF ts(_tileSize.width() / _mapRatio, _tileSize.height() / _mapRatio);
2019-03-02 16:51:14 +01:00
QPointF tl(floor(rect.left() / ts.width()) * ts.width(),
floor(rect.top() / ts.height()) * ts.height());
QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
for (int i = 0; i < ceil(s.width() / ts.width()); i++) {
for (int j = 0; j < ceil(s.height() / ts.height()); j++) {
int x = round(tl.x() * _mapRatio + i * _tileSize.width());
int y = round(tl.y() * _mapRatio + j * _tileSize.height());
2019-03-02 16:51:14 +01:00
QPixmap pixmap;
QString key = path() + "/" + QString::number(_zoom) + "_"
2019-03-02 16:51:14 +01:00
+ QString::number(x) + "_" + QString::number(y);
if (!QPixmapCache::find(key, &pixmap)) {
pixmap = tile(x, y);
if (!pixmap.isNull())
QPixmapCache::insert(key, pixmap);
}
if (pixmap.isNull())
qWarning("%s: error loading tile image", qPrintable(key));
else {
pixmap.setDevicePixelRatio(_mapRatio);
2019-03-02 16:51:14 +01:00
QPointF tp(tl.x() + i * ts.width(), tl.y() + j * ts.height());
painter->drawPixmap(tp, pixmap);
}
}
}
}
void RMap::setDevicePixelRatio(qreal deviceRatio, qreal mapRatio)
{
Q_UNUSED(deviceRatio);
_mapRatio = mapRatio;
}
Map *RMap::create(const QString &path, const Projection &, bool *isDir)
{
if (isDir)
*isDir = false;
return new RMap(path);
}