Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove QgsFeatureRequest::FilterRect #4283

Merged
merged 2 commits into from Mar 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/api_break.dox
Expand Up @@ -1049,6 +1049,9 @@ QgsFeatureRequest {#qgis_api_break_3_0_QgsFeatureRequest}
-----------------

- AllAttributes was renamed to ALL_ATTRIBUTES
- FilterRect was removed. This enum value was unused, and QgsFeatureRequest.filterRect() should be used
instead. Check for a null rectangle returned by filterRect() to determine whether a filter rect
is in place.

QgsFieldCombobox {#qgis_api_break_3_0_QgsFieldCombobox}
----------------
Expand Down
1 change: 0 additions & 1 deletion python/core/qgsfeaturerequest.sip
Expand Up @@ -21,7 +21,6 @@ class QgsFeatureRequest
enum FilterType
{
FilterNone, //!< No filter is applied
FilterRect, //!< Obsolete, will be ignored. If a filterRect is set it will be used anyway. Filter using a rectangle, no need to set NoGeometry. Instead check for request.filterRect().isNull()
FilterFid, //!< Filter using feature ID
FilterExpression, //!< Filter using expression
FilterFids //!< Filter using feature IDs
Expand Down
1 change: 0 additions & 1 deletion src/core/qgscacheindexfeatureid.cpp
Expand Up @@ -63,7 +63,6 @@ bool QgsCacheIndexFeatureId::getCacheIterator( QgsFeatureIterator &featureIterat
break;
}
case QgsFeatureRequest::FilterNone:
case QgsFeatureRequest::FilterRect:
case QgsFeatureRequest::FilterExpression:
{
if ( C->hasFullCache() )
Expand Down
42 changes: 11 additions & 31 deletions src/core/qgsfeaturerequest.cpp
Expand Up @@ -23,51 +23,36 @@
const QString QgsFeatureRequest::ALL_ATTRIBUTES = QStringLiteral( "#!allattributes!#" );

QgsFeatureRequest::QgsFeatureRequest()
: mFilter( FilterNone )
, mFilterFid( -1 )
, mFilterExpression( nullptr )
, mFlags( nullptr )
, mLimit( -1 )
: mFlags( nullptr )
{
}

QgsFeatureRequest::QgsFeatureRequest( QgsFeatureId fid )
: mFilter( FilterFid )
, mFilterFid( fid )
, mFilterExpression( nullptr )
, mFlags( nullptr )
, mLimit( -1 )
{
}

QgsFeatureRequest::QgsFeatureRequest( const QgsFeatureIds &fids )
: mFilter( FilterFids )
, mFilterFid( -1 )
, mFilterFids( fids )
, mFilterExpression( nullptr )
, mFlags( nullptr )
, mLimit( -1 )
{

}

QgsFeatureRequest::QgsFeatureRequest( const QgsRectangle &rect )
: mFilter( FilterRect )
, mFilterRect( rect )
, mFilterFid( -1 )
, mFilterExpression( nullptr )
: mFilterRect( rect )
, mFlags( nullptr )
, mLimit( -1 )
{
}

QgsFeatureRequest::QgsFeatureRequest( const QgsExpression &expr, const QgsExpressionContext &context )
: mFilter( FilterExpression )
, mFilterFid( -1 )
, mFilterExpression( new QgsExpression( expr ) )
, mExpressionContext( context )
, mFlags( nullptr )
, mLimit( -1 )
{
}

Expand All @@ -85,11 +70,11 @@ QgsFeatureRequest &QgsFeatureRequest::operator=( const QgsFeatureRequest &rh )
mFilterFids = rh.mFilterFids;
if ( rh.mFilterExpression )
{
mFilterExpression = new QgsExpression( *rh.mFilterExpression );
mFilterExpression.reset( new QgsExpression( *rh.mFilterExpression ) );
}
else
{
mFilterExpression = nullptr;
mFilterExpression.reset( nullptr );
}
mExpressionContext = rh.mExpressionContext;
mAttrs = rh.mAttrs;
Expand All @@ -99,15 +84,8 @@ QgsFeatureRequest &QgsFeatureRequest::operator=( const QgsFeatureRequest &rh )
return *this;
}

QgsFeatureRequest::~QgsFeatureRequest()
{
delete mFilterExpression;
}

QgsFeatureRequest &QgsFeatureRequest::setFilterRect( const QgsRectangle &rect )
{
if ( mFilter == FilterNone )
mFilter = FilterRect;
mFilterRect = rect;
return *this;
}
Expand All @@ -129,8 +107,7 @@ QgsFeatureRequest &QgsFeatureRequest::setFilterFids( const QgsFeatureIds &fids )
QgsFeatureRequest &QgsFeatureRequest::setFilterExpression( const QString &expression )
{
mFilter = FilterExpression;
delete mFilterExpression;
mFilterExpression = new QgsExpression( expression );
mFilterExpression.reset( new QgsExpression( expression ) );
return *this;
}

Expand Down Expand Up @@ -245,14 +222,17 @@ QgsFeatureRequest &QgsFeatureRequest::setSimplifyMethod( const QgsSimplifyMethod

bool QgsFeatureRequest::acceptFeature( const QgsFeature &feature )
{
if ( !mFilterRect.isNull() )
{
if ( !feature.hasGeometry() || !feature.geometry().intersects( mFilterRect ) )
return false;
}

switch ( mFilter )
{
case QgsFeatureRequest::FilterNone:
return true;

case QgsFeatureRequest::FilterRect:
return ( feature.hasGeometry() && feature.geometry().intersects( mFilterRect ) );

case QgsFeatureRequest::FilterFid:
return ( feature.id() == mFilterFid );

Expand Down
22 changes: 9 additions & 13 deletions src/core/qgsfeaturerequest.h
Expand Up @@ -18,6 +18,7 @@
#include "qgis_core.h"
#include <QFlags>
#include <QList>
#include <memory>

#include "qgsfeature.h"
#include "qgsrectangle.h"
Expand Down Expand Up @@ -79,7 +80,6 @@ class CORE_EXPORT QgsFeatureRequest
enum FilterType
{
FilterNone, //!< No filter is applied
FilterRect, //!< Obsolete, will be ignored. If a filterRect is set it will be used anyway. Filter using a rectangle, no need to set NoGeometry. Instead check for request.filterRect().isNull()
FilterFid, //!< Filter using feature ID
FilterExpression, //!< Filter using expression
FilterFids //!< Filter using feature IDs
Expand Down Expand Up @@ -242,22 +242,21 @@ class CORE_EXPORT QgsFeatureRequest
//! Assignment operator
QgsFeatureRequest &operator=( const QgsFeatureRequest &rh );

~QgsFeatureRequest();

/**
* Return the filter type which is currently set on this request
*
* @return Filter type
*/
FilterType filterType() const { if ( mFilter == FilterNone && !mFilterRect.isNull() ) return FilterRect; else return mFilter; }
FilterType filterType() const { return mFilter; }

/**
* Set rectangle from which features will be taken. Empty rectangle removes the filter.
*/
QgsFeatureRequest &setFilterRect( const QgsRectangle &rect );

/**
* Get the rectangle from which features will be taken.
* Get the rectangle from which features will be taken. If the returned
* rectangle is null, then no filter rectangle is set.
*/
const QgsRectangle &filterRect() const { return mFilterRect; }

Expand All @@ -282,7 +281,7 @@ class CORE_EXPORT QgsFeatureRequest
* @see setFilterExpression
* @see expressionContext
*/
QgsExpression *filterExpression() const { return mFilterExpression; }
QgsExpression *filterExpression() const { return mFilterExpression.get(); }

/** Modifies the existing filter expression to add an additional expression filter. The
* filter expressions are combined using AND, so only features matching both
Expand Down Expand Up @@ -404,20 +403,17 @@ class CORE_EXPORT QgsFeatureRequest
*/
bool acceptFeature( const QgsFeature &feature );

// TODO: in future
// void setFilterNativeExpression(con QString& expr); // using provider's SQL (if supported)

protected:
FilterType mFilter;
FilterType mFilter = FilterNone;
QgsRectangle mFilterRect;
QgsFeatureId mFilterFid;
QgsFeatureId mFilterFid = -1;
QgsFeatureIds mFilterFids;
QgsExpression *mFilterExpression = nullptr;
std::unique_ptr< QgsExpression > mFilterExpression;
QgsExpressionContext mExpressionContext;
Flags mFlags;
QgsAttributeList mAttrs;
QgsSimplifyMethod mSimplifyMethod;
long mLimit;
long mLimit = -1;
OrderBy mOrderBy;
};

Expand Down
1 change: 0 additions & 1 deletion src/core/qgsvectorlayercache.cpp
Expand Up @@ -310,7 +310,6 @@ bool QgsVectorLayerCache::canUseCacheForRequest( const QgsFeatureRequest &featur
break;
}
case QgsFeatureRequest::FilterNone:
case QgsFeatureRequest::FilterRect:
case QgsFeatureRequest::FilterExpression:
{
if ( mFullCache )
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsvectorlayerfeatureiterator.cpp
Expand Up @@ -191,7 +191,7 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
providerLimit += mSource->mChangedAttributeValues.size();
}

if ( mProviderRequest.filterType() == QgsFeatureRequest::FilterExpression || mProviderRequest.filterType() == QgsFeatureRequest::FilterRect )
if ( mProviderRequest.filterType() == QgsFeatureRequest::FilterExpression || !mProviderRequest.filterRect().isNull() )
{
// geometry changes may mean some features no longer match expression or rect, so increase limit sent to provider
providerLimit += mSource->mChangedGeometries.size();
Expand Down
2 changes: 1 addition & 1 deletion src/providers/arcgisrest/qgsafsfeatureiterator.cpp
Expand Up @@ -73,7 +73,7 @@ bool QgsAfsFeatureIterator::fetchFeature( QgsFeature &f )
else
{
QgsRectangle filterRect = mSource->provider()->extent();
if ( mRequest.filterType() == QgsFeatureRequest::FilterRect )
if ( !mRequest.filterRect().isNull() )
filterRect = filterRect.intersect( &mRequest.filterRect() );
while ( mFeatureIterator < mSource->provider()->featureCount() )
{
Expand Down
3 changes: 0 additions & 3 deletions src/providers/oracle/qgsoraclefeatureiterator.cpp
Expand Up @@ -142,9 +142,6 @@ QgsOracleFeatureIterator::QgsOracleFeatureIterator( QgsOracleFeatureSource *sour
//handled below
break;

case QgsFeatureRequest::FilterRect:
// Handled in the if-statement above
break;
}

if ( mSource->mRequestedGeomType != QgsWkbTypes::Unknown && mSource->mRequestedGeomType != mSource->mDetectedGeomType )
Expand Down
32 changes: 32 additions & 0 deletions tests/src/python/providertestbase.py
Expand Up @@ -141,6 +141,11 @@ def assert_query(self, provider, expression, expected):
result = set([f['pk'] for f in provider.getFeatures(QgsFeatureRequest().setFilterExpression(expression).setSubsetOfAttributes([0]))])
assert set(expected) == result, 'Expected {} and got {} when testing expression "{}" using empty attribute subset'.format(set(expected), result, expression)

# test that results match QgsFeatureRequest.acceptFeature
request = QgsFeatureRequest().setFilterExpression(expression)
for f in provider.getFeatures():
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)

def runGetFeatureTests(self, provider):
assert len([f for f in provider.getFeatures()]) == 5
self.assert_query(provider, 'name ILIKE \'QGIS\'', [])
Expand Down Expand Up @@ -485,6 +490,11 @@ def testGetFeaturesFidTests(self):
expected = [id]
assert result == expected, 'Expected {} and got {} when testing for feature ID filter'.format(expected, result)

# test that results match QgsFeatureRequest.acceptFeature
request = QgsFeatureRequest().setFilterFid(id)
for f in self.provider.getFeatures():
self.assertEqual(request.acceptFeature(f), f.id() == id)

# bad features
it = self.provider.getFeatures(QgsFeatureRequest().setFilterFid(-99999999))
feature = QgsFeature(5)
Expand All @@ -503,6 +513,10 @@ def testGetFeaturesFidsTests(self):
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
self.assertTrue(all_valid)

# test that results match QgsFeatureRequest.acceptFeature
for f in self.provider.getFeatures():
self.assertEqual(request.acceptFeature(f), f.id() in expected)

result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]))])
expected = set([fids[1], fids[3], fids[4]])
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
Expand Down Expand Up @@ -545,6 +559,10 @@ def testGetFeaturesFilterRectTests(self):
assert set(features) == set([2, 4]), 'Got {} instead'.format(features)
self.assertTrue(all_valid)

# test that results match QgsFeatureRequest.acceptFeature
for f in self.provider.getFeatures():
self.assertEqual(request.acceptFeature(f), f['pk'] in set([2, 4]))

# test with an empty rectangle
extent = QgsRectangle()
request = QgsFeatureRequest().setFilterRect(extent)
Expand Down Expand Up @@ -590,6 +608,20 @@ def testRectAndExpression(self):
assert set(expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(set(expected), result)
self.assertTrue(all_valid)

# shouldn't matter what order this is done in
request = QgsFeatureRequest().setFilterRect(extent).setFilterExpression('"cnt">200')
result = set([f['pk'] for f in self.provider.getFeatures(request)])
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
expected = [4]
assert set(
expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(
set(expected), result)
self.assertTrue(all_valid)

# test that results match QgsFeatureRequest.acceptFeature
for f in self.provider.getFeatures():
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)

def testGetFeaturesLimit(self):
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(2))
features = [f['pk'] for f in it]
Expand Down