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