Skip to content

Commit

Permalink
Merge pull request #4695 from nyalldawson/buffer_test
Browse files Browse the repository at this point in the history
Add feature source unit tests for vector layer with edits in buffer
  • Loading branch information
nyalldawson committed Jun 7, 2017
2 parents 6606d46 + 342897c commit 648ff29
Show file tree
Hide file tree
Showing 2 changed files with 313 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
293 changes: 293 additions & 0 deletions tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -2365,10 +2365,303 @@ 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


class TestQgsVectorLayerSourceDeletedFeaturesInBuffer(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())

# add a bunch of similar features to the provider
b1 = QgsFeature()
b1.setAttributes([5, -300, 'Apple', 'PEaR', '1'])
b1.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))

b2 = QgsFeature()
b2.setAttributes([3, 100, 'Orange', 'NuLl', '2'])
b2.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))

b3 = QgsFeature()
b3.setAttributes([1, -200, 'Honey', 'oranGe', '5'])

b4 = QgsFeature()
b4.setAttributes([2, 400, 'Pear', 'Honey', '3'])
b4.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))

b5 = QgsFeature()
b5.setAttributes([4, 200, NULL, 'oranGe', '3'])
b5.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))

vl.dataProvider().addFeatures([b1, b2, b3, b4, b5])

bad_ids = [f['pk'] for f in vl.getFeatures()]

# here's our good features
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)'))

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

# delete the bad features, but don't commit
vl.startEditing()
vl.deleteFeatures(bad_ids)
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
# - import


if __name__ == '__main__':
unittest.main()

0 comments on commit 648ff29

Please sign in to comment.