Skip to content

Commit

Permalink
Create QgsVectorLayerSelectedFeatureSource
Browse files Browse the repository at this point in the history
...which is a QgsFeatureSource subclass which only considers
selected features from a QgsVectorLayer
  • Loading branch information
nyalldawson committed Jun 5, 2017
1 parent f69d1c2 commit 405c55f
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
36 changes: 36 additions & 0 deletions python/core/qgsvectorlayerfeatureiterator.sip
Original file line number Diff line number Diff line change
Expand Up @@ -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:


Expand Down Expand Up @@ -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 *
* *
Expand Down
53 changes: 53 additions & 0 deletions src/core/qgsvectorlayerfeatureiterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<QgsVectorLayerFeatureSource>( source, ownSource, request )
Expand Down Expand Up @@ -1006,3 +1011,51 @@ bool QgsVectorLayerFeatureIterator::prepareOrderBy( const QList<QgsFeatureReques
return true;
}


//
// QgsVectorLayerSelectedFeatureSource
//

QgsVectorLayerSelectedFeatureSource::QgsVectorLayerSelectedFeatureSource( QgsVectorLayer *layer )
: mSource( layer )
, mSelectedFeatureIds( layer->selectedFeatureIds() )
, 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();
}
41 changes: 41 additions & 0 deletions src/core/qgsvectorlayerfeatureiterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "qgsfeatureiterator.h"
#include "qgsfields.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsfeaturesource.h"

#include <QSet>
#include <memory>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
56 changes: 56 additions & 0 deletions tests/src/python/test_qgsvectorlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
QgsSingleCategoryDiagramRenderer,
QgsDiagramLayerSettings,
QgsTextFormat,
QgsVectorLayerSelectedFeatureSource,
NULL)
from qgis.testing import start_app, unittest
from featuresourcetestbase import FeatureSourceTestCase
Expand Down Expand Up @@ -2298,6 +2299,61 @@ def testClone(self):
# 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
Expand Down

0 comments on commit 405c55f

Please sign in to comment.