1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-10-07 07:13:21 +02:00
GPXSee/src/map/bsbmap.cpp

465 lines
9.9 KiB
C++
Raw Normal View History

2020-11-27 01:11:50 +01:00
#include <cctype>
#include <QFileInfo>
#include <QPainter>
#include "image.h"
#include "gcs.h"
#include "pcs.h"
#include "calibrationpoint.h"
#include "color.h"
#include "bsbmap.h"
#define LINE_LIMIT 1024
static inline bool isEOH(const QByteArray &line)
{
return (line.size() >= 2 && line.at(line.size() - 2) == 0x1A
&& line.at(line.size() -1) == 0);
}
static inline bool isType(const QByteArray &line, const QByteArray &type)
{
return (line.left(4) == type);
}
static inline QByteArray hdrData(const QByteArray &line)
{
return line.right(line.size() - 4);
}
static bool readHeaderLine(QFile &file, QByteArray &line)
2020-11-27 01:11:50 +01:00
{
char c;
while (file.getChar(&c) && line.size() < LINE_LIMIT) {
2020-11-27 01:11:50 +01:00
if (c == '\0') {
line.append(c);
return true;
}
if (c == '\r')
continue;
if (c == '\n') {
if (!file.getChar(&c))
2020-11-27 01:11:50 +01:00
return false;
if (c == ' ') {
do {
if (!file.getChar(&c))
2020-11-27 01:11:50 +01:00
return false;
} while (c == ' ');
line.append(',');
file.ungetChar(c);
continue;
} else {
file.ungetChar(c);
return true;
}
}
line.append(c);
}
return false;
}
static inline bool isSplitter(const QByteArray &line, int i)
{
return (line.at(i) == ',' && line.size() - i > 3 && isupper(line.at(i+1))
&& (isupper(line.at(i+2)) || isdigit(line.at(i+2))) && line.at(i+3) == '=');
}
static QList<QByteArray> split(const QByteArray &line)
{
QList<QByteArray> list;
int ls = 0;
for (int i = 0; i < line.size(); i++) {
if (isSplitter(line, i)) {
list.append(line.mid(ls, i - ls));
ls = i + 1;
}
}
list.append(line.mid(ls, line.size() - ls));
return list;
}
static QMap<QByteArray, QByteArray> kvMap(const QByteArray &line)
2020-11-27 01:11:50 +01:00
{
QMap<QByteArray, QByteArray> map;
2020-11-27 01:11:50 +01:00
QList<QByteArray> parts(split(line));
for (int i = 0; i < parts.size(); i++) {
QList<QByteArray> ba = parts.at(i).split('=');
if (ba.size() != 2)
continue;
map.insert(ba.at(0), ba.at(1));
}
return map;
}
static double parameter(const QString &str, bool *res)
{
if (str.isEmpty() || str == "NOT_APPLICABLE" || str == "UNKNOWN") {
2020-11-27 01:11:50 +01:00
*res = true;
return NAN;
}
return str.toDouble(res);
}
bool BSBMap::parseBSB(const QByteArray &line)
{
QMap<QByteArray, QByteArray> map(kvMap(line));
2020-11-27 01:11:50 +01:00
_name = QString::fromLatin1(map.value("NA"));
2020-11-27 01:11:50 +01:00
if (_name.isEmpty()) {
_errorString = "Invalid/missing BSB NA field";
return false;
}
QList<QByteArray> sv(map.value("RA").split(','));
2020-11-27 01:11:50 +01:00
unsigned w, h;
bool wok = false, hok = false;
if (sv.size() == 2) {
w = sv.at(0).toUInt(&wok);
h = sv.at(1).toUInt(&hok);
}
if (!wok || !hok || !w || !h) {
_errorString = "Invalid BSB RA field";
return false;
}
_size = QSize(w, h);
return true;
}
bool BSBMap::parseKNP(const QByteArray &line, QString &datum, QString &proj,
double &pp)
{
QMap<QByteArray, QByteArray> map(kvMap(line));
2020-11-27 01:11:50 +01:00
bool ok;
if (!(map.contains("PR") && map.contains("GD") && map.contains("PP"))) {
_errorString = "Missing KNP PR/GD/PP field";
return false;
}
proj = map.value("PR");
datum = map.value("GD");
pp = parameter(map.value("PP"), &ok);
if (!ok) {
_errorString = "Invalid KNP PP field";
return false;
}
2020-12-01 19:03:58 +01:00
_skew = parameter(map.value("SK"), &ok);
if (!ok) {
_errorString = "Invalid KNP SK field";
return false;
}
2020-11-27 01:11:50 +01:00
return true;
}
bool BSBMap::parseKNQ(const QByteArray &line, double params[9])
2020-11-27 01:11:50 +01:00
{
QMap<QByteArray, QByteArray> map(kvMap(line));
2020-11-27 01:11:50 +01:00
bool ok;
for (int i = 1; i <= 8; i++) {
params[i] = parameter(map.value(QString("P%1").arg(i).toLatin1()), &ok);
if (!ok) {
_errorString = QString("Invalid KNQ P%1 parameter").arg(i);
return false;
}
2020-11-27 01:11:50 +01:00
}
return true;
}
2020-12-02 20:51:19 +01:00
bool BSBMap::parseREF(const QByteArray &line, const QString &datum,
const QString &proj, double params[9], QList<ReferencePoint> &points)
2020-11-27 01:11:50 +01:00
{
QList<QByteArray> fields(line.split(','));
if (fields.size() == 5) {
bool xok, yok, lonok, latok;
2020-12-02 20:51:19 +01:00
Coordinates c(fields.at(4).toDouble(&lonok),
fields.at(3).toDouble(&latok));
if (lonok && latok && c.isValid()) {
if (_projection.isNull()) {
if (!createProjection(datum, proj, params, c))
return false;
}
CalibrationPoint p(PointD(fields.at(1).toDouble(&xok),
fields.at(2).toDouble(&yok)), c);
if (xok && yok) {
points.append(p.rp(_projection));
return true;
}
2020-11-27 01:11:50 +01:00
}
}
_errorString = QString(line) + ": Invalid reference point entry";
return false;
}
bool BSBMap::parseRGB(const QByteArray &line)
{
QList<QByteArray> fields(line.split(','));
bool iok, rok, gok, bok;
int i = fields.at(0).toUInt(&iok);
2020-12-02 20:51:19 +01:00
if (iok && fields.size() == 4 && i > 0 && i < 256) {
2020-11-27 01:11:50 +01:00
_palette[i-1] = Color::rgb(fields.at(1).toUInt(&rok),
fields.at(2).toUInt(&gok), fields.at(3).toUInt(&bok));
2020-12-02 20:51:19 +01:00
if (rok && gok && bok)
return true;
2020-11-27 01:11:50 +01:00
}
_errorString = QString(line) + ": Invalid RGB entry";
return false;
}
bool BSBMap::readHeader(QFile &file)
2020-11-27 01:11:50 +01:00
{
QByteArray line;
QString datum, proj;
double params[9];
2020-11-27 01:11:50 +01:00
QList<ReferencePoint> points;
while (readHeaderLine(file, line)) {
2020-11-27 01:11:50 +01:00
if (isEOH(line)) {
if (!_size.isValid() || !_projection.isValid()) {
_errorString = "Invalid KAP file header";
2020-11-27 01:11:50 +01:00
return false;
}
return createTransform(points);
}
if (isType(line, "BSB/") && !parseBSB(hdrData(line)))
2020-11-27 01:11:50 +01:00
return false;
else if (isType(line, "KNP/")
&& !parseKNP(hdrData(line), datum, proj, params[0]))
2020-11-27 01:11:50 +01:00
return false;
else if (isType(line, "KNQ/") && !parseKNQ(hdrData(line), params))
2020-11-27 01:11:50 +01:00
return false;
2020-12-02 20:51:19 +01:00
else if (isType(line, "REF/")
&& !parseREF(hdrData(line), datum, proj, params, points))
return false;
else if (isType(line, "RGB/") && !parseRGB(hdrData(line)))
2020-11-27 01:11:50 +01:00
return false;
line.clear();
}
_errorString = "Not a KAP file";
2020-11-27 01:11:50 +01:00
return false;
}
2020-12-01 19:03:58 +01:00
bool BSBMap::createTransform(QList<ReferencePoint> &points)
2020-11-27 01:11:50 +01:00
{
2020-12-01 19:03:58 +01:00
if (_skew > 0.0 && _skew < 360.0) {
QTransform matrix;
matrix.rotate(-_skew);
QTransform t(QImage::trueMatrix(matrix, _size.width(), _size.height()));
2020-12-01 19:03:58 +01:00
for (int i = 0; i < points.size(); i++)
points[i].setXY(t.map(points.at(i).xy().toPointF()));
QPolygonF a(QRectF(0, 0, _size.width(), _size.height()));
a = t.map(a);
_skewSize = a.boundingRect().toAlignedRect().size();
2020-12-01 19:03:58 +01:00
}
2020-11-27 01:11:50 +01:00
_transform = Transform(points);
if (!_transform.isValid()) {
_errorString = _transform.errorString();
return false;
}
return true;
}
bool BSBMap::createProjection(const QString &datum, const QString &proj,
2020-12-02 20:51:19 +01:00
double params[9], const Coordinates &c)
2020-11-27 01:11:50 +01:00
{
const GCS *gcs = 0;
PCS pcs;
if (datum.isEmpty())
gcs = GCS::gcs(4326);
else
gcs = GCS::gcs(datum);
if (!gcs) {
_errorString = datum + ": Unknown datum";
return false;
}
if (!proj.compare("MERCATOR", Qt::CaseInsensitive)) {
2020-12-02 20:51:19 +01:00
Projection::Setup setup(0, c.lon(), NAN, 0, 0, NAN, NAN);
2020-11-28 14:50:10 +01:00
pcs = PCS(gcs, 9804, setup, 9001);
} else if (!proj.compare("TRANSVERSE MERCATOR", Qt::CaseInsensitive)) {
Projection::Setup setup(0, params[1], params[2], 0, 0, NAN, NAN);
2020-11-27 01:11:50 +01:00
pcs = PCS(gcs, 9807, setup, 9001);
} else if (!proj.compare("UNIVERSAL TRANSVERSE MERCATOR",
2020-11-27 01:11:50 +01:00
Qt::CaseInsensitive)) {
Projection::Setup setup(0, params[0], 0.9996, 500000, 0, NAN, NAN);
2020-11-27 01:11:50 +01:00
pcs = PCS(gcs, 9807, setup, 9001);
} else if (!proj.compare("LAMBERT CONFORMAL CONIC", Qt::CaseInsensitive)) {
Projection::Setup setup(0, params[0], NAN, 0, 0, params[2], params[3]);
2020-11-27 01:11:50 +01:00
pcs = PCS(gcs, 9802, setup, 9001);
} else if (!proj.compare("POLYCONIC", Qt::CaseInsensitive)) {
Projection::Setup setup(0, params[0], NAN, 0, 0, NAN, NAN);
pcs = PCS(gcs, 9818, setup, 9001);
2020-11-27 01:11:50 +01:00
} else {
_errorString = proj + ": Unknown/missing projection";
return false;
}
_projection = Projection(&pcs);
return true;
}
bool BSBMap::readRow(QFile &file, char bits, uchar *buf)
{
char c;
int multiplier;
int pixel = 1, written = 0;
static const char mask[] = {0, 63, 31, 15, 7, 3, 1, 0};
do {
if (!file.getChar(&c))
2020-11-27 01:11:50 +01:00
return false;
} while ((uchar)c >= 0x80);
while (true) {
if (!file.getChar(&c))
2020-11-27 01:11:50 +01:00
return false;
if (c == '\0')
break;
pixel = (c & 0x7f) >> (7 - bits);
multiplier = c & mask[(int)bits];
while ((uchar)c >= 0x80) {
if (!file.getChar(&c))
2020-11-27 01:11:50 +01:00
return false;
multiplier = (multiplier << 7) + (c & 0x7f);
}
multiplier++;
if (written + multiplier > _size.width())
multiplier = _size.width() - written;
memset(buf + written, pixel - 1, multiplier);
written += multiplier;
}
while (written < _size.width())
buf[written++] = pixel - 1;
return true;
}
QImage BSBMap::readImage()
{
QFile file(path());
2020-11-27 01:11:50 +01:00
char bits;
if (!file.open(QIODevice::ReadOnly))
return QImage();
file.seek(_dataOffset);
if (!file.getChar(&bits))
2020-11-27 01:11:50 +01:00
return QImage();
QImage img(_size, QImage::Format_Indexed8);
img.setColorTable(_palette);
for (int row = 0; row < _size.height(); row++) {
uchar *bsb_row = img.scanLine(row);
if (!readRow(file, bits, bsb_row))
return QImage();
}
return img;
}
BSBMap::BSBMap(const QString &fileName, QObject *parent)
: Map(fileName, parent), _img(0), _ratio(1.0), _dataOffset(-1), _valid(false)
2020-11-27 01:11:50 +01:00
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
_errorString = fileName + ": " + file.errorString();
return;
}
_palette.resize(256);
if (!readHeader(file))
2020-11-27 01:11:50 +01:00
return;
_dataOffset = file.pos();
_valid = true;
}
BSBMap::~BSBMap()
{
delete _img;
}
QPointF BSBMap::ll2xy(const Coordinates &c)
{
return QPointF(_transform.proj2img(_projection.ll2xy(c))) / _ratio;
}
Coordinates BSBMap::xy2ll(const QPointF &p)
{
return _projection.xy2ll(_transform.img2proj(p * _ratio));
}
QRectF BSBMap::bounds()
{
2020-12-01 19:03:58 +01:00
return _skewSize.isValid()
? QRectF(QPointF(0, 0), _skewSize / _ratio)
: QRectF(QPointF(0, 0), _size / _ratio);
2020-11-27 01:11:50 +01:00
}
void BSBMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
{
if (_img)
_img->draw(painter, rect, flags);
}
void BSBMap::setDevicePixelRatio(qreal deviceRatio, qreal mapRatio)
{
Q_UNUSED(deviceRatio);
_ratio = mapRatio;
if (_img)
_img->setDevicePixelRatio(_ratio);
}
void BSBMap::load()
{
2020-12-01 19:03:58 +01:00
if (!_img) {
if (_skew > 0.0 && _skew < 360.0) {
QTransform matrix;
matrix.rotate(-_skew);
_img = new Image(readImage().transformed(matrix));
2020-12-01 19:03:58 +01:00
} else
_img = new Image(readImage());
}
2020-11-27 01:11:50 +01:00
}
void BSBMap::unload()
{
delete _img;
_img = 0;
}