From b5867bff58b926a1c3169e9f2d92765790a786a8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 24 Sep 2018 09:42:03 +1000 Subject: [PATCH] Fix legend count is 0 if graduated/categorized expression uses geometry Likely fixes many other bugs too with graduated/categorized renderers Fixes #15544 --- .../qgscategorizedsymbolrenderer.sip.in | 2 ++ .../qgsgraduatedsymbolrenderer.sip.in | 2 ++ .../qgscategorizedsymbolrenderer.cpp | 13 +++++++++ .../symbology/qgscategorizedsymbolrenderer.h | 1 + .../symbology/qgsgraduatedsymbolrenderer.cpp | 13 +++++++++ .../symbology/qgsgraduatedsymbolrenderer.h | 1 + .../test_qgscategorizedsymbolrenderer.py | 25 +++++++++++++++++ .../python/test_qgsgraduatedsymbolrenderer.py | 27 ++++++++++++++++--- 8 files changed, 80 insertions(+), 4 deletions(-) diff --git a/python/core/auto_generated/symbology/qgscategorizedsymbolrenderer.sip.in b/python/core/auto_generated/symbology/qgscategorizedsymbolrenderer.sip.in index ec485ec41bb..e28fc1f8223 100644 --- a/python/core/auto_generated/symbology/qgscategorizedsymbolrenderer.sip.in +++ b/python/core/auto_generated/symbology/qgscategorizedsymbolrenderer.sip.in @@ -93,6 +93,8 @@ class QgsCategorizedSymbolRenderer : QgsFeatureRenderer virtual QSet usedAttributes( const QgsRenderContext &context ) const; + virtual bool filterNeedsGeometry() const; + virtual QString dump() const; virtual QgsCategorizedSymbolRenderer *clone() const /Factory/; diff --git a/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in b/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in index eb581e1fef6..49b7e287a29 100644 --- a/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in +++ b/python/core/auto_generated/symbology/qgsgraduatedsymbolrenderer.sip.in @@ -130,6 +130,8 @@ class QgsGraduatedSymbolRenderer : QgsFeatureRenderer virtual QSet usedAttributes( const QgsRenderContext &context ) const; + virtual bool filterNeedsGeometry() const; + virtual QString dump() const; virtual QgsGraduatedSymbolRenderer *clone() const /Factory/; diff --git a/src/core/symbology/qgscategorizedsymbolrenderer.cpp b/src/core/symbology/qgscategorizedsymbolrenderer.cpp index 108b69f5e0b..9109a881d9e 100644 --- a/src/core/symbology/qgscategorizedsymbolrenderer.cpp +++ b/src/core/symbology/qgscategorizedsymbolrenderer.cpp @@ -452,6 +452,19 @@ QSet QgsCategorizedSymbolRenderer::usedAttributes( const QgsRenderConte return attributes; } +bool QgsCategorizedSymbolRenderer::filterNeedsGeometry() const +{ + QgsExpression testExpr( mAttrName ); + if ( !testExpr.hasParserError() ) + { + QgsExpressionContext context; + context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available! + testExpr.prepare( &context ); + return testExpr.needsGeometry(); + } + return false; +} + QString QgsCategorizedSymbolRenderer::dump() const { QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName ); diff --git a/src/core/symbology/qgscategorizedsymbolrenderer.h b/src/core/symbology/qgscategorizedsymbolrenderer.h index 9fe5af7176e..3db44e54196 100644 --- a/src/core/symbology/qgscategorizedsymbolrenderer.h +++ b/src/core/symbology/qgscategorizedsymbolrenderer.h @@ -102,6 +102,7 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer void startRender( QgsRenderContext &context, const QgsFields &fields ) override; void stopRender( QgsRenderContext &context ) override; QSet usedAttributes( const QgsRenderContext &context ) const override; + bool filterNeedsGeometry() const override; QString dump() const override; QgsCategorizedSymbolRenderer *clone() const override SIP_FACTORY; void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const override; diff --git a/src/core/symbology/qgsgraduatedsymbolrenderer.cpp b/src/core/symbology/qgsgraduatedsymbolrenderer.cpp index 02cd03522b6..a1e6235229c 100644 --- a/src/core/symbology/qgsgraduatedsymbolrenderer.cpp +++ b/src/core/symbology/qgsgraduatedsymbolrenderer.cpp @@ -425,6 +425,19 @@ QSet QgsGraduatedSymbolRenderer::usedAttributes( const QgsRenderContext return attributes; } +bool QgsGraduatedSymbolRenderer::filterNeedsGeometry() const +{ + QgsExpression testExpr( mAttrName ); + if ( !testExpr.hasParserError() ) + { + QgsExpressionContext context; + context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available! + testExpr.prepare( &context ); + return testExpr.needsGeometry(); + } + return false; +} + bool QgsGraduatedSymbolRenderer::updateRangeSymbol( int rangeIndex, QgsSymbol *symbol ) { if ( rangeIndex < 0 || rangeIndex >= mRanges.size() ) diff --git a/src/core/symbology/qgsgraduatedsymbolrenderer.h b/src/core/symbology/qgsgraduatedsymbolrenderer.h index edc898ea309..343028067a0 100644 --- a/src/core/symbology/qgsgraduatedsymbolrenderer.h +++ b/src/core/symbology/qgsgraduatedsymbolrenderer.h @@ -152,6 +152,7 @@ class CORE_EXPORT QgsGraduatedSymbolRenderer : public QgsFeatureRenderer void startRender( QgsRenderContext &context, const QgsFields &fields ) override; void stopRender( QgsRenderContext &context ) override; QSet usedAttributes( const QgsRenderContext &context ) const override; + bool filterNeedsGeometry() const override; QString dump() const override; QgsGraduatedSymbolRenderer *clone() const override SIP_FACTORY; void toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props = QgsStringMap() ) const override; diff --git a/tests/src/python/test_qgscategorizedsymbolrenderer.py b/tests/src/python/test_qgscategorizedsymbolrenderer.py index 19522b3e696..39a861991ab 100644 --- a/tests/src/python/test_qgscategorizedsymbolrenderer.py +++ b/tests/src/python/test_qgscategorizedsymbolrenderer.py @@ -458,6 +458,31 @@ class TestQgsCategorizedSymbolRenderer(unittest.TestCase): self.assertEqual(symbol.color().name(), '#0aff0a') renderer.stopRender(context) + def testUsedAttributes(self): + renderer = QgsCategorizedSymbolRenderer() + ctx = QgsRenderContext() + + # attribute can contain either attribute name or an expression. + # Sometimes it is not possible to distinguish between those two, + # e.g. "a - b" can be both a valid attribute name or expression. + # Since we do not have access to fields here, the method should return both options. + renderer.setClassAttribute("value") + self.assertEqual(renderer.usedAttributes(ctx), {"value"}) + renderer.setClassAttribute("value - 1") + self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"}) + renderer.setClassAttribute("valuea - valueb") + self.assertEqual(renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"}) + + def testFilterNeedsGeometry(self): + renderer = QgsCategorizedSymbolRenderer() + + renderer.setClassAttribute("value") + self.assertFalse(renderer.filterNeedsGeometry()) + renderer.setClassAttribute("$area") + self.assertTrue(renderer.filterNeedsGeometry()) + renderer.setClassAttribute("value - $area") + self.assertTrue(renderer.filterNeedsGeometry()) + if __name__ == "__main__": unittest.main() diff --git a/tests/src/python/test_qgsgraduatedsymbolrenderer.py b/tests/src/python/test_qgsgraduatedsymbolrenderer.py index 4a047ef4e7b..50021cc2962 100644 --- a/tests/src/python/test_qgsgraduatedsymbolrenderer.py +++ b/tests/src/python/test_qgsgraduatedsymbolrenderer.py @@ -450,11 +450,30 @@ class TestQgsGraduatedSymbolRenderer(unittest.TestCase): '(0.5000-1.0000,1.0000-1.1000,1.1000-1.2000,1.2000-5.0000,)', 'Quantile classification not correct') - # Tests still needed + def testUsedAttributes(self): + renderer = QgsGraduatedSymbolRenderer() + ctx = QgsRenderContext() - # Other calculation method tests - # createRenderer function - # symbolForFeature correctly selects range + # attribute can contain either attribute name or an expression. + # Sometimes it is not possible to distinguish between those two, + # e.g. "a - b" can be both a valid attribute name or expression. + # Since we do not have access to fields here, the method should return both options. + renderer.setClassAttribute("value") + self.assertEqual(renderer.usedAttributes(ctx), {"value"}) + renderer.setClassAttribute("value - 1") + self.assertEqual(renderer.usedAttributes(ctx), {"value", "value - 1"}) + renderer.setClassAttribute("valuea - valueb") + self.assertEqual(renderer.usedAttributes(ctx), {"valuea", "valuea - valueb", "valueb"}) + + def testFilterNeedsGeometry(self): + renderer = QgsGraduatedSymbolRenderer() + + renderer.setClassAttribute("value") + self.assertFalse(renderer.filterNeedsGeometry()) + renderer.setClassAttribute("$area") + self.assertTrue(renderer.filterNeedsGeometry()) + renderer.setClassAttribute("value - $area") + self.assertTrue(renderer.filterNeedsGeometry()) if __name__ == "__main__":