QGIS API Documentation  3.25.0-Master (10b47c2603)
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 
24 QgsMbTiles::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 
44 bool QgsMbTiles::isOpen() const
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 
80 QString 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 
106 void 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 
143 QByteArray 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 
170 QImage 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 
182 void 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