Skip to content

Commit 648ff29

Browse files
authored
Merge pull request #4695 from nyalldawson/buffer_test
Add feature source unit tests for vector layer with edits in buffer
2 parents 6606d46 + 342897c commit 648ff29

File tree

2 files changed

+313
-2
lines changed

2 files changed

+313
-2
lines changed

src/core/qgsvectorlayerfeatureiterator.cpp

+20-2
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
208208
}
209209

210210
mProviderRequest.setLimit( providerLimit );
211+
mChangedFeaturesRequest.setLimit( providerLimit );
211212
}
212213
}
213214

@@ -276,6 +277,9 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
276277
if ( fetchNextChangedAttributeFeature( f ) )
277278
return true;
278279

280+
if ( fetchNextChangedGeomFeature( f ) )
281+
return true;
282+
279283
// no more changed features
280284
}
281285

@@ -410,6 +414,10 @@ void QgsVectorLayerFeatureIterator::useAddedFeature( const QgsFeature &src, QgsF
410414
{
411415
f.setGeometry( src.geometry() );
412416
}
417+
else
418+
{
419+
f.clearGeometry();
420+
}
413421

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

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

435443
mFetchConsidered << fid;
436444

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

441449
useChangedAttributeFeature( fid, *mFetchChangedGeomIt, f );
442450

451+
if ( mRequest.filterType() == QgsFeatureRequest::FilterExpression )
452+
{
453+
mRequest.expressionContext()->setFeature( f );
454+
if ( !mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() )
455+
{
456+
continue;
457+
}
458+
}
459+
443460
if ( testFeature( f ) )
444461
{
445462
// return complete feature
@@ -483,7 +500,8 @@ void QgsVectorLayerFeatureIterator::useChangedAttributeFeature( QgsFeatureId fid
483500
f.setValid( true );
484501
f.setFields( mSource->mFields );
485502

486-
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
503+
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) ||
504+
( mRequest.filterType() == QgsFeatureRequest::FilterExpression && mRequest.filterExpression()->needsGeometry() ) )
487505
{
488506
f.setGeometry( geom );
489507
}

tests/src/python/test_qgsvectorlayer.py

+293
Original file line numberDiff line numberDiff line change
@@ -2365,10 +2365,303 @@ def testQgsVectorLayerSelectedFeatureSource(self):
23652365
self.assertEqual(ids, {f1.id(), f3.id(), f5.id()})
23662366

23672367

2368+
class TestQgsVectorLayerSourceAddedFeaturesInBuffer(unittest.TestCase, FeatureSourceTestCase):
2369+
2370+
@classmethod
2371+
def getSource(cls):
2372+
vl = QgsVectorLayer(
2373+
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
2374+
'test', 'memory')
2375+
assert (vl.isValid())
2376+
2377+
f1 = QgsFeature()
2378+
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
2379+
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))
2380+
2381+
f2 = QgsFeature()
2382+
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])
2383+
2384+
f3 = QgsFeature()
2385+
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])
2386+
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))
2387+
2388+
f4 = QgsFeature()
2389+
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])
2390+
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))
2391+
2392+
f5 = QgsFeature()
2393+
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
2394+
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
2395+
2396+
# create a layer with features only in the added features buffer - not the provider
2397+
vl.startEditing()
2398+
vl.addFeatures([f1, f2, f3, f4, f5])
2399+
return vl
2400+
2401+
@classmethod
2402+
def setUpClass(cls):
2403+
"""Run before all tests"""
2404+
# Create test layer for FeatureSourceTestCase
2405+
cls.source = cls.getSource()
2406+
2407+
def testGetFeaturesSubsetAttributes2(self):
2408+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2409+
its features as direct copies (due to implicit sharing of QgsFeature)
2410+
"""
2411+
pass
2412+
2413+
def testGetFeaturesNoGeometry(self):
2414+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2415+
its features as direct copies (due to implicit sharing of QgsFeature)
2416+
"""
2417+
pass
2418+
2419+
def testOrderBy(self):
2420+
""" Skip order by tests - edited features are not sorted in iterators.
2421+
(Maybe they should be??)
2422+
"""
2423+
pass
2424+
2425+
2426+
class TestQgsVectorLayerSourceChangedGeometriesInBuffer(unittest.TestCase, FeatureSourceTestCase):
2427+
2428+
@classmethod
2429+
def getSource(cls):
2430+
vl = QgsVectorLayer(
2431+
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
2432+
'test', 'memory')
2433+
assert (vl.isValid())
2434+
2435+
f1 = QgsFeature()
2436+
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
2437+
2438+
f2 = QgsFeature()
2439+
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])
2440+
f2.setGeometry(QgsGeometry.fromWkt('Point (-70.5 65.2)'))
2441+
2442+
f3 = QgsFeature()
2443+
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])
2444+
2445+
f4 = QgsFeature()
2446+
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])
2447+
2448+
f5 = QgsFeature()
2449+
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
2450+
2451+
vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])
2452+
2453+
ids = {f['pk']: f.id() for f in vl.getFeatures()}
2454+
2455+
# modify geometries in buffer
2456+
vl.startEditing()
2457+
vl.changeGeometry(ids[5], QgsGeometry.fromWkt('Point (-71.123 78.23)'))
2458+
vl.changeGeometry(ids[3], QgsGeometry())
2459+
vl.changeGeometry(ids[1], QgsGeometry.fromWkt('Point (-70.332 66.33)'))
2460+
vl.changeGeometry(ids[2], QgsGeometry.fromWkt('Point (-68.2 70.8)'))
2461+
vl.changeGeometry(ids[4], QgsGeometry.fromWkt('Point (-65.32 78.3)'))
2462+
2463+
return vl
2464+
2465+
@classmethod
2466+
def setUpClass(cls):
2467+
"""Run before all tests"""
2468+
# Create test layer for FeatureSourceTestCase
2469+
cls.source = cls.getSource()
2470+
2471+
def testGetFeaturesSubsetAttributes2(self):
2472+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2473+
its features as direct copies (due to implicit sharing of QgsFeature)
2474+
"""
2475+
pass
2476+
2477+
def testGetFeaturesNoGeometry(self):
2478+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2479+
its features as direct copies (due to implicit sharing of QgsFeature)
2480+
"""
2481+
pass
2482+
2483+
def testOrderBy(self):
2484+
""" Skip order by tests - edited features are not sorted in iterators.
2485+
(Maybe they should be??)
2486+
"""
2487+
pass
2488+
2489+
2490+
class TestQgsVectorLayerSourceChangedAttributesInBuffer(unittest.TestCase, FeatureSourceTestCase):
2491+
2492+
@classmethod
2493+
def getSource(cls):
2494+
vl = QgsVectorLayer(
2495+
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
2496+
'test', 'memory')
2497+
assert (vl.isValid())
2498+
2499+
f1 = QgsFeature()
2500+
f1.setAttributes([5, 200, 'a', 'b', 'c'])
2501+
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))
2502+
2503+
f2 = QgsFeature()
2504+
f2.setAttributes([3, -200, 'd', 'e', 'f'])
2505+
2506+
f3 = QgsFeature()
2507+
f3.setAttributes([1, -100, 'g', 'h', 'i'])
2508+
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))
2509+
2510+
f4 = QgsFeature()
2511+
f4.setAttributes([2, -200, 'j', 'k', 'l'])
2512+
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))
2513+
2514+
f5 = QgsFeature()
2515+
f5.setAttributes([4, 400, 'm', 'n', 'o'])
2516+
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
2517+
2518+
vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])
2519+
2520+
ids = {f['pk']: f.id() for f in vl.getFeatures()}
2521+
2522+
# modify geometries in buffer
2523+
vl.startEditing()
2524+
vl.changeAttributeValue(ids[5], 1, -200)
2525+
vl.changeAttributeValue(ids[5], 2, NULL)
2526+
vl.changeAttributeValue(ids[5], 3, 'NuLl')
2527+
vl.changeAttributeValue(ids[5], 4, '5')
2528+
2529+
vl.changeAttributeValue(ids[3], 1, 300)
2530+
vl.changeAttributeValue(ids[3], 2, 'Pear')
2531+
vl.changeAttributeValue(ids[3], 3, 'PEaR')
2532+
vl.changeAttributeValue(ids[3], 4, '3')
2533+
2534+
vl.changeAttributeValue(ids[1], 1, 100)
2535+
vl.changeAttributeValue(ids[1], 2, 'Orange')
2536+
vl.changeAttributeValue(ids[1], 3, 'oranGe')
2537+
vl.changeAttributeValue(ids[1], 4, '1')
2538+
2539+
vl.changeAttributeValue(ids[2], 1, 200)
2540+
vl.changeAttributeValue(ids[2], 2, 'Apple')
2541+
vl.changeAttributeValue(ids[2], 3, 'Apple')
2542+
vl.changeAttributeValue(ids[2], 4, '2')
2543+
2544+
vl.changeAttributeValue(ids[4], 1, 400)
2545+
vl.changeAttributeValue(ids[4], 2, 'Honey')
2546+
vl.changeAttributeValue(ids[4], 3, 'Honey')
2547+
vl.changeAttributeValue(ids[4], 4, '4')
2548+
2549+
return vl
2550+
2551+
@classmethod
2552+
def setUpClass(cls):
2553+
"""Run before all tests"""
2554+
# Create test layer for FeatureSourceTestCase
2555+
cls.source = cls.getSource()
2556+
2557+
def testGetFeaturesSubsetAttributes2(self):
2558+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2559+
its features as direct copies (due to implicit sharing of QgsFeature)
2560+
"""
2561+
pass
2562+
2563+
def testGetFeaturesNoGeometry(self):
2564+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2565+
its features as direct copies (due to implicit sharing of QgsFeature)
2566+
"""
2567+
pass
2568+
2569+
def testOrderBy(self):
2570+
""" Skip order by tests - edited features are not sorted in iterators.
2571+
(Maybe they should be??)
2572+
"""
2573+
pass
2574+
2575+
2576+
class TestQgsVectorLayerSourceDeletedFeaturesInBuffer(unittest.TestCase, FeatureSourceTestCase):
2577+
2578+
@classmethod
2579+
def getSource(cls):
2580+
vl = QgsVectorLayer(
2581+
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
2582+
'test', 'memory')
2583+
assert (vl.isValid())
2584+
2585+
# add a bunch of similar features to the provider
2586+
b1 = QgsFeature()
2587+
b1.setAttributes([5, -300, 'Apple', 'PEaR', '1'])
2588+
b1.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))
2589+
2590+
b2 = QgsFeature()
2591+
b2.setAttributes([3, 100, 'Orange', 'NuLl', '2'])
2592+
b2.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))
2593+
2594+
b3 = QgsFeature()
2595+
b3.setAttributes([1, -200, 'Honey', 'oranGe', '5'])
2596+
2597+
b4 = QgsFeature()
2598+
b4.setAttributes([2, 400, 'Pear', 'Honey', '3'])
2599+
b4.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
2600+
2601+
b5 = QgsFeature()
2602+
b5.setAttributes([4, 200, NULL, 'oranGe', '3'])
2603+
b5.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))
2604+
2605+
vl.dataProvider().addFeatures([b1, b2, b3, b4, b5])
2606+
2607+
bad_ids = [f['pk'] for f in vl.getFeatures()]
2608+
2609+
# here's our good features
2610+
f1 = QgsFeature()
2611+
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
2612+
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))
2613+
2614+
f2 = QgsFeature()
2615+
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])
2616+
2617+
f3 = QgsFeature()
2618+
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])
2619+
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))
2620+
2621+
f4 = QgsFeature()
2622+
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])
2623+
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))
2624+
2625+
f5 = QgsFeature()
2626+
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
2627+
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
2628+
2629+
vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])
2630+
2631+
# delete the bad features, but don't commit
2632+
vl.startEditing()
2633+
vl.deleteFeatures(bad_ids)
2634+
return vl
2635+
2636+
@classmethod
2637+
def setUpClass(cls):
2638+
"""Run before all tests"""
2639+
# Create test layer for FeatureSourceTestCase
2640+
cls.source = cls.getSource()
2641+
2642+
def testGetFeaturesSubsetAttributes2(self):
2643+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2644+
its features as direct copies (due to implicit sharing of QgsFeature)
2645+
"""
2646+
pass
2647+
2648+
def testGetFeaturesNoGeometry(self):
2649+
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
2650+
its features as direct copies (due to implicit sharing of QgsFeature)
2651+
"""
2652+
pass
2653+
2654+
def testOrderBy(self):
2655+
""" Skip order by tests - edited features are not sorted in iterators.
2656+
(Maybe they should be??)
2657+
"""
2658+
pass
2659+
23682660
# TODO:
23692661
# - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect
23702662
# - more join tests
23712663
# - import
23722664

2665+
23732666
if __name__ == '__main__':
23742667
unittest.main()

0 commit comments

Comments
 (0)