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