QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 #include <zlib.h>
25 
26 
27 QgsMbTiles::QgsMbTiles( const QString &filename )
28  : mFilename( filename )
29 {
30 }
31 
33 {
34  if ( mDatabase )
35  return true; // already opened
36 
38  int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READONLY, nullptr );
39  if ( result != SQLITE_OK )
40  {
41  QgsDebugMsg( QStringLiteral( "Can't open MBTiles database: %1" ).arg( database.errorMessage() ) );
42  return false;
43  }
44  return true;
45 }
46 
47 bool QgsMbTiles::isOpen() const
48 {
49  return bool( mDatabase );
50 }
51 
53 {
54  if ( mDatabase )
55  return false;
56 
57  if ( QFile::exists( mFilename ) )
58  return false;
59 
61  int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
62  if ( result != SQLITE_OK )
63  {
64  QgsDebugMsg( QStringLiteral( "Can't create MBTiles database: %1" ).arg( database.errorMessage() ) );
65  return false;
66  }
67 
68  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  QgsDebugMsg( QStringLiteral( "Failed to initialize MBTiles database: " ) + errorMessage );
77  return false;
78  }
79 
80  return true;
81 }
82 
83 QString QgsMbTiles::metadataValue( const QString &key )
84 {
85  if ( !mDatabase )
86  {
87  QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
88  return QString();
89  }
90 
91  int result;
92  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  QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
97  return QString();
98  }
99 
100  if ( preparedStatement.step() != SQLITE_ROW )
101  {
102  QgsDebugMsg( QStringLiteral( "MBTile metadata value not found: " ) + key );
103  return QString();
104  }
105 
106  return preparedStatement.columnAsText( 0 );
107 }
108 
109 void QgsMbTiles::setMetadataValue( const QString &key, const QString &value )
110 {
111  if ( !mDatabase )
112  {
113  QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
114  return;
115  }
116 
117  int result;
118  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  QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
123  return;
124  }
125 
126  if ( preparedStatement.step() != SQLITE_DONE )
127  {
128  QgsDebugMsg( QStringLiteral( "MBTile metadata value failed to be set: " ) + key );
129  return;
130  }
131 }
132 
134 {
135  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 
146 QByteArray QgsMbTiles::tileData( int z, int x, int y )
147 {
148  if ( !mDatabase )
149  {
150  QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
151  return QByteArray();
152  }
153 
154  int result;
155  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  QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
160  return QByteArray();
161  }
162 
163  if ( preparedStatement.step() != SQLITE_ROW )
164  {
165  QgsDebugMsg( QStringLiteral( "MBTile not found: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
166  return QByteArray();
167  }
168 
169  return preparedStatement.columnAsBlob( 0 );
170 }
171 
172 QImage QgsMbTiles::tileDataAsImage( int z, int x, int y )
173 {
174  QImage tileImage;
175  QByteArray tileBlob = tileData( z, x, y );
176  if ( !tileImage.loadFromData( tileBlob ) )
177  {
178  QgsDebugMsg( QStringLiteral( "MBTile data failed to load: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
179  return QImage();
180  }
181  return tileImage;
182 }
183 
184 void QgsMbTiles::setTileData( int z, int x, int y, const QByteArray &data )
185 {
186  if ( !mDatabase )
187  {
188  QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
189  return;
190  }
191 
192  int result;
193  QString sql = QStringLiteral( "insert into tiles values (%1, %2, %3, ?)" ).arg( z ).arg( x ).arg( y );
194  sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
195  if ( result != SQLITE_OK )
196  {
197  QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
198  return;
199  }
200 
201  sqlite3_bind_blob( preparedStatement.get(), 1, data.constData(), data.size(), SQLITE_TRANSIENT );
202 
203  if ( preparedStatement.step() != SQLITE_DONE )
204  {
205  QgsDebugMsg( QStringLiteral( "MBTile tile failed to be set: %1,%2,%3" ).arg( z ).arg( x ).arg( y ) );
206  return;
207  }
208 }
209 
210 bool QgsMbTiles::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
211 {
212  unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
213  uint bytesInLeft = static_cast<uint>( bytesIn.count() );
214 
215  const uint CHUNK = 16384;
216  unsigned char out[CHUNK];
217  const int DEC_MAGIC_NUM_FOR_GZIP = 16;
218 
219  // allocate inflate state
220  z_stream strm;
221  strm.zalloc = Z_NULL;
222  strm.zfree = Z_NULL;
223  strm.opaque = Z_NULL;
224  strm.avail_in = 0;
225  strm.next_in = Z_NULL;
226 
227  int ret = inflateInit2( &strm, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP );
228  if ( ret != Z_OK )
229  return false;
230 
231  while ( ret != Z_STREAM_END ) // done when inflate() says it's done
232  {
233  // prepare next chunk
234  uint bytesToProcess = std::min( CHUNK, bytesInLeft );
235  strm.next_in = bytesInPtr;
236  strm.avail_in = bytesToProcess;
237  bytesInPtr += bytesToProcess;
238  bytesInLeft -= bytesToProcess;
239 
240  if ( bytesToProcess == 0 )
241  break; // we end with an error - no more data but inflate() wants more data
242 
243  // run inflate() on input until output buffer not full
244  do
245  {
246  strm.avail_out = CHUNK;
247  strm.next_out = out;
248  ret = inflate( &strm, Z_NO_FLUSH );
249  Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
250  if ( ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR )
251  {
252  inflateEnd( &strm );
253  return false;
254  }
255  unsigned have = CHUNK - strm.avail_out;
256  bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
257  }
258  while ( strm.avail_out == 0 );
259  }
260 
261  inflateEnd( &strm );
262  return ret == Z_STREAM_END;
263 }
264 
265 
266 bool QgsMbTiles::encodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
267 {
268  unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
269  uint bytesInLeft = static_cast<uint>( bytesIn.count() );
270 
271  const uint CHUNK = 16384;
272  unsigned char out[CHUNK];
273  const int DEC_MAGIC_NUM_FOR_GZIP = 16;
274 
275  // allocate deflate state
276  z_stream strm;
277  strm.zalloc = Z_NULL;
278  strm.zfree = Z_NULL;
279  strm.opaque = Z_NULL;
280 
281  int ret = deflateInit2( &strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP, 8, Z_DEFAULT_STRATEGY );
282  if ( ret != Z_OK )
283  return false;
284 
285  strm.avail_in = bytesInLeft;
286  strm.next_in = bytesInPtr;
287 
288  // run deflate() on input until output buffer not full, finish
289  // compression if all of source has been read in
290  do
291  {
292  strm.avail_out = CHUNK;
293  strm.next_out = out;
294  ret = deflate( &strm, Z_FINISH ); // no bad return value
295  Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
296 
297  unsigned have = CHUNK - strm.avail_out;
298  bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
299  }
300  while ( strm.avail_out == 0 );
301  Q_ASSERT( ret == Z_STREAM_END ); // stream will be complete
302 
303  // clean up and return
304  deflateEnd( &strm );
305  return true;
306 }
QgsMbTiles::setTileData
void setTileData(int z, int x, int y, const QByteArray &data)
Adds tile data for the given tile coordinates.
Definition: qgsmbtiles.cpp:184
QgsMbTiles::QgsMbTiles
QgsMbTiles(const QString &filename)
Constructs MBTiles reader (but it does not open the file yet)
Definition: qgsmbtiles.cpp:27
qgsrectangle.h
sqlite3_database_unique_ptr::prepare
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
Definition: qgssqliteutils.cpp:99
QgsMbTiles::tileDataAsImage
QImage tileDataAsImage(int z, int x, int y)
Returns tile decoded as a raster image (if stored in a known format like JPG or PNG)
Definition: qgsmbtiles.cpp:172
QgsMbTiles::isOpen
bool isOpen() const
Returns whether the MBTiles file is currently opened.
Definition: qgsmbtiles.cpp:47
QgsMbTiles::decodeGzip
static bool decodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Decodes gzip byte stream, returns true on success. Useful for reading vector tiles.
Definition: qgsmbtiles.cpp:210
QgsMbTiles::open
bool open()
Tries to open the file, returns true on success.
Definition: qgsmbtiles.cpp:32
sqlite3_statement_unique_ptr::columnAsBlob
QByteArray columnAsBlob(int column) const
Returns the column value from the current statement row as raw byte array.
Definition: qgssqliteutils.cpp:66
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsMbTiles::tileData
QByteArray tileData(int z, int x, int y)
Returns raw tile data for given tile.
Definition: qgsmbtiles.cpp:146
sqlite3_database_unique_ptr::errorMessage
QString errorMessage() const
Returns the most recent error message encountered by the database.
Definition: qgssqliteutils.cpp:94
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:42
sqlite3_statement_unique_ptr::step
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
Definition: qgssqliteutils.cpp:41
qgsmbtiles.h
QgsMbTiles::encodeGzip
static bool encodeGzip(const QByteArray &bytesIn, QByteArray &bytesOut)
Encodes gzip byte stream, returns true on success. Useful for writing vector tiles.
Definition: qgsmbtiles.cpp:266
QgsSqliteUtils::quotedValue
static QString quotedValue(const QVariant &value)
Returns a properly quoted and escaped version of value for use in SQL strings.
Definition: qgssqliteutils.cpp:266
QgsMbTiles::metadataValue
QString metadataValue(const QString &key)
Requests metadata value for the given key.
Definition: qgsmbtiles.cpp:83
sqlite3_database_unique_ptr::open_v2
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Definition: qgssqliteutils.cpp:86
QgsMbTiles::create
bool create()
Creates a new MBTiles file and initializes it with metadata and tiles tables.
Definition: qgsmbtiles.cpp:52
sqlite3_statement_unique_ptr::columnAsText
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
Definition: qgssqliteutils.cpp:61
sqlite3_database_unique_ptr::exec
int exec(const QString &sql, QString &errorMessage) const
Executes the sql command in the database.
Definition: qgssqliteutils.cpp:109
QgsMbTiles::extent
QgsRectangle extent()
Returns bounding box from metadata, given in WGS 84 (if available)
Definition: qgsmbtiles.cpp:133
qgslogger.h
QgsMbTiles::setMetadataValue
void setMetadataValue(const QString &key, const QString &value)
Sets metadata value for the given key.
Definition: qgsmbtiles.cpp:109
sqlite3_database_unique_ptr
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
Definition: qgssqliteutils.h:119
sqlite3_statement_unique_ptr
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
Definition: qgssqliteutils.h:70