Skip to content

Commit

Permalink
[FEATURE] Add expression function array_agg
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-morvan authored and m-kuhn committed Aug 12, 2017
1 parent c8876f2 commit e34a593
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 3 deletions.
3 changes: 2 additions & 1 deletion python/core/qgsaggregatecalculator.sip
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class QgsAggregateCalculator
StringMinimumLength,
StringMaximumLength,
StringConcatenate,
GeometryCollect
GeometryCollect,
ArrayAggregate
};

struct AggregateParameters
Expand Down
13 changes: 13 additions & 0 deletions resources/function_help/json/array_agg
Original file line number Diff line number Diff line change
@@ -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"}
]
}
6 changes: 6 additions & 0 deletions src/core/expression/qgsexpressionfunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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" ) )
Expand Down
41 changes: 41 additions & 0 deletions src/core/qgsaggregatecalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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:
Expand Down Expand Up @@ -293,6 +302,7 @@ QgsStatisticalSummary::Statistic QgsAggregateCalculator::numericStatFromAggregat
case StringMaximumLength:
case StringConcatenate:
case GeometryCollect:
case ArrayAggregate:
{
if ( ok )
*ok = false;
Expand Down Expand Up @@ -340,6 +350,7 @@ QgsStringStatisticalSummary::Statistic QgsAggregateCalculator::stringStatFromAgg
case InterQuartileRange:
case StringConcatenate:
case GeometryCollect:
case ArrayAggregate:
{
if ( ok )
*ok = false;
Expand Down Expand Up @@ -386,6 +397,7 @@ QgsDateTimeStatisticalSummary::Statistic QgsAggregateCalculator::dateTimeStatFro
case StringMaximumLength:
case StringConcatenate:
case GeometryCollect:
case ArrayAggregate:
{
if ( ok )
*ok = false;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
}
6 changes: 5 additions & 1 deletion src/core/qgsaggregatecalculator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
13 changes: 12 additions & 1 deletion tests/src/python/test_qgsaggregatecalculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,15 @@ def testNumeric(self):
[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)
Expand Down Expand Up @@ -171,6 +173,7 @@ def testString(self):
[QgsAggregateCalculator.Max, 'fldstring', 'eeee'],
[QgsAggregateCalculator.StringMinimumLength, 'fldstring', 0],
[QgsAggregateCalculator.StringMaximumLength, 'fldstring', 8],
[QgsAggregateCalculator.ArrayAggregate, 'fldstring', values],
]

agg = QgsAggregateCalculator(layer)
Expand Down Expand Up @@ -251,6 +254,8 @@ def testDateTime(self):
[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)
Expand Down Expand Up @@ -425,6 +430,12 @@ def testExpressionNoMatch(self):
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 """

Expand Down

0 comments on commit e34a593

Please sign in to comment.