Skip to content

Commit

Permalink
MBTiles raster support in WMS provider
Browse files Browse the repository at this point in the history
  • Loading branch information
wonder-sk committed Jan 17, 2020
1 parent 7b916aa commit 4dc26a3
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 9 deletions.
10 changes: 10 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -7008,6 +7008,16 @@ bool QgisApp::openLayer( const QString &fileName, bool allowInteractive )
}
}

if ( fileName.endsWith( QStringLiteral( ".mbtiles" ), Qt::CaseInsensitive ) )
{
// prefer to use WMS provider's implementation to open MBTiles rasters
QUrlQuery uq;
uq.addQueryItem( "type", "mbtiles" );
uq.addQueryItem( "url", QUrl::fromLocalFile( fileName ).toString() );
if ( addRasterLayer( uq.toString(), fileInfo.completeBaseName(), QStringLiteral( "wms" ) ) )
return true;
}

// try to load it as raster
if ( QgsRasterLayer::isValidRasterFileName( fileName ) )
{
Expand Down
12 changes: 12 additions & 0 deletions src/core/providers/gdal/qgsgdaldataitems.cpp
Expand Up @@ -261,6 +261,18 @@ QgsDataItem *QgsGdalDataItemProvider::createDataItem( const QString &pathIn, Qgs
#endif
}

if ( suffix == QStringLiteral( "mbtiles" ) )
{
// handled by WMS provider
QUrlQuery uq;
uq.addQueryItem( "type", "mbtiles" );
uq.addQueryItem( "url", QUrl::fromLocalFile( path ).toString() );
QString encodedUri = uq.toString();
QgsLayerItem *item = new QgsLayerItem( parentItem, name, path, encodedUri, QgsLayerItem::Raster, QStringLiteral( "wms" ) );
item->setState( QgsDataItem::Populated );
return item;
}

// Filters out the OGR/GDAL supported formats that can contain multiple layers
// and should be treated like a DB: GeoPackage and SQLite
// NOTE: this formats are scanned for rasters too and they are handled
Expand Down
7 changes: 7 additions & 0 deletions src/core/qgssqliteutils.cpp
Expand Up @@ -56,6 +56,13 @@ QString sqlite3_statement_unique_ptr::columnAsText( int column ) const
return QString::fromUtf8( reinterpret_cast<const char *>( sqlite3_column_text( get(), column ) ) );
}

QByteArray sqlite3_statement_unique_ptr::columnAsBlob( int column ) const
{
const void *blob = sqlite3_column_blob( get(), column );
int size = sqlite3_column_bytes( get(), column );
return QByteArray( reinterpret_cast<const char *>( blob ), size );
}

qlonglong sqlite3_statement_unique_ptr::columnAsInt64( int column ) const
{
return sqlite3_column_int64( get(), column );
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgssqliteutils.h
Expand Up @@ -85,6 +85,11 @@ class CORE_EXPORT sqlite3_statement_unique_ptr : public std::unique_ptr< sqlite3
*/
QString columnAsText( int column ) const;

/**
* Returns the column value from the current statement row as raw byte array.
*/
QByteArray columnAsBlob( int column ) const;

/**
* Gets column value from the current statement row as a long long integer (64 bits).
*/
Expand Down
1 change: 1 addition & 0 deletions src/providers/wms/CMakeLists.txt
@@ -1,4 +1,5 @@
SET (WMS_SRCS
qgsmbtilesreader.cpp
qgswmscapabilities.cpp
qgswmsprovider.cpp
qgswmsconnection.cpp
Expand Down
109 changes: 109 additions & 0 deletions src/providers/wms/qgsmbtilesreader.cpp
@@ -0,0 +1,109 @@
#include "qgsmbtilesreader.h"

#include "qgslogger.h"
#include "qgsrectangle.h"

#include <QImage>


QgsMBTilesReader::QgsMBTilesReader( const QString &filename )
: mFilename( filename )
{
}

bool QgsMBTilesReader::open()
{
if ( mDatabase )
return true; // already opened

sqlite3_database_unique_ptr database;
int result = mDatabase.open_v2( mFilename, SQLITE_OPEN_READONLY, nullptr );
if ( result != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "Can't open MBTiles database: %1" ).arg( database.errorMessage() ) );
return false;
}
return true;
}

bool QgsMBTilesReader::isOpen() const
{
return bool( mDatabase );
}

QString QgsMBTilesReader::metadataValue( const QString &key )
{
if ( !mDatabase )
{
QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
return QString();
}

int result;
QString sql = QStringLiteral( "select value from metadata where name='%1'" ).arg( key );
sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
if ( result != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
return QString();
}

if ( preparedStatement.step() != SQLITE_ROW )
{
QgsDebugMsg( QStringLiteral( "MBTile metadata value not found: " ) + key );
return QString();
}

return preparedStatement.columnAsText( 0 );
}

QgsRectangle QgsMBTilesReader::extent()
{
QString boundsStr = metadataValue( "bounds" );
if ( boundsStr.isEmpty() )
return QgsRectangle();
QStringList boundsArray = boundsStr.split( ',' );
if ( boundsArray.count() != 4 )
return QgsRectangle();

return QgsRectangle( boundsArray[0].toDouble(), boundsArray[1].toDouble(),
boundsArray[2].toDouble(), boundsArray[3].toDouble() );
}

QByteArray QgsMBTilesReader::tileData( int z, int x, int y )
{
if ( !mDatabase )
{
QgsDebugMsg( QStringLiteral( "MBTiles database not open: " ) + mFilename );
return QByteArray();
}

int result;
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 );
sqlite3_statement_unique_ptr preparedStatement = mDatabase.prepare( sql, result );
if ( result != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "MBTile failed to prepare statement: " ) + sql );
return QByteArray();
}

if ( preparedStatement.step() != SQLITE_ROW )
{
QgsDebugMsg( QStringLiteral( "MBTile not found: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
return QByteArray();
}

return preparedStatement.columnAsBlob( 0 );
}

QImage QgsMBTilesReader::tileDataAsImage( int z, int x, int y )
{
QImage tileImage;
QByteArray tileBlob = tileData( z, x, y );
if ( !tileImage.loadFromData( tileBlob ) )
{
QgsDebugMsg( QStringLiteral( "MBTile data failed to load: z=%1 x=%2 y=%3" ).arg( z ).arg( x ).arg( y ) );
return QImage();
}
return tileImage;
}
35 changes: 35 additions & 0 deletions src/providers/wms/qgsmbtilesreader.h
@@ -0,0 +1,35 @@
#ifndef QGSMBTILESREADER_H
#define QGSMBTILESREADER_H


#include "sqlite3.h"
#include "qgssqliteutils.h"

class QImage;
class QgsRectangle;

class QgsMBTilesReader
{
public:
explicit QgsMBTilesReader( const QString &filename );

bool open();

bool isOpen() const;

QString metadataValue( const QString &key );

//! given in WGS 84 (if available)
QgsRectangle extent();

QByteArray tileData( int z, int x, int y );

QImage tileDataAsImage( int z, int x, int y );

private:
QString mFilename;
sqlite3_database_unique_ptr mDatabase;
};


#endif // QGSMBTILESREADER_H
6 changes: 5 additions & 1 deletion src/providers/wms/qgswmscapabilities.cpp
Expand Up @@ -53,7 +53,8 @@ bool QgsWmsSettings::parseUri( const QString &uriString )
mAuth.mReferer = uri.param( QStringLiteral( "referer" ) );
mXyz = false; // assume WMS / WMTS

if ( uri.param( QStringLiteral( "type" ) ) == QLatin1String( "xyz" ) )
if ( uri.param( QStringLiteral( "type" ) ) == QLatin1String( "xyz" ) ||
uri.param( QStringLiteral( "type" ) ) == QLatin1String( "mbtiles" ) )
{
// for XYZ tiles most of the things do not apply
mTiled = true;
Expand All @@ -75,6 +76,9 @@ bool QgsWmsSettings::parseUri( const QString &uriString )
mImageMimeType.clear();
mCrsId = QStringLiteral( "EPSG:3857" );
mEnableContextualLegend = false;

mIsMBTiles = uri.param( QStringLiteral( "type" ) ) == QLatin1String( "mbtiles" );

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions src/providers/wms/qgswmscapabilities.h
Expand Up @@ -588,6 +588,8 @@ class QgsWmsSettings
bool mTiled;
//! whether we actually work with XYZ tiles instead of WMS / WMTS
bool mXyz;
//! whether we are dealing with MBTiles file rather than using network-based tiles
bool mIsMBTiles = false;
//! chosen values for dimensions in case of multi-dimensional data (key=dim id, value=dim value)
QHash<QString, QString> mTileDimensionValues;
//! name of the chosen tile matrix set
Expand Down

0 comments on commit 4dc26a3

Please sign in to comment.