QGIS API Documentation 4.3.0-Master (c8cdfe19d7b)
Loading...
Searching...
No Matches
qgsmbtiles.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmbtiles.cpp
3 --------------------------------------
4 Date : January 2020
5 Copyright : (C) 2020 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsmbtiles.h"
17
18#include "qgslogger.h"
19#include "qgsrectangle.h"
20
21#include <QFile>
22#include <QImage>
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
27QgsMbTiles::QgsMbTiles( const QString &filename )
28 : mFilename( filename )
29{}
30
32{
33 if ( mDatabase )
34 return true; // already opened
35
36 if ( mFilename.isEmpty() )
37 return false;
38
39 const int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READONLY, nullptr );
40 if ( result != SQLITE_OK )
41 {
42 mLastError = mDatabase.errorMessage();
43 QgsDebugError( u"Can't open MBTiles database: %1"_s.arg( mLastError ) );
44 return false;
45 }
46 return true;
47}
48
50{
51 return bool( mDatabase );
52}
53
55{
56 if ( !mDatabase )
57 return false;
58
59 if ( !mDeferredIndexCreation ) // nothing to do!
60 return true;
61
62 QString errorMessage;
63 mDatabase.exec( u"PRAGMA journal_mode = DELETE;"_s, errorMessage );
64
65 const int result = mDatabase.exec( u"CREATE UNIQUE INDEX IF NOT EXISTS tile_index ON tiles (zoom_level, tile_column, tile_row);"_s, mLastError );
66
67 return result == SQLITE_OK;
68}
69
70bool QgsMbTiles::create( bool deferIndexCreation )
71{
72 if ( mDatabase )
73 return false;
74
75 if ( QFile::exists( mFilename ) )
76 return false;
77
78 int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
79 if ( result != SQLITE_OK )
80 {
81 mLastError = mDatabase.errorMessage();
82 QgsDebugError( u"Can't create MBTiles database: %1"_s.arg( mLastError ) );
83 return false;
84 }
85
86 QString errorMessage;
87 // ignore errors from these, they aren't critical
88 // optimise writing speed
89 mDatabase.exec( u"PRAGMA journal_mode = WAL;"_s, errorMessage );
90 mDatabase.exec( u"PRAGMA synchronous = OFF;"_s, errorMessage );
91 mDatabase.exec( u"PRAGMA temp_store = MEMORY;"_s, errorMessage );
92 mDatabase.exec( u"PRAGMA cache_size = -64000;"_s, errorMessage );
93
94 QString sql = "CREATE TABLE metadata (name text, value text);"
95 "CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);";
96 if ( !deferIndexCreation )
97 {
98 sql += "CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);";
99 }
100 else
101 {
102 mDeferredIndexCreation = true;
103 }
104
105 result = mDatabase.exec( sql, mLastError );
106 if ( result != SQLITE_OK )
107 {
108 QgsDebugError( u"Failed to initialize MBTiles database: "_s + mLastError );
109 return false;
110 }
111
112 return true;
113}
114
115QString QgsMbTiles::metadataValue( const QString &key ) const
116{
117 if ( !mDatabase )
118 {
119 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
120 return QString();
121 }
122
123 int result;
124 const QString sql = u"select value from metadata where name='%1'"_s.arg( key );
125 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
126 if ( result != SQLITE_OK )
127 {
128 mLastError = mDatabase.errorMessage();
129 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
130 return QString();
131 }
132
133 if ( preparedStatement.step() != SQLITE_ROW )
134 {
135 mLastError = mDatabase.errorMessage();
136 QgsDebugError( u"MBTile metadata value not found: "_s + key );
137 return QString();
138 }
139
140 return preparedStatement.columnAsText( 0 );
141}
142
143void QgsMbTiles::setMetadataValue( const QString &key, const QString &value ) const
144{
145 if ( !mDatabase )
146 {
147 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
148 return;
149 }
150
151 int result;
152 const QString sql = u"insert into metadata values (%1, %2)"_s.arg( QgsSqliteUtils::quotedValue( key ), QgsSqliteUtils::quotedValue( value ) );
153 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
154 if ( result != SQLITE_OK )
155 {
156 mLastError = mDatabase.errorMessage();
157 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
158 return;
159 }
160
161 if ( preparedStatement.step() != SQLITE_DONE )
162 {
163 mLastError = mDatabase.errorMessage();
164 QgsDebugError( u"MBTile metadata value failed to be set: "_s + key );
165 return;
166 }
167}
168
170{
171 const QString boundsStr = metadataValue( "bounds" );
172 if ( boundsStr.isEmpty() )
173 return QgsRectangle();
174 QStringList boundsArray = boundsStr.split( ',' );
175 if ( boundsArray.count() != 4 )
176 return QgsRectangle();
177
178 return QgsRectangle( boundsArray[0].toDouble(), boundsArray[1].toDouble(), boundsArray[2].toDouble(), boundsArray[3].toDouble() );
179}
180
181QByteArray QgsMbTiles::tileData( int z, int x, int y ) const
182{
183 if ( !mDatabase )
184 {
185 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
186 return QByteArray();
187 }
188
189 int result;
190 const QString sql = u"select tile_data from tiles where zoom_level=%1 and tile_column=%2 and tile_row=%3"_s.arg( z ).arg( x ).arg( y );
191 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
192 if ( result != SQLITE_OK )
193 {
194 mLastError = mDatabase.errorMessage();
195 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
196 return QByteArray();
197 }
198
199 if ( preparedStatement.step() != SQLITE_ROW )
200 {
201 // this is not entirely unexpected -- user may have just requested a tile outside of the extent of the mbtiles package
202 QgsDebugMsgLevel( u"MBTile not found: z=%1 x=%2 y=%3"_s.arg( z ).arg( x ).arg( y ), 2 );
203 return QByteArray();
204 }
205
206 return preparedStatement.columnAsBlob( 0 );
207}
208
209QImage QgsMbTiles::tileDataAsImage( int z, int x, int y ) const
210{
211 QImage tileImage;
212 const QByteArray tileBlob = tileData( z, x, y );
213 if ( !tileImage.loadFromData( tileBlob ) )
214 {
215 QgsDebugError( u"MBTile data failed to load: z=%1 x=%2 y=%3"_s.arg( z ).arg( x ).arg( y ) );
216 return QImage();
217 }
218 return tileImage;
219}
220
221void QgsMbTiles::setTileData( int z, int x, int y, const QByteArray &data ) const
222{
223 if ( !mDatabase )
224 {
225 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
226 return;
227 }
228
229 int result;
230 const QString sql = u"insert into tiles values (%1, %2, %3, ?)"_s.arg( z ).arg( x ).arg( y );
231 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
232 if ( result != SQLITE_OK )
233 {
234 mLastError = mDatabase.errorMessage();
235 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
236 return;
237 }
238
239 sqlite3_bind_blob( preparedStatement.get(), 1, data.constData(), data.size(), SQLITE_TRANSIENT );
240
241 if ( preparedStatement.step() != SQLITE_DONE )
242 {
243 mLastError = mDatabase.errorMessage();
244 QgsDebugError( u"MBTile tile failed to be set: %1,%2,%3"_s.arg( z ).arg( x ).arg( y ) );
245 return;
246 }
247}
248
249void QgsMbTiles::setTileData( const QList<TileData> &tiles ) const
250{
251 if ( tiles.isEmpty() )
252 {
253 return;
254 }
255
256 if ( !mDatabase )
257 {
258 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
259 return;
260 }
261
262 int result = mDatabase.exec( u"BEGIN TRANSACTION;"_s, mLastError );
263 if ( result != SQLITE_OK )
264 {
265 QgsDebugError( u"Failed to begin transaction: %1"_s.arg( mLastError ) );
266 return;
267 }
268
269 const QString sql = u"INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)"_s;
270 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
271 if ( result != SQLITE_OK )
272 {
273 mLastError = mDatabase.errorMessage();
274 QgsDebugError( u"MBTile failed to prepare statement: %1"_s.arg( sql ) );
275 QString errorMessage;
276 mDatabase.exec( u"ROLLBACK TRANSACTION;"_s, errorMessage );
277 return;
278 }
279
280 for ( const TileData &tile : tiles )
281 {
282 sqlite3_reset( preparedStatement.get() );
283 sqlite3_clear_bindings( preparedStatement.get() );
284
285 sqlite3_bind_int( preparedStatement.get(), 1, tile.z );
286 sqlite3_bind_int( preparedStatement.get(), 2, tile.x );
287 sqlite3_bind_int( preparedStatement.get(), 3, tile.y );
288 sqlite3_bind_blob( preparedStatement.get(), 4, tile.data.constData(), tile.data.size(), SQLITE_TRANSIENT );
289
290 if ( preparedStatement.step() != SQLITE_DONE )
291 {
292 mLastError = mDatabase.errorMessage();
293 QgsDebugError( u"MBTile tile failed to be set: %1,%2,%3"_s.arg( tile.z ).arg( tile.x ).arg( tile.y ) );
294 }
295 }
296
297 result = mDatabase.exec( u"COMMIT TRANSACTION;"_s, mLastError );
298 if ( result != SQLITE_OK )
299 {
300 QgsDebugError( u"Failed to commit transaction: %1"_s.arg( mLastError ) );
301 }
302}
QgsMbTiles(const QString &filename)
Constructs MBTiles reader (but it does not open the file yet).
QString metadataValue(const QString &key) const
Requests metadata value for the given key.
bool open()
Tries to open the file, returns true on success.
QImage tileDataAsImage(int z, int x, int y) const
Returns tile decoded as a raster image (if stored in a known format like JPG or PNG).
bool finalize()
Finalizes the database after writing all tiles.
QByteArray tileData(int z, int x, int y) const
Returns raw tile data for given tile.
void setMetadataValue(const QString &key, const QString &value) const
Sets metadata value for the given key.
bool isOpen() const
Returns whether the MBTiles file is currently opened.
void setTileData(int z, int x, int y, const QByteArray &data) const
Adds tile data for the given tile coordinates.
bool create(bool deferIndexCreation=false)
Creates a new MBTiles file and initializes it with metadata and tiles tables.
QgsRectangle extent() const
Returns bounding box from metadata, given in WGS 84 (if available).
A rectangle specified with double values.
static QString quotedValue(const QVariant &value)
Returns a properly quoted and escaped version of value for use in SQL strings.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
QByteArray columnAsBlob(int column) const
Returns the column value from the current statement row as raw byte array.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
Struct representing a raw image tile.
Definition qgsmbtiles.h:100