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
);
82 _zooms
.append(Zoom(i
, i
));
86 _errorString
= "Empty tile set";
91 for (int i
= _zooms
.last().base
+ 1; i
<= OSM::ZOOMS
.max(); i
++)
92 _zooms
.append(Zoom(i
, _zooms
.last().base
));
95 _zi
= _zooms
.size() - 1;
100 bool MBTilesMap::getBounds()
102 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'bounds'", _db
);
104 RectC
b(str2bounds(query
.value(0).toString()));
106 _errorString
= "Invalid bounds metadata";
111 qWarning("%s: missing bounds metadata", qPrintable(path()));
113 int z
= _zooms
.first().z
;
114 QString sql
= QString("SELECT min(tile_column), min(tile_row), "
115 "max(tile_column), max(tile_row) FROM tiles WHERE zoom_level = %1")
117 QSqlQuery
query(sql
, _db
);
120 int minX
= qMin((1<<z
) - 1, qMax(0, query
.value(0).toInt()));
121 int minY
= qMin((1<<z
) - 1, qMax(0, query
.value(1).toInt()));
122 int maxX
= qMin((1<<z
) - 1, qMax(0, query
.value(2).toInt())) + 1;
123 int maxY
= qMin((1<<z
) - 1, qMax(0, query
.value(3).toInt())) + 1;
124 Coordinates
tl(OSM::tile2ll(QPoint(minX
, maxY
), z
));
125 Coordinates
br(OSM::tile2ll(QPoint(maxX
, minY
), z
));
126 // Workaround of broken zoom levels 0 and 1 due to numerical instability
127 tl
.rlat() = qMin(tl
.lat(), OSM::BOUNDS
.top());
128 br
.rlat() = qMax(br
.lat(), OSM::BOUNDS
.bottom());
129 _bounds
= RectC(tl
, br
);
135 bool MBTilesMap::getTileSize()
137 QString
sql("SELECT zoom_level, tile_data FROM tiles LIMIT 1");
138 QSqlQuery
query(sql
, _db
);
141 QByteArray
z(QByteArray::number(query
.value(0).toInt()));
142 QByteArray data
= query
.value(1).toByteArray();
143 QBuffer
buffer(&data
);
144 QImageReader
reader(&buffer
, z
);
145 QSize
tileSize(reader
.size());
147 if (!tileSize
.isValid() || tileSize
.width() != tileSize
.height()) {
148 _errorString
= "Unsupported/invalid tile images";
152 _tileSize
= tileSize
.width();
157 void MBTilesMap::getTileFormat()
159 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'format'", _db
);
161 if (query
.value(0).toString() == "pbf")
164 qWarning("%s: missing tiles format metadata", qPrintable(path()));
167 void MBTilesMap::getTilePixelRatio()
169 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'tilepixelratio'",
173 double ratio
= query
.value(0).toString().toDouble(&ok
);
179 void MBTilesMap::getName()
181 QSqlQuery
query("SELECT value FROM metadata WHERE name = 'name'", _db
);
183 _name
= query
.value(0).toString();
185 qWarning("%s: missing map name", qPrintable(path()));
186 _name
= Util::file2name(path());
190 MBTilesMap::MBTilesMap(const QString
&fileName
, QObject
*parent
)
191 : Map(fileName
, parent
), _mapRatio(1.0), _tileRatio(1.0), _scalable(false),
192 _scaledSize(0), _valid(false)
194 if (!Util::isSQLiteDB(fileName
, _errorString
))
197 _db
= QSqlDatabase::addDatabase("QSQLITE", fileName
);
198 _db
.setDatabaseName(fileName
);
199 _db
.setConnectOptions("QSQLITE_OPEN_READONLY");
202 _errorString
= _db
.lastError().text();
206 QSqlRecord r
= _db
.record("tiles");
208 || r
.field(0).name() != "zoom_level"
209 || META_TYPE(r
.field(0).type()) != QMetaType::Int
210 || r
.field(1).name() != "tile_column"
211 || META_TYPE(r
.field(1).type()) != QMetaType::Int
212 || r
.field(2).name() != "tile_row"
213 || META_TYPE(r
.field(2).type()) != QMetaType::Int
214 || r
.field(3).name() != "tile_data"
215 || META_TYPE(r
.field(3).type()) != QMetaType::QByteArray
) {
216 _errorString
= "Invalid table format";
235 void MBTilesMap::load(const Projection
&in
, const Projection
&out
,
236 qreal deviceRatio
, bool hidpi
)
241 _mapRatio
= hidpi
? deviceRatio
: 1.0;
244 _scaledSize
= _tileSize
* deviceRatio
;
245 _tileRatio
= deviceRatio
;
251 void MBTilesMap::unload()
257 QRectF
MBTilesMap::bounds()
259 return QRectF(ll2xy(_bounds
.topLeft()), ll2xy(_bounds
.bottomRight()));
262 int MBTilesMap::zoomFit(const QSize
&size
, const RectC
&rect
)
265 _zi
= _zooms
.size() - 1;
267 QRectF
tbr(OSM::ll2m(rect
.topLeft()), OSM::ll2m(rect
.bottomRight()));
268 QPointF
sc(tbr
.width() / size
.width(), tbr
.height() / size
.height());
269 int zoom
= OSM::scale2zoom(qMax(sc
.x(), -sc
.y()) / coordinatesRatio(),
273 for (int i
= 1; i
< _zooms
.size(); i
++) {
274 if (_zooms
.at(i
).z
> zoom
)
283 qreal
MBTilesMap::resolution(const QRectF
&rect
)
285 return OSM::resolution(rect
.center(), _zooms
.at(_zi
).z
, _tileSize
);
288 int MBTilesMap::zoomIn()
292 _zi
= qMin(_zi
+ 1, _zooms
.size() - 1);
296 int MBTilesMap::zoomOut()
300 _zi
= qMax(_zi
- 1, 0);
304 qreal
MBTilesMap::coordinatesRatio() const
306 return _mapRatio
> 1.0 ? _mapRatio
/ _tileRatio
: 1.0;
309 qreal
MBTilesMap::imageRatio() const
311 return _mapRatio
> 1.0 ? _mapRatio
: _tileRatio
;
314 qreal
MBTilesMap::tileSize() const
316 return (_tileSize
/ coordinatesRatio());
319 QByteArray
MBTilesMap::tileData(int zoom
, const QPoint
&tile
) const
321 QSqlQuery
query(_db
);
322 query
.prepare("SELECT tile_data FROM tiles "
323 "WHERE zoom_level=:zoom AND tile_column=:x AND tile_row=:y");
324 query
.bindValue(":zoom", zoom
);
325 query
.bindValue(":x", tile
.x());
326 query
.bindValue(":y", (1<<zoom
) - tile
.y() - 1);
330 return query
.value(0).toByteArray();
335 bool MBTilesMap::isRunning(const QString
&key
) const
337 for (int i
= 0; i
< _jobs
.size(); i
++) {
338 const QList
<MBTile
> &tiles
= _jobs
.at(i
)->tiles();
339 for (int j
= 0; j
< tiles
.size(); j
++)
340 if (tiles
.at(j
).key() == key
)
347 void MBTilesMap::runJob(MBTilesMapJob
*job
)
351 connect(job
, &MBTilesMapJob::finished
, this, &MBTilesMap::jobFinished
);
355 void MBTilesMap::removeJob(MBTilesMapJob
*job
)
357 _jobs
.removeOne(job
);
361 void MBTilesMap::jobFinished(MBTilesMapJob
*job
)
363 const QList
<MBTile
> &tiles
= job
->tiles();
365 for (int i
= 0; i
< tiles
.size(); i
++) {
366 const MBTile
&mt
= tiles
.at(i
);
367 if (!mt
.pixmap().isNull())
368 QPixmapCache::insert(mt
.key(), mt
.pixmap());
376 void MBTilesMap::cancelJobs(bool wait
)
378 for (int i
= 0; i
< _jobs
.size(); i
++)
379 _jobs
.at(i
)->cancel(wait
);
382 void MBTilesMap::draw(QPainter
*painter
, const QRectF
&rect
, Flags flags
)
384 const Zoom
&zoom
= _zooms
.at(_zi
);
385 unsigned overzoom
= zoom
.z
- zoom
.base
;
386 unsigned f
= 1U<<overzoom
;
387 qreal scale
= OSM::zoom2scale(zoom
.base
, _tileSize
* f
);
388 QPoint tile
= OSM::mercator2tile(QPointF(rect
.topLeft().x() * scale
,
389 -rect
.topLeft().y() * scale
) * coordinatesRatio(), zoom
.base
);
390 Coordinates
ctl(OSM::tile2ll(tile
, zoom
.base
));
391 QPointF
tl(ll2xy(Coordinates(ctl
.lon(), -ctl
.lat())));
392 QSizeF
s(rect
.right() - tl
.x(), rect
.bottom() - tl
.y());
393 int width
= ceil(s
.width() / (tileSize() * f
));
394 int height
= ceil(s
.height() / (tileSize() * f
));
398 for (int i
= 0; i
< width
; i
++) {
399 for (int j
= 0; j
< height
; j
++) {
401 QPoint
t(tile
.x() + i
, tile
.y() + j
);
402 QString key
= path() + "-" + QString::number(zoom
.z
) + "_"
403 + QString::number(t
.x()) + "_" + QString::number(t
.y());
408 if (QPixmapCache::find(key
, &pm
)) {
409 QPointF
tp(tl
.x() + (t
.x() - tile
.x()) * tileSize() * f
,
410 tl
.y() + (t
.y() - tile
.y()) * tileSize() * f
);
411 drawTile(painter
, pm
, tp
);
413 tiles
.append(MBTile(zoom
.z
, overzoom
, _scaledSize
, t
,
414 tileData(zoom
.base
, t
), key
));
418 if (!tiles
.isEmpty()) {
419 if (flags
& Map::Block
|| !_scalable
) {
420 QFuture
<void> future
= QtConcurrent::map(tiles
, &MBTile::load
);
421 future
.waitForFinished();
423 for (int i
= 0; i
< tiles
.size(); i
++) {
424 const MBTile
&mt
= tiles
.at(i
);
425 QPixmap
pm(mt
.pixmap());
429 QPixmapCache::insert(mt
.key(), pm
);
431 QPointF
tp(tl
.x() + (mt
.xy().x() - tile
.x()) * tileSize() * f
,
432 tl
.y() + (mt
.xy().y() - tile
.y()) * tileSize() * f
);
433 drawTile(painter
, pm
, tp
);
436 runJob(new MBTilesMapJob(tiles
));
440 void MBTilesMap::drawTile(QPainter
*painter
, QPixmap
&pixmap
, QPointF
&tp
)
442 pixmap
.setDevicePixelRatio(imageRatio());
443 painter
->drawPixmap(tp
, pixmap
);
446 QPointF
MBTilesMap::ll2xy(const Coordinates
&c
)
448 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
).z
, _tileSize
);
449 QPointF m
= OSM::ll2m(c
);
450 return QPointF(m
.x() / scale
, m
.y() / -scale
) / coordinatesRatio();
453 Coordinates
MBTilesMap::xy2ll(const QPointF
&p
)
455 qreal scale
= OSM::zoom2scale(_zooms
.at(_zi
).z
, _tileSize
);
456 return OSM::m2ll(QPointF(p
.x() * scale
, -p
.y() * scale
)
457 * coordinatesRatio());
460 Map
*MBTilesMap::create(const QString
&path
, const Projection
&proj
, bool *isDir
)
467 return new MBTilesMap(path
);
471 QDebug
operator<<(QDebug dbg
, const MBTilesMap::Zoom
&zoom
)
473 dbg
.nospace() << "Zoom(" << zoom
.z
<< ", " << zoom
.base
<< ")";
476 #endif // QT_NO_DEBUG