1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-11-27 21:24:47 +01:00

Added support for BSB charts maps

This commit is contained in:
Martin Tůma 2020-11-27 01:11:50 +01:00
parent 609e73256a
commit 96733883cb
11 changed files with 560 additions and 24 deletions

View File

@ -103,6 +103,7 @@ HEADERS += src/common/config.h \
src/map/IMG/rastertile.h \
src/map/IMG/textpathitem.h \
src/map/IMG/textpointitem.h \
src/map/bsbmap.h \
src/map/projection.h \
src/map/ellipsoid.h \
src/map/datum.h \
@ -271,6 +272,7 @@ SOURCES += src/main.cpp \
src/map/IMG/rastertile.cpp \
src/map/IMG/textpathitem.cpp \
src/map/IMG/textpointitem.cpp \
src/map/bsbmap.cpp \
src/map/maplist.cpp \
src/map/onlinemap.cpp \
src/map/downloader.cpp \

450
src/map/bsbmap.cpp Normal file
View File

@ -0,0 +1,450 @@
#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 getChar(QFile &file, bool mangled, char *c)
{
if (!file.getChar(c))
return false;
if (mangled)
*c = (char)((int)(*c - 9) & 0xFF);
return true;
}
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, bool mangled, QByteArray &line)
{
char c;
while (getChar(file, mangled, &c) && line.size() < LINE_LIMIT) {
if (c == '\0') {
line.append(c);
return true;
}
if (c == '\r')
continue;
if (c == '\n') {
if (!getChar(file, mangled, &c))
return false;
if (c == ' ') {
do {
if (!getChar(file, mangled, &c))
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<QString, QString> kvMap(const QByteArray &line)
{
QMap<QString, QString> map;
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") {
*res = true;
return NAN;
}
return str.toDouble(res);
}
bool BSBMap::parseBSB(const QByteArray &line)
{
QMap<QString, QString> map(kvMap(line));
_name = map.value("NA");
if (_name.isEmpty()) {
_errorString = "Invalid/missing BSB NA field";
return false;
}
QStringList sv(map.value("RA").split(','));
unsigned w, h;
bool wok = false, hok = false;
if (sv.size() == 2) {
w = sv.at(0).toUInt(&wok);
h = sv.at(1).toUInt(&hok);
} else if (sv.size() == 4) {
w = sv.at(2).toUInt(&wok);
h = sv.at(3).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<QString, QString> map(kvMap(line));
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;
}
return true;
}
bool BSBMap::parseKNQ(const QByteArray &line, double &p2, double &p3)
{
QMap<QString, QString> map(kvMap(line));
bool ok;
p2 = parameter(map.value("P2"), &ok);
if (!ok) {
_errorString = "Invalid KNQ P2 parameter";
return false;
}
p3 = parameter(map.value("P3"), &ok);
if (!ok) {
_errorString = "Invalid KNQ P3 parameter";
return false;
}
return true;
}
bool BSBMap::parseREF(const QByteArray &line, QList<ReferencePoint> &points)
{
QList<QByteArray> fields(line.split(','));
if (fields.size() == 5) {
bool xok, yok, lonok, latok;
CalibrationPoint p(PointD(fields.at(1).toDouble(&xok),
fields.at(2).toDouble(&yok)), Coordinates(fields.at(4).toDouble(&lonok),
fields.at(3).toDouble(&latok)));
if (xok && yok && lonok && latok) {
points.append(p.rp(_projection));
return true;
}
}
_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);
if (fields.size() == 4 && i > 0 && i < 256) {
_palette[i-1] = Color::rgb(fields.at(1).toUInt(&rok),
fields.at(2).toUInt(&gok), fields.at(3).toUInt(&bok));
return true;
}
_errorString = QString(line) + ": Invalid RGB entry";
return false;
}
bool BSBMap::readHeader(QFile &file, bool mangled)
{
QByteArray line;
QString datum, proj;
double pp, p2, p3;
QList<ReferencePoint> points;
while (readHeaderLine(file, mangled, line)) {
if (isEOH(line)) {
if (!_size.isValid() || !_projection.isValid()) {
_errorString = "Invalid KAP/NOS file header";
return false;
}
return createTransform(points);
}
if ((isType(line, "BSB/") || isType(line, "NOS/"))
&& !parseBSB(hdrData(line)))
return false;
else if (isType(line, "KNP/") && !parseKNP(hdrData(line), datum, proj,
pp))
return false;
else if (isType(line, "KNQ/") && !parseKNQ(hdrData(line), p2, p3))
return false;
else if (isType(line, "REF/")) {
if (_projection.isNull()) {
if (!createProjection(datum, proj, pp, p2, p3))
return false;
}
if (!parseREF(hdrData(line), points))
return false;
} else if (isType(line, "RGB/") && !parseRGB(hdrData(line)))
return false;
line.clear();
}
_errorString = "Not a KAP/NOS file";
return false;
}
bool BSBMap::createTransform(const QList<ReferencePoint> &points)
{
_transform = Transform(points);
if (!_transform.isValid()) {
_errorString = _transform.errorString();
return false;
}
return true;
}
bool BSBMap::createProjection(const QString &datum, const QString &proj,
double pp, double p2, double p3)
{
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)) {
Projection::Setup setup(0, 0, 1, 0, 0, 0, 0);
pcs = PCS(gcs, 9804, setup, 9001);
} else if (proj.compare("TRANSVERSE MERCATOR", Qt::CaseInsensitive)) {
Projection::Setup setup(0, pp, 1, 0, 0, 0, 0);
pcs = PCS(gcs, 9807, setup, 9001);
} else if (proj.compare("UNIVERSAL TRANSVERSE MERCATOR",
Qt::CaseInsensitive)) {
Projection::Setup setup(0, pp, 0.9996, 500000, 0, 0, 0);
pcs = PCS(gcs, 9807, setup, 9001);
} else if (proj.compare("LAMBERT CONFORMAL CONIC", Qt::CaseInsensitive)) {
Projection::Setup setup(0, pp, 1, 0, 0, p2, p3);
pcs = PCS(gcs, 9802, setup, 9001);
} 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 (!getChar(file, _mangled, &c))
return false;
} while ((uchar)c >= 0x80);
while (true) {
if (!getChar(file, _mangled, &c))
return false;
if (c == '\0')
break;
pixel = (c & 0x7f) >> (7 - bits);
multiplier = c & mask[(int)bits];
while ((uchar)c >= 0x80) {
if (!getChar(file, _mangled, &c))
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(_fileName);
char bits;
if (!file.open(QIODevice::ReadOnly))
return QImage();
file.seek(_dataOffset);
if (!getChar(file, _mangled, &bits))
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(parent), _fileName(fileName), _img(0), _ratio(1.0), _dataOffset(-1),
_valid(false)
{
QFileInfo fi(fileName);
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
_errorString = fileName + ": " + file.errorString();
return;
}
_palette.resize(256);
_mangled = !fi.suffix().compare("no1", Qt::CaseInsensitive);
if (!readHeader(file, _mangled))
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()
{
return QRectF(QPointF(0, 0), _size / _ratio);
}
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()
{
if (!_img)
_img = new Image(readImage());
}
void BSBMap::unload()
{
delete _img;
_img = 0;
}

64
src/map/bsbmap.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef BSBMAP_H
#define BSBMAP_H
#include <QColor>
#include "transform.h"
#include "projection.h"
#include "map.h"
class QFile;
class Image;
class BSBMap : public Map
{
Q_OBJECT
public:
BSBMap(const QString &fileName, QObject *parent = 0);
~BSBMap();
QString name() const {return _name;}
QRectF bounds();
QPointF ll2xy(const Coordinates &c);
Coordinates xy2ll(const QPointF &p);
void draw(QPainter *painter, const QRectF &rect, Flags flags);
void load();
void unload();
void setDevicePixelRatio(qreal deviceRatio, qreal mapRatio);
bool isValid() const {return _valid;}
QString errorString() const {return _errorString;}
private:
bool parseBSB(const QByteArray &line);
bool parseKNP(const QByteArray &line, QString &datum, QString &proj,
double &pp);
bool parseKNQ(const QByteArray &line, double &p2, double &p3);
bool parseREF(const QByteArray &line, QList<ReferencePoint> &points);
bool parseRGB(const QByteArray &line);
bool readHeader(QFile &file, bool mangled);
bool createProjection(const QString &datum, const QString &proj,
double pp, double p2, double p3);
bool createTransform(const QList<ReferencePoint> &points);
QImage readImage();
bool readRow(QFile &file, char bits, uchar *buf);
QString _fileName;
QString _name;
Projection _projection;
Transform _transform;
Image *_img;
QSize _size;
qreal _ratio;
qint64 _dataOffset;
bool _mangled;
QVector<QRgb> _palette;
bool _valid;
QString _errorString;
};
#endif // BSBMAP_H

View File

@ -1,6 +1,7 @@
#ifndef CALIBRATIONPOINT_H
#define CALIBRATIONPOINT_H
#include <QDebug>
#include "transform.h"
#include "projection.h"
@ -22,10 +23,21 @@ public:
: ReferencePoint(_xy, _pp);
}
friend QDebug operator<<(QDebug dbg, const CalibrationPoint &p);
private:
PointD _xy;
PointD _pp;
Coordinates _ll;
};
#ifndef QT_NO_DEBUG
inline QDebug operator<<(QDebug dbg, const CalibrationPoint &p)
{
dbg.nospace() << "CalibrationPoint(" << p._xy << ", " << p._pp << ", "
<< p._ll << ")";
return dbg.space();
}
#endif // QT_NO_DEBUG
#endif // CALIBRATIONPOINT_H

View File

@ -13,6 +13,11 @@ namespace Color
return (0xFF000000 | r << 16 | g << 8 | b);
}
inline QRgb rgb(quint32 r, quint32 g, quint32 b)
{
return (0xFF000000 | r << 16 | g << 8 | b);
}
}
#endif // COLOR_H

View File

@ -54,6 +54,7 @@ QList<GCS::Entry> GCS::defaults()
{
QList<GCS::Entry> list;
list.append(GCS::Entry(4326, 6326, "WGS 84", WGS84()));
list.append(GCS::Entry(4326, 6326, "WGS84", WGS84()));
return list;
}

View File

@ -1,7 +1,6 @@
#include <QFileInfo>
#include <QPainter>
#include <QImageReader>
#include "common/config.h"
#include "geotiff.h"
#include "image.h"
#include "geotiffmap.h"

View File

@ -1,15 +1,10 @@
#include <QPainter>
#include <QPixmapCache>
#include "common/config.h"
#include "image.h"
#define TILE_SIZE 256
Image::Image(const QString &fileName) : _img(fileName), _fileName(fileName)
{
}
void Image::draw(QPainter *painter, const QRectF &rect, Map::Flags flags)
{
#ifdef ENABLE_HIDPI
@ -19,26 +14,30 @@ void Image::draw(QPainter *painter, const QRectF &rect, Map::Flags flags)
#endif // ENABLE_HIDPI
QRectF sr(rect.topLeft() * ratio, rect.size() * ratio);
/* When OpenGL is used, big images are rendered incredibly slow or not at
all using the QPainter::drawImage() function with a source rect set. So
we have to tile the image ourself before it can be drawn.
We have to use a list of dynamically allocated pixmaps as QPainter
rendering is broken in yet another way with OpenGL and drawPixmap() does
access already deleted image instances when reusing a single pixmap. */
if (flags & Map::OpenGL) {
QList<QPixmap *> list;
for (int i = sr.left()/TILE_SIZE; i <= sr.right()/TILE_SIZE; i++) {
for (int j = sr.top()/TILE_SIZE; j <= sr.bottom()/TILE_SIZE; j++) {
QString key = _fileName + "-" + QString::number(i) + "_"
+ QString::number(j);
QPoint tl(i * TILE_SIZE, j * TILE_SIZE);
QPixmap pm;
if (!QPixmapCache::find(key, &pm)) {
QRect tile(tl, QSize(TILE_SIZE, TILE_SIZE));
pm = QPixmap::fromImage(_img.copy(tile));
if (!pm.isNull())
QPixmapCache::insert(key, pm);
}
QRect tile(tl, QSize(TILE_SIZE, TILE_SIZE));
QPixmap *pm = new QPixmap(QPixmap::fromImage(_img.copy(tile)));
list.append(pm);
#ifdef ENABLE_HIDPI
pm.setDevicePixelRatio(ratio);
pm->setDevicePixelRatio(ratio);
#endif // ENABLE_HIDPI
painter->drawPixmap(tl/ratio, pm);
painter->drawPixmap(tl/ratio, *pm);
}
}
qDeleteAll(list);
} else
painter->drawImage(rect.topLeft(), _img, sr);
}

View File

@ -9,14 +9,14 @@ class QPainter;
class Image
{
public:
Image(const QString &fileName);
Image(const QString &fileName) : _img(fileName) {}
Image(const QImage &img) : _img(img) {}
void draw(QPainter *painter, const QRectF &rect, Map::Flags flags);
void setDevicePixelRatio(qreal ratio);
private:
QImage _img;
QString _fileName;
};
#endif // IMAGE_H

View File

@ -144,7 +144,7 @@ JNXMap::JNXMap(const QString &fileName, QObject *parent)
_name = QFileInfo(fileName).fileName();
if (!_file.open(QIODevice::ReadOnly)) {
_errorString = QString("%1: Error opening file").arg(fileName);
_errorString = fileName + ": " + _file.errorString();
return;
}

View File

@ -10,6 +10,7 @@
#include "rmap.h"
#include "imgmap.h"
#include "IMG/gmap.h"
#include "bsbmap.h"
#include "maplist.h"
@ -45,6 +46,8 @@ Map *MapList::loadFile(const QString &path, QString &errorString,
map = new IMGMap(path);
else if (suffix == "map" || suffix == "tar")
map = new OziMap(path);
else if (suffix == "kap" || suffix == "no1" || suffix == "nos")
map = new BSBMap(path);
if (map && map->isValid())
return map;
@ -106,6 +109,7 @@ QString MapList::formats()
+ qApp->translate("MapList", "Garmin IMG maps")
+ " (*.gmap *.gmapi *.img *.xml);;"
+ qApp->translate("MapList", "Garmin JNX maps") + " (*.jnx);;"
+ qApp->translate("MapList", "BSB nautical charts") + " (*.kap *.no1 *.nos);;"
+ qApp->translate("MapList", "OziExplorer maps") + " (*.map);;"
+ qApp->translate("MapList", "MBTiles maps") + " (*.mbtiles);;"
+ qApp->translate("MapList", "TrekBuddy maps/atlases") + " (*.tar *.tba);;"
@ -117,8 +121,8 @@ QString MapList::formats()
QStringList MapList::filter()
{
QStringList filter;
filter << "*.gmap" << "*.gmapi" << "*.img" << "*.jnx" << "*.map"
<< "*.mbtiles" << "*.rmap" << "*.rtmap" << "*.tar" << "*.tba" << "*.tif"
<< "*.tiff" << "*.xml";
filter << "*.gmap" << "*.gmapi" << "*.img" << "*.jnx" << "*.kap" << "*.map"
<< "*.mbtiles" << "*.no1" << "*.nos" << "*.rmap" << "*.rtmap" << "*.tar"
<< "*.tba" << "*.tif" << "*.tiff" << "*.xml";
return filter;
}