Navigation Menu

Skip to content

Commit

Permalink
Add framework for querying for dataset sublayers via
Browse files Browse the repository at this point in the history
QgsProviderRegistry/QgsProviderMetadata
  • Loading branch information
nyalldawson committed Jun 21, 2021
1 parent 2328f4f commit 469f967
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 2 deletions.
7 changes: 7 additions & 0 deletions python/core/auto_additions/qgis.py
Expand Up @@ -331,3 +331,10 @@
Qgis.UnplacedLabelVisibility.__doc__ = 'Unplaced label visibility.\n\n.. versionadded:: 3.20\n\n' + '* ``FollowEngineSetting``: ' + Qgis.UnplacedLabelVisibility.FollowEngineSetting.__doc__ + '\n' + '* ``NeverShow``: ' + Qgis.UnplacedLabelVisibility.NeverShow.__doc__
# --
Qgis.UnplacedLabelVisibility.baseClass = Qgis
# monkey patching scoped based enum
Qgis.SublayerQueryFlag.FastScan.__doc__ = "Indicates that the provider must scan for sublayers using the fastest possible approach -- e.g. by first checking that a uri has an extension which is known to be readable by the provider"
Qgis.SublayerQueryFlag.ResolveGeometryType.__doc__ = "Attempt to resolve the geometry type for vector sublayers"
Qgis.SublayerQueryFlag.CountFeatures.__doc__ = "Count features in vector sublayers"
Qgis.SublayerQueryFlag.__doc__ = 'Flags which control how data providers will scan for sublayers in a dataset.\n\n.. versionadded:: 3.22\n\n' + '* ``FastScan``: ' + Qgis.SublayerQueryFlag.FastScan.__doc__ + '\n' + '* ``ResolveGeometryType``: ' + Qgis.SublayerQueryFlag.ResolveGeometryType.__doc__ + '\n' + '* ``CountFeatures``: ' + Qgis.SublayerQueryFlag.CountFeatures.__doc__
# --
Qgis.SublayerQueryFlag.baseClass = Qgis
18 changes: 18 additions & 0 deletions python/core/auto_generated/providers/qgsprovidermetadata.sip.in
Expand Up @@ -265,6 +265,24 @@ The default method returns ``False`` for all URIs.
ignore the specified ``uri``, not just the provider associated with this metadata!

.. versionadded:: 3.18
%End

virtual QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = 0 ) const;
%Docstring
Queries the specified ``uri`` and returns a list of any valid sublayers found in the dataset which can be handled by this provider.

The optional ``flags`` argument can be used to control the behavior of the query.

The optional ``feedback`` argument can be used to provide cancellation support for long-running queries.

.. note::

Providers which implement this method should return always return a list of sublayer details for any valid, even if the ``uri``
only relates to a single layer. Returning a non-empty list indicates that the provider is able to load at least one layer using the ``uri``,
and is used to collate a combined layer of all providers which support the URI (e.g. in the case that a URI may be readable by multiple
different providers).

.. versionadded:: 3.20
%End

virtual QgsDataProvider *createProvider( const QString &uri,
Expand Down
13 changes: 13 additions & 0 deletions python/core/auto_generated/providers/qgsproviderregistry.sip.in
Expand Up @@ -510,6 +510,19 @@ This method tests whether any of the registered providers return ``True`` for th
:py:func:`QgsProviderMetadata.uriIsBlocklisted()` implementation for the specified URI.

.. versionadded:: 3.18
%End

QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = 0 ) const;
%Docstring
Queries the specified ``uri`` and returns a list of any valid sublayers found in the dataset which can be handled by any registered data provider.

This method iteratively queries each registered data provider and returns the complete collated list of all valid sublayers found in the dataset which can be opened by the data providers.

The optional ``flags`` argument can be used to control the behavior of the query.

The optional ``feedback`` argument can be used to provide cancellation support for long-running queries.

.. versionadded:: 3.20
%End

QString fileVectorFilters() const;
Expand Down
10 changes: 10 additions & 0 deletions python/core/auto_generated/qgis.sip.in
Expand Up @@ -242,6 +242,15 @@ The development version
NeverShow,
};

enum class SublayerQueryFlag
{
FastScan,
ResolveGeometryType,
CountFeatures,
};
typedef QFlags<Qgis::SublayerQueryFlag> SublayerQueryFlags;


static const double DEFAULT_SEARCH_RADIUS_MM;

static const float DEFAULT_MAPTOPIXEL_THRESHOLD;
Expand Down Expand Up @@ -315,6 +324,7 @@ QFlags<Qgis::SymbolPreviewFlag> operator|(Qgis::SymbolPreviewFlag f1, QFlags<Qgi

QFlags<Qgis::BrowserItemCapability> operator|(Qgis::BrowserItemCapability f1, QFlags<Qgis::BrowserItemCapability> f2);

QFlags<Qgis::SublayerQueryFlag> operator|(Qgis::SublayerQueryFlag f1, QFlags<Qgis::SublayerQueryFlag> f2);



Expand Down
6 changes: 6 additions & 0 deletions src/core/providers/qgsprovidermetadata.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsmaplayer.h"
#include "qgsexception.h"
#include "qgsabstractdatabaseproviderconnection.h"
#include "qgsprovidersublayerdetails.h"

QgsProviderMetadata::QgsProviderMetadata( QString const &key,
QString const &description,
Expand Down Expand Up @@ -106,6 +107,11 @@ bool QgsProviderMetadata::uriIsBlocklisted( const QString & ) const
return false;
}

QList<QgsProviderSublayerDetails> QgsProviderMetadata::querySublayers( const QString &, Qgis::SublayerQueryFlags, QgsFeedback * ) const
{
return QList<QgsProviderSublayerDetails>();
}

QgsDataProvider *QgsProviderMetadata::createProvider( const QString &uri,
const QgsDataProvider::ProviderOptions &options,
QgsDataProvider::ReadFlags flags )
Expand Down
18 changes: 18 additions & 0 deletions src/core/providers/qgsprovidermetadata.h
Expand Up @@ -43,6 +43,8 @@ class QgsRasterDataProvider;
class QgsMeshDataProvider;
class QgsAbstractDatabaseProviderConnection;
class QgsLayerMetadata;
class QgsProviderSublayerDetails;
class QgsFeedback;

struct QgsMesh;

Expand Down Expand Up @@ -324,6 +326,22 @@ class CORE_EXPORT QgsProviderMetadata : public QObject
*/
virtual bool uriIsBlocklisted( const QString &uri ) const;

/**
* Queries the specified \a uri and returns a list of any valid sublayers found in the dataset which can be handled by this provider.
*
* The optional \a flags argument can be used to control the behavior of the query.
*
* The optional \a feedback argument can be used to provide cancellation support for long-running queries.
*
* \note Providers which implement this method should return always return a list of sublayer details for any valid, even if the \a uri
* only relates to a single layer. Returning a non-empty list indicates that the provider is able to load at least one layer using the \a uri,
* and is used to collate a combined layer of all providers which support the URI (e.g. in the case that a URI may be readable by multiple
* different providers).
*
* \since QGIS 3.20
*/
virtual QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = nullptr ) const;

/**
* Class factory to return a pointer to a newly created QgsDataProvider object
*
Expand Down
13 changes: 13 additions & 0 deletions src/core/providers/qgsproviderregistry.cpp
Expand Up @@ -32,6 +32,7 @@
#include "qgsvectorlayer.h"
#include "qgsvectortileprovidermetadata.h"
#include "qgsproject.h"
#include "qgsprovidersublayerdetails.h"
#include "providers/memory/qgsmemoryprovider.h"
#include "providers/gdal/qgsgdalprovider.h"
#include "providers/ogr/qgsogrprovidermetadata.h"
Expand Down Expand Up @@ -894,3 +895,15 @@ bool QgsProviderRegistry::uriIsBlocklisted( const QString &uri ) const
}
return false;
}

QList<QgsProviderSublayerDetails> QgsProviderRegistry::querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags, QgsFeedback *feedback ) const
{
QList<QgsProviderSublayerDetails> res;
for ( auto it = mProviders.begin(); it != mProviders.end(); ++it )
{
res.append( it->second->querySublayers( uri, flags ) );
if ( feedback && feedback->isCanceled() )
break;
}
return res;
}
15 changes: 15 additions & 0 deletions src/core/providers/qgsproviderregistry.h
Expand Up @@ -40,6 +40,8 @@ class QgsDataItem;
class QgsRasterDataProvider;
class QgsTransaction;
class QgsFields;
class QgsProviderSublayerDetails;
class QgsFeedback;

/**
* \ingroup core
Expand Down Expand Up @@ -543,6 +545,19 @@ class CORE_EXPORT QgsProviderRegistry
*/
bool uriIsBlocklisted( const QString &uri ) const;

/**
* Queries the specified \a uri and returns a list of any valid sublayers found in the dataset which can be handled by any registered data provider.
*
* This method iteratively queries each registered data provider and returns the complete collated list of all valid sublayers found in the dataset which can be opened by the data providers.
*
* The optional \a flags argument can be used to control the behavior of the query.
*
* The optional \a feedback argument can be used to provide cancellation support for long-running queries.
*
* \since QGIS 3.20
*/
QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = nullptr ) const;

/**
* Returns a file filter string for supported vector files.
*
Expand Down
16 changes: 15 additions & 1 deletion src/core/qgis.h
Expand Up @@ -357,6 +357,20 @@ class CORE_EXPORT Qgis
};
Q_ENUM( UnplacedLabelVisibility )

/**
* Flags which control how data providers will scan for sublayers in a dataset.
*
* \since QGIS 3.22
*/
enum class SublayerQueryFlag : int
{
FastScan = 1 << 0, //!< Indicates that the provider must scan for sublayers using the fastest possible approach -- e.g. by first checking that a uri has an extension which is known to be readable by the provider
ResolveGeometryType = 1 << 1, //!< Attempt to resolve the geometry type for vector sublayers
CountFeatures = 1 << 2, //!< Count features in vector sublayers
};
Q_DECLARE_FLAGS( SublayerQueryFlags, SublayerQueryFlag )
Q_ENUM( SublayerQueryFlag )

/**
* Identify search radius in mm
* \since QGIS 2.3
Expand Down Expand Up @@ -474,7 +488,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolRenderHints )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolPreviewFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::BrowserItemCapabilities )

Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SublayerQueryFlags )

// hack to workaround warnings when casting void pointers
// retrieved from QLibrary::resolve to function pointers.
Expand Down
31 changes: 30 additions & 1 deletion tests/src/python/test_qgsproviderregistry.py
Expand Up @@ -14,7 +14,10 @@

from qgis.core import (
QgsProviderRegistry,
QgsMapLayerType
QgsMapLayerType,
QgsProviderMetadata,
QgsProviderSublayerDetails,
Qgis
)
from qgis.testing import start_app, unittest

Expand All @@ -23,6 +26,20 @@
start_app()


class TestProviderMetadata(QgsProviderMetadata):
"""
Test metadata
"""

def __init__(self, key):
super().__init__(key, key)

def querySublayers(self, uri: str, flags=Qgis.SublayerQueryFlags(), feedback=None):
res = QgsProviderSublayerDetails()
res.setProviderKey(self.key())
return [res]


class TestQgsProviderRegistry(unittest.TestCase):

def testProviderList(self):
Expand Down Expand Up @@ -109,6 +126,18 @@ def testUnusableUriDetails(self):
self.assertTrue(res)
self.assertIn('LAZ', details.warning)

def testSublayerDetails(self):
provider1 = TestProviderMetadata('p1')
provider2 = TestProviderMetadata('p2')

self.assertFalse(QgsProviderRegistry.instance().querySublayers('test_uri'))

self.assertTrue(QgsProviderRegistry.instance().registerProvider(provider1))
self.assertTrue(QgsProviderRegistry.instance().registerProvider(provider2))

self.assertCountEqual([p.providerKey() for p in QgsProviderRegistry.instance().querySublayers('test_uri')],
['p1', 'p2'])


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

0 comments on commit 469f967

Please sign in to comment.