3 #include <QPixmapCache>
5 #include <QRegularExpression>
7 #include "common/rectc.h"
8 #include "common/wgs84.h"
9 #include "common/color.h"
10 #include "calibrationpoint.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();
26 PointD
xy(fields
.at(0).toDouble(&ret1
), fields
.at(1).toDouble(&ret2
));
28 return CalibrationPoint();
29 PointD
pp(fields
.at(3).toDouble(&ret1
), fields
.at(4).toDouble(&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(','));
44 int id
= fields
.at(0).toDouble(&ret
);
51 if (fields
.size() < 4)
53 zone
= fields
.at(2).toInt(&ret
);
56 if (fields
.at(3) == "S")
58 return Projection(PCS(gcs
, Conversion(9807, UTM::setup(zone
), 9001)));
60 return Projection(gcs
);
62 return Projection(PCS(gcs
, Conversion(1024, Projection::Setup(),
64 case 3: // Transversal Mercator
65 if (fields
.size() < 7)
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)
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,
91 case 184: // Swedish Grid
92 return Projection(PCS(gcs
, Conversion(9807, Projection::Setup(0,
93 15.808278, 1, 1500000, 0, NAN
, NAN
), 9001)));
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
));
117 calibrationPoints
.append(cp
);
119 _errorString
= point
+ ": invalid calibration point";
125 GCS
gcs(GCS::gcs(datum
));
127 _errorString
= datum
+ ": unknown/invalid datum";
130 _projection
= parseProjection(projection
, gcs
);
131 if (!_projection
.isValid()) {
132 _errorString
= projection
+ ": unknown/invalid projection";
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();
149 bool RMap::readPalette(QDataStream
&stream
, quint32 paletteSize
)
153 if (paletteSize
> 256)
156 _palette
.resize(256);
157 for (int i
= 0; i
< (int)paletteSize
; i
++) {
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
))
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
)
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
)
196 if (!(stream
.status() == QDataStream::Ok
&& zoomCount
))
199 QVector
<quint64
> zoomOffsets(zoomCount
);
200 for (int i
= 0; i
< zoomCount
; i
++)
201 stream
>> zoomOffsets
[i
];
202 if (stream
.status() != QDataStream::Ok
)
205 for (int i
= 0; i
< zoomOffsets
.size(); i
++)
206 if (!readZoomLevel(zoomOffsets
.at(i
), imageSize
))
212 QByteArray
RMap::readIMP(quint64 IMPOffset
)
214 if (!_file
.seek(IMPOffset
))
217 QDataStream
stream(&_file
);
218 stream
.setByteOrder(QDataStream::LittleEndian
);
220 quint32 IMPSize
, unknown
;
221 stream
>> unknown
>> IMPSize
;
222 if (stream
.status() != QDataStream::Ok
)
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";
246 stream
>> obfuscated
;
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";
256 if (hdr
.type
< 4 || hdr
.type
> 10) {
257 _errorString
= QString::number(hdr
.type
) + ": unsupported map type";
261 _errorString
= "Obfuscated maps not supported";
268 RMap::RMap(const QString
&fileName
, QObject
*parent
)
269 : Map(fileName
, parent
), _file(fileName
), _mapRatio(1.0), _zoom(0),
272 if (!_file
.open(QIODevice::ReadOnly
)) {
273 _errorString
= _file
.errorString();
277 QDataStream
stream(&_file
);
278 stream
.setByteOrder(QDataStream::LittleEndian
);
281 if (!readHeader(stream
, hdr
))
284 _tileSize
= QSize(hdr
.tileWidth
, hdr
.tileHeight
);
286 if (hdr
.paletteSize
&& !readPalette(stream
, hdr
.paletteSize
)) {
287 _errorString
= "Error reading color palette";
291 if (!readZooms(stream
, QSize(hdr
.width
, -(int)hdr
.height
))) {
292 _errorString
= "Error reading zoom levels";
296 QByteArray
IMP(readIMP(hdr
.IMPOffset
));
298 _errorString
= "Error reading IMP data";
301 _valid
= parseIMP(IMP
);
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
)
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
++) {
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())
334 _zoom
= qMax(_zoom
- 1, 0);
340 _zoom
= qMin(_zoom
+ 1, _zooms
.size() - 1);
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
,
364 _mapRatio
= hidpi
? deviceRatio
: 1.0;
365 _file
.open(QIODevice::ReadOnly
);
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())
382 quint64 offset
= zoom
.tiles
.at(index
);
383 if (!_file
.seek(offset
))
385 QDataStream
stream(&_file
);
386 stream
.setByteOrder(QDataStream::LittleEndian
);
389 if (stream
.status() != QDataStream::Ok
)
393 if (_palette
.isEmpty())
395 quint32 width
, height
, size
;
396 stream
>> width
>> height
>> size
;
397 QSize
tileSize(width
, -(int)height
);
399 quint32 bes
= qToBigEndian(tileSize
.width() * tileSize
.height());
401 ba
.resize(sizeof(bes
) + size
);
402 memcpy(ba
.data(), &bes
, sizeof(bes
));
404 if (stream
.readRawData(ba
.data() + sizeof(bes
), size
) != (int)size
)
406 QByteArray uba
= qUncompress(ba
);
407 if (uba
.size() < tileSize
.width() * tileSize
.height())
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) {
420 if (stream
.readRawData(ba
.data(), ba
.size()) != ba
.size())
423 QImage
img(QImage::fromData(ba
, "JPG"));
424 return QPixmap::fromImage(img
);
429 void RMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags 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());
444 QString key
= path() + "/" + QString::number(_zoom
) + "_"
445 + QString::number(x
) + "_" + QString::number(y
);
446 if (!QPixmapCache::find(key
, &pixmap
)) {
448 if (!pixmap
.isNull())
449 QPixmapCache::insert(key
, pixmap
);
453 qWarning("%s: error loading tile image", qPrintable(key
));
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
)
468 return new RMap(path
);