Skip to content

Commit 4682eaf

Browse files
nyalldawsonm-kuhn
authored andcommittedNov 16, 2016
Add QgsVectorLayer::uniqueStringsMatching() to retrieve
a list of unique strings matching a substring for a field
1 parent 3242321 commit 4682eaf

File tree

5 files changed

+183
-1
lines changed

5 files changed

+183
-1
lines changed
 

‎python/core/qgsvectorlayer.sip

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,22 @@ class QgsVectorLayer : QgsMapLayer, QgsExpressionContextGenerator
13261326
*/
13271327
void uniqueValues( int index, QList<QVariant> &uniqueValues /Out/, int limit = -1 ) const;
13281328

1329+
/**
1330+
* Returns unique string values of an attribute which contain a specified subset string. Subset
1331+
* matching is done in a case-insensitive manner. Note that
1332+
* in some circumstances when unsaved changes are present for the layer then the returned list
1333+
* may contain outdated values (for instance when the attribute value in a saved feature has
1334+
* been changed inside the edit buffer then the previous saved value will be included in the
1335+
* returned list).
1336+
* @param index column index for attribute
1337+
* @param substring substring to match (case insensitive)
1338+
* @param limit maxmum number of the values to return, or -1 to return all unique values
1339+
* @param feedback optional feedback object for cancelling request
1340+
* @returns list of unique strings containing substring
1341+
*/
1342+
QStringList uniqueStringsMatching( int index, const QString& substring, int limit = -1,
1343+
QgsFeedback* feedback = nullptr ) const;
1344+
13291345
/** Returns the minimum value for an attribute column or an invalid variant in case of error.
13301346
* Note that in some circumstances when unsaved changes are present for the layer then the
13311347
* returned value may be outdated (for instance when the attribute value in a saved feature has

‎src/core/qgsvectordataprovider.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
209209
* @param substring substring to match (case insensitive)
210210
* @param limit maxmum number of the values to return, or -1 to return all unique values
211211
* @param feedback optional feedback object for cancelling request
212-
* @returns list of unique strings containg substring
212+
* @returns list of unique strings containing substring
213213
*/
214214
virtual QStringList uniqueStringsMatching( int index, const QString& substring, int limit = -1,
215215
QgsFeedback* feedback = nullptr ) const;

‎src/core/qgsvectorlayer.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
#include "qgspallabeling.h"
8383
#include "qgssimplifymethod.h"
8484
#include "qgsexpressioncontext.h"
85+
#include "qgsfeedback.h"
8586

8687
#include "diagram/qgsdiagram.h"
8788

@@ -3223,6 +3224,107 @@ void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int
32233224
Q_ASSERT_X( false, "QgsVectorLayer::uniqueValues()", "Unknown source of the field!" );
32243225
}
32253226

3227+
QStringList QgsVectorLayer::uniqueStringsMatching( int index, const QString& substring, int limit, QgsFeedback* feedback ) const
3228+
{
3229+
QStringList results;
3230+
if ( !mDataProvider )
3231+
{
3232+
return results;
3233+
}
3234+
3235+
QgsFields::FieldOrigin origin = mFields.fieldOrigin( index );
3236+
switch ( origin )
3237+
{
3238+
case QgsFields::OriginUnknown:
3239+
return results;
3240+
3241+
case QgsFields::OriginProvider: //a provider field
3242+
{
3243+
results = mDataProvider->uniqueStringsMatching( index, substring, limit, feedback );
3244+
3245+
if ( mEditBuffer )
3246+
{
3247+
QgsFeatureMap added = mEditBuffer->addedFeatures();
3248+
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
3249+
while ( addedIt.hasNext() && ( limit < 0 || results.count() < limit ) && ( !feedback || !feedback->isCancelled() ) )
3250+
{
3251+
addedIt.next();
3252+
QVariant v = addedIt.value().attribute( index );
3253+
if ( v.isValid() )
3254+
{
3255+
QString vs = v.toString();
3256+
if ( vs.contains( substring, Qt::CaseInsensitive ) && !results.contains( vs ) )
3257+
{
3258+
results << vs;
3259+
}
3260+
}
3261+
}
3262+
3263+
QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
3264+
while ( it.hasNext() && ( limit < 0 || results.count() < limit ) && ( !feedback || !feedback->isCancelled() ) )
3265+
{
3266+
it.next();
3267+
QVariant v = it.value().value( index );
3268+
if ( v.isValid() )
3269+
{
3270+
QString vs = v.toString();
3271+
if ( vs.contains( substring, Qt::CaseInsensitive ) && !results.contains( vs ) )
3272+
{
3273+
results << vs;
3274+
}
3275+
}
3276+
}
3277+
}
3278+
3279+
return results;
3280+
}
3281+
3282+
case QgsFields::OriginEdit:
3283+
// the layer is editable, but in certain cases it can still be avoided going through all features
3284+
if ( mEditBuffer->mDeletedFeatureIds.isEmpty() &&
3285+
mEditBuffer->mAddedFeatures.isEmpty() &&
3286+
!mEditBuffer->mDeletedAttributeIds.contains( index ) &&
3287+
mEditBuffer->mChangedAttributeValues.isEmpty() )
3288+
{
3289+
return mDataProvider->uniqueStringsMatching( index, substring, limit, feedback );
3290+
}
3291+
FALLTHROUGH;
3292+
//we need to go through each feature
3293+
case QgsFields::OriginJoin:
3294+
case QgsFields::OriginExpression:
3295+
{
3296+
QgsAttributeList attList;
3297+
attList << index;
3298+
3299+
QgsFeatureRequest request;
3300+
request.setSubsetOfAttributes( attList );
3301+
request.setFlags( QgsFeatureRequest::NoGeometry );
3302+
QString fieldName = mFields.at( index ).name();
3303+
request.setFilterExpression( QStringLiteral( "\"%1\" ILIKE '%%2%'" ).arg( fieldName, substring ) );
3304+
QgsFeatureIterator fit = getFeatures( request );
3305+
3306+
QgsFeature f;
3307+
QString currentValue;
3308+
while ( fit.nextFeature( f ) )
3309+
{
3310+
currentValue = f.attribute( index ).toString();
3311+
if ( !results.contains( currentValue ) )
3312+
results << currentValue;
3313+
3314+
if (( limit >= 0 && results.size() >= limit ) || ( feedback && feedback->isCancelled() ) )
3315+
{
3316+
break;
3317+
}
3318+
}
3319+
3320+
return results;
3321+
}
3322+
}
3323+
3324+
Q_ASSERT_X( false, "QgsVectorLayer::uniqueStringsMatching()", "Unknown source of the field!" );
3325+
return results;
3326+
}
3327+
32263328
QVariant QgsVectorLayer::minimumValue( int index ) const
32273329
{
32283330
if ( !mDataProvider )

‎src/core/qgsvectorlayer.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class QgsVectorLayerEditBuffer;
6868
class QgsVectorLayerJoinBuffer;
6969
class QgsAbstractVectorLayerLabeling;
7070
class QgsPointV2;
71+
class QgsFeedback;
7172

7273
typedef QList<int> QgsAttributeList;
7374
typedef QSet<int> QgsAttributeIds;
@@ -1468,6 +1469,22 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
14681469
*/
14691470
void uniqueValues( int index, QList<QVariant> &uniqueValues, int limit = -1 ) const;
14701471

1472+
/**
1473+
* Returns unique string values of an attribute which contain a specified subset string. Subset
1474+
* matching is done in a case-insensitive manner. Note that
1475+
* in some circumstances when unsaved changes are present for the layer then the returned list
1476+
* may contain outdated values (for instance when the attribute value in a saved feature has
1477+
* been changed inside the edit buffer then the previous saved value will be included in the
1478+
* returned list).
1479+
* @param index column index for attribute
1480+
* @param substring substring to match (case insensitive)
1481+
* @param limit maxmum number of the values to return, or -1 to return all unique values
1482+
* @param feedback optional feedback object for cancelling request
1483+
* @returns list of unique strings containing substring
1484+
*/
1485+
QStringList uniqueStringsMatching( int index, const QString& substring, int limit = -1,
1486+
QgsFeedback* feedback = nullptr ) const;
1487+
14711488
/** Returns the minimum value for an attribute column or an invalid variant in case of error.
14721489
* Note that in some circumstances when unsaved changes are present for the layer then the
14731490
* returned value may be outdated (for instance when the attribute value in a saved feature has

‎tests/src/python/test_qgsvectorlayer.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,53 @@ def testUniqueValue(self):
12951295
# note - this isn't 100% accurate, since 123 no longer exists - but it avoids looping through all features
12961296
self.assertEqual(set(layer.uniqueValues(1)), set([123, 457, 888, -1, 0, 999, 9999, 481523]))
12971297

1298+
def testUniqueStringsMatching(self):
1299+
""" test retrieving unique strings matching subset """
1300+
layer = QgsVectorLayer("Point?field=fldtxt:string", "addfeat", "memory")
1301+
pr = layer.dataProvider()
1302+
f = QgsFeature()
1303+
f.setAttributes(["apple"])
1304+
f2 = QgsFeature()
1305+
f2.setAttributes(["orange"])
1306+
f3 = QgsFeature()
1307+
f3.setAttributes(["pear"])
1308+
f4 = QgsFeature()
1309+
f4.setAttributes(["BanaNa"])
1310+
f5 = QgsFeature()
1311+
f5.setAttributes(["ApriCot"])
1312+
assert pr.addFeatures([f, f2, f3, f4, f5])
1313+
assert layer.featureCount() == 5
1314+
1315+
# test layer with just provider features
1316+
self.assertEqual(set(layer.uniqueStringsMatching(0, 'N')), set(['orange', 'BanaNa']))
1317+
1318+
# add feature with new value
1319+
layer.startEditing()
1320+
f1 = QgsFeature()
1321+
f1.setAttributes(["waterMelon"])
1322+
self.assertTrue(layer.addFeature(f1))
1323+
1324+
# should be included in unique values
1325+
self.assertEqual(set(layer.uniqueStringsMatching(0, 'N')), set(['orange', 'BanaNa', 'waterMelon']))
1326+
# add it again, should be no change
1327+
f2 = QgsFeature()
1328+
f2.setAttributes(["waterMelon"])
1329+
self.assertTrue(layer.addFeature(f1))
1330+
self.assertEqual(set(layer.uniqueStringsMatching(0, 'N')), set(['orange', 'BanaNa', 'waterMelon']))
1331+
self.assertEqual(set(layer.uniqueStringsMatching(0, 'aN')), set(['orange', 'BanaNa']))
1332+
# add another feature
1333+
f3 = QgsFeature()
1334+
f3.setAttributes(["pineapple"])
1335+
self.assertTrue(layer.addFeature(f3))
1336+
self.assertEqual(set(layer.uniqueStringsMatching(0, 'n')), set(['orange', 'BanaNa', 'waterMelon', 'pineapple']))
1337+
1338+
# change an attribute value to a new unique value
1339+
f = QgsFeature()
1340+
f1_id = next(layer.getFeatures()).id()
1341+
self.assertTrue(layer.changeAttributeValue(f1_id, 0, 'coconut'))
1342+
# note - this isn't 100% accurate, since orange no longer exists - but it avoids looping through all features
1343+
self.assertEqual(set(layer.uniqueStringsMatching(0, 'n')), set(['orange', 'BanaNa', 'waterMelon', 'pineapple', 'coconut']))
1344+
12981345
def testMinValue(self):
12991346
""" test retrieving minimum values """
13001347
layer = createLayerWithFivePoints()

0 commit comments

Comments
 (0)
Please sign in to comment.