Skip to content

Commit

Permalink
Allow calculating aggregates using a subset of fids only
Browse files Browse the repository at this point in the history
  • Loading branch information
roya0045 authored and nyalldawson committed Jun 15, 2019
1 parent 6c6c145 commit a02a4ed
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 21 deletions.
11 changes: 10 additions & 1 deletion python/core/auto_generated/qgsaggregatecalculator.sip.in
Expand Up @@ -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

Expand Down Expand Up @@ -154,7 +164,6 @@ Structured information for available aggregates.

};


/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
4 changes: 3 additions & 1 deletion python/core/auto_generated/qgsvectordataprovider.sip.in
Expand Up @@ -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.
Expand All @@ -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

Expand Down
5 changes: 4 additions & 1 deletion python/core/auto_generated/qgsvectorlayer.sip.in
Expand Up @@ -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

Expand Down
31 changes: 19 additions & 12 deletions src/core/qgsaggregatecalculator.cpp
Expand Up @@ -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();

Expand All @@ -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;
Expand All @@ -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 )
Expand All @@ -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 );
Expand Down Expand Up @@ -846,3 +852,4 @@ QVariant QgsAggregateCalculator::calculateArrayAggregate( QgsFeatureIterator &fi
}
return array;
}

16 changes: 15 additions & 1 deletion src/core/qgsaggregatecalculator.h
Expand Up @@ -24,6 +24,7 @@
#include "qgsstringstatisticalsummary.h"
#include "qgsfeaturerequest.h"
#include <QVariant>
#include "qgsfeatureid.h"


class QgsFeatureIterator;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -224,4 +239,3 @@ class CORE_EXPORT QgsAggregateCalculator
};

#endif //QGSAGGREGATECALCULATOR_H

3 changes: 2 additions & 1 deletion src/core/qgsvectordataprovider.cpp
Expand Up @@ -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();
Expand Down
4 changes: 3 additions & 1 deletion src/core/qgsvectordataprovider.h
Expand Up @@ -239,14 +239,16 @@ 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
*/
virtual QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
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
Expand Down
7 changes: 5 additions & 2 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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 );
}
Expand Down
5 changes: 4 additions & 1 deletion src/core/qgsvectorlayer.h
Expand Up @@ -1925,19 +1925,22 @@ 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
*/
QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
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 );
Expand Down
13 changes: 13 additions & 0 deletions tests/src/python/test_qgsaggregatecalculator.py
Expand Up @@ -416,6 +416,19 @@ def testExpression(self):
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 """

Expand Down
18 changes: 18 additions & 0 deletions tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -2206,6 +2206,24 @@ def testAggregateInVirtualField(self):
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

Expand Down

0 comments on commit a02a4ed

Please sign in to comment.