Create data item provider for QgsGdalCloudProviderConnections

Adds a browser data item provider which shows connections
to cloud storage providers handled by GDAL's VSI handlers.

Allows loading vector and raster layers from VSI cloud storage
connections via the browser.
This commit is contained in:
Nyall Dawson 2024-06-25 13:06:29 +10:00
parent 392eaac69a
commit da2b387204
7 changed files with 335 additions and 0 deletions

View File

@ -1004,6 +1004,7 @@
<file>themes/default/mIconSearchWrapAround.svg</file>
<file>themes/default/mIconSearchRegex.svg</file>
<file>themes/default/mActionReplace.svg</file>
<file>themes/default/mIconCloud.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" width="16" height="16"><defs><linearGradient id="d"><stop offset="0" style="stop-color:#cbd9ef;stop-opacity:1"/><stop offset="1" style="stop-color:#93b1d1;stop-opacity:1"/></linearGradient><linearGradient id="c"><stop offset="0" style="stop-color:#ccdaef;stop-opacity:1"/><stop offset="1" style="stop-color:#b3c5dd;stop-opacity:1"/></linearGradient><linearGradient id="b"><stop offset="0" style="stop-color:#aec3dc;stop-opacity:1"/><stop offset="1" style="stop-color:#8babcd;stop-opacity:1"/></linearGradient><linearGradient id="a"><stop offset="0" style="stop-color:#aec5df;stop-opacity:1"/><stop offset="1" style="stop-color:#89aad1;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#d" id="e" x1="5.406" x2="13.188" y1="4.938" y2="11.531" gradientUnits="userSpaceOnUse"/></defs><path d="M7.729 3.634a3.503 3.503 0 0 0-3.45 3.476v.168l-.158-.045a1.661 1.661 0 0 0-1.744.547 1.659 1.659 0 0 0-.336 1.385l.016.076-.06.047a1.54 1.54 0 0 0 .958 2.742H13.28c.776 0 1.43-.579 1.525-1.35.15-1.222-.852-1.502-1.205-1.646.25-.797-.023-1.331-.508-1.972a2.181 2.181 0 0 0-1.68-.786h-.091l-.141.01-.027-.097h.002a3.504 3.504 0 0 0-3.426-2.555Z" style="stroke:#476280;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1;fill:url(#e)"/><path d="M4.254 7.071c.099.854.09 1.11.42 1.757M14.355 9.43c-.64-.627-2.027-.672-2.027-.672" style="fill:none;fill-opacity:1;stroke:#476280;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -308,6 +308,7 @@ set(QGIS_CORE_SRCS
providers/arcgis/qgsarcgisrestutils.cpp
providers/gdal/qgsgdalcloudconnection.cpp
providers/gdal/qgsgdalclouddataitems.cpp
providers/gdal/qgsgdalproviderbase.cpp
providers/gdal/qgsgdalprovider.cpp
@ -1815,6 +1816,7 @@ set(QGIS_CORE_HDRS
providers/arcgis/qgsarcgisrestutils.h
providers/gdal/qgsgdalcloudconnection.h
providers/gdal/qgsgdalclouddataitems.h
providers/gdal/qgsgdalprovider.h
providers/memory/qgsmemoryfeatureiterator.h

View File

@ -0,0 +1,232 @@
/***************************************************************************
qgsgdalclouddataitems.cpp
---------------------
begin : June 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgdalclouddataitems.h"
#include "qgsprovidermetadata.h"
#include "qgsgdalcloudconnection.h"
#include "qgsproviderregistry.h"
#include "qgsgdalutils.h"
#include "qgsfilebaseddataitemprovider.h"
///@cond PRIVATE
//
// QgsGdalCloudRootItem
//
QgsGdalCloudRootItem::QgsGdalCloudRootItem( QgsDataItem *parent, QString name, QString path )
: QgsConnectionsRootItem( parent, name, path, QStringLiteral( "cloud" ) )
{
mCapabilities |= Qgis::BrowserItemCapability::Fast;
mIconName = QStringLiteral( "mIconCloud.svg" );
populate();
}
QVector<QgsDataItem *> QgsGdalCloudRootItem::createChildren()
{
QVector<QgsDataItem *> connections;
QList< QgsGdalUtils::VsiNetworkFileSystemDetails > vsiDetails = QgsGdalUtils::vsiNetworkFileSystems();
std::sort( vsiDetails.begin(), vsiDetails.end(), []( const QgsGdalUtils::VsiNetworkFileSystemDetails & a, const QgsGdalUtils::VsiNetworkFileSystemDetails & b )
{
return QString::localeAwareCompare( a.name, b.name ) < 0;
} );
const QStringList connectionList = QgsGdalCloudProviderConnection::connectionList();
for ( const QgsGdalUtils::VsiNetworkFileSystemDetails &handler : vsiDetails )
{
// only expose items for drivers with stored connections
bool foundConnection = false;
for ( const QString &connName : connectionList )
{
const QgsGdalCloudProviderConnection::Data connectionData = QgsGdalCloudProviderConnection::connection( connName );
if ( connectionData.vsiHandler == handler.identifier )
{
foundConnection = true;
break;
}
}
if ( !foundConnection )
continue;
QgsDataItem *conn = new QgsGdalCloudProviderItem( this, handler );
connections.append( conn );
}
return connections;
}
//
// QgsGdalCloudProviderItem
//
QgsGdalCloudProviderItem::QgsGdalCloudProviderItem( QgsDataItem *parent, const QgsGdalUtils::VsiNetworkFileSystemDetails &handler )
: QgsDataCollectionItem( parent, handler.name, handler.identifier, handler.identifier )
, mVsiHandler( handler )
{
mIconName = QStringLiteral( "mIconCloud.svg" );
}
QVector<QgsDataItem *> QgsGdalCloudProviderItem::createChildren()
{
QVector<QgsDataItem *> connections;
const auto connectionList = QgsGdalCloudProviderConnection::connectionList();
for ( const QString &connName : connectionList )
{
const QgsGdalCloudProviderConnection::Data connectionData = QgsGdalCloudProviderConnection::connection( connName );
if ( connectionData.vsiHandler != mVsiHandler.identifier )
continue;
QgsDataItem *conn = new QgsGdalCloudConnectionItem( this, connName, mPath + '/' + connName );
connections.append( conn );
}
return connections;
}
//
// QgsGdalCloudConnectionItem
//
QgsGdalCloudConnectionItem::QgsGdalCloudConnectionItem( QgsDataItem *parent, const QString &name, const QString &path )
: QgsDataCollectionItem( parent, name, path )
, mConnName( name )
{
mIconName = QStringLiteral( "mIconConnect.svg" );
mCapabilities |= Qgis::BrowserItemCapability::Fertile;
}
bool QgsGdalCloudConnectionItem::equal( const QgsDataItem *other )
{
const QgsGdalCloudConnectionItem *o = qobject_cast<const QgsGdalCloudConnectionItem *>( other );
return ( type() == other->type() && o && mPath == o->mPath && mName == o->mName && mConnName == o->mConnName );
}
QVector<QgsDataItem *> QgsGdalCloudConnectionItem::createChildren()
{
QVector<QgsDataItem *> children;
const QgsGdalCloudProviderConnection::Data connectionData = QgsGdalCloudProviderConnection::connection( mConnName );
QgsGdalCloudProviderConnection conn = QgsGdalCloudProviderConnection( mConnName );
const QList< QgsGdalCloudProviderConnection::DirectoryObject > objects = conn.contents( connectionData.rootPath );
QVariantMap extraUriParts;
if ( !connectionData.credentialOptions.isEmpty() )
{
extraUriParts.insert( QStringLiteral( "credentialOptions" ), connectionData.credentialOptions );
}
for ( const QgsGdalCloudProviderConnection::DirectoryObject &object : objects )
{
const QString subPath = connectionData.rootPath.isEmpty() ? object.name : ( connectionData.rootPath + '/' + object.name );
if ( object.isDir )
{
QgsGdalCloudDirectoryItem *child = new QgsGdalCloudDirectoryItem( this,
object.name, mPath + '/' + object.name, mConnName, subPath );
children.append( child );
}
else if ( object.isFile )
{
const QString filePath = QStringLiteral( "/%1/%2/%3" ).arg( connectionData.vsiHandler, connectionData.container, subPath );
// QgsFileBasedDataItemProvider uses paths for item uris by default, so we need to specify that the credentialOptions should be appended to the layer URIs
if ( QgsDataItem *item = QgsFileBasedDataItemProvider::createLayerItemForPath( filePath, this, { QStringLiteral( "gdal" ), QStringLiteral( "ogr" )}, extraUriParts, Qgis::SublayerQueryFlag::FastScan ) )
{
item->setCapabilities( item->capabilities2() | Qgis::BrowserItemCapability::ReadOnly );
children.append( item );
}
}
}
return children;
}
//
// QgsGdalCloudDirectoryItem
//
QgsGdalCloudDirectoryItem::QgsGdalCloudDirectoryItem( QgsDataItem *parent, QString name, QString path, const QString &connectionName, const QString &directory )
: QgsDataCollectionItem( parent, name, path, connectionName )
, mConnName( connectionName )
, mDirectory( directory )
{
mIconName = QStringLiteral( "mIconFolder.svg" );
mCapabilities |= Qgis::BrowserItemCapability::Fertile;
}
QVector<QgsDataItem *> QgsGdalCloudDirectoryItem::createChildren()
{
QVector<QgsDataItem *> children;
const QgsGdalCloudProviderConnection::Data connectionData = QgsGdalCloudProviderConnection::connection( mConnName );
QgsGdalCloudProviderConnection conn = QgsGdalCloudProviderConnection( mConnName );
const QList< QgsGdalCloudProviderConnection::DirectoryObject > objects = conn.contents( mDirectory );
QVariantMap extraUriParts;
if ( !connectionData.credentialOptions.isEmpty() )
{
extraUriParts.insert( QStringLiteral( "credentialOptions" ), connectionData.credentialOptions );
}
for ( const QgsGdalCloudProviderConnection::DirectoryObject &object : objects )
{
const QString subPath = mDirectory + '/' + object.name;
if ( object.isDir )
{
QgsGdalCloudDirectoryItem *child = new QgsGdalCloudDirectoryItem( this,
object.name, mPath + '/' + object.name, mConnName, subPath );
children.append( child );
}
else if ( object.isFile )
{
const QString filePath = QStringLiteral( "/%1/%2/%3" ).arg( connectionData.vsiHandler, connectionData.container, subPath );
// QgsFileBasedDataItemProvider uses paths for item uris by default, so we need to specify that the credentialOptions should be appended to the layer URIs
if ( QgsDataItem *item = QgsFileBasedDataItemProvider::createLayerItemForPath( filePath, this, { QStringLiteral( "gdal" ), QStringLiteral( "ogr" )}, extraUriParts, Qgis::SublayerQueryFlag::FastScan ) )
{
item->setCapabilities( item->capabilities2() | Qgis::BrowserItemCapability::ReadOnly );
children.append( item );
}
}
}
return children;
}
//
// QgsGdalCloudDataItemProvider
//
QString QgsGdalCloudDataItemProvider::name()
{
return QStringLiteral( "GDAL Cloud" );
}
QString QgsGdalCloudDataItemProvider::dataProviderKey() const
{
return QStringLiteral( "cloud" );
}
Qgis::DataItemProviderCapabilities QgsGdalCloudDataItemProvider::capabilities() const
{
return Qgis::DataItemProviderCapability::NetworkSources;
}
QgsDataItem *QgsGdalCloudDataItemProvider::createDataItem( const QString &path, QgsDataItem *parentItem )
{
if ( path.isEmpty() )
return new QgsGdalCloudRootItem( parentItem, QObject::tr( "Cloud" ), QStringLiteral( "cloud:" ) );
return nullptr;
}
///@endcond

View File

@ -0,0 +1,89 @@
/***************************************************************************
qgsgdalclouddataitems.h
---------------------
begin : June 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSGDALCLOUDDATAITEMS_H
#define QGSGDALCLOUDDATAITEMS_H
#include "qgsconnectionsitem.h"
#include "qgsdataitemprovider.h"
#include "qgsgdalutils.h"
///@cond PRIVATE
#define SIP_NO_FILE
//! Root item for GDAL cloud connections
class CORE_EXPORT QgsGdalCloudRootItem : public QgsConnectionsRootItem
{
Q_OBJECT
public:
QgsGdalCloudRootItem( QgsDataItem *parent, QString name, QString path );
QVector<QgsDataItem *> createChildren() override;
QVariant sortKey() const override { return 8; }
};
//! Generic item for individual GDAL cloud providers, eg "Amazon S3", "Microsoft Azure"
class CORE_EXPORT QgsGdalCloudProviderItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsGdalCloudProviderItem( QgsDataItem *parent, const QgsGdalUtils::VsiNetworkFileSystemDetails &handler );
QVector<QgsDataItem *> createChildren() override;
const QgsGdalUtils::VsiNetworkFileSystemDetails &vsiHandler() const { return mVsiHandler; }
private:
QgsGdalUtils::VsiNetworkFileSystemDetails mVsiHandler;
};
//! Item for a specific connection to a cloud container
class CORE_EXPORT QgsGdalCloudConnectionItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsGdalCloudConnectionItem( QgsDataItem *parent, const QString &name, const QString &path );
bool equal( const QgsDataItem *other ) override;
QVector<QgsDataItem *> createChildren() override;
private:
QString mConnName;
};
//! Item for a directory within a cloud container
class CORE_EXPORT QgsGdalCloudDirectoryItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsGdalCloudDirectoryItem( QgsDataItem *parent, QString name, QString path, const QString &connectionName, const QString &directory );
QVector<QgsDataItem *> createChildren() override;
private:
QString mConnName;
QString mDirectory;
};
//! Provider for GDAL cloud root data item
class QgsGdalCloudDataItemProvider : public QgsDataItemProvider
{
public:
QString name() override;
QString dataProviderKey() const override;
Qgis::DataItemProviderCapabilities capabilities() const override;
QgsDataItem *createDataItem( const QString &path, QgsDataItem *parentItem ) override;
};
///@endcond
#endif // QGSGDALCLOUDDATAITEMS_H

View File

@ -42,6 +42,7 @@
#include "qgsproviderutils.h"
#include "qgscplerrorhandler_p.h"
#include "qgsmetadatautils.h"
#include "qgsgdalclouddataitems.h"
#include <QImage>
#include <QColor>
@ -4627,6 +4628,14 @@ QList<Qgis::LayerType> QgsGdalProviderMetadata::supportedLayerTypes() const
return { Qgis::LayerType::Raster };
}
QList<QgsDataItemProvider *> QgsGdalProviderMetadata::dataItemProviders() const
{
return
{
new QgsGdalCloudDataItemProvider()
};
}
int QgsGdalProviderMetadata::listStyles( const QString &uri, QStringList &ids, QStringList &names,
QStringList &descriptions, QString &errCause )
{

View File

@ -410,6 +410,7 @@ class QgsGdalProviderMetadata final: public QgsProviderMetadata
QStringList sidecarFilesForUri( const QString &uri ) const override;
QList< Qgis::LayerType > supportedLayerTypes() const override;
QList< QgsDataItemProvider * > dataItemProviders() const override;
int listStyles( const QString &uri, QStringList &ids, QStringList &names,
QStringList &descriptions, QString &errCause ) override;
bool styleExists( const QString &uri, const QString &styleId, QString &errCause SIP_OUT ) override;