diff --git a/python/core/processing/qgsprocessingutils.sip b/python/core/processing/qgsprocessingutils.sip index 4c206caab36..6392236ad42 100644 --- a/python/core/processing/qgsprocessingutils.sip +++ b/python/core/processing/qgsprocessingutils.sip @@ -232,6 +232,12 @@ class QgsProcessingFeatureSource : QgsFeatureSource virtual QString sourceName() const; + virtual QSet uniqueValues( int fieldIndex, int limit = -1 ) const; + + virtual QVariant minimumValue( int fieldIndex ) const; + + virtual QVariant maximumValue( int fieldIndex ) const; + }; diff --git a/python/core/qgsfeaturesource.sip b/python/core/qgsfeaturesource.sip index 3dbf8e73c33..c36697841ee 100644 --- a/python/core/qgsfeaturesource.sip +++ b/python/core/qgsfeaturesource.sip @@ -81,9 +81,31 @@ class QgsFeatureSource If specified, the ``limit`` option can be used to limit the number of returned values. The base class implementation uses a non-optimised approach of looping through all features in the source. +.. seealso:: minimumValue() +.. seealso:: maximumValue() :rtype: set of QVariant %End + virtual QVariant minimumValue( int fieldIndex ) const; +%Docstring + Returns the minimum value for an attribute column or an invalid variant in case of error. + The base class implementation uses a non-optimised approach of looping through + all features in the source. +.. seealso:: maximumValue() +.. seealso:: uniqueValues() + :rtype: QVariant +%End + + virtual QVariant maximumValue( int fieldIndex ) const; +%Docstring + Returns the maximum value for an attribute column or an invalid variant in case of error. + The base class implementation uses a non-optimised approach of looping through + all features in the source. +.. seealso:: minimumValue() +.. seealso:: uniqueValues() + :rtype: QVariant +%End + virtual QgsRectangle sourceExtent() const; %Docstring Returns the extent of all geometries from the source. diff --git a/python/core/qgsvectorlayer.sip b/python/core/qgsvectorlayer.sip index 5d54a577c0f..f6a8c33be20 100644 --- a/python/core/qgsvectorlayer.sip +++ b/python/core/qgsvectorlayer.sip @@ -1537,7 +1537,8 @@ Assembles mUpdatedFields considering provider fields, joined fields and added fi :rtype: list of str %End - QVariant minimumValue( int index ) const; + virtual QVariant minimumValue( int index ) const; + %Docstring 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 @@ -1548,7 +1549,8 @@ Assembles mUpdatedFields considering provider fields, joined fields and added fi :rtype: QVariant %End - QVariant maximumValue( int index ) const; + virtual QVariant maximumValue( int index ) const; + %Docstring 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 diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index 4efaa880176..b7689194652 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -566,3 +566,18 @@ QString QgsProcessingFeatureSource::sourceName() const return mSource->sourceName(); } + +QSet QgsProcessingFeatureSource::uniqueValues( int fieldIndex, int limit ) const +{ + return mSource->uniqueValues( fieldIndex, limit ); +} + +QVariant QgsProcessingFeatureSource::minimumValue( int fieldIndex ) const +{ + return mSource->minimumValue( fieldIndex ); +} + +QVariant QgsProcessingFeatureSource::maximumValue( int fieldIndex ) const +{ + return mSource->maximumValue( fieldIndex ); +} diff --git a/src/core/processing/qgsprocessingutils.h b/src/core/processing/qgsprocessingutils.h index 11f6a0c609d..cf5e08bcfb5 100644 --- a/src/core/processing/qgsprocessingutils.h +++ b/src/core/processing/qgsprocessingutils.h @@ -282,6 +282,9 @@ class CORE_EXPORT QgsProcessingFeatureSource : public QgsFeatureSource QgsWkbTypes::Type wkbType() const override; long featureCount() const override; QString sourceName() const override; + QSet uniqueValues( int fieldIndex, int limit = -1 ) const override; + QVariant minimumValue( int fieldIndex ) const override; + QVariant maximumValue( int fieldIndex ) const override; private: diff --git a/src/core/qgsfeaturesource.cpp b/src/core/qgsfeaturesource.cpp index 49765d842d1..65fd94ee2f1 100644 --- a/src/core/qgsfeaturesource.cpp +++ b/src/core/qgsfeaturesource.cpp @@ -40,6 +40,52 @@ QSet QgsFeatureSource::uniqueValues( int fieldIndex, int limit ) const return values; } +QVariant QgsFeatureSource::minimumValue( int fieldIndex ) const +{ + if ( fieldIndex < 0 || fieldIndex >= fields().count() ) + return QVariant(); + + QgsFeatureRequest req; + req.setFlags( QgsFeatureRequest::NoGeometry ); + req.setSubsetOfAttributes( QgsAttributeList() << fieldIndex ); + + QVariant min; + QgsFeatureIterator it = getFeatures( req ); + QgsFeature f; + while ( it.nextFeature( f ) ) + { + QVariant v = f.attribute( fieldIndex ); + if ( v.isValid() && qgsVariantLessThan( v, min ) ) + { + min = v; + } + } + return min; +} + +QVariant QgsFeatureSource::maximumValue( int fieldIndex ) const +{ + if ( fieldIndex < 0 || fieldIndex >= fields().count() ) + return QVariant(); + + QgsFeatureRequest req; + req.setFlags( QgsFeatureRequest::NoGeometry ); + req.setSubsetOfAttributes( QgsAttributeList() << fieldIndex ); + + QVariant max; + QgsFeatureIterator it = getFeatures( req ); + QgsFeature f; + while ( it.nextFeature( f ) ) + { + QVariant v = f.attribute( fieldIndex ); + if ( v.isValid() && qgsVariantGreaterThan( v, max ) ) + { + max = v; + } + } + return max; +} + QgsRectangle QgsFeatureSource::sourceExtent() const { QgsRectangle r; diff --git a/src/core/qgsfeaturesource.h b/src/core/qgsfeaturesource.h index 1d6dffff0a3..61ccab1d84a 100644 --- a/src/core/qgsfeaturesource.h +++ b/src/core/qgsfeaturesource.h @@ -89,9 +89,27 @@ class CORE_EXPORT QgsFeatureSource * If specified, the \a limit option can be used to limit the number of returned values. * The base class implementation uses a non-optimised approach of looping through * all features in the source. + * \see minimumValue() + * \see maximumValue() */ virtual QSet uniqueValues( int fieldIndex, int limit = -1 ) const; + /** Returns the minimum value for an attribute column or an invalid variant in case of error. + * The base class implementation uses a non-optimised approach of looping through + * all features in the source. + * \see maximumValue() + * \see uniqueValues() + */ + virtual QVariant minimumValue( int fieldIndex ) const; + + /** Returns the maximum value for an attribute column or an invalid variant in case of error. + * The base class implementation uses a non-optimised approach of looping through + * all features in the source. + * \see minimumValue() + * \see uniqueValues() + */ + virtual QVariant maximumValue( int fieldIndex ) const; + /** * Returns the extent of all geometries from the source. * The base class implementation uses a non-optimised approach of looping through diff --git a/src/core/qgsvectordataprovider.h b/src/core/qgsvectordataprovider.h index ff9cee54173..812099b0194 100644 --- a/src/core/qgsvectordataprovider.h +++ b/src/core/qgsvectordataprovider.h @@ -177,7 +177,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat * and maximal values. If provider has facilities to retrieve minimal * value directly, override this function. */ - virtual QVariant minimumValue( int index ) const; + virtual QVariant minimumValue( int index ) const override; /** * Returns the maximum value of an attribute @@ -187,7 +187,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat * and maximal values. If provider has facilities to retrieve maximal * value directly, override this function. */ - virtual QVariant maximumValue( int index ) const; + virtual QVariant maximumValue( int index ) const override; /** * Returns unique string values of an attribute which contain a specified subset string. Subset diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 7965c90102b..3b8ac2d9843 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -1452,7 +1452,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \see maximumValue() * \see uniqueValues() */ - QVariant minimumValue( int index ) const; + QVariant minimumValue( int index ) const override; /** 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 @@ -1461,7 +1461,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \see minimumValue() * \see uniqueValues() */ - QVariant maximumValue( int index ) const; + QVariant maximumValue( int index ) const override; /** Calculates an aggregated value from the layer's features. * \param aggregate aggregate to calculate diff --git a/tests/src/python/featuresourcetestbase.py b/tests/src/python/featuresourcetestbase.py index 9fa330eb1b0..c81ebeacb12 100755 --- a/tests/src/python/featuresourcetestbase.py +++ b/tests/src/python/featuresourcetestbase.py @@ -635,3 +635,15 @@ class FeatureSourceTestCase(object): assert f.hasGeometry(), 'Expected geometry, got none' self.assertTrue(f.isValid()) + + def testUniqueValues(self): + self.assertEqual(set(self.source.uniqueValues(1)), set([-200, 100, 200, 300, 400])) + assert set(['Apple', 'Honey', 'Orange', 'Pear', NULL]) == set(self.source.uniqueValues(2)), 'Got {}'.format(set(self.source.uniqueValues(2))) + + def testMinimumValue(self): + self.assertEqual(self.source.minimumValue(1), -200) + self.assertEqual(self.source.minimumValue(2), 'Apple') + + def testMaximumValue(self): + self.assertEqual(self.source.maximumValue(1), 400) + self.assertEqual(self.source.maximumValue(2), 'Pear') diff --git a/tests/src/python/test_qgsfeaturesource.py b/tests/src/python/test_qgsfeaturesource.py index 2ea7069885b..61399601b6a 100644 --- a/tests/src/python/test_qgsfeaturesource.py +++ b/tests/src/python/test_qgsfeaturesource.py @@ -62,6 +62,30 @@ class TestQgsFeatureSource(unittest.TestCase): self.assertEqual(layer.dataProvider().uniqueValues(0), {'test', 'test2', 'test3', 'test4'}) self.assertEqual(layer.dataProvider().uniqueValues(1), {1, 3, 3, 4}) + def testMinValues(self): + """ + Test retrieving min values using base class method + """ + + # memory provider uses base class method + layer = createLayerWithFivePoints() + self.assertFalse(layer.dataProvider().minimumValue(-1)) + self.assertFalse(layer.dataProvider().minimumValue(100)) + self.assertEqual(layer.dataProvider().minimumValue(0), 'test') + self.assertEqual(layer.dataProvider().minimumValue(1), 1) + + def testMaxValues(self): + """ + Test retrieving min values using base class method + """ + + # memory provider uses base class method + layer = createLayerWithFivePoints() + self.assertFalse(layer.dataProvider().maximumValue(-1)) + self.assertFalse(layer.dataProvider().maximumValue(100)) + self.assertEqual(layer.dataProvider().maximumValue(0), 'test4') + self.assertEqual(layer.dataProvider().maximumValue(1), 4) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 0887beb268c..bbce317f9c9 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -2511,6 +2511,12 @@ class TestQgsVectorLayerSourceAddedFeaturesInBuffer(unittest.TestCase, FeatureSo """ pass + def testMinimumValue(self): + """ Skip min values test - due to inconsistencies in how null values are treated by providers. + They are included here, but providers don't include them.... which is right? + """ + pass + class TestQgsVectorLayerSourceChangedGeometriesInBuffer(unittest.TestCase, FeatureSourceTestCase): @@ -2661,6 +2667,21 @@ class TestQgsVectorLayerSourceChangedAttributesInBuffer(unittest.TestCase, Featu """ pass + def testUniqueValues(self): + """ Skip unique values test - as noted in the docs this is unreliable when features are in the buffer + """ + pass + + def testMinimumValue(self): + """ Skip min values test - as noted in the docs this is unreliable when features are in the buffer + """ + pass + + def testMaximumValue(self): + """ Skip max values test - as noted in the docs this is unreliable when features are in the buffer + """ + pass + class TestQgsVectorLayerSourceDeletedFeaturesInBuffer(unittest.TestCase, FeatureSourceTestCase): @@ -2746,6 +2767,21 @@ class TestQgsVectorLayerSourceDeletedFeaturesInBuffer(unittest.TestCase, Feature """ pass + def testUniqueValues(self): + """ Skip unique values test - as noted in the docs this is unreliable when features are in the buffer + """ + pass + + def testMinimumValue(self): + """ Skip min values test - as noted in the docs this is unreliable when features are in the buffer + """ + pass + + def testMaximumValue(self): + """ Skip max values test - as noted in the docs this is unreliable when features are in the buffer + """ + pass + # TODO: # - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect # - more join tests diff --git a/tests/src/python/test_qgsvectorlayercache.py b/tests/src/python/test_qgsvectorlayercache.py index 83bbad071bb..310ded97bce 100644 --- a/tests/src/python/test_qgsvectorlayercache.py +++ b/tests/src/python/test_qgsvectorlayercache.py @@ -90,6 +90,21 @@ class TestQgsVectorLayerCache(unittest.TestCase, FeatureSourceTestCase): """ pass + def testUniqueValues(self): + """ Skip unique values test - not implemented by the cache (yet) + """ + pass + + def testMinimumValue(self): + """ Skip min values test - not implemented by the cache (yet) + """ + pass + + def testMaximumValue(self): + """ Skip max values test - not implemented by the cache (yet) + """ + pass + if __name__ == '__main__': unittest.main()