6 #include <QPixmapCache>
7 #include <QImageReader>
9 #include <QtConcurrent>
10 #include "common/util.h"
12 #include "mbtilesmap.h"
15 #define META_TYPE(type) static_cast<QMetaType::Type>(type)
20 MBTile(int zoom
, int scaledSize
, const QPoint
&xy
, const QByteArray
&data
,
21 const QString
&key
) : _zoom(zoom
), _scaledSize(scaledSize
), _xy(xy
),
22 _data(data
), _key(key
) {}
24 const QPoint
&xy() const {return _xy
;}
25 const QString
&key() const {return _key
;}
26 const QPixmap
&pixmap() const {return _pixmap
;}
29 QByteArray
z(QString::number(_zoom
).toLatin1());
31 QBuffer
buffer(&_data
);
32 QImageReader
reader(&buffer
, z
);
34 reader
.setScaledSize(QSize(_scaledSize
, _scaledSize
));
35 _pixmap
= QPixmap::fromImage(reader
.read());
48 MBTilesMap::MBTilesMap(const QString
&fileName
, QObject
*parent
)
49 : Map(fileName
, parent
), _mapRatio(1.0), _tileRatio(1.0), _scalable(false),
50 _scaledSize(0), _valid(false)
52 if (!Util::isSQLiteDB(fileName
, _errorString
))
55 _db
= QSqlDatabase::addDatabase("QSQLITE", fileName
);
56 _db
.setDatabaseName(fileName
);
57 _db
.setConnectOptions("QSQLITE_OPEN_READONLY");
60 _errorString
= _db
.lastError().text();
64 QSqlRecord r
= _db
.record("tiles");
66 || r
.field(0).name() != "zoom_level"
67 || META_TYPE(r
.field(0).type()) != QMetaType::Int
68 || r
.field(1).name() != "tile_column"
69 || META_TYPE(r
.field(1).type()) != QMetaType::Int
70 || r
.field(2).name() != "tile_row"
71 || META_TYPE(r
.field(2).type()) != QMetaType::Int
72 || r
.field(3).name() != "tile_data"
73 || META_TYPE(r
.field(3).type()) != QMetaType::QByteArray
) {
74 _errorString
= "Invalid table format";
79 QSqlQuery
query("SELECT DISTINCT zoom_level FROM tiles"
80 " ORDER BY zoom_level", _db
);
82 _zooms
.append(query
.value(0).toInt());
83 if (_zooms
.isEmpty()) {
84 _errorString
= "Empty tile set";
87 if (_zooms
.first() < 0) {
88 _errorString
= "Invalid zoom levels";
92 _zi
= _zooms
.size() - 1;
95 int z
= _zooms
.first();
96 QString sql
= QString("SELECT min(tile_column), min(tile_row), "
97 "max(tile_column), max(tile_row) FROM tiles WHERE zoom_level = %1")
99 QSqlQuery
query(sql
, _db
);
102 int minX
= qMin((1<<z
) - 1, qMax(0, query
.value(0).toInt()));
103 int minY
= qMin((1<<z
) - 1, qMax(0, query
.value(1).toInt()));
104 int maxX
= qMin((1<<z
) - 1, qMax(0, query
.value(2).toInt())) + 1;
105 int maxY
= qMin((1<<z
) - 1, qMax(0, query
.value(3).toInt())) + 1;
106 Coordinates
tl(OSM::tile2ll(QPoint(minX
, maxY
), z
));
107 Coordinates
br(OSM::tile2ll(QPoint(maxX
, minY
), z
));
108 // Workaround of broken zoom levels 0 and 1 due to numerical instability
109 tl
.rlat() = qMin(tl
.lat(), OSM::BOUNDS
.top());
110 br
.rlat() = qMax(br
.lat(), OSM::BOUNDS
.bottom());
111 _bounds
= RectC(tl
, br
);
115 QString sql
= QString("SELECT tile_data FROM tiles LIMIT 1");
116 QSqlQuery
query(sql
, _db
);
119 QByteArray data
= query
.value(0).toByteArray();
120 QBuffer
buffer(&data
);
121 QImageReader
reader(&buffer
);
122 QSize
tileSize(reader
.size());
124 if (!tileSize
.isValid() || tileSize
.width() != tileSize
.height()) {
125 _errorString
= "Unsupported/invalid tile images";
128 _tileSize
= tileSize
.width();
132 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'format'", _db
);
134 if (query
.value(0).toString() == "pbf")
137 qWarning("%s: missing tiles format", qPrintable(fileName
));
141 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'name'", _db
);
143 _name
= query
.value(0).toString();
145 qWarning("%s: missing map name", qPrintable(fileName
));
146 _name
= Util::file2name(fileName
);
152 "SELECT value FROM metadata WHERE name = 'tilepixelratio'", _db
);
155 _tileRatio
= query
.value(0).toString().toDouble(&ok
);
157 _errorString
= "Invalid tile pixel ratio";
168 void MBTilesMap::load(const Projection
&in
, const Projection
&out
,
169 qreal deviceRatio
, bool hidpi
)
174 _mapRatio
= hidpi
? deviceRatio
: 1.0;
177 _scaledSize
= _tileSize
* deviceRatio
;
178 _tileRatio
= deviceRatio
;
184 void MBTilesMap::unload()
189 QRectF
MBTilesMap::bounds()
191 return QRectF(ll2xy(_bounds
.topLeft()), ll2xy(_bounds
.bottomRight()));
194 int MBTilesMap::zoomFit(const QSize
&size
, const RectC
&rect
)
197 _zi
= _zooms
.size() - 1;
199 QRectF
tbr(OSM::ll2m(rect
.topLeft()), OSM::ll2m(rect
.bottomRight()));
200 QPointF
sc(tbr
.width() / size
.width(), tbr
.height() / size
.height());
201 int zoom
= OSM::scale2zoom(qMax(sc
.x(), -sc
.y()) / coordinatesRatio(),
205 for (int i
= 1; i
< _zooms
.size(); i
++) {
206 if (_zooms
.at(i
) > zoom
)
215 qreal
MBTilesMap::resolution(const QRectF
&rect
)
217 return OSM::resolution(rect
.center(), _zooms
.at(_zi
), _tileSize
);
220 int MBTilesMap::zoomIn()
222 _zi
= qMin(_zi
+ 1, _zooms
.size() - 1);
226 int MBTilesMap::zoomOut()
228 _zi
= qMax(_zi
- 1, 0);
232 qreal
MBTilesMap::coordinatesRatio() const
234 return _mapRatio
> 1.0 ? _mapRatio
/ _tileRatio
: 1.0;
237 qreal
MBTilesMap::imageRatio() const
239 return _mapRatio
> 1.0 ? _mapRatio
: _tileRatio
;
242 qreal
MBTilesMap::tileSize() const
244 return (_tileSize
/ coordinatesRatio());
247 QByteArray
MBTilesMap::tileData(int zoom
, const QPoint
&tile
) const
249 QSqlQuery
query(_db
);
250 query
.prepare("SELECT tile_data FROM tiles "
251 "WHERE zoom_level=:zoom AND tile_column=:x AND tile_row=:y");
252 query
.bindValue(":zoom", zoom
);
253 query
.bindValue(":x", tile
.x());
254 query
.bindValue(":y", (1<<zoom
) - tile
.y() - 1);
258 return query
.value(0).toByteArray();
263 void MBTilesMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags flags
)
266 int zoom
= _zooms
.at(_zi
);
267 qreal scale
= OSM::zoom2scale(zoom
, _tileSize
);
271 QPoint tile
= OSM::mercator2tile(QPointF(rect
.topLeft().x() * scale
,
272 -rect
.topLeft().y() * scale
) * coordinatesRatio(), zoom
);
273 QPointF
tl(floor(rect
.left() / tileSize())
274 * tileSize(), floor(rect
.top() / tileSize()) * tileSize());
276 QSizeF
s(qMin(rect
.right() - tl
.x(), b
.width()),
277 qMin(rect
.bottom() - tl
.y(), b
.height()));
278 int width
= ceil(s
.width() / tileSize());
279 int height
= ceil(s
.height() / tileSize());
284 for (int i
= 0; i
< width
; i
++) {
285 for (int j
= 0; j
< height
; j
++) {
287 QPoint
t(tile
.x() + i
, tile
.y() + j
);
288 QString key
= path() + "-" + QString::number(zoom
) + "_"
289 + QString::number(t
.x()) + "_" + QString::number(t
.y());
291 if (QPixmapCache::find(key
, &pm
)) {
292 QPointF
tp(qMax(tl
.x(), b
.left()) + (t
.x() - tile
.x())
293 * tileSize(), qMax(tl
.y(), b
.top()) + (t
.y() - tile
.y())
295 drawTile(painter
, pm
, tp
);
297 tiles
.append(MBTile(zoom
, _scaledSize
, t
, tileData(zoom
, t
),
303 QFuture
<void> future
= QtConcurrent::map(tiles
, &MBTile::load
);
304 future
.waitForFinished();
306 for (int i
= 0; i
< tiles
.size(); i
++) {
307 const MBTile
&mt
= tiles
.at(i
);
308 QPixmap
pm(mt
.pixmap());
312 QPixmapCache::insert(mt
.key(), pm
);
314 QPointF
tp(qMax(tl
.x(), b
.left()) + (mt
.xy().x() - tile
.x())
315 * tileSize(), qMax(tl
.y(), b
.top()) + (mt
.xy().y() - tile
.y())
317 drawTile(painter
, pm
, tp
);
321 void MBTilesMap::drawTile(QPainter
*painter
, QPixmap
&pixmap
, QPointF
&tp
)
323 pixmap
.setDevicePixelRatio(imageRatio());
324 painter
->drawPixmap(tp
, pixmap
);
327 QPointF
MBTilesMap::ll2xy(const Coordinates
&c
)
329 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
), _tileSize
);
330 QPointF m
= OSM::ll2m(c
);
331 return QPointF(m
.x() / scale
, m
.y() / -scale
) / coordinatesRatio();
334 Coordinates
MBTilesMap::xy2ll(const QPointF
&p
)
336 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
), _tileSize
);
337 return OSM::m2ll(QPointF(p
.x() * scale
, -p
.y() * scale
)
338 * coordinatesRatio());
341 Map
*MBTilesMap::create(const QString
&path
, const Projection
&proj
, bool *isDir
)
348 return new MBTilesMap(path
);