QGIS API Documentation 4.3.0-Master (6313fb5bce0)
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 sqlite3_database_unique_ptr database;
40 const int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READONLY, nullptr );
41 if ( result != SQLITE_OK )
42 {
43 QgsDebugError( u"Can't open MBTiles database: %1"_s.arg( database.errorMessage() ) );
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 const int result = mDatabase.exec( u"CREATE UNIQUE INDEX IF NOT EXISTS tile_index ON tiles (zoom_level, tile_column, tile_row);"_s, errorMessage );
64
65 return result == SQLITE_OK;
66}
67
68bool QgsMbTiles::create( bool deferIndexCreation )
69{
70 if ( mDatabase )
71 return false;
72
73 if ( QFile::exists( mFilename ) )
74 return false;
75
76 const sqlite3_database_unique_ptr database;
77 int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
78 if ( result != SQLITE_OK )
79 {
80 QgsDebugError( u"Can't create MBTiles database: %1"_s.arg( database.errorMessage() ) );
81 return false;
82 }
83
84 QString errorMessage;
85 // ignore errors from these, they aren't critical
86 // optimise writing speed
87 mDatabase.exec( u"PRAGMA journal_mode = WAL;"_s, errorMessage );
88 mDatabase.exec( u"PRAGMA synchronous = OFF;"_s, errorMessage );
89 mDatabase.exec( u"PRAGMA temp_store = MEMORY;"_s, errorMessage );
90 mDatabase.exec( u"PRAGMA cache_size = -64000;"_s, errorMessage );
91
92 QString sql = "CREATE TABLE metadata (name text, value text);"
93 "CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);";
94 if ( !deferIndexCreation )
95 {
96 sql += "CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);";
97 }
98 else
99 {
100 mDeferredIndexCreation = true;
101 }
102
103 result = mDatabase.exec( sql, errorMessage );
104 if ( result != SQLITE_OK )
105 {
106 QgsDebugError( u"Failed to initialize MBTiles database: "_s + errorMessage );
107 return false;
108 }
109
110 return true;
111}
112
113QString QgsMbTiles::metadataValue( const QString &key ) const
114{
115 if ( !mDatabase )
116 {
117 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
118 return QString();
119 }
120
121 int result;
122 const QString sql = u"select value from metadata where name='%1'"_s.arg( key );
123 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
124 if ( result != SQLITE_OK )
125 {
126 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
127 return QString();
128 }
129
130 if ( preparedStatement.step() != SQLITE_ROW )
131 {
132 QgsDebugError( u"MBTile metadata value not found: "_s + key );
133 return QString();
134 }
135
136 return preparedStatement.columnAsText( 0 );
137}
138
139void QgsMbTiles::setMetadataValue( const QString &key, const QString &value ) const
140{
141 if ( !mDatabase )
142 {
143 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
144 return;
145 }
146
147 int result;
148 const QString sql = u"insert into metadata values (%1, %2)"_s.arg( QgsSqliteUtils::quotedValue( key ), QgsSqliteUtils::quotedValue( value ) );
149 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
150 if ( result != SQLITE_OK )
151 {
152 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
153 return;
154 }
155
156 if ( preparedStatement.step() != SQLITE_DONE )
157 {
158 QgsDebugError( u"MBTile metadata value failed to be set: "_s + key );
159 return;
160 }
161}
162
164{
165 const QString boundsStr = metadataValue( "bounds" );
166 if ( boundsStr.isEmpty() )
167 return QgsRectangle();
168 QStringList boundsArray = boundsStr.split( ',' );
169 if ( boundsArray.count() != 4 )
170 return QgsRectangle();
171
172 return QgsRectangle( boundsArray[0].toDouble(), boundsArray[1].toDouble(), boundsArray[2].toDouble(), boundsArray[3].toDouble() );
173}
174
175QByteArray QgsMbTiles::tileData( int z, int x, int y ) const
176{
177 if ( !mDatabase )
178 {
179 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
180 return QByteArray();
181 }
182
183 int result;
184 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 );
185 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
186 if ( result != SQLITE_OK )
187 {
188 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
189 return QByteArray();
190 }
191
192 if ( preparedStatement.step() != SQLITE_ROW )
193 {
194 // this is not entirely unexpected -- user may have just requested a tile outside of the extent of the mbtiles package
195 QgsDebugMsgLevel( u"MBTile not found: z=%1 x=%2 y=%3"_s.arg( z ).arg( x ).arg( y ), 2 );
196 return QByteArray();
197 }
198
199 return preparedStatement.columnAsBlob( 0 );
200}
201
202QImage QgsMbTiles::tileDataAsImage( int z, int x, int y ) const
203{
204 QImage tileImage;
205 const QByteArray tileBlob = tileData( z, x, y );
206 if ( !tileImage.loadFromData( tileBlob ) )
207 {
208 QgsDebugError( u"MBTile data failed to load: z=%1 x=%2 y=%3"_s.arg( z ).arg( x ).arg( y ) );
209 return QImage();
210 }
211 return tileImage;
212}
213
214void QgsMbTiles::setTileData( int z, int x, int y, const QByteArray &data ) const
215{
216 if ( !mDatabase )
217 {
218 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
219 return;
220 }
221
222 int result;
223 const QString sql = u"insert into tiles values (%1, %2, %3, ?)"_s.arg( z ).arg( x ).arg( y );
224 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
225 if ( result != SQLITE_OK )
226 {
227 QgsDebugError( u"MBTile failed to prepare statement: "_s + sql );
228 return;
229 }
230
231 sqlite3_bind_blob( preparedStatement.get(), 1, data.constData(), data.size(), SQLITE_TRANSIENT );
232
233 if ( preparedStatement.step() != SQLITE_DONE )
234 {
235 QgsDebugError( u"MBTile tile failed to be set: %1,%2,%3"_s.arg( z ).arg( x ).arg( y ) );
236 return;
237 }
238}
239
240void QgsMbTiles::setTileData( const QList<TileData> &tiles ) const
241{
242 if ( tiles.isEmpty() )
243 {
244 return;
245 }
246
247 if ( !mDatabase )
248 {
249 QgsDebugError( u"MBTiles database not open: "_s + mFilename );
250 return;
251 }
252
253 QString errorMessage;
254 int result = mDatabase.exec( u"BEGIN TRANSACTION;"_s, errorMessage );
255 if ( result != SQLITE_OK )
256 {
257 QgsDebugError( u"Failed to begin transaction: %1"_s.arg( errorMessage ) );
258 return;
259 }
260
261 const QString sql = u"INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)"_s;
262 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
263 if ( result != SQLITE_OK )
264 {
265 QgsDebugError( u"MBTile failed to prepare statement: %1"_s.arg( sql ) );
266 mDatabase.exec( u"ROLLBACK TRANSACTION;"_s, errorMessage );
267 return;
268 }
269
270 for ( const TileData &tile : tiles )
271 {
272 sqlite3_reset( preparedStatement.get() );
273 sqlite3_clear_bindings( preparedStatement.get() );
274
275 sqlite3_bind_int( preparedStatement.get(), 1, tile.z );
276 sqlite3_bind_int( preparedStatement.get(), 2, tile.x );
277 sqlite3_bind_int( preparedStatement.get(), 3, tile.y );
278 sqlite3_bind_blob( preparedStatement.get(), 4, tile.data.constData(), tile.data.size(), SQLITE_TRANSIENT );
279
280 if ( preparedStatement.step() != SQLITE_DONE )
281 {
282 QgsDebugError( u"MBTile tile failed to be set: %1,%2,%3"_s.arg( tile.z ).arg( tile.x ).arg( tile.y ) );
283 }
284 }
285
286 result = mDatabase.exec( u"COMMIT TRANSACTION;"_s, errorMessage );
287 if ( result != SQLITE_OK )
288 {
289 QgsDebugError( u"Failed to commit transaction: %1"_s.arg( errorMessage ) );
290 }
291}
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 databases, which automatically closes the database when the pointer goes o...
QString errorMessage() const
Returns the most recent error message encountered by the database.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
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