diff --git a/gpxsee.pro b/gpxsee.pro index 2ad5a6fb..40c72107 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -83,7 +83,8 @@ HEADERS += src/config.h \ src/offlinemap.h \ src/mapdir.h \ src/matrix.h \ - src/tar.h + src/tar.h \ + src/atlas.h SOURCES += src/main.cpp \ src/gui.cpp \ src/poi.cpp \ @@ -142,7 +143,8 @@ SOURCES += src/main.cpp \ src/offlinemap.cpp \ src/mapdir.cpp \ src/matrix.cpp \ - src/tar.cpp + src/tar.cpp \ + src/atlas.cpp RESOURCES += gpxsee.qrc TRANSLATIONS = lang/gpxsee_cs.ts \ lang/gpxsee_sv.ts diff --git a/src/atlas.cpp b/src/atlas.cpp new file mode 100644 index 00000000..38f5c157 --- /dev/null +++ b/src/atlas.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include "tar.h" +#include "atlas.h" + + +#define ZOOM_THRESHOLD 0.9 + +#define TL(m) ((m)->xy2ll((m)->bounds().topLeft())) +#define BR(m) ((m)->xy2ll((m)->bounds().bottomRight())) + +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; +} + +static bool lonCmp(const OfflineMap *m1, const OfflineMap *m2) +{ + return TL(m1).lon() < TL(m2).lon(); +} + +static bool latCmp(const OfflineMap *m1, const OfflineMap *m2) +{ + return TL(m1).lat() > TL(m2).lat(); +} + +bool Atlas::isAtlas(const QFileInfoList &files) +{ + for (int i = 0; i < files.count(); i++) { + const QString &fileName = files.at(i).fileName(); + if (fileName.endsWith(".tar")) { + Tar tar; + if (!tar.load(files.at(i).absoluteFilePath())) { + qWarning("%s: %s: error loading tar file", qPrintable(_name), + qPrintable(fileName)); + return false; + } + QStringList tarFiles = tar.files(); + for (int j = 0; j < tarFiles.size(); j++) + if (tarFiles.at(j).endsWith(".tba")) + return true; + } else if (fileName.endsWith(".tba")) + return true; + } + + return false; +} + +void Atlas::computeZooms() +{ + qSort(_maps.begin(), _maps.end(), resCmp); + + _zooms.append(QPair(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(i, _maps.count() - 1)); + } + } +} + +void Atlas::computeBounds() +{ + QList offsets; + + for (int i = 0; i < _maps.count(); i++) + offsets.append(QPointF()); + + for (int z = 0; z < _zooms.count(); z++) { + qreal w = 0, h = 0; + + QList m; + for (int i = _zooms.at(z).first; i <= _zooms.at(z).second; i++) + m.append(_maps.at(i)); + + qSort(m.begin(), m.end(), lonCmp); + offsets[_maps.indexOf(m.first())].setX(w); + for (int i = 1; i < m.size(); i++) { + if (TL(m.at(i)).lon() > TL(m.at(i-1)).lon()) { + w += m.at(i-1)->ll2xy(Coordinates(TL(m.at(i)).lon(), + TL(m.at(i-1)).lat())).x() - m.at(i-1)->bounds().left(); + offsets[_maps.indexOf(m.at(i))].setX(round(w)); + } + } + + qSort(m.begin(), m.end(), latCmp); + offsets[_maps.indexOf(m.first())].setY(h); + for (int i = 1; i < m.size(); i++) { + if (TL(m.at(i)).lat() < TL(m.at(i-1)).lat()) { + h += m.at(i-1)->ll2xy(Coordinates(TL(m.at(i-1)).lon(), + TL(m.at(i)).lat())).y() - m.at(i-1)->bounds().top(); + offsets[_maps.indexOf(m.at(i))].setY(round(h)); + } + } + } + + for (int i = 0; i < _maps.count(); i++) + _bounds.append(QPair(QRectF(TL(_maps.at(i)).toPointF(), + BR(_maps.at(i)).toPointF()), QRectF(offsets.at(i), + _maps.at(i)->bounds().size()))); +} + +Atlas::Atlas(const QString &path, QObject *parent) : Map(parent) +{ + _valid = false; + + QFileInfo fi(path); + _name = fi.fileName(); + + QDir dir(path); + QFileInfoList files = dir.entryInfoList(QDir::Files); + if (!isAtlas(files)) + return; + + 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++) { + OfflineMap *map = new OfflineMap(maps.at(i).absoluteFilePath()); + if (map->isValid()) + _maps.append(map); + } + } + if (_maps.isEmpty()) { + qWarning("%s: No usable maps available", qPrintable(_name)); + return; + } + + computeZooms(); + computeBounds(); + + _zoom = 0; + _valid = true; +} + +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++) { + if (_bounds.at(i).second.contains(p)) { + idx = i; + break; + } + } + + return _maps.at(idx)->resolution(p); +} + +qreal Atlas::zoom() const +{ + return _zoom; +} + +qreal Atlas::zoomFit(const QSize &size, const QRectF &br) +{ + _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(br.center())) + 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; +} + +qreal Atlas::zoomIn() +{ + _zoom = qMin(_zoom + 1, _zooms.size() - 1); + return _zoom; +} + +qreal Atlas::zoomOut() +{ + _zoom = qMax(_zoom - 1, 0); + return _zoom; +} + +QPointF Atlas::ll2xy(const Coordinates &c) const +{ + int idx = _zooms.at(_zoom).first; + + for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) { + if (_bounds.at(i).first.contains(c.lon(), c.lat())) { + idx = i; + break; + } + } + + QPointF p = _maps.at(idx)->ll2xy(c); + return p + _bounds.at(idx).second.topLeft(); +} + +Coordinates Atlas::xy2ll(const QPointF &p) const +{ + int idx = _zooms.at(_zoom).first; + + for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) { + if (_bounds.at(i).second.contains(p)) { + 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) +{ + painter->fillRect(rect, Qt::white); + + for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) { + if (rect.intersects(_bounds.at(i).second)) { + OfflineMap *map = _maps.at(i); + QRectF ir = rect.intersected(_bounds.at(i).second); + const QPointF offset = _bounds.at(i).second.topLeft(); + QRectF pr = QRectF(ir.topLeft() - offset, ir.size()); + + painter->translate(offset); + map->draw(painter, pr); + painter->translate(-offset); + } + } +} diff --git a/src/atlas.h b/src/atlas.h new file mode 100644 index 00000000..e5307c58 --- /dev/null +++ b/src/atlas.h @@ -0,0 +1,47 @@ +#ifndef ATLAS_H +#define ATLAS_H + +#include +#include "map.h" +#include "offlinemap.h" + + +class Atlas : public Map +{ + Q_OBJECT + +public: + Atlas(const QString &path, QObject *parent = 0); + + const QString &name() const {return _name;} + + QRectF bounds() const; + qreal resolution(const QPointF &p) const; + + qreal zoom() const; + qreal zoomFit(const QSize &size, const QRectF &br); + qreal zoomIn(); + qreal zoomOut(); + + QPointF ll2xy(const Coordinates &c) const; + Coordinates xy2ll(const QPointF &p) const; + + void draw(QPainter *painter, const QRectF &rect); + + bool isValid() {return _valid;} + +private: + bool isAtlas(const QFileInfoList &files); + void computeZooms(); + void computeBounds(); + + QString _name; + bool _valid; + + QList _maps; + QVector > _zooms; + QVector > _bounds; + int _zoom; +}; + +#endif // ATLAS_H diff --git a/src/coordinates.h b/src/coordinates.h index 9437bc28..1251fe90 100644 --- a/src/coordinates.h +++ b/src/coordinates.h @@ -32,6 +32,8 @@ public: QPointF toMercator() const; static Coordinates fromMercator(const QPointF &m); + QPointF toPointF() const {return QPointF(_lon, _lat);} + private: qreal _lat, _lon; }; diff --git a/src/mapdir.cpp b/src/mapdir.cpp index aeca2be3..11efdc39 100644 --- a/src/mapdir.cpp +++ b/src/mapdir.cpp @@ -1,8 +1,7 @@ #include -#include "mapdir.h" - -#include +#include "atlas.h" #include "offlinemap.h" +#include "mapdir.h" QList MapDir::load(const QString &path, QObject *parent) { @@ -21,11 +20,20 @@ QList MapDir::load(const QString &path, QObject *parent) QFileInfoList list = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (int i = 0; i < list.size(); i++) { QFileInfo fileInfo = list.at(i); - OfflineMap *map = new OfflineMap(fileInfo.absoluteFilePath(), parent); - if (map->isValid()) - maps.append(map); - else - delete map; + + Atlas *atlas = new Atlas(fileInfo.absoluteFilePath(), parent); + if (atlas->isValid()) + maps.append(atlas); + else { + delete atlas; + + OfflineMap *map = new OfflineMap(fileInfo.absoluteFilePath(), + parent); + if (map->isValid()) + maps.append(map); + else + delete map; + } } return maps; diff --git a/src/offlinemap.cpp b/src/offlinemap.cpp index 40a62cf9..aa4832c5 100644 --- a/src/offlinemap.cpp +++ b/src/offlinemap.cpp @@ -353,7 +353,8 @@ void OfflineMap::draw(QPainter *painter, const QRectF &rect) if (!QPixmapCache::find(key, &pixmap)) { QByteArray ba = _tar.file(tileName); pixmap = QPixmap::fromImage(QImage::fromData(ba)); - QPixmapCache::insert(key, pixmap); + if (!pixmap.isNull()) + QPixmapCache::insert(key, pixmap); } } else pixmap = QPixmap(tileName); diff --git a/src/tar.cpp b/src/tar.cpp index 4a08d5ca..f4346e0b 100644 --- a/src/tar.cpp +++ b/src/tar.cpp @@ -26,7 +26,7 @@ struct Header char devmajor[8]; /* 329 */ char devminor[8]; /* 337 */ char prefix[155]; /* 345 */ - /* 500 */ + /* 500 */ }; static quint64 number(const char* data, size_t size) @@ -65,7 +65,8 @@ bool Tar::load(const QString &path) QByteArray Tar::file(const QString &name) { QMap::const_iterator it = _index.find(name); - Q_ASSERT(it != _index.end()); + if (it == _index.end()) + return QByteArray(); if (_file.seek(it.value().offset())) return _file.read(it.value().size());