Skip to content

Commit

Permalink
add circle vector tile layer support (#41584)
Browse files Browse the repository at this point in the history
* fix #41529: add circle vector tile layer support
  • Loading branch information
PeterPetrik authored and github-actions[bot] committed Feb 19, 2021
1 parent c2bf502 commit 1ad7b42
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 4 deletions.
Expand Up @@ -266,7 +266,22 @@ Parses a line layer.

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

:param jsonLayer: fill layer to parse
:param jsonLayer: line layer to parse
:param context: conversion context

:return: - ``True`` if the layer was successfully parsed.
- style: generated QGIS vector tile style
%End

static bool parseCircleLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style /Out/, QgsMapBoxGlStyleConversionContext &context );
%Docstring
Parses a circle layer.

.. warning::

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

:param jsonLayer: circle layer to parse
:param context: conversion context

:return: - ``True`` if the layer was successfully parsed.
Expand All @@ -285,7 +300,7 @@ Parses a symbol layer as renderer or labeling.

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

:param jsonLayer: fill layer to parse
:param jsonLayer: symbol layer to parse
: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
Expand Down
250 changes: 250 additions & 0 deletions src/core/vectortile/qgsmapboxglstyleconverter.cpp
Expand Up @@ -110,6 +110,10 @@ void QgsMapBoxGlStyleConverter::parseLayers( const QVariantList &layers, QgsMapB
{
hasRendererStyle = parseLineLayer( jsonLayer, rendererStyle, *context );
}
else if ( layerType == QLatin1String( "circle" ) )
{
hasRendererStyle = parseCircleLayer( jsonLayer, rendererStyle, *context );
}
else if ( layerType == QLatin1String( "symbol" ) )
{
parseSymbolLayer( jsonLayer, rendererStyle, hasRendererStyle, labelingStyle, hasLabelingStyle, *context );
Expand Down Expand Up @@ -625,6 +629,252 @@ bool QgsMapBoxGlStyleConverter::parseLineLayer( const QVariantMap &jsonLayer, Qg
return true;
}

bool QgsMapBoxGlStyleConverter::parseCircleLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style, QgsMapBoxGlStyleConversionContext &context )
{
if ( !jsonLayer.contains( QStringLiteral( "paint" ) ) )
{
context.pushWarning( QObject::tr( "%1: Style has no paint property, skipping" ).arg( context.layerId() ) );
return false;
}

const QVariantMap jsonPaint = jsonLayer.value( QStringLiteral( "paint" ) ).toMap();
QgsPropertyCollection ddProperties;

// circle color
QColor circleFillColor;
if ( jsonPaint.contains( QStringLiteral( "circle-color" ) ) )
{
const QVariant jsonCircleColor = jsonPaint.value( QStringLiteral( "circle-color" ) );
switch ( jsonCircleColor.type() )
{
case QVariant::Map:
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateColorByZoom( jsonCircleColor.toMap(), context, &circleFillColor ) );
break;

case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonCircleColor.toList(), PropertyType::Color, context, 1, 255, &circleFillColor ) );
break;

case QVariant::String:
circleFillColor = parseColor( jsonCircleColor.toString(), context );
break;

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleColor.type() ) ) );
break;
}
}
else
{
// defaults to #000000
circleFillColor = QColor( 0, 0, 0 );
}

// circle radius
double circleDiameter = 10.0;
if ( jsonPaint.contains( QStringLiteral( "circle-radius" ) ) )
{
const QVariant jsonCircleRadius = jsonPaint.value( QStringLiteral( "circle-radius" ) );
switch ( jsonCircleRadius.type() )
{
case QVariant::Int:
case QVariant::Double:
circleDiameter = jsonCircleRadius.toDouble() * context.pixelSizeConversionFactor() * 2;
break;

case QVariant::Map:
circleDiameter = -1;
ddProperties.setProperty( QgsSymbolLayer::PropertyWidth, parseInterpolateByZoom( jsonCircleRadius.toMap(), context, context.pixelSizeConversionFactor() * 2, &circleDiameter ) );
break;

case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyWidth, parseValueList( jsonCircleRadius.toList(), PropertyType::Numeric, context, context.pixelSizeConversionFactor() * 2, 255, nullptr, &circleDiameter ) );
break;

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-radius type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleRadius.type() ) ) );
break;
}
}

double circleOpacity = -1.0;
if ( jsonPaint.contains( QStringLiteral( "circle-opacity" ) ) )
{
const QVariant jsonCircleOpacity = jsonPaint.value( QStringLiteral( "circle-opacity" ) );
switch ( jsonCircleOpacity.type() )
{
case QVariant::Int:
case QVariant::Double:
circleOpacity = jsonCircleOpacity.toDouble();
break;

case QVariant::Map:
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseInterpolateOpacityByZoom( jsonCircleOpacity.toMap(), circleFillColor.isValid() ? circleFillColor.alpha() : 255 ) );
break;

case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyFillColor, parseValueList( jsonCircleOpacity.toList(), PropertyType::Opacity, context, 1, circleFillColor.isValid() ? circleFillColor.alpha() : 255 ) );
break;

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleOpacity.type() ) ) );
break;
}
}
if ( ( circleOpacity != -1 ) && circleFillColor.isValid() )
{
circleFillColor.setAlphaF( circleOpacity );
}

// circle stroke color
QColor circleStrokeColor;
if ( jsonPaint.contains( QStringLiteral( "circle-stroke-color" ) ) )
{
const QVariant jsonCircleStrokeColor = jsonPaint.value( QStringLiteral( "circle-stroke-color" ) );
switch ( jsonCircleStrokeColor.type() )
{
case QVariant::Map:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateColorByZoom( jsonCircleStrokeColor.toMap(), context, &circleStrokeColor ) );
break;

case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonCircleStrokeColor.toList(), PropertyType::Color, context, 1, 255, &circleStrokeColor ) );
break;

case QVariant::String:
circleStrokeColor = parseColor( jsonCircleStrokeColor.toString(), context );
break;

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-stroke-color type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleStrokeColor.type() ) ) );
break;
}
}

// circle stroke width
double circleStrokeWidth = -1.0;
if ( jsonPaint.contains( QStringLiteral( "circle-stroke-width" ) ) )
{
const QVariant circleStrokeWidthJson = jsonPaint.value( QStringLiteral( "circle-stroke-width" ) );
switch ( circleStrokeWidthJson.type() )
{
case QVariant::Int:
case QVariant::Double:
circleStrokeWidth = circleStrokeWidthJson.toDouble() * context.pixelSizeConversionFactor();
break;

case QVariant::Map:
circleStrokeWidth = -1.0;
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeWidth, parseInterpolateByZoom( circleStrokeWidthJson.toMap(), context, context.pixelSizeConversionFactor(), &circleStrokeWidth ) );
break;

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

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-stroke-width type (%2)" ).arg( context.layerId(), QMetaType::typeName( circleStrokeWidthJson.type() ) ) );
break;
}
}

double circleStrokeOpacity = -1.0;
if ( jsonPaint.contains( QStringLiteral( "circle-stroke-opacity" ) ) )
{
const QVariant jsonCircleStrokeOpacity = jsonPaint.value( QStringLiteral( "circle-stroke-opacity" ) );
switch ( jsonCircleStrokeOpacity.type() )
{
case QVariant::Int:
case QVariant::Double:
circleStrokeOpacity = jsonCircleStrokeOpacity.toDouble();
break;

case QVariant::Map:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseInterpolateOpacityByZoom( jsonCircleStrokeOpacity.toMap(), circleStrokeColor.isValid() ? circleStrokeColor.alpha() : 255 ) );
break;

case QVariant::List:
case QVariant::StringList:
ddProperties.setProperty( QgsSymbolLayer::PropertyStrokeColor, parseValueList( jsonCircleStrokeOpacity.toList(), PropertyType::Opacity, context, 1, circleStrokeColor.isValid() ? circleStrokeColor.alpha() : 255 ) );
break;

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-stroke-opacity type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleStrokeOpacity.type() ) ) );
break;
}
}
if ( ( circleStrokeOpacity != -1 ) && circleStrokeColor.isValid() )
{
circleStrokeColor.setAlphaF( circleStrokeOpacity );
}

// translate
QPointF circleTranslate;
if ( jsonPaint.contains( QStringLiteral( "circle-translate" ) ) )
{
const QVariant jsonCircleTranslate = jsonPaint.value( QStringLiteral( "circle-translate" ) );
switch ( jsonCircleTranslate.type() )
{

case QVariant::Map:
ddProperties.setProperty( QgsSymbolLayer::PropertyOffset, parseInterpolatePointByZoom( jsonCircleTranslate.toMap(), context, context.pixelSizeConversionFactor(), &circleTranslate ) );
break;

case QVariant::List:
case QVariant::StringList:
circleTranslate = QPointF( jsonCircleTranslate.toList().value( 0 ).toDouble() * context.pixelSizeConversionFactor(),
jsonCircleTranslate.toList().value( 1 ).toDouble() * context.pixelSizeConversionFactor() );
break;

default:
context.pushWarning( QObject::tr( "%1: Skipping unsupported circle-translate type (%2)" ).arg( context.layerId(), QMetaType::typeName( jsonCircleTranslate.type() ) ) );
break;
}
}

std::unique_ptr< QgsSymbol > symbol( qgis::make_unique< QgsMarkerSymbol >() );
QgsSimpleMarkerSymbolLayer *markerSymbolLayer = dynamic_cast< QgsSimpleMarkerSymbolLayer * >( symbol->symbolLayer( 0 ) );
Q_ASSERT( markerSymbolLayer );

// set render units
symbol->setOutputUnit( context.targetUnit() );
symbol->setDataDefinedProperties( ddProperties );

if ( !circleTranslate.isNull() )
{
markerSymbolLayer->setOffset( circleTranslate );
markerSymbolLayer->setOffsetUnit( context.targetUnit() );
}

if ( circleFillColor.isValid() )
{
markerSymbolLayer->setFillColor( circleFillColor );
}
if ( circleDiameter != -1 )
{
markerSymbolLayer->setSize( circleDiameter );
markerSymbolLayer->setSizeUnit( context.targetUnit() );
}
if ( circleStrokeColor.isValid() )
{
markerSymbolLayer->setStrokeColor( circleStrokeColor );
}
if ( circleStrokeWidth != -1 )
{
markerSymbolLayer->setStrokeWidth( circleStrokeWidth );
markerSymbolLayer->setStrokeWidthUnit( context.targetUnit() );
}

style.setGeometryType( QgsWkbTypes::PointGeometry );
style.setSymbol( symbol.release() );
return true;
}

void QgsMapBoxGlStyleConverter::parseSymbolLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &renderer, bool &hasRenderer, QgsVectorTileBasicLabelingStyle &labelingStyle, bool &hasLabeling, QgsMapBoxGlStyleConversionContext &context )
{
hasLabeling = false;
Expand Down
16 changes: 14 additions & 2 deletions src/core/vectortile/qgsmapboxglstyleconverter.h
Expand Up @@ -282,19 +282,31 @@ class CORE_EXPORT QgsMapBoxGlStyleConverter
*
* \warning This is private API only, and may change in future QGIS versions
*
* \param jsonLayer fill layer to parse
* \param jsonLayer line layer to parse
* \param style generated QGIS vector tile style
* \param context conversion context
* \returns TRUE if the layer was successfully parsed.
*/
static bool parseLineLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style SIP_OUT, QgsMapBoxGlStyleConversionContext &context );

/**
* Parses a circle layer.
*
* \warning This is private API only, and may change in future QGIS versions
*
* \param jsonLayer circle layer to parse
* \param style generated QGIS vector tile style
* \param context conversion context
* \returns TRUE if the layer was successfully parsed.
*/
static bool parseCircleLayer( const QVariantMap &jsonLayer, QgsVectorTileBasicRendererStyle &style SIP_OUT, QgsMapBoxGlStyleConversionContext &context );

/**
* Parses a symbol layer as renderer or labeling.
*
* \warning This is private API only, and may change in future QGIS versions
*
* \param jsonLayer fill layer to parse
* \param jsonLayer symbol layer to parse
* \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
Expand Down
41 changes: 41 additions & 0 deletions tests/src/python/test_qgsmapboxglconverter.py
Expand Up @@ -15,6 +15,7 @@
from qgis.PyQt.QtGui import (QColor)
from qgis.core import (QgsMapBoxGlStyleConverter,
QgsMapBoxGlStyleConversionContext,
QgsWkbTypes,
QgsEffectStack
)

Expand Down Expand Up @@ -548,6 +549,46 @@ def testConvertLabels(self):
self.assertEqual(labeling.labelSettings().fieldName, '''lower(concat(concat("name_en",' - ',"name_fr"),"bar"))''')
self.assertTrue(labeling.labelSettings().isExpression)

def testCircleLayer(self):
context = QgsMapBoxGlStyleConversionContext()
style = {
"id": "cicle_layer",
"type": "circle",
"paint": {
"circle-stroke-color": "rgba(46, 46, 46, 1)",
"circle-stroke-opacity": 0.5,
"circle-stroke-width": 3,
"circle-color": "rgba(22, 22, 22, 1)",
"circle-opacity": 0.6,
"circle-radius": 33,
"circle-translate": [11, 22]
}
}
has_renderer, rendererStyle = QgsMapBoxGlStyleConverter.parseCircleLayer(style, context)
self.assertTrue(has_renderer)
self.assertEqual(rendererStyle.geometryType(), QgsWkbTypes.PointGeometry)
properties = rendererStyle.symbol().symbolLayers()[0].properties()
expected_properties = {
'angle': '0',
'color': '22,22,22,153',
'horizontal_anchor_point': '1',
'joinstyle': 'bevel',
'name': 'circle',
'offset': '11,22',
'offset_map_unit_scale': '3x:0,0,0,0,0,0',
'offset_unit': 'Pixel',
'outline_color': '46,46,46,128',
'outline_style': 'solid',
'outline_width': '3',
'outline_width_map_unit_scale': '3x:0,0,0,0,0,0',
'outline_width_unit': 'Pixel',
'scale_method': 'diameter',
'size': '66',
'size_map_unit_scale': '3x:0,0,0,0,0,0',
'size_unit': 'Pixel',
'vertical_anchor_point': '1'}
self.assertEqual(properties, expected_properties)


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

0 comments on commit 1ad7b42

Please sign in to comment.