Skip to content

Commit

Permalink
Add feature source unit tests for vector layer with edits in buffer
Browse files Browse the repository at this point in the history
Run the feature source tests over a layer with added, edited
geometries, and edited attributes unsaved in an edit buffer

Fix identified issues when iterating over these edited features
  • Loading branch information
nyalldawson committed Jun 7, 2017
1 parent 7435d34 commit 3f81fc0
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 2 deletions.
22 changes: 20 additions & 2 deletions src/core/qgsvectorlayerfeatureiterator.cpp
Expand Up @@ -208,6 +208,7 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
}

mProviderRequest.setLimit( providerLimit );
mChangedFeaturesRequest.setLimit( providerLimit );
}
}

Expand Down Expand Up @@ -276,6 +277,9 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
if ( fetchNextChangedAttributeFeature( f ) )
return true;

if ( fetchNextChangedGeomFeature( f ) )
return true;

// no more changed features
}

Expand Down Expand Up @@ -410,6 +414,10 @@ void QgsVectorLayerFeatureIterator::useAddedFeature( const QgsFeature &src, QgsF
{
f.setGeometry( src.geometry() );
}
else
{
f.clearGeometry();
}

// TODO[MD]: if subset set just some attributes

Expand All @@ -434,12 +442,21 @@ bool QgsVectorLayerFeatureIterator::fetchNextChangedGeomFeature( QgsFeature &f )

mFetchConsidered << fid;

if ( !mFetchChangedGeomIt->intersects( mRequest.filterRect() ) )
if ( !mRequest.filterRect().isNull() && !mFetchChangedGeomIt->intersects( mRequest.filterRect() ) )
// skip changed geometries not in rectangle and don't check again
continue;

useChangedAttributeFeature( fid, *mFetchChangedGeomIt, f );

if ( mRequest.filterType() == QgsFeatureRequest::FilterExpression )
{
mRequest.expressionContext()->setFeature( f );
if ( !mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() )
{
continue;
}
}

if ( testFeature( f ) )
{
// return complete feature
Expand Down Expand Up @@ -483,7 +500,8 @@ void QgsVectorLayerFeatureIterator::useChangedAttributeFeature( QgsFeatureId fid
f.setValid( true );
f.setFields( mSource->mFields );

if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) ||
( mRequest.filterType() == QgsFeatureRequest::FilterExpression && mRequest.filterExpression()->needsGeometry() ) )
{
f.setGeometry( geom );
}
Expand Down
207 changes: 207 additions & 0 deletions tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -2365,6 +2365,213 @@ def testQgsVectorLayerSelectedFeatureSource(self):
self.assertEqual(ids, {f1.id(), f3.id(), f5.id()})


class TestQgsVectorLayerSourceAddedFeaturesInBuffer(unittest.TestCase, FeatureSourceTestCase):

@classmethod
def getSource(cls):
vl = QgsVectorLayer(
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
'test', 'memory')
assert (vl.isValid())

f1 = QgsFeature()
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))

f2 = QgsFeature()
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])

f3 = QgsFeature()
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))

f4 = QgsFeature()
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))

f5 = QgsFeature()
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))

# create a layer with features only in the added features buffer - not the provider
vl.startEditing()
vl.addFeatures([f1, f2, f3, f4, f5])
return vl

@classmethod
def setUpClass(cls):
"""Run before all tests"""
# Create test layer for FeatureSourceTestCase
cls.source = cls.getSource()

def testGetFeaturesSubsetAttributes2(self):
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
its features as direct copies (due to implicit sharing of QgsFeature)
"""
pass

def testGetFeaturesNoGeometry(self):
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
its features as direct copies (due to implicit sharing of QgsFeature)
"""
pass

def testOrderBy(self):
""" Skip order by tests - edited features are not sorted in iterators.
(Maybe they should be??)
"""
pass


class TestQgsVectorLayerSourceChangedGeometriesInBuffer(unittest.TestCase, FeatureSourceTestCase):

@classmethod
def getSource(cls):
vl = QgsVectorLayer(
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
'test', 'memory')
assert (vl.isValid())

f1 = QgsFeature()
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])

f2 = QgsFeature()
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])
f2.setGeometry(QgsGeometry.fromWkt('Point (-70.5 65.2)'))

f3 = QgsFeature()
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])

f4 = QgsFeature()
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])

f5 = QgsFeature()
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])

vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])

ids = {f['pk']: f.id() for f in vl.getFeatures()}

# modify geometries in buffer
vl.startEditing()
vl.changeGeometry(ids[5], QgsGeometry.fromWkt('Point (-71.123 78.23)'))
vl.changeGeometry(ids[3], QgsGeometry())
vl.changeGeometry(ids[1], QgsGeometry.fromWkt('Point (-70.332 66.33)'))
vl.changeGeometry(ids[2], QgsGeometry.fromWkt('Point (-68.2 70.8)'))
vl.changeGeometry(ids[4], QgsGeometry.fromWkt('Point (-65.32 78.3)'))

return vl

@classmethod
def setUpClass(cls):
"""Run before all tests"""
# Create test layer for FeatureSourceTestCase
cls.source = cls.getSource()

def testGetFeaturesSubsetAttributes2(self):
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
its features as direct copies (due to implicit sharing of QgsFeature)
"""
pass

def testGetFeaturesNoGeometry(self):
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
its features as direct copies (due to implicit sharing of QgsFeature)
"""
pass

def testOrderBy(self):
""" Skip order by tests - edited features are not sorted in iterators.
(Maybe they should be??)
"""
pass


class TestQgsVectorLayerSourceChangedAttributesInBuffer(unittest.TestCase, FeatureSourceTestCase):

@classmethod
def getSource(cls):
vl = QgsVectorLayer(
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
'test', 'memory')
assert (vl.isValid())

f1 = QgsFeature()
f1.setAttributes([5, 200, 'a', 'b', 'c'])
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))

f2 = QgsFeature()
f2.setAttributes([3, -200, 'd', 'e', 'f'])

f3 = QgsFeature()
f3.setAttributes([1, -100, 'g', 'h', 'i'])
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))

f4 = QgsFeature()
f4.setAttributes([2, -200, 'j', 'k', 'l'])
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))

f5 = QgsFeature()
f5.setAttributes([4, 400, 'm', 'n', 'o'])
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))

vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])

ids = {f['pk']: f.id() for f in vl.getFeatures()}

# modify geometries in buffer
vl.startEditing()
vl.changeAttributeValue(ids[5], 1, -200)
vl.changeAttributeValue(ids[5], 2, NULL)
vl.changeAttributeValue(ids[5], 3, 'NuLl')
vl.changeAttributeValue(ids[5], 4, '5')

vl.changeAttributeValue(ids[3], 1, 300)
vl.changeAttributeValue(ids[3], 2, 'Pear')
vl.changeAttributeValue(ids[3], 3, 'PEaR')
vl.changeAttributeValue(ids[3], 4, '3')

vl.changeAttributeValue(ids[1], 1, 100)
vl.changeAttributeValue(ids[1], 2, 'Orange')
vl.changeAttributeValue(ids[1], 3, 'oranGe')
vl.changeAttributeValue(ids[1], 4, '1')

vl.changeAttributeValue(ids[2], 1, 200)
vl.changeAttributeValue(ids[2], 2, 'Apple')
vl.changeAttributeValue(ids[2], 3, 'Apple')
vl.changeAttributeValue(ids[2], 4, '2')

vl.changeAttributeValue(ids[4], 1, 400)
vl.changeAttributeValue(ids[4], 2, 'Honey')
vl.changeAttributeValue(ids[4], 3, 'Honey')
vl.changeAttributeValue(ids[4], 4, '4')

return vl

@classmethod
def setUpClass(cls):
"""Run before all tests"""
# Create test layer for FeatureSourceTestCase
cls.source = cls.getSource()

def testGetFeaturesSubsetAttributes2(self):
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
its features as direct copies (due to implicit sharing of QgsFeature)
"""
pass

def testGetFeaturesNoGeometry(self):
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
its features as direct copies (due to implicit sharing of QgsFeature)
"""
pass

def testOrderBy(self):
""" Skip order by tests - edited features are not sorted in iterators.
(Maybe they should be??)
"""
pass

# TODO:
# - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect
# - more join tests
Expand Down

0 comments on commit 3f81fc0

Please sign in to comment.