6 #include <QPixmapCache>
7 #include <QtConcurrent>
8 #include "common/util.h"
10 #include "mbtilesmap.h"
12 #define MAX_TILE_SIZE 4096
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
);
82 _zoomsBase
.append(Zoom(i
, i
));
85 if (!_zoomsBase
.size()) {
86 _errorString
= "Empty tile set";
90 _zi
= _zoomsBase
.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
= _zoomsBase
.first().z
;
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 zoom_level, tile_data FROM tiles LIMIT 1");
133 QSqlQuery
query(sql
, _db
);
136 QByteArray
z(QByteArray::number(query
.value(0).toInt()));
137 QByteArray data
= query
.value(1).toByteArray();
138 QBuffer
buffer(&data
);
139 QImageReader
reader(&buffer
, z
);
140 QSize
tileSize(reader
.size());
142 if (!tileSize
.isValid() || tileSize
.width() != tileSize
.height()) {
143 _errorString
= "Unsupported/invalid tile images";
147 _tileSize
= tileSize
.width();
152 void MBTilesMap::getTileFormat()
154 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'format'", _db
);
156 if (query
.value(0).toString() == "pbf")
159 qWarning("%s: missing tiles format metadata", qPrintable(path()));
162 void MBTilesMap::getTilePixelRatio()
164 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'tilepixelratio'",
168 double ratio
= query
.value(0).toString().toDouble(&ok
);
174 void MBTilesMap::getName()
176 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'name'", _db
);
178 _name
= query
.value(0).toString();
180 qWarning("%s: missing map name", qPrintable(path()));
181 _name
= Util::file2name(path());
185 MBTilesMap::MBTilesMap(const QString
&fileName
, QObject
*parent
)
186 : Map(fileName
, parent
), _mapRatio(1.0), _tileRatio(1.0), _scalable(false),
187 _scaledSize(0), _valid(false)
189 if (!Util::isSQLiteDB(fileName
, _errorString
))
192 _db
= QSqlDatabase::addDatabase("QSQLITE", fileName
);
193 _db
.setDatabaseName(fileName
);
194 _db
.setConnectOptions("QSQLITE_OPEN_READONLY");
197 _errorString
= _db
.lastError().text();
201 QSqlRecord r
= _db
.record("tiles");
203 || r
.field(0).name() != "zoom_level"
204 || META_TYPE(r
.field(0).type()) != QMetaType::Int
205 || r
.field(1).name() != "tile_column"
206 || META_TYPE(r
.field(1).type()) != QMetaType::Int
207 || r
.field(2).name() != "tile_row"
208 || META_TYPE(r
.field(2).type()) != QMetaType::Int
209 || r
.field(3).name() != "tile_data"
210 || META_TYPE(r
.field(3).type()) != QMetaType::QByteArray
) {
211 _errorString
= "Invalid table format";
230 void MBTilesMap::load(const Projection
&in
, const Projection
&out
,
231 qreal deviceRatio
, bool hidpi
)
236 _mapRatio
= hidpi
? deviceRatio
: 1.0;
240 _scaledSize
= _tileSize
* deviceRatio
;
241 _tileRatio
= deviceRatio
;
243 for (int i
= _zooms
.last().base
+ 1; i
<= OSM::ZOOMS
.max(); i
++) {
244 Zoom
z(i
, _zooms
.last().base
);
245 if (_tileSize
* _tileRatio
* (1U<<(z
.z
- z
.base
)) > MAX_TILE_SIZE
)
247 _zooms
.append(Zoom(i
, _zooms
.last().base
));
254 void MBTilesMap::unload()
260 QRectF
MBTilesMap::bounds()
262 return QRectF(ll2xy(_bounds
.topLeft()), ll2xy(_bounds
.bottomRight()));
265 int MBTilesMap::zoomFit(const QSize
&size
, const RectC
&rect
)
268 _zi
= _zooms
.size() - 1;
270 QRectF
tbr(OSM::ll2m(rect
.topLeft()), OSM::ll2m(rect
.bottomRight()));
271 QPointF
sc(tbr
.width() / size
.width(), tbr
.height() / size
.height());
272 int zoom
= OSM::scale2zoom(qMax(sc
.x(), -sc
.y()) / coordinatesRatio(),
276 for (int i
= 1; i
< _zooms
.size(); i
++) {
277 if (_zooms
.at(i
).z
> zoom
)
286 qreal
MBTilesMap::resolution(const QRectF
&rect
)
288 return OSM::resolution(rect
.center(), _zooms
.at(_zi
).z
, tileSize());
291 int MBTilesMap::zoomIn()
295 _zi
= qMin(_zi
+ 1, _zooms
.size() - 1);
299 int MBTilesMap::zoomOut()
303 _zi
= qMax(_zi
- 1, 0);
307 qreal
MBTilesMap::coordinatesRatio() const
309 return _mapRatio
> 1.0 ? _mapRatio
/ _tileRatio
: 1.0;
312 qreal
MBTilesMap::imageRatio() const
314 return _mapRatio
> 1.0 ? _mapRatio
: _tileRatio
;
317 qreal
MBTilesMap::tileSize() const
319 return (_tileSize
/ coordinatesRatio());
322 QByteArray
MBTilesMap::tileData(int zoom
, const QPoint
&tile
) const
324 QSqlQuery
query(_db
);
325 query
.prepare("SELECT tile_data FROM tiles "
326 "WHERE zoom_level=:zoom AND tile_column=:x AND tile_row=:y");
327 query
.bindValue(":zoom", zoom
);
328 query
.bindValue(":x", tile
.x());
329 query
.bindValue(":y", (1<<zoom
) - tile
.y() - 1);
333 return query
.value(0).toByteArray();
338 bool MBTilesMap::isRunning(const QString
&key
) const
340 for (int i
= 0; i
< _jobs
.size(); i
++) {
341 const QList
<MBTile
> &tiles
= _jobs
.at(i
)->tiles();
342 for (int j
= 0; j
< tiles
.size(); j
++)
343 if (tiles
.at(j
).key() == key
)
350 void MBTilesMap::runJob(MBTilesMapJob
*job
)
354 connect(job
, &MBTilesMapJob::finished
, this, &MBTilesMap::jobFinished
);
358 void MBTilesMap::removeJob(MBTilesMapJob
*job
)
360 _jobs
.removeOne(job
);
364 void MBTilesMap::jobFinished(MBTilesMapJob
*job
)
366 const QList
<MBTile
> &tiles
= job
->tiles();
368 for (int i
= 0; i
< tiles
.size(); i
++) {
369 const MBTile
&mt
= tiles
.at(i
);
370 if (!mt
.pixmap().isNull())
371 QPixmapCache::insert(mt
.key(), mt
.pixmap());
379 void MBTilesMap::cancelJobs(bool wait
)
381 for (int i
= 0; i
< _jobs
.size(); i
++)
382 _jobs
.at(i
)->cancel(wait
);
385 void MBTilesMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags flags
)
387 const Zoom
&zoom
= _zooms
.at(_zi
);
388 unsigned overzoom
= zoom
.z
- zoom
.base
;
389 unsigned f
= 1U<<overzoom
;
390 qreal scale
= OSM::zoom2scale(zoom
.base
, _tileSize
* f
);
391 QPoint tile
= OSM::mercator2tile(QPointF(rect
.topLeft().x() * scale
,
392 -rect
.topLeft().y() * scale
) * coordinatesRatio(), zoom
.base
);
393 Coordinates
ctl(OSM::tile2ll(tile
, zoom
.base
));
394 QPointF
tl(ll2xy(Coordinates(ctl
.lon(), -ctl
.lat())));
395 QSizeF
s(rect
.right() - tl
.x(), rect
.bottom() - tl
.y());
396 int width
= ceil(s
.width() / (tileSize() * f
));
397 int height
= ceil(s
.height() / (tileSize() * f
));
401 for (int i
= 0; i
< width
; i
++) {
402 for (int j
= 0; j
< height
; j
++) {
404 QPoint
t(tile
.x() + i
, tile
.y() + j
);
405 QString key
= path() + "-" + QString::number(zoom
.z
) + "_"
406 + QString::number(t
.x()) + "_" + QString::number(t
.y());
411 if (QPixmapCache::find(key
, &pm
)) {
412 QPointF
tp(tl
.x() + (t
.x() - tile
.x()) * tileSize() * f
,
413 tl
.y() + (t
.y() - tile
.y()) * tileSize() * f
);
414 drawTile(painter
, pm
, tp
);
416 tiles
.append(MBTile(zoom
.z
, overzoom
, _scaledSize
, t
,
417 tileData(zoom
.base
, t
), key
));
421 if (!tiles
.isEmpty()) {
422 if (flags
& Map::Block
|| !_scalable
) {
423 QFuture
<void> future
= QtConcurrent::map(tiles
, &MBTile::load
);
424 future
.waitForFinished();
426 for (int i
= 0; i
< tiles
.size(); i
++) {
427 const MBTile
&mt
= tiles
.at(i
);
428 QPixmap
pm(mt
.pixmap());
432 QPixmapCache::insert(mt
.key(), pm
);
434 QPointF
tp(tl
.x() + (mt
.xy().x() - tile
.x()) * tileSize() * f
,
435 tl
.y() + (mt
.xy().y() - tile
.y()) * tileSize() * f
);
436 drawTile(painter
, pm
, tp
);
439 runJob(new MBTilesMapJob(tiles
));
443 void MBTilesMap::drawTile(QPainter
*painter
, QPixmap
&pixmap
, QPointF
&tp
)
445 pixmap
.setDevicePixelRatio(imageRatio());
446 painter
->drawPixmap(tp
, pixmap
);
449 QPointF
MBTilesMap::ll2xy(const Coordinates
&c
)
451 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
).z
, _tileSize
);
452 QPointF m
= OSM::ll2m(c
);
453 return QPointF(m
.x() / scale
, m
.y() / -scale
) / coordinatesRatio();
456 Coordinates
MBTilesMap::xy2ll(const QPointF
&p
)
458 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
).z
, _tileSize
);
459 return OSM::m2ll(QPointF(p
.x() * scale
, -p
.y() * scale
)
460 * coordinatesRatio());
463 Map
*MBTilesMap::create(const QString
&path
, const Projection
&proj
, bool *isDir
)
470 return new MBTilesMap(path
);
474 QDebug
operator<<(QDebug dbg
, const MBTilesMap::Zoom
&zoom
)
476 dbg
.nospace() << "Zoom(" << zoom
.z
<< ", " << zoom
.base
<< ")";
479 #endif // QT_NO_DEBUG