Add possibility to handle aggregate calculation at data provider

(not implemented for any providers yet)
This commit is contained in:
Nyall Dawson 2016-05-16 14:36:04 +10:00
parent 50e41c8133
commit dcc047af49
11 changed files with 199 additions and 14 deletions

View File

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

View File

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

View File

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

View File

@ -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:
@ -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 );
}
}

View File

@ -21,6 +21,8 @@
#include "qgsstatisticalsummary.h"
#include "qgsdatetimestatisticalsummary.h"
#include "qgsstringstatisticalsummary.h"
#include <QVariant>
class QgsFeatureIterator;
class QgsExpression;
@ -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
@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@
#include "qgsvectorsimplifymethod.h"
#include "qgseditformconfig.h"
#include "qgsattributetableconfig.h"
#include "qgsaggregatecalculator.h"
class QPainter;
class QImage;
@ -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

View File

@ -33,7 +33,8 @@ from qgis.core import (QGis,
QgsSingleSymbolRendererV2,
QgsCoordinateReferenceSystem,
QgsProject,
QgsUnitTypes)
QgsUnitTypes,
QgsAggregateCalculator)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
start_app()
@ -1062,6 +1063,44 @@ class TestQgsVectorLayer(unittest.TestCase):
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