Optimization
[GPXSee.git] / src / map / rmap.cpp
blob69bd2be8dd9f43470a81e27aa9542f16b9afbc7f
1 #include <QFileInfo>
2 #include <QDataStream>
3 #include <QPixmapCache>
4 #include <QPainter>
5 #include <QRegularExpression>
6 #include <QtEndian>
7 #include "common/rectc.h"
8 #include "common/wgs84.h"
9 #include "common/color.h"
10 #include "calibrationpoint.h"
11 #include "utm.h"
12 #include "pcs.h"
13 #include "rectd.h"
14 #include "rmap.h"
17 #define MAGIC "CompeGPSRasterImage"
19 static CalibrationPoint parseCalibrationPoint(const QString &str)
21 QStringList fields(str.split(","));
22 if (fields.size() != 5)
23 return CalibrationPoint();
25 bool ret1, ret2;
26 PointD xy(fields.at(0).toDouble(&ret1), fields.at(1).toDouble(&ret2));
27 if (!ret1 || !ret2)
28 return CalibrationPoint();
29 PointD pp(fields.at(3).toDouble(&ret1), fields.at(4).toDouble(&ret2));
30 if (!ret1 || !ret2)
31 return CalibrationPoint();
33 return (fields.at(2) == "A")
34 ? CalibrationPoint(xy, Coordinates(pp.x(), pp.y()))
35 : CalibrationPoint(xy, pp);
38 static Projection parseProjection(const QString &str, const GCS &gcs)
40 QStringList fields(str.split(','));
41 if (fields.isEmpty())
42 return Projection();
43 bool ret;
44 int id = fields.at(0).toDouble(&ret);
45 if (!ret)
46 return Projection();
47 int zone;
49 switch (id) {
50 case 0: // UTM
51 if (fields.size() < 4)
52 return Projection();
53 zone = fields.at(2).toInt(&ret);
54 if (!ret)
55 return Projection();
56 if (fields.at(3) == "S")
57 zone = -zone;
58 return Projection(PCS(gcs, Conversion(9807, UTM::setup(zone), 9001)));
59 case 1: // LatLon
60 return Projection(gcs);
61 case 2: // Mercator
62 return Projection(PCS(gcs, Conversion(1024, Projection::Setup(),
63 9001)));
64 case 3: // Transversal Mercator
65 if (fields.size() < 7)
66 return Projection();
67 return Projection(PCS(gcs, Conversion(9807, Projection::Setup(
68 fields.at(3).toDouble(), fields.at(2).toDouble(),
69 fields.at(6).toDouble(), fields.at(5).toDouble(),
70 fields.at(4).toDouble(), NAN, NAN), 9001)));
71 case 4: // Lambert 2SP
72 if (fields.size() < 8)
73 return Projection();
74 return Projection(PCS(gcs, Conversion(9802, Projection::Setup(
75 fields.at(4).toDouble(), fields.at(5).toDouble(), NAN,
76 fields.at(6).toDouble(), fields.at(7).toDouble(),
77 fields.at(3).toDouble(), fields.at(2).toDouble()), 9001)));
78 case 6: // BGN (British National Grid)
79 return Projection(PCS(gcs, Conversion(9807, Projection::Setup(49,
80 -2, 0.999601, 400000, -100000, NAN, NAN), 9001)));
81 case 12: // France Lambert II etendu
82 return Projection(PCS(gcs, Conversion(9801, Projection::Setup(52, 0,
83 0.99987742, 600000, 2200000, NAN, NAN), 9001)));
84 case 14: // Swiss Grid
85 return Projection(PCS(gcs, Conversion(9815, Projection::Setup(
86 46.570866, 7.26225, 1.0, 600000, 200000, 90.0, 90.0), 9001)));
87 case 108: // Dutch RD grid
88 return Projection(PCS(gcs, Conversion(9809, Projection::Setup(
89 52.15616055555555, 5.38763888888889, 0.9999079, 155000, 463000,
90 NAN, NAN), 9001)));
91 case 184: // Swedish Grid
92 return Projection(PCS(gcs, Conversion(9807, Projection::Setup(0,
93 15.808278, 1, 1500000, 0, NAN, NAN), 9001)));
94 default:
95 return Projection();
99 bool RMap::parseIMP(const QByteArray &data)
101 QStringList lines = QString(data).split("\r\n");
102 QVector<CalibrationPoint> calibrationPoints;
103 QString projection, datum;
104 QRegularExpression re("^P[0-9]+=");
106 for (int i = 0; i < lines.count(); i++) {
107 const QString &line = lines.at(i);
109 if (line.startsWith("Projection="))
110 projection = line.split("=").at(1);
111 else if (line.startsWith("Datum="))
112 datum = line.split("=").at(1);
113 else if (line.contains(re)) {
114 QString point(line.split("=").at(1));
115 CalibrationPoint cp(parseCalibrationPoint(point));
116 if (cp.isValid())
117 calibrationPoints.append(cp);
118 else {
119 _errorString = point + ": invalid calibration point";
120 return false;
125 GCS gcs(GCS::gcs(datum));
126 if (gcs.isNull()) {
127 _errorString = datum + ": unknown/invalid datum";
128 return false;
130 _projection = parseProjection(projection, gcs);
131 if (!_projection.isValid()) {
132 _errorString = projection + ": unknown/invalid projection";
133 return false;
136 QList<ReferencePoint> rp;
137 for (int i = 0; i < calibrationPoints.size(); i++)
138 rp.append(calibrationPoints.at(i).rp(_projection));
140 _transform = Transform(rp);
141 if (!_transform.isValid()) {
142 _errorString = _transform.errorString();
143 return false;
146 return true;
149 bool RMap::readPalette(QDataStream &stream, quint32 paletteSize)
151 quint32 bgr;
153 if (paletteSize > 256)
154 return false;
156 _palette.resize(256);
157 for (int i = 0; i < (int)paletteSize; i++) {
158 stream >> bgr;
159 _palette[i] = Color::bgr2rgb(bgr);
162 return (stream.status() == QDataStream::Ok);
165 bool RMap::readZoomLevel(quint64 offset, const QSize &imageSize)
167 if (!_file.seek(offset))
168 return false;
169 QDataStream stream(&_file);
170 stream.setByteOrder(QDataStream::LittleEndian);
172 _zooms.append(Zoom());
173 Zoom &zoom = _zooms.last();
175 quint32 width, height;
176 stream >> width >> height;
177 zoom.size = QSize(width, -(int)height);
178 stream >> width >> height;
179 zoom.dim = QSize(width, height);
180 zoom.scale = QPointF((qreal)zoom.size.width() / (qreal)imageSize.width(),
181 (qreal)zoom.size.height() / (qreal)imageSize.height());
182 if (stream.status() != QDataStream::Ok)
183 return false;
185 zoom.tiles.resize(zoom.dim.width() * zoom.dim.height());
186 for (int j = 0; j < zoom.tiles.size(); j++)
187 stream >> zoom.tiles[j];
189 return (stream.status() == QDataStream::Ok);
192 bool RMap::readZooms(QDataStream &stream, const QSize &imageSize)
194 qint32 zoomCount;
195 stream >> zoomCount;
196 if (!(stream.status() == QDataStream::Ok && zoomCount))
197 return false;
199 QVector<quint64> zoomOffsets(zoomCount);
200 for (int i = 0; i < zoomCount; i++)
201 stream >> zoomOffsets[i];
202 if (stream.status() != QDataStream::Ok)
203 return false;
205 for (int i = 0; i < zoomOffsets.size(); i++)
206 if (!readZoomLevel(zoomOffsets.at(i), imageSize))
207 return false;
209 return true;
212 QByteArray RMap::readIMP(quint64 IMPOffset)
214 if (!_file.seek(IMPOffset))
215 return QByteArray();
217 QDataStream stream(&_file);
218 stream.setByteOrder(QDataStream::LittleEndian);
220 quint32 IMPSize, unknown;
221 stream >> unknown >> IMPSize;
222 if (stream.status() != QDataStream::Ok)
223 return QByteArray();
225 QByteArray IMP;
226 IMP.resize(IMPSize);
227 return (stream.readRawData(IMP.data(), IMP.size()) == IMP.size())
228 ? IMP : QByteArray();
231 bool RMap::readHeader(QDataStream &stream, Header &hdr)
233 quint32 subtype, obfuscated;
234 char magic[sizeof(MAGIC) - 1];
236 if (stream.readRawData(magic, sizeof(magic)) != sizeof(magic)
237 || memcmp(MAGIC, magic, sizeof(magic))) {
238 _errorString = "Not a raster RMap file";
239 return false;
242 stream >> hdr.type;
243 if (hdr.type > 5)
244 stream >> subtype;
245 if (hdr.type > 6)
246 stream >> obfuscated;
247 else
248 obfuscated = 0;
249 stream >> hdr.width >> hdr.height >> hdr.bpp >> hdr.unknown >> hdr.tileWidth
250 >> hdr.tileHeight >> hdr.IMPOffset >> hdr.paletteSize;
251 if (stream.status() != QDataStream::Ok) {
252 _errorString = "Error reading RMap header";
253 return false;
256 if (hdr.type < 4 || hdr.type > 10) {
257 _errorString = QString::number(hdr.type) + ": unsupported map type";
258 return false;
260 if (obfuscated) {
261 _errorString = "Obfuscated maps not supported";
262 return false;
265 return true;
268 RMap::RMap(const QString &fileName, QObject *parent)
269 : Map(fileName, parent), _file(fileName), _mapRatio(1.0), _zoom(0),
270 _valid(false)
272 if (!_file.open(QIODevice::ReadOnly)) {
273 _errorString = _file.errorString();
274 return;
277 QDataStream stream(&_file);
278 stream.setByteOrder(QDataStream::LittleEndian);
280 Header hdr;
281 if (!readHeader(stream, hdr))
282 return;
284 _tileSize = QSize(hdr.tileWidth, hdr.tileHeight);
286 if (hdr.paletteSize && !readPalette(stream, hdr.paletteSize)) {
287 _errorString = "Error reading color palette";
288 return;
291 if (!readZooms(stream, QSize(hdr.width, -(int)hdr.height))) {
292 _errorString = "Error reading zoom levels";
293 return;
296 QByteArray IMP(readIMP(hdr.IMPOffset));
297 if (IMP.isNull()) {
298 _errorString = "Error reading IMP data";
299 return;
301 _valid = parseIMP(IMP);
303 _file.close();
306 QRectF RMap::bounds()
308 return QRectF(QPointF(0, 0), _zooms.at(_zoom).size / _mapRatio);
311 int RMap::zoomFit(const QSize &size, const RectC &rect)
313 if (!rect.isValid())
314 _zoom = 0;
315 else {
316 RectD prect(rect, _projection);
317 QRectF sbr(_transform.proj2img(prect.topLeft()),
318 _transform.proj2img(prect.bottomRight()));
320 for (int i = 0; i < _zooms.size(); i++) {
321 _zoom = i;
322 const Zoom &z = _zooms.at(i);
323 if (sbr.size().width() * z.scale.x() <= size.width()
324 && sbr.size().height() * z.scale.y() <= size.height())
325 break;
329 return _zoom;
332 int RMap::zoomIn()
334 _zoom = qMax(_zoom - 1, 0);
335 return _zoom;
338 int RMap::zoomOut()
340 _zoom = qMin(_zoom + 1, _zooms.size() - 1);
341 return _zoom;
344 QPointF RMap::ll2xy(const Coordinates &c)
346 const QPointF &scale = _zooms.at(_zoom).scale;
347 QPointF p(_transform.proj2img(_projection.ll2xy(c)));
348 return QPointF(p.x() * scale.x(), p.y() * scale.y()) / _mapRatio;
351 Coordinates RMap::xy2ll(const QPointF &p)
353 const QPointF &scale = _zooms.at(_zoom).scale;
354 return _projection.xy2ll(_transform.img2proj(QPointF(p.x() / scale.x(),
355 p.y() / scale.y()) * _mapRatio));
358 void RMap::load(const Projection &in, const Projection &out, qreal deviceRatio,
359 bool hidpi)
361 Q_UNUSED(in);
362 Q_UNUSED(out);
364 _mapRatio = hidpi ? deviceRatio : 1.0;
365 _file.open(QIODevice::ReadOnly);
368 void RMap::unload()
370 _file.close();
373 QPixmap RMap::tile(int x, int y)
375 const Zoom &zoom = _zooms.at(_zoom);
377 qint32 index = y / _tileSize.height() * zoom.dim.width()
378 + x / _tileSize.width();
379 if (index > zoom.tiles.size())
380 return QPixmap();
382 quint64 offset = zoom.tiles.at(index);
383 if (!_file.seek(offset))
384 return QPixmap();
385 QDataStream stream(&_file);
386 stream.setByteOrder(QDataStream::LittleEndian);
387 quint32 tag;
388 stream >> tag;
389 if (stream.status() != QDataStream::Ok)
390 return QPixmap();
392 if (tag == 2) {
393 if (_palette.isEmpty())
394 return QPixmap();
395 quint32 width, height, size;
396 stream >> width >> height >> size;
397 QSize tileSize(width, -(int)height);
399 quint32 bes = qToBigEndian(tileSize.width() * tileSize.height());
400 QByteArray ba;
401 ba.resize(sizeof(bes) + size);
402 memcpy(ba.data(), &bes, sizeof(bes));
404 if (stream.readRawData(ba.data() + sizeof(bes), size) != (int)size)
405 return QPixmap();
406 QByteArray uba = qUncompress(ba);
407 if (uba.size() < tileSize.width() * tileSize.height())
408 return QPixmap();
409 QImage img((const uchar*)uba.constData(), tileSize.width(),
410 tileSize.height(), QImage::Format_Indexed8);
411 img.setColorTable(_palette);
413 return QPixmap::fromImage(img);
414 } else if (tag == 7) {
415 quint32 len;
416 stream >> len;
418 QByteArray ba;
419 ba.resize(len);
420 if (stream.readRawData(ba.data(), ba.size()) != ba.size())
421 return QPixmap();
423 QImage img(QImage::fromData(ba, "JPG"));
424 return QPixmap::fromImage(img);
425 } else
426 return QPixmap();
429 void RMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
431 Q_UNUSED(flags);
433 QSizeF ts(_tileSize.width() / _mapRatio, _tileSize.height() / _mapRatio);
434 QPointF tl(floor(rect.left() / ts.width()) * ts.width(),
435 floor(rect.top() / ts.height()) * ts.height());
437 QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
438 for (int i = 0; i < ceil(s.width() / ts.width()); i++) {
439 for (int j = 0; j < ceil(s.height() / ts.height()); j++) {
440 int x = round(tl.x() * _mapRatio + i * _tileSize.width());
441 int y = round(tl.y() * _mapRatio + j * _tileSize.height());
443 QPixmap pixmap;
444 QString key = path() + "/" + QString::number(_zoom) + "_"
445 + QString::number(x) + "_" + QString::number(y);
446 if (!QPixmapCache::find(key, &pixmap)) {
447 pixmap = tile(x, y);
448 if (!pixmap.isNull())
449 QPixmapCache::insert(key, pixmap);
452 if (pixmap.isNull())
453 qWarning("%s: error loading tile image", qPrintable(key));
454 else {
455 pixmap.setDevicePixelRatio(_mapRatio);
456 QPointF tp(tl.x() + i * ts.width(), tl.y() + j * ts.height());
457 painter->drawPixmap(tp, pixmap);
463 Map *RMap::create(const QString &path, bool *isDir)
465 if (isDir)
466 *isDir = false;
468 return new RMap(path);