6 #include <QPixmapCache>
7 #include <QtConcurrent>
8 #include "common/util.h"
10 #include "mbtilesmap.h"
13 #define META_TYPE(type) static_cast<QMetaType::Type>(type)
15 static RectC
str2bounds(const QString
&str
)
17 QStringList
list(str
.split(','));
21 bool lok
, rok
, bok
, tok
;
22 double left
= list
.at(0).toDouble(&lok
);
23 double bottom
= list
.at(1).toDouble(&bok
);
24 double right
= list
.at(2).toDouble(&rok
);
25 double top
= list
.at(3).toDouble(&tok
);
27 return (lok
&& rok
&& bok
&& tok
)
28 ? RectC(Coordinates(left
, top
), Coordinates(right
, bottom
))
32 bool MBTilesMap::getMinZoom(int &zoom
)
34 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'minzoom'", _db
);
38 zoom
= query
.value(0).toString().toInt(&ok
);
39 if (!ok
|| zoom
< 0) {
40 _errorString
= "Invalid minzoom metadata";
44 qWarning("%s: missing minzoom metadata", qPrintable(path()));
45 zoom
= OSM::ZOOMS
.min();
51 bool MBTilesMap::getMaxZoom(int &zoom
)
53 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'maxzoom'", _db
);
57 zoom
= query
.value(0).toString().toInt(&ok
);
58 if (!ok
&& zoom
< 0) {
59 _errorString
= "Invalid maxzoom metadata";
63 qWarning("%s: missing maxzoom metadata", qPrintable(path()));
64 zoom
= OSM::ZOOMS
.max();
70 bool MBTilesMap::getZooms()
74 if (!(getMinZoom(minZoom
) && getMaxZoom(maxZoom
)))
77 for (int i
= minZoom
; i
<= maxZoom
; i
++) {
78 QString sql
= QString("SELECT zoom_level FROM tiles"
79 " WHERE zoom_level = %1 LIMIT 1").arg(i
);
80 QSqlQuery
query(sql
, _db
);
86 _errorString
= "Empty tile set";
90 _zi
= _zooms
.size() - 1;
95 bool MBTilesMap::getBounds()
97 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'bounds'", _db
);
99 RectC
b(str2bounds(query
.value(0).toString()));
101 _errorString
= "Invalid bounds metadata";
106 qWarning("%s: missing bounds metadata", qPrintable(path()));
108 int z
= _zooms
.first();
109 QString sql
= QString("SELECT min(tile_column), min(tile_row), "
110 "max(tile_column), max(tile_row) FROM tiles WHERE zoom_level = %1")
112 QSqlQuery
query(sql
, _db
);
115 int minX
= qMin((1<<z
) - 1, qMax(0, query
.value(0).toInt()));
116 int minY
= qMin((1<<z
) - 1, qMax(0, query
.value(1).toInt()));
117 int maxX
= qMin((1<<z
) - 1, qMax(0, query
.value(2).toInt())) + 1;
118 int maxY
= qMin((1<<z
) - 1, qMax(0, query
.value(3).toInt())) + 1;
119 Coordinates
tl(OSM::tile2ll(QPoint(minX
, maxY
), z
));
120 Coordinates
br(OSM::tile2ll(QPoint(maxX
, minY
), z
));
121 // Workaround of broken zoom levels 0 and 1 due to numerical instability
122 tl
.rlat() = qMin(tl
.lat(), OSM::BOUNDS
.top());
123 br
.rlat() = qMax(br
.lat(), OSM::BOUNDS
.bottom());
124 _bounds
= RectC(tl
, br
);
130 bool MBTilesMap::getTileSize()
132 QString
sql("SELECT tile_data FROM tiles LIMIT 1");
133 QSqlQuery
query(sql
, _db
);
136 QByteArray data
= query
.value(0).toByteArray();
137 QBuffer
buffer(&data
);
138 /* Explicitly specify the image plugin in case of vector tiles
139 (QTBUG-119910 workaround) */
140 QImageReader
reader(&buffer
, _scalable
? QByteArray("mvt") : QByteArray());
141 QSize
tileSize(reader
.size());
143 if (!tileSize
.isValid() || tileSize
.width() != tileSize
.height()) {
144 _errorString
= "Unsupported/invalid tile images";
148 _tileSize
= tileSize
.width();
153 void MBTilesMap::getTileFormat()
155 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'format'", _db
);
157 if (query
.value(0).toString() == "pbf")
160 qWarning("%s: missing tiles format metadata", qPrintable(path()));
163 void MBTilesMap::getTilePixelRatio()
165 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'tilepixelratio'",
169 double ratio
= query
.value(0).toString().toDouble(&ok
);
175 void MBTilesMap::getName()
177 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'name'", _db
);
179 _name
= query
.value(0).toString();
181 qWarning("%s: missing map name", qPrintable(path()));
182 _name
= Util::file2name(path());
186 MBTilesMap::MBTilesMap(const QString
&fileName
, QObject
*parent
)
187 : Map(fileName
, parent
), _mapRatio(1.0), _tileRatio(1.0), _scalable(false),
188 _scaledSize(0), _valid(false)
190 if (!Util::isSQLiteDB(fileName
, _errorString
))
193 _db
= QSqlDatabase::addDatabase("QSQLITE", fileName
);
194 _db
.setDatabaseName(fileName
);
195 _db
.setConnectOptions("QSQLITE_OPEN_READONLY");
198 _errorString
= _db
.lastError().text();
202 QSqlRecord r
= _db
.record("tiles");
204 || r
.field(0).name() != "zoom_level"
205 || META_TYPE(r
.field(0).type()) != QMetaType::Int
206 || r
.field(1).name() != "tile_column"
207 || META_TYPE(r
.field(1).type()) != QMetaType::Int
208 || r
.field(2).name() != "tile_row"
209 || META_TYPE(r
.field(2).type()) != QMetaType::Int
210 || r
.field(3).name() != "tile_data"
211 || META_TYPE(r
.field(3).type()) != QMetaType::QByteArray
) {
212 _errorString
= "Invalid table format";
231 void MBTilesMap::load(const Projection
&in
, const Projection
&out
,
232 qreal deviceRatio
, bool hidpi
)
237 _mapRatio
= hidpi
? deviceRatio
: 1.0;
240 _scaledSize
= _tileSize
* deviceRatio
;
241 _tileRatio
= deviceRatio
;
247 void MBTilesMap::unload()
253 QRectF
MBTilesMap::bounds()
255 return QRectF(ll2xy(_bounds
.topLeft()), ll2xy(_bounds
.bottomRight()));
258 int MBTilesMap::zoomFit(const QSize
&size
, const RectC
&rect
)
261 _zi
= _zooms
.size() - 1;
263 QRectF
tbr(OSM::ll2m(rect
.topLeft()), OSM::ll2m(rect
.bottomRight()));
264 QPointF
sc(tbr
.width() / size
.width(), tbr
.height() / size
.height());
265 int zoom
= OSM::scale2zoom(qMax(sc
.x(), -sc
.y()) / coordinatesRatio(),
269 for (int i
= 1; i
< _zooms
.size(); i
++) {
270 if (_zooms
.at(i
) > zoom
)
279 qreal
MBTilesMap::resolution(const QRectF
&rect
)
281 return OSM::resolution(rect
.center(), _zooms
.at(_zi
), _tileSize
);
284 int MBTilesMap::zoomIn()
288 _zi
= qMin(_zi
+ 1, _zooms
.size() - 1);
292 int MBTilesMap::zoomOut()
296 _zi
= qMax(_zi
- 1, 0);
300 qreal
MBTilesMap::coordinatesRatio() const
302 return _mapRatio
> 1.0 ? _mapRatio
/ _tileRatio
: 1.0;
305 qreal
MBTilesMap::imageRatio() const
307 return _mapRatio
> 1.0 ? _mapRatio
: _tileRatio
;
310 qreal
MBTilesMap::tileSize() const
312 return (_tileSize
/ coordinatesRatio());
315 QByteArray
MBTilesMap::tileData(int zoom
, const QPoint
&tile
) const
317 QSqlQuery
query(_db
);
318 query
.prepare("SELECT tile_data FROM tiles "
319 "WHERE zoom_level=:zoom AND tile_column=:x AND tile_row=:y");
320 query
.bindValue(":zoom", zoom
);
321 query
.bindValue(":x", tile
.x());
322 query
.bindValue(":y", (1<<zoom
) - tile
.y() - 1);
326 return query
.value(0).toByteArray();
331 bool MBTilesMap::isRunning(const QString
&key
) const
333 for (int i
= 0; i
< _jobs
.size(); i
++) {
334 const QList
<MBTile
> &tiles
= _jobs
.at(i
)->tiles();
335 for (int j
= 0; j
< tiles
.size(); j
++)
336 if (tiles
.at(j
).key() == key
)
343 void MBTilesMap::runJob(MBTilesMapJob
*job
)
347 connect(job
, &MBTilesMapJob::finished
, this, &MBTilesMap::jobFinished
);
351 void MBTilesMap::removeJob(MBTilesMapJob
*job
)
353 _jobs
.removeOne(job
);
357 void MBTilesMap::jobFinished(MBTilesMapJob
*job
)
359 const QList
<MBTile
> &tiles
= job
->tiles();
361 for (int i
= 0; i
< tiles
.size(); i
++) {
362 const MBTile
&mt
= tiles
.at(i
);
363 if (!mt
.pixmap().isNull())
364 QPixmapCache::insert(mt
.key(), mt
.pixmap());
372 void MBTilesMap::cancelJobs(bool wait
)
374 for (int i
= 0; i
< _jobs
.size(); i
++)
375 _jobs
.at(i
)->cancel(wait
);
378 void MBTilesMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags flags
)
380 int zoom
= _zooms
.at(_zi
);
381 qreal scale
= OSM::zoom2scale(zoom
, _tileSize
);
382 QPoint tile
= OSM::mercator2tile(QPointF(rect
.topLeft().x() * scale
,
383 -rect
.topLeft().y() * scale
) * coordinatesRatio(), zoom
);
384 Coordinates
ctl(OSM::tile2ll(tile
, zoom
));
385 QPointF
tl(ll2xy(Coordinates(ctl
.lon(), -ctl
.lat())));
386 QSizeF
s(rect
.right() - tl
.x(), rect
.bottom() - tl
.y());
387 int width
= ceil(s
.width() / tileSize());
388 int height
= ceil(s
.height() / tileSize());
392 for (int i
= 0; i
< width
; i
++) {
393 for (int j
= 0; j
< height
; j
++) {
395 QPoint
t(tile
.x() + i
, tile
.y() + j
);
396 QString key
= path() + "-" + QString::number(zoom
) + "_"
397 + QString::number(t
.x()) + "_" + QString::number(t
.y());
402 if (QPixmapCache::find(key
, &pm
)) {
403 QPointF
tp(tl
.x() + (t
.x() - tile
.x()) * tileSize(),
404 tl
.y() + (t
.y() - tile
.y()) * tileSize());
405 drawTile(painter
, pm
, tp
);
407 tiles
.append(MBTile(zoom
, _scaledSize
, t
, tileData(zoom
, t
),
413 if (!tiles
.isEmpty()) {
414 if (flags
& Map::Block
|| !_scalable
) {
415 QFuture
<void> future
= QtConcurrent::map(tiles
, &MBTile::load
);
416 future
.waitForFinished();
418 for (int i
= 0; i
< tiles
.size(); i
++) {
419 const MBTile
&mt
= tiles
.at(i
);
420 QPixmap
pm(mt
.pixmap());
424 QPixmapCache::insert(mt
.key(), pm
);
426 QPointF
tp(tl
.x() + (mt
.xy().x() - tile
.x()) * tileSize(),
427 tl
.y() + (mt
.xy().y() - tile
.y()) * tileSize());
428 drawTile(painter
, pm
, tp
);
431 runJob(new MBTilesMapJob(tiles
));
435 void MBTilesMap::drawTile(QPainter
*painter
, QPixmap
&pixmap
, QPointF
&tp
)
437 pixmap
.setDevicePixelRatio(imageRatio());
438 painter
->drawPixmap(tp
, pixmap
);
441 QPointF
MBTilesMap::ll2xy(const Coordinates
&c
)
443 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
), _tileSize
);
444 QPointF m
= OSM::ll2m(c
);
445 return QPointF(m
.x() / scale
, m
.y() / -scale
) / coordinatesRatio();
448 Coordinates
MBTilesMap::xy2ll(const QPointF
&p
)
450 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
), _tileSize
);
451 return OSM::m2ll(QPointF(p
.x() * scale
, -p
.y() * scale
)
452 * coordinatesRatio());
455 Map
*MBTilesMap::create(const QString
&path
, const Projection
&proj
, bool *isDir
)
462 return new MBTilesMap(path
);