Skip to content
Permalink
Browse files

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 50c35929d86ab01b22c29cd129fd7019a1bf624a
@@ -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.
@@ -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 ) )
{
@@ -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:
{
@@ -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
@@ -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.
@@ -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 );
@@ -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()

0 comments on commit 50c3592

Please sign in to comment.
You can’t perform that action at this time.