From a02a4ede6e314e46bcbe1afe05a171618c124610 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 15 Jun 2019 17:00:26 -0400 Subject: [PATCH] Allow calculating aggregates using a subset of fids only --- .../qgsaggregatecalculator.sip.in | 11 ++++++- .../qgsvectordataprovider.sip.in | 4 ++- .../core/auto_generated/qgsvectorlayer.sip.in | 5 ++- src/core/qgsaggregatecalculator.cpp | 31 ++++++++++++------- src/core/qgsaggregatecalculator.h | 16 +++++++++- src/core/qgsvectordataprovider.cpp | 3 +- src/core/qgsvectordataprovider.h | 4 ++- src/core/qgsvectorlayer.cpp | 7 +++-- src/core/qgsvectorlayer.h | 5 ++- .../src/python/test_qgsaggregatecalculator.py | 13 ++++++++ tests/src/python/test_qgsvectorlayer.py | 18 +++++++++++ 11 files changed, 96 insertions(+), 21 deletions(-) diff --git a/python/core/auto_generated/qgsaggregatecalculator.sip.in b/python/core/auto_generated/qgsaggregatecalculator.sip.in index d90b1947ab8..f2a6f4144e6 100644 --- a/python/core/auto_generated/qgsaggregatecalculator.sip.in +++ b/python/core/auto_generated/qgsaggregatecalculator.sip.in @@ -95,6 +95,16 @@ Sets a filter to limit the features used during the aggregate calculation. :param filterExpression: expression for filtering features, or empty string to remove filter +.. seealso:: :py:func:`filter` +%End + + void setFidsFilter( const QgsFeatureIds &fids ); +%Docstring +Sets a filter to limit the features used during the aggregate calculation. +If an expression filter is set, it will override this filter. + +:param fids: feature ids for feature filtering, and empty list will return no features. + .. seealso:: :py:func:`filter` %End @@ -154,7 +164,6 @@ Structured information for available aggregates. }; - /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/core/auto_generated/qgsvectordataprovider.sip.in b/python/core/auto_generated/qgsvectordataprovider.sip.in index ca9b45afc07..db500295d33 100644 --- a/python/core/auto_generated/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/qgsvectordataprovider.sip.in @@ -206,7 +206,8 @@ matching is done in a case-insensitive manner. int index, const QgsAggregateCalculator::AggregateParameters ¶meters, QgsExpressionContext *context, - bool &ok ) const; + bool &ok, + QgsFeatureIds *fids = 0 ) const; %Docstring Calculates an aggregated value from the layer's features. The base implementation does nothing, but subclasses can override this method to handoff calculation of aggregates to the provider. @@ -216,6 +217,7 @@ but subclasses can override this method to handoff calculation of aggregates to :param parameters: parameters controlling aggregate calculation :param context: expression context for filter :param ok: will be set to ``True`` if calculation was successfully performed by the data provider +:param fids: list of fids to filter, otherwise will use all fids :return: calculated aggregate value diff --git a/python/core/auto_generated/qgsvectorlayer.sip.in b/python/core/auto_generated/qgsvectorlayer.sip.in index 71b4f469d54..e2c8e4cfb76 100644 --- a/python/core/auto_generated/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/qgsvectorlayer.sip.in @@ -2101,15 +2101,18 @@ been changed inside the edit buffer then the previous saved value may be returne const QString &fieldOrExpression, const QgsAggregateCalculator::AggregateParameters ¶meters = QgsAggregateCalculator::AggregateParameters(), QgsExpressionContext *context = 0, - bool *ok = 0 ) const; + bool *ok = 0, + QgsFeatureIds *fids = 0 ) const; %Docstring Calculates an aggregated value from the layer's features. +Currently any filtering expression provided will override filters in the FeatureRequest. :param aggregate: aggregate to calculate :param fieldOrExpression: source field or expression to use as basis for aggregated values. :param parameters: parameters controlling aggregate calculation :param context: expression context for expressions and filters :param ok: if specified, will be set to ``True`` if aggregate calculation was successful +:param fids: list of fids to filter, otherwise will use all fids :return: calculated aggregate value diff --git a/src/core/qgsaggregatecalculator.cpp b/src/core/qgsaggregatecalculator.cpp index c57572d0f7b..f15d3c7103b 100644 --- a/src/core/qgsaggregatecalculator.cpp +++ b/src/core/qgsaggregatecalculator.cpp @@ -42,13 +42,20 @@ void QgsAggregateCalculator::setParameters( const AggregateParameters ¶meter mOrderBy = parameters.orderBy; } +void QgsAggregateCalculator::setFidsFilter( const QgsFeatureIds &fids ) +{ + mFidsSet = true; + mFidsFilter = fids; +} + QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate aggregate, - const QString &fieldOrExpression, - QgsExpressionContext *context, bool *ok ) const + const QString &fieldOrExpression, QgsExpressionContext *context, bool *ok ) const { if ( ok ) *ok = false; + QgsFeatureRequest request = QgsFeatureRequest(); + if ( !mLayer ) return QVariant(); @@ -67,9 +74,7 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag expression.reset( new QgsExpression( fieldOrExpression ) ); if ( expression->hasParserError() || !expression->prepare( context ) ) - { return QVariant(); - } } QSet lst; @@ -78,18 +83,21 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag else lst = expression->referencedColumns(); - QgsFeatureRequest request = QgsFeatureRequest() - .setFlags( ( expression && expression->needsGeometry() ) ? - QgsFeatureRequest::NoFlags : - QgsFeatureRequest::NoGeometry ) - .setSubsetOfAttributes( lst, mLayer->fields() ); + request.setFlags( ( expression && expression->needsGeometry() ) ? + QgsFeatureRequest::NoFlags : + QgsFeatureRequest::NoGeometry ) + .setSubsetOfAttributes( lst, mLayer->fields() ); + + if ( mFidsSet ) + request.setFilterFids( mFidsFilter ); + if ( !mOrderBy.empty() ) request.setOrderBy( mOrderBy ); + if ( !mFilterExpression.isEmpty() ) request.setFilterExpression( mFilterExpression ); if ( context ) request.setExpressionContext( *context ); - //determine result type QVariant::Type resultType = QVariant::Double; if ( attrNum == -1 ) @@ -113,9 +121,7 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag resultType = v.type(); } else - { resultType = mLayer->fields().at( attrNum ).type(); - } QgsFeatureIterator fit = mLayer->getFeatures( request ); return calculate( aggregate, fit, resultType, attrNum, expression.get(), mDelimiter, context, ok ); @@ -846,3 +852,4 @@ QVariant QgsAggregateCalculator::calculateArrayAggregate( QgsFeatureIterator &fi } return array; } + diff --git a/src/core/qgsaggregatecalculator.h b/src/core/qgsaggregatecalculator.h index 111b8221b6d..e805a8ee56b 100644 --- a/src/core/qgsaggregatecalculator.h +++ b/src/core/qgsaggregatecalculator.h @@ -24,6 +24,7 @@ #include "qgsstringstatisticalsummary.h" #include "qgsfeaturerequest.h" #include +#include "qgsfeatureid.h" class QgsFeatureIterator; @@ -136,6 +137,14 @@ class CORE_EXPORT QgsAggregateCalculator */ void setFilter( const QString &filterExpression ) { mFilterExpression = filterExpression; } + /** + * Sets a filter to limit the features used during the aggregate calculation. + * If an expression filter is set, it will override this filter. + * \param fids feature ids for feature filtering, and empty list will return no features. + * \see filter() + */ + void setFidsFilter( const QgsFeatureIds &fids ); + /** * Returns the filter which limits the features used during the aggregate calculation. * \see setFilter() @@ -196,6 +205,12 @@ class CORE_EXPORT QgsAggregateCalculator //! Delimiter to use for concatenate aggregate QString mDelimiter; + //!list of fids to filter + QgsFeatureIds mFidsFilter; + + //trigger variable + bool mFidsSet = false; + static QgsStatisticalSummary::Statistic numericStatFromAggregate( Aggregate aggregate, bool *ok = nullptr ); static QgsStringStatisticalSummary::Statistic stringStatFromAggregate( Aggregate aggregate, bool *ok = nullptr ); static QgsDateTimeStatisticalSummary::Statistic dateTimeStatFromAggregate( Aggregate aggregate, bool *ok = nullptr ); @@ -224,4 +239,3 @@ class CORE_EXPORT QgsAggregateCalculator }; #endif //QGSAGGREGATECALCULATOR_H - diff --git a/src/core/qgsvectordataprovider.cpp b/src/core/qgsvectordataprovider.cpp index 4c8d0a375dc..7c40d00e24d 100644 --- a/src/core/qgsvectordataprovider.cpp +++ b/src/core/qgsvectordataprovider.cpp @@ -466,13 +466,14 @@ QStringList QgsVectorDataProvider::uniqueStringsMatching( int index, const QStri } QVariant QgsVectorDataProvider::aggregate( QgsAggregateCalculator::Aggregate aggregate, int index, - const QgsAggregateCalculator::AggregateParameters ¶meters, QgsExpressionContext *context, bool &ok ) const + const QgsAggregateCalculator::AggregateParameters ¶meters, QgsExpressionContext *context, bool &ok, QgsFeatureIds *fids ) const { //base implementation does nothing Q_UNUSED( aggregate ) Q_UNUSED( index ) Q_UNUSED( parameters ) Q_UNUSED( context ) + Q_UNUSED( fids ) ok = false; return QVariant(); diff --git a/src/core/qgsvectordataprovider.h b/src/core/qgsvectordataprovider.h index 11380a447be..3c33bc78775 100644 --- a/src/core/qgsvectordataprovider.h +++ b/src/core/qgsvectordataprovider.h @@ -239,6 +239,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat * \param parameters parameters controlling aggregate calculation * \param context expression context for filter * \param ok will be set to TRUE if calculation was successfully performed by the data provider + * \param fids list of fids to filter, otherwise will use all fids * \returns calculated aggregate value * \since QGIS 2.16 */ @@ -246,7 +247,8 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat int index, const QgsAggregateCalculator::AggregateParameters ¶meters, QgsExpressionContext *context, - bool &ok ) const; + bool &ok, + QgsFeatureIds *fids = nullptr ) const; /** * Returns the possible enum values of an attribute. Returns an empty stringlist if a provider does not support enum types diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index d536bcf1be2..2e96d662743 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -3912,7 +3912,8 @@ QVariant QgsVectorLayer::maximumValue( int index ) const } QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate, const QString &fieldOrExpression, - const QgsAggregateCalculator::AggregateParameters ¶meters, QgsExpressionContext *context, bool *ok ) const + const QgsAggregateCalculator::AggregateParameters ¶meters, QgsExpressionContext *context, + bool *ok, QgsFeatureIds *fids ) const { if ( ok ) *ok = false; @@ -3932,7 +3933,7 @@ QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate, if ( origin == QgsFields::OriginProvider ) { bool providerOk = false; - QVariant val = mDataProvider->aggregate( aggregate, attrIndex, parameters, context, providerOk ); + QVariant val = mDataProvider->aggregate( aggregate, attrIndex, parameters, context, providerOk, fids ); if ( providerOk ) { // provider handled calculation @@ -3945,6 +3946,8 @@ QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate, // fallback to using aggregate calculator to determine aggregate QgsAggregateCalculator c( this ); + if ( fids ) + c.setFidsFilter( *fids ); c.setParameters( parameters ); return c.calculate( aggregate, fieldOrExpression, context, ok ); } diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 56c6d9003b5..9b4df827e14 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -1925,11 +1925,13 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte /** * Calculates an aggregated value from the layer's features. + * Currently any filtering expression provided will override filters in the FeatureRequest. * \param aggregate aggregate to calculate * \param fieldOrExpression source field or expression to use as basis for aggregated values. * \param parameters parameters controlling aggregate calculation * \param context expression context for expressions and filters * \param ok if specified, will be set to TRUE if aggregate calculation was successful + * \param fids list of fids to filter, otherwise will use all fids * \returns calculated aggregate value * \since QGIS 2.16 */ @@ -1937,7 +1939,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte const QString &fieldOrExpression, const QgsAggregateCalculator::AggregateParameters ¶meters = QgsAggregateCalculator::AggregateParameters(), QgsExpressionContext *context = nullptr, - bool *ok = nullptr ) const; + bool *ok = nullptr, + QgsFeatureIds *fids = nullptr ) const; //! Sets the blending mode used for rendering each feature void setFeatureBlendMode( QPainter::CompositionMode blendMode ); diff --git a/tests/src/python/test_qgsaggregatecalculator.py b/tests/src/python/test_qgsaggregatecalculator.py index 8b9d6919181..b3b2f028f80 100644 --- a/tests/src/python/test_qgsaggregatecalculator.py +++ b/tests/src/python/test_qgsaggregatecalculator.py @@ -416,6 +416,19 @@ class TestQgsAggregateCalculator(unittest.TestCase): self.assertTrue(ok) self.assertEqual(val, 5) + # test with subset + agg = QgsAggregateCalculator(layer) # reset to remove expression filter + agg.setFidsFilter([1, 2]) + val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint') + self.assertTrue(ok) + self.assertEqual(val, 6.0) + + # test with empty subset + agg.setFidsFilter(list()) + val, ok = agg.calculate(QgsAggregateCalculator.Sum, 'fldint') + self.assertTrue(ok) + self.assertEqual(val, 0.0) + def testExpressionNoMatch(self): """ test aggregate calculation using an expression with no features """ diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 33110f7c078..6e80d4ce804 100755 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -2206,6 +2206,24 @@ class TestQgsVectorLayer(unittest.TestCase, FeatureSourceTestCase): vals = [f['virtual'] for f in layer.getFeatures()] self.assertEqual(vals, [48, 48, 48, 48, 48, 48, 48]) + def testAggregateFilter(self): + """ Test aggregate calculation """ + layer = QgsVectorLayer("Point?field=fldint:integer", "layer", "memory") + pr = layer.dataProvider() + + int_values = [4, 2, 3, 2, 5, None, 8] + features = [] + for i in int_values: + f = QgsFeature() + f.setFields(layer.fields()) + f.setAttributes([i]) + features.append(f) + assert pr.addFeatures(features) + + val, ok = layer.aggregate(QgsAggregateCalculator.Sum, 'fldint', fids=[1, 2]) + self.assertTrue(ok) + self.assertEqual(val, 6.0) + def onLayerOpacityChanged(self, tr): self.opacityTest = tr