3 #include <QPixmapCache>
4 #include <QImageReader>
6 #include <QtConcurrent>
7 #include "common/hash.h"
13 #define MAGIC "FLATPACK1"
15 static bool parseHeader(const QByteArray
&data
, QString
&name
)
17 QList
<QByteArray
> lines
= data
.split('\n');
19 for (int i
= 0; i
< lines
.count(); i
++) {
20 const QByteArray
&line
= lines
.at(i
);
22 QList
<QByteArray
> tokens
= line
.split('=');
23 if (tokens
.size() != 2)
26 QByteArray
key(tokens
.at(0).trimmed());
27 QByteArray
value(tokens
.at(1).trimmed());
33 return !name
.isEmpty();
36 static bool parseLevel(const QByteArray
&data
, int &zoom
, int &tileSize
,
40 int xtsize
= 0, ytsize
= 0;
41 int xtmin
= 0, xtmax
= 0, ytmin
= 0, ytmax
= 0;
42 QList
<QByteArray
> lines
= data
.split('\n');
44 for (int i
= 0; i
< lines
.count(); i
++) {
45 const QByteArray
&line
= lines
.at(i
);
47 QList
<QByteArray
> tokens
= line
.split('=');
48 if (tokens
.size() != 2)
51 QByteArray
key(tokens
.at(0).trimmed());
52 QByteArray
value(tokens
.at(1).trimmed());
56 id
= value
.toInt(&ok
);
57 else if (key
== "xtsize")
58 xtsize
= value
.toInt(&ok
);
59 else if (key
== "ytsize")
60 ytsize
= value
.toInt(&ok
);
61 else if (key
== "xtmin")
62 xtmin
= value
.toInt(&ok
);
63 else if (key
== "xtmax")
64 xtmax
= value
.toInt(&ok
);
65 else if (key
== "ytmin")
66 ytmin
= value
.toInt(&ok
);
67 else if (key
== "ytmax")
68 ytmax
= value
.toInt(&ok
);
74 if (xtsize
<= 0 || ytsize
<= 0 || ytsize
!= xtsize
|| id
< 0)
79 rect
= QRect(QPoint(xtmin
, (1<<zoom
) - ytmax
/*- 1*/),
80 QPoint(xtmax
, (1<<zoom
) - ytmin
/*- 1*/));
86 bool AQMMap::readSize(size_t &size
)
92 while (_file
.getChar(&c
)) {
94 size
= size
* 10 + c
- '0';
104 bool AQMMap::readString(QByteArray
&str
)
110 while (_file
.getChar(&c
)) {
120 bool AQMMap::readFile(File
&file
)
122 if (!readString(file
.name
))
124 if (!readSize(file
.offset
))
130 bool AQMMap::readData(QByteArray
&data
)
137 return _file
.read(data
.data(), size
) == (qint64
)size
;
140 bool AQMMap::readHeader()
142 size_t hdrSize
, numFiles
;
145 if (!readSize(hdrSize
))
147 if (!readSize(numFiles
))
150 QVector
<File
> files(numFiles
);
151 for (size_t i
= 0; i
< numFiles
; i
++) {
152 if (!readFile(files
[i
]))
156 size_t start
= _file
.pos();
157 for (int i
= 0; i
< files
.size(); i
++)
158 files
[i
].offset
+= start
;
161 for (int i
= 0; i
< files
.size(); i
++) {
162 if (files
.at(i
).name
== "V2HEADER") {
163 if (!(_file
.seek(files
.at(i
).offset
) && readData(data
)))
165 if (!parseHeader(data
, _name
))
167 } else if (files
.at(i
).name
== "V2LEVEL") {
171 if (!(_file
.seek(files
.at(i
).offset
) && readData(data
)))
173 if (!parseLevel(data
, zoom
, tileSize
, bounds
))
176 if (_bounds
.isNull()) {
177 int minX
= qMin((1<<zoom
) - 1, qMax(0, bounds
.left()));
178 int minY
= qMin((1<<zoom
) - 1, qMax(0, bounds
.top()));
179 int maxX
= qMin((1<<zoom
) - 1, qMax(0, bounds
.right())) + 1;
180 int maxY
= qMin((1<<zoom
) - 1, qMax(0, bounds
.bottom())) + 1;
181 Coordinates
tl(OSM::tile2ll(QPoint(minX
, minY
), zoom
));
182 tl
.rlat() = -tl
.lat();
183 Coordinates
br(OSM::tile2ll(QPoint(maxX
, maxY
), zoom
));
184 br
.rlat() = -br
.lat();
185 // Workaround of broken zoom levels 0 and 1 due to numerical
187 tl
.rlat() = qMin(tl
.lat(), OSM::BOUNDS
.top());
188 br
.rlat() = qMax(br
.lat(), OSM::BOUNDS
.bottom());
189 _bounds
= RectC(tl
, br
);
191 _zooms
.append(Zoom(zoom
, tileSize
));
192 } else if (files
.at(i
).name
== "@LEVEL") {
202 for (int i
= li
; i
< files
.size(); i
++) {
203 if (files
.at(i
).name
== "@LEVEL")
205 else if (files
.at(i
).name
== "#END")
208 if (level
< 0 || level
> _zooms
.size() - 1)
211 QList
<QByteArray
> ba(files
.at(i
).name
.split('_'));
215 int x
= ba
.at(0).toInt(&xok
);
216 int y
= ba
.at(1).toInt(&yok
);
219 int zoom
= _zooms
.at(level
).zoom
;
220 _zooms
[level
].tiles
.insert(QPoint(x
, (1<<zoom
) - y
/*- 1*/),
228 AQMMap::AQMMap(const QString
&fileName
, QObject
*parent
)
229 : Map(fileName
, parent
), _file(fileName
), _zoom(0), _mapRatio(1.0),
232 char magic
[sizeof(MAGIC
) - 1];
234 if (!_file
.open(QIODevice::ReadOnly
)) {
235 _errorString
= _file
.errorString();
239 if (_file
.read(magic
, sizeof(magic
)) != sizeof(magic
)
240 || memcmp(magic
, MAGIC
, sizeof(magic
))) {
241 _errorString
= "Not an AlpineQuest map";
246 _errorString
= "AQM file format error";
255 void AQMMap::load(const Projection
&in
, const Projection
&out
,
256 qreal deviceRatio
, bool hidpi
)
261 _mapRatio
= hidpi
? deviceRatio
: 1.0;
262 _file
.open(QIODevice::ReadOnly
);
265 void AQMMap::unload()
270 QRectF
AQMMap::bounds()
272 return QRectF(ll2xy(_bounds
.topLeft()), ll2xy(_bounds
.bottomRight()));
275 int AQMMap::zoomFit(const QSize
&size
, const RectC
&rect
)
278 _zoom
= _zooms
.size() - 1;
280 for (int i
= 1; i
< _zooms
.count(); i
++) {
282 QRect
sbr(QPoint(ll2xy(rect
.topLeft()).toPoint()),
283 QPoint(ll2xy(rect
.bottomRight()).toPoint()));
284 if (sbr
.size().width() >= size
.width() || sbr
.size().height()
295 qreal
AQMMap::resolution(const QRectF
&rect
)
297 const Zoom
&z
= _zooms
.at(_zoom
);
298 return OSM::resolution(rect
.center(), z
.zoom
, tileSize());
303 _zoom
= qMin(_zoom
+ 1, _zooms
.size() - 1);
307 int AQMMap::zoomOut()
309 _zoom
= qMax(_zoom
- 1, 0);
313 QPointF
AQMMap::ll2xy(const Coordinates
&c
)
315 const Zoom
&z
= _zooms
.at(_zoom
);
316 qreal scale
= OSM::zoom2scale(z
.zoom
, z
.tileSize
);
317 QPointF m
= OSM::ll2m(c
);
318 return QPointF(m
.x() / scale
, m
.y() / -scale
) / _mapRatio
;
321 Coordinates
AQMMap::xy2ll(const QPointF
&p
)
323 const Zoom
&z
= _zooms
.at(_zoom
);
324 qreal scale
= OSM::zoom2scale(z
.zoom
, z
.tileSize
);
325 return OSM::m2ll(QPointF(p
.x() * scale
, -p
.y() * scale
) * _mapRatio
);
328 qreal
AQMMap::tileSize() const
330 return (_zooms
.at(_zoom
).tileSize
/ _mapRatio
);
333 QByteArray
AQMMap::tileData(const QPoint
&tile
)
335 const Zoom
&z
= _zooms
.at(_zoom
);
338 size_t offset
= z
.tiles
.value(tile
);
339 if (!(offset
&& _file
.seek(offset
) && readData(ba
)))
345 void AQMMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags flags
)
348 const Zoom
&z
= _zooms
.at(_zoom
);
349 qreal scale
= OSM::zoom2scale(z
.zoom
, z
.tileSize
);
350 QPoint tile
= OSM::mercator2tile(QPointF(rect
.topLeft().x() * scale
,
351 -rect
.topLeft().y() * scale
) * _mapRatio
, z
.zoom
);
352 Coordinates
ctl(OSM::tile2ll(tile
, z
.zoom
));
353 QPointF
tl(ll2xy(Coordinates(ctl
.lon(), -ctl
.lat())));
354 QSizeF
s(rect
.right() - tl
.x(), rect
.bottom() - tl
.y());
355 int width
= ceil(s
.width() / tileSize());
356 int height
= ceil(s
.height() / tileSize());
358 QList
<DataTile
> tiles
;
360 for (int i
= 0; i
< width
; i
++) {
361 for (int j
= 0; j
< height
; j
++) {
363 QPoint
t(tile
.x() + i
, tile
.y() + j
);
364 QString key
= path() + "-" + QString::number(z
.zoom
) + "_"
365 + QString::number(t
.x()) + "_" + QString::number(t
.y());
367 if (QPixmapCache::find(key
, &pm
)) {
368 QPointF
tp(tl
.x() + (t
.x() - tile
.x()) * tileSize(),
369 tl
.y() + (t
.y() - tile
.y()) * tileSize());
370 drawTile(painter
, pm
, tp
);
372 tiles
.append(DataTile(t
, tileData(t
), key
));
376 QFuture
<void> future
= QtConcurrent::map(tiles
, &DataTile::load
);
377 future
.waitForFinished();
379 for (int i
= 0; i
< tiles
.size(); i
++) {
380 const DataTile
&mt
= tiles
.at(i
);
381 QPixmap
pm(mt
.pixmap());
385 QPixmapCache::insert(mt
.key(), pm
);
387 QPointF
tp(tl
.x() + (mt
.xy().x() - tile
.x()) * tileSize(),
388 tl
.y() + (mt
.xy().y() - tile
.y()) * tileSize());
389 drawTile(painter
, pm
, tp
);
393 void AQMMap::drawTile(QPainter
*painter
, QPixmap
&pixmap
, QPointF
&tp
)
395 pixmap
.setDevicePixelRatio(_mapRatio
);
396 painter
->drawPixmap(tp
, pixmap
);
399 Map
*AQMMap::create(const QString
&path
, const Projection
&proj
, bool *isDir
)
406 return new AQMMap(path
);
410 QDebug
operator<<(QDebug dbg
, const AQMMap::File
&file
)
412 dbg
.nospace() << "File(" << file
.name
<< ", " << file
.offset
<< ")";
416 QDebug
operator<<(QDebug dbg
, const AQMMap::Zoom
&zoom
)
418 dbg
.nospace() << "Zoom(" << zoom
.zoom
<< ", " << zoom
.tileSize
<< ", "
419 << zoom
.tiles
<< ")";
422 #endif // QT_NO_DEBUG