Skip to content

Commit

Permalink
Merge pull request #58233 from 3nids/mvt-sprites
Browse files Browse the repository at this point in the history
[mvt] implement step expressions + improve conversion of sprites
  • Loading branch information
3nids authored Aug 16, 2024
2 parents 4e4329c + 0f762cc commit 9d87ecf
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,16 @@ Parses and converts a value list (e.g. an interpolate list).
%Docstring
Parses and converts a match function value list.

.. warning::

This is private API only, and may change in future QGIS versions
%End

static QgsProperty parseStepList( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1,
int maxOpacity = 255, QColor *defaultColor /Out/ = 0, double *defaultNumber /Out/ = 0 );
%Docstring
Parses and converts a match function value list.

.. warning::

This is private API only, and may change in future QGIS versions
Expand Down Expand Up @@ -776,12 +786,22 @@ The ``context`` must have valid sprite definitions and images set via :py:func:`
prior to conversion.
%End

static QString retrieveSpriteAsBase64( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize, QString &spriteProperty, QString &spriteSizeProperty );
static QString retrieveSpriteAsBase64( const QVariant &value, QgsMapBoxGlStyleConversionContext &context );
%Docstring
Retrieves the sprite image with the specified ``name``, taken from the specified ``context`` as a base64 encoded value

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

static QString retrieveSpriteAsBase64WithProperties( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize /Out/, QString &spriteProperty /Out/, QString &spriteSizeProperty /Out/ );
%Docstring
Retrieves the sprite image with the specified ``name``, taken from the specified ``context`` as a base64 encoded value

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

.. versionadded:: 3.40
%End

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,16 @@ Parses and converts a value list (e.g. an interpolate list).
%Docstring
Parses and converts a match function value list.

.. warning::

This is private API only, and may change in future QGIS versions
%End

static QgsProperty parseStepList( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1,
int maxOpacity = 255, QColor *defaultColor /Out/ = 0, double *defaultNumber /Out/ = 0 );
%Docstring
Parses and converts a match function value list.

.. warning::

This is private API only, and may change in future QGIS versions
Expand Down Expand Up @@ -776,12 +786,22 @@ The ``context`` must have valid sprite definitions and images set via :py:func:`
prior to conversion.
%End

static QString retrieveSpriteAsBase64( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize, QString &spriteProperty, QString &spriteSizeProperty );
static QString retrieveSpriteAsBase64( const QVariant &value, QgsMapBoxGlStyleConversionContext &context );
%Docstring
Retrieves the sprite image with the specified ``name``, taken from the specified ``context`` as a base64 encoded value

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

static QString retrieveSpriteAsBase64WithProperties( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize /Out/, QString &spriteProperty /Out/, QString &spriteSizeProperty /Out/ );
%Docstring
Retrieves the sprite image with the specified ``name``, taken from the specified ``context`` as a base64 encoded value

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

.. versionadded:: 3.40
%End

private:
Expand Down
228 changes: 185 additions & 43 deletions src/core/vectortile/qgsmapboxglstyleconverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, Qg

QSize spriteSize;
QString spriteProperty, spriteSizeProperty;
const QString sprite = retrieveSpriteAsBase64( fillPatternJson, context, spriteSize, spriteProperty, spriteSizeProperty );
const QString sprite = retrieveSpriteAsBase64WithProperties( fillPatternJson, context, spriteSize, spriteProperty, spriteSizeProperty );
if ( !sprite.isEmpty() )
{
// when fill-pattern exists, set and insert QgsRasterFillSymbolLayer
Expand Down Expand Up @@ -496,7 +496,7 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
{
QSize spriteSize;
QString spriteProperty, spriteSizeProperty;
rasterLineSprite = retrieveSpriteAsBase64( jsonLinePattern, context, spriteSize, spriteProperty, spriteSizeProperty );
rasterLineSprite = retrieveSpriteAsBase64WithProperties( jsonLinePattern, context, spriteSize, spriteProperty, spriteSizeProperty );
ddProperties.setProperty( QgsSymbolLayer::Property::File, QgsProperty::fromExpression( spriteProperty ) );
break;
}
Expand Down Expand Up @@ -1850,7 +1850,7 @@ void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer,
{
QSize spriteSize;
QString spriteProperty, spriteSizeProperty;
const QString sprite = retrieveSpriteAsBase64( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
const QString sprite = retrieveSpriteAsBase64WithProperties( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
if ( !sprite.isEmpty() )
{
QgsRasterMarkerSymbolLayer *markerLayer = new QgsRasterMarkerSymbolLayer( );
Expand Down Expand Up @@ -1998,7 +1998,7 @@ bool QgsMapBoxGlStyleConverter::parseSymbolLayerAsRenderer( const QVariantMap &j
QgsRasterMarkerSymbolLayer *markerLayer = new QgsRasterMarkerSymbolLayer( );
QSize spriteSize;
QString spriteProperty, spriteSizeProperty;
const QString sprite = retrieveSpriteAsBase64( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
const QString sprite = retrieveSpriteAsBase64WithProperties( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
if ( !sprite.isNull() )
{
markerLayer->setPath( sprite );
Expand Down Expand Up @@ -2078,8 +2078,8 @@ bool QgsMapBoxGlStyleConverter::parseSymbolLayerAsRenderer( const QVariantMap &j

QSize spriteSize;
QString spriteProperty, spriteSizeProperty;
const QString sprite = retrieveSpriteAsBase64( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
if ( !sprite.isEmpty() )
const QString sprite = retrieveSpriteAsBase64WithProperties( jsonLayout.value( QStringLiteral( "icon-image" ) ), context, spriteSize, spriteProperty, spriteSizeProperty );
if ( !sprite.isEmpty() || !spriteProperty.isEmpty() )
{
QgsRasterMarkerSymbolLayer *rasterMarker = new QgsRasterMarkerSymbolLayer( );
rasterMarker->setPath( sprite );
Expand Down Expand Up @@ -2676,6 +2676,10 @@ QgsProperty QgsMapBoxGlStyleConverter::parseValueList( const QVariantList &json,
{
return parseMatchList( json, type, context, multiplier, maxOpacity, defaultColor, defaultNumber );
}
else if ( method == QLatin1String( "step" ) )
{
return parseStepList( json, type, context, multiplier, maxOpacity, defaultColor, defaultNumber );
}
else
{
return QgsProperty::fromExpression( parseExpression( json, context ) );
Expand Down Expand Up @@ -2743,8 +2747,14 @@ QgsProperty QgsMapBoxGlStyleConverter::parseMatchList( const QVariantList &json,

}

caseString += QStringLiteral( "WHEN %1 IN (%2) THEN %3 " ).arg( attribute,
matchString.join( ',' ), valueString );
if ( matchString.count() == 1 )
{
caseString += QStringLiteral( "WHEN %1 IS %2 THEN %3 " ).arg( attribute, matchString.at( 0 ), valueString );
}
else
{
caseString += QStringLiteral( "WHEN %1 IN (%2) THEN %3 " ).arg( attribute, matchString.join( ',' ), valueString );
}
}


Expand Down Expand Up @@ -2792,6 +2802,76 @@ QgsProperty QgsMapBoxGlStyleConverter::parseMatchList( const QVariantList &json,
return QgsProperty::fromExpression( caseString );
}

QgsProperty QgsMapBoxGlStyleConverter::parseStepList( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier, int maxOpacity, QColor *defaultColor, double *defaultNumber )
{
const QString expression = parseExpression( json.value( 1 ).toList(), context );
if ( expression.isEmpty() )
{
context.pushWarning( QObject::tr( "%1: Could not interpret match list" ).arg( context.layerId() ) );
return QgsProperty();
}

QString caseString = QStringLiteral( "CASE " );


for ( int i = json.length() - 2; i > 0; i -= 2 )
{
const QVariant stepValue = json.value( i + 1 );

QString valueString;
if ( stepValue.canConvert<QVariantList>() && ( stepValue.toList().count() != 2 || type != PropertyType::Point ) )
{
valueString = parseValueList( stepValue.toList(), type, context, multiplier, maxOpacity, defaultColor, defaultNumber ).expressionString();
}
else
{
switch ( type )
{
case PropertyType::Color:
{
const QColor color = parseColor( stepValue, context );
valueString = QgsExpression::quotedString( color.name() );
break;
}

case PropertyType::Numeric:
{
const double v = stepValue.toDouble() * multiplier;
valueString = QString::number( v );
break;
}

case PropertyType::Opacity:
{
const double v = stepValue.toDouble() * maxOpacity;
valueString = QString::number( v );
break;
}

case PropertyType::Point:
{
valueString = QStringLiteral( "array(%1,%2)" ).arg(
stepValue.toList().value( 0 ).toDouble() * multiplier ).arg(
stepValue.toList().value( 0 ).toDouble() * multiplier
);
break;
}
}
}

if ( i > 1 )
{
const QString stepKey = QgsExpression::quotedValue( json.value( i ) );
caseString += QStringLiteral( " WHEN %1 >= %2 THEN (%3) " ).arg( expression, stepKey, valueString );
}
else
{
caseString += QStringLiteral( "ELSE (%1) END" ).arg( valueString );
}
}
return QgsProperty::fromExpression( caseString );
}

QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateListByZoom( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier, int maxOpacity, QColor *defaultColor, double *defaultNumber )
{
if ( json.value( 0 ).toString() != QLatin1String( "interpolate" ) )
Expand Down Expand Up @@ -3143,6 +3223,10 @@ QString QgsMapBoxGlStyleConverter::parseExpression( const QVariantList &expressi
caseString += QStringLiteral( " ELSE %1 END" ).arg( value );
return caseString;
}
else if ( op == QLatin1String( "zoom" ) && expression.count() == 1 )
{
return QStringLiteral( "@vector_tile_zoom" );
}
else
{
context.pushWarning( QObject::tr( "%1: Skipping unsupported expression \"%2\"" ).arg( context.layerId(), op ) );
Expand Down Expand Up @@ -3179,7 +3263,7 @@ QImage QgsMapBoxGlStyleConverter::retrieveSprite( const QString &name, QgsMapBox
return sprite;
}

QString QgsMapBoxGlStyleConverter::retrieveSpriteAsBase64( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize, QString &spriteProperty, QString &spriteSizeProperty )
QString QgsMapBoxGlStyleConverter::retrieveSpriteAsBase64WithProperties( const QVariant &value, QgsMapBoxGlStyleConversionContext &context, QSize &spriteSize, QString &spriteProperty, QString &spriteSizeProperty )
{
QString spritePath;

Expand Down Expand Up @@ -3309,53 +3393,111 @@ QString QgsMapBoxGlStyleConverter::retrieveSpriteAsBase64( const QVariant &value
{
const QVariantList json = value.toList();
const QString method = json.value( 0 ).toString();
if ( method != QLatin1String( "match" ) )
{
context.pushWarning( QObject::tr( "%1: Could not interpret sprite value list with method %2" ).arg( context.layerId(), method ) );
break;
}

const QString attribute = parseExpression( json.value( 1 ).toList(), context );
if ( attribute.isEmpty() )
if ( method == QLatin1String( "match" ) )
{
context.pushWarning( QObject::tr( "%1: Could not interpret match list" ).arg( context.layerId() ) );
break;
}
const QString attribute = parseExpression( json.value( 1 ).toList(), context );
if ( attribute.isEmpty() )
{
context.pushWarning( QObject::tr( "%1: Could not interpret match list" ).arg( context.layerId() ) );
break;
}

spriteProperty = QStringLiteral( "CASE " );
spriteSizeProperty = QStringLiteral( "CASE " );
spriteProperty = QStringLiteral( "CASE" );
spriteSizeProperty = QStringLiteral( "CASE" );

for ( int i = 2; i < json.length() - 1; i += 2 )
{
const QVariantList keys = json.value( i ).toList();
for ( int i = 2; i < json.length() - 1; i += 2 )
{
const QVariant matchKey = json.value( i );
const QVariant matchValue = json.value( i + 1 );
QString matchString;
switch ( matchKey.userType() )
{
case QMetaType::Type::QVariantList:
case QMetaType::Type::QStringList:
{
const QVariantList keys = matchKey.toList();
QStringList matchStringList;
for ( const QVariant &key : keys )
{
matchStringList << QgsExpression::quotedValue( key );
}
matchString = matchStringList.join( ',' );
break;
}

case QMetaType::Type::Bool:
case QMetaType::Type::QString:
case QMetaType::Type::Int:
case QMetaType::Type::LongLong:
case QMetaType::Type::Double:
{
matchString = QgsExpression::quotedValue( matchKey );
break;
}

QStringList matchString;
for ( const QVariant &key : keys )
default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported sprite type (%2)." ).arg( context.layerId(), QMetaType::typeName( static_cast<QMetaType::Type>( value.userType() ) ) ) );
break;

}

const QImage sprite = retrieveSprite( matchValue.toString(), context, spriteSize );
spritePath = prepareBase64( sprite );

spriteProperty += QStringLiteral( " WHEN %1 IN (%2) "
"THEN '%3'" ).arg( attribute,
matchString,
spritePath );

spriteSizeProperty += QStringLiteral( " WHEN %1 IN (%2) "
"THEN %3" ).arg( attribute,
matchString ).arg( spriteSize.width() );
}

const QImage sprite = retrieveSprite( json.constLast().toString(), context, spriteSize );
spritePath = prepareBase64( sprite );

spriteProperty += QStringLiteral( " ELSE '%1' END" ).arg( spritePath );
spriteSizeProperty += QStringLiteral( " ELSE %3 END" ).arg( spriteSize.width() );
break;
}
else if ( method == QLatin1String( "step" ) )
{
const QString expression = parseExpression( json.value( 1 ).toList(), context );
if ( expression.isEmpty() )
{
matchString << QgsExpression::quotedValue( key );
context.pushWarning( QObject::tr( "%1: Could not interpret step list" ).arg( context.layerId() ) );
break;
}

const QVariant value = json.value( i + 1 );
spriteProperty = QStringLiteral( "CASE" );
spriteSizeProperty = QStringLiteral( "CASE" );
for ( int i = json.length() - 2; i > 2; i -= 2 )
{
const QString stepKey = QgsExpression::quotedValue( json.value( i ) );
const QString stepValue = json.value( i + 1 ).toString();

const QImage sprite = retrieveSprite( value.toString(), context, spriteSize );
spritePath = prepareBase64( sprite );
const QImage sprite = retrieveSprite( stepValue, context, spriteSize );
spritePath = prepareBase64( sprite );

spriteProperty += QStringLiteral( " WHEN %1 IN (%2) "
"THEN '%3' " ).arg( attribute,
matchString.join( ',' ),
spritePath );
spriteProperty += QStringLiteral( " WHEN %1 >= %2 THEN '%3' " ).arg( expression, stepKey, spritePath );
spriteSizeProperty += QStringLiteral( " WHEN %1 >= %2 THEN %3 " ).arg( expression ).arg( stepKey ).arg( spriteSize.width() );
}

spriteSizeProperty += QStringLiteral( " WHEN %1 IN (%2) "
"THEN %3 " ).arg( attribute,
matchString.join( ',' ) ).arg( spriteSize.width() );
}
const QImage sprite = retrieveSprite( json.at( 2 ).toString(), context, spriteSize );
spritePath = prepareBase64( sprite );

const QImage sprite = retrieveSprite( json.constLast().toString(), context, spriteSize );
spritePath = prepareBase64( sprite );
spriteProperty += QStringLiteral( "ELSE '%1' END" ).arg( spritePath );
spriteSizeProperty += QStringLiteral( "ELSE %3 END" ).arg( spriteSize.width() );
break;

spriteProperty += QStringLiteral( "ELSE %1 END" ).arg( spritePath );
spriteSizeProperty += QStringLiteral( "ELSE %3 END" ).arg( spriteSize.width() );
break;
}
else
{
context.pushWarning( QObject::tr( "%1: Could not interpret sprite value list with method %2" ).arg( context.layerId(), method ) );
break;
}
}

default:
Expand Down
Loading

0 comments on commit 9d87ecf

Please sign in to comment.