Skip to content

Commit b6e1eea

Browse files
committed
Handle request destinationCrs in QgsVectorLayerFeatureIterator
1 parent a989235 commit b6e1eea

File tree

4 files changed

+69
-27
lines changed

4 files changed

+69
-27
lines changed

python/core/qgsvectorlayerfeatureiterator.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ Setup the simplification of geometries to fetch using the specified simplify met
141141

142142

143143

144+
144145
private:
145146
QgsVectorLayerFeatureIterator( const QgsVectorLayerFeatureIterator &rhs );
146147
};

src/core/qgsvectorlayerfeatureiterator.cpp

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
109109
, mFetchedFid( false )
110110
, mInterruptionChecker( nullptr )
111111
{
112+
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
113+
{
114+
mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs() );
115+
}
116+
mFilterRect = transformedFilterRect( mTransform );
117+
if ( !mFilterRect.isNull() )
118+
{
119+
// update request to be the unprojected filter rect
120+
mRequest.setFilterRect( mFilterRect );
121+
}
122+
112123
if ( mRequest.filterType() == QgsFeatureRequest::FilterExpression )
113124
{
114125
mRequest.expressionContext()->setFields( mSource->mFields );
@@ -129,6 +140,13 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
129140

130141
// by default provider's request is the same
131142
mProviderRequest = mRequest;
143+
// but we remove any destination CRS parameter - that is handled in QgsVectorLayerFeatureIterator,
144+
// not at the provider level. Otherwise virtual fields depending on geometry would have incorrect
145+
// values
146+
if ( mRequest.destinationCrs().isValid() )
147+
{
148+
mProviderRequest.setDestinationCrs( QgsCoordinateReferenceSystem() );
149+
}
132150

133151
if ( mProviderRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
134152
{
@@ -253,7 +271,7 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
253271
if ( mFetchedFid )
254272
return false;
255273
bool res = nextFeatureFid( f );
256-
if ( res && testFeature( f ) )
274+
if ( res && postProcessFeature( f ) )
257275
{
258276
mFetchedFid = true;
259277
return res;
@@ -264,7 +282,7 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
264282
}
265283
}
266284

267-
if ( !mRequest.filterRect().isNull() )
285+
if ( !mFilterRect.isNull() )
268286
{
269287
if ( fetchNextChangedGeomFeature( f ) )
270288
return true;
@@ -327,7 +345,7 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
327345
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
328346
updateFeatureGeometry( f );
329347

330-
if ( !testFeature( f ) )
348+
if ( !postProcessFeature( f ) )
331349
continue;
332350

333351
return true;
@@ -387,15 +405,17 @@ bool QgsVectorLayerFeatureIterator::fetchNextAddedFeature( QgsFeature &f )
387405
// must have changed geometry outside rectangle
388406
continue;
389407

390-
if ( !mRequest.acceptFeature( *mFetchAddedFeaturesIt ) )
408+
useAddedFeature( *mFetchAddedFeaturesIt, f );
409+
410+
// can't test for feature acceptance until after calling useAddedFeature
411+
// since acceptFeature may rely on virtual fields
412+
if ( !mRequest.acceptFeature( f ) )
391413
// skip features which are not accepted by the filter
392414
continue;
393415

394-
if ( !testFeature( *mFetchAddedFeaturesIt ) )
416+
if ( !postProcessFeature( f ) )
395417
continue;
396418

397-
useAddedFeature( *mFetchAddedFeaturesIt, f );
398-
399419
return true;
400420
}
401421

@@ -406,23 +426,13 @@ bool QgsVectorLayerFeatureIterator::fetchNextAddedFeature( QgsFeature &f )
406426

407427
void QgsVectorLayerFeatureIterator::useAddedFeature( const QgsFeature &src, QgsFeature &f )
408428
{
409-
f.setId( src.id() );
429+
// since QgsFeature is implicitly shared, it's more efficient to just copy the
430+
// whole feature, even if flags like NoGeometry or a subset of attributes is set at the request.
431+
// This helps potentially avoid an unnecessary detach of the feature
432+
f = src;
410433
f.setValid( true );
411434
f.setFields( mSource->mFields );
412435

413-
if ( src.hasGeometry() && !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
414-
{
415-
f.setGeometry( src.geometry() );
416-
}
417-
else
418-
{
419-
f.clearGeometry();
420-
}
421-
422-
// TODO[MD]: if subset set just some attributes
423-
424-
f.setAttributes( src.attributes() );
425-
426436
if ( mHasVirtualAttributes )
427437
addVirtualAttributes( f );
428438
}
@@ -442,7 +452,7 @@ bool QgsVectorLayerFeatureIterator::fetchNextChangedGeomFeature( QgsFeature &f )
442452

443453
mFetchConsidered << fid;
444454

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

@@ -457,7 +467,7 @@ bool QgsVectorLayerFeatureIterator::fetchNextChangedGeomFeature( QgsFeature &f )
457467
}
458468
}
459469

460-
if ( testFeature( f ) )
470+
if ( postProcessFeature( f ) )
461471
{
462472
// return complete feature
463473
mFetchChangedGeomIt++;
@@ -484,7 +494,7 @@ bool QgsVectorLayerFeatureIterator::fetchNextChangedAttributeFeature( QgsFeature
484494
addVirtualAttributes( f );
485495

486496
mRequest.expressionContext()->setFeature( f );
487-
if ( mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() && testFeature( f ) )
497+
if ( mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() && postProcessFeature( f ) )
488498
{
489499
return true;
490500
}
@@ -703,9 +713,11 @@ void QgsVectorLayerFeatureIterator::createOrderedJoinList()
703713
}
704714
}
705715

706-
bool QgsVectorLayerFeatureIterator::testFeature( const QgsFeature &feature )
716+
bool QgsVectorLayerFeatureIterator::postProcessFeature( QgsFeature &feature )
707717
{
708718
bool result = checkGeometryValidity( feature );
719+
if ( result )
720+
transformFeatureGeometry( feature, mTransform );
709721
return result;
710722
}
711723

src/core/qgsvectorlayerfeatureiterator.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
208208
QgsFeatureRequest mChangedFeaturesRequest;
209209
QgsFeatureIterator mChangedFeaturesIterator;
210210

211+
QgsRectangle mFilterRect;
212+
QgsCoordinateTransform mTransform;
213+
211214
// only related to editing
212215
QSet<QgsFeatureId> mFetchConsidered;
213216
QgsGeometryMap::ConstIterator mFetchChangedGeomIt;
@@ -250,9 +253,9 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
250253
void createOrderedJoinList();
251254

252255
/**
253-
* Performs any feature based validity checking, e.g. checking for geometry validity.
256+
* Performs any post-processing (such as transformation) and feature based validity checking, e.g. checking for geometry validity.
254257
*/
255-
bool testFeature( const QgsFeature &feature );
258+
bool postProcessFeature( QgsFeature &feature );
256259

257260
/**
258261
* Checks a feature's geometry for validity, if requested in feature request.

tests/src/python/test_qgsvectorlayer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,6 +2364,32 @@ def testQgsVectorLayerSelectedFeatureSource(self):
23642364
ids = set([f.id() for f in source.getFeatures()])
23652365
self.assertEqual(ids, {f1.id(), f3.id(), f5.id()})
23662366

2367+
def testFeatureRequestWithReprojectionAndVirtualFields(self):
2368+
layer = self.getSource()
2369+
field = QgsField('virtual', QVariant.Double)
2370+
layer.addExpressionField('$x', field)
2371+
virtual_values = [f['virtual'] for f in layer.getFeatures()]
2372+
self.assertAlmostEqual(virtual_values[0], -71.123, 2)
2373+
self.assertEqual(virtual_values[1], NULL)
2374+
self.assertAlmostEqual(virtual_values[2], -70.332, 2)
2375+
self.assertAlmostEqual(virtual_values[3], -68.2, 2)
2376+
self.assertAlmostEqual(virtual_values[4], -65.32, 2)
2377+
2378+
# repeat, with reprojection on request
2379+
request = QgsFeatureRequest().setDestinationCrs(QgsCoordinateReferenceSystem('epsg:3785'))
2380+
features = [f for f in layer.getFeatures(request)]
2381+
# virtual field value should not change, even though geometry has
2382+
self.assertAlmostEqual(features[0]['virtual'], -71.123, 2)
2383+
self.assertAlmostEqual(features[0].geometry().geometry().x(), -7917376, -5)
2384+
self.assertEqual(features[1]['virtual'], NULL)
2385+
self.assertFalse(features[1].hasGeometry())
2386+
self.assertAlmostEqual(features[2]['virtual'], -70.332, 2)
2387+
self.assertAlmostEqual(features[2].geometry().geometry().x(), -7829322, -5)
2388+
self.assertAlmostEqual(features[3]['virtual'], -68.2, 2)
2389+
self.assertAlmostEqual(features[3].geometry().geometry().x(), -7591989, -5)
2390+
self.assertAlmostEqual(features[4]['virtual'], -65.32, 2)
2391+
self.assertAlmostEqual(features[4].geometry().geometry().x(), -7271389, -5)
2392+
23672393

23682394
class TestQgsVectorLayerSourceAddedFeaturesInBuffer(unittest.TestCase, FeatureSourceTestCase):
23692395

0 commit comments

Comments
 (0)