Skip to content

Commit 80d07cb

Browse files
committedApr 25, 2017
Add method to perform invalid geometry checking in QgsFeatureRequest
Allows requests to specify how invalid geometries should be handled. Default is to perform no geometry validity checking.
1 parent cd521d6 commit 80d07cb

File tree

6 files changed

+152
-7
lines changed

6 files changed

+152
-7
lines changed
 

‎python/core/qgsfeaturerequest.sip

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ class QgsFeatureRequest
2626
FilterFids //!< Filter using feature IDs
2727
};
2828

29+
//! Handling of features with invalid geometries
30+
enum InvalidGeometryCheck
31+
{
32+
GeometryNoCheck,
33+
GeometrySkipInvalid,
34+
GeometryAbortOnInvalid,
35+
};
36+
2937
/**
3038
* The OrderByClause class represents an order by clause for a QgsFeatureRequest.
3139
*
@@ -196,6 +204,22 @@ class QgsFeatureRequest
196204
//! Get feature IDs that should be fetched.
197205
const QgsFeatureIds& filterFids() const;
198206

207+
/**
208+
* Sets invalid geometry checking behavior.
209+
* \note Invalid geometry checking is not performed when retrieving features
210+
* directly from a QgsVectorDataProvider.
211+
* \see invalidGeometryCheck()
212+
* \since QGIS 3.0
213+
*/
214+
QgsFeatureRequest &setInvalidGeometryCheck( InvalidGeometryCheck check );
215+
216+
/**
217+
* Returns the invalid geometry checking behavior.
218+
* \see setInvalidGeometryCheck()
219+
* \since QGIS 3.0
220+
*/
221+
InvalidGeometryCheck invalidGeometryCheck() const;
222+
199223
/** Set the filter expression. {@see QgsExpression}
200224
* @param expression expression string
201225
* @see filterExpression

‎src/core/qgsfeaturerequest.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ QgsFeatureRequest &QgsFeatureRequest::operator=( const QgsFeatureRequest &rh )
7676
{
7777
mFilterExpression.reset( nullptr );
7878
}
79+
mInvalidGeometryFilter = rh.mInvalidGeometryFilter;
7980
mExpressionContext = rh.mExpressionContext;
8081
mAttrs = rh.mAttrs;
8182
mSimplifyMethod = rh.mSimplifyMethod;
@@ -104,6 +105,12 @@ QgsFeatureRequest &QgsFeatureRequest::setFilterFids( const QgsFeatureIds &fids )
104105
return *this;
105106
}
106107

108+
QgsFeatureRequest &QgsFeatureRequest::setInvalidGeometryCheck( QgsFeatureRequest::InvalidGeometryCheck check )
109+
{
110+
mInvalidGeometryFilter = check;
111+
return *this;
112+
}
113+
107114
QgsFeatureRequest &QgsFeatureRequest::setFilterExpression( const QString &expression )
108115
{
109116
mFilter = FilterExpression;

‎src/core/qgsfeaturerequest.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ class CORE_EXPORT QgsFeatureRequest
8585
FilterFids //!< Filter using feature IDs
8686
};
8787

88+
//! Handling of features with invalid geometries
89+
enum InvalidGeometryCheck
90+
{
91+
GeometryNoCheck = 0, //!< No invalid geometry checking
92+
GeometrySkipInvalid = 1, //!< Skip any features with invalid geometry. This requires a slow geometry validity check for every feature.
93+
GeometryAbortOnInvalid = 2, //!< Close iterator on encountering any features with invalid geometry. This requires a slow geometry validity check for every feature.
94+
};
95+
8896
/** \ingroup core
8997
* The OrderByClause class represents an order by clause for a QgsFeatureRequest.
9098
*
@@ -270,6 +278,22 @@ class CORE_EXPORT QgsFeatureRequest
270278
//! Get feature IDs that should be fetched.
271279
const QgsFeatureIds &filterFids() const { return mFilterFids; }
272280

281+
/**
282+
* Sets invalid geometry checking behavior.
283+
* \note Invalid geometry checking is not performed when retrieving features
284+
* directly from a QgsVectorDataProvider.
285+
* \see invalidGeometryCheck()
286+
* \since QGIS 3.0
287+
*/
288+
QgsFeatureRequest &setInvalidGeometryCheck( InvalidGeometryCheck check );
289+
290+
/**
291+
* Returns the invalid geometry checking behavior.
292+
* \see setInvalidGeometryCheck()
293+
* \since QGIS 3.0
294+
*/
295+
InvalidGeometryCheck invalidGeometryCheck() const { return mInvalidGeometryFilter; }
296+
273297
/** Set the filter expression. {\see QgsExpression}
274298
* \param expression expression string
275299
* \see filterExpression
@@ -415,6 +439,7 @@ class CORE_EXPORT QgsFeatureRequest
415439
QgsSimplifyMethod mSimplifyMethod;
416440
long mLimit = -1;
417441
OrderBy mOrderBy;
442+
InvalidGeometryCheck mInvalidGeometryFilter = GeometryNoCheck;
418443
};
419444

420445
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags )

‎src/core/qgsvectorlayerfeatureiterator.cpp

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "qgsexpressioncontext.h"
2525
#include "qgsdistancearea.h"
2626
#include "qgsproject.h"
27+
#include "qgsmessagelog.h"
2728

2829
QgsVectorLayerFeatureSource::QgsVectorLayerFeatureSource( const QgsVectorLayer *layer )
2930
{
@@ -241,8 +242,15 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
241242
if ( mFetchedFid )
242243
return false;
243244
bool res = nextFeatureFid( f );
244-
mFetchedFid = true;
245-
return res;
245+
if ( res && checkGeometry( f ) )
246+
{
247+
mFetchedFid = true;
248+
return res;
249+
}
250+
else
251+
{
252+
return false;
253+
}
246254
}
247255

248256
if ( !mRequest.filterRect().isNull() )
@@ -305,6 +313,9 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
305313
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
306314
updateFeatureGeometry( f );
307315

316+
if ( !checkGeometry( f ) )
317+
continue;
318+
308319
return true;
309320
}
310321
// no more provider features
@@ -366,6 +377,9 @@ bool QgsVectorLayerFeatureIterator::fetchNextAddedFeature( QgsFeature &f )
366377
// skip features which are not accepted by the filter
367378
continue;
368379

380+
if ( !checkGeometry( *mFetchAddedFeaturesIt ) )
381+
continue;
382+
369383
useAddedFeature( *mFetchAddedFeaturesIt, f );
370384

371385
return true;
@@ -416,9 +430,12 @@ bool QgsVectorLayerFeatureIterator::fetchNextChangedGeomFeature( QgsFeature &f )
416430

417431
useChangedAttributeFeature( fid, *mFetchChangedGeomIt, f );
418432

419-
// return complete feature
420-
mFetchChangedGeomIt++;
421-
return true;
433+
if ( checkGeometry( f ) )
434+
{
435+
// return complete feature
436+
mFetchChangedGeomIt++;
437+
return true;
438+
}
422439
}
423440

424441
return false; // no more changed geometries
@@ -440,7 +457,7 @@ bool QgsVectorLayerFeatureIterator::fetchNextChangedAttributeFeature( QgsFeature
440457
addVirtualAttributes( f );
441458

442459
mRequest.expressionContext()->setFeature( f );
443-
if ( mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() )
460+
if ( mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() && checkGeometry( f ) )
444461
{
445462
return true;
446463
}
@@ -658,6 +675,39 @@ void QgsVectorLayerFeatureIterator::createOrderedJoinList()
658675
}
659676
}
660677

678+
bool QgsVectorLayerFeatureIterator::checkGeometry( const QgsFeature &feature )
679+
{
680+
if ( !feature.hasGeometry() )
681+
return true;
682+
683+
switch ( mRequest.invalidGeometryCheck() )
684+
{
685+
case QgsFeatureRequest::GeometryNoCheck:
686+
return true;
687+
688+
case QgsFeatureRequest::GeometrySkipInvalid:
689+
{
690+
if ( !feature.geometry().isGeosValid() )
691+
{
692+
QgsMessageLog::logMessage( QObject::tr( "Geometry error: One or more input features have invalid geometry." ), QString(), QgsMessageLog::CRITICAL );
693+
return false;
694+
}
695+
break;
696+
}
697+
698+
case QgsFeatureRequest::GeometryAbortOnInvalid:
699+
if ( !feature.geometry().isGeosValid() )
700+
{
701+
QgsMessageLog::logMessage( QObject::tr( "Geometry error: One or more input features have invalid geometry." ), QString(), QgsMessageLog::CRITICAL);
702+
close();
703+
return false;
704+
}
705+
break;
706+
}
707+
708+
return true;
709+
}
710+
661711
void QgsVectorLayerFeatureIterator::prepareField( int fieldIdx )
662712
{
663713
switch ( mSource->mFields.fieldOrigin( fieldIdx ) )

‎src/core/qgsvectorlayerfeatureiterator.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
221221
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const override;
222222

223223
void createOrderedJoinList();
224+
225+
/**
226+
* Performs any geometry validity checking.
227+
*/
228+
bool checkGeometry( const QgsFeature &feature );
224229
};
225230

226231
#endif // QGSVECTORLAYERFEATUREITERATOR_H

‎tests/src/python/test_qgsfeatureiterator.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@
1616

1717
import os
1818

19-
from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsField, NULL, QgsProject, QgsVectorLayerJoinInfo
19+
from qgis.core import (QgsVectorLayer,
20+
QgsFeatureRequest,
21+
QgsFeature,
22+
QgsField,
23+
NULL,
24+
QgsProject,
25+
QgsVectorLayerJoinInfo,
26+
QgsGeometry)
2027
from qgis.testing import start_app, unittest
2128
from qgis.PyQt.QtCore import QVariant
2229

@@ -273,6 +280,33 @@ def test_JoinUsingFeatureRequestExpression(self):
273280

274281
QgsProject.instance().removeMapLayers([layer.id(), joinLayer.id()])
275282

283+
def test_invalidGeometryFilter(self):
284+
layer = QgsVectorLayer(
285+
"Polygon?field=x:string",
286+
"joinlayer", "memory")
287+
288+
# add some features, one has invalid geometry
289+
pr = layer.dataProvider()
290+
f1 = QgsFeature()
291+
f1.setAttributes(["a"])
292+
f1.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid
293+
f2 = QgsFeature()
294+
f2.setAttributes(["b"])
295+
f2.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 0 1, 1 1, 0 0))')) # invalid
296+
f3 = QgsFeature()
297+
f3.setAttributes(["c"])
298+
f3.setGeometry(QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))')) # valid
299+
self.assertTrue(pr.addFeatures([f1, f2, f3]))
300+
301+
res = [f['x'] for f in
302+
layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryNoCheck))]
303+
self.assertEqual(res, ['a', 'b', 'c'])
304+
res = [f['x'] for f in
305+
layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid))]
306+
self.assertEqual(res, ['a', 'c'])
307+
res = [f['x'] for f in
308+
layer.getFeatures(QgsFeatureRequest().setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid))]
309+
self.assertEqual(res, ['a'])
276310

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

0 commit comments

Comments
 (0)
Please sign in to comment.