Branch data Line data Source code
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 : 0 : QgsMbTiles::QgsMbTiles( const QString &filename )
28 : 0 : : mFilename( filename )
29 : : {
30 : 0 : }
31 : :
32 : 0 : bool QgsMbTiles::open()
33 : : {
34 : 0 : if ( mDatabase )
35 : 0 : return true; // already opened
36 : :
37 : 0 : sqlite3_database_unique_ptr database;
38 : 0 : int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READONLY, nullptr );
39 : 0 : if ( result != SQLITE_OK )
40 : : {
41 : 0 : QgsDebugMsg( QStringLiteral( "Can't open MBTiles database: %1" ).arg( database.errorMessage() ) );
42 : 0 : return false;
43 : : }
44 : 0 : return true;
45 : 0 : }
46 : :
47 : 0 : bool QgsMbTiles::isOpen() const
48 : : {
49 : 0 : return bool( mDatabase );
50 : : }
51 : :
52 : 0 : bool QgsMbTiles::create()
53 : : {
54 : 0 : if ( mDatabase )
55 : 0 : return false;
56 : :
57 : 0 : if ( QFile::exists( mFilename ) )
58 : 0 : return false;
59 : :
60 : 0 : sqlite3_database_unique_ptr database;
61 : 0 : int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
62 : 0 : if ( result != SQLITE_OK )
63 : : {
64 : 0 : QgsDebugMsg( QStringLiteral( "Can't create MBTiles database: %1" ).arg( database.errorMessage() ) );
65 : 0 : return false;
66 : : }
67 : :
68 : : QString sql = \
69 : 0 : "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 : 0 : QString errorMessage;
73 : 0 : result = mDatabase.exec( sql, errorMessage );
74 : 0 : if ( result != SQLITE_OK )
75 : : {
76 : 0 : QgsDebugMsg( QStringLiteral( "Failed to initialize MBTiles database: " ) + errorMessage );
77 : 0 : return false;
78 : : }
79 : :
80 : 0 : return true;
81 : 0 : }
82 : :
83 : 0 : QString QgsMbTiles::metadataValue( const QString &key )
84 : : {
85 : 0 : if ( !mDatabase )
86 : : {
87 : 0 : QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
88 : 0 : return QString();
89 : : }
90 : :
91 : : int result;
92 : 0 : QString sql = QStringLiteral( "select value from metadata where name='%1'" ).arg( key );
93 : 0 : sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
94 : 0 : if ( result != SQLITE_OK )
95 : : {
96 : 0 : QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
97 : 0 : return QString();
98 : : }
99 : :
100 : 0 : if ( preparedStatement.step() != SQLITE_ROW )
101 : : {
102 : 0 : QgsDebugMsg( QStringLiteral( "MBTile metadata value not found: " ) + key );
103 : 0 : return QString();
104 : : }
105 : :
106 : 0 : return preparedStatement.columnAsText( 0 );
107 : 0 : }
108 : :
109 : 0 : void QgsMbTiles::setMetadataValue( const QString &key, const QString &value )
110 : : {
111 : 0 : if ( !mDatabase )
112 : : {
113 : 0 : QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
114 : 0 : return;
115 : : }
116 : :
117 : : int result;
118 : 0 : QString sql = QStringLiteral( "insert into metadata values (%1, %2)" ).arg( QgsSqliteUtils::quotedValue( key ), QgsSqliteUtils::quotedValue( value ) );
119 : 0 : sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
120 : 0 : if ( result != SQLITE_OK )
121 : : {
122 : 0 : QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
123 : 0 : return;
124 : : }
125 : :
126 : 0 : if ( preparedStatement.step() != SQLITE_DONE )
127 : : {
128 : 0 : QgsDebugMsg( QStringLiteral( "MBTile metadata value failed to be set: " ) + key );
129 : 0 : return;
130 : : }
131 : 0 : }
132 : :
133 : 0 : QgsRectangle QgsMbTiles::extent()
134 : : {
135 : 0 : QString boundsStr = metadataValue( "bounds" );
136 : 0 : if ( boundsStr.isEmpty() )
137 : 0 : return QgsRectangle();
138 : 0 : QStringList boundsArray = boundsStr.split( ',' );
139 : 0 : if ( boundsArray.count() != 4 )
140 : 0 : return QgsRectangle();
141 : :
142 : 0 : return QgsRectangle( boundsArray[0].toDouble(), boundsArray[1].toDouble(),
143 : 0 : boundsArray[2].toDouble(), boundsArray[3].toDouble() );
144 : 0 : }
145 : :
146 : 0 : QByteArray QgsMbTiles::tileData( int z, int x, int y )
147 : : {
148 : 0 : if ( !mDatabase )
149 : : {
150 : 0 : QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
151 : 0 : return QByteArray();
152 : : }
153 : :
154 : : int result;
155 : 0 : 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 : 0 : sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
157 : 0 : if ( result != SQLITE_OK )
158 : : {
159 : 0 : QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
160 : 0 : return QByteArray();
161 : : }
162 : :
163 : 0 : if ( preparedStatement.step() != SQLITE_ROW )
164 : : {
165 : 0 : QgsDebugMsg( QStringLiteral( "MBTile not found: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
166 : 0 : return QByteArray();
167 : : }
168 : :
169 : 0 : return preparedStatement.columnAsBlob( 0 );
170 : 0 : }
171 : :
172 : 0 : QImage QgsMbTiles::tileDataAsImage( int z, int x, int y )
173 : : {
174 : 0 : QImage tileImage;
175 : 0 : QByteArray tileBlob = tileData( z, x, y );
176 : 0 : if ( !tileImage.loadFromData( tileBlob ) )
177 : : {
178 : 0 : QgsDebugMsg( QStringLiteral( "MBTile data failed to load: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
179 : 0 : return QImage();
180 : : }
181 : 0 : return tileImage;
182 : 0 : }
183 : :
184 : 0 : void QgsMbTiles::setTileData( int z, int x, int y, const QByteArray &data )
185 : : {
186 : 0 : if ( !mDatabase )
187 : : {
188 : 0 : QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
189 : 0 : return;
190 : : }
191 : :
192 : : int result;
193 : 0 : QString sql = QStringLiteral( "insert into tiles values (%1, %2, %3, ?)" ).arg( z ).arg( x ).arg( y );
194 : 0 : sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
195 : 0 : if ( result != SQLITE_OK )
196 : : {
197 : 0 : QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
198 : 0 : return;
199 : : }
200 : :
201 : 0 : sqlite3_bind_blob( preparedStatement.get(), 1, data.constData(), data.size(), SQLITE_TRANSIENT );
202 : :
203 : 0 : if ( preparedStatement.step() != SQLITE_DONE )
204 : : {
205 : 0 : QgsDebugMsg( QStringLiteral( "MBTile tile failed to be set: %1,%2,%3" ).arg( z ).arg( x ).arg( y ) );
206 : 0 : return;
207 : : }
208 : 0 : }
209 : :
210 : 0 : bool QgsMbTiles::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
211 : : {
212 : 0 : unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
213 : 0 : uint bytesInLeft = static_cast<uint>( bytesIn.count() );
214 : :
215 : 0 : const uint CHUNK = 16384;
216 : : unsigned char out[CHUNK];
217 : 0 : const int DEC_MAGIC_NUM_FOR_GZIP = 16;
218 : :
219 : : // allocate inflate state
220 : : z_stream strm;
221 : 0 : strm.zalloc = Z_NULL;
222 : 0 : strm.zfree = Z_NULL;
223 : 0 : strm.opaque = Z_NULL;
224 : 0 : strm.avail_in = 0;
225 : 0 : strm.next_in = Z_NULL;
226 : :
227 : 0 : int ret = inflateInit2( &strm, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP );
228 : 0 : if ( ret != Z_OK )
229 : 0 : return false;
230 : :
231 : 0 : while ( ret != Z_STREAM_END ) // done when inflate() says it's done
232 : : {
233 : : // prepare next chunk
234 : 0 : uint bytesToProcess = std::min( CHUNK, bytesInLeft );
235 : 0 : strm.next_in = bytesInPtr;
236 : 0 : strm.avail_in = bytesToProcess;
237 : 0 : bytesInPtr += bytesToProcess;
238 : 0 : bytesInLeft -= bytesToProcess;
239 : :
240 : 0 : if ( bytesToProcess == 0 )
241 : 0 : 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 : 0 : do
245 : : {
246 : 0 : strm.avail_out = CHUNK;
247 : 0 : strm.next_out = out;
248 : 0 : ret = inflate( &strm, Z_NO_FLUSH );
249 : : Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
250 : 0 : if ( ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR )
251 : : {
252 : 0 : inflateEnd( &strm );
253 : 0 : return false;
254 : : }
255 : 0 : unsigned have = CHUNK - strm.avail_out;
256 : 0 : bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
257 : 0 : }
258 : 0 : while ( strm.avail_out == 0 );
259 : : }
260 : :
261 : 0 : inflateEnd( &strm );
262 : 0 : return ret == Z_STREAM_END;
263 : 0 : }
264 : :
265 : :
266 : 0 : bool QgsMbTiles::encodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
267 : : {
268 : 0 : unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
269 : 0 : uint bytesInLeft = static_cast<uint>( bytesIn.count() );
270 : :
271 : 0 : const uint CHUNK = 16384;
272 : : unsigned char out[CHUNK];
273 : 0 : const int DEC_MAGIC_NUM_FOR_GZIP = 16;
274 : :
275 : : // allocate deflate state
276 : : z_stream strm;
277 : 0 : strm.zalloc = Z_NULL;
278 : 0 : strm.zfree = Z_NULL;
279 : 0 : strm.opaque = Z_NULL;
280 : :
281 : 0 : int ret = deflateInit2( &strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP, 8, Z_DEFAULT_STRATEGY );
282 : 0 : if ( ret != Z_OK )
283 : 0 : return false;
284 : :
285 : 0 : strm.avail_in = bytesInLeft;
286 : 0 : 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 : 0 : do
291 : : {
292 : 0 : strm.avail_out = CHUNK;
293 : 0 : strm.next_out = out;
294 : 0 : ret = deflate( &strm, Z_FINISH ); // no bad return value
295 : : Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
296 : :
297 : 0 : unsigned have = CHUNK - strm.avail_out;
298 : 0 : bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
299 : 0 : }
300 : 0 : while ( strm.avail_out == 0 );
301 : : Q_ASSERT( ret == Z_STREAM_END ); // stream will be complete
302 : :
303 : : // clean up and return
304 : 0 : deflateEnd( &strm );
305 : 0 : return true;
306 : 0 : }
|