Completed port

This commit is contained in:
Nyall Dawson 2020-09-04 16:23:14 +10:00
parent ed597b7c9c
commit 659388a57e
4 changed files with 792 additions and 3 deletions

View File

@ -77,6 +77,33 @@ Parses a fill layer.
- style: generated QGIS vector tile style
%End
static bool parseLineLayer( const QVariantMap &jsonLayer, const QString &styleName, QgsVectorTileBasicRendererStyle &style /Out/ );
%Docstring
Parses a line layer.
:param jsonLayer: fill layer to parse
:param styleName: style name
:return: - ``True`` if the layer was successfully parsed.
- style: generated QGIS vector tile style
%End
static void parseSymbolLayer( const QVariantMap &jsonLayer, const QString &styleName,
QgsVectorTileBasicRendererStyle &rendererStyle /Out/,
bool &hasRenderer /Out/,
QgsVectorTileBasicLabelingStyle &labelingStyle /Out/,
bool &hasLabeling /Out/ );
%Docstring
Parses a symbol layer.
:param jsonLayer: fill layer to parse
:param styleName: style name
:param rendererStyle: generated QGIS vector tile style
:param hasRenderer: will be set to ``True`` if symbol layer generated a renderer style
:param labelingStyle: generated QGIS vector tile labeling
%End
static QgsProperty parseInterpolateColorByZoom( const QVariantMap &json );
static QgsProperty parseInterpolateByZoom( const QVariantMap &json, double multiplier = 1 );
@ -126,6 +153,21 @@ Takes a QColor object and returns HSLA components in required format for QGIS :p
%Docstring
Generates an interpolation for values between ``valueMin`` and ``valueMax``, scaled between the
ranges ``zoomMin`` to ``zoomMax``.
%End
static Qt::PenCapStyle parseCapStyle( const QString &style );
%Docstring
Converts a value to Qt.PenCapStyle enum from JSON value.
%End
static Qt::PenJoinStyle parseJoinStyle( const QString &style );
%Docstring
Converts a value to Qt.PenJoinStyle enum from JSON value.
%End
static QString parseExpression( const QVariantList &expression );
%Docstring
Converts a MapBox GL expression to a QGIS expression.
%End
private:

View File

@ -20,6 +20,10 @@
#include "qgssymbollayerutils.h"
#include "qgslogger.h"
#include "qgsfillsymbollayer.h"
#include "qgslinesymbollayer.h"
#include "qgsfontutils.h"
constexpr double PIXEL_RATIO = 1;
QgsMapBoxGlStyleConverter::QgsMapBoxGlStyleConverter( const QVariantMap &style, const QString &styleName )
: mStyle( style )
@ -60,7 +64,7 @@ void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers, const Q
QString filterExpression;
if ( jsonLayer.contains( QStringLiteral( "filter" ) ) )
{
// filterExpression = parseExpression( jsonLayer.value( QStringLiteral( "filter" ) ) );
filterExpression = parseExpression( jsonLayer.value( QStringLiteral( "filter" ) ).toList() );
}
QgsVectorTileBasicRendererStyle rendererStyle;
@ -74,11 +78,11 @@ void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers, const Q
}
else if ( layerType == QLatin1String( "line" ) )
{
// hasRendererStyle = parseLineLayer( jsonLayer, styleName );
hasRendererStyle = parseLineLayer( jsonLayer, styleName, rendererStyle );
}
else if ( layerType == QLatin1String( "symbol" ) )
{
// hasLabelingStyle = parseSymbolLayer( jsonLayer, styleName );
parseSymbolLayer( jsonLayer, styleName, rendererStyle, hasRendererStyle, labelingStyle, hasLabelingStyle );
}
else
{
@ -237,6 +241,7 @@ bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, co
}
// TODO: fill-translate
std::unique_ptr< QgsSymbol > symbol( QgsSymbol::defaultSymbol( QgsWkbTypes::PolygonGeometry ) );
QgsSimpleFillSymbolLayer *fillSymbol = dynamic_cast< QgsSimpleFillSymbolLayer * >( symbol->symbolLayer( 0 ) );
@ -300,6 +305,483 @@ bool QgsMapBoxGlStyleConverter::parseFillLayer( const QVariantMap &jsonLayer, co
return true;
}
bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, const QString &, QgsVectorTileBasicRendererStyle &style )
{
if ( !jsonLayer.contains( QStringLiteral( "paint" ) ) )
{
QgsDebugMsg( QStringLiteral( "Style layer %1 has no paint property, skipping" ).arg( jsonLayer.value( QStringLiteral( "id" ) ).toString() ) );
return false;
}
const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
QgsPropertyCollection ddProperties;
// line color
QColor lineColor;
if ( jsonLayer.contains( QStringLiteral( "line-color" ) ) )
{
const QVariant jsonLineColor = jsonPaint.value( QStringLiteral( "line-color" ) );
switch ( jsonLineColor.type() )
{
case QVariant::Map:
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateColorByZoom( jsonLineColor.toMap() ) );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, ddProperties.property( QgsSymbolLayer::PropertyFillColor ) );
break;
case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateListByZoom( jsonLineColor.toList(), PropertyType::Color ) );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, ddProperties.property( QgsSymbolLayer::PropertyFillColor ) );
break;
case QVariant::String:
lineColor = parseColor( jsonLineColor.toString() );
break;
default:
QgsDebugMsg( "Skipping non-implemented color expression" );
break;
}
}
double lineWidth = 1.0;
if ( jsonPaint.contains( QStringLiteral( "line-width" ) ) )
{
const QVariant jsonLineWidth = jsonPaint.value( QStringLiteral( "line-width" ) );
switch ( jsonLineWidth.type() )
{
case QVariant::Int:
case QVariant::Double:
lineWidth = jsonLineWidth.toDouble();
break;
case QVariant::Map:
lineWidth = -1;
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateByZoom( jsonLineWidth.toMap(), PIXEL_RATIO ) );
break;
case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateListByZoom( jsonLineWidth.toList(), PropertyType::Line, PIXEL_RATIO ) );
break;
default:
QgsDebugMsg( "Skipping non-implemented line-width expression" );
break;
}
}
double lineOpacity = -1.0;
if ( jsonPaint.contains( QStringLiteral( "line-opacity" ) ) )
{
const QVariant jsonLineOpacity = jsonPaint.value( QStringLiteral( "line-opacity" ) );
switch ( jsonLineOpacity.type() )
{
case QVariant::Int:
case QVariant::Double:
lineOpacity = jsonLineOpacity.toDouble();
break;
case QVariant::Map:
if ( ddProperties.isActive( QgsSymbolLayer::PropertyStrokeColor ) )
{
QgsDebugMsg( QStringLiteral( "Could not set opacity of layer %1, opacity already defined in stroke color" ).arg( jsonLayer.value( QStringLiteral( "id" ) ).toString() ) );
}
else
{
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateOpacityByZoom( jsonLineOpacity.toMap() ) );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateOpacityByZoom( jsonLineOpacity.toMap() ) );
}
break;
case QVariant::List:
case QVariant::StringList:
if ( ddProperties.isActive( QgsSymbolLayer::PropertyStrokeColor ) )
{
QgsDebugMsg( QStringLiteral( "Could not set opacity of layer %1, opacity already defined in stroke color" ).arg( jsonLayer.value( QStringLiteral( "id" ) ).toString() ) );
}
else
{
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateListByZoom( jsonLineOpacity.toList(), PropertyType::Opacity ) );
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateListByZoom( jsonLineOpacity.toList(), PropertyType::Opacity ) );
}
break;
default:
QgsDebugMsg( "Skipping non-implemented opacity expression" );
break;
}
}
QVector< double > dashVector;
if ( jsonPaint.contains( QStringLiteral( "line-dasharray" ) ) )
{
const QVariant jsonLineDashArray = jsonPaint.value( QStringLiteral( "line-dasharray" ) );
switch ( jsonLineDashArray.type() )
{
case QVariant::Map:
{
//TODO improve parsing (use PropertyCustomDash?)
const QVariantList dashSource = jsonLineDashArray.toMap().value( QStringLiteral( "stops" ) ).toList().last().toList().value( 1 ).toList();
for ( const QVariant &v : dashSource )
{
dashVector << v.toDouble() * PIXEL_RATIO;
}
break;
}
case QVariant::List:
case QVariant::StringList:
{
const QVariantList dashSource = jsonLineDashArray.toList();
for ( const QVariant &v : dashSource )
{
dashVector << v.toDouble() * PIXEL_RATIO;
}
break;
}
default:
QgsDebugMsg( "Skipping non-implemented dash vector expression" );
break;
}
}
Qt::PenCapStyle penCapStyle = Qt::FlatCap;
Qt::PenJoinStyle penJoinStyle = Qt::MiterJoin;
if ( jsonLayer.contains( QStringLiteral( "layout" ) ) )
{
const QVariantMap jsonLayout = jsonPaint.value( QStringLiteral( "layout" ) ).toMap();
if ( jsonLayout.contains( QStringLiteral( "line-cap" ) ) )
{
penCapStyle = parseCapStyle( jsonLayout.value( QStringLiteral( "line-cap" ) ).toString() );
}
if ( jsonLayout.contains( QStringLiteral( "line-join" ) ) )
{
penJoinStyle = parseJoinStyle( jsonLayout.value( QStringLiteral( "line-join" ) ).toString() );
}
}
std::unique_ptr< QgsSymbol > symbol( QgsSymbol::defaultSymbol( QgsWkbTypes::LineGeometry ) );
QgsSimpleLineSymbolLayer *lineSymbol = dynamic_cast< QgsSimpleLineSymbolLayer * >( symbol->symbolLayer( 0 ) );
// set render units
symbol->setOutputUnit( QgsUnitTypes::RenderPixels );
lineSymbol->setOutputUnit( QgsUnitTypes::RenderPixels );
lineSymbol->setPenCapStyle( penCapStyle );
lineSymbol->setPenJoinStyle( penJoinStyle );
lineSymbol->setDataDefinedProperties( ddProperties );
if ( lineOpacity != -1 )
{
symbol->setOpacity( lineOpacity );
}
if ( lineColor.isValid() )
{
lineSymbol->setStrokeColor( lineColor );
}
if ( lineWidth != -1 )
{
lineSymbol->setWidth( lineWidth * PIXEL_RATIO );
}
if ( !dashVector.empty() )
{
lineSymbol->setUseCustomDashPattern( true );
lineSymbol->setCustomDashVector( dashVector );
}
style.setGeometryType( QgsWkbTypes::LineGeometry );
style.setSymbol( symbol.release() );
return true;
}
void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer, const QString &, QgsVectorTileBasicRendererStyle &, bool &hasRenderer, QgsVectorTileBasicLabelingStyle &labelingStyle, bool &hasLabeling )
{
hasLabeling = false;
hasRenderer = false;
if ( !jsonLayer.contains( QStringLiteral( "paint" ) ) )
{
QgsDebugMsg( QStringLiteral( "Style layer %1 has no paint property, skipping" ).arg( jsonLayer.value( QStringLiteral( "id" ) ).toString() ) );
return;
}
const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
if ( !jsonLayer.contains( QStringLiteral( "layout" ) ) )
{
QgsDebugMsg( QStringLiteral( "Style layer %1 has no layout property, skipping" ).arg( jsonLayer.value( QStringLiteral( "id" ) ).toString() ) );
return;
}
const QVariantMap jsonLayout = jsonLayer.value( QStringLiteral( "layout" ) ).toMap();
QgsPropertyCollection ddLabelProperties;
double textSize = 16.0;
if ( jsonLayout.contains( QStringLiteral( "text-size" ) ) )
{
const QVariant jsonTextSize = jsonLayout.value( QStringLiteral( "text-size" ) );
switch ( jsonTextSize.type() )
{
case QVariant::Int:
case QVariant::Double:
textSize = jsonTextSize.toDouble();
break;
case QVariant::Map:
textSize = -1;
ddLabelProperties.setProperty( QgsPalLayerSettings::Size, parseInterpolateByZoom( jsonTextSize.toMap() ) );
break;
case QVariant::List:
case QVariant::StringList:
textSize = -1;
ddLabelProperties.setProperty( QgsPalLayerSettings::Size, parseInterpolateListByZoom( jsonTextSize.toList(), PropertyType::Text ) );
break;
default:
QgsDebugMsg( "Skipping non-implemented text-size expression" );
break;
}
}
QFont textFont;
bool foundFont = false;
if ( jsonLayout.contains( QStringLiteral( "text-font" ) ) )
{
const QVariant jsonTextFont = jsonLayout.value( QStringLiteral( "text-font" ) );
if ( jsonTextFont.type() != QVariant::List && jsonTextFont.type() != QVariant::StringList && jsonTextFont.type() != QVariant::String )
{
QgsDebugMsg( "Skipping non-implemented text-font expression" );
}
else
{
QString fontName;
switch ( jsonTextFont.type() )
{
case QVariant::List:
case QVariant::StringList:
fontName = jsonTextFont.toList().value( 0 ).toString();
break;
case QVariant::String:
fontName = jsonTextFont.toString();
break;
default:
break;
}
const QStringList textFontParts = fontName.split( ' ' );
for ( int i = 1; i < textFontParts.size(); ++i )
{
const QString candidateFontName = textFontParts.mid( 0, i ).join( ' ' );
const QString candidateFontStyle = textFontParts.mid( i ).join( ' ' );
if ( QgsFontUtils::fontFamilyHasStyle( candidateFontName, candidateFontStyle ) )
{
textFont = QFont( candidateFontName );
textFont.setStyleName( candidateFontStyle );
foundFont = true;
break;
}
}
if ( !foundFont )
{
// probably won't work, but we'll try anyway... maybe the json isn't following the spec correctly!!
textFont = QFont( fontName );
foundFont = true;
}
}
}
// text color
QColor textColor;
if ( jsonPaint.contains( QStringLiteral( "text-color" ) ) )
{
const QVariant jsonTextColor = jsonPaint.value( QStringLiteral( "text-color" ) );
switch ( jsonTextColor.type() )
{
case QVariant::Map:
ddLabelProperties.setProperty( QgsPalLayerSettings::Color, parseInterpolateColorByZoom( jsonTextColor.toMap() ) );
break;
case QVariant::List:
case QVariant::StringList:
ddLabelProperties.setProperty( QgsPalLayerSettings::Color, parseInterpolateListByZoom( jsonTextColor.toList(), PropertyType::Color ) );
break;
case QVariant::String:
textColor = parseColor( jsonTextColor.toString() );
break;
default:
QgsDebugMsg( "Skipping non-implemented text-color expression" );
break;
}
}
// buffer color
QColor bufferColor;
if ( jsonPaint.contains( QStringLiteral( "text-halo-color" ) ) )
{
const QVariant jsonBufferColor = jsonPaint.value( QStringLiteral( "text-halo-color" ) );
switch ( jsonBufferColor.type() )
{
case QVariant::Map:
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferColor, parseInterpolateColorByZoom( jsonBufferColor.toMap() ) );
break;
case QVariant::List:
case QVariant::StringList:
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferColor, parseInterpolateListByZoom( jsonBufferColor.toList(), PropertyType::Color ) );
break;
case QVariant::String:
bufferColor = parseColor( jsonBufferColor.toString() );
break;
default:
QgsDebugMsg( "Skipping non-implemented text-halo-color expression" );
break;
}
}
double bufferSize = 0.0;
if ( jsonPaint.contains( QStringLiteral( "text-halo-width" ) ) )
{
const QVariant jsonHaloWidth = jsonPaint.value( QStringLiteral( "text-halo-width" ) );
switch ( jsonHaloWidth.type() )
{
case QVariant::Int:
case QVariant::Double:
bufferSize = jsonHaloWidth.toDouble();
break;
case QVariant::Map:
bufferSize = 1;
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateByZoom( jsonHaloWidth.toMap() ) );
break;
case QVariant::List:
case QVariant::StringList:
bufferSize = 1;
ddLabelProperties.setProperty( QgsPalLayerSettings::BufferSize, parseInterpolateListByZoom( jsonHaloWidth.toList(), PropertyType::Text ) );
break;
default:
QgsDebugMsg( "Skipping non-implemented text-halo-width expression" );
break;
}
}
// TODO implement halo blur
#if 0
if ( 'text-halo-blur' in json_paint )
{
json_text_halo_blur = json_paint['text-halo-blur'];
if ( isinstance( json_text_halo_blur, ( float, int ) ) )
buffer_size = buffer_size - json_text_halo_blur;
else
print( "skipping non-float text-halo-blur", json_text_halo_blur );
}
#endif
QgsTextFormat format;
format.setSizeUnit( QgsUnitTypes::RenderPixels );
if ( textColor.isValid() )
format.setColor( textColor );
if ( textSize >= 0 )
format.setSize( textSize );
if ( foundFont )
format.setFont( textFont );
if ( bufferSize > 0 )
{
format.buffer().setEnabled( true );
format.buffer().setSize( bufferSize * PIXEL_RATIO );
format.buffer().setSizeUnit( QgsUnitTypes::RenderPixels );
format.buffer().setColor( bufferColor );
}
QgsPalLayerSettings labelSettings;
// TODO - likely improvements required here
labelSettings.fieldName = QStringLiteral( "name:latin" );
if ( jsonLayout.contains( QStringLiteral( "text-field" ) ) )
{
const QVariant jsonTextField = jsonLayout.value( QStringLiteral( "text-field" ) );
switch ( jsonTextField.type() )
{
case QVariant::String:
labelSettings.fieldName = jsonTextField.toString();
break;
case QVariant::List:
case QVariant::StringList:
labelSettings.fieldName = jsonTextField.toList().value( 1 ).toList().value( 1 ).toString();
break;
default:
QgsDebugMsg( "Skipping non-implemented text-field expression" );
break;
}
// handle ESRI specific field name constants
if ( labelSettings.fieldName == QLatin1String( "{_name_global}" ) )
labelSettings.fieldName = QStringLiteral( "_name_global" );
else if ( labelSettings.fieldName == QLatin1String( "{_name}" ) )
labelSettings.fieldName = QStringLiteral( "_name" );
}
if ( jsonLayout.contains( QStringLiteral( "text-transform" ) ) )
{
labelSettings.isExpression = true;
const QString textTransform = jsonLayout.value( QStringLiteral( "text-transform" ) ).toString();
if ( textTransform == QLatin1String( "uppercase" ) )
{
labelSettings.fieldName = QStringLiteral( "upper(%1)" ).arg( labelSettings.fieldName );
}
else if ( textTransform == QLatin1String( "lowercase" ) )
{
labelSettings.fieldName = QStringLiteral( "lower(%1)" ).arg( labelSettings.fieldName );
}
}
labelSettings.placement = QgsPalLayerSettings::OverPoint;
QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::PointGeometry;
if ( jsonLayout.contains( QStringLiteral( "symbol-placement" ) ) )
{
const QString symbolPlacement = jsonLayout.value( QStringLiteral( "symbol-placement" ) ).toString();
if ( symbolPlacement == QLatin1String( "line" ) )
{
labelSettings.placement = QgsPalLayerSettings::Curved;
labelSettings.lineSettings().setPlacementFlags( QgsLabeling::OnLine );
geometryType = QgsWkbTypes::LineGeometry;
}
}
if ( textSize >= 0 )
{
// TODO -- this probably needs revisiting -- it was copied from the MapTiler code, but may be wrong...
labelSettings.priority = std::min( textSize / 3, 10.0 );
}
labelSettings.setFormat( format );
// use a low obstacle weight for layers by default -- we'd rather have more labels for these layers, even if placement isn't ideal
labelSettings.obstacleSettings().setFactor( 0.1 );
labelSettings.setDataDefinedProperties( ddLabelProperties );
labelingStyle.setGeometryType( geometryType );
labelingStyle.setLabelSettings( labelSettings );
hasLabeling = true;
}
QgsProperty QgsMapBoxGlStyleConverter::parseInterpolateColorByZoom( const QVariantMap &json )
{
const double base = json.value( QStringLiteral( "base" ), QStringLiteral( "1" ) ).toDouble();
@ -639,6 +1121,198 @@ QString QgsMapBoxGlStyleConverter::interpolateExpression( int zoomMin, int zoomM
.arg( zoomMax );
}
Qt::PenCapStyle QgsMapBoxGlStyleConverter::parseCapStyle( const QString &style )
{
if ( style == QLatin1String( "round" ) )
return Qt::RoundCap;
else if ( style == QLatin1String( "square" ) )
return Qt::SquareCap;
else
return Qt::FlatCap; // "butt" is default
}
Qt::PenJoinStyle QgsMapBoxGlStyleConverter::parseJoinStyle( const QString &style )
{
if ( style == QLatin1String( "bevel" ) )
return Qt::BevelJoin;
else if ( style == QLatin1String( "round" ) )
return Qt::RoundJoin;
else
return Qt::MiterJoin; // "miter" is default
}
QString QgsMapBoxGlStyleConverter::parseExpression( const QVariantList &expression )
{
QString op = expression.value( 0 ).toString();
if ( op == QLatin1String( "all" ) )
{
QStringList parts;
for ( int i = 1; i < expression.size(); ++i )
{
QString part = parseValue( expression.at( i ) );
if ( part.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Skipping unsupported expression" ) );
return QString();
}
parts << part;
}
return QStringLiteral( "(%1)" ).arg( parts.join( QStringLiteral( ") AND (" ) ) );
}
else if ( op == QLatin1String( "any" ) )
{
QStringList parts;
for ( int i = 1; i < expression.size(); ++i )
{
QString part = parseValue( expression.at( i ) );
if ( part.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Skipping unsupported expression" ) );
return QString();
}
parts << part;
}
return QStringLiteral( "(%1)" ).arg( parts.join( QStringLiteral( ") OR (" ) ) );
}
else if ( op == QLatin1String( "none" ) )
{
QStringList parts;
for ( int i = 1; i < expression.size(); ++i )
{
QString part = parseValue( expression.at( i ) );
if ( part.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Skipping unsupported expression" ) );
return QString();
}
parts << part;
}
return QStringLiteral( "NOT (%1)" ).arg( parts.join( QStringLiteral( ") AND NOT (" ) ) );
}
else if ( op == '!' )
{
// ! inverts next expression's meaning
QVariantList contraJsonExpr = expression.value( 1 ).toList();
contraJsonExpr[0] = op + contraJsonExpr[0].toString();
// ['!', ['has', 'level']] -> ['!has', 'level']
return parseKey( contraJsonExpr );
}
else if ( op == QLatin1String( "==" )
|| op == QLatin1String( "!=" )
|| op == QLatin1String( ">=" )
|| op == '>'
|| op == QLatin1String( "<=" )
|| op == '<' )
{
// use IS and NOT IS instead of = and != because they can deal with NULL values
if ( op == QLatin1String( "==" ) )
op = QStringLiteral( "IS" );
else if ( op == QLatin1String( "!=" ) )
op = QStringLiteral( "IS NOT" );
return QStringLiteral( "%1 %2 %3" ).arg( parseKey( expression.value( 1 ) ),
op, parseValue( expression.value( 2 ) ) );
}
else if ( op == QLatin1String( "has" ) )
{
return parseKey( expression.value( 1 ) ) + QStringLiteral( " IS NOT NULL" );
}
else if ( op == QLatin1String( "!has" ) )
{
return parseKey( expression.value( 1 ) ) + QStringLiteral( " IS NULL" );
}
else if ( op == QLatin1String( "in" ) || op == QLatin1String( "!in" ) )
{
const QString key = parseKey( expression.value( 1 ) );
QStringList parts;
for ( int i = 2; i < expression.size(); ++i )
{
QString part = parseValue( expression.at( i ) );
if ( part.isEmpty() )
{
QgsDebugMsg( QStringLiteral( "Skipping unsupported expression" ) );
return QString();
}
parts << part;
}
if ( op == QLatin1String( "in" ) )
return QStringLiteral( "%1 IN (%2)" ).arg( key, parts.join( QStringLiteral( ", " ) ) );
else
return QStringLiteral( "(%1 IS NULL OR %1 NOT IN (%2))" ).arg( key, parts.join( QStringLiteral( ", " ) ) );
}
else if ( op == QLatin1String( "get" ) )
{
return parseKey( expression.value( 1 ).toList().value( 1 ) );
}
else if ( op == QLatin1String( "match" ) )
{
const QString attribute = expression.value( 1 ).toList().value( 1 ).toString();
QString caseString = QStringLiteral( "CASE " );
for ( int i = 2; i < expression.size() - 2; ++i )
{
if ( expression.at( i ).type() == QVariant::List || expression.at( i ).type() == QVariant::StringList )
{
QStringList parts;
for ( const QVariant &p : expression.at( i ).toList() )
{
parts << QgsExpression::quotedValue( p );
}
caseString += QStringLiteral( "WHEN (%1 IN (%2)) " ).arg( QgsExpression::quotedColumnRef( attribute ), parts.join( ", " ) );
}
else if ( expression.at( i ).type() == QVariant::String || expression.at( i ).type() == QVariant::Int
|| expression.at( i ).type() == QVariant::Double )
{
caseString += QStringLiteral( "WHEN (%1) " ).arg( QgsExpression::createFieldEqualityExpression( attribute, expression.at( i ) ) );
}
caseString += QStringLiteral( "THEN %1 " ).arg( expression.at( i + 1 ).toString() );
i += 2;
}
caseString += QStringLiteral( " ELSE %1 END" ).arg( expression.last().toString() );
return caseString;
}
else
{
QgsDebugMsg( QStringLiteral( "Skipping non-supported expression" ) );
return QString();
}
}
QString QgsMapBoxGlStyleConverter::parseValue( const QVariant &value )
{
switch ( value.type() )
{
case QVariant::List:
case QVariant::StringList:
return parseExpression( value.toList() );
case QVariant::String:
return QgsExpression::quotedValue( value.toString() );
case QVariant::Int:
case QVariant::Double:
return value.toString();
default:
QgsDebugMsg( QStringLiteral( "Skipping unsupported expression part" ) );
break;
}
return QString();
}
QString QgsMapBoxGlStyleConverter::parseKey( const QVariant &value )
{
if ( value.toString() == QLatin1String( "$type" ) )
return QStringLiteral( "_geom_type" );
else if ( value.type() == QVariant::List || value.type() == QVariant::StringList )
{
if ( value.toList().size() > 1 )
return value.toList().at( 1 ).toString();
else
return value.toList().value( 0 ).toString();
}
return QgsExpression::quotedColumnRef( value.toString() );
}
QgsVectorTileRenderer *QgsMapBoxGlStyleConverter::renderer() const
{
return mRenderer ? mRenderer->clone() : nullptr;

View File

@ -25,6 +25,7 @@
class QgsVectorTileRenderer;
class QgsVectorTileLabeling;
class QgsVectorTileBasicRendererStyle;
class QgsVectorTileBasicLabelingStyle;
/**
* \ingroup core
@ -94,6 +95,33 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*/
static bool parseFillLayer( const QVariantMap &jsonLayer, const QString &styleName, QgsVectorTileBasicRendererStyle &style SIP_OUT );
/**
* Parses a line layer.
*
* \param jsonLayer fill layer to parse
* \param styleName style name
* \param style generated QGIS vector tile style
* \returns TRUE if the layer was successfully parsed.
*/
static bool parseLineLayer( const QVariantMap &jsonLayer, const QString &styleName, QgsVectorTileBasicRendererStyle &style SIP_OUT );
/**
* Parses a symbol layer.
*
* \param jsonLayer fill layer to parse
* \param styleName style name
* \param rendererStyle generated QGIS vector tile style
* \param hasRenderer will be set to TRUE if symbol layer generated a renderer style
* \param labelingStyle generated QGIS vector tile labeling
* \param hasLabeling will be set to TRUE if symbol layer generated a labeling style
*/
static void parseSymbolLayer( const QVariantMap &jsonLayer, const QString &styleName,
QgsVectorTileBasicRendererStyle &rendererStyle SIP_OUT,
bool &hasRenderer SIP_OUT,
QgsVectorTileBasicLabelingStyle &labelingStyle SIP_OUT,
bool &hasLabeling SIP_OUT );
static QgsProperty parseInterpolateColorByZoom( const QVariantMap &json );
static QgsProperty parseInterpolateByZoom( const QVariantMap &json, double multiplier = 1 );
@ -144,12 +172,30 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*/
static QString interpolateExpression( int zoomMin, int zoomMax, double valueMin, double valueMax, double base );
/**
* Converts a value to Qt::PenCapStyle enum from JSON value.
*/
static Qt::PenCapStyle parseCapStyle( const QString &style );
/**
* Converts a value to Qt::PenJoinStyle enum from JSON value.
*/
static Qt::PenJoinStyle parseJoinStyle( const QString &style );
/**
* Converts a MapBox GL expression to a QGIS expression.
*/
static QString parseExpression( const QVariantList &expression );
private:
#ifdef SIP_RUN
QgsMapBoxGlStyleConverter( const QgsMapBoxGlStyleConverter &other );
#endif
static QString parseValue( const QVariant &value );
static QString parseKey( const QVariant &value );
QVariantMap mStyle;
QString mError;

View File

@ -145,6 +145,33 @@ class TestQgsMapBoxGlStyleConverter(unittest.TestCase):
], QgsMapBoxGlStyleConverter.PropertyType.Line, 2).expressionString(),
"CASE WHEN @zoom_level > 10 AND @zoom_level <= 15 THEN scale_linear(@zoom_level, 10, 15, 0.1, 0.3) * 2 WHEN @zoom_level > 15 AND @zoom_level <= 18 THEN scale_linear(@zoom_level, 15, 18, 0.3, 0.6) * 2 END")
def testParseExpression(self):
self.assertEqual(QgsMapBoxGlStyleConverter.parseExpression([
"all",
["==", ["get", "level"], 0],
["match", ["get", "type"], ["Restricted"], True, False]
]),
'''(level IS 0) AND (CASE WHEN ("type" IN ('Restricted')) THEN true ELSE false END)''')
self.assertEqual(QgsMapBoxGlStyleConverter.parseExpression([
"all",
["match", ["get", "level"], [1], True, False],
["match", ["get", "type"], ["Local"], True, False]
]),
'''(CASE WHEN ("level" IN (1)) THEN true ELSE false END) AND (CASE WHEN ("type" IN ('Local')) THEN true ELSE false END)''')
self.assertEqual(QgsMapBoxGlStyleConverter.parseExpression([
"match",
["get", "type"],
["Primary", "Motorway"],
False,
True
]),
'''CASE WHEN ("type" IN ('Primary', 'Motorway')) THEN false ELSE true END''')
self.assertEqual(QgsMapBoxGlStyleConverter.parseExpression(["==", "_symbol", 0]),
'''"_symbol" IS 0''')
self.assertEqual(QgsMapBoxGlStyleConverter.parseExpression(["all", ["==", "_symbol", 8], ["!in", "Viz", 3]]),
'''("_symbol" IS 8) AND (("Viz" IS NULL OR "Viz" NOT IN (3)))''')
if __name__ == '__main__':
unittest.main()