Skip to content

Commit 50e4b06

Browse files
committed
Implement provider side FilterFids iterators for OGR provider
Makes some operations with OGR sources magnitudes faster, ie zoom to 20 selected features in a 4 million point dataset: before: 14 seconds of blocked gui after: instant (cherry-picked from 1f02fd4)
1 parent f8ede27 commit 50e4b06

File tree

4 files changed

+72
-21
lines changed

4 files changed

+72
-21
lines changed

src/providers/ogr/qgsogrfeatureiterator.cpp

+36-13
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource* source, bool
4343
, mSubsetStringSet( false )
4444
, mFetchGeometry( false )
4545
, mExpressionCompiled( false )
46+
, mFilterFids( mRequest.filterFids() )
47+
, mFilterFidsIt( mFilterFids.constBegin() )
4648
{
4749
mConn = QgsOgrConnPool::instance()->acquireConnection( mSource->mProvider->dataSourceUri() );
4850
if ( !mConn->ds )
@@ -166,6 +168,22 @@ bool QgsOgrFeatureIterator::nextFeatureFilterExpression( QgsFeature& f )
166168
return fetchFeature( f );
167169
}
168170

171+
bool QgsOgrFeatureIterator::fetchFeatureWithId( QgsFeatureId id, QgsFeature& feature ) const
172+
{
173+
feature.setValid( false );
174+
OGRFeatureH fet = OGR_L_GetFeature( ogrLayer, FID_TO_NUMBER( id ) );
175+
if ( !fet )
176+
{
177+
return false;
178+
}
179+
180+
if ( readFeature( fet, feature ) )
181+
OGR_F_Destroy( fet );
182+
183+
feature.setValid( true );
184+
return true;
185+
}
186+
169187
bool QgsOgrFeatureIterator::fetchFeature( QgsFeature& feature )
170188
{
171189
feature.setValid( false );
@@ -175,19 +193,22 @@ bool QgsOgrFeatureIterator::fetchFeature( QgsFeature& feature )
175193

176194
if ( mRequest.filterType() == QgsFeatureRequest::FilterFid )
177195
{
178-
OGRFeatureH fet = OGR_L_GetFeature( ogrLayer, FID_TO_NUMBER( mRequest.filterFid() ) );
179-
if ( !fet )
196+
bool result = fetchFeatureWithId( mRequest.filterFid(), feature );
197+
close(); // the feature has been read or was not found: we have finished here
198+
return result;
199+
}
200+
else if ( mRequest.filterType() == QgsFeatureRequest::FilterFids )
201+
{
202+
while ( mFilterFidsIt != mFilterFids.constEnd() )
180203
{
181-
close();
182-
return false;
183-
}
184-
185-
if ( readFeature( fet, feature ) )
186-
OGR_F_Destroy( fet );
204+
QgsFeatureId nextId = *mFilterFidsIt;
205+
mFilterFidsIt++;
187206

188-
feature.setValid( true );
189-
close(); // the feature has been read: we have finished here
190-
return true;
207+
if ( fetchFeatureWithId( nextId, feature ) )
208+
return true;
209+
}
210+
close();
211+
return false;
191212
}
192213

193214
OGRFeatureH fet;
@@ -220,6 +241,8 @@ bool QgsOgrFeatureIterator::rewind()
220241

221242
OGR_L_ResetReading( ogrLayer );
222243

244+
mFilterFidsIt = mFilterFids.constBegin();
245+
223246
return true;
224247
}
225248

@@ -246,7 +269,7 @@ bool QgsOgrFeatureIterator::close()
246269
}
247270

248271

249-
void QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature & f, int attindex )
272+
void QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature & f, int attindex ) const
250273
{
251274
if ( mSource->mFirstFieldIsFid && attindex == 0 )
252275
{
@@ -264,7 +287,7 @@ void QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature
264287
}
265288

266289

267-
bool QgsOgrFeatureIterator::readFeature( OGRFeatureH fet, QgsFeature& feature )
290+
bool QgsOgrFeatureIterator::readFeature( OGRFeatureH fet, QgsFeature& feature ) const
268291
{
269292
feature.setFeatureId( OGR_F_GetFID( fet ) );
270293
feature.initAttributes( mSource->mFields.count() );

src/providers/ogr/qgsogrfeatureiterator.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgr
6868
//! fetch next feature filter expression
6969
bool nextFeatureFilterExpression( QgsFeature& f ) override;
7070

71-
bool readFeature( OGRFeatureH fet, QgsFeature& feature );
71+
bool readFeature( OGRFeatureH fet, QgsFeature& feature ) const;
7272

7373
//! Get an attribute associated with a feature
74-
void getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature & f, int attindex );
74+
void getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature & f, int attindex ) const;
7575

7676
bool mFeatureFetched;
7777

@@ -85,6 +85,11 @@ class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgr
8585

8686
private:
8787
bool mExpressionCompiled;
88+
QgsFeatureIds mFilterFids;
89+
QgsFeatureIds::const_iterator mFilterFidsIt;
90+
91+
bool fetchFeatureWithId( QgsFeatureId id, QgsFeature& feature ) const;
92+
8893
};
8994

9095
#endif // QGSOGRFEATUREITERATOR_H

tests/src/python/providertestbase.py

+26
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,36 @@ def testGetFeaturesFidsTests(self):
383383
expected = set([fids[1], fids[3], fids[4]])
384384
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
385385

386+
#providers should ignore non-existant fids
387+
result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([-101, fids[1], -102, fids[3], -103, fids[4], -104]))])
388+
expected = set([fids[1], fids[3], fids[4]])
389+
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
390+
386391
result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([]))])
387392
expected = set([])
388393
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
389394

395+
# Rewind mid-way
396+
request = QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]])
397+
feature_it = self.provider.getFeatures(request)
398+
feature = QgsFeature()
399+
feature.setValid(True)
400+
self.assertTrue(feature_it.nextFeature(feature))
401+
self.assertIn(feature.id(), [fids[1], fids[3], fids[4]])
402+
first_feature = feature
403+
self.assertTrue(feature.isValid())
404+
# rewind
405+
self.assertTrue(feature_it.rewind())
406+
self.assertTrue(feature_it.nextFeature(feature))
407+
self.assertEqual(feature.id(), first_feature.id())
408+
self.assertTrue(feature.isValid())
409+
# grab all features
410+
self.assertTrue(feature_it.nextFeature(feature))
411+
self.assertTrue(feature_it.nextFeature(feature))
412+
# none left
413+
self.assertFalse(feature_it.nextFeature(feature))
414+
self.assertFalse(feature.isValid())
415+
390416
def testGetFeaturesFilterRectTests(self):
391417
extent = QgsRectangle(-70, 67, -60, 80)
392418
request = QgsFeatureRequest().setFilterRect(extent)

tests/src/python/test_qgsfeatureiterator.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -79,23 +79,20 @@ def test_FilterFids(self):
7979

8080
ids = [feat.id() for feat in pointLayer.getFeatures(QgsFeatureRequest().setFilterFids([7, 8, 12, 30]))]
8181
expectedIds = [7, 8, 12]
82-
myMessage = '\nExpected: {0} features\nGot: {1} features'.format(repr(expectedIds), repr(ids))
83-
assert ids == expectedIds, myMessage
82+
self.assertEquals(set(ids), set(expectedIds))
8483

8584
pointLayer.startEditing()
8685
self.addFeatures(pointLayer)
8786

8887
ids = [feat.id() for feat in pointLayer.getFeatures(QgsFeatureRequest().setFilterFids([-4, 7, 8, 12, 30]))]
8988
expectedIds = [-4, 7, 8, 12]
90-
myMessage = '\nExpected: {0} features\nGot: {1} features'.format(repr(expectedIds), repr(ids))
91-
assert ids == expectedIds, myMessage
89+
self.assertEquals(set(ids), set(expectedIds))
9290

9391
pointLayer.rollBack()
9492

9593
ids = [feat.id() for feat in pointLayer.getFeatures(QgsFeatureRequest().setFilterFids([-2, 7, 8, 12, 30]))]
9694
expectedIds = [7, 8, 12]
97-
myMessage = '\nExpected: {0} features\nGot: {1} features'.format(repr(expectedIds), repr(ids))
98-
assert ids == expectedIds, myMessage
95+
self.assertEquals(set(ids), set(expectedIds))
9996

10097
def addFeatures(self, vl):
10198
feat = QgsFeature()

0 commit comments

Comments
 (0)