diff --git a/images/images.qrc b/images/images.qrc
index 117010e8d713..38062a658d41 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -1009,6 +1009,7 @@
themes/default/mActionTextInsideRect.svg
themes/default/mIconLabelingRules.svg
themes/default/stacked-diagram.svg
+ themes/default/mIconStac.svg
qgis_tips/symbol_levels.png
diff --git a/images/themes/default/mIconStac.svg b/images/themes/default/mIconStac.svg
new file mode 100644
index 000000000000..fbf4910a015d
--- /dev/null
+++ b/images/themes/default/mIconStac.svg
@@ -0,0 +1,82 @@
+
+
diff --git a/python/PyQt6/gui/auto_additions/qgsmanageconnectionsdialog.py b/python/PyQt6/gui/auto_additions/qgsmanageconnectionsdialog.py
index 87c00751fe0f..ef072a547dd5 100644
--- a/python/PyQt6/gui/auto_additions/qgsmanageconnectionsdialog.py
+++ b/python/PyQt6/gui/auto_additions/qgsmanageconnectionsdialog.py
@@ -15,3 +15,4 @@
QgsManageConnectionsDialog.TiledScene = QgsManageConnectionsDialog.Type.TiledScene
QgsManageConnectionsDialog.SensorThings = QgsManageConnectionsDialog.Type.SensorThings
QgsManageConnectionsDialog.CloudStorage = QgsManageConnectionsDialog.Type.CloudStorage
+QgsManageConnectionsDialog.STAC = QgsManageConnectionsDialog.Type.STAC
diff --git a/python/PyQt6/gui/auto_generated/qgsmanageconnectionsdialog.sip.in b/python/PyQt6/gui/auto_generated/qgsmanageconnectionsdialog.sip.in
index 3b9431b74fa1..dda52f832009 100644
--- a/python/PyQt6/gui/auto_generated/qgsmanageconnectionsdialog.sip.in
+++ b/python/PyQt6/gui/auto_generated/qgsmanageconnectionsdialog.sip.in
@@ -38,6 +38,7 @@ class QgsManageConnectionsDialog : QDialog
TiledScene,
SensorThings,
CloudStorage,
+ STAC,
};
QgsManageConnectionsDialog( QWidget *parent /TransferThis/ = 0, Mode mode = Export, Type type = WMS, const QString &fileName = QString() );
diff --git a/python/gui/auto_generated/qgsmanageconnectionsdialog.sip.in b/python/gui/auto_generated/qgsmanageconnectionsdialog.sip.in
index b1248fdde6b6..9ae8f04e8034 100644
--- a/python/gui/auto_generated/qgsmanageconnectionsdialog.sip.in
+++ b/python/gui/auto_generated/qgsmanageconnectionsdialog.sip.in
@@ -38,6 +38,7 @@ class QgsManageConnectionsDialog : QDialog
TiledScene,
SensorThings,
CloudStorage,
+ STAC,
};
QgsManageConnectionsDialog( QWidget *parent /TransferThis/ = 0, Mode mode = Export, Type type = WMS, const QString &fileName = QString() );
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index e584b303f47e..74716e65276b 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -233,6 +233,8 @@
#include "qgscustomlayerorderwidget.h"
#include "qgsdataitemproviderregistry.h"
#include "qgsdataitemguiproviderregistry.h"
+#include "qgsstacdataitems.h"
+#include "qgsstacdataitemguiprovider.h"
#include "qgsdatasourceuri.h"
#include "qgsdatumtransformdialog.h"
#include "qgsdoublespinbox.h"
@@ -1442,6 +1444,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
QgsGui::dataItemGuiProviderRegistry()->addProvider( new QgsFieldDomainItemGuiProvider() );
QgsGui::dataItemGuiProviderRegistry()->addProvider( new QgsRelationshipItemGuiProvider() );
QgsGui::dataItemGuiProviderRegistry()->addProvider( new QgsDatabaseItemGuiProvider() );
+ QgsGui::dataItemGuiProviderRegistry()->addProvider( new QgsStacDataItemGuiProvider() );
QShortcut *showBrowserDock = new QShortcut( QKeySequence( tr( "Ctrl+2" ) ), this );
connect( showBrowserDock, &QShortcut::activated, mBrowserWidget, &QgsDockWidget::toggleUserVisible );
@@ -1647,6 +1650,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
#endif
QgsApplication::dataItemProviderRegistry()->addProvider( new QgsProjectDataItemProvider() );
+ QgsApplication::dataItemProviderRegistry()->addProvider( new QgsStacDataItemProvider() );
// now when all data item providers are registered, customize both browsers
QgsCustomization::instance()->updateBrowserWidget( mBrowserWidget );
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index dc1ee15a7a2d..d0fab41a0f74 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -505,6 +505,11 @@ set(QGIS_GUI_SRCS
settings/qgssettingstreemodel.cpp
settings/qgssettingstreewidget.cpp
+ stac/qgsstacconnectiondialog.cpp
+ stac/qgsstacdataitemguiprovider.cpp
+ stac/qgsstacdownloadassetsdialog.cpp
+ stac/qgsstacobjectdetailsdialog.cpp
+
tableeditor/qgstableeditordialog.cpp
tableeditor/qgstableeditorformattingwidget.cpp
tableeditor/qgstableeditorwidget.cpp
@@ -1560,6 +1565,11 @@ set(QGIS_GUI_HDRS
settings/qgssettingstreemodel.h
settings/qgssettingstreewidget.h
+ stac/qgsstacconnectiondialog.h
+ stac/qgsstacdataitemguiprovider.h
+ stac/qgsstacdownloadassetsdialog.h
+ stac/qgsstacobjectdetailsdialog.h
+
tableeditor/qgstableeditordialog.h
tableeditor/qgstableeditorformattingwidget.h
tableeditor/qgstableeditorwidget.h
@@ -1720,6 +1730,7 @@ target_include_directories(qgis_gui PUBLIC
${CMAKE_SOURCE_DIR}/src/gui/raster
${CMAKE_SOURCE_DIR}/src/gui/sensor
${CMAKE_SOURCE_DIR}/src/gui/settings
+ ${CMAKE_SOURCE_DIR}/src/gui/stac
${CMAKE_SOURCE_DIR}/src/gui/tableeditor
${CMAKE_SOURCE_DIR}/src/gui/tiledscene
${CMAKE_SOURCE_DIR}/src/gui/vector
diff --git a/src/gui/qgsmanageconnectionsdialog.cpp b/src/gui/qgsmanageconnectionsdialog.cpp
index 33cefe551acc..98968d24dca8 100644
--- a/src/gui/qgsmanageconnectionsdialog.cpp
+++ b/src/gui/qgsmanageconnectionsdialog.cpp
@@ -30,6 +30,7 @@
#include "qgstiledsceneconnection.h"
#include "qgssensorthingsconnection.h"
#include "qgsgdalcloudconnection.h"
+#include "qgsstacconnection.h"
QgsManageConnectionsDialog::QgsManageConnectionsDialog( QWidget *parent, Mode mode, Type type, const QString &fileName )
: QDialog( parent )
@@ -159,6 +160,9 @@ void QgsManageConnectionsDialog::doExportImport()
case CloudStorage:
doc = saveCloudStorageConnections( items );
break;
+ case STAC:
+ doc = saveStacConnections( items );
+ break;
}
QFile file( mFileName );
@@ -245,6 +249,9 @@ void QgsManageConnectionsDialog::doExportImport()
case CloudStorage:
loadCloudStorageConnections( doc, items );
break;
+ case STAC:
+ loadStacConnections( doc, items );
+ break;
}
// clear connections list and close window
listConnections->clear();
@@ -307,6 +314,9 @@ bool QgsManageConnectionsDialog::populateConnections()
case CloudStorage:
connections = QgsGdalCloudProviderConnection::sTreeConnectionCloud->items();
break;
+ case STAC:
+ connections = QgsStacConnection::sTreeConnectionStac->items();
+ break;
}
for ( const QString &connection : std::as_const( connections ) )
{
@@ -462,6 +472,14 @@ bool QgsManageConnectionsDialog::populateConnections()
return false;
}
break;
+ case STAC:
+ if ( root.tagName() != QLatin1String( "qgsStacConnections" ) )
+ {
+ QMessageBox::information( this, tr( "Loading Connections" ),
+ tr( "The file is not a STAC connections exchange file." ) );
+ return false;
+ }
+ break;
}
QDomElement child = root.firstChildElement();
@@ -891,6 +909,32 @@ QDomDocument QgsManageConnectionsDialog::saveCloudStorageConnections( const QStr
return doc;
}
+QDomDocument QgsManageConnectionsDialog::saveStacConnections( const QStringList &connections )
+{
+ QDomDocument doc( QStringLiteral( "connections" ) );
+ QDomElement root = doc.createElement( QStringLiteral( "qgsStacConnections" ) );
+ root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0" ) );
+ doc.appendChild( root );
+
+ for ( int i = 0; i < connections.count(); ++i )
+ {
+ QDomElement el = doc.createElement( QStringLiteral( "stac" ) );
+
+ el.setAttribute( QStringLiteral( "name" ), connections[ i ] );
+ el.setAttribute( QStringLiteral( "url" ), QgsStacConnection::settingsUrl->value( connections[ i ] ) );
+ el.setAttribute( QStringLiteral( "authcfg" ), QgsStacConnection::settingsAuthcfg->value( connections[ i ] ) );
+ el.setAttribute( QStringLiteral( "username" ), QgsStacConnection::settingsUsername->value( connections[ i ] ) );
+ el.setAttribute( QStringLiteral( "password" ), QgsStacConnection::settingsPassword->value( connections[ i ] ) );
+
+ QgsHttpHeaders httpHeader( QgsStacConnection::settingsHeaders->value( connections[ i ] ) );
+ httpHeader.updateDomElement( el );
+
+ root.appendChild( el );
+ }
+
+ return doc;
+}
+
void QgsManageConnectionsDialog::loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service )
{
const QDomElement root = doc.documentElement();
@@ -1974,6 +2018,89 @@ void QgsManageConnectionsDialog::loadCloudStorageConnections( const QDomDocument
}
}
+void QgsManageConnectionsDialog::loadStacConnections( const QDomDocument &doc, const QStringList &items )
+{
+ const QDomElement root = doc.documentElement();
+ if ( root.tagName() != QLatin1String( "qgsStacConnections" ) )
+ {
+ QMessageBox::information( this, tr( "Loading Connections" ),
+ tr( "The file is not a STAC connections exchange file." ) );
+ return;
+ }
+
+ QString connectionName;
+ QgsSettings settings;
+ settings.beginGroup( QStringLiteral( "/qgis/connections-stac" ) );
+ QStringList keys = settings.childGroups();
+ settings.endGroup();
+ QDomElement child = root.firstChildElement();
+ bool prompt = true;
+ bool overwrite = true;
+
+ while ( !child.isNull() )
+ {
+ connectionName = child.attribute( QStringLiteral( "name" ) );
+ if ( !items.contains( connectionName ) )
+ {
+ child = child.nextSiblingElement();
+ continue;
+ }
+
+ // check for duplicates
+ if ( keys.contains( connectionName ) && prompt )
+ {
+ const int res = QMessageBox::warning( this,
+ tr( "Loading Connections" ),
+ tr( "Connection with name '%1' already exists. Overwrite?" )
+ .arg( connectionName ),
+ QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel );
+
+ switch ( res )
+ {
+ case QMessageBox::Cancel:
+ return;
+ case QMessageBox::No:
+ child = child.nextSiblingElement();
+ continue;
+ case QMessageBox::Yes:
+ overwrite = true;
+ break;
+ case QMessageBox::YesToAll:
+ prompt = false;
+ overwrite = true;
+ break;
+ case QMessageBox::NoToAll:
+ prompt = false;
+ overwrite = false;
+ break;
+ }
+ }
+
+ if ( keys.contains( connectionName ) )
+ {
+ if ( !overwrite )
+ {
+ child = child.nextSiblingElement();
+ continue;
+ }
+ }
+ else
+ {
+ keys << connectionName;
+ }
+
+ QgsStacConnection::settingsUrl->setValue( child.attribute( QStringLiteral( "url" ) ), connectionName );
+ QgsStacConnection::settingsAuthcfg->setValue( child.attribute( QStringLiteral( "authcfg" ) ), connectionName );
+ QgsStacConnection::settingsUsername->setValue( child.attribute( QStringLiteral( "username" ) ), connectionName );
+ QgsStacConnection::settingsPassword->setValue( child.attribute( QStringLiteral( "password" ) ), connectionName );
+
+ QgsHttpHeaders httpHeader( child );
+ QgsStacConnection::settingsHeaders->setValue( httpHeader.headers(), connectionName );
+
+ child = child.nextSiblingElement();
+ }
+}
+
void QgsManageConnectionsDialog::selectAll()
{
listConnections->selectAll();
diff --git a/src/gui/qgsmanageconnectionsdialog.h b/src/gui/qgsmanageconnectionsdialog.h
index 74df71d8b7bc..3e1a4bfb1a6a 100644
--- a/src/gui/qgsmanageconnectionsdialog.h
+++ b/src/gui/qgsmanageconnectionsdialog.h
@@ -55,6 +55,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan
TiledScene, //!< Tiled scene connection \since QGIS 3.34
SensorThings, //!< SensorThings connections \since QGIS 3.36
CloudStorage, //!< Cloud storage connections \since QGIS 3.40
+ STAC, //!< SpatioTemporal Asset Catalog connections \since QGIS 3.40
};
/**
@@ -83,6 +84,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan
QDomDocument saveTiledSceneConnections( const QStringList &connections );
QDomDocument saveSensorThingsConnections( const QStringList &connections );
QDomDocument saveCloudStorageConnections( const QStringList &connections );
+ QDomDocument saveStacConnections( const QStringList &connections );
void loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service );
void loadWfsConnections( const QDomDocument &doc, const QStringList &items );
@@ -96,6 +98,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan
void loadTiledSceneConnections( const QDomDocument &doc, const QStringList &items );
void loadSensorThingsConnections( const QDomDocument &doc, const QStringList &items );
void loadCloudStorageConnections( const QDomDocument &doc, const QStringList &items );
+ void loadStacConnections( const QDomDocument &doc, const QStringList &items );
QString mFileName;
Mode mDialogMode;
diff --git a/src/gui/stac/qgsstacconnectiondialog.cpp b/src/gui/stac/qgsstacconnectiondialog.cpp
new file mode 100644
index 000000000000..e963b0f7f6be
--- /dev/null
+++ b/src/gui/stac/qgsstacconnectiondialog.cpp
@@ -0,0 +1,78 @@
+/***************************************************************************
+ qgsstacconnectiondialog.h
+ ---------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 "qgsstacconnectiondialog.h"
+#include "qgsstacconnection.h"
+#include "qgsgui.h"
+#include
+#include
+
+///@cond PRIVATE
+
+QgsStacConnectionDialog::QgsStacConnectionDialog( QWidget *parent )
+ : QDialog( parent )
+{
+ setupUi( this );
+ QgsGui::enableAutoGeometryRestore( this );
+
+ buttonBox->button( QDialogButtonBox::Ok )->setDisabled( true );
+
+ connect( mEditName, &QLineEdit::textChanged, this, &QgsStacConnectionDialog::updateOkButtonState );
+ connect( mEditUrl, &QLineEdit::textChanged, this, &QgsStacConnectionDialog::updateOkButtonState );
+}
+
+void QgsStacConnectionDialog::setConnection( const QString &name, const QString &uri )
+{
+ mEditName->setText( name );
+
+ const QgsStacConnection::Data conn = QgsStacConnection::decodedUri( uri );
+ mEditUrl->setText( conn.url );
+
+ mAuthSettings->setUsername( conn.username );
+ mAuthSettings->setPassword( conn.password );
+ mEditReferer->setText( conn.httpHeaders[QgsHttpHeaders::KEY_REFERER].toString() );
+ mAuthSettings->setConfigId( conn.authCfg );
+}
+
+QString QgsStacConnectionDialog::connectionUri() const
+{
+ QgsStacConnection::Data conn;
+ conn.url = mEditUrl->text();
+
+ conn.username = mAuthSettings->username();
+ conn.password = mAuthSettings->password();
+ conn.httpHeaders[QgsHttpHeaders::KEY_REFERER] = mEditReferer->text();
+ conn.authCfg = mAuthSettings->configId( );
+
+ return QgsStacConnection::encodedUri( conn );
+}
+
+QString QgsStacConnectionDialog::connectionName() const
+{
+ return mEditName->text();
+}
+
+void QgsStacConnectionDialog::updateOkButtonState()
+{
+ const bool enabled = !mEditName->text().isEmpty() && !mEditUrl->text().isEmpty();
+ buttonBox->button( QDialogButtonBox::Ok )->setEnabled( enabled );
+}
+
+void QgsStacConnectionDialog::accept()
+{
+ QDialog::accept();
+}
+
+///@endcond
diff --git a/src/gui/stac/qgsstacconnectiondialog.h b/src/gui/stac/qgsstacconnectiondialog.h
new file mode 100644
index 000000000000..81fed692cfb6
--- /dev/null
+++ b/src/gui/stac/qgsstacconnectiondialog.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ qgsstacconnectiondialog.h
+ ---------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 QGSSTACCONNECTIONDIALOG_H
+#define QGSSTACCONNECTIONDIALOG_H
+
+///@cond PRIVATE
+#define SIP_NO_FILE
+
+#include
+
+#include "ui_qgsstacconnectiondialog.h"
+
+
+class QgsStacConnectionDialog : public QDialog, public Ui::QgsStacConnectionDialog
+{
+ Q_OBJECT
+ public:
+ explicit QgsStacConnectionDialog( QWidget *parent = nullptr );
+
+ void setConnection( const QString &name, const QString &uri );
+
+ QString connectionUri() const;
+ QString connectionName() const;
+
+ void accept() override;
+
+ private slots:
+ void updateOkButtonState();
+
+};
+
+///@endcond
+
+#endif // QGSSTACCONNECTIONDIALOG_H
diff --git a/src/gui/stac/qgsstacdataitemguiprovider.cpp b/src/gui/stac/qgsstacdataitemguiprovider.cpp
new file mode 100644
index 000000000000..7d709bee69cf
--- /dev/null
+++ b/src/gui/stac/qgsstacdataitemguiprovider.cpp
@@ -0,0 +1,276 @@
+/***************************************************************************
+ qgsstacdataitemguiprovider.cpp
+ --------------------------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 "qgsstacdataitemguiprovider.h"
+#include "qgsnetworkcontentfetcherregistry.h"
+#include "qgsstaccontroller.h"
+#include "qgsstacdataitems.h"
+#include "qgsstacconnection.h"
+#include "qgsstacconnectiondialog.h"
+#include "qgsmanageconnectionsdialog.h"
+#include "qgsdataitemguiproviderutils.h"
+#include "qgsstacitem.h"
+#include "qgsstacdownloadassetsdialog.h"
+#include "qgsstacobjectdetailsdialog.h"
+#include "qgsapplication.h"
+
+
+///@cond PRIVATE
+
+void QgsStacDataItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList &selection, QgsDataItemGuiContext context )
+{
+ if ( QgsStacRootItem *rootItem = qobject_cast< QgsStacRootItem * >( item ) )
+ {
+ QAction *actionNewConnection = new QAction( tr( "New STAC Connection…" ), menu );
+ connect( actionNewConnection, &QAction::triggered, this, [rootItem] { newConnection( rootItem ); } );
+ menu->addAction( actionNewConnection );
+
+ menu->addSeparator();
+
+ QAction *actionSave = new QAction( tr( "Save Connections…" ), menu );
+ connect( actionSave, &QAction::triggered, this, [] { saveConnections(); } );
+ menu->addAction( actionSave );
+
+ QAction *actionLoad = new QAction( tr( "Load Connections…" ), menu );
+ connect( actionLoad, &QAction::triggered, this, [rootItem] { loadConnections( rootItem ); } );
+ menu->addAction( actionLoad );
+ }
+
+ if ( QgsStacConnectionItem *connItem = qobject_cast< QgsStacConnectionItem * >( item ) )
+ {
+ QAction *actionEdit = new QAction( tr( "Edit Connection…" ), menu );
+ connect( actionEdit, &QAction::triggered, this, [connItem] { editConnection( connItem ); } );
+ menu->addAction( actionEdit );
+
+ const QList< QgsStacConnectionItem * > stacConnectionItems = QgsDataItem::filteredItems( selection );
+ QAction *actionDelete = new QAction( stacConnectionItems.size() > 1 ? tr( "Remove Connections…" ) : tr( "Remove Connection…" ), menu );
+ connect( actionDelete, &QAction::triggered, this, [stacConnectionItems, context]
+ {
+ QgsDataItemGuiProviderUtils::deleteConnections( stacConnectionItems, []( const QString & connectionName )
+ {
+ QgsStacConnection( QString() ).remove( connectionName );
+ }, context );
+ } );
+ menu->addAction( actionDelete );
+ }
+
+ if ( QgsStacCatalogItem *catalogItem = qobject_cast< QgsStacCatalogItem * >( item ) )
+ {
+ menu->addSeparator();
+
+ QAction *actionRefresh = new QAction( tr( "Refresh" ), menu );
+ connect( actionRefresh, &QAction::triggered, this, [catalogItem] { catalogItem->refresh(); } );
+ menu->addAction( actionRefresh );
+
+ if ( catalogItem->stacCatalog() )
+ {
+ QAction *actionDetails = new QAction( tr( "Details" ), menu );
+ connect( actionDetails, &QAction::triggered, this, [catalogItem] { showDetails( catalogItem ); } );
+ menu->addAction( actionDetails );
+ }
+ }
+
+ if ( QgsStacItemItem *itemItem = qobject_cast< QgsStacItemItem * >( item ) )
+ {
+ QAction *actionRefresh = new QAction( tr( "Refresh" ), menu );
+ connect( actionRefresh, &QAction::triggered, this, [itemItem] { itemItem->refresh(); } );
+ menu->addAction( actionRefresh );
+
+ if ( itemItem->stacItem() )
+ {
+ menu->addSeparator();
+
+ 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 );
+ }
+ }
+}
+
+void QgsStacDataItemGuiProvider::editConnection( QgsDataItem *item )
+{
+ const QgsStacConnection::Data connection = QgsStacConnection::connection( item->name() );
+ const QString uri = QgsStacConnection::encodedUri( connection );
+
+ QgsStacConnectionDialog dlg;
+
+ dlg.setConnection( item->name(), uri );
+ if ( !dlg.exec() )
+ return;
+
+ QgsStacConnection( QString() ).remove( item->name() );
+
+ QgsStacConnection::Data newConnection = QgsStacConnection::decodedUri( dlg.connectionUri() );
+
+ QgsStacConnection::addConnection( dlg.connectionName(), newConnection );
+
+ item->parent()->refreshConnections();
+}
+
+void QgsStacDataItemGuiProvider::refreshConnection( QgsDataItem *item )
+{
+ item->refresh();
+ // the parent should be updated
+ if ( item->parent() )
+ item->parent()->refreshConnections();
+}
+
+void QgsStacDataItemGuiProvider::newConnection( QgsDataItem *item )
+{
+ QgsStacConnectionDialog dlg;
+ if ( !dlg.exec() )
+ return;
+
+ QgsStacConnection::Data conn = QgsStacConnection::decodedUri( dlg.connectionUri() );
+
+ QgsStacConnection::addConnection( dlg.connectionName(), conn );
+
+ item->refreshConnections();
+}
+
+void QgsStacDataItemGuiProvider::saveConnections()
+{
+ QgsManageConnectionsDialog dlg( nullptr, QgsManageConnectionsDialog::Export, QgsManageConnectionsDialog::STAC );
+ dlg.exec();
+}
+
+void QgsStacDataItemGuiProvider::loadConnections( QgsDataItem *item )
+{
+ const QString fileName = QFileDialog::getOpenFileName( nullptr, tr( "Load Connections" ), QDir::homePath(),
+ tr( "XML files (*.xml *.XML)" ) );
+ if ( fileName.isEmpty() )
+ {
+ return;
+ }
+
+ QgsManageConnectionsDialog dlg( nullptr, QgsManageConnectionsDialog::Import, QgsManageConnectionsDialog::STAC, fileName );
+ if ( dlg.exec() == QDialog::Accepted )
+ item->refreshConnections();
+}
+
+void QgsStacDataItemGuiProvider::showDetails( QgsDataItem *item )
+{
+ QgsStacObject *obj = nullptr;
+
+ if ( QgsStacItemItem *itemItem = qobject_cast< QgsStacItemItem * >( item ) )
+ {
+ obj = itemItem->stacItem();
+ }
+ else if ( QgsStacCatalogItem *catalogItem = qobject_cast< QgsStacCatalogItem * >( item ) )
+ {
+ obj = catalogItem->stacCatalog();
+ }
+
+ if ( obj )
+ {
+ QgsStacObjectDetailsDialog d;
+ d.setStacObject( obj );
+ d.exec();
+ }
+}
+
+void QgsStacDataItemGuiProvider::downloadAssets( QgsDataItem *item, QgsDataItemGuiContext context )
+{
+ QgsStacItemItem *itemItem = qobject_cast< QgsStacItemItem * >( item );
+
+ if ( ! itemItem )
+ return;
+
+ QgsStacDownloadAssetsDialog dialog;
+ dialog.setStacItem( itemItem->stacItem() );
+
+ if ( dialog.exec() == QDialog::Accepted )
+ {
+ const QString folder = dialog.selectedFolder();
+ const QStringList urls = dialog.selectedUrls();
+ for ( const QString &url : urls )
+ {
+ QgsNetworkContentFetcherTask *fetcher = new QgsNetworkContentFetcherTask( url,
+ itemItem->stacController()->authCfg(),
+ QgsTask::CanCancel,
+ tr( "Downloading STAC asset" ) );
+
+ connect( fetcher, &QgsNetworkContentFetcherTask::errorOccurred, item, [context]( QNetworkReply::NetworkError, const QString & errorMsg )
+ {
+ notify( tr( "Error downloading STAC asset" ),
+ errorMsg,
+ context,
+ Qgis::MessageLevel::Critical );
+ } );
+
+ connect( fetcher, &QgsNetworkContentFetcherTask::fetched, item, [fetcher, folder, context]
+ {
+ QNetworkReply *reply = fetcher->reply();
+ if ( !reply )
+ {
+ // canceled
+ return;
+ }
+ else
+ {
+ const QString fileName = fetcher->contentDispositionFilename().isEmpty() ? reply->url().fileName() : fetcher->contentDispositionFilename();
+ QFileInfo fi( fileName );
+ QFile file( QStringLiteral( "%1/%2" ).arg( folder, fileName ) );
+ int i = 1;
+ while ( file.exists() )
+ {
+ QString uniqueName = QStringLiteral( "%1/%2(%3)" ).arg( folder, fi.baseName() ).arg( i++ );
+ if ( !fi.completeSuffix().isEmpty() )
+ uniqueName.append( QStringLiteral( ".%1" ).arg( fi.completeSuffix() ) );
+ file.setFileName( uniqueName );
+ }
+
+ bool failed = false;
+ if ( file.open( QIODevice::WriteOnly ) )
+ {
+ const QByteArray data = reply->readAll();
+ if ( file.write( data ) < 0 )
+ failed = true;
+
+ file.close();
+ }
+ else
+ {
+ failed = true;
+ }
+
+ if ( failed )
+ {
+ notify( tr( "Error downloading STAC asset" ),
+ tr( "Could not write to file %1" ).arg( file.fileName() ),
+ context,
+ Qgis::MessageLevel::Critical );
+ }
+ else
+ {
+ notify( tr( "STAC asset downloaded" ),
+ file.fileName(),
+ context,
+ Qgis::MessageLevel::Success );
+ }
+ }
+ } );
+
+ QgsApplication::taskManager()->addTask( fetcher );
+ }
+ }
+
+}
+
+///@endcond
diff --git a/src/gui/stac/qgsstacdataitemguiprovider.h b/src/gui/stac/qgsstacdataitemguiprovider.h
new file mode 100644
index 000000000000..15446158c3e6
--- /dev/null
+++ b/src/gui/stac/qgsstacdataitemguiprovider.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+ qgsstacdataitemguiprovider.h
+ --------------------------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 QGSSTACDATAITEMGUIPROVIDER_H
+#define QGSSTACDATAITEMGUIPROVIDER_H
+
+///@cond PRIVATE
+#define SIP_NO_FILE
+
+#include "qgsdataitemguiprovider.h"
+#include "qgis_gui.h"
+
+
+class GUI_EXPORT QgsStacDataItemGuiProvider : public QObject, public QgsDataItemGuiProvider
+{
+ Q_OBJECT
+ public:
+
+ QgsStacDataItemGuiProvider() = default;
+
+ QString name() override { return QStringLiteral( "STAC" ); }
+
+ void populateContextMenu( QgsDataItem *item, QMenu *menu,
+ const QList &selectedItems,
+ QgsDataItemGuiContext context ) override;
+
+ private:
+ static void newConnection( QgsDataItem *item );
+ static void editConnection( QgsDataItem *item );
+ static void refreshConnection( QgsDataItem *item );
+ static void saveConnections();
+ static void loadConnections( QgsDataItem *item );
+
+ static void showDetails( QgsDataItem *item );
+ static void downloadAssets( QgsDataItem *item, QgsDataItemGuiContext context );
+};
+
+///@endcond
+
+#endif // QGSSTACDATAITEMGUIPROVIDER_H
diff --git a/src/gui/stac/qgsstacdownloadassetsdialog.cpp b/src/gui/stac/qgsstacdownloadassetsdialog.cpp
new file mode 100644
index 000000000000..0dab898f4210
--- /dev/null
+++ b/src/gui/stac/qgsstacdownloadassetsdialog.cpp
@@ -0,0 +1,106 @@
+/***************************************************************************
+ qgsstacdownloadassetsdialog.cpp
+ ---------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 "qgsstacdownloadassetsdialog.h"
+#include "qgsgui.h"
+#include "qgssettings.h"
+#include "qgsproject.h"
+
+#include
+#include
+
+///@cond PRIVATE
+
+QgsStacDownloadAssetsDialog::QgsStacDownloadAssetsDialog( QWidget *parent ) :
+ QDialog( parent )
+{
+ setupUi( this );
+ QgsGui::enableAutoGeometryRestore( this );
+ mFileWidget->setStorageMode( QgsFileWidget::StorageMode::GetDirectory );
+
+ QString defPath = QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() );
+ defPath = QgsSettings().value( QStringLiteral( "UI/lastFileNameWidgetDir" ), defPath ).toString();
+ if ( defPath.isEmpty() )
+ defPath = QDir::homePath();
+ mFileWidget->setFilePath( defPath );
+ mFileWidget->lineEdit()->setReadOnly( true );
+
+ connect( mSelectAllButton, &QPushButton::clicked, this, &QgsStacDownloadAssetsDialog::selectAll );
+ connect( mDeselectAllButton, &QPushButton::clicked, this, &QgsStacDownloadAssetsDialog::deselectAll );
+}
+
+void QgsStacDownloadAssetsDialog::setStacItem( QgsStacItem *stacItem )
+{
+ if ( ! stacItem )
+ return;
+
+ 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 );
+ }
+}
+
+QString QgsStacDownloadAssetsDialog::selectedFolder()
+{
+ return mFileWidget->filePath();
+}
+
+QStringList QgsStacDownloadAssetsDialog::selectedUrls()
+{
+ QStringList urls;
+ for ( int i = 0; i < mTreeWidget->topLevelItemCount(); ++i )
+ {
+ QTreeWidgetItem *item = mTreeWidget->topLevelItem( i );
+ if ( item->checkState( 0 ) == Qt::Checked )
+ urls.append( item->text( 5 ) );
+ }
+ return urls;
+}
+
+void QgsStacDownloadAssetsDialog::selectAll()
+{
+ for ( int i = 0; i < mTreeWidget->topLevelItemCount(); ++i )
+ {
+ QTreeWidgetItem *item = mTreeWidget->topLevelItem( i );
+ item->setCheckState( 0, Qt::Checked );
+ }
+}
+
+void QgsStacDownloadAssetsDialog::deselectAll()
+{
+ for ( int i = 0; i < mTreeWidget->topLevelItemCount(); ++i )
+ {
+ QTreeWidgetItem *item = mTreeWidget->topLevelItem( i );
+ item->setCheckState( 0, Qt::Unchecked );
+ }
+}
+
+///@endcond
diff --git a/src/gui/stac/qgsstacdownloadassetsdialog.h b/src/gui/stac/qgsstacdownloadassetsdialog.h
new file mode 100644
index 000000000000..f6a69c53bfeb
--- /dev/null
+++ b/src/gui/stac/qgsstacdownloadassetsdialog.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ qgsstacdownloadassetsdialog.h
+ ---------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 QGSSTACDOWNLOADASSETSDIALOG_H
+#define QGSSTACDOWNLOADASSETSDIALOG_H
+
+///@cond PRIVATE
+#define SIP_NO_FILE
+
+#include "qgsstacitem.h"
+#include "ui_qgsstacdownloadassetsdialog.h"
+
+#include
+
+
+class QgsStacDownloadAssetsDialog : public QDialog, private Ui::QgsStacDownloadAssetsDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit QgsStacDownloadAssetsDialog( QWidget *parent = nullptr );
+
+ void setStacItem( QgsStacItem *stacItem );
+ QString selectedFolder();
+ QStringList selectedUrls();
+
+ private:
+ void selectAll();
+ void deselectAll();
+
+};
+
+///@endcond
+
+#endif // QGSSTACDOWNLOADASSETSDIALOG_H
diff --git a/src/gui/stac/qgsstacobjectdetailsdialog.cpp b/src/gui/stac/qgsstacobjectdetailsdialog.cpp
new file mode 100644
index 000000000000..b38a075d1c5a
--- /dev/null
+++ b/src/gui/stac/qgsstacobjectdetailsdialog.cpp
@@ -0,0 +1,58 @@
+/***************************************************************************
+ qgsstacobjectdetailsdialog.cpp
+ ---------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 "qgsstacobjectdetailsdialog.h"
+#include "qgsgui.h"
+#include "qgsapplication.h"
+#include "qgsstacitem.h"
+
+///@cond PRIVATE
+
+QgsStacObjectDetailsDialog::QgsStacObjectDetailsDialog( QWidget *parent ) :
+ QDialog( parent )
+{
+ setupUi( this );
+ QgsGui::enableAutoGeometryRestore( this );
+}
+
+void QgsStacObjectDetailsDialog::setStacObject( QgsStacObject *stacObject )
+{
+ if ( ! stacObject )
+ return;
+
+ QStringList thumbnails;
+ if ( QgsStacItem *item = dynamic_cast( stacObject ) )
+ {
+ const QMap assets = item->assets();
+ for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it )
+ {
+ if ( it->roles().contains( QLatin1String( "thumbnail" ) ) )
+ {
+ thumbnails.append( QStringLiteral( "
" ).arg( it->href() ) );
+ }
+ }
+ }
+
+ const QString myStyle = QgsApplication::reportStyleSheet( QgsApplication::StyleSheetType::WebBrowser );
+ // inject thumbnails
+ QString html = stacObject->toHtml().replace( QLatin1String( "" ), QStringLiteral( "\n%1" ).arg( thumbnails.join( QString() ) ) );
+ // inject stylesheet
+ html = html.replace( QLatin1String( "" ), QStringLiteral( R"raw()raw" ) ).arg( myStyle );
+
+ mWebView->page()->setLinkDelegationPolicy( QWebPage::LinkDelegationPolicy::DelegateAllLinks );
+ mWebView->setHtml( html );
+}
+
+///@endcond
diff --git a/src/gui/stac/qgsstacobjectdetailsdialog.h b/src/gui/stac/qgsstacobjectdetailsdialog.h
new file mode 100644
index 000000000000..9f831fac594a
--- /dev/null
+++ b/src/gui/stac/qgsstacobjectdetailsdialog.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+ qgsstacobjectdetailsdialog.h
+ ---------------------
+ begin : September 2024
+ copyright : (C) 2024 by Stefanos Natsis
+ email : uclaros 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 QGSSTACOBJECTDETAILSDIALOG_H
+#define QGSSTACOBJECTDETAILSDIALOG_H
+
+///@cond PRIVATE
+#define SIP_NO_FILE
+
+#include "qgsstacobject.h"
+#include "ui_qgsstacobjectdetailsdialog.h"
+
+#include
+
+class QgsStacObjectDetailsDialog : public QDialog, private Ui::QgsStacObjectDetailsDialog
+{
+ Q_OBJECT
+
+ public:
+ explicit QgsStacObjectDetailsDialog( QWidget *parent = nullptr );
+
+ void setStacObject( QgsStacObject *stacObject );
+};
+
+///@endcond
+
+#endif // QGSSTACOBJECTDETAILSDIALOG_H
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 7902222682b0..d89d204dc362 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -19,6 +19,7 @@ file(GLOB GEOREFERENCER_UIS "${CMAKE_CURRENT_SOURCE_DIR}/georeferencer/*.ui")
file(GLOB ANNOTATION_UIS "${CMAKE_CURRENT_SOURCE_DIR}/annotations/*.ui")
file(GLOB SENSOR_UIS "${CMAKE_CURRENT_SOURCE_DIR}/sensor/*.ui")
file(GLOB TILED_SCENE_UIS "${CMAKE_CURRENT_SOURCE_DIR}/tiledscene/*.ui")
+file(GLOB STAC_UIS "${CMAKE_CURRENT_SOURCE_DIR}/stac/*.ui")
if (BUILD_WITH_QT6)
QT6_WRAP_UI(QGIS_UIS_H
@@ -42,6 +43,7 @@ if (BUILD_WITH_QT6)
${ANNOTATION_UIS}
${SENSOR_UIS}
${TILED_SCENE_UIS}
+ ${STAC_UIS}
)
else()
QT5_WRAP_UI(QGIS_UIS_H
@@ -65,6 +67,7 @@ else()
${ANNOTATION_UIS}
${SENSOR_UIS}
${TILED_SCENE_UIS}
+ ${STAC_UIS}
)
endif()
diff --git a/src/ui/stac/qgsstacconnectiondialog.ui b/src/ui/stac/qgsstacconnectiondialog.ui
new file mode 100644
index 000000000000..2838e15fa6af
--- /dev/null
+++ b/src/ui/stac/qgsstacconnectiondialog.ui
@@ -0,0 +1,158 @@
+
+
+ QgsStacConnectionDialog
+
+
+
+ 0
+ 0
+ 659
+ 442
+
+
+
+ STAC Connection
+
+
+ -
+
+
+ Connection Details
+
+
+
-
+
+
+ Referer
+
+
+ mEditReferer
+
+
+
+ -
+
+
+ Authentication
+
+
+
+ 6
+
+
+ 6
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+
+
+
+ -
+
+
+ Name of the new connection
+
+
+
+ -
+
+
+ Optional custom referer
+
+
+
+ -
+
+
+ URL
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+ URL of the connection
+
+
+ http://example.com/stac/
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+ QgsAuthSettingsWidget
+ QWidget
+
+ 1
+
+
+
+ mEditName
+ mEditUrl
+ mEditReferer
+
+
+
+
+ buttonBox
+ accepted()
+ QgsStacConnectionDialog
+ accept()
+
+
+ 224
+ 381
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ QgsStacConnectionDialog
+ reject()
+
+
+ 292
+ 387
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/ui/stac/qgsstacdownloadassetsdialog.ui b/src/ui/stac/qgsstacdownloadassetsdialog.ui
new file mode 100644
index 000000000000..2f9323a48132
--- /dev/null
+++ b/src/ui/stac/qgsstacdownloadassetsdialog.ui
@@ -0,0 +1,159 @@
+
+
+ QgsStacDownloadAssetsDialog
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Download STAC assets
+
+
+ -
+
+
+ Download location:
+
+
+
+ -
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+ 6
+
+
+ true
+
+
+
+ Id
+
+
+
+
+ Title
+
+
+
+
+ Description
+
+
+
+
+ Roles
+
+
+
+
+ Media Type
+
+
+
+
+ Url
+
+
+
+
+ -
+
+
-
+
+
+ Select All
+
+
+
+ -
+
+
+ Deselect All
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+ QgsFileWidget
+ QWidget
+
+ 1
+
+
+
+
+
+ buttonBox
+ accepted()
+ QgsStacDownloadAssetsDialog
+ accept()
+
+
+ 199
+ 278
+
+
+ 199
+ 149
+
+
+
+
+ buttonBox
+ rejected()
+ QgsStacDownloadAssetsDialog
+ reject()
+
+
+ 199
+ 278
+
+
+ 199
+ 149
+
+
+
+
+
diff --git a/src/ui/stac/qgsstacobjectdetailsdialog.ui b/src/ui/stac/qgsstacobjectdetailsdialog.ui
new file mode 100644
index 000000000000..a0b7140b9822
--- /dev/null
+++ b/src/ui/stac/qgsstacobjectdetailsdialog.ui
@@ -0,0 +1,75 @@
+
+
+ QgsStacObjectDetailsDialog
+
+
+
+ 0
+ 0
+ 530
+ 554
+
+
+
+ STAC Object Details
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Close
+
+
+
+
+
+
+
+ QgsWebView
+ QWidget
+
+ 1
+
+
+
+
+
+ buttonBox
+ accepted()
+ QgsStacObjectDetailsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ QgsStacObjectDetailsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+