Skip to content
Permalink
Browse files

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.
  • Loading branch information
nyalldawson committed Dec 3, 2015
1 parent 97e5d31 commit 9eee12111567a84f4d4de7e020392b3c01c28598
@@ -66,6 +66,8 @@ class QgsCategorizedSymbolRendererV2 : QgsFeatureRendererV2
//! returns bitwise OR-ed capabilities of the renderer
virtual int capabilities();

virtual QString filter( const QgsFields& fields = QgsFields() );

//! @note available in python as symbols2
virtual QgsSymbolV2List symbols( QgsRenderContext& context ) /PyName=symbols2/;
void updateSymbols( QgsSymbolV2 * sym );
@@ -103,7 +103,18 @@ class QgsFeatureRendererV2

virtual void stopRender( QgsRenderContext& context ) = 0;

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

virtual QList<QString> usedAttributes() = 0;

@@ -326,7 +326,7 @@ class QgsRuleBasedRendererV2 : QgsFeatureRendererV2

virtual void stopRender( QgsRenderContext& context );

virtual QString filter();
virtual QString filter( const QgsFields& fields = QgsFields() );

virtual QList<QString> usedAttributes();

@@ -145,7 +145,7 @@ bool QgsVectorLayerRenderer::render()

mRendererV2->startRender( mContext, mFields );

QString rendererFilter = mRendererV2->filter();
QString rendererFilter = mRendererV2->filter( mFields );

QgsRectangle requestExtent = mContext.extent();
mRendererV2->modifyRequestExtent( requestExtent, mContext );
@@ -519,6 +519,75 @@ void QgsCategorizedSymbolRendererV2::toSld( QDomDocument &doc, QDomElement &elem
}
}

QString QgsCategorizedSymbolRendererV2::filter( const QgsFields& fields )
{
int attrNum = fields.fieldNameIndex( mAttrName );
bool isExpression = ( attrNum == -1 );

bool hasDefault = false;
bool defaultActive = false;
bool allActive = true;
bool noneActive = true;

//we need to build lists of both inactive and active values, as either list may be required
//depending on whether the default category is active or not
QString activeValues;
QString inactiveValues;

Q_FOREACH ( const QgsRendererCategoryV2& cat, mCategories )
{
if ( cat.value() == "" )
{
hasDefault = true;
defaultActive = cat.renderState();
}

noneActive = noneActive && !cat.renderState();
allActive = allActive && cat.renderState();

QVariant::Type valType = isExpression ? cat.value().type() : fields.at( attrNum ).type();
QString value = QgsExpression::quotedValue( cat.value(), valType );

if ( !cat.renderState() )
{
if ( cat.value() != "" )
{
if ( !inactiveValues.isEmpty() )
inactiveValues.append( ',' );

inactiveValues.append( value );
}
}
else
{
if ( cat.value() != "" )
{
if ( !activeValues.isEmpty() )
activeValues.append( ',' );

activeValues.append( value );
}
}
}

if ( allActive && hasDefault )
{
return QString();
}
else if ( noneActive )
{
return "FALSE";
}
else if ( defaultActive )
{
return QString( "(%1) NOT IN (%2)" ).arg( mAttrName, inactiveValues );
}
else
{
return QString( "(%1) IN (%2)" ).arg( mAttrName, activeValues );
}
}

QgsSymbolV2List QgsCategorizedSymbolRendererV2::symbols( QgsRenderContext &context )
{
Q_UNUSED( context );
@@ -97,6 +97,8 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
//! returns bitwise OR-ed capabilities of the renderer
virtual int capabilities() override { return SymbolLevels | RotationField | Filter; }

virtual QString filter( const QgsFields& fields = QgsFields() ) override;

//! @note available in python as symbols2
virtual QgsSymbolV2List symbols( QgsRenderContext& context ) override;
void updateSymbols( QgsSymbolV2 * sym );
@@ -20,6 +20,7 @@
#include "qgsrectangle.h"
#include "qgsrendercontext.h"
#include "qgssymbolv2.h"
#include "qgsfield.h"

#include <QList>
#include <QString>
@@ -30,7 +31,6 @@
#include <QDomElement>

class QgsFeature;
class QgsFields;
class QgsVectorLayer;
class QgsPaintEffect;

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

virtual QList<QString> usedAttributes() = 0;

@@ -895,7 +895,7 @@ void QgsRuleBasedRendererV2::stopRender( QgsRenderContext& context )
mRootRule->stopRender( context );
}

QString QgsRuleBasedRendererV2::filter()
QString QgsRuleBasedRendererV2::filter( const QgsFields& )
{
return mFilter;
}
@@ -385,7 +385,7 @@ class CORE_EXPORT QgsRuleBasedRendererV2 : public QgsFeatureRendererV2

virtual void stopRender( QgsRenderContext& context ) override;

virtual QString filter() override;
virtual QString filter( const QgsFields& fields = QgsFields() ) override;

virtual QList<QString> usedAttributes() override;

@@ -13,6 +13,7 @@ ADD_PYTHON_TEST(PyQgsAtlasComposition test_qgsatlascomposition.py)
ADD_PYTHON_TEST(PyQgsAttributeTableModel test_qgsattributetablemodel.py)
#ADD_PYTHON_TEST(PyQgsAuthenticationSystem test_qgsauthsystem.py)
ADD_PYTHON_TEST(PyQgsBlendModes test_qgsblendmodes.py)
ADD_PYTHON_TEST(PyQgsCategorizedSymbolRendererV2 test_qgscategorizedsymbolrendererv2.py)
ADD_PYTHON_TEST(PyQgsColorScheme test_qgscolorscheme.py)
ADD_PYTHON_TEST(PyQgsColorSchemeRegistry test_qgscolorschemeregistry.py)
ADD_PYTHON_TEST(PyQgsComposerEffects test_qgscomposereffects.py)
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsCategorizedSymbolRendererV2
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Nyall Dawson'
__date__ = '2/12/2015'
__copyright__ = 'Copyright 2015, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import qgis

from utilities import (unittest,
TestCase,
getQgisTestApp,
)
from qgis.core import (QgsCategorizedSymbolRendererV2,
QgsRendererCategoryV2,
QgsMarkerSymbolV2,
QgsVectorGradientColorRampV2,
QgsVectorLayer,
QgsFeature,
QgsGeometry,
QgsPoint,
QgsSymbolV2,
QgsSymbolLayerV2Utils,
QgsRenderContext
)
from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtXml import QDomDocument
from PyQt4.QtGui import QColor

QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()


def createMarkerSymbol():
symbol = QgsMarkerSymbolV2.createSimple({
"color": "100,150,50",
"name": "square",
"size": "3.0"
})
return symbol


class TestQgsCategorizedSymbolRendererV2(TestCase):

def testFilter(self):
"""Test filter creation"""
renderer = QgsCategorizedSymbolRendererV2()
renderer.setClassAttribute('field')

renderer.addCategory(QgsRendererCategoryV2('a', createMarkerSymbol(), 'a'))
renderer.addCategory(QgsRendererCategoryV2('b', createMarkerSymbol(), 'b'))
renderer.addCategory(QgsRendererCategoryV2('c', createMarkerSymbol(), 'c'))
# add default category
renderer.addCategory(QgsRendererCategoryV2('', createMarkerSymbol(), 'default'))

self.assertEqual(renderer.filter(), '')
#remove categories, leaving default
assert renderer.updateCategoryRenderState(0, False)
self.assertEqual(renderer.filter(), "(field) NOT IN ('a')")
assert renderer.updateCategoryRenderState(1, False)
self.assertEqual(renderer.filter(), "(field) NOT IN ('a','b')")
assert renderer.updateCategoryRenderState(2, False)
self.assertEqual(renderer.filter(), "(field) NOT IN ('a','b','c')")
#remove default category
assert renderer.updateCategoryRenderState(3, False)
self.assertEqual(renderer.filter(), "FALSE")
#add back other categories, leaving default disabled
assert renderer.updateCategoryRenderState(0, True)
self.assertEqual(renderer.filter(), "(field) IN ('a')")
assert renderer.updateCategoryRenderState(1, True)
self.assertEqual(renderer.filter(), "(field) IN ('a','b')")
assert renderer.updateCategoryRenderState(2, True)
self.assertEqual(renderer.filter(), "(field) IN ('a','b','c')")

renderer.deleteAllCategories()
# just default category
renderer.addCategory(QgsRendererCategoryV2('', createMarkerSymbol(), 'default'))
self.assertEqual(renderer.filter(), '')
assert renderer.updateCategoryRenderState(0, False)
self.assertEqual(renderer.filter(), 'FALSE')

renderer.deleteAllCategories()
# no default category
renderer.addCategory(QgsRendererCategoryV2('a', createMarkerSymbol(), 'a'))
renderer.addCategory(QgsRendererCategoryV2('b', createMarkerSymbol(), 'b'))
renderer.addCategory(QgsRendererCategoryV2('c', createMarkerSymbol(), 'c'))
self.assertEqual(renderer.filter(), "(field) IN ('a','b','c')")
assert renderer.updateCategoryRenderState(0, False)
self.assertEqual(renderer.filter(), "(field) IN ('b','c')")
assert renderer.updateCategoryRenderState(2, False)
self.assertEqual(renderer.filter(), "(field) IN ('b')")
assert renderer.updateCategoryRenderState(1, False)
self.assertEqual(renderer.filter(), "FALSE")

renderer.deleteAllCategories()
#numeric categories
renderer.addCategory(QgsRendererCategoryV2(1, createMarkerSymbol(), 'a'))
renderer.addCategory(QgsRendererCategoryV2(2, createMarkerSymbol(), 'b'))
renderer.addCategory(QgsRendererCategoryV2(3, createMarkerSymbol(), 'c'))
self.assertEqual(renderer.filter(), '(field) IN (1,2,3)')
assert renderer.updateCategoryRenderState(0, False)
self.assertEqual(renderer.filter(), "(field) IN (2,3)")
assert renderer.updateCategoryRenderState(2, False)
self.assertEqual(renderer.filter(), "(field) IN (2)")
assert renderer.updateCategoryRenderState(1, False)
self.assertEqual(renderer.filter(), "FALSE")


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

0 comments on commit 9eee121

Please sign in to comment.
You can’t perform that action at this time.