Skip to content

Commit e34a593

Browse files
arnaud-morvanm-kuhn
authored andcommittedAug 12, 2017
[FEATURE] Add expression function array_agg
1 parent c8876f2 commit e34a593

File tree

7 files changed

+83
-3
lines changed

7 files changed

+83
-3
lines changed
 

‎python/core/qgsaggregatecalculator.sip

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ class QgsAggregateCalculator
4747
StringMinimumLength,
4848
StringMaximumLength,
4949
StringConcatenate,
50-
GeometryCollect
50+
GeometryCollect,
51+
ArrayAggregate
5152
};
5253

5354
struct AggregateParameters
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "array_agg",
3+
"type": "function",
4+
"description": "Returns an array of aggregated values from a field or expression.",
5+
"arguments": [
6+
{"arg": "expression", "description": "sub expression of field to aggregate"},
7+
{"arg": "group_by", "optional": true, "description": "optional expression to use to group aggregate calculations"},
8+
{"arg": "filter", "optional": true, "description": "optional expression to use to filter features used to calculate aggregate"}
9+
],
10+
"examples": [
11+
{ "expression": "array_agg(\"name\",group_by:=\"state\")", "returns":"list of name values, grouped by state field"}
12+
]
13+
}

‎src/core/expression/qgsexpressionfunction.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,11 @@ static QVariant fcnAggregateStringConcat( const QVariantList &values, const QgsE
830830
return fcnAggregateGeneric( QgsAggregateCalculator::StringConcatenate, values, parameters, context, parent );
831831
}
832832

833+
static QVariant fcnAggregateArray( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent )
834+
{
835+
return fcnAggregateGeneric( QgsAggregateCalculator::ArrayAggregate, values, QgsAggregateCalculator::AggregateParameters(), context, parent );
836+
}
837+
833838
static QVariant fcnClamp( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent )
834839
{
835840
double minValue = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
@@ -3918,6 +3923,7 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
39183923
<< new QgsStaticExpressionFunction( QStringLiteral( "max_length" ), aggParams, fcnAggregateMaxLength, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
39193924
<< new QgsStaticExpressionFunction( QStringLiteral( "collect" ), aggParams, fcnAggregateCollectGeometry, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
39203925
<< new QgsStaticExpressionFunction( QStringLiteral( "concatenate" ), aggParams << QgsExpressionFunction::Parameter( QStringLiteral( "concatenator" ), true ), fcnAggregateStringConcat, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
3926+
<< new QgsStaticExpressionFunction( QStringLiteral( "array_agg" ), aggParams, fcnAggregateArray, QStringLiteral( "Aggregates" ), QString(), false, QSet<QString>(), true )
39213927

39223928
<< new QgsStaticExpressionFunction( QStringLiteral( "regexp_match" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "regex" ) ), fcnRegexpMatch, QStringList() << QStringLiteral( "Conditionals" ) << QStringLiteral( "String" ) )
39233929
<< new QgsStaticExpressionFunction( QStringLiteral( "regexp_matches" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "string" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "regex" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "emptyvalue" ), true, "" ), fcnRegexpMatches, QStringLiteral( "Arrays" ) )

‎src/core/qgsaggregatecalculator.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ QgsAggregateCalculator::Aggregate QgsAggregateCalculator::stringToAggregate( con
165165
return StringConcatenate;
166166
else if ( normalized == QLatin1String( "collect" ) )
167167
return GeometryCollect;
168+
else if ( normalized == QLatin1String( "array_agg" ) )
169+
return ArrayAggregate;
168170

169171
if ( ok )
170172
*ok = false;
@@ -178,6 +180,13 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag
178180
if ( ok )
179181
*ok = false;
180182

183+
if ( aggregate == QgsAggregateCalculator::ArrayAggregate )
184+
{
185+
if ( ok )
186+
*ok = true;
187+
return calculateArrayAggregate( fit, attr, expression, context );
188+
}
189+
181190
switch ( resultType )
182191
{
183192
case QVariant::Int:
@@ -293,6 +302,7 @@ QgsStatisticalSummary::Statistic QgsAggregateCalculator::numericStatFromAggregat
293302
case StringMaximumLength:
294303
case StringConcatenate:
295304
case GeometryCollect:
305+
case ArrayAggregate:
296306
{
297307
if ( ok )
298308
*ok = false;
@@ -340,6 +350,7 @@ QgsStringStatisticalSummary::Statistic QgsAggregateCalculator::stringStatFromAgg
340350
case InterQuartileRange:
341351
case StringConcatenate:
342352
case GeometryCollect:
353+
case ArrayAggregate:
343354
{
344355
if ( ok )
345356
*ok = false;
@@ -386,6 +397,7 @@ QgsDateTimeStatisticalSummary::Statistic QgsAggregateCalculator::dateTimeStatFro
386397
case StringMaximumLength:
387398
case StringConcatenate:
388399
case GeometryCollect:
400+
case ArrayAggregate:
389401
{
390402
if ( ok )
391403
*ok = false;
@@ -512,6 +524,9 @@ QVariant QgsAggregateCalculator::defaultValue( QgsAggregateCalculator::Aggregate
512524
case StringConcatenate:
513525
return ""; // zero length string - not null!
514526

527+
case ArrayAggregate:
528+
return QVariantList(); // empty list
529+
515530
// undefined - nothing makes sense here
516531
case Sum:
517532
case Min:
@@ -559,3 +574,29 @@ QVariant QgsAggregateCalculator::calculateDateTimeAggregate( QgsFeatureIterator
559574
s.finalize();
560575
return s.statistic( stat );
561576
}
577+
578+
QVariant QgsAggregateCalculator::calculateArrayAggregate( QgsFeatureIterator &fit, int attr, QgsExpression *expression,
579+
QgsExpressionContext *context )
580+
{
581+
Q_ASSERT( expression || attr >= 0 );
582+
583+
QgsFeature f;
584+
585+
QVariantList array;
586+
587+
while ( fit.nextFeature( f ) )
588+
{
589+
if ( expression )
590+
{
591+
Q_ASSERT( context );
592+
context->setFeature( f );
593+
QVariant v = expression->evaluate( context );
594+
array.append( v );
595+
}
596+
else
597+
{
598+
array.append( f.attribute( attr ) );
599+
}
600+
}
601+
return array;
602+
}

‎src/core/qgsaggregatecalculator.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ class CORE_EXPORT QgsAggregateCalculator
6565
StringMinimumLength, //!< Minimum length of string (string fields only)
6666
StringMaximumLength, //!< Maximum length of string (string fields only)
6767
StringConcatenate, //! Concatenate values with a joining string (string fields only). Specify the delimiter using setDelimiter().
68-
GeometryCollect //! Create a multipart geometry from aggregated geometries
68+
GeometryCollect, //! Create a multipart geometry from aggregated geometries
69+
ArrayAggregate //! Create an array of values
6970
};
7071

7172
//! A bundle of parameters controlling aggregate calculation
@@ -165,6 +166,9 @@ class CORE_EXPORT QgsAggregateCalculator
165166
QgsExpressionContext *context, QgsDateTimeStatisticalSummary::Statistic stat );
166167
static QVariant calculateGeometryAggregate( QgsFeatureIterator &fit, QgsExpression *expression, QgsExpressionContext *context );
167168

169+
static QVariant calculateArrayAggregate( QgsFeatureIterator &fit, int attr, QgsExpression *expression,
170+
QgsExpressionContext *context );
171+
168172
static QVariant calculate( Aggregate aggregate, QgsFeatureIterator &fit, QVariant::Type resultType,
169173
int attr, QgsExpression *expression,
170174
const QString &delimiter,

‎tests/src/core/testqgsexpression.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,10 @@ class TestQgsExpression: public QObject
13751375

13761376
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))" ) );
13771377

1378+
QVariantList array;
1379+
array << "test" << QVariant( QVariant::String ) << "test333" << "test4" << QVariant( QVariant::String ) << "test4";
1380+
QTest::newRow( "array aggregate" ) << "aggregate('aggregate_layer','array_agg',\"col2\")" << false << QVariant( array );
1381+
13781382
QTest::newRow( "sub expression" ) << "aggregate('test','sum',\"col1\" * 2)" << false << QVariant( 65 * 2 );
13791383
QTest::newRow( "bad sub expression" ) << "aggregate('test','sum',\"xcvxcv\" * 2)" << true << QVariant();
13801384

‎tests/src/python/test_qgsaggregatecalculator.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,15 @@ def testNumeric(self):
130130
[QgsAggregateCalculator.ThirdQuartile, 'flddbl', 7.5],
131131
[QgsAggregateCalculator.InterQuartileRange, 'fldint', 3.0],
132132
[QgsAggregateCalculator.InterQuartileRange, 'flddbl', 2.5],
133+
[QgsAggregateCalculator.ArrayAggregate, 'fldint', int_values],
134+
[QgsAggregateCalculator.ArrayAggregate, 'flddbl', dbl_values],
133135
]
134136

135137
agg = QgsAggregateCalculator(layer)
136138
for t in tests:
137139
val, ok = agg.calculate(t[0], t[1])
138140
self.assertTrue(ok)
139-
if isinstance(t[2], int):
141+
if isinstance(t[2], (int, list)):
140142
self.assertEqual(val, t[2])
141143
else:
142144
self.assertAlmostEqual(val, t[2], 3)
@@ -171,6 +173,7 @@ def testString(self):
171173
[QgsAggregateCalculator.Max, 'fldstring', 'eeee'],
172174
[QgsAggregateCalculator.StringMinimumLength, 'fldstring', 0],
173175
[QgsAggregateCalculator.StringMaximumLength, 'fldstring', 8],
176+
[QgsAggregateCalculator.ArrayAggregate, 'fldstring', values],
174177
]
175178

176179
agg = QgsAggregateCalculator(layer)
@@ -251,6 +254,8 @@ def testDateTime(self):
251254
[QgsAggregateCalculator.Range, 'flddatetime', QgsInterval(693871147)],
252255
[QgsAggregateCalculator.Range, 'flddate', QgsInterval(693792000)],
253256

257+
[QgsAggregateCalculator.ArrayAggregate, 'flddatetime', [None if v.isNull() else v for v in datetime_values]],
258+
[QgsAggregateCalculator.ArrayAggregate, 'flddate', [None if v.isNull() else v for v in date_values]],
254259
]
255260

256261
agg = QgsAggregateCalculator(layer)
@@ -425,6 +430,12 @@ def testExpressionNoMatch(self):
425430
self.assertTrue(ok)
426431
self.assertEqual(val, None)
427432

433+
# array_agg
434+
agg = QgsAggregateCalculator(layer)
435+
val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate, 'fldint * 2')
436+
self.assertTrue(ok)
437+
self.assertEqual(val, [])
438+
428439
def testStringToAggregate(self):
429440
""" test converting strings to aggregate types """
430441

0 commit comments

Comments
 (0)
Please sign in to comment.