Skip to content

Commit 9eee121

Browse files
committedDec 3, 2015
Apply filters to feature request for categorized renderer
Makes rendering much faster when only certain categories are checked, as only the matching records for the displayed features are fetched from the provider.
1 parent 97e5d31 commit 9eee121

11 files changed

+208
-7
lines changed
 

‎python/core/symbology-ng/qgscategorizedsymbolrendererv2.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class QgsCategorizedSymbolRendererV2 : QgsFeatureRendererV2
6666
//! returns bitwise OR-ed capabilities of the renderer
6767
virtual int capabilities();
6868

69+
virtual QString filter( const QgsFields& fields = QgsFields() );
70+
6971
//! @note available in python as symbols2
7072
virtual QgsSymbolV2List symbols( QgsRenderContext& context ) /PyName=symbols2/;
7173
void updateSymbols( QgsSymbolV2 * sym );

‎python/core/symbology-ng/qgsrendererv2.sip

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,18 @@ class QgsFeatureRendererV2
103103

104104
virtual void stopRender( QgsRenderContext& context ) = 0;
105105

106-
virtual QString filter();
106+
/**
107+
* If a renderer does not require all the features this method may be overridden
108+
* and return an expression used as where clause.
109+
* This will be called once after {@link startRender()} and before the first call
110+
* to {@link renderFeature()}.
111+
* By default this returns a null string and all features will be requested.
112+
* You do not need to specify the extent in here, this is taken care of separately and
113+
* will be combined with a filter returned from this method.
114+
*
115+
* @return An expression used as where clause
116+
*/
117+
virtual QString filter( const QgsFields& fields = QgsFields() );
107118

108119
virtual QList<QString> usedAttributes() = 0;
109120

‎python/core/symbology-ng/qgsrulebasedrendererv2.sip

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ class QgsRuleBasedRendererV2 : QgsFeatureRendererV2
326326

327327
virtual void stopRender( QgsRenderContext& context );
328328

329-
virtual QString filter();
329+
virtual QString filter( const QgsFields& fields = QgsFields() );
330330

331331
virtual QList<QString> usedAttributes();
332332

‎src/core/qgsvectorlayerrenderer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ bool QgsVectorLayerRenderer::render()
145145

146146
mRendererV2->startRender( mContext, mFields );
147147

148-
QString rendererFilter = mRendererV2->filter();
148+
QString rendererFilter = mRendererV2->filter( mFields );
149149

150150
QgsRectangle requestExtent = mContext.extent();
151151
mRendererV2->modifyRequestExtent( requestExtent, mContext );

‎src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,75 @@ void QgsCategorizedSymbolRendererV2::toSld( QDomDocument &doc, QDomElement &elem
519519
}
520520
}
521521

522+
QString QgsCategorizedSymbolRendererV2::filter( const QgsFields& fields )
523+
{
524+
int attrNum = fields.fieldNameIndex( mAttrName );
525+
bool isExpression = ( attrNum == -1 );
526+
527+
bool hasDefault = false;
528+
bool defaultActive = false;
529+
bool allActive = true;
530+
bool noneActive = true;
531+
532+
//we need to build lists of both inactive and active values, as either list may be required
533+
//depending on whether the default category is active or not
534+
QString activeValues;
535+
QString inactiveValues;
536+
537+
Q_FOREACH ( const QgsRendererCategoryV2& cat, mCategories )
538+
{
539+
if ( cat.value() == "" )
540+
{
541+
hasDefault = true;
542+
defaultActive = cat.renderState();
543+
}
544+
545+
noneActive = noneActive && !cat.renderState();
546+
allActive = allActive && cat.renderState();
547+
548+
QVariant::Type valType = isExpression ? cat.value().type() : fields.at( attrNum ).type();
549+
QString value = QgsExpression::quotedValue( cat.value(), valType );
550+
551+
if ( !cat.renderState() )
552+
{
553+
if ( cat.value() != "" )
554+
{
555+
if ( !inactiveValues.isEmpty() )
556+
inactiveValues.append( ',' );
557+
558+
inactiveValues.append( value );
559+
}
560+
}
561+
else
562+
{
563+
if ( cat.value() != "" )
564+
{
565+
if ( !activeValues.isEmpty() )
566+
activeValues.append( ',' );
567+
568+
activeValues.append( value );
569+
}
570+
}
571+
}
572+
573+
if ( allActive && hasDefault )
574+
{
575+
return QString();
576+
}
577+
else if ( noneActive )
578+
{
579+
return "FALSE";
580+
}
581+
else if ( defaultActive )
582+
{
583+
return QString( "(%1) NOT IN (%2)" ).arg( mAttrName, inactiveValues );
584+
}
585+
else
586+
{
587+
return QString( "(%1) IN (%2)" ).arg( mAttrName, activeValues );
588+
}
589+
}
590+
522591
QgsSymbolV2List QgsCategorizedSymbolRendererV2::symbols( QgsRenderContext &context )
523592
{
524593
Q_UNUSED( context );

‎src/core/symbology-ng/qgscategorizedsymbolrendererv2.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
9797
//! returns bitwise OR-ed capabilities of the renderer
9898
virtual int capabilities() override { return SymbolLevels | RotationField | Filter; }
9999

100+
virtual QString filter( const QgsFields& fields = QgsFields() ) override;
101+
100102
//! @note available in python as symbols2
101103
virtual QgsSymbolV2List symbols( QgsRenderContext& context ) override;
102104
void updateSymbols( QgsSymbolV2 * sym );

‎src/core/symbology-ng/qgsrendererv2.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "qgsrectangle.h"
2121
#include "qgsrendercontext.h"
2222
#include "qgssymbolv2.h"
23+
#include "qgsfield.h"
2324

2425
#include <QList>
2526
#include <QString>
@@ -30,7 +31,6 @@
3031
#include <QDomElement>
3132

3233
class QgsFeature;
33-
class QgsFields;
3434
class QgsVectorLayer;
3535
class QgsPaintEffect;
3636

@@ -141,7 +141,7 @@ class CORE_EXPORT QgsFeatureRendererV2
141141
*
142142
* @return An expression used as where clause
143143
*/
144-
virtual QString filter() { return QString::null; }
144+
virtual QString filter( const QgsFields& fields = QgsFields() ) { Q_UNUSED( fields ); return QString::null; }
145145

146146
virtual QList<QString> usedAttributes() = 0;
147147

‎src/core/symbology-ng/qgsrulebasedrendererv2.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ void QgsRuleBasedRendererV2::stopRender( QgsRenderContext& context )
895895
mRootRule->stopRender( context );
896896
}
897897

898-
QString QgsRuleBasedRendererV2::filter()
898+
QString QgsRuleBasedRendererV2::filter( const QgsFields& )
899899
{
900900
return mFilter;
901901
}

‎src/core/symbology-ng/qgsrulebasedrendererv2.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2
385385

386386
virtual void stopRender( QgsRenderContext& context ) override;
387387

388-
virtual QString filter() override;
388+
virtual QString filter( const QgsFields& fields = QgsFields() ) override;
389389

390390
virtual QList<QString> usedAttributes() override;
391391

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ADD_PYTHON_TEST(PyQgsAtlasComposition test_qgsatlascomposition.py)
1313
ADD_PYTHON_TEST(PyQgsAttributeTableModel test_qgsattributetablemodel.py)
1414
#ADD_PYTHON_TEST(PyQgsAuthenticationSystem test_qgsauthsystem.py)
1515
ADD_PYTHON_TEST(PyQgsBlendModes test_qgsblendmodes.py)
16+
ADD_PYTHON_TEST(PyQgsCategorizedSymbolRendererV2 test_qgscategorizedsymbolrendererv2.py)
1617
ADD_PYTHON_TEST(PyQgsColorScheme test_qgscolorscheme.py)
1718
ADD_PYTHON_TEST(PyQgsColorSchemeRegistry test_qgscolorschemeregistry.py)
1819
ADD_PYTHON_TEST(PyQgsComposerEffects test_qgscomposereffects.py)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsCategorizedSymbolRendererV2
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '2/12/2015'
11+
__copyright__ = 'Copyright 2015, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis
16+
17+
from utilities import (unittest,
18+
TestCase,
19+
getQgisTestApp,
20+
)
21+
from qgis.core import (QgsCategorizedSymbolRendererV2,
22+
QgsRendererCategoryV2,
23+
QgsMarkerSymbolV2,
24+
QgsVectorGradientColorRampV2,
25+
QgsVectorLayer,
26+
QgsFeature,
27+
QgsGeometry,
28+
QgsPoint,
29+
QgsSymbolV2,
30+
QgsSymbolLayerV2Utils,
31+
QgsRenderContext
32+
)
33+
from PyQt4.QtCore import Qt, QVariant
34+
from PyQt4.QtXml import QDomDocument
35+
from PyQt4.QtGui import QColor
36+
37+
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
38+
39+
40+
def createMarkerSymbol():
41+
symbol = QgsMarkerSymbolV2.createSimple({
42+
"color": "100,150,50",
43+
"name": "square",
44+
"size": "3.0"
45+
})
46+
return symbol
47+
48+
49+
class TestQgsCategorizedSymbolRendererV2(TestCase):
50+
51+
def testFilter(self):
52+
"""Test filter creation"""
53+
renderer = QgsCategorizedSymbolRendererV2()
54+
renderer.setClassAttribute('field')
55+
56+
renderer.addCategory(QgsRendererCategoryV2('a', createMarkerSymbol(), 'a'))
57+
renderer.addCategory(QgsRendererCategoryV2('b', createMarkerSymbol(), 'b'))
58+
renderer.addCategory(QgsRendererCategoryV2('c', createMarkerSymbol(), 'c'))
59+
# add default category
60+
renderer.addCategory(QgsRendererCategoryV2('', createMarkerSymbol(), 'default'))
61+
62+
self.assertEqual(renderer.filter(), '')
63+
#remove categories, leaving default
64+
assert renderer.updateCategoryRenderState(0, False)
65+
self.assertEqual(renderer.filter(), "(field) NOT IN ('a')")
66+
assert renderer.updateCategoryRenderState(1, False)
67+
self.assertEqual(renderer.filter(), "(field) NOT IN ('a','b')")
68+
assert renderer.updateCategoryRenderState(2, False)
69+
self.assertEqual(renderer.filter(), "(field) NOT IN ('a','b','c')")
70+
#remove default category
71+
assert renderer.updateCategoryRenderState(3, False)
72+
self.assertEqual(renderer.filter(), "FALSE")
73+
#add back other categories, leaving default disabled
74+
assert renderer.updateCategoryRenderState(0, True)
75+
self.assertEqual(renderer.filter(), "(field) IN ('a')")
76+
assert renderer.updateCategoryRenderState(1, True)
77+
self.assertEqual(renderer.filter(), "(field) IN ('a','b')")
78+
assert renderer.updateCategoryRenderState(2, True)
79+
self.assertEqual(renderer.filter(), "(field) IN ('a','b','c')")
80+
81+
renderer.deleteAllCategories()
82+
# just default category
83+
renderer.addCategory(QgsRendererCategoryV2('', createMarkerSymbol(), 'default'))
84+
self.assertEqual(renderer.filter(), '')
85+
assert renderer.updateCategoryRenderState(0, False)
86+
self.assertEqual(renderer.filter(), 'FALSE')
87+
88+
renderer.deleteAllCategories()
89+
# no default category
90+
renderer.addCategory(QgsRendererCategoryV2('a', createMarkerSymbol(), 'a'))
91+
renderer.addCategory(QgsRendererCategoryV2('b', createMarkerSymbol(), 'b'))
92+
renderer.addCategory(QgsRendererCategoryV2('c', createMarkerSymbol(), 'c'))
93+
self.assertEqual(renderer.filter(), "(field) IN ('a','b','c')")
94+
assert renderer.updateCategoryRenderState(0, False)
95+
self.assertEqual(renderer.filter(), "(field) IN ('b','c')")
96+
assert renderer.updateCategoryRenderState(2, False)
97+
self.assertEqual(renderer.filter(), "(field) IN ('b')")
98+
assert renderer.updateCategoryRenderState(1, False)
99+
self.assertEqual(renderer.filter(), "FALSE")
100+
101+
renderer.deleteAllCategories()
102+
#numeric categories
103+
renderer.addCategory(QgsRendererCategoryV2(1, createMarkerSymbol(), 'a'))
104+
renderer.addCategory(QgsRendererCategoryV2(2, createMarkerSymbol(), 'b'))
105+
renderer.addCategory(QgsRendererCategoryV2(3, createMarkerSymbol(), 'c'))
106+
self.assertEqual(renderer.filter(), '(field) IN (1,2,3)')
107+
assert renderer.updateCategoryRenderState(0, False)
108+
self.assertEqual(renderer.filter(), "(field) IN (2,3)")
109+
assert renderer.updateCategoryRenderState(2, False)
110+
self.assertEqual(renderer.filter(), "(field) IN (2)")
111+
assert renderer.updateCategoryRenderState(1, False)
112+
self.assertEqual(renderer.filter(), "FALSE")
113+
114+
115+
if __name__ == "__main__":
116+
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.