Now using a strict horizontal map scale
[GPXSee.git] / src / map / atlas.cpp
blob01318e8f4ac84ce15bf78cbeb147f0135c2f0a2b
1 #include <QDir>
2 #include <QtAlgorithms>
3 #include <QPainter>
4 #include "common/rectc.h"
5 #include "offlinemap.h"
6 #include "tar.h"
7 #include "atlas.h"
10 #define ZOOM_THRESHOLD 0.9
12 #define TL(m) ((m)->xy2pp((m)->bounds().topLeft()))
13 #define BR(m) ((m)->xy2pp((m)->bounds().bottomRight()))
15 static bool resCmp(OfflineMap *m1, OfflineMap *m2)
17 qreal r1, r2;
19 r1 = m1->resolution(m1->bounds());
20 r2 = m2->resolution(m2->bounds());
22 return r1 > r2;
25 static bool xCmp(const OfflineMap *m1, const OfflineMap *m2)
27 return TL(m1).x() < TL(m2).x();
30 static bool yCmp(const OfflineMap *m1, const OfflineMap *m2)
32 return TL(m1).y() > TL(m2).y();
35 void Atlas::computeZooms()
37 qSort(_maps.begin(), _maps.end(), resCmp);
39 _zooms.append(Zoom(0, _maps.count() - 1));
40 for (int i = 1; i < _maps.count(); i++) {
41 qreal last = _maps.at(i-1)->resolution(_maps.at(i)->bounds());
42 qreal cur = _maps.at(i)->resolution(_maps.at(i)->bounds());
43 if (cur < last * ZOOM_THRESHOLD) {
44 _zooms.last().last = i-1;
45 _zooms.append(Zoom(i, _maps.count() - 1));
50 void Atlas::computeBounds()
52 QList<QPointF> offsets;
54 for (int i = 0; i < _maps.count(); i++)
55 offsets.append(QPointF());
57 for (int z = 0; z < _zooms.count(); z++) {
58 QList<OfflineMap*> m;
59 for (int i = _zooms.at(z).first; i <= _zooms.at(z).last; i++)
60 m.append(_maps.at(i));
62 qSort(m.begin(), m.end(), xCmp);
63 offsets[_maps.indexOf(m.first())].setX(0);
64 for (int i = 1; i < m.size(); i++) {
65 qreal w = round(m.first()->pp2xy(TL(m.at(i))).x());
66 offsets[_maps.indexOf(m.at(i))].setX(w);
69 qSort(m.begin(), m.end(), yCmp);
70 offsets[_maps.indexOf(m.first())].setY(0);
71 for (int i = 1; i < m.size(); i++) {
72 qreal h = round(m.first()->pp2xy(TL(m.at(i))).y());
73 offsets[_maps.indexOf(m.at(i))].setY(h);
77 for (int i = 0; i < _maps.count(); i++)
78 _bounds.append(Bounds(RectD(TL(_maps.at(i)), BR(_maps.at(i))),
79 QRectF(offsets.at(i), _maps.at(i)->bounds().size())));
82 Atlas::Atlas(const QString &fileName, QObject *parent)
83 : Map(parent), _zoom(0), _mapIndex(-1), _valid(false)
85 QFileInfo fi(fileName);
86 QByteArray ba;
87 QString suffix = fi.suffix().toLower();
88 Tar tar(fileName);
91 _name = fi.dir().dirName();
93 if (suffix == "tar") {
94 if (!tar.open()) {
95 _errorString = "Error reading tar file";
96 return;
98 QString tbaFileName = fi.completeBaseName() + ".tba";
99 ba = tar.file(tbaFileName);
100 } else if (suffix == "tba") {
101 QFile tbaFile(fileName);
102 if (!tbaFile.open(QIODevice::ReadOnly)) {
103 _errorString = QString("Error opening tba file: %1")
104 .arg(tbaFile.errorString());
105 return;
107 ba = tbaFile.readAll();
109 if (!ba.startsWith("Atlas 1.0")) {
110 _errorString = "Missing or invalid tba file";
111 return;
114 QDir dir(fi.absolutePath());
115 QFileInfoList layers = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
116 for (int n = 0; n < layers.count(); n++) {
117 QDir zdir(layers.at(n).absoluteFilePath());
118 QFileInfoList maps = zdir.entryInfoList(QDir::Dirs
119 | QDir::NoDotAndDotDot);
120 for (int i = 0; i < maps.count(); i++) {
121 QString mapFile = maps.at(i).absoluteFilePath() + "/"
122 + maps.at(i).fileName() + ".map";
124 OfflineMap *map;
125 if (tar.isOpen())
126 map = new OfflineMap(mapFile, tar, this);
127 else
128 map = new OfflineMap(mapFile, this);
130 if (map->isValid())
131 _maps.append(map);
132 else {
133 _errorString = QString("Error loading map: %1: %2")
134 .arg(mapFile, map->errorString());
135 return;
139 if (_maps.isEmpty()) {
140 _errorString = "No maps found in atlas";
141 return;
144 computeZooms();
145 computeBounds();
147 _valid = true;
150 QRectF Atlas::bounds() const
152 QSizeF s(0, 0);
154 for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
155 if (_bounds.at(i).xy.right() > s.width())
156 s.setWidth(_bounds.at(i).xy.right());
157 if (_bounds.at(i).xy.bottom() > s.height())
158 s.setHeight(_bounds.at(i).xy.bottom());
161 return QRectF(QPointF(0, 0), s);
164 int Atlas::zoomFit(const QSize &size, const RectC &br)
166 _zoom = 0;
167 _mapIndex = -1;
169 if (!br.isValid()) {
170 _zoom = _zooms.size() - 1;
171 return _zoom;
174 for (int z = 0; z < _zooms.count(); z++) {
175 for (int i = _zooms.at(z).first; i <= _zooms.at(z).last; i++) {
176 if (!_bounds.at(i).pp.contains(_maps.at(i)->ll2pp(br.center())))
177 continue;
179 QRect sbr = QRectF(_maps.at(i)->ll2xy(br.topLeft()),
180 _maps.at(i)->ll2xy(br.bottomRight())).toRect().normalized();
182 if (sbr.size().width() > size.width()
183 || sbr.size().height() > size.height())
184 return _zoom;
186 _zoom = z;
187 break;
191 return _zoom;
194 void Atlas::setZoom(int zoom)
196 _mapIndex = -1;
197 _zoom = zoom;
200 int Atlas::zoomIn()
202 _zoom = qMin(_zoom + 1, _zooms.size() - 1);
203 _mapIndex = -1;
205 return _zoom;
208 int Atlas::zoomOut()
210 _zoom = qMax(_zoom - 1, 0);
211 _mapIndex = -1;
213 return _zoom;
216 QPointF Atlas::ll2xy(const Coordinates &c)
218 PointD pp;
220 if (_mapIndex >= 0)
221 pp = _maps.at(_mapIndex)->ll2pp(c);
222 if (_mapIndex < 0 || !_bounds.at(_mapIndex).pp.contains(pp)) {
223 _mapIndex = _zooms.at(_zoom).first;
224 for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
225 pp = _maps.at(i)->ll2pp(c);
226 if (_bounds.at(i).pp.contains(pp)) {
227 _mapIndex = i;
228 break;
233 QPointF p = _maps.at(_mapIndex)->pp2xy(pp);
234 return p + _bounds.at(_mapIndex).xy.topLeft();
237 Coordinates Atlas::xy2ll(const QPointF &p)
239 int idx = _zooms.at(_zoom).first;
241 for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
242 if (_bounds.at(i).xy.contains(p)) {
243 idx = i;
244 break;
248 QPointF p2 = p - _bounds.at(idx).xy.topLeft();
249 return _maps.at(idx)->xy2ll(p2);
252 void Atlas::draw(QPainter *painter, const QRectF &rect, bool block)
254 Q_UNUSED(block);
256 // All in one map
257 for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
258 if (_bounds.at(i).xy.contains(rect)) {
259 draw(painter, rect, i);
260 return;
264 // Multiple maps
265 for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
266 QRectF ir = rect.intersected(_bounds.at(i).xy);
267 if (!ir.isNull())
268 draw(painter, ir, i);
272 void Atlas::draw(QPainter *painter, const QRectF &rect, int mapIndex)
274 OfflineMap *map = _maps.at(mapIndex);
275 const QPointF offset = _bounds.at(mapIndex).xy.topLeft();
276 QRectF pr = QRectF(rect.topLeft() - offset, rect.size());
278 map->load();
280 painter->translate(offset);
281 map->draw(painter, pr, true);
282 painter->translate(-offset);
285 void Atlas::unload()
287 for (int i = 0; i < _maps.count(); i++)
288 _maps.at(i)->unload();
291 bool Atlas::isAtlas(const QString &path)
293 QFileInfo fi(path);
294 QString suffix = fi.suffix().toLower();
295 Tar tar(path);
297 if (suffix == "tar") {
298 if (!tar.open())
299 return false;
300 QString tbaFileName = fi.completeBaseName() + ".tba";
301 return tar.contains(tbaFileName);
302 } else if (suffix == "tba")
303 return true;
305 return false;