mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
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.
This commit is contained in:
parent
97e5d31706
commit
9eee121115
@ -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)
|
||||
|
116
tests/src/python/test_qgscategorizedsymbolrendererv2.py
Normal file
116
tests/src/python/test_qgscategorizedsymbolrendererv2.py
Normal file
@ -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()
|
Loading…
x
Reference in New Issue
Block a user