Skip to content
Permalink
Browse files
Merge pull request #4402 from nyalldawson/iterator_invalid_geom
Add invalid geometry handling to QgsFeatureRequest/QgsVectorLayerFeatureIterator
  • Loading branch information
nyalldawson committed Apr 25, 2017
2 parents 3fa1162 + d27cbe7 commit 997b6304c800253114031f1bb4cc4d9ea9c49f7d
@@ -26,6 +26,14 @@ class QgsFeatureRequest
FilterFids //!< Filter using feature IDs
};

//! Handling of features with invalid geometries
enum InvalidGeometryCheck
{
GeometryNoCheck,
GeometrySkipInvalid,
GeometryAbortOnInvalid,
};

/**
* The OrderByClause class represents an order by clause for a QgsFeatureRequest.
*
@@ -196,6 +204,53 @@ class QgsFeatureRequest
//! Get feature IDs that should be fetched.
const QgsFeatureIds& filterFids() const;

/**
* Sets invalid geometry checking behavior.
* \note Invalid geometry checking is not performed when retrieving features
* directly from a QgsVectorDataProvider.
* \see invalidGeometryCheck()
* \since QGIS 3.0
*/
QgsFeatureRequest &setInvalidGeometryCheck( InvalidGeometryCheck check );

/**
* Returns the invalid geometry checking behavior.
* \see setInvalidGeometryCheck()
* \since QGIS 3.0
*/
InvalidGeometryCheck invalidGeometryCheck() const;

/**
* Sets a callback function to use when encountering an invalid geometry and
* invalidGeometryCheck() is set to GeometryAbortOnInvalid. This function will be
* called using the feature with invalid geometry as a parameter.
* \since QGIS 3.0
* \see invalidGeometryCallback()
*/
QgsFeatureRequest &setInvalidGeometryCallback( SIP_PYCALLABLE /AllowNone/ );
%MethodCode
Py_BEGIN_ALLOW_THREADS

sipCpp->setInvalidGeometryCallback([a0](const QgsFeature &arg) {
SIP_BLOCK_THREADS
Py_XDECREF( sipCallMethod(NULL, a0, "D", &arg, sipType_QgsFeature, NULL) );
SIP_UNBLOCK_THREADS
});

sipRes = sipCpp;

Py_END_ALLOW_THREADS
%End

/**
* Returns the callback function to use when encountering an invalid geometry and
* invalidGeometryCheck() is set to GeometryAbortOnInvalid.
* \since QGIS 3.0
* \note not available in Python bindings
* \see setInvalidGeometryCallback()
*/
// std::function< void( const QgsFeature & ) > invalidGeometryCallback() const;

/** Set the filter expression. {@see QgsExpression}
* @param expression expression string
* @see filterExpression
@@ -99,34 +99,20 @@ class Features(object):
def __init__(self, layer, request):
self.layer = layer
self.selection = False

invalidFeaturesMethod = ProcessingConfig.getSetting(ProcessingConfig.FILTER_INVALID_GEOMETRIES)
if invalidFeaturesMethod == self.IGNORE:
request.setInvalidGeometryCheck(QgsFeatureRequest.GeometrySkipInvalid)
elif invalidFeaturesMethod == self.RAISE_EXCEPTION:
request.setInvalidGeometryCheck(QgsFeatureRequest.GeometryAbortOnInvalid)

if ProcessingConfig.getSetting(ProcessingConfig.USE_SELECTED)\
and layer.selectedFeatureCount() > 0:
self.iter = layer.selectedFeaturesIterator(request)
self.selection = True
else:
self.iter = layer.getFeatures(request)

invalidFeaturesMethod = ProcessingConfig.getSetting(ProcessingConfig.FILTER_INVALID_GEOMETRIES)

def filterFeature(f, ignoreInvalid):
geom = f.geometry()
if geom is None:
ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
self.tr('Feature with NULL geometry found.'))
elif not geom.isGeosValid():
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'))
if ignoreInvalid:
return False
else:
raise GeoAlgorithmExecutionException(self.tr('Features with invalid geometries found. Please fix these geometries or specify the "Ignore invalid input features" flag'))
return True

if invalidFeaturesMethod == self.IGNORE:
self.iter = filter(lambda x: filterFeature(x, True), self.iter)
elif invalidFeaturesMethod == self.RAISE_EXCEPTION:
self.iter = filter(lambda x: filterFeature(x, False), self.iter)

def __iter__(self):
return self.iter

@@ -76,6 +76,8 @@ QgsFeatureRequest &QgsFeatureRequest::operator=( const QgsFeatureRequest &rh )
{
mFilterExpression.reset( nullptr );
}
mInvalidGeometryFilter = rh.mInvalidGeometryFilter;
mInvalidGeometryCallback = rh.mInvalidGeometryCallback;
mExpressionContext = rh.mExpressionContext;
mAttrs = rh.mAttrs;
mSimplifyMethod = rh.mSimplifyMethod;
@@ -104,6 +106,18 @@ QgsFeatureRequest &QgsFeatureRequest::setFilterFids( const QgsFeatureIds &fids )
return *this;
}

QgsFeatureRequest &QgsFeatureRequest::setInvalidGeometryCheck( QgsFeatureRequest::InvalidGeometryCheck check )
{
mInvalidGeometryFilter = check;
return *this;
}

QgsFeatureRequest &QgsFeatureRequest::setInvalidGeometryCallback( std::function<void ( const QgsFeature & )> callback )
{
mInvalidGeometryCallback = callback;
return *this;
}

QgsFeatureRequest &QgsFeatureRequest::setFilterExpression( const QString &expression )
{
mFilter = FilterExpression;
@@ -85,6 +85,14 @@ class CORE_EXPORT QgsFeatureRequest
FilterFids //!< Filter using feature IDs
};

//! Handling of features with invalid geometries
enum InvalidGeometryCheck
{
GeometryNoCheck = 0, //!< No invalid geometry checking
GeometrySkipInvalid = 1, //!< Skip any features with invalid geometry. This requires a slow geometry validity check for every feature.
GeometryAbortOnInvalid = 2, //!< Close iterator on encountering any features with invalid geometry. This requires a slow geometry validity check for every feature.
};

/** \ingroup core
* The OrderByClause class represents an order by clause for a QgsFeatureRequest.
*
@@ -270,6 +278,40 @@ class CORE_EXPORT QgsFeatureRequest
//! Get feature IDs that should be fetched.
const QgsFeatureIds &filterFids() const { return mFilterFids; }

/**
* Sets invalid geometry checking behavior.
* \note Invalid geometry checking is not performed when retrieving features
* directly from a QgsVectorDataProvider.
* \see invalidGeometryCheck()
* \since QGIS 3.0
*/
QgsFeatureRequest &setInvalidGeometryCheck( InvalidGeometryCheck check );

/**
* Returns the invalid geometry checking behavior.
* \see setInvalidGeometryCheck()
* \since QGIS 3.0
*/
InvalidGeometryCheck invalidGeometryCheck() const { return mInvalidGeometryFilter; }

/**
* Sets a callback function to use when encountering an invalid geometry and
* invalidGeometryCheck() is set to GeometryAbortOnInvalid. This function will be
* called using the feature with invalid geometry as a parameter.
* \since QGIS 3.0
* \see invalidGeometryCallback()
*/
QgsFeatureRequest &setInvalidGeometryCallback( std::function< void( const QgsFeature & ) > callback );

/**
* Returns the callback function to use when encountering an invalid geometry and
* invalidGeometryCheck() is set to GeometryAbortOnInvalid.
* \since QGIS 3.0
* \note not available in Python bindings
* \see setInvalidGeometryCallback()
*/
std::function< void( const QgsFeature & ) > invalidGeometryCallback() const { return mInvalidGeometryCallback; }

/** Set the filter expression. {\see QgsExpression}
* \param expression expression string
* \see filterExpression
@@ -415,6 +457,8 @@ class CORE_EXPORT QgsFeatureRequest
QgsSimplifyMethod mSimplifyMethod;
long mLimit = -1;
OrderBy mOrderBy;
InvalidGeometryCheck mInvalidGeometryFilter = GeometryNoCheck;
std::function< void( const QgsFeature & ) > mInvalidGeometryCallback;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags )
@@ -24,6 +24,7 @@
#include "qgsexpressioncontext.h"
#include "qgsdistancearea.h"
#include "qgsproject.h"
#include "qgsmessagelog.h"

QgsVectorLayerFeatureSource::QgsVectorLayerFeatureSource( const QgsVectorLayer *layer )
{
@@ -241,8 +242,15 @@ bool QgsVectorLayerFeatureIterator::fetchFeature( QgsFeature &f )
if ( mFetchedFid )
return false;
bool res = nextFeatureFid( f );
mFetchedFid = true;
return res;
if ( res && testFeature( f ) )
{
mFetchedFid = true;
return res;
}
else
{
return false;
}
}

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

if ( !testFeature( f ) )
continue;

return true;
}
// no more provider features
@@ -366,6 +377,9 @@ bool QgsVectorLayerFeatureIterator::fetchNextAddedFeature( QgsFeature &f )
// skip features which are not accepted by the filter
continue;

if ( !testFeature( *mFetchAddedFeaturesIt ) )
continue;

useAddedFeature( *mFetchAddedFeaturesIt, f );

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

useChangedAttributeFeature( fid, *mFetchChangedGeomIt, f );

// return complete feature
mFetchChangedGeomIt++;
return true;
if ( testFeature( f ) )
{
// return complete feature
mFetchChangedGeomIt++;
return true;
}
}

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

mRequest.expressionContext()->setFeature( f );
if ( mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() )
if ( mRequest.filterExpression()->evaluate( mRequest.expressionContext() ).toBool() && testFeature( f ) )
{
return true;
}
@@ -658,6 +675,49 @@ void QgsVectorLayerFeatureIterator::createOrderedJoinList()
}
}

bool QgsVectorLayerFeatureIterator::testFeature( const QgsFeature &feature )
{
bool result = checkGeometryValidity( feature );
return result;
}

bool QgsVectorLayerFeatureIterator::checkGeometryValidity( const QgsFeature &feature )
{
if ( !feature.hasGeometry() )
return true;

switch ( mRequest.invalidGeometryCheck() )
{
case QgsFeatureRequest::GeometryNoCheck:
return true;

case QgsFeatureRequest::GeometrySkipInvalid:
{
if ( !feature.geometry().isGeosValid() )
{
QgsMessageLog::logMessage( QObject::tr( "Geometry error: One or more input features have invalid geometry." ), QString(), QgsMessageLog::CRITICAL );
return false;
}
break;
}

case QgsFeatureRequest::GeometryAbortOnInvalid:
if ( !feature.geometry().isGeosValid() )
{
QgsMessageLog::logMessage( QObject::tr( "Geometry error: One or more input features have invalid geometry." ), QString(), QgsMessageLog::CRITICAL );
close();
if ( mRequest.invalidGeometryCallback() )
{
mRequest.invalidGeometryCallback()( feature );
}
return false;
}
break;
}

return true;
}

void QgsVectorLayerFeatureIterator::prepareField( int fieldIdx )
{
switch ( mSource->mFields.fieldOrigin( fieldIdx ) )
@@ -221,6 +221,16 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const override;

void createOrderedJoinList();

/**
* Performs any feature based validity checking, e.g. checking for geometry validity.
*/
bool testFeature( const QgsFeature &feature );

/**
* Checks a feature's geometry for validity, if requested in feature request.
*/
bool checkGeometryValidity( const QgsFeature &feature );
};

#endif // QGSVECTORLAYERFEATUREITERATOR_H
Loading

0 comments on commit 997b630

Please sign in to comment.