Skip to content

Commit

Permalink
Add possibility to handle aggregate calculation at data provider
Browse files Browse the repository at this point in the history
(not implemented for any providers yet)
  • Loading branch information
nyalldawson committed May 17, 2016
1 parent 50e41c8 commit dcc047a
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 14 deletions.
4 changes: 3 additions & 1 deletion python/core/qgsaggregatecalculator.sip
@@ -1,7 +1,9 @@
/** \ingroup core
* \class QgsAggregateCalculator
* \brief Utility class for calculating aggregates for a field (or expression) over the features
* from a vector layer.
* from a vector layer. It is recommended that QgsVectorLayer::aggregate() is used rather then
* directly using this class, as the QgsVectorLayer method can handle delegating aggregate calculation
* to a data provider for remote calculation.
* \note added in QGIS 2.16
*/
class QgsAggregateCalculator
Expand Down
17 changes: 17 additions & 0 deletions python/core/qgsvectordataprovider.sip
Expand Up @@ -154,6 +154,23 @@ class QgsVectorDataProvider : QgsDataProvider
*/
virtual void uniqueValues( int index, QList<QVariant> &uniqueValues /Out/, int limit = -1 );

/** 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.
* @param aggregate aggregate to calculate
* @param index the index of the attribute to calculate aggregate over
* @param filter optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features
* @param context expression context for filter
* @param ok will be set to true if calculation was successfully performed by the data provider
* @return calculated aggregate value
* @note added in QGIS 2.16
*/
virtual QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
int index,
const QString& filter,
QgsExpressionContext* context,
bool& ok );

/**
* Returns the possible enum values of an attribute. Returns an empty stringlist if a provider does not support enum types
* or if the given attribute is not an enum type.
Expand Down
16 changes: 16 additions & 0 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -1275,6 +1275,22 @@ class QgsVectorLayer : QgsMapLayer
/** Returns maximum value for an attribute column or invalid variant in case of error */
QVariant maximumValue( int index );

/** Calculates an aggregated value from the layer's features.
* @param aggregate aggregate to calculate
* @param fieldOrExpression source field or expression to use as basis for aggregated values.
* @param filter optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features
* @param context expression context for expressions and filters
* @param ok if specified, will be set to true if aggregate calculation was successful
* @return calculated aggregate value
* @note added in QGIS 2.16
*/
QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
const QString& fieldOrExpression,
const QString& filter = QString(),
QgsExpressionContext* context = nullptr,
bool* ok = nullptr );

/** Fetches all values from a specified field name or expression.
* @param fieldOrExpression field name or an expression string
* @param ok will be set to false if field or expression is invalid, otherwise true
Expand Down
38 changes: 27 additions & 11 deletions src/core/qgsaggregatecalculator.cpp
Expand Up @@ -101,8 +101,16 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag
resultType = mLayer->fields().at( attrNum ).type();
}


QgsFeatureIterator fit = mLayer->getFeatures( request );
return calculate( aggregate, fit, resultType, attrNum, expression.data(), context, ok );
}

QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate aggregate, QgsFeatureIterator& fit, QVariant::Type resultType,
int attr, QgsExpression* expression, QgsExpressionContext* context, bool* ok )
{
if ( ok )
*ok = false;

switch ( resultType )
{
case QVariant::Int:
Expand All @@ -111,32 +119,40 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag
case QVariant::ULongLong:
case QVariant::Double:
{
QgsStatisticalSummary::Statistic stat = numericStatFromAggregate( aggregate, ok );
if ( !ok )
bool statOk = false;
QgsStatisticalSummary::Statistic stat = numericStatFromAggregate( aggregate, &statOk );
if ( !statOk )
return QVariant();


return calculateNumericAggregate( fit, attrNum, expression.data(), context, stat );
if ( ok )
*ok = true;
return calculateNumericAggregate( fit, attr, expression, context, stat );
}

case QVariant::Date:
case QVariant::DateTime:
{
QgsDateTimeStatisticalSummary::Statistic stat = dateTimeStatFromAggregate( aggregate, ok );
if ( !ok )
bool statOk = false;
QgsDateTimeStatisticalSummary::Statistic stat = dateTimeStatFromAggregate( aggregate, &statOk );
if ( !statOk )
return QVariant();

return calculateDateTimeAggregate( fit, attrNum, expression.data(), context, stat );
if ( ok )
*ok = true;
return calculateDateTimeAggregate( fit, attr, expression, context, stat );
}

default:
{
// treat as string
QgsStringStatisticalSummary::Statistic stat = stringStatFromAggregate( aggregate, ok );
if ( !ok )
bool statOk = false;
QgsStringStatisticalSummary::Statistic stat = stringStatFromAggregate( aggregate, &statOk );
if ( !statOk )
return QVariant();

return calculateStringAggregate( fit, attrNum, expression.data(), context, stat );
if ( ok )
*ok = true;
return calculateStringAggregate( fit, attr, expression, context, stat );
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/core/qgsaggregatecalculator.h
Expand Up @@ -21,6 +21,8 @@
#include "qgsstatisticalsummary.h"
#include "qgsdatetimestatisticalsummary.h"
#include "qgsstringstatisticalsummary.h"
#include <QVariant>


class QgsFeatureIterator;
class QgsExpression;
Expand All @@ -30,7 +32,9 @@ class QgsExpressionContext;
/** \ingroup core
* \class QgsAggregateCalculator
* \brief Utility class for calculating aggregates for a field (or expression) over the features
* from a vector layer.
* from a vector layer. It is recommended that QgsVectorLayer::aggregate() is used rather then
* directly using this class, as the QgsVectorLayer method can handle delegating aggregate calculation
* to a data provider for remote calculation.
* \note added in QGIS 2.16
*/
class CORE_EXPORT QgsAggregateCalculator
Expand Down Expand Up @@ -114,6 +118,10 @@ class CORE_EXPORT QgsAggregateCalculator
QgsExpressionContext* context, QgsDateTimeStatisticalSummary::Statistic stat );

QgsExpressionContext* createContext() const;

static QVariant calculate( Aggregate aggregate, QgsFeatureIterator& fit, QVariant::Type resultType,
int attr, QgsExpression* expression,
QgsExpressionContext* context, bool* ok = nullptr );
};

#endif //QGSAGGREGATECALCULATOR_H
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsstatisticalsummary.h
Expand Up @@ -144,6 +144,7 @@ class CORE_EXPORT QgsStatisticalSummary
int count() const { return mCount; }

/** Returns the number of missing (null) values
* @note added in QGIS 2.16
*/
int countMissing() const { return mMissing; }

Expand Down
13 changes: 13 additions & 0 deletions src/core/qgsvectordataprovider.cpp
Expand Up @@ -381,6 +381,19 @@ void QgsVectorDataProvider::uniqueValues( int index, QList<QVariant> &values, in
}
}

QVariant QgsVectorDataProvider::aggregate( QgsAggregateCalculator::Aggregate aggregate, int index,
const QString& filter, QgsExpressionContext* context, bool& ok )
{
//base implementation does nothing
Q_UNUSED( aggregate );
Q_UNUSED( index );
Q_UNUSED( filter );
Q_UNUSED( context );

ok = false;
return QVariant();
}

void QgsVectorDataProvider::clearMinMaxCache()
{
mCacheMinMaxDirty = true;
Expand Down
18 changes: 18 additions & 0 deletions src/core/qgsvectordataprovider.h
Expand Up @@ -28,6 +28,7 @@ class QTextCodec;
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgsrectangle.h"
#include "qgsaggregatecalculator.h"

typedef QList<int> QgsAttributeList;
typedef QSet<int> QgsAttributeIds;
Expand Down Expand Up @@ -202,6 +203,23 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
*/
virtual void uniqueValues( int index, QList<QVariant> &uniqueValues, int limit = -1 );

/** 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.
* @param aggregate aggregate to calculate
* @param index the index of the attribute to calculate aggregate over
* @param filter optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features
* @param context expression context for filter
* @param ok will be set to true if calculation was successfully performed by the data provider
* @return calculated aggregate value
* @note added in QGIS 2.16
*/
virtual QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
int index,
const QString& filter,
QgsExpressionContext* context,
bool& ok );

/**
* Returns the possible enum values of an attribute. Returns an empty stringlist if a provider does not support enum types
* or if the given attribute is not an enum type.
Expand Down
38 changes: 38 additions & 0 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -3254,6 +3254,44 @@ QVariant QgsVectorLayer::maximumValue( int index )
return QVariant();
}

QVariant QgsVectorLayer::aggregate( QgsAggregateCalculator::Aggregate aggregate, const QString& fieldOrExpression,
const QString& filter, QgsExpressionContext* context, bool* ok )
{
if ( ok )
*ok = false;

if ( !mDataProvider )
{
return QVariant();
}

// test if we are calculating based on a field
int attrIndex = mUpdatedFields.fieldNameIndex( fieldOrExpression );
if ( attrIndex >= 0 )
{
// aggregate is based on a field - if it's a provider field, we could possibly hand over the calculation
// to the provider itself
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin( attrIndex );
if ( origin == QgsFields::OriginProvider )
{
bool providerOk = false;
QVariant val = mDataProvider->aggregate( aggregate, attrIndex, filter, context, providerOk );
if ( providerOk )
{
// provider handled calculation
if ( ok )
*ok = true;
return val;
}
}
}

// fallback to using aggregate calculator to determine aggregate
QgsAggregateCalculator c( this );
c.setFilter( filter );
return c.calculate( aggregate, fieldOrExpression, context, ok );
}

QList<QVariant> QgsVectorLayer::getValues( const QString &fieldOrExpression, bool& ok, bool selectedOnly )
{
QList<QVariant> values;
Expand Down
17 changes: 17 additions & 0 deletions src/core/qgsvectorlayer.h
Expand Up @@ -35,6 +35,7 @@
#include "qgsvectorsimplifymethod.h"
#include "qgseditformconfig.h"
#include "qgsattributetableconfig.h"
#include "qgsaggregatecalculator.h"

class QPainter;
class QImage;
Expand Down Expand Up @@ -1656,6 +1657,22 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
/** Returns maximum value for an attribute column or invalid variant in case of error */
QVariant maximumValue( int index );

/** Calculates an aggregated value from the layer's features.
* @param aggregate aggregate to calculate
* @param fieldOrExpression source field or expression to use as basis for aggregated values.
* @param filter optional filter for calculating aggregate over a subset of features, or an
* empty string to use all features
* @param context expression context for expressions and filters
* @param ok if specified, will be set to true if aggregate calculation was successful
* @return calculated aggregate value
* @note added in QGIS 2.16
*/
QVariant aggregate( QgsAggregateCalculator::Aggregate aggregate,
const QString& fieldOrExpression,
const QString& filter = QString(),
QgsExpressionContext* context = nullptr,
bool* ok = nullptr );

/** Fetches all values from a specified field name or expression.
* @param fieldOrExpression field name or an expression string
* @param ok will be set to false if field or expression is invalid, otherwise true
Expand Down
41 changes: 40 additions & 1 deletion tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -33,7 +33,8 @@
QgsSingleSymbolRendererV2,
QgsCoordinateReferenceSystem,
QgsProject,
QgsUnitTypes)
QgsUnitTypes,
QgsAggregateCalculator)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
start_app()
Expand Down Expand Up @@ -1062,6 +1063,44 @@ def test_ExpressionFilter(self):

assert(len(list(features)) == 1)

def testAggregate(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)

tests = [[QgsAggregateCalculator.Count, 6],
[QgsAggregateCalculator.Sum, 24],
[QgsAggregateCalculator.Mean, 4],
[QgsAggregateCalculator.StDev, 2.0816],
[QgsAggregateCalculator.StDevSample, 2.2803],
[QgsAggregateCalculator.Min, 2],
[QgsAggregateCalculator.Max, 8],
[QgsAggregateCalculator.Range, 6],
[QgsAggregateCalculator.Median, 3.5],
[QgsAggregateCalculator.CountDistinct, 5],
[QgsAggregateCalculator.CountMissing, 1],
[QgsAggregateCalculator.FirstQuartile, 2],
[QgsAggregateCalculator.ThirdQuartile, 5.0],
[QgsAggregateCalculator.InterQuartileRange, 3.0]
]

for t in tests:
val, ok = layer.aggregate(t[0], 'fldint')
self.assertTrue(ok)
if isinstance(t[1], int):
self.assertEqual(val, t[1])
else:
self.assertAlmostEqual(val, t[1], 3)

def onLayerTransparencyChanged(self, tr):
self.transparencyTest = tr

Expand Down

0 comments on commit dcc047a

Please sign in to comment.