diff --git a/src/qgis_stac/gui/assets_dialog.py b/src/qgis_stac/gui/assets_dialog.py
index 3dfbe9b..61e456d 100644
--- a/src/qgis_stac/gui/assets_dialog.py
+++ b/src/qgis_stac/gui/assets_dialog.py
@@ -37,7 +37,8 @@
from ..api.models import (
AssetLayerType,
- ApiCapability
+ ApiCapability,
+ ResourceAsset
)
from ..definitions.constants import (
@@ -640,7 +641,211 @@ def handle_layer_error(self, message):
message
)
+class ItemsAssetsDialog(AssetsDialog):
+ def __init__(
+ self,
+ item,
+ parent,
+ main_widget,
+ items,
+ ):
+ super().__init__(item, parent, main_widget)
+ self.items = items
+
+ def prepare_assets(self):
+ """ Loads the dialog with the list of assets.
+ """
+ super().prepare_assets()
+ if len(self.assets) > 0:
+ self.title.setText(
+ tr("Collection {}").
+ format(self.item.id)
+ )
+ self.asset_count.setText(
+ tr("{} available asset(s)").
+ format(len(self.assets))
+
+ )
+ else:
+ self.title.setText(
+ tr("Collection {} has no assets").
+ format(self.item.id)
+ )
+
+
+ def load_btn_clicked(self):
+ """ Runs logic after the asset load button has been clicked.
+ """
+ for key, asset in self.load_assets.items():
+ for item in self.items:
+ if key in item.stac_object.assets:
+ asset = item.stac_object.assets[key]
+ rasset = ResourceAsset(
+ href=asset.href,
+ title=asset.title or key,
+ description=asset.description,
+ type=asset.media_type or asset.type,
+ roles=asset.roles or []
+ )
+
+ try:
+ load_task = QgsTask.fromFunction(
+ 'Load asset function',
+ self.load_asset(rasset)
+ )
+ QgsApplication.taskManager().addTask(load_task)
+ except Exception as err:
+ log(tr("An error occurred when running task for "
+ "loading an asset, error message \"{}\" ".format(err))
+ )
+
+ def download_btn_clicked(self):
+ """ Runs logic after the asset download button has been clicked.
+ """
+ auto_asset_loading = settings_manager.get_value(
+ Settings.AUTO_ASSET_LOADING,
+ False,
+ setting_type=bool
+ )
+
+ for key, asset in self.download_assets.items():
+ for item in self.items:
+ if key in item.stac_object.assets:
+ asset = item.stac_object.assets[key]
+ rasset = ResourceAsset(
+ href=asset.href,
+ title=asset.title or key,
+ description=asset.description,
+ type=asset.media_type or asset.type,
+ roles=asset.roles or []
+ )
+
+ try:
+ download_task = QgsTask.fromFunction(
+ 'Download asset function',
+ self.download_asset(rasset, item.id, auto_asset_loading)
+ )
+ QgsApplication.taskManager().addTask(download_task)
+
+ except Exception as err:
+ self.update_inputs(True)
+ log(tr("An error occured when running task for"
+ " downloading asset {}, error message \"{}\" ").format(
+ asset.title,
+ str(err))
+ )
+
+ def download_asset(self, asset, item_id, load_asset=False):
+ """ Downloads the passed asset into directory defined in the plugin settings.
+
+ :param asset: Item asset
+ :type asset: models.ResourceAsset
+
+ :param load_asset: Whether to load an asset after download has finished.
+ :type load_asset: bool
+ """
+ self.update_inputs(False)
+ download_folder = settings_manager.get_value(
+ Settings.DOWNLOAD_FOLDER
+ )
+ item_folder = os.path.join(download_folder, item_id) \
+ if download_folder else None
+ feedback = QgsProcessingFeedback()
+ try:
+ if item_folder:
+ os.mkdir(item_folder)
+ except FileExistsError as fe:
+ pass
+ except FileNotFoundError as fn:
+ self.update_inputs(True)
+ self.main_widget.show_message(
+ tr("Folder {} is not found").format(download_folder),
+ Qgis.Critical
+ )
+ return
+ except PermissionError as pe:
+ self.update_inputs(True)
+ self.main_widget.show_message(
+ tr("Permission error writing in download folder"),
+ Qgis.Critical
+ )
+ return
+
+ url = self.sign_asset_href(asset.href)
+ extension = Path(asset.href).suffix
+ extension_suffix = extension.split('?')[0] if extension else ""
+ title = f"{asset.title}{extension_suffix}"
+
+ title = self.clean_filename(title)
+
+ output = os.path.join(
+ item_folder, title
+ ) if item_folder else QgsProcessing.TEMPORARY_OUTPUT
+ params = {'URL': url, 'OUTPUT': output}
+
+ self.download_result["file"] = output
+
+ layer_types = [
+ AssetLayerType.COG.value,
+ AssetLayerType.COPC.value,
+ AssetLayerType.GEOTIFF.value,
+ AssetLayerType.NETCDF.value,
+ ]
+ try:
+ self.main_widget.show_message(
+ tr("Download for file {} to {} has started."
+ ).format(
+ title,
+ item_folder
+ ),
+ level=Qgis.Info
+ )
+ self.main_widget.show_progress(
+ f"Downloading {url}",
+ minimum=0,
+ maximum=100,
+ )
+
+ feedback.progressChanged.connect(
+ self.main_widget.update_progress_bar
+ )
+ feedback.progressChanged.connect(self.download_progress)
+
+ results = processing.run(
+ "qgis:filedownloader",
+ params,
+ feedback=feedback
+ )
+
+ # After asset download has finished, load the asset
+ # if it can be loaded as a QGIS map layer.
+ if results and load_asset and asset.type in ''.join(layer_types):
+ asset.href = self.download_result["file"]
+ asset.name = title
+ asset.type = AssetLayerType.GEOTIFF.value \
+ if AssetLayerType.COG.value in asset.type else asset.type
+ self.load_asset(asset)
+ except Exception as e:
+ self.update_inputs(True)
+ self.main_widget.show_message(
+ tr("Error in downloading file, {}").format(str(e))
+ )
+
+ def update_inputs(self, enabled):
+ """ Updates the inputs widgets state in the main search item widget.
+
+ :param enabled: Whether to enable the inputs or disable them.
+ :type enabled: bool
+ """
+ self.scroll_area.setEnabled(enabled)
+ # self.parent.update_inputs(enabled)
+ self.load_btn.setEnabled(
+ enabled and len(self.load_assets.items()) > 0
+ )
+ self.download_btn.setEnabled(
+ enabled and len(self.download_assets.items()) > 0
+ )
class LayerLoader(QgsTask):
""" Prepares and loads items assets inside QGIS as layers."""
diff --git a/src/qgis_stac/gui/qgis_stac_widget.py b/src/qgis_stac/gui/qgis_stac_widget.py
index 74018c9..0c75d08 100644
--- a/src/qgis_stac/gui/qgis_stac_widget.py
+++ b/src/qgis_stac/gui/qgis_stac_widget.py
@@ -5,6 +5,7 @@
"""
import os
+from copy import deepcopy
from functools import partial
@@ -41,7 +42,8 @@
SearchFilters,
SortField,
SortOrder,
- QueryableFetchType
+ QueryableFetchType,
+ ResourceAsset
)
from ..api.client import Client
@@ -54,7 +56,8 @@
tr,
)
-from .result_item_widget import add_footprint_helper, ResultItemWidget
+from .result_item_widget import add_footprint_helper, add_footprints_helper, ResultItemWidget
+from .assets_dialog import ItemsAssetsDialog
WidgetUi, _ = loadUiType(
os.path.join(os.path.dirname(__file__), "../ui/qgis_stac_main.ui")
@@ -93,6 +96,13 @@ def __init__(
self.footprint_btn.clicked.connect(
self.footprint_btn_clicked
)
+ self.items_btn.clicked.connect(
+ self.open_selected_items_dialog
+ )
+ self.items_btn.setEnabled(
+ len(self.footprint_items.items()) > 0
+ )
+
self.all_footprints_btn.clicked.connect(
self.all_footprints_btn_clicked
)
@@ -218,6 +228,10 @@ def __init__(
self.queryable_property_widgets = []
self.queryable_properties = []
+ self.items_assets_btn.setEnabled(self.result_items is not None)
+ self.items_assets_btn.clicked.connect(self.open_all_items_dialog)
+
+
def prepare_plugin_settings(self):
""" Initializes all the plugin related settings"""
@@ -766,6 +780,9 @@ def display_results(self, results, pagination=None):
self.footprint_btn.setEnabled(
False
)
+ self.items_btn.setEnabled(
+ False
+ )
self.all_footprints_btn.setEnabled(
len(self.result_items) > 0
)
@@ -854,51 +871,138 @@ def footprint_selected(self, item):
self.footprint_btn.setEnabled(
len(self.footprint_items.items()) > 0
)
-
+ self.items_btn.setText(
+ f"Add the selected item(s) ({len(self.footprint_items.items())})"
+ )
+ self.items_btn.setEnabled(
+ len(self.footprint_items.items()) > 0
+ )
def footprint_deselected(self, item):
""" Removes the passed item from the list of the
footprints to be added.
"""
self.footprint_items.pop(item.id)
- self.footprint_btn.setText(
- f"Add the selected footprint(s) ({len(self.footprint_items.items())})"
- ) if self.footprint_items else \
+ if self.footprint_items:
+ self.footprint_btn.setText(
+ f"Add the selected footprint(s) ({len(self.footprint_items.items())})"
+ )
+ self.items_btn.setText(
+ f"Add the selected item(s) ({len(self.footprint_items.items())})"
+ )
+ else:
self.footprint_btn.setText(
"Add the selected footprint(s)"
)
+ self.items_btn.setText(
+ f"Add the selected item(s)"
+ )
+
self.footprint_btn.setEnabled(
len(self.footprint_items.items()) > 0
)
-
+ self.items_btn.setEnabled(
+ len(self.footprint_items.items()) > 0
+ )
def footprint_btn_clicked(self):
""" Adds selected footprints as map layers."""
- for key, item in self.footprint_items.items():
- try:
- footprint_task = QgsTask.fromFunction(
- 'Add footprints',
- add_footprint_helper(item, self)
- )
- QgsApplication.taskManager().addTask(footprint_task)
- except Exception as err:
- log(
- tr("Error loading item footprint {}, {}".
- format(item.id, err))
- )
+ items = self.footprint_items.values()
+ try:
+ footprint_task = QgsTask.fromFunction(
+ 'Add footprints',
+ add_footprints_helper(items, self)
+ )
+ QgsApplication.taskManager().addTask(footprint_task)
+ except Exception as err:
+ log(
+ tr("Error loading item footprints {}".
+ format(err))
+ )
def all_footprints_btn_clicked(self):
""" Adds all footprints for the current page items as map layers."""
- for item in self.result_items:
- try:
- footprint_task = QgsTask.fromFunction(
- 'Add footprint',
- add_footprint_helper(item, self)
- )
- QgsApplication.taskManager().addTask(footprint_task)
- except Exception as err:
- log(
- tr("Error loading item footprint {}, {}".
- format(item.id, err))
- )
+ try:
+ footprint_task = QgsTask.fromFunction(
+ 'Add footprint',
+ add_footprints_helper(self.result_items, self)
+ )
+ QgsApplication.taskManager().addTask(footprint_task)
+ except Exception as err:
+ log(
+ tr("Error loading item footprints {}".format(err))
+ )
+
+ def open_all_items_dialog(self):
+ """ Opens the assets dialog for the STAC item.
+ Queries the plugin Item from the plugin settings to get the
+ most recent updated assets.
+ """
+ # connection = settings_manager.get_current_connection()
+ # saved_item = settings_manager.get_items(
+ # connection.id,
+ # [str(self.item.item_uuid)]
+ # )
+ # if saved_item:
+ items = self.result_items
+ if len(set([item.collection for item in items])) > 1:
+ raise NotImplementedError(
+ "Adding assets from multiple collections is not supported.\n"
+ "Please select a single collection.")
+
+ item = deepcopy(items[0])
+ if item.collection is not None:
+ item.id = item.collection
+ stored_assets = [
+ ResourceAsset(
+ href=asset.href,
+ title=key,
+ description=asset.description,
+ type=asset.media_type,
+ roles=asset.roles or []
+ )
+ for key, asset in item.stac_object.assets.items()
+ ]
+ item.assets = stored_assets
+
+ assets_dialog = ItemsAssetsDialog(
+ item,
+ parent=self,
+ main_widget=self,
+ items=items,
+ )
+ assets_dialog.exec_()
+
+ def open_selected_items_dialog(self):
+ """ Opens the assets dialog for the selected STAC items,
+ based on the first item assets.
+ """
+ items = list(self.footprint_items.values())
+ if len(set([item.collection for item in items])) > 1:
+ raise NotImplementedError(
+ "Adding assets from multiple collections is not supported.\n"
+ "Please select a single collection.")
+
+ item = deepcopy(items[0])
+ if item.collection is not None:
+ item.id = item.collection
+ stored_assets = [
+ ResourceAsset(
+ href=asset.href,
+ title=key,
+ description=asset.description,
+ type=asset.media_type,
+ roles=asset.roles or []
+ )
+ for key, asset in item.stac_object.assets.items()
+ ]
+ item.assets = stored_assets
+
+ assets_dialog = ItemsAssetsDialog(
+ item,
+ parent=self,
+ main_widget=self,
+ items=items,
+ )
+ assets_dialog.exec_()
def clear_search_results(self):
""" Clear current search results from the UI"""
diff --git a/src/qgis_stac/gui/result_item_widget.py b/src/qgis_stac/gui/result_item_widget.py
index 807d419..86967d6 100644
--- a/src/qgis_stac/gui/result_item_widget.py
+++ b/src/qgis_stac/gui/result_item_widget.py
@@ -421,3 +421,46 @@ def add_footprint_helper(item, main_widget):
).format(item.id),
level=Qgis.Critical
)
+
+from ..lib.pystac import ItemCollection
+def add_footprints_helper(items, main_widget):
+ """ Adds the item footprint inside QGIS as a map layer
+
+ :param item: STAC item whose footprint is going to be added
+ :type item: Item
+
+ :param main_widget: Parent widget that the function is called from
+ :type main_widget: QWidget
+ """
+ layer_file = tempfile.NamedTemporaryFile(
+ mode="w+",
+ suffix='.geojson',
+ delete=False
+ )
+ layer_name = "stac_footprints"
+ ItemCollection([item.stac_object for item in items]).save_object(layer_file.name)
+
+ layer = QgsVectorLayer(
+ layer_file.name,
+ layer_name,
+ AssetLayerType.VECTOR.value
+ )
+ if layer.isValid():
+ QgsProject.instance().addMapLayer(layer)
+ main_widget.show_message(
+ tr(
+ "Successfully loaded footprint layer for item {}."
+ ).format(
+ ", ".join([item.id for item in items])
+ ),
+ level=Qgis.Info
+ )
+
+ else:
+ main_widget.show_message(
+ tr(
+ "Couldn't load footprint into QGIS for items {},"
+ " its layer is not valid."
+ ).format(", ".join([item.id for item in items])),
+ level=Qgis.Critical
+ )
diff --git a/src/qgis_stac/ui/qgis_stac_main.ui b/src/qgis_stac/ui/qgis_stac_main.ui
index b541bd4..b8063db 100644
--- a/src/qgis_stac/ui/qgis_stac_main.ui
+++ b/src/qgis_stac/ui/qgis_stac_main.ui
@@ -896,18 +896,50 @@
Add the selected footprints
+
+ Add the footprint of the selected items in a vector layer
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+ Add the selected items
+
+
+ Add assets of the selected items as new layers
+
-
- Add all current items footprints at once as separate layers
+ Add the footprint of all current items in a vector layer
Add all footprints
+ -
+
+
+ Add assets of all current items
+
+
+ Add all items assets
+
+
+
-
diff --git a/src/qgis_stac/ui/result_item_widget.ui b/src/qgis_stac/ui/result_item_widget.ui
index c3414bb..4dbec0d 100644
--- a/src/qgis_stac/ui/result_item_widget.ui
+++ b/src/qgis_stac/ui/result_item_widget.ui
@@ -181,10 +181,10 @@
-
- Select footprint and add it to the list of footprints to be added.
+ Select item and add it to the list of footprints or items assets to be added.
- Select footprint
+ Select item