Skip to content

Commit

Permalink
Allow conversion of embedded renderer to rule based renderers
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Mar 6, 2021
1 parent b4b5162 commit 8845a76
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 9 deletions.
Expand Up @@ -546,9 +546,11 @@ take a rule and create a list of new rules based on the ranges from graduated sy
take a rule and create a list of new rules with intervals of scales given by the passed scale denominators
%End

static QgsRuleBasedRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) /Factory/;
static QgsRuleBasedRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer, QgsVectorLayer *layer = 0 ) /Factory/;
%Docstring
creates a QgsRuleBasedRenderer from an existing renderer.
Creates a new QgsRuleBasedRenderer from an existing ``renderer``.

The optional ``layer`` parameter is required for conversions of some renderer types.

:return: a new renderer if the conversion was possible, otherwise ``None``.

Expand Down
2 changes: 1 addition & 1 deletion src/core/symbology/qgscategorizedsymbolrenderer.cpp
Expand Up @@ -1085,7 +1085,7 @@ QgsCategorizedSymbolRenderer *QgsCategorizedSymbolRenderer::convertFromRenderer(
if ( invertedPolygonRenderer )
r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
}
if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
{
const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
QgsCategoryList categories;
Expand Down
35 changes: 33 additions & 2 deletions src/core/symbology/qgsrulebasedrenderer.cpp
Expand Up @@ -28,7 +28,7 @@
#include "qgspainteffectregistry.h"
#include "qgsproperty.h"
#include "qgsstyleentityvisitor.h"

#include "qgsembeddedsymbolrenderer.h"
#include <QSet>

#include <QDomDocument>
Expand Down Expand Up @@ -1308,7 +1308,7 @@ bool QgsRuleBasedRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) con
return mRootRule->accept( visitor );
}

QgsRuleBasedRenderer *QgsRuleBasedRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer )
QgsRuleBasedRenderer *QgsRuleBasedRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer, QgsVectorLayer *layer )
{
std::unique_ptr< QgsRuleBasedRenderer > r;
if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
Expand Down Expand Up @@ -1483,6 +1483,37 @@ QgsRuleBasedRenderer *QgsRuleBasedRenderer::convertFromRenderer( const QgsFeatur
if ( const QgsMergedFeatureRenderer *mergedRenderer = dynamic_cast<const QgsMergedFeatureRenderer *>( renderer ) )
r.reset( convertFromRenderer( mergedRenderer->embeddedRenderer() ) );
}
else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
{
const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );

std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );

QgsFeatureRequest req;
req.setFlags( QgsFeatureRequest::EmbeddedSymbols | QgsFeatureRequest::NoGeometry );
req.setNoAttributes();
QgsFeatureIterator it = layer->getFeatures( req );
QgsFeature feature;
while ( it.nextFeature( feature ) && rootrule->children().size() < 500 )
{
if ( feature.embeddedSymbol() )
{
std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
rule->setFilterExpression( QStringLiteral( "$id=%1" ).arg( feature.id() ) );
rule->setLabel( QString::number( feature.id() ) );
rule->setSymbol( feature.embeddedSymbol()->clone() );
rootrule->appendChild( rule.release() );
}
}

std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
rule->setFilterExpression( QStringLiteral( "ELSE" ) );
rule->setLabel( QObject::tr( "All other features" ) );
rule->setSymbol( embeddedRenderer->defaultSymbol()->clone() );
rootrule->appendChild( rule.release() );

r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
}

if ( r )
{
Expand Down
7 changes: 5 additions & 2 deletions src/core/symbology/qgsrulebasedrenderer.h
Expand Up @@ -545,11 +545,14 @@ class CORE_EXPORT QgsRuleBasedRenderer : public QgsFeatureRenderer
static void refineRuleScales( QgsRuleBasedRenderer::Rule *initialRule, QList<int> scales );

/**
* creates a QgsRuleBasedRenderer from an existing renderer.
* Creates a new QgsRuleBasedRenderer from an existing \a renderer.
*
* The optional \a layer parameter is required for conversions of some renderer types.
*
* \returns a new renderer if the conversion was possible, otherwise NULLPTR.
* \since QGIS 2.5
*/
static QgsRuleBasedRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer ) SIP_FACTORY;
static QgsRuleBasedRenderer *convertFromRenderer( const QgsFeatureRenderer *renderer, QgsVectorLayer *layer = nullptr ) SIP_FACTORY;

//! helper function to convert the size scale and rotation fields present in some other renderers to data defined symbology
static void convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField = QString() );
Expand Down
2 changes: 1 addition & 1 deletion src/gui/symbology/qgsrulebasedrendererwidget.cpp
Expand Up @@ -61,7 +61,7 @@ QgsRuleBasedRendererWidget::QgsRuleBasedRendererWidget( QgsVectorLayer *layer, Q

if ( renderer )
{
mRenderer = QgsRuleBasedRenderer::convertFromRenderer( renderer );
mRenderer = QgsRuleBasedRenderer::convertFromRenderer( renderer, layer );
}
if ( !mRenderer )
{
Expand Down
41 changes: 40 additions & 1 deletion tests/src/python/test_qgsrulebasedrenderer.py
Expand Up @@ -46,7 +46,10 @@
QgsRenderContext,
QgsSymbolLayer,
QgsSimpleMarkerSymbolLayer,
QgsProperty
QgsProperty,
QgsFeature,
QgsGeometry,
QgsEmbeddedSymbolRenderer
)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
Expand Down Expand Up @@ -456,6 +459,42 @@ def testPointsUsedAttributes(self):

QgsProject.instance().removeMapLayer(points_layer)

def testConvertFromEmbedded(self):
"""
Test converting an embedded symbol renderer to a rule based renderer
"""
points_layer = QgsVectorLayer('Point', 'Polys', 'memory')
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt('Point(-100 30)'))
f.setEmbeddedSymbol(
QgsMarkerSymbol.createSimple({'name': 'triangle', 'size': 10, 'color': '#ff0000', 'outline_style': 'no'}))
self.assertTrue(points_layer.dataProvider().addFeature(f))
f.setGeometry(QgsGeometry.fromWkt('Point(-110 40)'))
f.setEmbeddedSymbol(
QgsMarkerSymbol.createSimple({'name': 'square', 'size': 7, 'color': '#00ff00', 'outline_style': 'no'}))
self.assertTrue(points_layer.dataProvider().addFeature(f))
f.setGeometry(QgsGeometry.fromWkt('Point(-90 50)'))
f.setEmbeddedSymbol(None)
self.assertTrue(points_layer.dataProvider().addFeature(f))

renderer = QgsEmbeddedSymbolRenderer(defaultSymbol=QgsMarkerSymbol.createSimple({'name': 'star', 'size': 10, 'color': '#ff00ff', 'outline_style': 'no'}))
points_layer.setRenderer(renderer)

rule_based = QgsRuleBasedRenderer.convertFromRenderer(renderer, points_layer)
self.assertEqual(len(rule_based.rootRule().children()), 3)
rule_0 = rule_based.rootRule().children()[0]
self.assertEqual(rule_0.filterExpression(), '$id=1')
self.assertEqual(rule_0.label(), '1')
self.assertEqual(rule_0.symbol().color().name(), '#ff0000')
rule_1 = rule_based.rootRule().children()[1]
self.assertEqual(rule_1.filterExpression(), '$id=2')
self.assertEqual(rule_1.label(), '2')
self.assertEqual(rule_1.symbol().color().name(), '#00ff00')
rule_2 = rule_based.rootRule().children()[2]
self.assertEqual(rule_2.filterExpression(), 'ELSE')
self.assertEqual(rule_2.label(), 'All other features')
self.assertEqual(rule_2.symbol().color().name(), '#ff00ff')


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

0 comments on commit 8845a76

Please sign in to comment.