Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MBTiles raster support in WMS provider #33855

Merged
merged 2 commits into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SET (WMS_SRCS
qgsmbtilesreader.cpp
qgswmscapabilities.cpp
qgswmsprovider.cpp
qgswmsconnection.cpp
Expand Down
124 changes: 124 additions & 0 deletions src/providers/wms/qgsmbtilesreader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/***************************************************************************
qgsmbtilesreader.cpp
--------------------------------------
Date : January 2020
Copyright : (C) 2020 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#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;
}
50 changes: 50 additions & 0 deletions src/providers/wms/qgsmbtilesreader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/***************************************************************************
qgsmbtilesreader.h
--------------------------------------
Date : January 2020
Copyright : (C) 2020 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading