Improved image/tiles info fetching
[GPXSee.git] / src / offlinemap.cpp
blob40a62cf94fa90848d723e6817560ed91a6e408e4
1 #include <QtGlobal>
2 #include <QPainter>
3 #include <QFileInfo>
4 #include <QMap>
5 #include <QDir>
6 #include <QBuffer>
7 #include <QImage>
8 #include <QImageReader>
9 #include <QPixmapCache>
10 #include "misc.h"
11 #include "rd.h"
12 #include "wgs84.h"
13 #include "coordinates.h"
14 #include "matrix.h"
15 #include "offlinemap.h"
18 int OfflineMap::parseMapFile(QIODevice &device, QList<ReferencePoint> &points)
20 bool res;
21 int ln = 1;
24 if (!device.open(QIODevice::ReadOnly))
25 return -1;
27 while (!device.atEnd()) {
28 QByteArray line = device.readLine();
30 if (ln == 1) {
31 if (line.trimmed() != "OziExplorer Map Data File Version 2.2")
32 return ln;
33 } else if (ln == 3)
34 _imgPath = line.trimmed();
35 else {
36 QList<QByteArray> list = line.split(',');
37 QString key(list.at(0).trimmed());
39 if (key.startsWith("Point") && list.count() == 17
40 && !list.at(2).trimmed().isEmpty()) {
41 int x = list.at(2).trimmed().toInt(&res);
42 if (!res)
43 return ln;
44 int y = list.at(3).trimmed().toInt(&res);
45 if (!res)
46 return ln;
47 int latd = list.at(6).trimmed().toInt(&res);
48 if (!res)
49 return ln;
50 qreal latm = list.at(7).trimmed().toFloat(&res);
51 if (!res)
52 return ln;
53 int lond = list.at(9).trimmed().toInt(&res);
54 if (!res)
55 return ln;
56 qreal lonm = list.at(10).trimmed().toFloat(&res);
57 if (!res)
58 return ln;
60 if (list.at(8).trimmed() == "S")
61 latd = -latd;
62 if (list.at(11).trimmed() == "W")
63 lond = -lond;
64 points.append(QPair<QPoint, Coordinates>(QPoint(x, y),
65 Coordinates(lond + lonm/60.0, latd + latm/60.0)));
66 } else if (key == "IWH") {
67 int w = list.at(2).trimmed().toInt(&res);
68 if (!res)
69 return ln;
70 int h = list.at(3).trimmed().toInt(&res);
71 if (!res)
72 return ln;
73 _size = QSize(w, h);
77 ln++;
80 return 0;
83 bool OfflineMap::computeTransformation(const QList<ReferencePoint> &points)
85 if (points.count() < 2)
86 return false;
88 Matrix c(3, 2);
89 c.zeroize();
90 for (size_t j = 0; j < c.w(); j++) {
91 for (size_t k = 0; k < c.h(); k++) {
92 for (int i = 0; i < points.size(); i++) {
93 double f[3], t[2];
94 QPointF p = points.at(i).second.toMercator();
96 f[0] = p.x();
97 f[1] = p.y();
98 f[2] = 1.0;
99 t[0] = points.at(i).first.x();
100 t[1] = points.at(i).first.y();
101 c.m(k,j) += f[k] * t[j];
106 Matrix Q(3, 3);
107 Q.zeroize();
108 for (int qi = 0; qi < points.size(); qi++) {
109 double v[3];
110 QPointF p = points.at(qi).second.toMercator();
112 v[0] = p.x();
113 v[1] = p.y();
114 v[2] = 1.0;
115 for (size_t i = 0; i < Q.h(); i++)
116 for (size_t j = 0; j < Q.w(); j++)
117 Q.m(i,j) += v[i] * v[j];
120 Matrix M = Q.augemented(c);
121 if (!M.eliminate())
122 return false;
124 _transform = QTransform(M.m(0,3), M.m(1,3), M.m(0,4), M.m(1,4),
125 M.m(2,3), M.m(2,4));
127 return true;
130 bool OfflineMap::computeResolution(QList<ReferencePoint> &points)
132 if (points.count() < 2)
133 return false;
135 int maxLon = 0, minLon = 0, maxLat = 0, minLat = 0;
136 qreal dLon, pLon, dLat, pLat;
138 for (int i = 1; i < points.size(); i++) {
139 if (points.at(i).second.lon() < points.at(minLon).second.lon())
140 minLon = i;
141 if (points.at(i).second.lon() > points.at(maxLon).second.lon())
142 maxLon = i;
143 if (points.at(i).second.lat() < points.at(minLat).second.lon())
144 minLat = i;
145 if (points.at(i).second.lat() > points.at(maxLat).second.lon())
146 maxLat = i;
149 dLon = points.at(minLon).second.distanceTo(points.at(maxLon).second);
150 pLon = QLineF(points.at(minLon).first, points.at(maxLon).first).length();
151 dLat = points.at(minLat).second.distanceTo(points.at(maxLat).second);
152 pLat = QLineF(points.at(minLat).first, points.at(maxLat).first).length();
154 _resolution = (dLon/pLon + dLat/pLat) / 2.0;
156 return true;
159 bool OfflineMap::getImageInfo(const QString &path)
161 QFileInfo ii(_imgPath);
162 if (ii.isRelative())
163 _imgPath = path + "/" + _imgPath;
165 QImageReader img(_imgPath);
166 _size = img.size();
167 if (!_size.isValid()) {
168 qWarning("%s: %s: error reading map image", qPrintable(_name),
169 qPrintable(ii.absoluteFilePath()));
170 return false;
173 return true;
176 bool OfflineMap::getTileInfo(const QStringList &tiles, const QString &path)
178 if (!_size.isValid()) {
179 qWarning("%s: missing total image size (IWH)", qPrintable(_name));
180 return false;
183 if (tiles.isEmpty()) {
184 qWarning("%s: empty tile set", qPrintable(_name));
185 return false;
188 QRegExp rx("_[0-9]+_[0-9]+\\.");
189 for (int i = 0; i < tiles.size(); i++) {
190 if (tiles.at(i).contains(rx)) {
191 _tileName = QString(tiles.at(i)).replace(rx, "_%1_%2.");
193 if (_tar.isOpen()) {
194 QByteArray ba = _tar.file(tiles.at(i));
195 QBuffer buffer(&ba);
196 _tileSize = QImageReader(&buffer).size();
197 } else {
198 _tileName = path + "/" + _tileName;
199 _tileSize = QImageReader(path + "/" + tiles.at(i)).size();
201 if (!_tileSize.isValid()) {
202 qWarning("%s: error retrieving tile size: %s: invalid image",
203 qPrintable(_name), qPrintable(QFileInfo(tiles.at(i))
204 .fileName()));
205 return false;
208 return true;
212 qWarning("%s: invalid tile names", qPrintable(_name));
214 return false;
217 OfflineMap::OfflineMap(const QString &path, QObject *parent) : Map(parent)
219 int errorLine = -2;
220 QList<ReferencePoint> points;
223 _valid = false;
225 QFileInfo fi(path);
226 _name = fi.fileName();
228 QDir dir(path);
229 QFileInfoList mapFiles = dir.entryInfoList(QDir::Files);
230 for (int i = 0; i < mapFiles.count(); i++) {
231 const QString &fileName = mapFiles.at(i).fileName();
232 if (fileName.endsWith(".tar")) {
233 if (!_tar.load(mapFiles.at(i).absoluteFilePath())) {
234 qWarning("%s: %s: error loading tar file", qPrintable(_name),
235 qPrintable(fileName));
236 return;
238 QStringList tarFiles = _tar.files();
239 for (int j = 0; j < tarFiles.size(); j++) {
240 if (tarFiles.at(j).endsWith(".map")) {
241 QByteArray ba = _tar.file(tarFiles.at(j));
242 QBuffer buffer(&ba);
243 errorLine = parseMapFile(buffer, points);
244 break;
247 break;
248 } else if (fileName.endsWith(".map")) {
249 QFile mapFile(mapFiles.at(i).absoluteFilePath());
250 errorLine = parseMapFile(mapFile, points);
251 break;
254 if (errorLine) {
255 if (errorLine == -2)
256 qWarning("%s: no map file found", qPrintable(_name));
257 else if (errorLine == -1)
258 qWarning("%s: error opening map file", qPrintable(_name));
259 else
260 qWarning("%s: map file parse error on line: %d", qPrintable(_name),
261 errorLine);
262 return;
265 if (!computeTransformation(points)) {
266 qWarning("%s: error computing map transformation", qPrintable(_name));
267 return;
269 computeResolution(points);
271 if (_tar.isOpen()) {
272 if (!getTileInfo(_tar.files()))
273 return;
274 } else {
275 QDir set(fi.absoluteFilePath() + "/" + "set");
276 if (set.exists()) {
277 if (!getTileInfo(set.entryList(), set.absolutePath()))
278 return;
279 } else {
280 if (!getImageInfo(fi.absoluteFilePath()))
281 return;
285 _img = 0;
286 _valid = true;
289 void OfflineMap::load()
291 if (_tileSize.isValid())
292 return;
294 _img = new QImage(_imgPath);
295 if (_img->isNull())
296 qWarning("%s: error loading map image", qPrintable(_imgPath));
299 void OfflineMap::unload()
301 if (_img)
302 delete _img;
305 QRectF OfflineMap::bounds() const
307 return QRectF(QPointF(0, 0), _size);
310 qreal OfflineMap::zoomFit(const QSize &size, const QRectF &br)
312 Q_UNUSED(size);
313 Q_UNUSED(br);
315 return 1.0;
318 qreal OfflineMap::resolution(const QPointF &p) const
320 Q_UNUSED(p);
322 return _resolution;
325 qreal OfflineMap::zoomIn()
327 return 1.0;
330 qreal OfflineMap::zoomOut()
332 return 1.0;
335 void OfflineMap::draw(QPainter *painter, const QRectF &rect)
337 if (_tileSize.isValid()) {
338 QPoint tl = QPoint((int)floor(rect.left() / (qreal)_tileSize.width())
339 * _tileSize.width(), (int)floor(rect.top() / _tileSize.height())
340 * _tileSize.height());
342 QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
343 for (int i = 0; i < ceil(s.width() / _tileSize.width()); i++) {
344 for (int j = 0; j < ceil(s.height() / _tileSize.height()); j++) {
345 int x = tl.x() + i * _tileSize.width();
346 int y = tl.y() + j * _tileSize.height();
347 QString tileName(_tileName.arg(QString::number(x),
348 QString::number(y)));
349 QPixmap pixmap;
351 if (_tar.isOpen()) {
352 QString key = _tar.fileName() + "/" + tileName;
353 if (!QPixmapCache::find(key, &pixmap)) {
354 QByteArray ba = _tar.file(tileName);
355 pixmap = QPixmap::fromImage(QImage::fromData(ba));
356 QPixmapCache::insert(key, pixmap);
358 } else
359 pixmap = QPixmap(tileName);
361 if (pixmap.isNull()) {
362 qWarning("%s: error loading tile image", qPrintable(
363 _tileName.arg(QString::number(x), QString::number(y))));
364 painter->fillRect(QRectF(QPoint(x, y), _tileSize),
365 Qt::white);
366 } else
367 painter->drawPixmap(QPoint(x, y), pixmap);
370 } else {
371 if (_img->isNull())
372 painter->fillRect(rect, Qt::white);
373 else {
374 QPoint p = rect.topLeft().toPoint();
375 QImage crop = _img->copy(QRect(p, rect.size().toSize()));
376 painter->drawImage(rect.topLeft(), crop);
381 QPointF OfflineMap::ll2xy(const Coordinates &c) const
383 return _transform.map(c.toMercator());
386 Coordinates OfflineMap::xy2ll(const QPointF &p) const
388 return Coordinates::fromMercator(_transform.inverted().map(p));