mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-03 00:04:47 -04:00
Compare commits
12 Commits
b5e695f3ad
...
1e7e899d09
Author | SHA1 | Date | |
---|---|---|---|
|
1e7e899d09 | ||
|
4409d0726d | ||
|
0a5c968df2 | ||
|
31b4cccc04 | ||
|
18e7b2c1c8 | ||
|
71867e5c67 | ||
|
bb3ed1ee31 | ||
|
29e33c0ae7 | ||
|
3aca600332 | ||
|
e396939ab4 | ||
|
88d224f64c | ||
|
337ec4c116 |
@ -76,9 +76,32 @@ Returns the format name for cloud optimized formats
|
||||
QgsMimeDataUtils::Uri uri() const;
|
||||
%Docstring
|
||||
Returns a uri for the asset if it is a cloud optimized file like COG or
|
||||
COPC
|
||||
COPC, empty auth configuration
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QgsMimeDataUtils::Uri uri( const QString &authcfg ) const;
|
||||
%Docstring
|
||||
Returns a uri for the asset if it is a cloud optimized file like COG or
|
||||
COPC
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
QString toHtml( const QString &assetId ) const;
|
||||
%Docstring
|
||||
Returns an HTML representation of the STAC Asset including its ID within
|
||||
its container
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
bool isDownloadable() const;
|
||||
%Docstring
|
||||
Returns whether the asset can be downloaded
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
};
|
||||
|
@ -82,14 +82,12 @@ Sets the item's additional metadata to ``properties``
|
||||
|
||||
QMap< QString, QgsStacAsset > assets() const;
|
||||
%Docstring
|
||||
Returns a dictionary of asset objects that can be downloaded, each with
|
||||
a unique key.
|
||||
Returns a dictionary of asset objects, each with a unique key.
|
||||
%End
|
||||
|
||||
void setAssets( const QMap< QString, QgsStacAsset > &assets );
|
||||
%Docstring
|
||||
Sets the ``asset`` objects that can be downloaded, each with a unique
|
||||
key.
|
||||
Sets the ``asset`` objects, each with a unique key.
|
||||
%End
|
||||
|
||||
QString collection() const;
|
||||
|
@ -76,9 +76,32 @@ Returns the format name for cloud optimized formats
|
||||
QgsMimeDataUtils::Uri uri() const;
|
||||
%Docstring
|
||||
Returns a uri for the asset if it is a cloud optimized file like COG or
|
||||
COPC
|
||||
COPC, empty auth configuration
|
||||
|
||||
.. versionadded:: 3.42
|
||||
%End
|
||||
|
||||
QgsMimeDataUtils::Uri uri( const QString &authcfg ) const;
|
||||
%Docstring
|
||||
Returns a uri for the asset if it is a cloud optimized file like COG or
|
||||
COPC
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
QString toHtml( const QString &assetId ) const;
|
||||
%Docstring
|
||||
Returns an HTML representation of the STAC Asset including its ID within
|
||||
its container
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
bool isDownloadable() const;
|
||||
%Docstring
|
||||
Returns whether the asset can be downloaded
|
||||
|
||||
.. versionadded:: 4.0
|
||||
%End
|
||||
|
||||
};
|
||||
|
@ -82,14 +82,12 @@ Sets the item's additional metadata to ``properties``
|
||||
|
||||
QMap< QString, QgsStacAsset > assets() const;
|
||||
%Docstring
|
||||
Returns a dictionary of asset objects that can be downloaded, each with
|
||||
a unique key.
|
||||
Returns a dictionary of asset objects, each with a unique key.
|
||||
%End
|
||||
|
||||
void setAssets( const QMap< QString, QgsStacAsset > &assets );
|
||||
%Docstring
|
||||
Sets the ``asset`` objects that can be downloaded, each with a unique
|
||||
key.
|
||||
Sets the ``asset`` objects, each with a unique key.
|
||||
%End
|
||||
|
||||
QString collection() const;
|
||||
|
@ -60,7 +60,8 @@ bool QgsStacAsset::isCloudOptimized() const
|
||||
const QString format = formatName();
|
||||
return format == QLatin1String( "COG" ) ||
|
||||
format == QLatin1String( "COPC" ) ||
|
||||
format == QLatin1String( "EPT" );
|
||||
format == QLatin1String( "EPT" ) ||
|
||||
format == QLatin1String( "Zarr" );
|
||||
}
|
||||
|
||||
QString QgsStacAsset::formatName() const
|
||||
@ -72,10 +73,18 @@ QString QgsStacAsset::formatName() const
|
||||
return QStringLiteral( "COPC" );
|
||||
else if ( mHref.endsWith( QLatin1String( "/ept.json" ) ) )
|
||||
return QStringLiteral( "EPT" );
|
||||
else if ( mMediaType == QLatin1String( "application/vnd+zarr" ) )
|
||||
return QStringLiteral( "Zarr" );
|
||||
return QString();
|
||||
}
|
||||
|
||||
QgsMimeDataUtils::Uri QgsStacAsset::uri() const
|
||||
{
|
||||
return uri( QString() );
|
||||
}
|
||||
|
||||
|
||||
QgsMimeDataUtils::Uri QgsStacAsset::uri( const QString &authcfg ) const
|
||||
{
|
||||
QgsMimeDataUtils::Uri uri;
|
||||
QUrl url( href() );
|
||||
@ -87,6 +96,8 @@ QgsMimeDataUtils::Uri QgsStacAsset::uri() const
|
||||
href().startsWith( QLatin1String( "ftp" ), Qt::CaseInsensitive ) )
|
||||
{
|
||||
uri.uri = QStringLiteral( "/vsicurl/%1" ).arg( href() );
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
else if ( href().startsWith( QLatin1String( "s3://" ), Qt::CaseInsensitive ) )
|
||||
{
|
||||
@ -102,12 +113,37 @@ QgsMimeDataUtils::Uri QgsStacAsset::uri() const
|
||||
uri.layerType = QStringLiteral( "pointcloud" );
|
||||
uri.providerKey = QStringLiteral( "copc" );
|
||||
uri.uri = href();
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
else if ( formatName() == QLatin1String( "EPT" ) )
|
||||
{
|
||||
uri.layerType = QStringLiteral( "pointcloud" );
|
||||
uri.providerKey = QStringLiteral( "ept" );
|
||||
uri.uri = href();
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
else if ( formatName() == QLatin1String( "Zarr" ) )
|
||||
{
|
||||
uri.layerType = QStringLiteral( "raster" );
|
||||
uri.providerKey = QStringLiteral( "gdal" );
|
||||
if ( href().startsWith( QLatin1String( "http" ), Qt::CaseInsensitive ) ||
|
||||
href().startsWith( QLatin1String( "ftp" ), Qt::CaseInsensitive ) )
|
||||
{
|
||||
uri.uri = QStringLiteral( "ZARR:\"/vsicurl/%1\"" ).arg( href() );
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
else if ( href().startsWith( QLatin1String( "s3://" ), Qt::CaseInsensitive ) )
|
||||
{
|
||||
// Remove the s3:// protocol prefix for compatibility with GDAL's /vsis3
|
||||
uri.uri = QStringLiteral( "ZARR:\"/vsis3/%1\"" ).arg( href().mid( 5 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
uri.uri = href();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -118,3 +154,34 @@ QgsMimeDataUtils::Uri QgsStacAsset::uri() const
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
QString QgsStacAsset::toHtml( const QString &assetId ) const
|
||||
{
|
||||
QString html = QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Asset" ) );
|
||||
html += QStringLiteral( "<table class=\"list-view\">\n" );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), assetId );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "title" ), title() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "description" ), description() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td><a href=\"%2\">%2</a></td></tr>\n" ).arg( QStringLiteral( "url" ), href() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "type" ), mediaType() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "roles" ), roles().join( ',' ) );
|
||||
html += QStringLiteral( "</table><br/>\n" );
|
||||
return html;
|
||||
}
|
||||
|
||||
bool QgsStacAsset::isDownloadable() const
|
||||
{
|
||||
/*
|
||||
* Directory-based data types like Zarr should not offer downloads.
|
||||
* Download attempts might
|
||||
* - fail with 4xx,
|
||||
* - succeed but download an HTML directory listing response, or
|
||||
* - something else that does not meet the user's needs.
|
||||
*/
|
||||
if ( formatName() == QLatin1String( "Zarr" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -73,11 +73,29 @@ class CORE_EXPORT QgsStacAsset
|
||||
QString formatName() const;
|
||||
|
||||
/**
|
||||
* Returns a uri for the asset if it is a cloud optimized file like COG or COPC
|
||||
* Returns a uri for the asset if it is a cloud optimized file like COG or COPC, empty auth configuration
|
||||
* \since QGIS 3.42
|
||||
*/
|
||||
QgsMimeDataUtils::Uri uri() const;
|
||||
|
||||
/**
|
||||
* Returns a uri for the asset if it is a cloud optimized file like COG or COPC
|
||||
* \since QGIS 4.0
|
||||
*/
|
||||
QgsMimeDataUtils::Uri uri( const QString &authcfg ) const;
|
||||
|
||||
/**
|
||||
* Returns an HTML representation of the STAC Asset including its ID within its container
|
||||
* \since QGIS 4.0
|
||||
*/
|
||||
QString toHtml( const QString &assetId ) const;
|
||||
|
||||
/**
|
||||
* Returns whether the asset can be downloaded
|
||||
* \since QGIS 4.0
|
||||
*/
|
||||
bool isDownloadable() const;
|
||||
|
||||
private:
|
||||
QString mHref;
|
||||
QString mTitle;
|
||||
|
@ -27,6 +27,59 @@
|
||||
constexpr int MAX_DISPLAYED_ITEMS = 20;
|
||||
|
||||
|
||||
//
|
||||
// QgsStacAssetItem
|
||||
//
|
||||
|
||||
QgsStacAssetItem::QgsStacAssetItem( QgsDataItem *parent, const QString &name, const QgsStacAsset *asset )
|
||||
: QgsDataItem( Qgis::BrowserItemType::Custom, parent, name, QString( "%1/%2" ).arg( parent->path(), name ), QStringLiteral( "special:Stac" ) ),
|
||||
mStacAsset( asset ),
|
||||
mName( name )
|
||||
{
|
||||
mIconName = QStringLiteral( "mActionPropertiesWidget.svg" );
|
||||
updateToolTip();
|
||||
setState( Qgis::BrowserItemState::Populated );
|
||||
}
|
||||
|
||||
bool QgsStacAssetItem::hasDragEnabled() const
|
||||
{
|
||||
return mStacAsset->isCloudOptimized();
|
||||
}
|
||||
|
||||
QgsMimeDataUtils::UriList QgsStacAssetItem::mimeUris() const
|
||||
{
|
||||
QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( parent() );
|
||||
const QString authcfg = itemItem->stacController()->authCfg();
|
||||
|
||||
QgsMimeDataUtils::Uri uri;
|
||||
QUrl url( mStacAsset->href() );
|
||||
if ( url.isLocalFile() )
|
||||
{
|
||||
uri.uri = mStacAsset->href();
|
||||
}
|
||||
else
|
||||
{
|
||||
uri = mStacAsset->uri( authcfg );
|
||||
}
|
||||
|
||||
return { uri };
|
||||
}
|
||||
|
||||
bool QgsStacAssetItem::equal( const QgsDataItem * )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void QgsStacAssetItem::updateToolTip()
|
||||
{
|
||||
QString title = mStacAsset->title();
|
||||
if ( title.isNull() || title.isEmpty() )
|
||||
{
|
||||
title = mName;
|
||||
}
|
||||
mToolTip = QStringLiteral( "STAC Asset:\n%1\n%2" ).arg( title, mStacAsset->href() );
|
||||
}
|
||||
|
||||
//
|
||||
// QgsStacFetchMoreItem
|
||||
//
|
||||
@ -53,7 +106,6 @@ bool QgsStacFetchMoreItem::handleDoubleClick()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsStacItemItem
|
||||
//
|
||||
@ -74,7 +126,15 @@ QVector<QgsDataItem *> QgsStacItemItem::createChildren()
|
||||
if ( !mStacItem )
|
||||
return { new QgsErrorItem( this, error, path() + QStringLiteral( "/error" ) ) };
|
||||
|
||||
return {};
|
||||
QVector<QgsDataItem *> contents;
|
||||
contents.reserve( mStacItem->assets().size() );
|
||||
const QMap<QString, QgsStacAsset> assets = mStacItem->assets();
|
||||
for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it )
|
||||
{
|
||||
QgsStacAssetItem *assetItem = new QgsStacAssetItem( this, it.key(), &it.value() );
|
||||
contents.append( assetItem );
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
bool QgsStacItemItem::hasDragEnabled() const
|
||||
@ -109,44 +169,10 @@ QgsMimeDataUtils::UriList QgsStacItemItem::mimeUris() const
|
||||
{
|
||||
uri.uri = it->href();
|
||||
}
|
||||
else if ( it->mediaType() == QLatin1String( "image/tiff; application=geotiff; profile=cloud-optimized" ) ||
|
||||
it->mediaType() == QLatin1String( "image/vnd.stac.geotiff; cloud-optimized=true" ) )
|
||||
else
|
||||
{
|
||||
uri.layerType = QStringLiteral( "raster" );
|
||||
uri.providerKey = QStringLiteral( "gdal" );
|
||||
if ( it->href().startsWith( QLatin1String( "http" ), Qt::CaseInsensitive ) ||
|
||||
it->href().startsWith( QLatin1String( "ftp" ), Qt::CaseInsensitive ) )
|
||||
{
|
||||
uri.uri = QStringLiteral( "/vsicurl/%1" ).arg( it->href() );
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
else if ( it->href().startsWith( QLatin1String( "s3://" ), Qt::CaseInsensitive ) )
|
||||
{
|
||||
uri.uri = QStringLiteral( "/vsis3/%1" ).arg( it->href().mid( 5 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
uri.uri = it->href();
|
||||
}
|
||||
uri = it->uri( authcfg );
|
||||
}
|
||||
else if ( it->mediaType() == QLatin1String( "application/vnd.laszip+copc" ) )
|
||||
{
|
||||
uri.layerType = QStringLiteral( "pointcloud" );
|
||||
uri.providerKey = QStringLiteral( "copc" );
|
||||
uri.uri = it->href();
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
else if ( it->href().endsWith( QLatin1String( "/ept.json" ) ) )
|
||||
{
|
||||
uri.layerType = QStringLiteral( "pointcloud" );
|
||||
uri.providerKey = QStringLiteral( "ept" );
|
||||
uri.uri = it->href();
|
||||
if ( !authcfg.isEmpty() )
|
||||
uri.uri.append( QStringLiteral( " authcfg='%1'" ).arg( authcfg ) );
|
||||
}
|
||||
uri.name = it->title().isEmpty() ? url.fileName() : it->title();
|
||||
uris.append( uri );
|
||||
}
|
||||
|
||||
@ -210,7 +236,6 @@ void QgsStacItemItem::itemRequestFinished( int requestId, QString error )
|
||||
mIconName = QStringLiteral( "/mIconDelete.svg" );
|
||||
mName = error;
|
||||
}
|
||||
setState( Qgis::BrowserItemState::Populated );
|
||||
}
|
||||
|
||||
|
||||
@ -498,6 +523,8 @@ QVector< QgsDataItem * > QgsStacCatalogItem::createItems( const QVector<QgsStacI
|
||||
|
||||
QgsStacItemItem *i = new QgsStacItemItem( this, name, item->url() );
|
||||
i->setStacItem( std::move( object ) );
|
||||
// create any assets beneath the item, so that they can be individually drag-dropped as layers if compatible
|
||||
i->populate( true );
|
||||
i->setState( Qgis::BrowserItemState::Populated );
|
||||
contents.append( i );
|
||||
}
|
||||
|
@ -30,6 +30,29 @@ class QgsStacCollection;
|
||||
///@cond PRIVATE
|
||||
#define SIP_NO_FILE
|
||||
|
||||
|
||||
/**
|
||||
* \brief Item for STAC Asset within a collection or item.
|
||||
* \since QGIS 4.0
|
||||
*/
|
||||
class CORE_EXPORT QgsStacAssetItem : public QgsDataItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QgsStacAssetItem( QgsDataItem *parent, const QString &name, const QgsStacAsset *asset );
|
||||
|
||||
bool hasDragEnabled() const override;
|
||||
QgsMimeDataUtils::UriList mimeUris() const override;
|
||||
bool equal( const QgsDataItem *other ) override;
|
||||
QVariant sortKey() const override { return QStringLiteral( "4 %1" ).arg( mName ); }
|
||||
void updateToolTip();
|
||||
const QgsStacAsset *stacAsset() { return mStacAsset; }
|
||||
|
||||
private:
|
||||
const QgsStacAsset *mStacAsset;
|
||||
const QString mName;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Item to display that there are additional STAC items which are not loaded.
|
||||
* \since QGIS 3.40
|
||||
|
@ -38,9 +38,7 @@ Qgis::StacObjectType QgsStacItem::type() const
|
||||
|
||||
QString QgsStacItem::toHtml() const
|
||||
{
|
||||
QString html = QStringLiteral( "<html><head></head>\n<body>\n" );
|
||||
|
||||
html += QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Item" ) );
|
||||
QString html = QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Item" ) );
|
||||
html += QLatin1String( "<table class=\"list-view\">\n" );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), id() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "stac_version" ), stacVersion() );
|
||||
@ -95,18 +93,9 @@ QString QgsStacItem::toHtml() const
|
||||
html += QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Assets" ) );
|
||||
for ( auto it = mAssets.constBegin(); it != mAssets.constEnd(); ++it )
|
||||
{
|
||||
html += QLatin1String( "<table class=\"list-view\">\n" );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), it.key() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "title" ), it->title() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "description" ), it->description() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td><a href=\"%2\">%2</a></td></tr>\n" ).arg( QStringLiteral( "url" ), it->href() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "type" ), it->mediaType() );
|
||||
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "roles" ), it->roles().join( ',' ) );
|
||||
html += QLatin1String( "</table><br/>\n" );
|
||||
html += it->toHtml( it.key() );
|
||||
}
|
||||
}
|
||||
|
||||
html += QLatin1String( "\n</body>\n</html>\n" );
|
||||
return html;
|
||||
}
|
||||
|
||||
|
@ -76,10 +76,10 @@ class CORE_EXPORT QgsStacItem : public QgsStacObject
|
||||
//! Sets the item's additional metadata to \a properties
|
||||
void setProperties( const QVariantMap &properties );
|
||||
|
||||
//! Returns a dictionary of asset objects that can be downloaded, each with a unique key.
|
||||
//! Returns a dictionary of asset objects, each with a unique key.
|
||||
QMap< QString, QgsStacAsset > assets() const;
|
||||
|
||||
//! Sets the \a asset objects that can be downloaded, each with a unique key.
|
||||
//! Sets the \a asset objects, each with a unique key.
|
||||
void setAssets( const QMap< QString, QgsStacAsset > &assets );
|
||||
|
||||
//! Returns the id of the STAC Collection this Item references to
|
||||
|
@ -89,15 +89,41 @@ void QgsStacDataItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *
|
||||
{
|
||||
menu->addSeparator();
|
||||
|
||||
QAction *actionDownload = new QAction( tr( "Download Assets…" ), menu );
|
||||
connect( actionDownload, &QAction::triggered, this, [itemItem, context] { downloadAssets( itemItem, context ); } );
|
||||
menu->addAction( actionDownload );
|
||||
int downloadableAssets = 0;
|
||||
const QMap<QString, QgsStacAsset> assets = itemItem->stacItem()->assets();
|
||||
for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it )
|
||||
{
|
||||
if ( it.value().isDownloadable() )
|
||||
{
|
||||
downloadableAssets += 1;
|
||||
}
|
||||
}
|
||||
if ( downloadableAssets > 0 )
|
||||
{
|
||||
QAction *actionDownload = new QAction( tr( "Download Assets…" ), menu );
|
||||
connect( actionDownload, &QAction::triggered, this, [itemItem, context] { downloadAssets( itemItem, context ); } );
|
||||
menu->addAction( actionDownload );
|
||||
}
|
||||
|
||||
QAction *actionDetails = new QAction( tr( "Details…" ), menu );
|
||||
connect( actionDetails, &QAction::triggered, this, [itemItem] { showDetails( itemItem ); } );
|
||||
menu->addAction( actionDetails );
|
||||
}
|
||||
}
|
||||
|
||||
if ( QgsStacAssetItem *assetItem = qobject_cast<QgsStacAssetItem *>( item ) )
|
||||
{
|
||||
if ( assetItem->stacAsset()->isDownloadable() )
|
||||
{
|
||||
QAction *actionDownload = new QAction( tr( "Download Asset…" ), menu );
|
||||
connect( actionDownload, &QAction::triggered, this, [assetItem, context] { downloadAssets( assetItem, context ); } );
|
||||
menu->addAction( actionDownload );
|
||||
}
|
||||
|
||||
QAction *actionDetails = new QAction( tr( "Details…" ), menu );
|
||||
connect( actionDetails, &QAction::triggered, this, [assetItem] { showDetails( assetItem ); } );
|
||||
menu->addAction( actionDetails );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsStacDataItemGuiProvider::editConnection( QgsDataItem *item )
|
||||
@ -162,37 +188,56 @@ void QgsStacDataItemGuiProvider::loadConnections( QgsDataItem *item )
|
||||
|
||||
void QgsStacDataItemGuiProvider::showDetails( QgsDataItem *item )
|
||||
{
|
||||
QgsStacObject *obj = nullptr;
|
||||
QString authcfg;
|
||||
|
||||
if ( QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( item ) )
|
||||
QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( item );
|
||||
QgsStacCatalogItem *catalogItem = qobject_cast<QgsStacCatalogItem *>( item );
|
||||
QgsStacAssetItem *assetItem = qobject_cast<QgsStacAssetItem *>( item );
|
||||
|
||||
if ( !( itemItem || catalogItem || assetItem ) )
|
||||
{
|
||||
obj = itemItem->stacItem();
|
||||
authcfg = itemItem->stacController()->authCfg();
|
||||
}
|
||||
else if ( QgsStacCatalogItem *catalogItem = qobject_cast<QgsStacCatalogItem *>( item ) )
|
||||
{
|
||||
obj = catalogItem->stacCatalog();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( obj )
|
||||
QgsStacObjectDetailsDialog d;
|
||||
if ( itemItem )
|
||||
{
|
||||
QgsStacObjectDetailsDialog d;
|
||||
d.setAuthcfg( authcfg );
|
||||
d.setStacObject( obj );
|
||||
d.exec();
|
||||
authcfg = itemItem->stacController()->authCfg();
|
||||
d.setContentFromStacObject( itemItem->stacItem() );
|
||||
}
|
||||
else if ( catalogItem )
|
||||
{
|
||||
d.setContentFromStacObject( catalogItem->stacCatalog() );
|
||||
}
|
||||
else if ( assetItem )
|
||||
{
|
||||
QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( assetItem->parent() );
|
||||
authcfg = itemItem->stacController()->authCfg();
|
||||
d.setContentFromStacAsset( assetItem->name(), assetItem->stacAsset() );
|
||||
}
|
||||
d.setAuthcfg( authcfg );
|
||||
d.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
void QgsStacDataItemGuiProvider::downloadAssets( QgsDataItem *item, QgsDataItemGuiContext context )
|
||||
{
|
||||
QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( item );
|
||||
QgsStacAssetItem *assetItem = qobject_cast<QgsStacAssetItem *>( item );
|
||||
|
||||
if ( !itemItem )
|
||||
if ( !( itemItem || assetItem ) )
|
||||
return;
|
||||
|
||||
QgsStacDownloadAssetsDialog dialog;
|
||||
dialog.setStacItem( itemItem->stacItem() );
|
||||
if ( itemItem )
|
||||
{
|
||||
dialog.setStacItem( itemItem->stacItem() );
|
||||
}
|
||||
else if ( assetItem )
|
||||
{
|
||||
itemItem = qobject_cast<QgsStacItemItem *>( assetItem->parent() );
|
||||
dialog.addStacAsset( assetItem->name(), assetItem->stacAsset() );
|
||||
}
|
||||
dialog.setMessageBar( context.messageBar() );
|
||||
dialog.setAuthCfg( itemItem->stacController()->authCfg() );
|
||||
dialog.exec();
|
||||
|
@ -150,25 +150,34 @@ void QgsStacDownloadAssetsDialog::setStacItem( QgsStacItem *stacItem )
|
||||
const QMap<QString, QgsStacAsset> assets = stacItem->assets();
|
||||
for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it )
|
||||
{
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem();
|
||||
item->setText( 0, it.key() );
|
||||
item->setToolTip( 0, it.key() );
|
||||
item->setCheckState( 0, Qt::Checked );
|
||||
item->setText( 1, it->title() );
|
||||
item->setToolTip( 1, it->title() );
|
||||
item->setText( 2, it->description() );
|
||||
item->setToolTip( 2, it->description() );
|
||||
item->setText( 3, it->roles().join( "," ) );
|
||||
item->setToolTip( 3, it->roles().join( "," ) );
|
||||
item->setText( 4, it->mediaType() );
|
||||
item->setToolTip( 4, it->mediaType() );
|
||||
item->setText( 5, it->href() );
|
||||
item->setToolTip( 5, it->href() );
|
||||
|
||||
mTreeWidget->addTopLevelItem( item );
|
||||
if ( it.value().isDownloadable() )
|
||||
{
|
||||
addStacAsset( it.key(), &it.value() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsStacDownloadAssetsDialog::addStacAsset( const QString &assetId, const QgsStacAsset *stacAsset )
|
||||
{
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem();
|
||||
|
||||
item->setText( 0, assetId );
|
||||
item->setToolTip( 0, assetId );
|
||||
item->setCheckState( 0, Qt::Checked );
|
||||
item->setText( 1, stacAsset->title() );
|
||||
item->setToolTip( 1, stacAsset->title() );
|
||||
item->setText( 2, stacAsset->description() );
|
||||
item->setToolTip( 2, stacAsset->description() );
|
||||
item->setText( 3, stacAsset->roles().join( "," ) );
|
||||
item->setToolTip( 3, stacAsset->roles().join( "," ) );
|
||||
item->setText( 4, stacAsset->mediaType() );
|
||||
item->setToolTip( 4, stacAsset->mediaType() );
|
||||
item->setText( 5, stacAsset->href() );
|
||||
item->setToolTip( 5, stacAsset->href() );
|
||||
|
||||
mTreeWidget->addTopLevelItem( item );
|
||||
}
|
||||
|
||||
QString QgsStacDownloadAssetsDialog::selectedFolder()
|
||||
{
|
||||
return mFileWidget->filePath();
|
||||
|
@ -38,6 +38,7 @@ class QgsStacDownloadAssetsDialog : public QDialog, private Ui::QgsStacDownloadA
|
||||
void setAuthCfg( const QString &authCfg );
|
||||
void setMessageBar( QgsMessageBar *bar );
|
||||
void setStacItem( QgsStacItem *stacItem );
|
||||
void addStacAsset( const QString &assetId, const QgsStacAsset *stacAsset );
|
||||
QString selectedFolder();
|
||||
QStringList selectedUrls();
|
||||
|
||||
|
@ -31,7 +31,8 @@ QgsStacObjectDetailsDialog::QgsStacObjectDetailsDialog( QWidget *parent )
|
||||
QgsGui::enableAutoGeometryRestore( this );
|
||||
}
|
||||
|
||||
void QgsStacObjectDetailsDialog::setStacObject( QgsStacObject *stacObject )
|
||||
|
||||
void QgsStacObjectDetailsDialog::setContentFromStacObject( QgsStacObject *stacObject )
|
||||
{
|
||||
if ( !stacObject )
|
||||
return;
|
||||
@ -42,28 +43,40 @@ void QgsStacObjectDetailsDialog::setStacObject( QgsStacObject *stacObject )
|
||||
const QMap<QString, QgsStacAsset> assets = item->assets();
|
||||
for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it )
|
||||
{
|
||||
if ( it->roles().contains( QLatin1String( "thumbnail" ) ) )
|
||||
if ( isThumbnailAsset( &it.value() ) )
|
||||
{
|
||||
QString uri = it->href();
|
||||
if ( !mAuthcfg.isEmpty() )
|
||||
{
|
||||
QStringList connectionItems;
|
||||
connectionItems << uri;
|
||||
QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, mAuthcfg );
|
||||
uri = connectionItems.first();
|
||||
}
|
||||
|
||||
thumbnails.append( QStringLiteral( "<img src=\"%1\" border=1><br>" ).arg( uri ) );
|
||||
thumbnails.append( thumbnailHtmlContent( &it.value() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QString myStyle = QgsApplication::reportStyleSheet( QgsApplication::StyleSheetType::WebBrowser );
|
||||
// inject thumbnails
|
||||
QString html = stacObject->toHtml().replace( QLatin1String( "<head>" ), QStringLiteral( "<head>\n%1" ).arg( thumbnails.join( QString() ) ) );
|
||||
// inject stylesheet
|
||||
html = html.replace( QLatin1String( "<head>" ), QStringLiteral( R"raw(<head><style type="text/css">%1</style>)raw" ) ).arg( myStyle );
|
||||
QString thumbnailHtml = thumbnails.join( QString() );
|
||||
QString bodyHtml = stacObject->toHtml();
|
||||
setContent( bodyHtml, thumbnailHtml );
|
||||
}
|
||||
|
||||
|
||||
void QgsStacObjectDetailsDialog::setContentFromStacAsset( const QString &assetId, const QgsStacAsset *stacAsset )
|
||||
{
|
||||
QString thumbnailHtml;
|
||||
if ( isThumbnailAsset( stacAsset ) )
|
||||
{
|
||||
thumbnailHtml = thumbnailHtmlContent( stacAsset );
|
||||
}
|
||||
QString bodyHtml = stacAsset->toHtml( assetId );
|
||||
setContent( bodyHtml, thumbnailHtml );
|
||||
}
|
||||
|
||||
|
||||
void QgsStacObjectDetailsDialog::setContent( QString bodyHtml, QString thumbnailHtml )
|
||||
{
|
||||
const QString myStyle = QgsApplication::reportStyleSheet( QgsApplication::StyleSheetType::WebBrowser );
|
||||
QString html = QStringLiteral( "<html>\n<head>\n" );
|
||||
html += QStringLiteral( "<style type=\"text/css\">%1</style>\n" ).arg( myStyle );
|
||||
html += QStringLiteral( "%1\n" ).arg( thumbnailHtml );
|
||||
html += QStringLiteral( "</head>\n<body>\n" );
|
||||
html += QStringLiteral( "%1\n" ).arg( bodyHtml );
|
||||
html += QLatin1String( "</body>\n</html>\n" );
|
||||
mWebView->page()->setLinkDelegationPolicy( QWebPage::LinkDelegationPolicy::DelegateAllLinks );
|
||||
connect( mWebView, &QgsWebView::linkClicked, this, []( const QUrl &url ) {
|
||||
QDesktopServices::openUrl( url );
|
||||
@ -76,4 +89,22 @@ void QgsStacObjectDetailsDialog::setAuthcfg( const QString &authcfg )
|
||||
mAuthcfg = authcfg;
|
||||
}
|
||||
|
||||
bool QgsStacObjectDetailsDialog::isThumbnailAsset( const QgsStacAsset *stacAsset )
|
||||
{
|
||||
return stacAsset->roles().contains( QLatin1String( "thumbnail" ) );
|
||||
}
|
||||
|
||||
QString QgsStacObjectDetailsDialog::thumbnailHtmlContent( const QgsStacAsset *stacAsset )
|
||||
{
|
||||
QString uri = stacAsset->href();
|
||||
if ( !mAuthcfg.isEmpty() )
|
||||
{
|
||||
QStringList connectionItems;
|
||||
connectionItems << uri;
|
||||
QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, mAuthcfg );
|
||||
uri = connectionItems.first();
|
||||
}
|
||||
return QStringLiteral( "<img src=\"%1\" border=1><br>" ).arg( uri );
|
||||
}
|
||||
|
||||
///@endcond
|
||||
|
@ -19,6 +19,7 @@
|
||||
///@cond PRIVATE
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include "qgsstacasset.h"
|
||||
#include "qgsstacobject.h"
|
||||
#include "ui_qgsstacobjectdetailsdialog.h"
|
||||
|
||||
@ -31,12 +32,16 @@ class QgsStacObjectDetailsDialog : public QDialog, private Ui::QgsStacObjectDeta
|
||||
public:
|
||||
explicit QgsStacObjectDetailsDialog( QWidget *parent = nullptr );
|
||||
|
||||
void setStacObject( QgsStacObject *stacObject );
|
||||
|
||||
void setAuthcfg( const QString &authcfg );
|
||||
|
||||
void setContentFromStacObject( QgsStacObject *stacObject );
|
||||
void setContentFromStacAsset( const QString &assetId, const QgsStacAsset *stacAsset );
|
||||
|
||||
private:
|
||||
QString mAuthcfg;
|
||||
void setContent( QString bodyHtml, QString thumbnailHtml );
|
||||
bool isThumbnailAsset( const QgsStacAsset *stacAsset );
|
||||
QString thumbnailHtmlContent( const QgsStacAsset *stacAsset );
|
||||
};
|
||||
|
||||
///@endcond
|
||||
|
@ -142,7 +142,7 @@ void QgsStacSourceSelect::showItemDetails( const QModelIndex &index )
|
||||
{
|
||||
QgsStacObjectDetailsDialog details( this );
|
||||
details.setAuthcfg( mStac->authCfg() );
|
||||
details.setStacObject( index.data( QgsStacItemListModel::Role::StacObject ).value<QgsStacObject *>() );
|
||||
details.setContentFromStacObject( index.data( QgsStacItemListModel::Role::StacObject ).value<QgsStacObject *>() );
|
||||
details.exec();
|
||||
}
|
||||
|
||||
|
@ -172,11 +172,13 @@ void TestQgsStac::testParseLocalItem()
|
||||
QCOMPARE( item->description(), QStringLiteral( "A sample STAC Item that includes examples of all common metadata" ) );
|
||||
|
||||
const QgsMimeDataUtils::UriList uris = item->uris();
|
||||
QCOMPARE( uris.size(), 2 );
|
||||
QCOMPARE( uris.first().uri, QStringLiteral( "file://%1%2" ).arg( mDataDir, QStringLiteral( "20201211_223832_CS2_analytic.tif" ) ) );
|
||||
QCOMPARE( uris.first().name, QStringLiteral( "4-Band Analytic" ) );
|
||||
QCOMPARE( uris.last().uri, QStringLiteral( "/vsicurl/https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif" ) );
|
||||
QCOMPARE( uris.last().name, QStringLiteral( "3-Band Visual" ) );
|
||||
QCOMPARE( uris.size(), 3 );
|
||||
QCOMPARE( uris.at( 0 ).uri, QStringLiteral( "file://%1%2" ).arg( mDataDir, QStringLiteral( "20201211_223832_CS2_analytic.tif" ) ) );
|
||||
QCOMPARE( uris.at( 0 ).name, QStringLiteral( "4-Band Analytic" ) );
|
||||
QCOMPARE( uris.at( 1 ).uri, QStringLiteral( "/vsicurl/https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif" ) );
|
||||
QCOMPARE( uris.at( 1 ).name, QStringLiteral( "3-Band Visual" ) );
|
||||
QCOMPARE( uris.at( 2 ).uri, QStringLiteral( "ZARR:\"/vsicurl/https://objectstore.eodc.eu:2222/e05ab01a9d56408d82ac32d69a5aae2a:202505-s02msil2a/22/products/cpm_v256/S2B_MSIL2A_20250522T125039_N0511_R095_T26TML_20250522T133252.zarr\"" ) );
|
||||
QCOMPARE( uris.at( 2 ).name, QStringLiteral( "Example Zarr Store" ) );
|
||||
|
||||
// check that relative links are correctly resolved into absolute links
|
||||
const QVector<QgsStacLink> links = item->links();
|
||||
@ -187,11 +189,12 @@ void TestQgsStac::testParseLocalItem()
|
||||
QCOMPARE( links.at( 2 ).href(), QStringLiteral( "%1collection.json" ).arg( basePath ) );
|
||||
QCOMPARE( links.at( 3 ).href(), QStringLiteral( "http://remotedata.io/catalog/20201211_223832_CS2/index.html" ) );
|
||||
|
||||
QCOMPARE( item->assets().size(), 6 );
|
||||
QCOMPARE( item->assets().size(), 7 );
|
||||
QgsStacAsset asset = item->assets().value( QStringLiteral( "analytic" ), QgsStacAsset( {}, {}, {}, {}, {} ) );
|
||||
QCOMPARE( asset.href(), basePath + QStringLiteral( "20201211_223832_CS2_analytic.tif" ) );
|
||||
QVERIFY( asset.isCloudOptimized() );
|
||||
QCOMPARE( asset.formatName(), QStringLiteral( "COG" ) );
|
||||
QVERIFY( asset.isDownloadable() );
|
||||
|
||||
QgsMimeDataUtils::Uri uri = asset.uri();
|
||||
QCOMPARE( uri.uri, basePath + QStringLiteral( "20201211_223832_CS2_analytic.tif" ) );
|
||||
@ -205,6 +208,7 @@ void TestQgsStac::testParseLocalItem()
|
||||
QVERIFY( !uri.isValid() );
|
||||
QVERIFY( uri.uri.isEmpty() );
|
||||
QVERIFY( uri.name.isEmpty() );
|
||||
QVERIFY( asset.isDownloadable() );
|
||||
|
||||
// normal geotiff is not cloud optimized
|
||||
asset = item->assets().value( QStringLiteral( "udm" ), QgsStacAsset( {}, {}, {}, {}, {} ) );
|
||||
@ -214,6 +218,14 @@ void TestQgsStac::testParseLocalItem()
|
||||
QVERIFY( !uri.isValid() );
|
||||
QVERIFY( uri.uri.isEmpty() );
|
||||
QVERIFY( uri.name.isEmpty() );
|
||||
QVERIFY( asset.isDownloadable() );
|
||||
|
||||
// Zarr recognised as cloud optimized
|
||||
asset = item->assets().value( QStringLiteral( "zarr-store" ), QgsStacAsset( {}, {}, {}, {}, {} ) );
|
||||
QVERIFY( asset.isCloudOptimized() );
|
||||
QCOMPARE( asset.formatName(), QStringLiteral( "Zarr" ) );
|
||||
QCOMPARE( asset.uri().layerType, QStringLiteral( "raster" ) );
|
||||
QVERIFY( !asset.isDownloadable() );
|
||||
}
|
||||
|
||||
void TestQgsStac::testParseLocalItemCollection()
|
||||
|
9
tests/testdata/stac/core-item.json
vendored
9
tests/testdata/stac/core-item.json
vendored
@ -120,6 +120,15 @@
|
||||
"ephemeris": {
|
||||
"href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH",
|
||||
"title": "Satellite Ephemeris Metadata"
|
||||
},
|
||||
"zarr-store": {
|
||||
"href": "https://objectstore.eodc.eu:2222/e05ab01a9d56408d82ac32d69a5aae2a:202505-s02msil2a/22/products/cpm_v256/S2B_MSIL2A_20250522T125039_N0511_R095_T26TML_20250522T133252.zarr",
|
||||
"title": "Example Zarr Store",
|
||||
"type": "application/vnd+zarr",
|
||||
"roles": [
|
||||
"data",
|
||||
"metadata"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user