Skip to content
Permalink
Browse files

Merge pull request #4693 from nyalldawson/request_crs

Allow specifying a destination CRS in QgsFeatureRequest
  • Loading branch information
nyalldawson committed Jun 9, 2017
2 parents 9f71156 + 482ed3f commit 32ecbcfa217976abb17230cdc0646d31c0c54ea7
Showing with 788 additions and 125 deletions.
  1. +23 −0 python/core/qgsfeatureiterator.sip
  2. +86 −5 python/core/qgsfeaturerequest.sip
  3. +1 −0 python/core/qgsvectorlayerfeatureiterator.sip
  4. +28 −7 src/core/providers/memory/qgsmemoryfeatureiterator.cpp
  5. +3 −0 src/core/providers/memory/qgsmemoryfeatureiterator.h
  6. +44 −1 src/core/qgscachedfeatureiterator.cpp
  7. +4 −0 src/core/qgscachedfeatureiterator.h
  8. +30 −1 src/core/qgsfeatureiterator.cpp
  9. +22 −0 src/core/qgsfeatureiterator.h
  10. +20 −0 src/core/qgsfeaturerequest.cpp
  11. +100 −5 src/core/qgsfeaturerequest.h
  12. +47 −25 src/core/qgsvectorlayerfeatureiterator.cpp
  13. +5 −2 src/core/qgsvectorlayerfeatureiterator.h
  14. +23 −4 src/providers/arcgisrest/qgsafsfeatureiterator.cpp
  15. +3 −0 src/providers/arcgisrest/qgsafsfeatureiterator.h
  16. +24 −6 src/providers/db2/qgsdb2featureiterator.cpp
  17. +5 −0 src/providers/db2/qgsdb2featureiterator.h
  18. +27 −10 src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.cpp
  19. +3 −0 src/providers/delimitedtext/qgsdelimitedtextfeatureiterator.h
  20. +32 −13 src/providers/gpx/qgsgpxfeatureiterator.cpp
  21. +4 −0 src/providers/gpx/qgsgpxfeatureiterator.h
  22. +24 −6 src/providers/mssql/qgsmssqlfeatureiterator.cpp
  23. +5 −0 src/providers/mssql/qgsmssqlfeatureiterator.h
  24. +25 −9 src/providers/ogr/qgsogrfeatureiterator.cpp
  25. +4 −0 src/providers/ogr/qgsogrfeatureiterator.h
  26. +26 −8 src/providers/oracle/qgsoraclefeatureiterator.cpp
  27. +4 −0 src/providers/oracle/qgsoraclefeatureiterator.h
  28. +20 −2 src/providers/postgres/qgspostgresfeatureiterator.cpp
  29. +4 −0 src/providers/postgres/qgspostgresfeatureiterator.h
  30. +28 −11 src/providers/spatialite/qgsspatialitefeatureiterator.cpp
  31. +4 −0 src/providers/spatialite/qgsspatialitefeatureiterator.h
  32. +20 −3 src/providers/virtual/qgsvirtuallayerfeatureiterator.cpp
  33. +3 −0 src/providers/virtual/qgsvirtuallayerfeatureiterator.h
  34. +25 −7 src/providers/wfs/qgswfsfeatureiterator.cpp
  35. +4 −0 src/providers/wfs/qgswfsfeatureiterator.h
  36. +32 −0 tests/src/python/featuresourcetestbase.py
  37. +26 −0 tests/src/python/test_qgsvectorlayer.py
@@ -102,6 +102,29 @@ end of iterating: free the resources / lock
:rtype: bool
%End

void geometryToDestinationCrs( QgsFeature &feature, const QgsCoordinateTransform &transform ) const;
%Docstring
Transforms ``feature``'s geometry according to the specified coordinate ``transform``.
If ``feature`` has no geometry or ``transform`` is invalid then calling this method
has no effect and will be shortcut.
Iterators should call this method before returning features to ensure that any
QgsFeatureRequest.destinationCrs() set on the request is respected.
.. versionadded:: 3.0
%End


QgsRectangle filterRectToSourceCrs( const QgsCoordinateTransform &transform ) const;
%Docstring
Returns a rectangle representing the original request's QgsFeatureRequest.filterRect().
If ``transform`` is a valid coordinate transform, the return rectangle will represent
the requested filterRect() transformed to the source's coordinate reference system.
Iterators should call this method and use the returned rectangle for filtering
features to ensure that any QgsFeatureRequest.destinationCrs() set on the request is respected.
Will throw a QgsCsException if the rect cannot be transformed from the destination CRS.
.. versionadded:: 3.0
:rtype: QgsRectangle
%End




@@ -267,10 +267,16 @@ construct a request with feature ID filter
%Docstring
construct a request with feature ID filter
%End
explicit QgsFeatureRequest( const QgsRectangle &rect );

explicit QgsFeatureRequest( const QgsRectangle &rectangle );
%Docstring
construct a request with rectangle filter
Construct a request with ``rectangle`` bounding box filter.

When a destination CRS is set using setDestinationCrs(), ``rectangle``
is expected to be in the same CRS as the destinationCrs(). Otherwise, ``rectangle``
should use the same CRS as the source layer/provider.
%End

explicit QgsFeatureRequest( const QgsExpression &expr, const QgsExpressionContext &context = QgsExpressionContext() );
%Docstring
construct a request with a filter expression
@@ -288,16 +294,28 @@ copy constructor
:rtype: FilterType
%End

QgsFeatureRequest &setFilterRect( const QgsRectangle &rect );
QgsFeatureRequest &setFilterRect( const QgsRectangle &rectangle );
%Docstring
Set rectangle from which features will be taken. Empty rectangle removes the filter.
Sets the ``rectangle`` from which features will be taken. An empty rectangle removes the filter.

When a destination CRS is set using setDestinationCrs(), ``rectangle``
is expected to be in the same CRS as the destinationCrs(). Otherwise, ``rectangle``
should use the same CRS as the source layer/provider.

.. seealso:: filterRect()
:rtype: QgsFeatureRequest
%End

const QgsRectangle &filterRect() const;
%Docstring
Get the rectangle from which features will be taken. If the returned
Returns the rectangle from which features will be taken. If the returned
rectangle is null, then no filter rectangle is set.

When a destination CRS is set using setDestinationCrs(), the rectangle
will be in the same CRS as the destinationCrs(). Otherwise, the rectangle
will use the same CRS as the source layer/provider.

.. seealso:: setFilterRect()
:rtype: QgsRectangle
%End

@@ -528,6 +546,69 @@ Set a subset of attributes by names that will be fetched
:rtype: QgsSimplifyMethod
%End

QgsCoordinateReferenceSystem destinationCrs() const;
%Docstring
Returns the destination coordinate reference system for feature's geometries,
or an invalid QgsCoordinateReferenceSystem if no reprojection will be done
and all features will be left with their original geometry.
.. seealso:: setDestinationCrs()
.. versionadded:: 3.0
:rtype: QgsCoordinateReferenceSystem
%End

QgsFeatureRequest &setDestinationCrs( const QgsCoordinateReferenceSystem &crs );
%Docstring
Sets the destination ``crs`` for feature's geometries. If set, all
geometries will be reprojected from their original coordinate reference
system to this desired reference system. If ``crs`` is an invalid
QgsCoordinateReferenceSystem then no reprojection will be done
and all features will be left with their original geometry.

When a ``crs`` is set using setDestinationCrs(), then any filterRect()
set on the request is expected to be in the same CRS as the destination
CRS.

The feature geometry transformation to the destination CRS is performed
after all filter expressions are tested and any virtual fields are
calculated. Accordingly, any geometric expressions used in
filterExpression() will be performed in the original
source CRS. This ensures consistent results are returned regardless of the
destination CRS. Similarly, virtual field values will be calculated using the
original geometry in the source CRS, so these values are not affected by
any destination CRS transform present in the feature request.

.. seealso:: destinationCrs()
.. versionadded:: 3.0
:rtype: QgsFeatureRequest
%End

QgsFeatureRequest &setTransformErrorCallback( SIP_PYCALLABLE / AllowNone / );
%Docstring
Sets a callback function to use when encountering a transform error when iterating
features and a destinationCrs() is set. This function will be
called using the feature which encountered the transform error as a parameter.
.. versionadded:: 3.0
.. seealso:: transformErrorCallback()
.. seealso:: setDestinationCrs()
:rtype: QgsFeatureRequest
%End
%MethodCode
Py_BEGIN_ALLOW_THREADS

sipCpp->setTransformErrorCallback( [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



bool acceptFeature( const QgsFeature &feature );
%Docstring
Check if a feature is accepted by this requests filter
@@ -141,6 +141,7 @@ Setup the simplification of geometries to fetch using the specified simplify met




private:
QgsVectorLayerFeatureIterator( const QgsVectorLayerFeatureIterator &rhs );
};
@@ -21,31 +21,47 @@
#include "qgsspatialindex.h"
#include "qgsmessagelog.h"
#include "qgsproject.h"
#include "qgscsexception.h"

///@cond PRIVATE

QgsMemoryFeatureIterator::QgsMemoryFeatureIterator( QgsMemoryFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
: QgsAbstractFeatureIteratorFromSource<QgsMemoryFeatureSource>( source, ownSource, request )
{
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
{
mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs() );
}
try
{
mFilterRect = filterRectToSourceCrs( mTransform );
}
catch ( QgsCsException & )
{
// can't reproject mFilterRect
mClosed = true;
return;
}

if ( !mSource->mSubsetString.isEmpty() )
{
mSubsetExpression = new QgsExpression( mSource->mSubsetString );
mSubsetExpression->prepare( &mSource->mExpressionContext );
}

if ( !mRequest.filterRect().isNull() && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
if ( !mFilterRect.isNull() && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
{
mSelectRectGeom = QgsGeometry::fromRect( request.filterRect() );
mSelectRectGeom = QgsGeometry::fromRect( mFilterRect );
mSelectRectEngine.reset( QgsGeometry::createGeometryEngine( mSelectRectGeom.geometry() ) );
mSelectRectEngine->prepareGeometry();
}

// if there's spatial index, use it!
// (but don't use it when selection rect is not specified)
if ( !mRequest.filterRect().isNull() && mSource->mSpatialIndex )
if ( !mFilterRect.isNull() && mSource->mSpatialIndex )
{
mUsingFeatureIdList = true;
mFeatureIdList = mSource->mSpatialIndex->intersects( mRequest.filterRect() );
mFeatureIdList = mSource->mSpatialIndex->intersects( mFilterRect );
QgsDebugMsg( "Features returned by spatial index: " + QString::number( mFeatureIdList.count() ) );
}
else if ( mRequest.filterType() == QgsFeatureRequest::FilterFid )
@@ -92,7 +108,7 @@ bool QgsMemoryFeatureIterator::nextFeatureUsingList( QgsFeature &feature )
// option 1: we have a list of features to traverse
while ( mFeatureIdListIterator != mFeatureIdList.constEnd() )
{
if ( !mRequest.filterRect().isNull() && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
if ( !mFilterRect.isNull() && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
{
// do exact check in case we're doing intersection
if ( mSource->mFeatures.value( *mFeatureIdListIterator ).hasGeometry() && mSelectRectEngine->intersects( *mSource->mFeatures.value( *mFeatureIdListIterator ).geometry().geometry() ) )
@@ -124,7 +140,10 @@ bool QgsMemoryFeatureIterator::nextFeatureUsingList( QgsFeature &feature )
close();

if ( hasFeature )
{
feature.setFields( mSource->mFields ); // allow name-based attribute lookups
geometryToDestinationCrs( feature, mTransform );
}

return hasFeature;
}
@@ -137,7 +156,7 @@ bool QgsMemoryFeatureIterator::nextFeatureTraverseAll( QgsFeature &feature )
// option 2: traversing the whole layer
while ( mSelectIterator != mSource->mFeatures.constEnd() )
{
if ( mRequest.filterRect().isNull() )
if ( mFilterRect.isNull() )
{
// selection rect empty => using all features
hasFeature = true;
@@ -153,7 +172,7 @@ bool QgsMemoryFeatureIterator::nextFeatureTraverseAll( QgsFeature &feature )
else
{
// check just bounding box against rect when not using intersection
if ( mSelectIterator->hasGeometry() && mSelectIterator->geometry().boundingBox().intersects( mRequest.filterRect() ) )
if ( mSelectIterator->hasGeometry() && mSelectIterator->geometry().boundingBox().intersects( mFilterRect ) )
hasFeature = true;
}
}
@@ -178,6 +197,7 @@ bool QgsMemoryFeatureIterator::nextFeatureTraverseAll( QgsFeature &feature )
++mSelectIterator;
feature.setValid( true );
feature.setFields( mSource->mFields ); // allow name-based attribute lookups
geometryToDestinationCrs( feature, mTransform );
}
else
close();
@@ -216,6 +236,7 @@ QgsMemoryFeatureSource::QgsMemoryFeatureSource( const QgsMemoryProvider *p )
, mFeatures( p->mFeatures )
, mSpatialIndex( p->mSpatialIndex ? new QgsSpatialIndex( *p->mSpatialIndex ) : nullptr ) // just shallow copy
, mSubsetString( p->mSubsetString )
, mCrs( p->mCrs )
{
mExpressionContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() );
@@ -42,6 +42,7 @@ class QgsMemoryFeatureSource : public QgsAbstractFeatureSource
std::unique_ptr< QgsSpatialIndex > mSpatialIndex;
QString mSubsetString;
QgsExpressionContext mExpressionContext;
QgsCoordinateReferenceSystem mCrs;

friend class QgsMemoryFeatureIterator;
};
@@ -67,11 +68,13 @@ class QgsMemoryFeatureIterator : public QgsAbstractFeatureIteratorFromSource<Qgs

QgsGeometry mSelectRectGeom;
std::unique_ptr< QgsGeometryEngine > mSelectRectEngine;
QgsRectangle mFilterRect;
QgsFeatureMap::const_iterator mSelectIterator;
bool mUsingFeatureIdList = false;
QList<QgsFeatureId> mFeatureIdList;
QList<QgsFeatureId>::const_iterator mFeatureIdListIterator;
QgsExpression *mSubsetExpression = nullptr;
QgsCoordinateTransform mTransform;

};

@@ -15,11 +15,32 @@

#include "qgscachedfeatureiterator.h"
#include "qgsvectorlayercache.h"
#include "qgscsexception.h"

QgsCachedFeatureIterator::QgsCachedFeatureIterator( QgsVectorLayerCache *vlCache, const QgsFeatureRequest &featureRequest )
: QgsAbstractFeatureIterator( featureRequest )
, mVectorLayerCache( vlCache )
{
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mVectorLayerCache->sourceCrs() )
{
mTransform = QgsCoordinateTransform( mVectorLayerCache->sourceCrs(), mRequest.destinationCrs() );
}
try
{
mFilterRect = filterRectToSourceCrs( mTransform );
}
catch ( QgsCsException & )
{
// can't reproject mFilterRect
mClosed = true;
return;
}
if ( !mFilterRect.isNull() )
{
// update request to be the unprojected filter rect
mRequest.setFilterRect( mFilterRect );
}

switch ( featureRequest.filterType() )
{
case QgsFeatureRequest::FilterFids:
@@ -61,6 +82,7 @@ bool QgsCachedFeatureIterator::fetchFeature( QgsFeature &f )
if ( mRequest.acceptFeature( f ) )
{
f.setValid( true );
geometryToDestinationCrs( f, mTransform );
return true;
}
}
@@ -85,7 +107,27 @@ QgsCachedFeatureWriterIterator::QgsCachedFeatureWriterIterator( QgsVectorLayerCa
: QgsAbstractFeatureIterator( featureRequest )
, mVectorLayerCache( vlCache )
{
mFeatIt = vlCache->layer()->getFeatures( featureRequest );
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mVectorLayerCache->sourceCrs() )
{
mTransform = QgsCoordinateTransform( mVectorLayerCache->sourceCrs(), mRequest.destinationCrs() );
}
try
{
mFilterRect = filterRectToSourceCrs( mTransform );
}
catch ( QgsCsException & )
{
// can't reproject mFilterRect
mClosed = true;
return;
}
if ( !mFilterRect.isNull() )
{
// update request to be the unprojected filter rect
mRequest.setFilterRect( mFilterRect );
}

mFeatIt = vlCache->layer()->getFeatures( mRequest );
}

bool QgsCachedFeatureWriterIterator::fetchFeature( QgsFeature &f )
@@ -100,6 +142,7 @@ bool QgsCachedFeatureWriterIterator::fetchFeature( QgsFeature &f )
// As long as features can be fetched from the provider: Write them to cache
mVectorLayerCache->cacheFeature( f );
mFids.insert( f.id() );
geometryToDestinationCrs( f, mTransform );
return true;
}
else
@@ -78,6 +78,8 @@ class CORE_EXPORT QgsCachedFeatureIterator : public QgsAbstractFeatureIterator
QgsFeatureIds mFeatureIds;
QgsVectorLayerCache *mVectorLayerCache = nullptr;
QgsFeatureIds::ConstIterator mFeatureIdIterator;
QgsCoordinateTransform mTransform;
QgsRectangle mFilterRect;
};

/** \ingroup core
@@ -127,5 +129,7 @@ class CORE_EXPORT QgsCachedFeatureWriterIterator : public QgsAbstractFeatureIter
QgsFeatureIterator mFeatIt;
QgsVectorLayerCache *mVectorLayerCache = nullptr;
QgsFeatureIds mFids;
QgsCoordinateTransform mTransform;
QgsRectangle mFilterRect;
};
#endif // QGSCACHEDFEATUREITERATOR_H

0 comments on commit 32ecbcf

Please sign in to comment.
You can’t perform that action at this time.