mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-01 00:46:20 -05:00
Completed port
This commit is contained in:
parent
ed597b7c9c
commit
659388a57e
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user