Allow calculating aggregates using a subset of fids only

This commit is contained in:
Alex 2019-06-15 17:00:26 -04:00 committed by Nyall Dawson
parent 6c6c145590
commit a02a4ede6e
11 changed files with 96 additions and 21 deletions

View File

@ -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 *
* *

View File

@ -206,7 +206,8 @@ matching is done in a case-insensitive manner.
int index,
const QgsAggregateCalculator::AggregateParameters &parameters,
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

View File

@ -2101,15 +2101,18 @@ been changed inside the edit buffer then the previous saved value may be returne
const QString &fieldOrExpression,
const QgsAggregateCalculator::AggregateParameters &parameters = 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

View File

@ -42,13 +42,20 @@ void QgsAggregateCalculator::setParameters( const AggregateParameters &parameter
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<QString> 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;
}

View File

@ -24,6 +24,7 @@
#include "qgsstringstatisticalsummary.h"
#include "qgsfeaturerequest.h"
#include <QVariant>
#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

View File

@ -466,13 +466,14 @@ QStringList QgsVectorDataProvider::uniqueStringsMatching( int index, const QStri
}
QVariant QgsVectorDataProvider::aggregate( QgsAggregateCalculator::Aggregate aggregate, int index,
const QgsAggregateCalculator::AggregateParameters &parameters, QgsExpressionContext *context, bool &ok ) const
const QgsAggregateCalculator::AggregateParameters &parameters, 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();

View File

@ -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 &parameters,
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

View File

@ -3912,7 +3912,8 @@ QVariant QgsVectorLayer::maximumValue( int index ) const
}
QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate, const QString &fieldOrExpression,
const QgsAggregateCalculator::AggregateParameters &parameters, QgsExpressionContext *context, bool *ok ) const
const QgsAggregateCalculator::AggregateParameters &parameters, 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 );
}

View File

@ -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 &parameters = 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 );

View File

@ -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 """

View File

@ -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