Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add a aggregation method to concatenate unique values
  • Loading branch information
nirvn committed Apr 5, 2019
1 parent 4582cb8 commit 9fd5509
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 13 deletions.
3 changes: 2 additions & 1 deletion python/core/auto_generated/qgsaggregatecalculator.sip.in
Expand Up @@ -56,7 +56,8 @@ to a data provider for remote calculation.
StringMaximumLength,
StringConcatenate,
GeometryCollect,
ArrayAggregate
ArrayAggregate,
StringConcatenateUnique
};

struct AggregateParameters
Expand Down
31 changes: 23 additions & 8 deletions src/core/qgsaggregatecalculator.cpp
Expand Up @@ -163,6 +163,8 @@ QgsAggregateCalculator::Aggregate QgsAggregateCalculator::stringToAggregate( con
return StringMaximumLength;
else if ( normalized == QLatin1String( "concatenate" ) )
return StringConcatenate;
else if ( normalized == QLatin1String( "concatenate_unique" ) )
return StringConcatenateUnique;
else if ( normalized == QLatin1String( "collect" ) )
return GeometryCollect;
else if ( normalized == QLatin1String( "array_agg" ) )
Expand Down Expand Up @@ -470,6 +472,13 @@ QVariant QgsAggregateCalculator::calculate( QgsAggregateCalculator::Aggregate ag
*ok = true;
return concatenateStrings( fit, attr, expression, context, delimiter );
}
else if ( aggregate == StringConcatenateUnique )
{
//special case
if ( ok )
*ok = true;
return concatenateStrings( fit, attr, expression, context, delimiter, true );
}

bool statOk = false;
QgsStringStatisticalSummary::Statistic stat = stringStatFromAggregate( aggregate, &statOk );
Expand Down Expand Up @@ -529,6 +538,7 @@ QgsStatisticalSummary::Statistic QgsAggregateCalculator::numericStatFromAggregat
case StringMinimumLength:
case StringMaximumLength:
case StringConcatenate:
case StringConcatenateUnique:
case GeometryCollect:
case ArrayAggregate:
{
Expand Down Expand Up @@ -577,6 +587,7 @@ QgsStringStatisticalSummary::Statistic QgsAggregateCalculator::stringStatFromAgg
case ThirdQuartile:
case InterQuartileRange:
case StringConcatenate:
case StringConcatenateUnique:
case GeometryCollect:
case ArrayAggregate:
{
Expand Down Expand Up @@ -624,6 +635,7 @@ QgsDateTimeStatisticalSummary::Statistic QgsAggregateCalculator::dateTimeStatFro
case StringMinimumLength:
case StringMaximumLength:
case StringConcatenate:
case StringConcatenateUnique:
case GeometryCollect:
case ArrayAggregate:
{
Expand Down Expand Up @@ -712,30 +724,32 @@ QVariant QgsAggregateCalculator::calculateGeometryAggregate( QgsFeatureIterator
}

QVariant QgsAggregateCalculator::concatenateStrings( QgsFeatureIterator &fit, int attr, QgsExpression *expression,
QgsExpressionContext *context, const QString &delimiter )
QgsExpressionContext *context, const QString &delimiter, bool unique )
{
Q_ASSERT( expression || attr >= 0 );

QgsFeature f;
QString result;
QStringList results;
while ( fit.nextFeature( f ) )
{
if ( !result.isEmpty() )
result += delimiter;

QString result;
if ( expression )
{
Q_ASSERT( context );
context->setFeature( f );
QVariant v = expression->evaluate( context );
result += v.toString();
result = v.toString();
}
else
{
result += f.attribute( attr ).toString();
result = f.attribute( attr ).toString();
}

if ( !unique || !results.contains( result ) )
results << result;
}
return result;

return results.join( delimiter );
}

QVariant QgsAggregateCalculator::defaultValue( QgsAggregateCalculator::Aggregate aggregate ) const
Expand All @@ -750,6 +764,7 @@ QVariant QgsAggregateCalculator::defaultValue( QgsAggregateCalculator::Aggregate
return 0;

case StringConcatenate:
case StringConcatenateUnique:
return ""; // zero length string - not null!

case ArrayAggregate:
Expand Down
5 changes: 3 additions & 2 deletions src/core/qgsaggregatecalculator.h
Expand Up @@ -81,7 +81,8 @@ class CORE_EXPORT QgsAggregateCalculator
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
ArrayAggregate //!< Create an array of values
ArrayAggregate, //!< Create an array of values
StringConcatenateUnique //!< Concatenate unique values with a joining string (string fields only). Specify the delimiter using setDelimiter().
};

//! A bundle of parameters controlling aggregate calculation
Expand Down Expand Up @@ -207,7 +208,7 @@ class CORE_EXPORT QgsAggregateCalculator
const QString &delimiter,
QgsExpressionContext *context, bool *ok = nullptr );
static QVariant concatenateStrings( QgsFeatureIterator &fit, int attr, QgsExpression *expression,
QgsExpressionContext *context, const QString &delimiter );
QgsExpressionContext *context, const QString &delimiter, bool unique = false );

QVariant defaultValue( Aggregate aggregate ) const;
};
Expand Down
4 changes: 4 additions & 0 deletions tests/src/python/test_qgsaggregatecalculator.py
Expand Up @@ -188,6 +188,9 @@ def testString(self):
val, ok = agg.calculate(QgsAggregateCalculator.StringConcatenate, 'fldstring')
self.assertTrue(ok)
self.assertEqual(val, 'cc,aaaa,bbbbbbbb,aaaa,eeee,,eeee,,dddd')
val, ok = agg.calculate(QgsAggregateCalculator.StringConcatenateUnique, 'fldstring')
self.assertTrue(ok)
self.assertEqual(val, 'cc,aaaa,bbbbbbbb,eeee,,dddd')

# bad tests - the following stats should not be calculatable for string fields
for t in [QgsAggregateCalculator.Sum,
Expand Down Expand Up @@ -458,6 +461,7 @@ def testStringToAggregate(self):
[QgsAggregateCalculator.StringMinimumLength, 'min_length'],
[QgsAggregateCalculator.StringMaximumLength, 'max_length'],
[QgsAggregateCalculator.StringConcatenate, 'concatenate'],
[QgsAggregateCalculator.StringConcatenateUnique, 'concatenate_unique'],
[QgsAggregateCalculator.GeometryCollect, 'collect']]

for t in tests:
Expand Down
7 changes: 5 additions & 2 deletions tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -2074,7 +2074,7 @@ def testAggregate(self):
layer = QgsVectorLayer("Point?field=fldstring:string", "layer", "memory")
pr = layer.dataProvider()

string_values = ['this', 'is', 'a', 'test']
string_values = ['this', 'is', 'a', 'test', 'a', 'nice', 'test']
features = []
for s in string_values:
f = QgsFeature()
Expand All @@ -2086,7 +2086,10 @@ def testAggregate(self):
params.delimiter = ' '
val, ok = layer.aggregate(QgsAggregateCalculator.StringConcatenate, 'fldstring', params)
self.assertTrue(ok)
self.assertEqual(val, 'this is a test')
self.assertEqual(val, 'this is a test a nice test')
val, ok = layer.aggregate(QgsAggregateCalculator.StringConcatenateUnique, 'fldstring', params)
self.assertTrue(ok)
self.assertEqual(val, 'this is a test nice')

def testAggregateInVirtualField(self):
"""
Expand Down
Binary file modified tests/testdata/polys_overlapping_with_id.dbf
Binary file not shown.

0 comments on commit 9fd5509

Please sign in to comment.