QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
24QgsMbTiles::QgsMbTiles( const QString &filename )
25 : mFilename( filename )
26{
27}
28
30{
31 if ( mDatabase )
32 return true; // already opened
33
34 if ( mFilename.isEmpty() )
35 return false;
36
37 const sqlite3_database_unique_ptr database;
38 const int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READONLY, nullptr );
39 if ( result != SQLITE_OK )
40 {
41 QgsDebugError( QStringLiteral( "Can't open MBTiles database: %1" ).arg( database.errorMessage() ) );
42 return false;
43 }
44 return true;
45}
46
48{
49 return bool( mDatabase );
50}
51
53{
54 if ( mDatabase )
55 return false;
56
57 if ( QFile::exists( mFilename ) )
58 return false;
59
60 const sqlite3_database_unique_ptr database;
61 int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
62 if ( result != SQLITE_OK )
63 {
64 QgsDebugError( QStringLiteral( "Can't create MBTiles database: %1" ).arg( database.errorMessage() ) );
65 return false;
66 }
67
68 const QString sql = \
69 "CREATE TABLE metadata (name text, value text);" \
70 "CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);" \
71 "CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);";
72 QString errorMessage;
73 result = mDatabase.exec( sql, errorMessage );
74 if ( result != SQLITE_OK )
75 {
76 QgsDebugError( QStringLiteral( "Failed to initialize MBTiles database: " ) + errorMessage );
77 return false;
78 }
79
80 return true;
81}
82
83QString QgsMbTiles::metadataValue( const QString &key ) const
84{
85 if ( !mDatabase )
86 {
87 QgsDebugError( QStringLiteral( "MBTiles database not open: " ) + mFilename );
88 return QString();
89 }
90
91 int result;
92 const QString sql = QStringLiteral( "select value from metadata where name='%1'" ).arg( key );
93 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
94 if ( result != SQLITE_OK )
95 {
96 QgsDebugError( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
97 return QString();
98 }
99
100 if ( preparedStatement.step() != SQLITE_ROW )
101 {
102 QgsDebugError( QStringLiteral( "MBTile metadata value not found: " ) + key );
103 return QString();
104 }
105
106 return preparedStatement.columnAsText( 0 );
107}
108
109void QgsMbTiles::setMetadataValue( const QString &key, const QString &value ) const
110{
111 if ( !mDatabase )
112 {
113 QgsDebugError( QStringLiteral( "MBTiles database not open: " ) + mFilename );
114 return;
115 }
116
117 int result;
118 const QString sql = QStringLiteral( "insert into metadata values (%1, %2)" ).arg( QgsSqliteUtils::quotedValue( key ), QgsSqliteUtils::quotedValue( value ) );
119 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
120 if ( result != SQLITE_OK )
121 {
122 QgsDebugError( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
123 return;
124 }
125
126 if ( preparedStatement.step() != SQLITE_DONE )
127 {
128 QgsDebugError( QStringLiteral( "MBTile metadata value failed to be set: " ) + key );
129 return;
130 }
131}
132
134{
135 const QString boundsStr = metadataValue( "bounds" );
136 if ( boundsStr.isEmpty() )
137 return QgsRectangle();
138 QStringList boundsArray = boundsStr.split( ',' );
139 if ( boundsArray.count() != 4 )
140 return QgsRectangle();
141
142 return QgsRectangle( boundsArray[0].toDouble(), boundsArray[1].toDouble(),
143 boundsArray[2].toDouble(), boundsArray[3].toDouble() );
144}
145
146QByteArray QgsMbTiles::tileData( int z, int x, int y ) const
147{
148 if ( !mDatabase )
149 {
150 QgsDebugError( QStringLiteral( "MBTiles database not open: " ) + mFilename );
151 return QByteArray();
152 }
153
154 int result;
155 const QString sql = QStringLiteral( "select tile_data from tiles where zoom_level=%1 and tile_column=%2 and tile_row=%3" ).arg( z ).arg( x ).arg( y );
156 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
157 if ( result != SQLITE_OK )
158 {
159 QgsDebugError( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
160 return QByteArray();
161 }
162
163 if ( preparedStatement.step() != SQLITE_ROW )
164 {
165 // this is not entirely unexpected -- user may have just requested a tile outside of the extent of the mbtiles package
166 QgsDebugMsgLevel( QStringLiteral( "MBTile not found: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ), 2 );
167 return QByteArray();
168 }
169
170 return preparedStatement.columnAsBlob( 0 );
171}
172
173QImage QgsMbTiles::tileDataAsImage( int z, int x, int y ) const
174{
175 QImage tileImage;
176 const QByteArray tileBlob = tileData( z, x, y );
177 if ( !tileImage.loadFromData( tileBlob ) )
178 {
179 QgsDebugError( QStringLiteral( "MBTile data failed to load: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
180 return QImage();
181 }
182 return tileImage;
183}
184
185void QgsMbTiles::setTileData( int z, int x, int y, const QByteArray &data ) const
186{
187 if ( !mDatabase )
188 {
189 QgsDebugError( QStringLiteral( "MBTiles database not open: " ) + mFilename );
190 return;
191 }
192
193 int result;
194 const QString sql = QStringLiteral( "insert into tiles values (%1, %2, %3, ?)" ).arg( z ).arg( x ).arg( y );
195 sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
196 if ( result != SQLITE_OK )
197 {
198 QgsDebugError( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
199 return;
200 }
201
202 sqlite3_bind_blob( preparedStatement.get(), 1, data.constData(), data.size(), SQLITE_TRANSIENT );
203
204 if ( preparedStatement.step() != SQLITE_DONE )
205 {
206 QgsDebugError( QStringLiteral( "MBTile tile failed to be set: %1,%2,%3" ).arg( z ).arg( x ).arg( y ) );
207 return;
208 }
209}
bool create()
Creates a new MBTiles file and initializes it with metadata and tiles tables.
Definition: qgsmbtiles.cpp:52
QgsMbTiles(const QString &filename)
Constructs MBTiles reader (but it does not open the file yet)
Definition: qgsmbtiles.cpp:24
QString metadataValue(const QString &key) const
Requests metadata value for the given key.
Definition: qgsmbtiles.cpp:83
bool open()
Tries to open the file, returns true on success.
Definition: qgsmbtiles.cpp:29
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)
Definition: qgsmbtiles.cpp:173
QByteArray tileData(int z, int x, int y) const
Returns raw tile data for given tile.
Definition: qgsmbtiles.cpp:146
void setMetadataValue(const QString &key, const QString &value) const
Sets metadata value for the given key.
Definition: qgsmbtiles.cpp:109
bool isOpen() const
Returns whether the MBTiles file is currently opened.
Definition: qgsmbtiles.cpp:47
void setTileData(int z, int x, int y, const QByteArray &data) const
Adds tile data for the given tile coordinates.
Definition: qgsmbtiles.cpp:185
QgsRectangle extent() const
Returns bounding box from metadata, given in WGS 84 (if available)
Definition: qgsmbtiles.cpp:133
A rectangle specified with double values.
Definition: qgsrectangle.h:42
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...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
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.
int exec(const QString &sql, QString &errorMessage) const
Executes the sql command in the database.
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:39
#define QgsDebugError(str)
Definition: qgslogger.h:38