[FEATURE] Add expression function array_agg

This commit is contained in:
arnaud.morvan@camptocamp.com 2017-08-06 15:08:27 +02:00 committed by Matthias Kuhn
parent c8876f2c23
commit e34a593c65
7 changed files with 83 additions and 3 deletions

View File

@ -47,7 +47,8 @@ class QgsAggregateCalculator
StringMinimumLength,
StringMaximumLength,
StringConcatenate,
GeometryCollect
GeometryCollect,
ArrayAggregate
};
struct AggregateParameters

View File

@ -0,0 +1,13 @@
{
"name": "array_agg",
"type": "function",
"description": "Returns an array of aggregated values from a field or expression.",
"arguments": [
{"arg": "expression", "description": "sub expression of field to aggregate"},
{"arg": "group_by", "optional": true, "description": "optional expression to use to group aggregate calculations"},
{"arg": "filter", "optional": true, "description": "optional expression to use to filter features used to calculate aggregate"}
],
"examples": [
{ "expression": "array_agg(\"name\",group_by:=\"state\")", "returns":"list of name values, grouped by state field"}
]
}

View File

@ -830,6 +830,11 @@ static QVariant fcnAggregateStringConcat( const QVariantList &values, const QgsE
return fcnAggregateGeneric( QgsAggregateCalculator::StringConcatenate, values, parameters, context, parent );
}
static QVariant fcnAggregateArray( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent )
{
return fcnAggregateGeneric( QgsAggregateCalculator::ArrayAggregate, values, QgsAggregateCalculator::AggregateParameters(), context, parent );
}
static QVariant fcnClamp( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
{
double minValue = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
@ -3918,6 +3923,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "max_length" ), aggParams, fcnAggregateMaxLength, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
<< new QgsStaticExpressionFunction( QStringLiteral( "collect" ), aggParams, fcnAggregateCollectGeometry, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
<< new QgsStaticExpressionFunction( QStringLiteral( "concatenate" ), aggParams << QgsExpressionFunction::Parameter( QStringLiteral( "concatenator" ), true ), fcnAggregateStringConcat, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
<< new QgsStaticExpressionFunction( QStringLiteral( "array_agg" ), aggParams, fcnAggregateArray, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
<< new QgsStaticExpressionFunction( QStringLiteral( "regexp_match" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "regex" ) ), fcnRegexpMatch, QStringList() << QStringLiteral( "Conditionals" ) << QStringLiteral( "String" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "regexp_matches" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "regex" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "emptyvalue" ), true, "" ), fcnRegexpMatches, QStringLiteral( "Arrays" ) )

View File

@ -165,6 +165,8 @@ QgsAggregateCalculator::Aggregate QgsAggregateCalculator::stringToAggregate( con
return StringConcatenate;
else if ( normalized == QLatin1String( "collect" ) )
return GeometryCollect;
else if ( normalized == QLatin1String( "array_agg" ) )
return ArrayAggregate;
if ( ok )
*ok = false;
@ -178,6 +180,13 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag
if ( ok )
*ok = false;
if ( aggregate == QgsAggregateCalculator::ArrayAggregate )
{
if ( ok )
*ok = true;
return calculateArrayAggregate( fit, attr, expression, context );
}
switch ( resultType )
{
case QVariant::Int:
@ -293,6 +302,7 @@ QgsStatisticalSummary::Statistic QgsAggregateCalculator::numericStatFromAggregat
case StringMaximumLength:
case StringConcatenate:
case GeometryCollect:
case ArrayAggregate:
{
if ( ok )
*ok = false;
@ -340,6 +350,7 @@ QgsStringStatisticalSummary::Statistic QgsAggregateCalculator::stringStatFromAgg
case InterQuartileRange:
case StringConcatenate:
case GeometryCollect:
case ArrayAggregate:
{
if ( ok )
*ok = false;
@ -386,6 +397,7 @@ QgsDateTimeStatisticalSummary::Statistic QgsAggregateCalculator::dateTimeStatFro
case StringMaximumLength:
case StringConcatenate:
case GeometryCollect:
case ArrayAggregate:
{
if ( ok )
*ok = false;
@ -512,6 +524,9 @@ QVariant QgsAggregateCalculator::defaultValue( QgsAggregateCalculator::Aggregate
case StringConcatenate:
return ""; // zero length string - not null!
case ArrayAggregate:
return QVariantList(); // empty list
// undefined - nothing makes sense here
case Sum:
case Min:
@ -559,3 +574,29 @@ QVariant QgsAggregateCalculator::calculateDateTimeAggregate( QgsFeatureIterator
s.finalize();
return s.statistic( stat );
}
QVariant QgsAggregateCalculator::calculateArrayAggregate( QgsFeatureIterator &fit, int attr, QgsExpression *expression,
QgsExpressionContext *context )
{
Q_ASSERT( expression || attr >= 0 );
QgsFeature f;
QVariantList array;
while ( fit.nextFeature( f ) )
{
if ( expression )
{
Q_ASSERT( context );
context->setFeature( f );
QVariant v = expression->evaluate( context );
array.append( v );
}
else
{
array.append( f.attribute( attr ) );
}
}
return array;
}

View File

@ -65,7 +65,8 @@ class CORE_EXPORT QgsAggregateCalculator
StringMinimumLength, //!< Minimum length of string (string fields only)
StringMaximumLength, //!< Maximum length of string (string fields only)
StringConcatenate, //! Concatenate values with a joining string (string fields only). Specify the delimiter using setDelimiter().
GeometryCollect //! Create a multipart geometry from aggregated geometries
GeometryCollect, //! Create a multipart geometry from aggregated geometries
ArrayAggregate //! Create an array of values
};
//! A bundle of parameters controlling aggregate calculation
@ -165,6 +166,9 @@ class CORE_EXPORT QgsAggregateCalculator
QgsExpressionContext *context, QgsDateTimeStatisticalSummary::Statistic stat );
static QVariant calculateGeometryAggregate( QgsFeatureIterator &fit, QgsExpression *expression, QgsExpressionContext *context );
static QVariant calculateArrayAggregate( QgsFeatureIterator &fit, int attr, QgsExpression *expression,
QgsExpressionContext *context );
static QVariant calculate( Aggregate aggregate, QgsFeatureIterator &fit, QVariant::Type resultType,
int attr, QgsExpression *expression,
const QString &delimiter,

View File

@ -1375,6 +1375,10 @@ class TestQgsExpression: public QObject
QTest::newRow( "geometry collect" ) << "geom_to_wkt(aggregate('aggregate_layer','collect',$geometry))" << false << QVariant( QStringLiteral( "MultiPoint ((0 0),(1 0),(2 0),(3 0),(5 0))" ) );
QVariantList array;
array << "test" << QVariant( QVariant::String ) << "test333" << "test4" << QVariant( QVariant::String ) << "test4";
QTest::newRow( "array aggregate" ) << "aggregate('aggregate_layer','array_agg',\"col2\")" << false << QVariant( array );
QTest::newRow( "sub expression" ) << "aggregate('test','sum',\"col1\" * 2)" << false << QVariant( 65 * 2 );
QTest::newRow( "bad sub expression" ) << "aggregate('test','sum',\"xcvxcv\" * 2)" << true << QVariant();

View File

@ -130,13 +130,15 @@ class TestQgsAggregateCalculator(unittest.TestCase):
[QgsAggregateCalculator.ThirdQuartile, 'flddbl', 7.5],
[QgsAggregateCalculator.InterQuartileRange, 'fldint', 3.0],
[QgsAggregateCalculator.InterQuartileRange, 'flddbl', 2.5],
[QgsAggregateCalculator.ArrayAggregate, 'fldint', int_values],
[QgsAggregateCalculator.ArrayAggregate, 'flddbl', dbl_values],
]
agg = QgsAggregateCalculator(layer)
for t in tests:
val, ok = agg.calculate(t[0], t[1])
self.assertTrue(ok)
if isinstance(t[2], int):
if isinstance(t[2], (int, list)):
self.assertEqual(val, t[2])
else:
self.assertAlmostEqual(val, t[2], 3)
@ -171,6 +173,7 @@ class TestQgsAggregateCalculator(unittest.TestCase):
[QgsAggregateCalculator.Max, 'fldstring', 'eeee'],
[QgsAggregateCalculator.StringMinimumLength, 'fldstring', 0],
[QgsAggregateCalculator.StringMaximumLength, 'fldstring', 8],
[QgsAggregateCalculator.ArrayAggregate, 'fldstring', values],
]
agg = QgsAggregateCalculator(layer)
@ -251,6 +254,8 @@ class TestQgsAggregateCalculator(unittest.TestCase):
[QgsAggregateCalculator.Range, 'flddatetime', QgsInterval(693871147)],
[QgsAggregateCalculator.Range, 'flddate', QgsInterval(693792000)],
[QgsAggregateCalculator.ArrayAggregate, 'flddatetime', [None if v.isNull() else v for v in datetime_values]],
[QgsAggregateCalculator.ArrayAggregate, 'flddate', [None if v.isNull() else v for v in date_values]],
]
agg = QgsAggregateCalculator(layer)
@ -425,6 +430,12 @@ class TestQgsAggregateCalculator(unittest.TestCase):
self.assertTrue(ok)
self.assertEqual(val, None)
# array_agg
agg = QgsAggregateCalculator(layer)
val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate, 'fldint * 2')
self.assertTrue(ok)
self.assertEqual(val, [])
def testStringToAggregate(self):
""" test converting strings to aggregate types """