Skip to content
Permalink
Browse files

More interpolation work

  • Loading branch information
nyalldawson committed Sep 6, 2020
1 parent c4858b4 commit b9dd8b2602d4045048ddbe16c637801b05978680
@@ -53,6 +53,14 @@ or ``None`` if the style could not be converted successfully.

protected:

enum PropertyType
{
Color,
Line,
Opacity,
Text
};

void parseLayers( const QVariantList &layers, const QString &styleName );
%Docstring
Parse list of ``layers`` from JSON
@@ -70,6 +78,27 @@ Parses a fill layer.
%End

static QgsProperty parseInterpolateColorByZoom( const QVariantMap &json );
static QgsProperty parseInterpolateByZoom( const QVariantMap &json, double multiplier = 1 );

static QgsProperty parseInterpolateOpacityByZoom( const QVariantMap &json );
%Docstring
Interpolates opacity with either :py:func:`~QgsMapBoxGlStyleConverter.scale_linear` or :py:func:`~QgsMapBoxGlStyleConverter.scale_exp` (depending on base value).
For ``json`` with intermediate stops it uses :py:func:`~QgsMapBoxGlStyleConverter.parseOpacityStops` function.
It uses QGIS :py:func:`~QgsMapBoxGlStyleConverter.set_color_part` function to set alpha component of color.
%End

static QString parseOpacityStops( double base, const QVariantList &stops );
%Docstring
Takes values from stops and uses either :py:func:`~QgsMapBoxGlStyleConverter.scale_linear` or :py:func:`~QgsMapBoxGlStyleConverter.scale_exp` functions
to interpolate alpha component of color.
%End

static QString parseStops( double base, const QVariantList &stops, double multiplier );

static QgsProperty parseInterpolateListByZoom( const QVariantList &json, PropertyType type, double multiplier = 1 );
%Docstring
Interpolates a list which starts with the interpolate function.
%End

static QColor parseColor( const QVariant &color );
%Docstring
@@ -144,14 +144,15 @@ bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, co

case QVariant::List:
case QVariant::StringList:

ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateListByZoom( jsonFillColor.toList(), PropertyType::Color ) );
break;

case QVariant::String:

fillColor = parseColor( jsonFillColor.toString() );
break;

default:
QgsDebugMsg( "Skipping non-implemented color expression" );
break;
}

@@ -258,6 +259,219 @@ QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateColorByZoom( const QVaria
return QgsProperty::fromExpression( caseString );
}

QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateByZoom( const QVariantMap &json, double multiplier )
{
const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
if ( stops.empty() )
return QgsProperty();

QString scaleExpression;
if ( stops.size() <= 2 )
{
if ( base == 1 )
{
scaleExpression = QStringLiteral( "scale_linear(@zoom_level, %1, %2, %3, %4) * %5" ).arg( stops.value( 0 ).toList().value( 0 ).toString(),
stops.last().toList().value( 0 ).toString(),
stops.value( 0 ).toList().value( 1 ).toString(),
stops.last().toList().value( 1 ).toString() ).arg( multiplier );
}
else
{
scaleExpression = interpolateExpression( stops.value( 0 ).toList().value( 0 ).toInt(),
stops.last().toList().value( 0 ).toInt(),
stops.value( 0 ).toList().value( 1 ).toInt(),
stops.last().toList().value( 1 ).toInt(), base ) + QStringLiteral( "* %1" ).arg( multiplier );
}
}
else
{
scaleExpression = parseStops( base, stops, multiplier );
}
return QgsProperty::fromExpression( scaleExpression );
}

QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateOpacityByZoom( const QVariantMap &json )
{
const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
const QVariantList stops = json.value( QStringLiteral( "stops" ) ).toList();
if ( stops.empty() )
return QgsProperty();

QString scaleExpression;
if ( stops.length() <= 2 )
{
if ( base == 1 )
{
scaleExpression = QStringLiteral( "set_color_part(@symbol_color, 'alpha', scale_linear(@zoom_level, %1, %2, %3, %4))" )
.arg( stops.value( 0 ).toList().value( 0 ).toString(),
stops.last().toList().value( 0 ).toString() )
.arg( stops.value( 0 ).toList().value( 1 ).toDouble() * 255 )
.arg( stops.last().toList().value( 1 ).toDouble() * 255 );
}
else
{
scaleExpression = QStringLiteral( "set_color_part(@symbol_color, 'alpha', %1)" )
.arg( interpolateExpression( stops.value( 0 ).toList().value( 0 ).toInt(),
stops.last().toList().value( 0 ).toInt(),
stops.value( 0 ).toList().value( 1 ).toDouble() * 255,
stops.value( 0 ).toList().value( 1 ).toDouble() * 255, base ) );
}
}
else
{
scaleExpression = parseOpacityStops( base, stops );
}
return QgsProperty::fromExpression( scaleExpression );
}

QString QgsMapBoxGlStyleConverter::parseOpacityStops( double base, const QVariantList &stops )
{
QString caseString = QStringLiteral( "CASE WHEN @zoom_level < %1 THEN set_color_part(@symbol_color, 'alpha', %2 * 255)" )
.arg( stops.value( 0 ).toList().value( 0 ).toString(),
stops.value( 0 ).toList().value( 1 ).toString() );

if ( base == 1 )
{
// base = 1 -> scale_linear
for ( int i = 0; i < stops.size() - 1; ++i )
{
caseString += QStringLiteral( " WHEN @zoom_level >= %1 AND @zoom_level < %2 "
"THEN set_color_part(@symbol_color, 'alpha', "
"scale_linear(@zoom_level, %1, %2, "
"%3 * 255, %4 * 255)) " )
.arg( stops.value( i ).toList().value( 0 ).toString(),
stops.value( i + 1 ).toList().value( 0 ).toString(),
stops.value( i ).toList().value( 1 ).toString(),
stops.value( i + 1 ).toList().value( 1 ).toString() );
}
}
else
{
// base != 1 -> scale_expr
for ( int i = 0; i < stops.size() - 1; ++i )
{
caseString += QStringLiteral( " WHEN @zoom_level >= %1 AND @zoom_level < %2 "
"THEN set_color_part(@symbol_color, 'alpha', %3)" )
.arg( stops.value( i ).toList().value( 0 ).toString(),
stops.value( i + 1 ).toList().value( 0 ).toString(),
interpolateExpression( stops.value( i ).toList().value( 0 ).toInt(),
stops.value( i + 1 ).toList().value( 0 ).toInt(),
stops.value( i ).toList().value( 1 ).toDouble() * 255,
stops.value( i + 1 ).toList().value( 1 ).toDouble() * 255, base ) );
}
}

caseString += QStringLiteral( "WHEN @zoom_level >= %1 "
"THEN set_color_part(@symbol_color, 'alpha', %2) END" )
.arg( stops.last().toList().value( 0 ).toString(),
stops.last().toList().value( 1 ).toString() );
return caseString;
}

QString QgsMapBoxGlStyleConverter::parseStops( double base, const QVariantList &stops, double multiplier )
{
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 QVariant bv = stops.value( i ).toList().value( 1 );
if ( bz.type() == QVariant::List || bz.type() == QVariant::StringList )
{
QgsDebugMsg( "QGIS does not support expressions in interpolation function, skipping." );
return QString();
}

// top zoom and value
const QVariant tz = stops.value( i + 1 ).toList().value( 0 );
const QVariant tv = stops.value( i + 1 ).toList().value( 1 );
if ( tz.type() == QVariant::List || tz.type() == QVariant::StringList )
{
QgsDebugMsg( "QGIS does not support expressions in interpolation function, skipping." );
return QString();
}

if ( base == 1 )
{
// base = 1 -> scale_linear
caseString += QStringLiteral( "WHEN @zoom_level > %1 AND @zoom_level <= %2 "
"THEN scale_linear(@zoom_level, %1, %2, %3, %4) "
"* %5 " ).arg( bz.toString(),
tz.toString(),
bv.toString(),
tv.toString() ).arg( multiplier );
}
else
{
// base != 1 -> scale_exp
caseString += QStringLiteral( "WHEN @zoom_level > %1 AND @zoom_level <= %2 "
"THEN %3 * %4 " ).arg( bz.toString(),
tz.toString(),
interpolateExpression( bz.toInt(), tz.toInt(), bv.toDouble(), tv.toDouble(), base ) ).arg( multiplier );
}
}
caseString += QStringLiteral( "END" );
return caseString;
}

QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateListByZoom( const QVariantList &json, PropertyType type, double multiplier )
{
if ( json.value( 0 ).toString() != QLatin1String( "interpolate" ) )
{
QgsDebugMsg( QStringLiteral( "Could not interpret value list" ) );
return QgsProperty();
}

double base = 1;
const QString technique = json.value( 1 ).toList().value( 0 ).toString();
if ( technique == QLatin1String( "linear" ) )
base = 1;
else if ( technique == QLatin1String( "exponential" ) )
base = json.value( 1 ).toList(). value( 1 ).toDouble();
else if ( technique == QLatin1String( "cubic-bezier" ) )
{
QgsDebugMsg( QStringLiteral( "QGIS does not support cubic-bezier interpolation, linear used instead." ) );
base = 1;
}
else
{
QgsDebugMsg( QStringLiteral( "Skipping not implemented interpolation method %1" ).arg( technique ) );
return QgsProperty();
}

if ( json.value( 2 ).toList().value( 0 ).toString() != QLatin1String( "zoom" ) )
{
QgsDebugMsg( QStringLiteral( "Skipping not implemented interpolation input %1" ).arg( json.value( 2 ).toString() ) );
return QgsProperty();
}

// Convert stops into list of lists
QVariantList stops;
for ( int i = 3; i < json.length(); i += 2 )
{
stops.push_back( QVariantList() << json.value( i ).toString() << json.value( i + 1 ).toString() );
}

QVariantMap props;
props.insert( QStringLiteral( "stops" ), stops );
props.insert( QStringLiteral( "base" ), base );
switch ( type )
{
case PropertyType::Color:
return parseInterpolateColorByZoom( props );

case PropertyType::Line:
case PropertyType::Text:
return parseInterpolateByZoom( props, multiplier );

case PropertyType::Opacity:
return parseInterpolateOpacityByZoom( props );
}
return QgsProperty();
}

QColor QgsMapBoxGlStyleConverter::parseColor( const QVariant &color )
{
if ( color.type() != QVariant::String )
@@ -71,6 +71,14 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter

protected:

enum PropertyType
{
Color,
Line,
Opacity,
Text
};

/**
* Parse list of \a layers from JSON
*/
@@ -87,6 +95,27 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
static bool parseFillLayer( const QVariantMap &jsonLayer, const QString &styleName, QgsVectorTileBasicRendererStyle &style SIP_OUT );

static QgsProperty parseInterpolateColorByZoom( const QVariantMap &json );
static QgsProperty parseInterpolateByZoom( const QVariantMap &json, double multiplier = 1 );

/**
* Interpolates opacity with either scale_linear() or scale_exp() (depending on base value).
* For \a json with intermediate stops it uses parseOpacityStops() function.
* It uses QGIS set_color_part() function to set alpha component of color.
*/
static QgsProperty parseInterpolateOpacityByZoom( const QVariantMap &json );

/**
* Takes values from stops and uses either scale_linear() or scale_exp() functions
* to interpolate alpha component of color.
*/
static QString parseOpacityStops( double base, const QVariantList &stops );

static QString parseStops( double base, const QVariantList &stops, double multiplier );

/**
* Interpolates a list which starts with the interpolate function.
*/
static QgsProperty parseInterpolateListByZoom( const QVariantList &json, PropertyType type, double multiplier = 1 );

/**
* Parses a \a color in one of these supported formats:

0 comments on commit b9dd8b2

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