diff --git a/python/PyQt6/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in b/python/PyQt6/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in index 36c9950e7f89..247199876d8a 100644 --- a/python/PyQt6/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in +++ b/python/PyQt6/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in @@ -676,6 +676,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 diff --git a/python/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in b/python/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in index 68c48d27231f..00e684e70041 100644 --- a/python/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in +++ b/python/core/auto_generated/vectortile/qgsmapboxglstyleconverter.sip.in @@ -676,6 +676,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 diff --git a/src/core/vectortile/qgsmapboxglstyleconverter.cpp b/src/core/vectortile/qgsmapboxglstyleconverter.cpp index 624d89a64dbc..27005e126acd 100644 --- a/src/core/vectortile/qgsmapboxglstyleconverter.cpp +++ b/src/core/vectortile/qgsmapboxglstyleconverter.cpp @@ -2664,6 +2664,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 ) ); @@ -2731,8 +2735,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 ); + } } @@ -2780,6 +2790,74 @@ 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() && ( 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, + 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" ) ) @@ -3351,14 +3429,10 @@ QString QgsMapBoxGlStyleConverter::retrieveSpriteAsBase64WithProperties( const Q } else if ( method == QLatin1String( "step" ) ) { - QString attribute = json.value( 1 ).toList().value( 0 ).toString(); - if ( attribute == QStringLiteral( "zoom" ) ) - { - attribute = QStringLiteral( "@vector_tile_zoom" ); - } - else + const QString expression = parseExpression( json.value( 1 ).toList(), context ); + if ( expression.isEmpty() ) { - context.pushWarning( QObject::tr( "%1: Could not interpret step list with attribute %2" ).arg( context.layerId(), attribute ) ); + context.pushWarning( QObject::tr( "%1: Could not interpret step list" ).arg( context.layerId() ) ); break; } @@ -3372,8 +3446,8 @@ QString QgsMapBoxGlStyleConverter::retrieveSpriteAsBase64WithProperties( const Q const QImage sprite = retrieveSprite( stepValue, context, spriteSize ); spritePath = prepareBase64( sprite ); - spriteProperty += QStringLiteral( " WHEN %1 >= %2 THEN '%3' " ).arg( attribute, stepKey, spritePath ); - spriteSizeProperty += QStringLiteral( " WHEN %1 >= %2 THEN %3 " ).arg( attribute ).arg( stepKey ).arg( spriteSize.width() ); + 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() ); } const QImage sprite = retrieveSprite( json.at( 2 ).toString(), context, spriteSize ); diff --git a/src/core/vectortile/qgsmapboxglstyleconverter.h b/src/core/vectortile/qgsmapboxglstyleconverter.h index 1f5d68d1f335..047f8bf3ee9e 100644 --- a/src/core/vectortile/qgsmapboxglstyleconverter.h +++ b/src/core/vectortile/qgsmapboxglstyleconverter.h @@ -671,6 +671,14 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter static QgsProperty parseMatchList( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1, int maxOpacity = 255, QColor *defaultColor SIP_OUT = nullptr, double *defaultNumber SIP_OUT = nullptr ); + /** + * Parses and converts a match function value list. + * + * \warning This is private API only, and may change in future QGIS versions + */ + static QgsProperty parseStepList( const QVariantList &json, PropertyType type, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1, + int maxOpacity = 255, QColor *defaultColor SIP_OUT = nullptr, double *defaultNumber SIP_OUT = nullptr ); + /** * Interpolates a list which starts with the interpolate function. * diff --git a/tests/src/python/test_qgsmapboxglconverter.py b/tests/src/python/test_qgsmapboxglconverter.py index 1337d43d077a..7c140042dfa5 100644 --- a/tests/src/python/test_qgsmapboxglconverter.py +++ b/tests/src/python/test_qgsmapboxglconverter.py @@ -223,6 +223,22 @@ def testParseMatchList(self): self.assertEqual(res.asExpression(), 'CASE WHEN "luminosity" IN (-15) THEN \'#c8d2d5\' WHEN "luminosity" IN (-14) THEN \'#cbd5d8\' WHEN "luminosity" IN (-13) THEN \'#cfd7da\' WHEN "luminosity" IN (-12) THEN \'#d2dadd\' WHEN "luminosity" IN (-11) THEN \'#d5dde0\' WHEN "luminosity" IN (-10) THEN \'#d9e0e2\' WHEN "luminosity" IN (-9) THEN \'#dce3e5\' WHEN "luminosity" IN (-8) THEN \'#e0e6e7\' WHEN "luminosity" IN (-7) THEN \'#e3e8ea\' WHEN "luminosity" IN (-6) THEN \'#e7ebed\' WHEN "luminosity" IN (-5) THEN \'#eaeeef\' WHEN "luminosity" IN (-4) THEN \'#eef1f2\' WHEN "luminosity" IN (-3) THEN \'#f1f4f5\' WHEN "luminosity" IN (-2) THEN \'#f5f7f7\' WHEN "luminosity" IN (-1) THEN \'#f8f9fa\' ELSE \'#fcfcfc\' END') self.assertTrue(qgsDoubleNear(default_number, 0.0)) + def testParseStepList(self): + conversion_context = QgsMapBoxGlStyleConversionContext() + res, default_color, default_number = QgsMapBoxGlStyleConverter.parseStepList([ + "step", + ["zoom"], + 0, + 7, ["match", ["get", "capital"], [2, 4], 1, 0], + 8, ["case", [">", 14, ["get", "rank"]], 1, 0], + 9, ["case", [">", 15, ["get", "rank"]], 1, 0], + 10, ["case", [">", 18, ["get", "rank"]], 1, 0], + 11, ["case", [">", 28, ["get", "rank"]], 1, 0], + 12, 1, + 13, 0 + ], QgsMapBoxGlStyleConverter.PropertyType.Opacity, conversion_context, 1, 100) + self.assertEqual(res.asExpression(), 'CASE WHEN @vector_tile_zoom >= 13 THEN (0) WHEN @vector_tile_zoom >= 12 THEN (100) WHEN @vector_tile_zoom >= 11 THEN (CASE WHEN ("28" > "rank") THEN 1 ELSE 0 END) WHEN @vector_tile_zoom >= 10 THEN (CASE WHEN ("18" > "rank") THEN 1 ELSE 0 END) WHEN @vector_tile_zoom >= 9 THEN (CASE WHEN ("15" > "rank") THEN 1 ELSE 0 END) WHEN @vector_tile_zoom >= 8 THEN (CASE WHEN ("14" > "rank") THEN 1 ELSE 0 END) WHEN @vector_tile_zoom >= 7 THEN (CASE WHEN "capital" IN (2,4) THEN 100 ELSE 0 END) ELSE (0) END') + def testParseValueList(self): conversion_context = QgsMapBoxGlStyleConversionContext() res, default_color, default_number = QgsMapBoxGlStyleConverter.parseValueList([