Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Make QgsVectorLayer uniqueValues/min/maxValue consider edits
Previously these methods would inconsistently handle the
edit buffer, eg uniqueValues would consider changed attributes
but not added features. Now uniqueValues, minimumValue and
maximumValue all consider both added features and changed
attribute values when performing their calculation.

The most noticable effect of this fix is that the unique
values widget now correctly shows values for features which
have been added but not yet committed to the provider.
  • Loading branch information
nyalldawson committed Aug 15, 2016
1 parent 251fffa commit 50c3592
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 11 deletions.
26 changes: 22 additions & 4 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -1265,17 +1265,35 @@ class QgsVectorLayer : QgsMapLayer
// marked as const as these are just caches, and need to be created from const accessors
void createJoinCaches() const;

/** Returns unique values for column
/** Calculates a list of unique values contained within an attribute in the layer. Note that
* in some circumstances when unsaved changes are present for the layer then the returned list
* may contain outdated values (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value will be included in the
* returned list).
* @param index column index for attribute
* @param uniqueValues out: result list
* @param limit maximum number of values to return (-1 if unlimited)
* @param limit maximum number of values to return (or -1 if unlimited)
* @see minimumValue()
* @see maximumValue()
*/
void uniqueValues( int index, QList<QVariant> &uniqueValues /Out/, int limit = -1 ) const;

/** Returns minimum value for an attribute column or invalid variant in case of error */
/** Returns the minimum value for an attribute column or an invalid variant in case of error.
* Note that in some circumstances when unsaved changes are present for the layer then the
* returned value may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the minimum).
* @see maximumValue()
* @see uniqueValues()
*/
QVariant minimumValue( int index ) const;

/** Returns maximum value for an attribute column or invalid variant in case of error */
/** Returns the maximum value for an attribute column or an invalid variant in case of error.
* Note that in some circumstances when unsaved changes are present for the layer then the
* returned value may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the maximum).
* @see minimumValue()
* @see uniqueValues()
*/
QVariant maximumValue( int index ) const;

/** Calculates an aggregated value from the layer's features.
Expand Down
77 changes: 75 additions & 2 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -2819,6 +2819,23 @@ void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int
vals << v.toString();
}

QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() && ( limit < 0 || uniqueValues.count() < limit ) )
{
addedIt.next();
QVariant v = addedIt.value().attribute( index );
if ( v.isValid() )
{
QString vs = v.toString();
if ( !vals.contains( vs ) )
{
vals << vs;
uniqueValues << v;
}
}
}

QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() && ( limit < 0 || uniqueValues.count() < limit ) )
{
Expand Down Expand Up @@ -2897,7 +2914,35 @@ QVariant QgsVectorLayer::minimumValue( int index ) const
return QVariant();

case QgsFields::OriginProvider: //a provider field
return mDataProvider->minimumValue( index );
{
QVariant min = mDataProvider->minimumValue( index );
if ( mEditBuffer )
{
QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() )
{
addedIt.next();
QVariant v = addedIt.value().attribute( index );
if ( v.isValid() && qgsVariantLessThan( v, min ) )
{
min = v;
}
}

QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() )
{
it.next();
QVariant v = it.value().value( index );
if ( v.isValid() && qgsVariantLessThan( v, min ) )
{
min = v;
}
}
}
return min;
}

case QgsFields::OriginEdit:
{
Expand Down Expand Up @@ -2956,7 +3001,35 @@ QVariant QgsVectorLayer::maximumValue( int index ) const
return QVariant();

case QgsFields::OriginProvider: //a provider field
return mDataProvider->maximumValue( index );
{
QVariant min = mDataProvider->maximumValue( index );
if ( mEditBuffer )
{
QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() )
{
addedIt.next();
QVariant v = addedIt.value().attribute( index );
if ( v.isValid() && qgsVariantGreaterThan( v, min ) )
{
min = v;
}
}

QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() )
{
it.next();
QVariant v = it.value().value( index );
if ( v.isValid() && qgsVariantGreaterThan( v, min ) )
{
min = v;
}
}
}
return min;
}

case QgsFields::OriginEdit:
// the layer is editable, but in certain cases it can still be avoided going through all features
Expand Down
26 changes: 22 additions & 4 deletions src/core/qgsvectorlayer.h
Expand Up @@ -1378,17 +1378,35 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
// marked as const as these are just caches, and need to be created from const accessors
void createJoinCaches() const;

/** Returns unique values for column
/** Calculates a list of unique values contained within an attribute in the layer. Note that
* in some circumstances when unsaved changes are present for the layer then the returned list
* may contain outdated values (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value will be included in the
* returned list).
* @param index column index for attribute
* @param uniqueValues out: result list
* @param limit maximum number of values to return (-1 if unlimited)
* @param limit maximum number of values to return (or -1 if unlimited)
* @see minimumValue()
* @see maximumValue()
*/
void uniqueValues( int index, QList<QVariant> &uniqueValues, int limit = -1 ) const;

/** Returns minimum value for an attribute column or invalid variant in case of error */
/** Returns the minimum value for an attribute column or an invalid variant in case of error.
* Note that in some circumstances when unsaved changes are present for the layer then the
* returned value may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the minimum).
* @see maximumValue()
* @see uniqueValues()
*/
QVariant minimumValue( int index ) const;

/** Returns maximum value for an attribute column or invalid variant in case of error */
/** Returns the maximum value for an attribute column or an invalid variant in case of error.
* Note that in some circumstances when unsaved changes are present for the layer then the
* returned value may be outdated (for instance when the attribute value in a saved feature has
* been changed inside the edit buffer then the previous saved value may be returned as the maximum).
* @see minimumValue()
* @see uniqueValues()
*/
QVariant maximumValue( int index ) const;

/** Calculates an aggregated value from the layer's features.
Expand Down
2 changes: 1 addition & 1 deletion src/gui/qgsrelationeditorwidget.cpp
Expand Up @@ -440,7 +440,7 @@ void QgsRelationEditorWidget::unlinkFeature()
while ( linkedIterator.nextFeature( f ) )
{
fids << f.id();
QgsDebugMsg( f.id() );
QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 );
}

mRelation.referencingLayer()->deleteFeatures( fids );
Expand Down
97 changes: 97 additions & 0 deletions tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -1172,6 +1172,103 @@ def test_JoinStats(self):
self.assertEqual(layer.maximumValue(3), 321)
self.assertEqual(set(layer.uniqueValues(3)), set([111, 321]))

def testUniqueValue(self):
""" test retrieving unique values """
layer = createLayerWithFivePoints()

# test layer with just provider features
self.assertEqual(set(layer.uniqueValues(1)), set([123, 457, 888, -1, 0]))

# add feature with new value
layer.startEditing()
f1 = QgsFeature()
f1.setAttributes(["test2", 999])
self.assertTrue(layer.addFeature(f1))

# should be included in unique values
self.assertEqual(set(layer.uniqueValues(1)), set([123, 457, 888, -1, 0, 999]))
# add it again, should be no change
f2 = QgsFeature()
f2.setAttributes(["test2", 999])
self.assertTrue(layer.addFeature(f1))
self.assertEqual(set(layer.uniqueValues(1)), set([123, 457, 888, -1, 0, 999]))
# add another feature
f3 = QgsFeature()
f3.setAttributes(["test2", 9999])
self.assertTrue(layer.addFeature(f3))
self.assertEqual(set(layer.uniqueValues(1)), set([123, 457, 888, -1, 0, 999, 9999]))

# change an attribute value to a new unique value
f = QgsFeature()
f1_id = layer.getFeatures().next().id()
self.assertTrue(layer.changeAttributeValue(f1_id, 1, 481523))
# note - this isn't 100% accurate, since 123 no longer exists - but it avoids looping through all features
self.assertEqual(set(layer.uniqueValues(1)), set([123, 457, 888, -1, 0, 999, 9999, 481523]))

def testMinValue(self):
""" test retrieving minimum values """
layer = createLayerWithFivePoints()

# test layer with just provider features
self.assertEqual(layer.minimumValue(1), -1)

# add feature with new value
layer.startEditing()
f1 = QgsFeature()
f1.setAttributes(["test2", -999])
self.assertTrue(layer.addFeature(f1))

# should be new minimum value
self.assertEqual(layer.minimumValue(1), -999)
# add it again, should be no change
f2 = QgsFeature()
f2.setAttributes(["test2", -999])
self.assertTrue(layer.addFeature(f1))
self.assertEqual(layer.minimumValue(1), -999)
# add another feature
f3 = QgsFeature()
f3.setAttributes(["test2", -1000])
self.assertTrue(layer.addFeature(f3))
self.assertEqual(layer.minimumValue(1), -1000)

# change an attribute value to a new minimum value
f = QgsFeature()
f1_id = layer.getFeatures().next().id()
self.assertTrue(layer.changeAttributeValue(f1_id, 1, -1001))
self.assertEqual(layer.minimumValue(1), -1001)

def testMaxValue(self):
""" test retrieving maximum values """
layer = createLayerWithFivePoints()

# test layer with just provider features
self.assertEqual(layer.maximumValue(1), 888)

# add feature with new value
layer.startEditing()
f1 = QgsFeature()
f1.setAttributes(["test2", 999])
self.assertTrue(layer.addFeature(f1))

# should be new maximum value
self.assertEqual(layer.maximumValue(1), 999)
# add it again, should be no change
f2 = QgsFeature()
f2.setAttributes(["test2", 999])
self.assertTrue(layer.addFeature(f1))
self.assertEqual(layer.maximumValue(1), 999)
# add another feature
f3 = QgsFeature()
f3.setAttributes(["test2", 1000])
self.assertTrue(layer.addFeature(f3))
self.assertEqual(layer.maximumValue(1), 1000)

# change an attribute value to a new maximum value
f = QgsFeature()
f1_id = layer.getFeatures().next().id()
self.assertTrue(layer.changeAttributeValue(f1_id, 1, 1001))
self.assertEqual(layer.maximumValue(1), 1001)

def test_InvalidOperations(self):
layer = createLayerWithOnePoint()

Expand Down

0 comments on commit 50c3592

Please sign in to comment.