2018-05-22 22:40:15 +02:00
|
|
|
#include <QtEndian>
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QFileInfo>
|
2018-05-30 22:10:35 +02:00
|
|
|
#include <QPixmapCache>
|
2021-01-17 19:33:06 +01:00
|
|
|
#include "common/util.h"
|
2018-05-22 22:40:15 +02:00
|
|
|
#include "rectd.h"
|
2018-07-13 10:10:44 +02:00
|
|
|
#include "gcs.h"
|
|
|
|
#include "pcs.h"
|
2018-05-22 22:40:15 +02:00
|
|
|
#include "jnxmap.h"
|
|
|
|
|
|
|
|
|
2018-05-24 22:11:00 +02:00
|
|
|
#define ic2dc(x) ((x) * 180.0 / 0x7FFFFFFF)
|
|
|
|
|
2018-05-22 22:40:15 +02:00
|
|
|
struct Level {
|
|
|
|
quint32 count;
|
|
|
|
quint32 offset;
|
|
|
|
quint32 scale;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Ctx {
|
|
|
|
QPainter *painter;
|
|
|
|
QFile *file;
|
2018-08-18 21:06:36 +02:00
|
|
|
qreal ratio;
|
2018-05-22 22:40:15 +02:00
|
|
|
|
2018-08-18 21:06:36 +02:00
|
|
|
Ctx(QPainter *painter, QFile *file, qreal ratio)
|
|
|
|
: painter(painter), file(file), ratio(ratio) {}
|
2018-05-22 22:40:15 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<class T> bool JNXMap::readValue(T &val)
|
|
|
|
{
|
|
|
|
T data;
|
|
|
|
|
|
|
|
if (_file.read((char*)&data, sizeof(T)) < (qint64)sizeof(T))
|
|
|
|
return false;
|
|
|
|
|
2019-08-13 20:50:43 +02:00
|
|
|
val = qFromLittleEndian(data);
|
2018-05-22 22:40:15 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool JNXMap::readString(QByteArray& ba)
|
|
|
|
{
|
|
|
|
char byte;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
if (!_file.getChar(&byte))
|
|
|
|
return false;
|
|
|
|
else if (!byte)
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
ba += byte;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool JNXMap::readTiles()
|
|
|
|
{
|
|
|
|
qint32 lat1, lon2, lat2, lon1;
|
|
|
|
quint32 version, dummy, levels;
|
|
|
|
|
|
|
|
if (!(readValue(version) && readValue(dummy) && readValue(lat1)
|
|
|
|
&& readValue(lon2) && readValue(lat2) && readValue(lon1)
|
|
|
|
&& readValue(levels)))
|
|
|
|
return false;
|
|
|
|
|
2018-05-24 22:11:00 +02:00
|
|
|
_bounds = RectC(Coordinates(ic2dc(lon1), ic2dc(lat1)),
|
|
|
|
Coordinates(ic2dc(lon2), ic2dc(lat2)));
|
|
|
|
if (!levels || !_bounds.isValid())
|
|
|
|
return false;
|
2018-05-22 22:40:15 +02:00
|
|
|
|
|
|
|
if (!_file.seek(version > 3 ? 0x34 : 0x30))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
QVector<Level> lh(levels);
|
|
|
|
for (int i = 0; i < lh.count(); i++) {
|
2018-05-24 22:11:00 +02:00
|
|
|
Level &l = lh[i];
|
|
|
|
|
|
|
|
if (!(readValue(l.count) && readValue(l.offset) && readValue(l.scale)))
|
2018-05-22 22:40:15 +02:00
|
|
|
return false;
|
|
|
|
if (version > 3) {
|
|
|
|
QByteArray ba;
|
|
|
|
if (!(readValue(dummy) && readString(ba)))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_zooms = QVector<Zoom>(lh.size());
|
|
|
|
for (int i = 0; i < lh.count(); i++) {
|
2018-05-24 22:11:00 +02:00
|
|
|
Zoom &z = _zooms[i];
|
|
|
|
const Level &l = lh.at(i);
|
|
|
|
|
|
|
|
if (!_file.seek(l.offset))
|
2018-05-22 22:40:15 +02:00
|
|
|
return false;
|
|
|
|
|
2018-05-24 22:11:00 +02:00
|
|
|
z.tiles = QVector<Tile>(l.count);
|
|
|
|
for (quint32 j = 0; j < l.count; j++) {
|
|
|
|
Tile &tile = z.tiles[j];
|
2018-05-22 22:40:15 +02:00
|
|
|
|
2020-12-24 16:33:17 +01:00
|
|
|
if (!(readValue(tile.top) && readValue(tile.right)
|
|
|
|
&& readValue(tile.bottom) && readValue(tile.left)
|
|
|
|
&& readValue(tile.width) && readValue(tile.height)
|
|
|
|
&& readValue(tile.size) && readValue(tile.offset)))
|
2018-05-22 22:40:15 +02:00
|
|
|
return false;
|
|
|
|
|
2020-12-24 16:33:17 +01:00
|
|
|
RectC llrect(Coordinates(ic2dc(tile.left), ic2dc(tile.top)),
|
|
|
|
Coordinates(ic2dc(tile.right), ic2dc(tile.bottom)));
|
|
|
|
RectD rect(_projection.ll2xy(llrect.topLeft()),
|
|
|
|
_projection.ll2xy(llrect.bottomRight()));
|
2018-05-22 22:40:15 +02:00
|
|
|
|
|
|
|
if (j == 0) {
|
|
|
|
ReferencePoint tl(PointD(0, 0), rect.topLeft());
|
2020-12-24 16:33:17 +01:00
|
|
|
ReferencePoint br(PointD(tile.width, tile.height),
|
|
|
|
rect.bottomRight());
|
2018-05-24 22:11:00 +02:00
|
|
|
z.transform = Transform(tl, br);
|
2018-05-22 22:40:15 +02:00
|
|
|
}
|
|
|
|
|
2018-05-24 22:11:00 +02:00
|
|
|
QRectF trect(z.transform.proj2img(rect.topLeft()),
|
|
|
|
z.transform.proj2img(rect.bottomRight()));
|
2018-05-22 22:40:15 +02:00
|
|
|
tile.pos = trect.topLeft();
|
|
|
|
|
|
|
|
qreal min[2], max[2];
|
|
|
|
min[0] = trect.left();
|
|
|
|
min[1] = trect.top();
|
|
|
|
max[0] = trect.right();
|
|
|
|
max[1] = trect.bottom();
|
2018-05-24 22:11:00 +02:00
|
|
|
z.tree.Insert(min, max, &tile);
|
2018-05-22 22:40:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
JNXMap::JNXMap(const QString &fileName, QObject *parent)
|
2020-12-02 23:58:11 +01:00
|
|
|
: Map(fileName, parent), _file(fileName), _zoom(0), _mapRatio(1.0),
|
|
|
|
_valid(false)
|
2018-05-22 22:40:15 +02:00
|
|
|
{
|
2020-12-24 16:33:17 +01:00
|
|
|
_projection = Projection(GCS::gcs(4326));
|
2018-05-22 22:40:15 +02:00
|
|
|
|
|
|
|
if (!_file.open(QIODevice::ReadOnly)) {
|
2020-11-27 01:11:50 +01:00
|
|
|
_errorString = fileName + ": " + _file.errorString();
|
2018-05-22 22:40:15 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!readTiles()) {
|
|
|
|
_errorString = "JNX file format error";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-22 22:09:09 +01:00
|
|
|
_file.close();
|
|
|
|
|
2018-05-22 22:40:15 +02:00
|
|
|
_valid = true;
|
|
|
|
}
|
|
|
|
|
2020-12-22 22:09:09 +01:00
|
|
|
void JNXMap::load()
|
|
|
|
{
|
|
|
|
_file.open(QIODevice::ReadOnly);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JNXMap::unload()
|
|
|
|
{
|
|
|
|
_file.close();
|
|
|
|
}
|
|
|
|
|
2018-06-30 12:14:58 +02:00
|
|
|
QPointF JNXMap::ll2xy(const Coordinates &c)
|
2018-05-22 22:40:15 +02:00
|
|
|
{
|
2018-07-13 10:10:44 +02:00
|
|
|
const Zoom &z = _zooms.at(_zoom);
|
2018-11-17 10:10:35 +01:00
|
|
|
return z.transform.proj2img(_projection.ll2xy(c)) / _mapRatio;
|
2018-05-22 22:40:15 +02:00
|
|
|
}
|
|
|
|
|
2018-06-30 12:14:58 +02:00
|
|
|
Coordinates JNXMap::xy2ll(const QPointF &p)
|
2018-05-22 22:40:15 +02:00
|
|
|
{
|
2018-07-13 10:10:44 +02:00
|
|
|
const Zoom &z = _zooms.at(_zoom);
|
2018-11-17 10:10:35 +01:00
|
|
|
return _projection.xy2ll(z.transform.img2proj(p * _mapRatio));
|
2018-05-22 22:40:15 +02:00
|
|
|
}
|
|
|
|
|
2018-07-13 09:51:41 +02:00
|
|
|
QRectF JNXMap::bounds()
|
2018-05-22 22:40:15 +02:00
|
|
|
{
|
2018-07-13 10:10:44 +02:00
|
|
|
return QRectF(ll2xy(_bounds.topLeft()), ll2xy(_bounds.bottomRight()));
|
2018-05-22 22:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int JNXMap::zoomFit(const QSize &size, const RectC &rect)
|
|
|
|
{
|
|
|
|
if (!rect.isValid())
|
|
|
|
_zoom = _zooms.size() - 1;
|
|
|
|
else {
|
|
|
|
for (int i = 1; i < _zooms.count(); i++) {
|
|
|
|
_zoom = i;
|
|
|
|
QRect sbr(QPoint(ll2xy(rect.topLeft()).toPoint()),
|
|
|
|
QPoint(ll2xy(rect.bottomRight()).toPoint()));
|
|
|
|
if (sbr.size().width() >= size.width() || sbr.size().height()
|
|
|
|
>= size.height()) {
|
|
|
|
_zoom--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
int JNXMap::zoomIn()
|
|
|
|
{
|
|
|
|
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
int JNXMap::zoomOut()
|
|
|
|
{
|
|
|
|
_zoom = qMax(_zoom - 1, 0);
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
QPixmap JNXMap::pixmap(const Tile *tile, QFile *file)
|
|
|
|
{
|
2018-05-30 22:10:35 +02:00
|
|
|
QPixmap pm;
|
|
|
|
|
|
|
|
QString key = file->fileName() + "-" + QString::number(tile->offset);
|
|
|
|
if (!QPixmapCache::find(key, &pm)) {
|
|
|
|
QByteArray ba;
|
|
|
|
ba.resize(tile->size + 2);
|
|
|
|
ba[0] = (char)0xFF;
|
|
|
|
ba[1] = (char)0xD8;
|
|
|
|
char *data = ba.data() + 2;
|
|
|
|
|
|
|
|
if (!file->seek(tile->offset))
|
|
|
|
return QPixmap();
|
|
|
|
if (!file->read(data, tile->size))
|
|
|
|
return QPixmap();
|
|
|
|
pm = QPixmap::fromImage(QImage::fromData(ba));
|
|
|
|
|
|
|
|
if (!pm.isNull())
|
|
|
|
QPixmapCache::insert(key, pm);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pm;
|
2018-05-22 22:40:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool JNXMap::cb(Tile *tile, void *context)
|
|
|
|
{
|
|
|
|
Ctx *ctx = static_cast<Ctx*>(context);
|
2018-08-18 21:06:36 +02:00
|
|
|
QPixmap pm(pixmap(tile, ctx->file));
|
|
|
|
pm.setDevicePixelRatio(ctx->ratio);
|
|
|
|
ctx->painter->drawPixmap(tile->pos / ctx->ratio, pm);
|
2018-05-22 22:40:15 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-23 20:26:10 +02:00
|
|
|
void JNXMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
|
2018-05-22 22:40:15 +02:00
|
|
|
{
|
2018-08-23 20:26:10 +02:00
|
|
|
Q_UNUSED(flags);
|
2018-05-22 22:40:15 +02:00
|
|
|
const RTree<Tile*, qreal, 2> &tree = _zooms.at(_zoom).tree;
|
2018-11-17 10:10:35 +01:00
|
|
|
Ctx ctx(painter, &_file, _mapRatio);
|
|
|
|
QRectF rr(rect.topLeft() * _mapRatio, rect.size() * _mapRatio);
|
2018-05-22 22:40:15 +02:00
|
|
|
|
|
|
|
qreal min[2], max[2];
|
2018-08-18 21:06:36 +02:00
|
|
|
min[0] = rr.left();
|
|
|
|
min[1] = rr.top();
|
|
|
|
max[0] = rr.right();
|
|
|
|
max[1] = rr.bottom();
|
2018-05-22 22:40:15 +02:00
|
|
|
tree.Search(min, max, cb, &ctx);
|
|
|
|
}
|
2020-12-24 16:33:17 +01:00
|
|
|
|
|
|
|
void JNXMap::setInputProjection(const Projection &projection)
|
|
|
|
{
|
|
|
|
if (projection == _projection)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_projection = projection;
|
|
|
|
|
|
|
|
for (int i = 0; i < _zooms.size(); i++) {
|
|
|
|
Zoom &z = _zooms[i];
|
|
|
|
|
|
|
|
z.tree.RemoveAll();
|
|
|
|
|
|
|
|
for (int j = 0; j < z.tiles.size(); j++) {
|
|
|
|
Tile &tile = z.tiles[j];
|
|
|
|
|
|
|
|
RectC llrect(Coordinates(ic2dc(tile.left), ic2dc(tile.top)),
|
|
|
|
Coordinates(ic2dc(tile.right), ic2dc(tile.bottom)));
|
|
|
|
RectD rect(_projection.ll2xy(llrect.topLeft()),
|
|
|
|
_projection.ll2xy(llrect.bottomRight()));
|
|
|
|
|
|
|
|
if (j == 0) {
|
|
|
|
ReferencePoint tl(PointD(0, 0), rect.topLeft());
|
|
|
|
ReferencePoint br(PointD(tile.width, tile.height),
|
|
|
|
rect.bottomRight());
|
|
|
|
z.transform = Transform(tl, br);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRectF trect(z.transform.proj2img(rect.topLeft()),
|
|
|
|
z.transform.proj2img(rect.bottomRight()));
|
|
|
|
tile.pos = trect.topLeft();
|
|
|
|
|
|
|
|
qreal min[2], max[2];
|
|
|
|
min[0] = trect.left();
|
|
|
|
min[1] = trect.top();
|
|
|
|
max[0] = trect.right();
|
|
|
|
max[1] = trect.bottom();
|
|
|
|
z.tree.Insert(min, max, &tile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|