diff --git a/python/core/qgsvectorlayerfeatureiterator.sip b/python/core/qgsvectorlayerfeatureiterator.sip index 64d77bb80ef..1a6e60bc347 100644 --- a/python/core/qgsvectorlayerfeatureiterator.sip +++ b/python/core/qgsvectorlayerfeatureiterator.sip @@ -46,6 +46,13 @@ class QgsVectorLayerFeatureSource : QgsAbstractFeatureSource :rtype: QgsFields %End + QgsCoordinateReferenceSystem crs() const; +%Docstring + Returns the coordinate reference system for features retrieved from this source. +.. versionadded:: 3.0 + :rtype: QgsCoordinateReferenceSystem +%End + protected: @@ -138,6 +145,35 @@ Setup the simplification of geometries to fetch using the specified simplify met QgsVectorLayerFeatureIterator( const QgsVectorLayerFeatureIterator &rhs ); }; + + +class QgsVectorLayerSelectedFeatureSource : QgsFeatureSource +{ +%Docstring + QgsFeatureSource subclass for the selected features from a QgsVectorLayer. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsvectorlayerfeatureiterator.h" +%End + public: + + QgsVectorLayerSelectedFeatureSource( QgsVectorLayer *layer ); +%Docstring + Constructor for QgsVectorLayerSelectedFeatureSource, for selected features from the specified ``layer``. + The currently selected feature IDs are stored, so change to the layer selection after constructing + the QgsVectorLayerSelectedFeatureSource will not be reflected. +%End + + virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const; + virtual QgsCoordinateReferenceSystem sourceCrs() const; + virtual QgsFields fields() const; + virtual QgsWkbTypes::Type wkbType() const; + virtual long featureCount() const; + +}; + /************************************************************************ * This file has been generated automatically from * * * diff --git a/src/core/qgsvectorlayerfeatureiterator.cpp b/src/core/qgsvectorlayerfeatureiterator.cpp index bbcd73c481b..d39f03ce29d 100644 --- a/src/core/qgsvectorlayerfeatureiterator.cpp +++ b/src/core/qgsvectorlayerfeatureiterator.cpp @@ -98,6 +98,11 @@ QgsFields QgsVectorLayerFeatureSource::fields() const return mFields; } +QgsCoordinateReferenceSystem QgsVectorLayerFeatureSource::crs() const +{ + return mCrs; +} + QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeatureSource *source, bool ownSource, const QgsFeatureRequest &request ) : QgsAbstractFeatureIteratorFromSource( source, ownSource, request ) @@ -1006,3 +1011,51 @@ bool QgsVectorLayerFeatureIterator::prepareOrderBy( const QListselectedFeatureIds() ) + , mWkbType( layer->wkbType() ) +{} + +QgsFeatureIterator QgsVectorLayerSelectedFeatureSource::getFeatures( const QgsFeatureRequest &request ) const +{ + QgsFeatureRequest req( request ); + + if ( req.filterFids().isEmpty() && req.filterType() != QgsFeatureRequest::FilterFids ) + { + req.setFilterFids( mSelectedFeatureIds ); + } + else if ( !req.filterFids().isEmpty() ) + { + QgsFeatureIds reqIds = mSelectedFeatureIds; + reqIds.intersect( req.filterFids() ); + req.setFilterFids( reqIds ); + } + + return mSource.getFeatures( req ); +} + +QgsCoordinateReferenceSystem QgsVectorLayerSelectedFeatureSource::sourceCrs() const +{ + return mSource.crs(); +} + +QgsFields QgsVectorLayerSelectedFeatureSource::fields() const +{ + return mSource.fields(); +} + +QgsWkbTypes::Type QgsVectorLayerSelectedFeatureSource::wkbType() const +{ + return mWkbType; +} + +long QgsVectorLayerSelectedFeatureSource::featureCount() const +{ + return mSelectedFeatureIds.count(); +} diff --git a/src/core/qgsvectorlayerfeatureiterator.h b/src/core/qgsvectorlayerfeatureiterator.h index e75db6e44a2..a3c36cdef6e 100644 --- a/src/core/qgsvectorlayerfeatureiterator.h +++ b/src/core/qgsvectorlayerfeatureiterator.h @@ -20,6 +20,7 @@ #include "qgsfeatureiterator.h" #include "qgsfields.h" #include "qgscoordinatereferencesystem.h" +#include "qgsfeaturesource.h" #include #include @@ -67,6 +68,12 @@ class CORE_EXPORT QgsVectorLayerFeatureSource : public QgsAbstractFeatureSource */ QgsFields fields() const; + /** + * Returns the coordinate reference system for features retrieved from this source. + * \since QGIS 3.0 + */ + QgsCoordinateReferenceSystem crs() const; + protected: QgsAbstractFeatureSource *mProviderFeatureSource = nullptr; @@ -253,4 +260,38 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera bool checkGeometryValidity( const QgsFeature &feature ); }; + + +/** + * \class QgsVectorLayerSelectedFeatureSource + * \ingroup core + * QgsFeatureSource subclass for the selected features from a QgsVectorLayer. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsVectorLayerSelectedFeatureSource : public QgsFeatureSource +{ + public: + + /** + * Constructor for QgsVectorLayerSelectedFeatureSource, for selected features from the specified \a layer. + * The currently selected feature IDs are stored, so change to the layer selection after constructing + * the QgsVectorLayerSelectedFeatureSource will not be reflected. + */ + QgsVectorLayerSelectedFeatureSource( QgsVectorLayer *layer ); + + virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const override; + virtual QgsCoordinateReferenceSystem sourceCrs() const override; + virtual QgsFields fields() const override; + virtual QgsWkbTypes::Type wkbType() const override; + virtual long featureCount() const override; + + private: + + // ideally this wouldn't be mutable, but QgsVectorLayerFeatureSource has non-const getFeatures() + mutable QgsVectorLayerFeatureSource mSource; + QgsFeatureIds mSelectedFeatureIds; + QgsWkbTypes::Type mWkbType = QgsWkbTypes::Unknown; + +}; + #endif // QGSVECTORLAYERFEATUREITERATOR_H diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 344428da340..63568b8b6ff 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -52,6 +52,7 @@ from qgis.core import (QgsWkbTypes, QgsSingleCategoryDiagramRenderer, QgsDiagramLayerSettings, QgsTextFormat, + QgsVectorLayerSelectedFeatureSource, NULL) from qgis.testing import start_app, unittest from featuresourcetestbase import FeatureSourceTestCase @@ -2298,6 +2299,61 @@ class TestQgsVectorLayer(unittest.TestCase, FeatureSourceTestCase): # compare xml documents self.assertEqual(layer_doc.toString(), clone_doc.toString()) + def testQgsVectorLayerSelectedFeatureSource(self): + """ + test QgsVectorLayerSelectedFeatureSource + """ + + layer = QgsVectorLayer("Point?crs=epsg:3111&field=fldtxt:string&field=fldint:integer", + "addfeat", "memory") + pr = layer.dataProvider() + f1 = QgsFeature(1) + f1.setAttributes(["test", 123]) + f1.setGeometry(QgsGeometry.fromPoint(QgsPointXY(100, 200))) + f2 = QgsFeature(2) + f2.setAttributes(["test2", 457]) + f2.setGeometry(QgsGeometry.fromPoint(QgsPointXY(200, 200))) + f3 = QgsFeature(3) + f3.setAttributes(["test2", 888]) + f3.setGeometry(QgsGeometry.fromPoint(QgsPointXY(300, 200))) + f4 = QgsFeature(4) + f4.setAttributes(["test3", -1]) + f4.setGeometry(QgsGeometry.fromPoint(QgsPointXY(400, 300))) + f5 = QgsFeature(5) + f5.setAttributes(["test4", 0]) + f5.setGeometry(QgsGeometry.fromPoint(QgsPointXY(0, 0))) + self.assertTrue(pr.addFeatures([f1, f2, f3, f4, f5])) + self.assertEqual(layer.featureCount(), 5) + + source = QgsVectorLayerSelectedFeatureSource(layer) + self.assertEqual(source.sourceCrs().authid(), 'EPSG:3111') + self.assertEqual(source.wkbType(), QgsWkbTypes.Point) + self.assertEqual(source.fields(), layer.fields()) + + # no selection + self.assertEqual(source.featureCount(), 0) + it = source.getFeatures() + f = QgsFeature() + self.assertFalse(it.nextFeature(f)) + + # with selection + layer.selectByIds([f1.id(), f3.id(), f5.id()]) + source = QgsVectorLayerSelectedFeatureSource(layer) + self.assertEqual(source.featureCount(), 3) + ids = set([f.id() for f in source.getFeatures()]) + self.assertEqual(ids, {f1.id(), f3.id(), f5.id()}) + + # test that source has stored snapshot of selected features + layer.selectByIds([f2.id(), f4.id()]) + self.assertEqual(source.featureCount(), 3) + ids = set([f.id() for f in source.getFeatures()]) + self.assertEqual(ids, {f1.id(), f3.id(), f5.id()}) + + # test that source is not dependent on layer + del layer + ids = set([f.id() for f in source.getFeatures()]) + self.assertEqual(ids, {f1.id(), f3.id(), f5.id()}) + # TODO: # - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect