Improved error reporting
[GPXSee.git] / src / map / qctmap.cpp
blob6d05ef24941675ac376544efc0e168c2f088fc17
1 #include <cstring>
2 #include <QDataStream>
3 #include <QPixmapCache>
4 #include <QPainter>
5 #include "common/util.h"
6 #include "common/color.h"
7 #include "qctmap.h"
9 #define TILE_SIZE 64
10 #define TILE_PIXELS (TILE_SIZE * TILE_SIZE)
11 #define MAGIC 0x1423D5FF
13 static quint8 bpp(quint8 colours)
15 if (colours <= 2)
16 return 1;
17 if (colours <= 4)
18 return 2;
19 if (colours <= 8)
20 return 3;
21 if (colours <= 16)
22 return 4;
23 if (colours <= 32)
24 return 5;
25 if (colours <= 64)
26 return 6;
27 if (colours <= 128)
28 return 7;
30 return 8;
33 static bool validateTable(const QVector<quint8> &table)
35 int delta;
37 for (int i = 0; i < table.size(); i++) {
38 if (table.at(i) == 128) {
39 if (i + 2 >= table.size())
40 return false;
41 delta = 65537 - (256 * table.at(i+2) + table.at(i+1)) + 2;
42 if (i + delta >= table.size())
43 return false;
44 i += 2;
45 } else if (table.at(i) > 128) {
46 delta = 257 - table.at(i);
47 if (i + delta >= table.size())
48 return false;
52 return true;
55 static bool createTable(QDataStream &stream, QVector<quint8> &table)
57 int idx = 0;
58 int colours = 0;
59 int branches = 0;
61 table.reserve(256);
63 while (stream.status() == QDataStream::Ok && colours <= branches) {
64 table.resize(table.size() + 1);
65 stream >> table[idx];
67 if (table[idx] == 128) {
68 table.resize(table.size() + 2);
69 stream >> table[++idx];
70 stream >> table[++idx];
71 branches++;
72 } else if (table[idx] > 128)
73 branches++;
74 else
75 colours++;
77 idx++;
80 return (stream.status() == QDataStream::Ok);
83 static bool huffman(QDataStream &stream, quint8 tileData[TILE_PIXELS])
85 QVector<quint8> table;
86 if (!createTable(stream, table))
87 return false;
89 if (table.size() == 1) {
90 memset(tileData, table[0], TILE_PIXELS);
91 } else {
92 if (!validateTable(table))
93 return false;
95 const quint8 *tp = table.constData();
96 int bitsLeft = 8;
97 int bitVal;
98 quint8 val;
100 stream >> val;
102 for (int pixelnum = 0; pixelnum < TILE_PIXELS; ) {
103 if (*tp < 128) {
104 tileData[pixelnum++] = *tp;
105 tp = table.constData();
106 } else {
107 bitVal = (val & 1);
109 val >>= 1;
110 bitsLeft--;
111 if (bitsLeft == 0) {
112 stream >> val;
113 bitsLeft = 8;
116 if (bitVal == 0) {
117 if (*tp == 128)
118 tp += 2;
119 tp++;
120 } else {
121 if (*tp > 128)
122 tp += 257 - (*tp);
123 else if (*tp == 128)
124 tp += 65537 - (256 * tp[2] + tp[1]) + 2;
130 return (stream.status() == QDataStream::Ok);
133 static bool pixelPacking(QDataStream &stream, quint8 tileData[TILE_PIXELS],
134 quint8 colours)
136 quint8 shift = bpp(colours);
137 quint32 mask = (1 << shift) - 1;
138 int wordSize = 32 / shift;
139 quint8 paletteIndex[256];
141 for (quint8 i = 0; i < colours; i++)
142 stream >> paletteIndex[i];
144 for (int pixelnum = 0; pixelnum < TILE_PIXELS; ) {
145 quint32 colour, val;
146 stream >> val;
148 for (int runs = 0; runs < wordSize; runs++) {
149 colour = val & mask;
150 val = val >> shift;
151 tileData[pixelnum++] = paletteIndex[colour];
155 return (stream.status() == QDataStream::Ok);
158 static bool rle(QDataStream &stream, quint8 tileData[TILE_PIXELS],
159 quint8 colours)
161 quint8 bits = bpp(colours);
162 quint8 paletteMask = (1 << bits) - 1;
163 quint8 paletteIndex[256];
164 quint8 val;
166 for (quint8 i = 0; i < colours; i++)
167 stream >> paletteIndex[i];
169 for (int pixelnum = 0; pixelnum < TILE_PIXELS; ) {
170 stream >> val;
172 quint8 colour = val & paletteMask;
173 quint8 runs = val >> bits;
175 while (runs-- > 0)
176 tileData[pixelnum++] = paletteIndex[colour];
179 return (stream.status() == QDataStream::Ok);
182 static bool readString(QDataStream &stream, quint32 offset, QString &str)
184 char c;
185 QByteArray ba;
187 if (!stream.device()->seek(offset))
188 return false;
190 while (stream.readRawData(&c, 1) == 1) {
191 if (c)
192 ba.append(c);
193 else {
194 str = QString::fromUtf8(ba);
195 return true;
199 return false;
202 bool QCTMap::readName(QDataStream &stream)
204 quint32 title, name;
206 stream >> title >> name;
207 if (stream.status() != QDataStream::Ok)
208 return false;
210 if (name) {
211 if (!readString(stream, name, _name))
212 return false;
213 } else if (title) {
214 if (!readString(stream, title, _name))
215 return false;
216 } else
217 _name = Util::file2name(path());
219 return true;
222 bool QCTMap::readSize(QDataStream &stream)
224 stream >> _cols >> _rows;
225 return (stream.status() == QDataStream::Ok);
228 bool QCTMap::readDatumShift(QDataStream &stream)
230 quint32 ext, shift;
232 if (!stream.device()->seek(0x54))
233 return false;
234 stream >> ext;
235 if (stream.status() != QDataStream::Ok)
236 return false;
237 if (!ext)
238 return true;
239 if (!stream.device()->seek(ext + 4))
240 return false;
241 stream >> shift;
242 if (stream.status() != QDataStream::Ok)
243 return false;
244 if (!shift)
245 return true;
246 if (!stream.device()->seek(shift))
247 return false;
248 stream >> _shiftN >> _shiftE;
250 return (stream.status() == QDataStream::Ok);
253 bool QCTMap::readHeader(QDataStream &stream)
255 quint32 magic, version;
256 stream >> magic >> version;
258 if (stream.status() != QDataStream::Ok || magic != MAGIC) {
259 _errorString = "Not a QCT map";
260 return false;
262 if (version == 0x20000001) {
263 _errorString = "QC3 files not supported";
264 return false;
266 if (!readSize(stream)) {
267 _errorString = "Error reading map dimensions";
268 return false;
270 if (!readName(stream)) {
271 _errorString = "Error reading map name";
272 return false;
274 if (!readDatumShift(stream)) {
275 _errorString = "Error reading datum shift";
276 return false;
279 return true;
282 bool QCTMap::readGeoRef(QDataStream &stream)
284 if (!stream.device()->seek(0x60))
285 return false;
287 stream >> _eas >> _easY >> _easX >> _easYY >> _easXY >> _easXX >> _easYYY
288 >> _easYYX >> _easXXY >> _easXXX >> _nor >> _norY >> _norX >> _norYY
289 >> _norXY >> _norXX >> _norYYY >> _norYYX >> _norXXY >> _norXXX;
290 stream >> _lat >> _latX >> _latY >> _latXX >> _latXY >> _latYY >> _latXXX
291 >> _latXXY >> _latXYY >> _latYYY >> _lon >> _lonX >> _lonY >> _lonXX
292 >> _lonXY >> _lonYY >> _lonXXX >> _lonXXY >> _lonXYY >> _lonYYY;
294 return (stream.status() == QDataStream::Ok);
297 bool QCTMap::readPalette(QDataStream &stream)
299 if (!stream.device()->seek(0x01A0))
300 return false;
302 _palette.resize(256);
304 quint32 bgr;
305 for (int i = 0; i < _palette.size(); i++) {
306 stream >> bgr;
307 _palette[i] = Color::bgr2rgb(bgr);
310 return (stream.status() == QDataStream::Ok);
313 bool QCTMap::readIndex(QDataStream &stream)
315 if (!stream.device()->seek(0x45A0))
316 return false;
318 _index.resize(_cols * _rows);
319 for (int i = 0; i < _cols * _rows; i++)
320 stream >> _index[i];
322 return (stream.status() == QDataStream::Ok);
325 QCTMap::QCTMap(const QString &fileName, QObject *parent)
326 : Map(fileName, parent), _file(fileName), _shiftE(0), _shiftN(0),
327 _mapRatio(1.0), _valid(false)
329 if (!_file.open(QIODevice::ReadOnly)) {
330 _errorString = _file.errorString();
331 return;
334 QDataStream stream(&_file);
335 stream.setByteOrder(QDataStream::LittleEndian);
337 if (!readHeader(stream))
338 return;
339 if (!readGeoRef(stream)) {
340 _errorString = "Error reading georeference info";
341 return;
343 if (!readPalette(stream)) {
344 _errorString = "Error reading colour palette";
345 return;
347 if (!readIndex(stream)) {
348 _errorString = "Error reading tile index";
349 return;
352 _file.close();
354 _valid = true;
357 void QCTMap::load(const Projection &in, const Projection &out,
358 qreal deviceRatio, bool hidpi)
360 Q_UNUSED(in);
361 Q_UNUSED(out);
363 _mapRatio = hidpi ? deviceRatio : 1.0;
364 _file.open(QIODevice::ReadOnly);
367 void QCTMap::unload()
369 _file.close();
372 QRectF QCTMap::bounds()
374 return QRectF(QPointF(0, 0), QSizeF(_cols * TILE_SIZE, _rows * TILE_SIZE)
375 / _mapRatio);
378 QPointF QCTMap::ll2xy(const Coordinates &c)
380 double lon = c.lon() - _shiftE;
381 double lon2 = lon * lon;
382 double lon3 = lon2 * lon;
383 double lat = c.lat() - _shiftN;
384 double lat2 = lat * lat;
385 double lat3 = lat2 * lat;
387 double x = _easXXX*lon3 + _easXX*lon2 + _easX*lon + _easYYY*lat3
388 + _easYY*lat2 + _easY*lat + _easXXY*lon2*lat
389 + _easYYX*lat2*lon + _easXY*lon*lat + _eas;
390 double y = _norXXX*lon3 + _norXX*lon2 + _norX*lon + _norYYY*lat3
391 + _norYY*lat2 + _norY*lat + _norXXY*lon2*lat
392 + _norYYX*lat2*lon + _norXY*lon*lat + _nor;
394 return QPointF(x - _shiftE, y - _shiftN) / _mapRatio;
397 Coordinates QCTMap::xy2ll(const QPointF &p)
399 qreal x = p.x() * _mapRatio;
400 qreal x2 = x * x;
401 qreal x3 = x2 * x;
402 qreal y = p.y() * _mapRatio;
403 qreal y2 = y * y;
404 qreal y3 = y2 * y;
406 double lon = _lon + _lonX*x + _lonY*y + _lonXX*x2
407 + _lonXY*x*y + _lonYY*y2 + _lonXXX*x3 + _lonXXY*x2*y
408 + _lonXYY*x*y2 + _lonYYY*y3;
409 double lat = _lat + _latX*x + _latY*y + _latXX*x2
410 + _latXY*x*y + _latYY*y2 + _latXXX*x3 + _latXXY*x2 * y
411 + _latXYY*x*y2 + _latYYY*y3;
413 return Coordinates(lon + _shiftE, lat + _shiftN);
416 QPixmap QCTMap::tile(int x, int y)
418 static quint8 rowSeq[] = {
419 0, 32, 16, 48, 8, 40, 24, 56, 4, 36, 20, 52, 12, 44, 28, 60,
420 2, 34, 18, 50, 10, 42, 26, 58, 6, 38, 22, 54, 14, 46, 30, 62,
421 1, 33, 17, 49, 9, 41, 25, 57, 5, 37, 21, 53, 13, 45, 29, 61,
422 3, 35, 19, 51, 11, 43, 27, 59, 7, 39, 23, 55, 15, 47, 31, 63
424 quint8 tileData[TILE_PIXELS], imgData[TILE_PIXELS];
425 quint8 packing;
426 bool ret;
429 if (!_file.seek(_index.at(y * _cols + x)))
430 return QPixmap();
432 QDataStream stream(&_file);
433 stream.setByteOrder(QDataStream::LittleEndian);
435 stream >> packing;
436 if (stream.status() != QDataStream::Ok)
437 return QPixmap();
439 if (packing == 0 || packing == 255)
440 ret = huffman(stream, tileData);
441 else if (packing > 127)
442 ret = pixelPacking(stream, tileData, 256 - packing);
443 else
444 ret = rle(stream, tileData, packing);
446 if (!ret)
447 return QPixmap();
449 for (int i = 0; i < TILE_SIZE; i++)
450 memcpy(imgData + i * TILE_SIZE, tileData + rowSeq[i] * TILE_SIZE,
451 TILE_SIZE);
453 QImage img(imgData, TILE_SIZE, TILE_SIZE, TILE_SIZE,
454 QImage::Format_Indexed8);
455 img.setColorTable(_palette);
457 return QPixmap::fromImage(img);
460 void QCTMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
462 Q_UNUSED(flags);
464 QSizeF ts(TILE_SIZE / _mapRatio, TILE_SIZE / _mapRatio);
465 QPointF tl(floor(rect.left() / ts.width()) * ts.width(),
466 floor(rect.top() / ts.height()) * ts.height());
468 QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
469 for (int i = 0; i < ceil(s.width() / ts.width()); i++) {
470 for (int j = 0; j < ceil(s.height() / ts.height()); j++) {
471 int x = round(tl.x() * _mapRatio + i * TILE_SIZE) / TILE_SIZE;
472 int y = round(tl.y() * _mapRatio + j * TILE_SIZE) / TILE_SIZE;
474 QPixmap pixmap;
475 QString key = path() + "/" + QString::number(x) + "_"
476 + QString::number(y);
477 if (!QPixmapCache::find(key, &pixmap)) {
478 pixmap = tile(x, y);
479 if (!pixmap.isNull())
480 QPixmapCache::insert(key, pixmap);
483 if (pixmap.isNull())
484 qWarning("%s: error loading tile image", qPrintable(key));
485 else {
486 pixmap.setDevicePixelRatio(_mapRatio);
487 QPointF tp(tl.x() + i * ts.width(), tl.y() + j * ts.height());
488 painter->drawPixmap(tp, pixmap);
494 Map *QCTMap::create(const QString &path, const Projection &proj, bool *isDir)
496 Q_UNUSED(proj);
498 if (isDir)
499 *isDir = false;
501 return new QCTMap(path);