Skip to content

Commit

Permalink
Merge pull request #4693 from nyalldawson/request_crs
Browse files Browse the repository at this point in the history
Allow specifying a destination CRS in QgsFeatureRequest
  • Loading branch information
nyalldawson committed Jun 9, 2017
2 parents 9f71156 + 482ed3f commit 32ecbcf
Show file tree
Hide file tree
Showing 37 changed files with 788 additions and 125 deletions.
23 changes: 23 additions & 0 deletions python/core/qgsfeatureiterator.sip
Expand Up @@ -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




Expand Down
91 changes: 86 additions & 5 deletions python/core/qgsfeaturerequest.sip
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions python/core/qgsvectorlayerfeatureiterator.sip
Expand Up @@ -141,6 +141,7 @@ Setup the simplification of geometries to fetch using the specified simplify met




private:
QgsVectorLayerFeatureIterator( const QgsVectorLayerFeatureIterator &rhs );
};
Expand Down
35 changes: 28 additions & 7 deletions src/core/providers/memory/qgsmemoryfeatureiterator.cpp
Expand Up @@ -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 )
Expand Down Expand Up @@ -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() ) )
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -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();
Expand Down Expand Up @@ -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() );
Expand Down
3 changes: 3 additions & 0 deletions src/core/providers/memory/qgsmemoryfeatureiterator.h
Expand Up @@ -42,6 +42,7 @@ class QgsMemoryFeatureSource : public QgsAbstractFeatureSource
std::unique_ptr< QgsSpatialIndex > mSpatialIndex;
QString mSubsetString;
QgsExpressionContext mExpressionContext;
QgsCoordinateReferenceSystem mCrs;

friend class QgsMemoryFeatureIterator;
};
Expand All @@ -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;

};

Expand Down
45 changes: 44 additions & 1 deletion src/core/qgscachedfeatureiterator.cpp
Expand Up @@ -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:
Expand Down Expand Up @@ -61,6 +82,7 @@ bool QgsCachedFeatureIterator::fetchFeature( QgsFeature &f )
if ( mRequest.acceptFeature( f ) )
{
f.setValid( true );
geometryToDestinationCrs( f, mTransform );
return true;
}
}
Expand All @@ -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 )
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/core/qgscachedfeatureiterator.h
Expand Up @@ -78,6 +78,8 @@ class CORE_EXPORT QgsCachedFeatureIterator : public QgsAbstractFeatureIterator
QgsFeatureIds mFeatureIds;
QgsVectorLayerCache *mVectorLayerCache = nullptr;
QgsFeatureIds::ConstIterator mFeatureIdIterator;
QgsCoordinateTransform mTransform;
QgsRectangle mFilterRect;
};

/** \ingroup core
Expand Down Expand Up @@ -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.