mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
Merge pull request #7016 from m-kuhn/featureSourceEmpty
QgsFeatureSource::empty() method
This commit is contained in:
commit
d7b85c69f8
@ -260,6 +260,9 @@ An optional ``request`` can be used to optimise the returned
|
||||
iterator, eg by restricting the returned attributes or geometry.
|
||||
%End
|
||||
|
||||
virtual QgsFeatureSource::FeatureAvailability hasFeatures() const;
|
||||
|
||||
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const;
|
||||
|
||||
virtual QgsCoordinateReferenceSystem sourceCrs() const;
|
||||
|
||||
@ -23,6 +23,13 @@ An interface for objects which provide features via a getFeatures method.
|
||||
%End
|
||||
public:
|
||||
|
||||
enum FeatureAvailability
|
||||
{
|
||||
NoFeaturesAvailable,
|
||||
FeaturesAvailable,
|
||||
FeaturesMaybeAvailable
|
||||
};
|
||||
|
||||
virtual ~QgsFeatureSource();
|
||||
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const = 0;
|
||||
@ -66,6 +73,13 @@ if the feature count is unknown.
|
||||
%Docstring
|
||||
Returns the number of features contained in the source, or -1
|
||||
if the feature count is unknown.
|
||||
%End
|
||||
|
||||
virtual FeatureAvailability hasFeatures() const;
|
||||
%Docstring
|
||||
Determines if there are any features available in the source.
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
virtual QSet<QVariant> uniqueValues( int fieldIndex, int limit = -1 ) const;
|
||||
@ -139,7 +153,6 @@ for its ownership.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -122,6 +122,26 @@ Returns the geometry type which is returned by this layer
|
||||
Number of features in the layer
|
||||
|
||||
:return: long containing number of features
|
||||
%End
|
||||
|
||||
virtual bool empty() const;
|
||||
%Docstring
|
||||
Returns true if the layer contains at least one feature.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
%End
|
||||
|
||||
virtual QgsFeatureSource::FeatureAvailability hasFeatures() const final;
|
||||
%Docstring
|
||||
Will always return FeatureAvailability.FeaturesAvailable or
|
||||
FeatureAvailability.NoFeaturesAvailable.
|
||||
|
||||
Calls empty() internally. Providers should override empty()
|
||||
instead if they provide an optimized version of this call.
|
||||
|
||||
.. seealso:: :py:func:`empty`
|
||||
|
||||
.. versionadded:: 3.4
|
||||
%End
|
||||
|
||||
virtual QgsFields fields() const = 0;
|
||||
|
||||
@ -941,6 +941,21 @@ Number of features rendered with specified legend key. Features must be first
|
||||
calculated by countSymbolFeatures()
|
||||
|
||||
:return: number of features rendered by symbol or -1 if failed or counts are not available
|
||||
%End
|
||||
|
||||
virtual FeatureAvailability hasFeatures() const;
|
||||
|
||||
%Docstring
|
||||
Determines if this vector layer has features.
|
||||
|
||||
.. warning::
|
||||
|
||||
when a layer is editable and some features
|
||||
have been deleted, this will return
|
||||
QgsFeatureSource.FeatureAvailability.FeaturesMayBeAvailable
|
||||
to avoid a potentially expensive call to the dataprovider.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
%End
|
||||
|
||||
void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, bool loadDefaultStyleFlag = false ) /Deprecated/;
|
||||
|
||||
@ -721,6 +721,18 @@ QgsFeatureIterator QgsProcessingFeatureSource::getFeatures( const QgsFeatureRequ
|
||||
return mSource->getFeatures( req );
|
||||
}
|
||||
|
||||
QgsFeatureSource::FeatureAvailability QgsProcessingFeatureSource::hasFeatures() const
|
||||
{
|
||||
FeatureAvailability sourceAvailability = mSource->hasFeatures();
|
||||
if ( sourceAvailability == NoFeaturesAvailable )
|
||||
return NoFeaturesAvailable; // never going to be features if underlying source has no features
|
||||
else if ( mInvalidGeometryCheck == QgsFeatureRequest::GeometryNoCheck )
|
||||
return sourceAvailability;
|
||||
else
|
||||
// we don't know... source has features, but these may be filtered out by invalid geometry check
|
||||
return FeaturesMaybeAvailable;
|
||||
}
|
||||
|
||||
QgsFeatureIterator QgsProcessingFeatureSource::getFeatures( const QgsFeatureRequest &request ) const
|
||||
{
|
||||
QgsFeatureRequest req( request );
|
||||
|
||||
@ -317,6 +317,8 @@ class CORE_EXPORT QgsProcessingFeatureSource : public QgsFeatureSource
|
||||
*/
|
||||
QgsFeatureIterator getFeatures( const QgsFeatureRequest &request, Flags flags ) const;
|
||||
|
||||
QgsFeatureSource::FeatureAvailability hasFeatures() const override;
|
||||
|
||||
QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const override;
|
||||
QgsCoordinateReferenceSystem sourceCrs() const override;
|
||||
QgsFields fields() const override;
|
||||
|
||||
@ -23,6 +23,11 @@
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsvectordataprovider.h"
|
||||
|
||||
QgsFeatureSource::FeatureAvailability QgsFeatureSource::hasFeatures() const
|
||||
{
|
||||
return FeaturesMaybeAvailable;
|
||||
}
|
||||
|
||||
QSet<QVariant> QgsFeatureSource::uniqueValues( int fieldIndex, int limit ) const
|
||||
{
|
||||
if ( fieldIndex < 0 || fieldIndex >= fields().count() )
|
||||
|
||||
@ -38,6 +38,22 @@ class CORE_EXPORT QgsFeatureSource
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Possible return value for hasFeatures() to determine if a source is empty.
|
||||
* It is implemented as a three-value logic, so it can return if
|
||||
* there are features available for sure, if there are no features
|
||||
* available for sure or if there might be features available but
|
||||
* there is no guarantee for this.
|
||||
*
|
||||
* \since QGIS 3.4
|
||||
*/
|
||||
enum FeatureAvailability
|
||||
{
|
||||
NoFeaturesAvailable, //!< There are certainly no features available in this source
|
||||
FeaturesAvailable, //!< There is at least one feature available in this source
|
||||
FeaturesMaybeAvailable //!< There may be features available in this source
|
||||
};
|
||||
|
||||
virtual ~QgsFeatureSource() = default;
|
||||
|
||||
/**
|
||||
@ -85,6 +101,13 @@ class CORE_EXPORT QgsFeatureSource
|
||||
*/
|
||||
virtual long featureCount() const = 0;
|
||||
|
||||
/**
|
||||
* Determines if there are any features available in the source.
|
||||
*
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
virtual FeatureAvailability hasFeatures() const;
|
||||
|
||||
/**
|
||||
* Returns the set of unique values contained within the specified \a fieldIndex from this source.
|
||||
* If specified, the \a limit option can be used to limit the number of returned values.
|
||||
@ -150,7 +173,6 @@ class CORE_EXPORT QgsFeatureSource
|
||||
*/
|
||||
QgsVectorLayer *materialize( const QgsFeatureRequest &request,
|
||||
QgsFeedback *feedback = nullptr ) SIP_FACTORY;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( QgsFeatureSource * )
|
||||
|
||||
@ -46,6 +46,27 @@ QString QgsVectorDataProvider::storageType() const
|
||||
return QStringLiteral( "Generic vector file" );
|
||||
}
|
||||
|
||||
bool QgsVectorDataProvider::empty() const
|
||||
{
|
||||
QgsFeature f;
|
||||
QgsFeatureRequest request;
|
||||
request.setSubsetOfAttributes( QgsAttributeList() );
|
||||
request.setFlags( QgsFeatureRequest::NoGeometry );
|
||||
request.setLimit( 1 );
|
||||
if ( getFeatures( request ).nextFeature( f ) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
QgsFeatureSource::FeatureAvailability QgsVectorDataProvider::hasFeatures() const
|
||||
{
|
||||
if ( empty() )
|
||||
return QgsFeatureSource::FeatureAvailability::NoFeaturesAvailable;
|
||||
else
|
||||
return QgsFeatureSource::FeatureAvailability::FeaturesAvailable;
|
||||
}
|
||||
|
||||
QgsCoordinateReferenceSystem QgsVectorDataProvider::sourceCrs() const
|
||||
{
|
||||
return crs();
|
||||
|
||||
@ -163,6 +163,25 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat
|
||||
*/
|
||||
long featureCount() const override = 0;
|
||||
|
||||
/**
|
||||
* Returns true if the layer contains at least one feature.
|
||||
*
|
||||
* \since QGIS 3.4
|
||||
*/
|
||||
virtual bool empty() const;
|
||||
|
||||
/**
|
||||
* Will always return FeatureAvailability::FeaturesAvailable or
|
||||
* FeatureAvailability::NoFeaturesAvailable.
|
||||
*
|
||||
* Calls empty() internally. Providers should override empty()
|
||||
* instead if they provide an optimized version of this call.
|
||||
*
|
||||
* \see empty()
|
||||
* \since QGIS 3.4
|
||||
*/
|
||||
virtual QgsFeatureSource::FeatureAvailability hasFeatures() const final;
|
||||
|
||||
/**
|
||||
* Returns the fields associated with this data provider.
|
||||
*/
|
||||
|
||||
@ -704,6 +704,8 @@ long QgsVectorLayer::featureCount( const QString &legendKey ) const
|
||||
return mSymbolFeatureCountMap.value( legendKey );
|
||||
}
|
||||
|
||||
|
||||
|
||||
QgsVectorLayerFeatureCounter *QgsVectorLayer::countSymbolFeatures()
|
||||
{
|
||||
if ( mSymbolFeatureCounted || mFeatureCounter )
|
||||
@ -2766,6 +2768,25 @@ long QgsVectorLayer::featureCount() const
|
||||
( mEditBuffer ? mEditBuffer->mAddedFeatures.size() - mEditBuffer->mDeletedFeatureIds.size() : 0 );
|
||||
}
|
||||
|
||||
QgsFeatureSource::FeatureAvailability QgsVectorLayer::hasFeatures() const
|
||||
{
|
||||
const QgsFeatureIds deletedFeatures( mEditBuffer ? mEditBuffer->deletedFeatureIds() : QgsFeatureIds() );
|
||||
const QgsFeatureMap addedFeatures( mEditBuffer ? mEditBuffer->addedFeatures() : QgsFeatureMap() );
|
||||
|
||||
if ( mEditBuffer && !deletedFeatures.empty() )
|
||||
{
|
||||
if ( addedFeatures.size() > deletedFeatures.size() )
|
||||
return QgsFeatureSource::FeatureAvailability::FeaturesAvailable;
|
||||
else
|
||||
return QgsFeatureSource::FeatureAvailability::FeaturesMaybeAvailable;
|
||||
}
|
||||
|
||||
if ( ( !mEditBuffer || addedFeatures.empty() ) && mDataProvider->empty() )
|
||||
return QgsFeatureSource::FeatureAvailability::NoFeaturesAvailable;
|
||||
else
|
||||
return QgsFeatureSource::FeatureAvailability::FeaturesAvailable;
|
||||
}
|
||||
|
||||
bool QgsVectorLayer::commitChanges()
|
||||
{
|
||||
mCommitErrors.clear();
|
||||
|
||||
@ -930,6 +930,18 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
*/
|
||||
long featureCount( const QString &legendKey ) const;
|
||||
|
||||
/**
|
||||
* Determines if this vector layer has features.
|
||||
*
|
||||
* \warning when a layer is editable and some features
|
||||
* have been deleted, this will return
|
||||
* QgsFeatureSource::FeatureAvailability::FeaturesMayBeAvailable
|
||||
* to avoid a potentially expensive call to the dataprovider.
|
||||
*
|
||||
* \since QGIS 3.4
|
||||
*/
|
||||
FeatureAvailability hasFeatures() const override;
|
||||
|
||||
/**
|
||||
* Update the data source of the layer. The layer's renderer and legend will be preserved only
|
||||
* if the geometry type of the new data source matches the current geometry type of the layer.
|
||||
|
||||
@ -3190,6 +3190,19 @@ long QgsPostgresProvider::featureCount() const
|
||||
return num;
|
||||
}
|
||||
|
||||
bool QgsPostgresProvider::empty() const
|
||||
{
|
||||
QString sql = QStringLiteral( "SELECT EXISTS (SELECT * FROM %1%2 LIMIT 1)" ).arg( mQuery, filterWhereClause() );
|
||||
QgsPostgresResult res( connectionRO()->PQexec( sql ) );
|
||||
if ( res.PQresultStatus() != PGRES_TUPLES_OK )
|
||||
{
|
||||
pushError( res.PQresultErrorMessage() );
|
||||
return false;
|
||||
}
|
||||
|
||||
return res.PQgetvalue( 0, 0 ) != 't';
|
||||
}
|
||||
|
||||
QgsRectangle QgsPostgresProvider::extent() const
|
||||
{
|
||||
if ( mGeometryColumn.isNull() )
|
||||
|
||||
@ -109,6 +109,16 @@ class QgsPostgresProvider : public QgsVectorDataProvider
|
||||
|
||||
long featureCount() const override;
|
||||
|
||||
/**
|
||||
* Determines if there is at least one feature available on this table.
|
||||
*
|
||||
* \note In contrast to the featureCount() method, this method is not
|
||||
* affected by estimated metadata.
|
||||
*
|
||||
* \since QGIS 3.4
|
||||
*/
|
||||
bool empty() const override;
|
||||
|
||||
/**
|
||||
* Returns a string representation of the endian-ness for the layer
|
||||
*/
|
||||
|
||||
@ -1206,6 +1206,27 @@ QString QgsWFSProvider::translateMetadataValue( const QString &mdKey, const QVar
|
||||
{
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsWFSProvider::empty() const
|
||||
{
|
||||
QgsFeature f;
|
||||
QgsFeatureRequest request;
|
||||
request.setSubsetOfAttributes( QgsAttributeList() );
|
||||
request.setFlags( QgsFeatureRequest::NoGeometry );
|
||||
|
||||
// Whoops, the WFS provider returns an empty iterator when we are using
|
||||
// a setLimit call in combination with a subsetString.
|
||||
// Remove this method (and default to the QgsVectorDataProvider one)
|
||||
// once this is fixed
|
||||
#if 0
|
||||
request.setLimit( 1 );
|
||||
#endif
|
||||
if ( getFeatures( request ).nextFeature( f ) )
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields &fields, QgsWkbTypes::Type &geomType )
|
||||
|
||||
@ -111,6 +111,8 @@ class QgsWFSProvider : public QgsVectorDataProvider
|
||||
QString translateMetadataKey( const QString &mdKey ) const override;
|
||||
QString translateMetadataValue( const QString &mdKey, const QVariant &value ) const override;
|
||||
|
||||
bool empty() const override;
|
||||
|
||||
public slots:
|
||||
|
||||
void reloadData() override;
|
||||
|
||||
@ -28,6 +28,7 @@ from qgis.core import (
|
||||
QgsVectorLayerFeatureSource,
|
||||
QgsFeatureSink,
|
||||
QgsTestUtils,
|
||||
QgsFeatureSource,
|
||||
NULL
|
||||
)
|
||||
from qgis.PyQt.QtTest import QSignalSpy
|
||||
@ -422,6 +423,46 @@ class ProviderTestCase(FeatureSourceTestCase):
|
||||
self.assertEqual(count, 0)
|
||||
self.assertEqual(self.source.featureCount(), 5)
|
||||
|
||||
def testEmpty(self):
|
||||
self.assertFalse(self.source.empty())
|
||||
self.assertEqual(self.source.hasFeatures(), QgsFeatureSource.FeaturesAvailable)
|
||||
|
||||
if self.source.supportsSubsetString():
|
||||
try:
|
||||
backup = self.source.subsetString()
|
||||
# Add a subset string and test feature count
|
||||
subset = self.getSubsetString()
|
||||
self.source.setSubsetString(subset)
|
||||
self.assertFalse(self.source.empty())
|
||||
self.assertEqual(self.source.hasFeatures(), QgsFeatureSource.FeaturesAvailable)
|
||||
subsetNoMatching = self.getSubsetStringNoMatching()
|
||||
self.source.setSubsetString(subsetNoMatching)
|
||||
self.assertTrue(self.source.empty())
|
||||
self.assertEqual(self.source.hasFeatures(), QgsFeatureSource.NoFeaturesAvailable)
|
||||
finally:
|
||||
self.source.setSubsetString(None)
|
||||
self.assertFalse(self.source.empty())
|
||||
|
||||
# If the provider supports tests on editable layers
|
||||
if getattr(self, 'getEditableLayer', None):
|
||||
l = self.getEditableLayer()
|
||||
self.assertTrue(l.isValid())
|
||||
|
||||
self.assertEqual(l.hasFeatures(), QgsFeatureSource.FeaturesAvailable)
|
||||
|
||||
# Test that deleting some features in the edit buffer does not
|
||||
# return empty, we accept FeaturesAvailable as well as
|
||||
# MaybeAvailable
|
||||
l.startEditing()
|
||||
l.deleteFeature(next(l.getFeatures()).id())
|
||||
self.assertNotEqual(l.hasFeatures(), QgsFeatureSource.NoFeaturesAvailable)
|
||||
l.rollBack()
|
||||
|
||||
# Call truncate(), we need an empty set now
|
||||
l.dataProvider().truncate()
|
||||
self.assertTrue(l.dataProvider().empty())
|
||||
self.assertEqual(l.dataProvider().hasFeatures(), QgsFeatureSource.NoFeaturesAvailable)
|
||||
|
||||
def testGetFeaturesNoGeometry(self):
|
||||
""" Test that no geometry is present when fetching features without geometry"""
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user