Skip to content
Permalink
Browse files
[vectortile] Fix mapbox gl converter line-dasharray handling (#46082)
  • Loading branch information
nirvn committed Nov 18, 2021
1 parent d99460e commit 91c536466690eb04466d4a4e1ae03c3e2180fa13
@@ -397,6 +397,15 @@ For ``json`` with intermediate stops it uses :py:func:`~QgsMapBoxGlStyleConverte
Takes values from stops and uses either :py:func:`~QgsMapBoxGlStyleConverter.scale_linear` or :py:func:`~QgsMapBoxGlStyleConverter.scale_exp` functions
to interpolate point/offset values.

.. warning::

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

static QString parseArrayStops( const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1 );
%Docstring
Takes numerical arrays from stops.

.. warning::

This is private API only, and may change in future QGIS versions
@@ -472,6 +472,7 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg


double lineWidth = 1.0;
QgsProperty lineWidthProperty;
if ( jsonPaint.contains( QStringLiteral( "line-width" ) ) )
{
const QVariant jsonLineWidth = jsonPaint.value( QStringLiteral( "line-width" ) );
@@ -484,12 +485,14 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg

case QVariant::Map:
lineWidth = -1;
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateByZoom( jsonLineWidth.toMap(), context, context.pixelSizeConversionFactor(), &lineWidth ) );
lineWidthProperty = parseInterpolateByZoom( jsonLineWidth.toMap(), context, context.pixelSizeConversionFactor(), &lineWidth );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, lineWidthProperty );
break;

case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseValueList( jsonLineWidth.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &lineWidth ) );
lineWidthProperty = parseValueList( jsonLineWidth.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor(), 255, nullptr, &lineWidth );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, lineWidthProperty );
break;

default:
@@ -573,22 +576,41 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
{
case QVariant::Map:
{
//TODO improve parsing (use PropertyCustomDash?)
const QVariantList dashSource = jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList().last().toList().value( 1 ).toList();
QString arrayExpression;
if ( !lineWidthProperty.asExpression().isEmpty() )
{
arrayExpression = QStringLiteral( "array_to_string(array_foreach(%1,@element * (%2)), ';')" ) // skip-keyword-check
.arg( parseArrayStops( jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList(), context, 1 ),
lineWidthProperty.asExpression() );
}
else
{
arrayExpression = QStringLiteral( "array_to_string(%1, ';')" ).arg( parseArrayStops( jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList(), context, lineWidth ) );
}
ddProperties.setProperty( QgsSymbolLayer::PropertyCustomDash, QgsProperty::fromExpression( arrayExpression ) );

const QVariantList dashSource = jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList().first().toList().value( 1 ).toList();
for ( const QVariant &v : dashSource )
{
dashVector << v.toDouble() * context.pixelSizeConversionFactor();
dashVector << v.toDouble() * lineWidth;
}
break;
}

case QVariant::List:
case QVariant::StringList:
{
if ( ( !lineWidthProperty.asExpression().isEmpty() ) )
{
QString arrayExpression = QStringLiteral( "array_to_string(array_foreach(array(%1),@element * (%2)), ';')" ) // skip-keyword-check
.arg( jsonLineDashArray.toStringList().join( ',' ),
lineWidthProperty.asExpression() );
ddProperties.setProperty( QgsSymbolLayer::PropertyCustomDash, QgsProperty::fromExpression( arrayExpression ) );
}
const QVariantList dashSource = jsonLineDashArray.toList();
for ( const QVariant &v : dashSource )
{
dashVector << v.toDouble() * context.pixelSizeConversionFactor();
dashVector << v.toDouble() * lineWidth;
}
break;
}
@@ -2276,6 +2298,51 @@ QString QgsMapBoxGlStyleConverter::parsePointStops( double base, const QVariantL
return caseString;
}

QString QgsMapBoxGlStyleConverter::parseArrayStops( const QVariantList &stops, QgsMapBoxGlStyleConversionContext &, double multiplier )
{
if ( stops.length() < 2 )
return QString();

QString caseString = QStringLiteral( "CASE " );

for ( int i = 0; i < stops.length() - 1; ++i )
{
// bottom zoom and value
const QVariant bz = stops.value( i ).toList().value( 0 );
const QList<QVariant> bv = stops.value( i ).toList().value( 1 ).toList();
QStringList bl;
bool ok = false;
for ( const QVariant &value : bv )
{
const double number = value.toDouble( &ok );
if ( ok )
bl << QString::number( number * multiplier );
}

// top zoom and value
const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 AND @vector_tile_zoom <= %2 "
"THEN array(%3) " ).arg( bz.toString(),
tz.toString(),
bl.join( ',' ) );
}
const QVariant lz = stops.value( stops.length() - 1 ).toList().value( 0 );
const QList<QVariant> lv = stops.value( stops.length() - 1 ).toList().value( 1 ).toList();
QStringList ll;
bool ok = false;
for ( const QVariant &value : lv )
{
const double number = value.toDouble( &ok );
if ( ok )
ll << QString::number( number * multiplier );
}
caseString += QStringLiteral( "WHEN @vector_tile_zoom > %1 "
"THEN array(%2) " ).arg( lz.toString(),
ll.join( ',' ) );
caseString += QLatin1String( "END" );
return caseString;
}

QString QgsMapBoxGlStyleConverter::parseStops( double base, const QVariantList &stops, double multiplier, QgsMapBoxGlStyleConversionContext &context )
{
QString caseString = QStringLiteral( "CASE " );
@@ -402,6 +402,13 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*/
static QString parsePointStops( double base, const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1 );

/**
* Takes numerical arrays from stops.
*
* \warning This is private API only, and may change in future QGIS versions
*/
static QString parseArrayStops( const QVariantList &stops, QgsMapBoxGlStyleConversionContext &context, double multiplier = 1 );

/**
* Parses a list of interpolation stops
*
@@ -653,6 +653,47 @@ def testCircleLayer(self):
'vertical_anchor_point': '1'}
self.assertEqual(properties, expected_properties)

def testParseArrayStops(self):
conversion_context = QgsMapBoxGlStyleConversionContext()
exp = QgsMapBoxGlStyleConverter.parseArrayStops({}, conversion_context, 1)
self.assertEqual(exp, '')

exp = QgsMapBoxGlStyleConverter.parseArrayStops([[0, [0, 1]], [2, [3, 4]]], conversion_context, 1)
self.assertEqual(exp, 'CASE WHEN @vector_tile_zoom > 0 AND @vector_tile_zoom <= 2 THEN array(0,1) WHEN @vector_tile_zoom > 2 THEN array(3,4) END')

exp = QgsMapBoxGlStyleConverter.parseArrayStops([[0, [0, 1]], [2, [3, 4]]], conversion_context, 2)
self.assertEqual(exp, 'CASE WHEN @vector_tile_zoom > 0 AND @vector_tile_zoom <= 2 THEN array(0,2) WHEN @vector_tile_zoom > 2 THEN array(6,8) END')

def testParseLineDashArray(self):
conversion_context = QgsMapBoxGlStyleConversionContext()
style = {
"id": "water line (intermittent)/river",
"type": "line",
"source": "esri",
"source-layer": "water line (intermittent)",
"filter": ["==", "_symbol", 3],
"minzoom": 10,
"layout": {
"line-join": "round"
},
"paint": {
"line-color": "#aad3df",
"line-dasharray": {
"stops": [[10, [1, 1]], [17, [0.3, 0.2]]]
},
"line-width": {
"base": 1.2,
"stops": [[10, 1.5], [11, 2], [12, 3], [13, 5], [14, 6], [16, 10], [17, 12]]
}
}
}
has_renderer, rendererStyle = QgsMapBoxGlStyleConverter.parseLineLayer(style, conversion_context)
self.assertTrue(has_renderer)
self.assertEqual(rendererStyle.geometryType(), QgsWkbTypes.LineGeometry)
dd_properties = rendererStyle.symbol().symbolLayers()[0].dataDefinedProperties()
self.assertEqual(dd_properties.property(QgsSymbolLayer.PropertyStrokeWidth).asExpression(), 'CASE WHEN @vector_tile_zoom > 10 AND @vector_tile_zoom <= 11 THEN scale_exp(@vector_tile_zoom,10,11,1.5,2,1.2) WHEN @vector_tile_zoom > 11 AND @vector_tile_zoom <= 12 THEN scale_exp(@vector_tile_zoom,11,12,2,3,1.2) WHEN @vector_tile_zoom > 12 AND @vector_tile_zoom <= 13 THEN scale_exp(@vector_tile_zoom,12,13,3,5,1.2) WHEN @vector_tile_zoom > 13 AND @vector_tile_zoom <= 14 THEN scale_exp(@vector_tile_zoom,13,14,5,6,1.2) WHEN @vector_tile_zoom > 14 AND @vector_tile_zoom <= 16 THEN scale_exp(@vector_tile_zoom,14,16,6,10,1.2) WHEN @vector_tile_zoom > 16 AND @vector_tile_zoom <= 17 THEN scale_exp(@vector_tile_zoom,16,17,10,12,1.2) WHEN @vector_tile_zoom > 17 THEN 12 END')
self.assertEqual(dd_properties.property(QgsSymbolLayer.PropertyCustomDash).asExpression(), 'array_to_string(array_foreach(CASE WHEN @vector_tile_zoom > 10 AND @vector_tile_zoom <= 17 THEN array(1,1) WHEN @vector_tile_zoom > 17 THEN array(0.3,0.2) END,@element * (CASE WHEN @vector_tile_zoom > 10 AND @vector_tile_zoom <= 11 THEN scale_exp(@vector_tile_zoom,10,11,1.5,2,1.2) WHEN @vector_tile_zoom > 11 AND @vector_tile_zoom <= 12 THEN scale_exp(@vector_tile_zoom,11,12,2,3,1.2) WHEN @vector_tile_zoom > 12 AND @vector_tile_zoom <= 13 THEN scale_exp(@vector_tile_zoom,12,13,3,5,1.2) WHEN @vector_tile_zoom > 13 AND @vector_tile_zoom <= 14 THEN scale_exp(@vector_tile_zoom,13,14,5,6,1.2) WHEN @vector_tile_zoom > 14 AND @vector_tile_zoom <= 16 THEN scale_exp(@vector_tile_zoom,14,16,6,10,1.2) WHEN @vector_tile_zoom > 16 AND @vector_tile_zoom <= 17 THEN scale_exp(@vector_tile_zoom,16,17,10,12,1.2) WHEN @vector_tile_zoom > 17 THEN 12 END)), \';\')')


if __name__ == '__main__':
unittest.main()

0 comments on commit 91c5364

Please sign in to comment.