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 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;
239 _scaledSize
= _tileSize
* deviceRatio
;
240 _tileRatio
= deviceRatio
;
246 void MBTilesMap::unload()
252 QRectF
MBTilesMap::bounds()
254 return QRectF(ll2xy(_bounds
.topLeft()), ll2xy(_bounds
.bottomRight()));
257 int MBTilesMap::zoomFit(const QSize
&size
, const RectC
&rect
)
260 _zi
= _zooms
.size() - 1;
262 QRectF
tbr(OSM::ll2m(rect
.topLeft()), OSM::ll2m(rect
.bottomRight()));
263 QPointF
sc(tbr
.width() / size
.width(), tbr
.height() / size
.height());
264 int zoom
= OSM::scale2zoom(qMax(sc
.x(), -sc
.y()) / coordinatesRatio(),
268 for (int i
= 1; i
< _zooms
.size(); i
++) {
269 if (_zooms
.at(i
) > zoom
)
278 qreal
MBTilesMap::resolution(const QRectF
&rect
)
280 return OSM::resolution(rect
.center(), _zooms
.at(_zi
), _tileSize
);
283 int MBTilesMap::zoomIn()
287 _zi
= qMin(_zi
+ 1, _zooms
.size() - 1);
291 int MBTilesMap::zoomOut()
295 _zi
= qMax(_zi
- 1, 0);
299 qreal
MBTilesMap::coordinatesRatio() const
301 return _mapRatio
> 1.0 ? _mapRatio
/ _tileRatio
: 1.0;
304 qreal
MBTilesMap::imageRatio() const
306 return _mapRatio
> 1.0 ? _mapRatio
: _tileRatio
;
309 qreal
MBTilesMap::tileSize() const
311 return (_tileSize
/ coordinatesRatio());
314 QByteArray
MBTilesMap::tileData(int zoom
, const QPoint
&tile
) const
316 QSqlQuery
query(_db
);
317 query
.prepare("SELECT tile_data FROM tiles "
318 "WHERE zoom_level=:zoom AND tile_column=:x AND tile_row=:y");
319 query
.bindValue(":zoom", zoom
);
320 query
.bindValue(":x", tile
.x());
321 query
.bindValue(":y", (1<<zoom
) - tile
.y() - 1);
325 return query
.value(0).toByteArray();
330 bool MBTilesMap::isRunning(const QString
&key
) const
332 for (int i
= 0; i
< _jobs
.size(); i
++) {
333 const QList
<MBTile
> &tiles
= _jobs
.at(i
)->tiles();
334 for (int j
= 0; j
< tiles
.size(); j
++)
335 if (tiles
.at(j
).key() == key
)
342 void MBTilesMap::runJob(MBTilesMapJob
*job
)
346 connect(job
, &MBTilesMapJob::finished
, this, &MBTilesMap::jobFinished
);
350 void MBTilesMap::removeJob(MBTilesMapJob
*job
)
352 _jobs
.removeOne(job
);
356 void MBTilesMap::jobFinished(MBTilesMapJob
*job
)
358 const QList
<MBTile
> &tiles
= job
->tiles();
360 for (int i
= 0; i
< tiles
.size(); i
++) {
361 const MBTile
&mt
= tiles
.at(i
);
362 if (!mt
.pixmap().isNull())
363 QPixmapCache::insert(mt
.key(), mt
.pixmap());
371 void MBTilesMap::cancelJobs(bool wait
)
373 for (int i
= 0; i
< _jobs
.size(); i
++)
374 _jobs
.at(i
)->cancel(wait
);
377 void MBTilesMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags flags
)
379 int zoom
= _zooms
.at(_zi
);
380 qreal scale
= OSM::zoom2scale(zoom
, _tileSize
);
381 QPoint tile
= OSM::mercator2tile(QPointF(rect
.topLeft().x() * scale
,
382 -rect
.topLeft().y() * scale
) * coordinatesRatio(), zoom
);
383 Coordinates
ctl(OSM::tile2ll(tile
, zoom
));
384 QPointF
tl(ll2xy(Coordinates(ctl
.lon(), -ctl
.lat())));
385 QSizeF
s(rect
.right() - tl
.x(), rect
.bottom() - tl
.y());
386 int width
= ceil(s
.width() / tileSize());
387 int height
= ceil(s
.height() / tileSize());
391 for (int i
= 0; i
< width
; i
++) {
392 for (int j
= 0; j
< height
; j
++) {
394 QPoint
t(tile
.x() + i
, tile
.y() + j
);
395 QString key
= path() + "-" + QString::number(zoom
) + "_"
396 + QString::number(t
.x()) + "_" + QString::number(t
.y());
401 if (QPixmapCache::find(key
, &pm
)) {
402 QPointF
tp(tl
.x() + (t
.x() - tile
.x()) * tileSize(),
403 tl
.y() + (t
.y() - tile
.y()) * tileSize());
404 drawTile(painter
, pm
, tp
);
406 tiles
.append(MBTile(zoom
, _scaledSize
, t
, tileData(zoom
, t
),
412 if (!tiles
.isEmpty()) {
413 if (flags
& Map::Block
|| !_scalable
) {
414 QFuture
<void> future
= QtConcurrent::map(tiles
, &MBTile::load
);
415 future
.waitForFinished();
417 for (int i
= 0; i
< tiles
.size(); i
++) {
418 const MBTile
&mt
= tiles
.at(i
);
419 QPixmap
pm(mt
.pixmap());
423 QPixmapCache::insert(mt
.key(), pm
);
425 QPointF
tp(tl
.x() + (mt
.xy().x() - tile
.x()) * tileSize(),
426 tl
.y() + (mt
.xy().y() - tile
.y()) * tileSize());
427 drawTile(painter
, pm
, tp
);
430 runJob(new MBTilesMapJob(tiles
));
434 void MBTilesMap::drawTile(QPainter
*painter
, QPixmap
&pixmap
, QPointF
&tp
)
436 pixmap
.setDevicePixelRatio(imageRatio());
437 painter
->drawPixmap(tp
, pixmap
);
440 QPointF
MBTilesMap::ll2xy(const Coordinates
&c
)
442 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
), _tileSize
);
443 QPointF m
= OSM::ll2m(c
);
444 return QPointF(m
.x() / scale
, m
.y() / -scale
) / coordinatesRatio();
447 Coordinates
MBTilesMap::xy2ll(const QPointF
&p
)
449 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
), _tileSize
);
450 return OSM::m2ll(QPointF(p
.x() * scale
, -p
.y() * scale
)
451 * coordinatesRatio());
454 Map
*MBTilesMap::create(const QString
&path
, const Projection
&proj
, bool *isDir
)
461 return new MBTilesMap(path
);