Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add API to QgsFeatureSource to determine whether a spatial index
exists on the source (or not, or unknown presence)
  • Loading branch information
nyalldawson committed Nov 16, 2019
1 parent 29824c1 commit fe32fdf
Show file tree
Hide file tree
Showing 18 changed files with 115 additions and 61 deletions.
Expand Up @@ -412,6 +412,8 @@ iterator, eg by restricting the returned attributes or geometry.

virtual QgsFeatureIds allFeatureIds() const;

virtual SpatialIndexPresence hasSpatialIndex() const;


QgsExpressionContextScope *createExpressionContextScope() const /Factory/;
%Docstring
Expand Down
18 changes: 18 additions & 0 deletions python/core/auto_generated/qgsfeaturesource.sip.in
Expand Up @@ -158,6 +158,24 @@ The returned value is a new instance and the caller takes responsibility
for its ownership.

.. versionadded:: 3.0
%End

enum SpatialIndexPresence
{
SpatialIndexUnknown,
SpatialIndexNotPresent,
SpatialIndexPresent,
};

virtual SpatialIndexPresence hasSpatialIndex() const;
%Docstring
Returns an enum value representing the presence of a valid spatial index on the source,
if it can be determined.

If QgsFeatureSource.SpatialIndexUnknown is returned then the presence of an index cannot
be determined.

.. versionadded:: 3.10.1
%End
};

Expand Down
3 changes: 3 additions & 0 deletions python/core/auto_generated/qgsvectorlayer.sip.in
Expand Up @@ -2558,6 +2558,9 @@ Sets the coordinate transform context to ``transformContext``
.. versionadded:: 3.8
%End

virtual SpatialIndexPresence hasSpatialIndex() const;


virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;


Expand Down
Expand Up @@ -195,6 +195,7 @@ the QgsVectorLayerSelectedFeatureSource will not be reflected.

virtual QgsExpressionContextScope *createExpressionContextScope() const;

virtual SpatialIndexPresence hasSpatialIndex() const;


};
Expand Down
5 changes: 5 additions & 0 deletions src/core/processing/qgsprocessingutils.cpp
Expand Up @@ -1039,6 +1039,11 @@ QgsFeatureIds QgsProcessingFeatureSource::allFeatureIds() const
return mSource->allFeatureIds();
}

QgsFeatureSource::SpatialIndexPresence QgsProcessingFeatureSource::hasSpatialIndex() const
{
return mSource->hasSpatialIndex();
}

QgsExpressionContextScope *QgsProcessingFeatureSource::createExpressionContextScope() const
{
QgsExpressionContextScope *expressionContextScope = nullptr;
Expand Down
1 change: 1 addition & 0 deletions src/core/processing/qgsprocessingutils.h
Expand Up @@ -466,6 +466,7 @@ class CORE_EXPORT QgsProcessingFeatureSource : public QgsFeatureSource
QVariant maximumValue( int fieldIndex ) const override;
QgsRectangle sourceExtent() const override;
QgsFeatureIds allFeatureIds() const override;
SpatialIndexPresence hasSpatialIndex() const override;

/**
* Returns an expression context scope suitable for this source.
Expand Down
5 changes: 5 additions & 0 deletions src/core/providers/memory/qgsmemoryprovider.cpp
Expand Up @@ -630,6 +630,11 @@ bool QgsMemoryProvider::createSpatialIndex()
return true;
}

QgsFeatureSource::SpatialIndexPresence QgsMemoryProvider::hasSpatialIndex() const
{
return mSpatialIndex ? SpatialIndexPresent : SpatialIndexNotPresent;
}

QgsVectorDataProvider::Capabilities QgsMemoryProvider::capabilities() const
{
return AddFeatures | DeleteFeatures | ChangeGeometries |
Expand Down
1 change: 1 addition & 0 deletions src/core/providers/memory/qgsmemoryprovider.h
Expand Up @@ -67,6 +67,7 @@ class QgsMemoryProvider : public QgsVectorDataProvider
bool setSubsetString( const QString &theSQL, bool updateFeatureCount = true ) override;
bool supportsSubsetString() const override { return true; }
bool createSpatialIndex() override;
QgsFeatureSource::SpatialIndexPresence hasSpatialIndex() const override;
QgsVectorDataProvider::Capabilities capabilities() const override;
bool truncate() override;

Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsfeaturesource.cpp
Expand Up @@ -187,3 +187,8 @@ QgsVectorLayer *QgsFeatureSource::materialize( const QgsFeatureRequest &request,
return layer.release();
}

QgsFeatureSource::SpatialIndexPresence QgsFeatureSource::hasSpatialIndex() const
{
return SpatialIndexUnknown;
}

22 changes: 22 additions & 0 deletions src/core/qgsfeaturesource.h
Expand Up @@ -179,6 +179,28 @@ class CORE_EXPORT QgsFeatureSource
*/
QgsVectorLayer *materialize( const QgsFeatureRequest &request,
QgsFeedback *feedback = nullptr ) SIP_FACTORY;

/**
* Enumeration of spatial index presence states.
* \since QGIS 3.10.1
*/
enum SpatialIndexPresence
{
SpatialIndexUnknown = 0, //!< Spatial index presence cannot be determined, index may or may not exist
SpatialIndexNotPresent = 1, //!< No spatial index exists for the source
SpatialIndexPresent = 2, //!< A valid spatial index exists for the source
};

/**
* Returns an enum value representing the presence of a valid spatial index on the source,
* if it can be determined.
*
* If QgsFeatureSource::SpatialIndexUnknown is returned then the presence of an index cannot
* be determined.
*
* \since QGIS 3.10.1
*/
virtual SpatialIndexPresence hasSpatialIndex() const;
};

Q_DECLARE_METATYPE( QgsFeatureSource * )
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -1437,6 +1437,11 @@ void QgsVectorLayer::setTransformContext( const QgsCoordinateTransformContext &t
mDataProvider->setTransformContext( transformContext );
}

QgsFeatureSource::SpatialIndexPresence QgsVectorLayer::hasSpatialIndex() const
{
return mDataProvider ? mDataProvider->hasSpatialIndex() : QgsFeatureSource::SpatialIndexUnknown;
}

bool QgsVectorLayer::accept( QgsStyleEntityVisitorInterface *visitor ) const
{
if ( mRenderer )
Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsvectorlayer.h
Expand Up @@ -2374,6 +2374,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
*/
virtual void setTransformContext( const QgsCoordinateTransformContext &transformContext ) override;

SpatialIndexPresence hasSpatialIndex() const override;

bool accept( QgsStyleEntityVisitorInterface *visitor ) const override;

signals:
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsvectorlayerfeatureiterator.cpp
Expand Up @@ -1249,6 +1249,14 @@ QgsExpressionContextScope *QgsVectorLayerSelectedFeatureSource::createExpression
return nullptr;
}

QgsFeatureSource::SpatialIndexPresence QgsVectorLayerSelectedFeatureSource::hasSpatialIndex() const
{
if ( mLayer )
return mLayer->hasSpatialIndex();
else
return QgsFeatureSource::SpatialIndexUnknown;
}

//
// QgsVectorLayerSelectedFeatureIterator
//
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsvectorlayerfeatureiterator.h
Expand Up @@ -315,7 +315,7 @@ class CORE_EXPORT QgsVectorLayerSelectedFeatureSource : public QgsFeatureSource,
long featureCount() const override;
QString sourceName() const override;
QgsExpressionContextScope *createExpressionContextScope() const override;

SpatialIndexPresence hasSpatialIndex() const override;

private:

Expand Down
5 changes: 5 additions & 0 deletions src/providers/delimitedtext/qgsdelimitedtextprovider.cpp
Expand Up @@ -282,6 +282,11 @@ bool QgsDelimitedTextProvider::createSpatialIndex()
return true;
}

QgsFeatureSource::SpatialIndexPresence QgsDelimitedTextProvider::hasSpatialIndex() const
{
return mSpatialIndex ? QgsFeatureSource::SpatialIndexPresent : QgsFeatureSource::SpatialIndexNotPresent;
}

// Really want to merge scanFile and rescan into single code. Currently the reason
// this is not done is that scanFile is done initially to create field names and, rescan
// file includes building subset expression and assumes field names/types are already
Expand Down
59 changes: 1 addition & 58 deletions src/providers/delimitedtext/qgsdelimitedtextprovider.h
Expand Up @@ -86,78 +86,21 @@ class QgsDelimitedTextProvider : public QgsVectorDataProvider
/* Implementation of functions from QgsVectorDataProvider */

QgsAbstractFeatureSource *featureSource() const override;

/**
* Returns the permanent storage type for this layer as a friendly name.
*/
QString storageType() const override;

QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) const override;

QgsWkbTypes::Type wkbType() const override;

long featureCount() const override;

QgsFields fields() const override;

/**
* Returns a bitmask containing the supported capabilities
* Note, some capabilities may change depending on whether
* a spatial filter is active on this provider, so it may
* be prudent to check this value per intended operation.
*/
QgsVectorDataProvider::Capabilities capabilities() const override;

/**
* Creates a spatial index on the data
* \returns indexCreated Returns true if a spatial index is created
*/
bool createSpatialIndex() override;

/* Implementation of functions from QgsDataProvider */

/**
* Returns a provider name
*
* Essentially just returns the provider key. Should be used to build file
* dialogs so that providers can be shown with their supported types. Thus
* if more than one provider supports a given format, the user is able to
* select a specific provider to open that file.
*
* \note
*
* Instead of being pure virtual, might be better to generalize this
* behavior and presume that none of the sub-classes are going to do
* anything strange with regards to their name or description?
*/
QgsFeatureSource::SpatialIndexPresence hasSpatialIndex() const override;
QString name() const override;

/**
* Returns description
*
* Return a terse string describing what the provider is.
*
* \note
*
* Instead of being pure virtual, might be better to generalize this
* behavior and presume that none of the sub-classes are going to do
* anything strange with regards to their name or description?
*/
QString description() const override;

QgsRectangle extent() const override;
bool isValid() const override;

QgsCoordinateReferenceSystem crs() const override;

/**
* Set the subset string used to create a subset of features in
* the layer.
*/
bool setSubsetString( const QString &subset, bool updateFeatureCount = true ) override;

bool supportsSubsetString() const override { return true; }

QString subsetString() const override
{
return mSubsetString;
Expand Down
11 changes: 10 additions & 1 deletion tests/src/python/test_provider_memory.py
Expand Up @@ -26,7 +26,8 @@
QgsMemoryProviderUtils,
QgsCoordinateReferenceSystem,
QgsRectangle,
QgsTestUtils
QgsTestUtils,
QgsFeatureSource
)

from qgis.testing import (
Expand Down Expand Up @@ -643,6 +644,14 @@ def testBool(self):

self.assertEqual([f.attributes() for f in dp.getFeatures()], [[1, True, NULL], [2, False, NULL], [3, NULL, NULL], [2, NULL, True]])

def testSpatialIndex(self):
vl = QgsVectorLayer(
'Point?crs=epsg:4326&field=f1:integer&field=f2:bool',
'test', 'memory')
self.assertEqual(vl.hasSpatialIndex(), QgsFeatureSource.SpatialIndexNotPresent)
vl.dataProvider().createSpatialIndex()
self.assertEqual(vl.hasSpatialIndex(), QgsFeatureSource.SpatialIndexPresent)


class TestPyQgsMemoryProviderIndexed(unittest.TestCase, ProviderTestCase):

Expand Down
21 changes: 20 additions & 1 deletion tests/src/python/test_qgsdelimitedtextprovider.py
Expand Up @@ -42,7 +42,8 @@
QgsRectangle,
QgsApplication,
QgsFeature,
QgsWkbTypes)
QgsWkbTypes,
QgsFeatureSource)

from qgis.testing import start_app, unittest
from utilities import unitTestDataPath, compareWkt
Expand Down Expand Up @@ -888,6 +889,24 @@ def test_046_M(self):
assert vl.wkbType() == QgsWkbTypes.PointM, "wrong wkb type, should be PointM"
assert vl.getFeature(2).geometry().asWkt() == "PointM (-71.12300000000000466 78.23000000000000398 2)", "wrong PointM geometry"

def testSpatialIndex(self):
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
basetestfile = os.path.join(srcpath, 'delimited_xyzm.csv')

url = MyUrl.fromLocalFile(basetestfile)
url.addQueryItem("crs", "epsg:4326")
url.addQueryItem("type", "csv")
url.addQueryItem("xField", "X")
url.addQueryItem("yField", "Y")
url.addQueryItem("spatialIndex", "no")

vl = QgsVectorLayer(url.toString(), 'test', 'delimitedtext')
self.assertTrue(vl.isValid())

self.assertEqual(vl.hasSpatialIndex(), QgsFeatureSource.SpatialIndexNotPresent)
vl.dataProvider().createSpatialIndex()
self.assertEqual(vl.hasSpatialIndex(), QgsFeatureSource.SpatialIndexPresent)


if __name__ == '__main__':
unittest.main()

0 comments on commit fe32fdf

Please sign in to comment.