Skip to content
Permalink
Browse files

Add support for parsing and converting MapBox GL raster fills to QGIS…

… symbology
  • Loading branch information
nyalldawson committed Sep 9, 2020
1 parent dcf8728 commit b6846295fec88cb5cc058d9db8e9e8f3b14e0407
@@ -84,6 +84,42 @@ Sets the pixel size conversion factor, used to scale the original pixel sizes
when converting styles.

.. seealso:: :py:func:`pixelSizeConversionFactor`
%End

QImage spriteImage() const;
%Docstring
Returns the sprite image to use during conversion, or an invalid image if this is not set.

.. seealso:: :py:func:`spriteDefinitions`

.. seealso:: :py:func:`setSprites`
%End

QVariantMap spriteDefinitions() const;
%Docstring
Returns the sprite definitions to use during conversion.

.. seealso:: :py:func:`spriteImage`

.. seealso:: :py:func:`setSprites`
%End

void setSprites( const QImage &image, const QVariantMap &definitions );
%Docstring
Sets the sprite ``image`` and ``definitions`` JSON to use during conversion.

.. seealso:: :py:func:`spriteImage`

.. seealso:: :py:func:`spriteDefinitions`
%End

void setSprites( const QImage &image, const QString &definitions );
%Docstring
Sets the sprite ``image`` and ``definitions`` JSON string to use during conversion.

.. seealso:: :py:func:`spriteImage`

.. seealso:: :py:func:`spriteDefinitions`
%End

};
@@ -374,6 +410,14 @@ Converts a MapBox GL expression to a QGIS expression.
This is private API only, and may change in future QGIS versions
%End

static QImage retrieveSprite( const QString &name, QgsMapBoxGlStyleConversionContext &context );
%Docstring
Retrieves the sprite image with the specified ``name``, taken from the specified ``context``.

The ``context`` must have valid sprite definitions and images set via :py:func:`QgsMapBoxGlStyleConversionContext.setSprites()`
prior to conversion.
%End

private:
QgsMapBoxGlStyleConverter( const QgsMapBoxGlStyleConverter &other );
};
@@ -283,32 +283,59 @@ bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, Qg

if ( jsonPaint.contains( QStringLiteral( "fill-pattern" ) ) )
{
context.pushWarning( QObject::tr( "fill-pattern is not yet supported" ) );
#if 0 // todo
// get fill-pattern to set sprite
// sprite imgs will already have been downloaded in converter.py
fill_pattern = json_paint.get( "fill-pattern" )

// fill-pattern can be String or Object
// String: {"fill-pattern": "dash-t"}
// Object: {"fill-pattern":{"stops":[[11,"wetland8"],[12,"wetland16"]]}}
const QVariant fillPatternJson = jsonPaint.value( QStringLiteral( "fill-pattern" ) );

// if Object, simpify into one sprite.
// TODO:
if isinstance( fill_pattern, dict )
// fill-pattern can be String or Object
// String: {"fill-pattern": "dash-t"}
// Object: {"fill-pattern":{"stops":[[11,"wetland8"],[12,"wetland16"]]}}

switch ( fillPatternJson.type() )
{
pattern_stops = fill_pattern.get( "stops", [None] )
fill_pattern = pattern_stops[-1][-1]
}
case QVariant::String:
{
const QImage sprite = retrieveSprite( fillPatternJson.toString(), context );

// when fill-pattern exists, set and insert RasterFillSymbolLayer
if fill_pattern
{
SPRITES_PATH = os.path.join( os.path.dirname( os.path.realpath( __file__ ) ), "sprites" )
raster_fill_symbol = QgsRasterFillSymbolLayer( os.path.join( SPRITES_PATH, fill_pattern + ".png" ) )
sym.appendSymbolLayer( raster_fill_symbol )
}
if ( !sprite.isNull() )
{
// when fill-pattern exists, set and insert QgsRasterFillSymbolLayer
QgsRasterFillSymbolLayer *rasterFill = new QgsRasterFillSymbolLayer();

QByteArray blob;
QBuffer buffer( &blob );
buffer.open( QIODevice::WriteOnly );
sprite.save( &buffer, "PNG" );
buffer.close();
QByteArray encoded = blob.toBase64();

QString path( encoded );
path.prepend( QLatin1String( "base64:" ) );
rasterFill->setImageFilePath( path );

symbol->appendSymbolLayer( rasterFill );
}
break;
}

case QVariant::Map:
{
#if 0
// if Object, simpify into one sprite.
// TODO:
if isinstance( fill_pattern, dict )
{
pattern_stops = fill_pattern.get( "stops", [None] )
fill_pattern = pattern_stops[-1][-1]
}
#endif
FALLTHROUGH
}

default:

break;
}
}

fillSymbol->setDataDefinedProperties( ddProperties );
@@ -1442,6 +1469,34 @@ QString QgsMapBoxGlStyleConverter::parseExpression( const QVariantList &expressi
}
}

QImage QgsMapBoxGlStyleConverter::retrieveSprite( const QString &name, QgsMapBoxGlStyleConversionContext &context )
{
if ( context.spriteImage().isNull() )
{
context.pushWarning( QObject::tr( "Could not retrieve sprite '%1'" ).arg( name ) );
return QImage();
}

const QVariantMap spriteDefinition = context.spriteDefinitions().value( name ).toMap();
if ( spriteDefinition.size() == 0 )
{
context.pushWarning( QObject::tr( "Could not retrieve sprite '%1'" ).arg( name ) );
return QImage();
}

const QImage sprite = context.spriteImage().copy( spriteDefinition.value( QStringLiteral( "x" ) ).toInt(),
spriteDefinition.value( QStringLiteral( "y" ) ).toInt(),
spriteDefinition.value( QStringLiteral( "width" ) ).toInt(),
spriteDefinition.value( QStringLiteral( "height" ) ).toInt() );
if ( sprite.isNull() )
{
context.pushWarning( QObject::tr( "Could not retrieve sprite '%1'" ).arg( name ) );
return QImage();
}

return sprite;
}

QString QgsMapBoxGlStyleConverter::parseValue( const QVariant &value, QgsMapBoxGlStyleConversionContext &context )
{
switch ( value.type() )
@@ -1516,3 +1571,24 @@ void QgsMapBoxGlStyleConversionContext::setPixelSizeConversionFactor( double siz
{
mSizeConversionFactor = sizeConversionFactor;
}

QImage QgsMapBoxGlStyleConversionContext::spriteImage() const
{
return mSpriteImage;
}

QVariantMap QgsMapBoxGlStyleConversionContext::spriteDefinitions() const
{
return mSpriteDefinitions;
}

void QgsMapBoxGlStyleConversionContext::setSprites( const QImage &image, const QVariantMap &definitions )
{
mSpriteImage = image;
mSpriteDefinitions = definitions;
}

void QgsMapBoxGlStyleConversionContext::setSprites( const QImage &image, const QString &definitions )
{
setSprites( image, QgsJsonUtils::parseJson( definitions ).toMap() );
}
@@ -97,13 +97,48 @@ class CORE_EXPORT QgsMapBoxGlStyleConversionContext
*/
void setPixelSizeConversionFactor( double sizeConversionFactor );

/**
* Returns the sprite image to use during conversion, or an invalid image if this is not set.
*
* \see spriteDefinitions()
* \see setSprites()
*/
QImage spriteImage() const;

/**
* Returns the sprite definitions to use during conversion.
*
* \see spriteImage()
* \see setSprites()
*/
QVariantMap spriteDefinitions() const;

/**
* Sets the sprite \a image and \a definitions JSON to use during conversion.
*
* \see spriteImage()
* \see spriteDefinitions()
*/
void setSprites( const QImage &image, const QVariantMap &definitions );

/**
* Sets the sprite \a image and \a definitions JSON string to use during conversion.
*
* \see spriteImage()
* \see spriteDefinitions()
*/
void setSprites( const QImage &image, const QString &definitions );

private:

QStringList mWarnings;

QgsUnitTypes::RenderUnit mTargetUnit = QgsUnitTypes::RenderPixels;

double mSizeConversionFactor = 1.0;

QImage mSpriteImage;
QVariantMap mSpriteDefinitions;
};

/**
@@ -369,6 +404,14 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*/
static QString parseExpression( const QVariantList &expression, QgsMapBoxGlStyleConversionContext &context );

/**
* Retrieves the sprite image with the specified \a name, taken from the specified \a context.
*
* The \a context must have valid sprite definitions and images set via QgsMapBoxGlStyleConversionContext::setSprites()
* prior to conversion.
*/
static QImage retrieveSprite( const QString &name, QgsMapBoxGlStyleConversionContext &context );

private:

#ifdef SIP_RUN
@@ -29,6 +29,7 @@
#include "qgslayermetadataformatter.h"
#include "qgsblockingnetworkrequest.h"
#include "qgsmapboxglstyleconverter.h"
#include "qgsjsonutils.h"

QgsVectorTileLayer::QgsVectorTileLayer( const QString &uri, const QString &baseName )
: QgsMapLayer( QgsMapLayerType::VectorTileLayer, baseName )
@@ -329,7 +330,7 @@ QString QgsVectorTileLayer::loadDefaultStyle( bool &resultFlag )
}

const QgsNetworkReplyContent content = networkRequest.reply();
const QByteArray raw = content.content();
const QVariantMap styleDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();

QgsMapBoxGlStyleConversionContext context;
// convert automatically from pixel sizes to millimeters, because pixel sizes
@@ -338,8 +339,58 @@ QString QgsVectorTileLayer::loadDefaultStyle( bool &resultFlag )
//assume source uses 96 dpi
context.setPixelSizeConversionFactor( 25.4 / 96.0 );

if ( styleDefinition.contains( QStringLiteral( "sprite" ) ) )
{
// retrieve sprite definition
const QString spriteUriBase = mArcgisLayerConfiguration.value( QStringLiteral( "serviceUri" ) ).toString()
+ '/' + mArcgisLayerConfiguration.value( QStringLiteral( "defaultStyles" ) ).toString()
+ '/' + styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
QNetworkRequest request = QNetworkRequest( QUrl( spriteUriBase + QStringLiteral( ".json" ) ) );

QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) );

QgsBlockingNetworkRequest networkRequest;
switch ( networkRequest.get( request ) )
{
case QgsBlockingNetworkRequest::NoError:
{
const QgsNetworkReplyContent content = networkRequest.reply();
const QVariantMap spriteDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();

// retrieve sprite images
QNetworkRequest request = QNetworkRequest( QUrl( spriteUriBase + QStringLiteral( ".png" ) ) );

QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) );

QgsBlockingNetworkRequest networkRequest;
switch ( networkRequest.get( request ) )
{
case QgsBlockingNetworkRequest::NoError:
{
const QgsNetworkReplyContent imageContent = networkRequest.reply();
QImage spriteImage( QImage::fromData( imageContent.content() ) );
context.setSprites( spriteImage, spriteDefinition );
break;
}

case QgsBlockingNetworkRequest::NetworkError:
case QgsBlockingNetworkRequest::TimeoutError:
case QgsBlockingNetworkRequest::ServerExceptionError:
break;
}

break;
}

case QgsBlockingNetworkRequest::NetworkError:
case QgsBlockingNetworkRequest::TimeoutError:
case QgsBlockingNetworkRequest::ServerExceptionError:
break;
}
}

QgsMapBoxGlStyleConverter converter;
if ( converter.convert( raw, &context ) != QgsMapBoxGlStyleConverter::Success )
if ( converter.convert( styleDefinition, &context ) != QgsMapBoxGlStyleConverter::Success )
{
resultFlag = false;
return converter.errorMessage();

0 comments on commit b684629

Please sign in to comment.
You can’t perform that action at this time.