mirror of
https://github.com/tumic0/GPXSee.git
synced 2025-02-12 06:10:48 +01:00
372 lines
7.6 KiB
C++
372 lines
7.6 KiB
C++
#include <QPainter>
|
|
#include <QPixmapCache>
|
|
#include "common/range.h"
|
|
#include "common/wgs84.h"
|
|
#include "ENC/mapdata.h"
|
|
#include "ENC/style.h"
|
|
#include "rectd.h"
|
|
#include "pcs.h"
|
|
#include "encjob.h"
|
|
#include "encmap.h"
|
|
|
|
|
|
using namespace ENC;
|
|
|
|
#define EPSILON 1e-6
|
|
#define TILE_SIZE 512
|
|
|
|
static Range zooms(const RectC &bounds)
|
|
{
|
|
double size = qMin(bounds.width(), bounds.height());
|
|
|
|
if (size > 180)
|
|
return Range(0, 10);
|
|
else if (size > 90)
|
|
return Range(1, 11);
|
|
else if (size > 45)
|
|
return Range(2, 12);
|
|
else if (size > 22.5)
|
|
return Range(3, 13);
|
|
else if (size > 11.25)
|
|
return Range(4, 14);
|
|
else if (size > 5.625)
|
|
return Range(5, 15);
|
|
else if (size > 2.813)
|
|
return Range(6, 16);
|
|
else if (size > 1.406)
|
|
return Range(7, 17);
|
|
else if (size > 0.703)
|
|
return Range(8, 18);
|
|
else if (size > 0.352)
|
|
return Range(9, 19);
|
|
else if (size > 0.176)
|
|
return Range(10, 20);
|
|
else if (size > 0.088)
|
|
return Range(11, 20);
|
|
else if (size > 0.044)
|
|
return Range(12, 20);
|
|
else if (size > 0.022)
|
|
return Range(13, 20);
|
|
else if (size > 0.011)
|
|
return Range(14, 20);
|
|
else
|
|
return Range(15, 20);
|
|
}
|
|
|
|
static const ISO8211::Field *SGXD(const ISO8211::Record &r)
|
|
{
|
|
const ISO8211::Field *f;
|
|
|
|
if ((f = ISO8211::field(r, "SG2D")))
|
|
return f;
|
|
else if ((f = ISO8211::field(r, "SG3D")))
|
|
return f;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool ENCMap::bounds(const ISO8211::Record &record, Rect &rect)
|
|
{
|
|
bool xok, yok;
|
|
// edge geometries can be empty!
|
|
const ISO8211::Field *f = SGXD(record);
|
|
if (!f)
|
|
return true;
|
|
|
|
for (int i = 0; i < f->data().size(); i++) {
|
|
const QVector<QVariant> &c = f->data().at(i);
|
|
rect.unite(c.at(1).toInt(&xok), c.at(0).toInt(&yok));
|
|
if (!(xok && yok))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ENCMap::bounds(const QVector<ISO8211::Record> &gv, Rect &b)
|
|
{
|
|
Rect r;
|
|
|
|
for (int i = 0; i < gv.size(); i++) {
|
|
if (!bounds(gv.at(i), r))
|
|
return false;
|
|
b |= r;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ENCMap::processRecord(const ISO8211::Record &record,
|
|
QVector<ISO8211::Record> &rv, uint &COMF, QString &name)
|
|
{
|
|
if (record.size() < 2)
|
|
return false;
|
|
|
|
const ISO8211::Field &f = record.at(1);
|
|
const QByteArray &ba = f.tag();
|
|
|
|
if (ba == "VRID") {
|
|
rv.append(record);
|
|
} else if (ba == "DSID") {
|
|
QByteArray DSNM;
|
|
if (!f.subfield("DSNM", &DSNM))
|
|
return false;
|
|
name = DSNM;
|
|
} else if (ba == "DSPM") {
|
|
if (!f.subfield("COMF", &COMF))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ENCMap::ENCMap(const QString &fileName, QObject *parent)
|
|
: Map(fileName, parent), _data(0), _style(0), _projection(PCS::pcs(3857)),
|
|
_tileRatio(1.0), _valid(false)
|
|
{
|
|
QVector<ISO8211::Record> gv;
|
|
ISO8211 ddf(fileName);
|
|
ISO8211::Record record;
|
|
uint COMF = 1;
|
|
|
|
if (!ddf.readDDR()) {
|
|
_errorString = ddf.errorString();
|
|
return;
|
|
}
|
|
while (ddf.readRecord(record)) {
|
|
if (!processRecord(record, gv, COMF, _name)) {
|
|
_errorString = "Invalid S-57 record";
|
|
return;
|
|
}
|
|
}
|
|
if (!ddf.errorString().isNull()) {
|
|
_errorString = ddf.errorString();
|
|
return;
|
|
}
|
|
|
|
Rect b;
|
|
if (!bounds(gv, b)) {
|
|
_errorString = "Error fetching geometries bounds";
|
|
return;
|
|
}
|
|
Coordinates tl(b.minX() / (double)COMF, b.maxY() / (double)COMF);
|
|
Coordinates br(b.maxX() / (double)COMF, b.minY() / (double)COMF);
|
|
_llBounds = RectC(tl, br);
|
|
if (!_llBounds.isValid()) {
|
|
_errorString = "Invalid geometries bounds";
|
|
return;
|
|
}
|
|
|
|
_zooms = zooms(_llBounds);
|
|
_zoom = _zooms.min();
|
|
updateTransform();
|
|
|
|
_valid = true;
|
|
}
|
|
|
|
ENCMap::~ENCMap()
|
|
{
|
|
delete _data;
|
|
delete _style;
|
|
}
|
|
|
|
void ENCMap::load(const Projection &in, const Projection &out,
|
|
qreal deviceRatio, bool hidpi)
|
|
{
|
|
Q_UNUSED(in);
|
|
Q_UNUSED(hidpi);
|
|
|
|
_tileRatio = deviceRatio;
|
|
_projection = out;
|
|
Q_ASSERT(!_data);
|
|
_data = new MapData(path());
|
|
Q_ASSERT(!_style);
|
|
_style = new Style(deviceRatio);
|
|
|
|
QPixmapCache::clear();
|
|
}
|
|
|
|
void ENCMap::unload()
|
|
{
|
|
cancelJobs(true);
|
|
|
|
delete _data;
|
|
_data = 0;
|
|
delete _style;
|
|
_style = 0;
|
|
}
|
|
|
|
int ENCMap::zoomFit(const QSize &size, const RectC &rect)
|
|
{
|
|
if (rect.isValid()) {
|
|
RectD pr(rect, _projection, 10);
|
|
|
|
_zoom = _zooms.min();
|
|
for (int i = _zooms.min() + 1; i <= _zooms.max(); i++) {
|
|
Transform t(transform(i));
|
|
QRectF r(t.proj2img(pr.topLeft()), t.proj2img(pr.bottomRight()));
|
|
if (size.width() + EPSILON < r.width()
|
|
|| size.height() + EPSILON < r.height())
|
|
break;
|
|
_zoom = i;
|
|
}
|
|
} else
|
|
_zoom = _zooms.max();
|
|
|
|
updateTransform();
|
|
|
|
return _zoom;
|
|
}
|
|
|
|
int ENCMap::zoomIn()
|
|
{
|
|
cancelJobs(false);
|
|
|
|
_zoom = qMin(_zoom + 1, _zooms.max());
|
|
updateTransform();
|
|
return _zoom;
|
|
}
|
|
|
|
int ENCMap::zoomOut()
|
|
{
|
|
cancelJobs(false);
|
|
|
|
_zoom = qMax(_zoom - 1, _zooms.min());
|
|
updateTransform();
|
|
return _zoom;
|
|
}
|
|
|
|
void ENCMap::setZoom(int zoom)
|
|
{
|
|
_zoom = zoom;
|
|
updateTransform();
|
|
}
|
|
|
|
Transform ENCMap::transform(int zoom) const
|
|
{
|
|
int z = zoom + Util::log2i(TILE_SIZE);
|
|
|
|
double scale = _projection.isGeographic()
|
|
? 360.0 / (1<<z) : (2.0 * M_PI * WGS84_RADIUS) / (1<<z);
|
|
PointD topLeft(_projection.ll2xy(_llBounds.topLeft()));
|
|
return Transform(ReferencePoint(PointD(0, 0), topLeft),
|
|
PointD(scale, scale));
|
|
}
|
|
|
|
void ENCMap::updateTransform()
|
|
{
|
|
_transform = transform(_zoom);
|
|
|
|
RectD prect(_llBounds, _projection);
|
|
_bounds = QRectF(_transform.proj2img(prect.topLeft()),
|
|
_transform.proj2img(prect.bottomRight()));
|
|
}
|
|
|
|
bool ENCMap::isRunning(int zoom, const QPoint &xy) const
|
|
{
|
|
for (int i = 0; i < _jobs.size(); i++) {
|
|
const QList<ENC::RasterTile> &tiles = _jobs.at(i)->tiles();
|
|
for (int j = 0; j < tiles.size(); j++) {
|
|
const ENC::RasterTile &mt = tiles.at(j);
|
|
if (mt.zoom() == zoom && mt.xy() == xy)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ENCMap::runJob(ENCJob *job)
|
|
{
|
|
_jobs.append(job);
|
|
|
|
connect(job, &ENCJob::finished, this, &ENCMap::jobFinished);
|
|
job->run();
|
|
}
|
|
|
|
void ENCMap::removeJob(ENCJob *job)
|
|
{
|
|
_jobs.removeOne(job);
|
|
job->deleteLater();
|
|
}
|
|
|
|
void ENCMap::jobFinished(ENCJob *job)
|
|
{
|
|
const QList<ENC::RasterTile> &tiles = job->tiles();
|
|
|
|
for (int i = 0; i < tiles.size(); i++) {
|
|
const ENC::RasterTile &mt = tiles.at(i);
|
|
if (!mt.pixmap().isNull())
|
|
QPixmapCache::insert(key(mt.zoom(), mt.xy()), mt.pixmap());
|
|
}
|
|
|
|
removeJob(job);
|
|
|
|
emit tilesLoaded();
|
|
}
|
|
|
|
void ENCMap::cancelJobs(bool wait)
|
|
{
|
|
for (int i = 0; i < _jobs.size(); i++)
|
|
_jobs.at(i)->cancel(wait);
|
|
}
|
|
|
|
QString ENCMap::key(int zoom, const QPoint &xy) const
|
|
{
|
|
return path() + "-" + QString::number(zoom) + "_"
|
|
+ QString::number(xy.x()) + "_" + QString::number(xy.y());
|
|
}
|
|
|
|
void ENCMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
|
|
{
|
|
Q_UNUSED(flags);
|
|
QPointF tl(floor(rect.left() / TILE_SIZE) * TILE_SIZE,
|
|
floor(rect.top() / TILE_SIZE) * TILE_SIZE);
|
|
QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
|
|
int width = ceil(s.width() / TILE_SIZE);
|
|
int height = ceil(s.height() / TILE_SIZE);
|
|
|
|
QList<RasterTile> tiles;
|
|
|
|
for (int i = 0; i < width; i++) {
|
|
for (int j = 0; j < height; j++) {
|
|
QPoint ttl(tl.x() + i * TILE_SIZE, tl.y() + j * TILE_SIZE);
|
|
if (isRunning(_zoom, ttl))
|
|
continue;
|
|
|
|
QPixmap pm;
|
|
if (QPixmapCache::find(key(_zoom, ttl), &pm))
|
|
painter->drawPixmap(ttl, pm);
|
|
else
|
|
tiles.append(RasterTile(_projection, _transform, _style, _data,
|
|
_zoom, _zooms, QRect(ttl, QSize(TILE_SIZE, TILE_SIZE)),
|
|
_tileRatio));
|
|
}
|
|
}
|
|
|
|
if (!tiles.isEmpty()) {
|
|
if (flags & Map::Block) {
|
|
QFuture<void> future = QtConcurrent::map(tiles, &RasterTile::render);
|
|
future.waitForFinished();
|
|
|
|
for (int i = 0; i < tiles.size(); i++) {
|
|
const RasterTile &mt = tiles.at(i);
|
|
const QPixmap &pm = mt.pixmap();
|
|
painter->drawPixmap(mt.xy(), pm);
|
|
QPixmapCache::insert(key(mt.zoom(), mt.xy()), pm);
|
|
}
|
|
} else
|
|
runJob(new ENCJob(tiles));
|
|
}
|
|
}
|
|
|
|
Map *ENCMap::create(const QString &path, const Projection &proj, bool *isMap)
|
|
{
|
|
Q_UNUSED(proj);
|
|
|
|
if (isMap)
|
|
*isMap = false;
|
|
|
|
return new ENCMap(path);
|
|
}
|