2017-03-26 15:32:55 +02:00
|
|
|
#include <QDir>
|
|
|
|
#include <QtAlgorithms>
|
|
|
|
#include <QPainter>
|
2017-11-26 18:54:03 +01:00
|
|
|
#include "common/rectc.h"
|
2017-03-26 15:32:55 +02:00
|
|
|
#include "tar.h"
|
|
|
|
#include "atlas.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define ZOOM_THRESHOLD 0.9
|
|
|
|
|
2017-03-31 19:18:01 +02:00
|
|
|
#define TL(m) ((m)->xy2pp((m)->bounds().topLeft()))
|
|
|
|
#define BR(m) ((m)->xy2pp((m)->bounds().bottomRight()))
|
2017-03-26 15:32:55 +02:00
|
|
|
|
|
|
|
static bool resCmp(const OfflineMap *m1, const OfflineMap *m2)
|
|
|
|
{
|
|
|
|
qreal r1, r2;
|
|
|
|
|
|
|
|
r1 = m1->resolution(m1->bounds().center());
|
|
|
|
r2 = m2->resolution(m2->bounds().center());
|
|
|
|
|
|
|
|
return r1 > r2;
|
|
|
|
}
|
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
static bool xCmp(const OfflineMap *m1, const OfflineMap *m2)
|
2017-03-26 15:32:55 +02:00
|
|
|
{
|
2017-03-31 19:18:01 +02:00
|
|
|
return TL(m1).x() < TL(m2).x();
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
static bool yCmp(const OfflineMap *m1, const OfflineMap *m2)
|
2017-03-26 15:32:55 +02:00
|
|
|
{
|
2017-03-31 19:18:01 +02:00
|
|
|
return TL(m1).y() > TL(m2).y();
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Atlas::computeZooms()
|
|
|
|
{
|
|
|
|
qSort(_maps.begin(), _maps.end(), resCmp);
|
|
|
|
|
|
|
|
_zooms.append(QPair<int, int>(0, _maps.count() - 1));
|
|
|
|
for (int i = 1; i < _maps.count(); i++) {
|
|
|
|
qreal last = _maps.at(i-1)->resolution(_maps.at(i)->bounds().center());
|
|
|
|
qreal cur = _maps.at(i)->resolution(_maps.at(i)->bounds().center());
|
|
|
|
if (cur < last * ZOOM_THRESHOLD) {
|
|
|
|
_zooms.last().second = i-1;
|
|
|
|
_zooms.append(QPair<int, int>(i, _maps.count() - 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Atlas::computeBounds()
|
|
|
|
{
|
|
|
|
QList<QPointF> offsets;
|
|
|
|
|
|
|
|
for (int i = 0; i < _maps.count(); i++)
|
|
|
|
offsets.append(QPointF());
|
|
|
|
|
|
|
|
for (int z = 0; z < _zooms.count(); z++) {
|
|
|
|
QList<OfflineMap*> m;
|
|
|
|
for (int i = _zooms.at(z).first; i <= _zooms.at(z).second; i++)
|
|
|
|
m.append(_maps.at(i));
|
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
qSort(m.begin(), m.end(), xCmp);
|
2017-08-08 18:41:19 +02:00
|
|
|
offsets[_maps.indexOf(m.first())].setX(0);
|
2017-03-26 15:32:55 +02:00
|
|
|
for (int i = 1; i < m.size(); i++) {
|
2017-08-08 18:41:19 +02:00
|
|
|
qreal w = round(m.first()->pp2xy(TL(m.at(i))).x());
|
2017-04-04 01:19:17 +02:00
|
|
|
offsets[_maps.indexOf(m.at(i))].setX(w);
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
qSort(m.begin(), m.end(), yCmp);
|
2017-08-08 18:41:19 +02:00
|
|
|
offsets[_maps.indexOf(m.first())].setY(0);
|
2017-03-26 15:32:55 +02:00
|
|
|
for (int i = 1; i < m.size(); i++) {
|
2017-08-08 18:41:19 +02:00
|
|
|
qreal h = round(m.first()->pp2xy(TL(m.at(i))).y());
|
2017-04-04 01:19:17 +02:00
|
|
|
offsets[_maps.indexOf(m.at(i))].setY(h);
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < _maps.count(); i++)
|
2017-03-31 19:18:01 +02:00
|
|
|
_bounds.append(QPair<QRectF, QRectF>(QRectF(TL(_maps.at(i)),
|
|
|
|
BR(_maps.at(i))), QRectF(offsets.at(i), _maps.at(i)->bounds().size())));
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
|
2017-04-21 21:15:58 +02:00
|
|
|
Atlas::Atlas(const QString &fileName, QObject *parent) : Map(parent)
|
2017-03-26 15:32:55 +02:00
|
|
|
{
|
2017-04-21 21:15:58 +02:00
|
|
|
QFileInfo fi(fileName);
|
2018-01-14 23:03:27 +01:00
|
|
|
QByteArray ba;
|
|
|
|
QString suffix = fi.suffix().toLower();
|
|
|
|
Tar tar;
|
2017-04-04 22:03:42 +02:00
|
|
|
|
2017-03-26 15:32:55 +02:00
|
|
|
_valid = false;
|
2017-04-02 09:32:40 +02:00
|
|
|
_zoom = 0;
|
2017-04-21 21:15:58 +02:00
|
|
|
_name = fi.dir().dirName();
|
2017-04-30 00:19:53 +02:00
|
|
|
_ci = -1; _cz = -1;
|
2017-03-26 15:32:55 +02:00
|
|
|
|
2018-01-14 23:03:27 +01:00
|
|
|
|
|
|
|
if (suffix == "tar") {
|
|
|
|
if (!tar.load(fileName)) {
|
|
|
|
_errorString = "Error reading tar file";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QString tbaFileName = fi.completeBaseName() + ".tba";
|
|
|
|
ba = tar.file(tbaFileName);
|
|
|
|
} else if (suffix == "tba") {
|
|
|
|
QFile tbaFile(fileName);
|
|
|
|
if (!tbaFile.open(QIODevice::ReadOnly)) {
|
|
|
|
_errorString = QString("Error opening tba file: %1")
|
|
|
|
.arg(tbaFile.errorString());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ba = tbaFile.readAll();
|
|
|
|
}
|
|
|
|
if (!ba.startsWith("Atlas 1.0")) {
|
|
|
|
_errorString = "Missing or invalid tba file";
|
2017-03-26 15:32:55 +02:00
|
|
|
return;
|
2018-01-14 23:03:27 +01:00
|
|
|
}
|
|
|
|
|
2017-03-26 15:32:55 +02:00
|
|
|
|
2017-04-21 21:15:58 +02:00
|
|
|
QDir dir(fi.absolutePath());
|
2017-03-26 15:32:55 +02:00
|
|
|
QFileInfoList layers = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
for (int n = 0; n < layers.count(); n++) {
|
|
|
|
QDir zdir(layers.at(n).absoluteFilePath());
|
|
|
|
QFileInfoList maps = zdir.entryInfoList(QDir::Dirs
|
|
|
|
| QDir::NoDotAndDotDot);
|
|
|
|
for (int i = 0; i < maps.count(); i++) {
|
2017-04-21 21:15:58 +02:00
|
|
|
QString mapFile = maps.at(i).absoluteFilePath() + "/"
|
|
|
|
+ maps.at(i).fileName() + ".map";
|
|
|
|
|
2017-03-27 02:41:30 +02:00
|
|
|
OfflineMap *map;
|
2017-04-04 22:03:42 +02:00
|
|
|
if (tar.isOpen())
|
2017-04-21 21:15:58 +02:00
|
|
|
map = new OfflineMap(mapFile, tar, this);
|
2017-03-27 02:41:30 +02:00
|
|
|
else
|
2017-04-21 21:15:58 +02:00
|
|
|
map = new OfflineMap(mapFile, this);
|
|
|
|
|
2017-03-26 15:32:55 +02:00
|
|
|
if (map->isValid())
|
|
|
|
_maps.append(map);
|
2017-04-21 21:15:58 +02:00
|
|
|
else {
|
|
|
|
_errorString = QString("Error loading map: %1: %2")
|
|
|
|
.arg(mapFile, map->errorString());
|
|
|
|
return;
|
|
|
|
}
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_maps.isEmpty()) {
|
2017-04-21 21:15:58 +02:00
|
|
|
_errorString = "No maps found in atlas";
|
2017-03-26 15:32:55 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
computeZooms();
|
|
|
|
computeBounds();
|
|
|
|
|
|
|
|
_valid = true;
|
|
|
|
}
|
|
|
|
|
2017-04-01 06:10:36 +02:00
|
|
|
Atlas::~Atlas()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < _maps.size(); i++)
|
|
|
|
delete _maps.at(i);
|
|
|
|
}
|
|
|
|
|
2017-03-26 15:32:55 +02:00
|
|
|
QRectF Atlas::bounds() const
|
|
|
|
{
|
|
|
|
QSizeF s(0, 0);
|
|
|
|
|
|
|
|
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
|
|
|
|
if (_bounds.at(i).second.right() > s.width())
|
|
|
|
s.setWidth(_bounds.at(i).second.right());
|
|
|
|
if (_bounds.at(i).second.bottom() > s.height())
|
|
|
|
s.setHeight(_bounds.at(i).second.bottom());
|
|
|
|
}
|
|
|
|
|
|
|
|
return QRectF(QPointF(0, 0), s);
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal Atlas::resolution(const QPointF &p) const
|
|
|
|
{
|
|
|
|
int idx = _zooms.at(_zoom).first;
|
|
|
|
|
|
|
|
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
|
2017-03-31 19:18:01 +02:00
|
|
|
if (_bounds.at(i).second.contains(_maps.at(i)->xy2pp(p))) {
|
2017-03-26 15:32:55 +02:00
|
|
|
idx = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _maps.at(idx)->resolution(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal Atlas::zoom() const
|
|
|
|
{
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
2017-06-29 22:29:27 +02:00
|
|
|
qreal Atlas::zoomFit(const QSize &size, const RectC &br)
|
2017-03-26 15:32:55 +02:00
|
|
|
{
|
|
|
|
_zoom = 0;
|
|
|
|
|
2017-06-30 18:15:22 +02:00
|
|
|
if (!br.isValid()) {
|
2017-05-01 22:49:01 +02:00
|
|
|
_zoom = _zooms.size() - 1;
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
2017-03-26 15:32:55 +02:00
|
|
|
for (int z = 0; z < _zooms.count(); z++) {
|
|
|
|
for (int i = _zooms.at(z).first; i <= _zooms.at(z).second; i++) {
|
2017-04-19 00:07:01 +02:00
|
|
|
if (!_bounds.at(i).first.contains(_maps.at(i)->ll2pp(br.center())))
|
2017-03-26 15:32:55 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
QRect sbr = QRectF(_maps.at(i)->ll2xy(br.topLeft()),
|
|
|
|
_maps.at(i)->ll2xy(br.bottomRight())).toRect().normalized();
|
|
|
|
|
|
|
|
if (sbr.size().width() > size.width()
|
|
|
|
|| sbr.size().height() > size.height())
|
|
|
|
return _zoom;
|
|
|
|
|
|
|
|
_zoom = z;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
2017-06-26 00:20:42 +02:00
|
|
|
qreal Atlas::zoomFit(qreal resolution, const Coordinates &c)
|
|
|
|
{
|
|
|
|
_zoom = 0;
|
|
|
|
|
|
|
|
for (int z = 0; z < _zooms.count(); z++) {
|
|
|
|
for (int i = _zooms.at(z).first; i <= _zooms.at(z).second; i++) {
|
|
|
|
if (!_bounds.at(i).first.contains(_maps.at(i)->ll2pp(c)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (_maps.at(i)->resolution(_maps.at(i)->ll2xy(c)) < resolution)
|
|
|
|
return _zoom;
|
|
|
|
|
|
|
|
_zoom = z;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
2017-03-26 15:32:55 +02:00
|
|
|
qreal Atlas::zoomIn()
|
|
|
|
{
|
|
|
|
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal Atlas::zoomOut()
|
|
|
|
{
|
|
|
|
_zoom = qMax(_zoom - 1, 0);
|
|
|
|
return _zoom;
|
|
|
|
}
|
|
|
|
|
2017-04-30 00:19:53 +02:00
|
|
|
QPointF Atlas::ll2xy(const Coordinates &c)
|
2017-03-26 15:32:55 +02:00
|
|
|
{
|
2017-04-29 23:15:44 +02:00
|
|
|
QPointF pp;
|
2017-03-26 15:32:55 +02:00
|
|
|
|
2017-04-30 00:19:53 +02:00
|
|
|
if (_cz != _zoom) {
|
|
|
|
_ci = -1;
|
|
|
|
_cz = _zoom;
|
2017-04-29 23:15:44 +02:00
|
|
|
}
|
|
|
|
|
2017-04-30 00:19:53 +02:00
|
|
|
if (_ci >= 0)
|
|
|
|
pp = _maps.at(_ci)->ll2pp(c);
|
|
|
|
if (_ci < 0 || !_bounds.at(_ci).first.contains(pp)) {
|
|
|
|
_ci = _zooms.at(_zoom).first;
|
|
|
|
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
|
2017-04-29 23:15:44 +02:00
|
|
|
pp = _maps.at(i)->ll2pp(c);
|
|
|
|
if (_bounds.at(i).first.contains(pp)) {
|
2017-04-30 00:19:53 +02:00
|
|
|
_ci = i;
|
2017-04-29 23:15:44 +02:00
|
|
|
break;
|
|
|
|
}
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-30 00:19:53 +02:00
|
|
|
QPointF p = _maps.at(_ci)->pp2xy(pp);
|
|
|
|
return p + _bounds.at(_ci).second.topLeft();
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
|
|
|
|
2017-04-30 00:19:53 +02:00
|
|
|
Coordinates Atlas::xy2ll(const QPointF &p)
|
2017-03-26 15:32:55 +02:00
|
|
|
{
|
|
|
|
int idx = _zooms.at(_zoom).first;
|
|
|
|
|
|
|
|
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
|
2017-03-31 19:18:01 +02:00
|
|
|
if (_bounds.at(i).second.contains(_maps.at(i)->xy2pp(p))) {
|
2017-03-26 15:32:55 +02:00
|
|
|
idx = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QPointF p2 = p - _bounds.at(idx).second.topLeft();
|
|
|
|
return _maps.at(idx)->xy2ll(p2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Atlas::draw(QPainter *painter, const QRectF &rect)
|
|
|
|
{
|
2017-04-01 05:59:55 +02:00
|
|
|
// All in one map
|
|
|
|
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
|
2017-08-27 12:19:33 +02:00
|
|
|
if (_bounds.at(i).second.contains(rect)) {
|
2017-04-01 05:59:55 +02:00
|
|
|
draw(painter, rect, i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2017-03-26 15:32:55 +02:00
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
// Multiple maps
|
2017-09-15 00:07:09 +02:00
|
|
|
painter->fillRect(rect, _backgroundColor);
|
2017-03-26 15:32:55 +02:00
|
|
|
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
|
2017-04-01 05:59:55 +02:00
|
|
|
QRectF ir = rect.intersected(_bounds.at(i).second);
|
|
|
|
if (!ir.isNull())
|
|
|
|
draw(painter, ir, i);
|
|
|
|
}
|
|
|
|
}
|
2017-03-26 15:32:55 +02:00
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
void Atlas::draw(QPainter *painter, const QRectF &rect, int mapIndex)
|
|
|
|
{
|
|
|
|
OfflineMap *map = _maps.at(mapIndex);
|
|
|
|
const QPointF offset = _bounds.at(mapIndex).second.topLeft();
|
|
|
|
QRectF pr = QRectF(rect.topLeft() - offset, rect.size());
|
2017-03-27 02:41:30 +02:00
|
|
|
|
2017-04-01 05:59:55 +02:00
|
|
|
map->load();
|
|
|
|
|
|
|
|
painter->translate(offset);
|
|
|
|
map->draw(painter, pr);
|
|
|
|
painter->translate(-offset);
|
2017-03-26 15:32:55 +02:00
|
|
|
}
|
2017-04-22 10:19:02 +02:00
|
|
|
|
|
|
|
void Atlas::unload()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < _maps.count(); i++)
|
|
|
|
_maps.at(i)->unload();
|
|
|
|
}
|
2018-01-14 23:03:27 +01:00
|
|
|
|
|
|
|
bool Atlas::isAtlas(const QString &path)
|
|
|
|
{
|
|
|
|
QFileInfo fi(path);
|
|
|
|
QString suffix = fi.suffix().toLower();
|
|
|
|
Tar tar;
|
|
|
|
|
|
|
|
if (suffix == "tar") {
|
|
|
|
if (!tar.load(path))
|
|
|
|
return false;
|
|
|
|
QString tbaFileName = fi.completeBaseName() + ".tba";
|
|
|
|
return tar.contains(tbaFileName);
|
|
|
|
} else if (suffix == "tba")
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|