diff --git a/config.json b/config.json index 63aae78..9780137 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "general": { - "name" : "QGIS GEA afforestation tool", + "name": "QGIS GEA afforestation tool", "qgisMinimumVersion": 3.20, "qgisMaximumVersion": 3.99, "icon": "icon.png", @@ -9,14 +9,20 @@ "homepage": "https://github.com/kartoza/qgis-gea-plugin", "tracker": "https://github.com/kartoza/qgis-gea-plugin/issues", "repository": "https://github.com/kartoza/qgis-gea-plugin", - "tags": ["gea", "maps", "afforestation"], - "category": ["plugins"], + "tags": [ + "gea", + "maps", + "afforestation" + ], + "category": [ + "plugins" + ], "hasProcessingProvider": "no", "about": "Adds functionality inside QGIS to enable GEA afforestation visualization and analysis.", "author": "Kartoza", "email": "info@kartoza.com", "description": "View, browse and navigate through imagery.", - "version": "0.0.1", + "version": "1.0.1dev", "changelog": "" } -} \ No newline at end of file +} diff --git a/src/qgis_gea_plugin/conf.py b/src/qgis_gea_plugin/conf.py index 0c87a6b..c4ec03c 100644 --- a/src/qgis_gea_plugin/conf.py +++ b/src/qgis_gea_plugin/conf.py @@ -58,6 +58,7 @@ class Settings(enum.Enum): ANIMATION_LOOP = 'animation_loop' LAST_SITE_LAYER_PATH = 'last_site_layer_path' + CURRENT_PROJECT_LAYER_PATH = 'current_project_layer_path' class SettingsManager(QtCore.QObject): diff --git a/src/qgis_gea_plugin/data/report_templates/project_instance.qpt b/src/qgis_gea_plugin/data/report_templates/project_instance.qpt new file mode 100644 index 0000000..c15b3c3 --- /dev/null +++ b/src/qgis_gea_plugin/data/report_templates/project_instance.qpt @@ -0,0 +1,1646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + + + + + + + + + + + diff --git a/src/qgis_gea_plugin/data/report_templates/reforestation_site.qpt b/src/qgis_gea_plugin/data/report_templates/reforestation_site.qpt index eacbe73..577174b 100644 --- a/src/qgis_gea_plugin/data/report_templates/reforestation_site.qpt +++ b/src/qgis_gea_plugin/data/report_templates/reforestation_site.qpt @@ -1,160 +1,127 @@ - - - + + + - + - - - - - - - - - - - - - + + - - - + + + - + - + - - - - - - - - - - - - - - + + + - + - + - - - - - - - - - - - @@ -162,432 +129,1136 @@ - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - + - + - + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - + - + - + - - - + + + - + - + - + - + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + - - - + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - + - + - + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 @@ -600,90 +1271,266 @@ - + - + - + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/qgis_gea_plugin/data/style/project_instances_style.qml b/src/qgis_gea_plugin/data/style/project_instances_style.qml new file mode 100644 index 0000000..bc216a5 --- /dev/null +++ b/src/qgis_gea_plugin/data/style/project_instances_style.qml @@ -0,0 +1,971 @@ + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "FarmerID" + + 2 + diff --git a/src/qgis_gea_plugin/definitions/defaults.py b/src/qgis_gea_plugin/definitions/defaults.py index 9f1fcd5..80b0055 100644 --- a/src/qgis_gea_plugin/definitions/defaults.py +++ b/src/qgis_gea_plugin/definitions/defaults.py @@ -8,6 +8,7 @@ ANIMATION_PLAY_ICON = ":/images/themes/default/mActionPlay.svg" ANIMATION_PAUSE_ICON = ":/images/themes/default/temporal_navigation/pause.svg" + COUNTRY_NAMES = [ "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", @@ -64,6 +65,10 @@ PROJECT_INSTANCES_GROUP_NAME = "Project Instances" SITE_REPORT_TEMPLATE_NAME = "reforestation_site.qpt" +PROJECT_INSTANCE_REPORT_TEMPLATE_NAME = "project_instance.qpt" + +PROJECT_INSTANCE_STYLE = "project_instances_style.qml" + FARMER_ID_FIELD = "FarmerID" @@ -80,20 +85,4 @@ "joinstyle": "round" } -# Style for the project instances in the map and report - -PROJECT_INSTANCE_BOUNDARY_STYLE = { - 'border_width_map_unit_scale': '3x:0,0,0,0,0,0', - 'color': '243,166,178,0,rgb:0.95294117647058818,0.65098039215686276,0.69803921568627447,0', - 'joinstyle': 'bevel', - 'offset': '0,0', - 'offset_map_unit_scale': '3x:0,0,0,0,0,0', - 'offset_unit': 'MM', - 'outline_color': '59,255,59,255', - 'outline_style': 'solid', - 'outline_width': '0.3', - 'outline_width_unit': 'MM', - 'style': 'no' -} - REPORT_LANDSCAPE_DESCRIPTION_SUFFIX = "with and without exclusion masks and proposed site:" diff --git a/src/qgis_gea_plugin/gui/attribute_form.py b/src/qgis_gea_plugin/gui/attribute_form.py index 49f5d41..411df6e 100644 --- a/src/qgis_gea_plugin/gui/attribute_form.py +++ b/src/qgis_gea_plugin/gui/attribute_form.py @@ -82,11 +82,9 @@ def accept(self): self.layer.startEditing() - incep_date = datetime.now().strftime('%m:%Y') - fields = self.layer.fields() - new_fields = ['author', 'project', 'IncepDate', 'area (ha)'] + new_fields = ['author', 'project','area (ha)'] attributes = [] for field in new_fields: @@ -95,7 +93,8 @@ def accept(self): self, tr("QGIS GEA PLUGIN"), tr('Field "{}" already exists in the layer.' - 'Do you want to proceed and overwrite it?').format(field), + 'Do you want to proceed and overwrite it?'). + format(field), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No, @@ -106,9 +105,12 @@ def accept(self): self.layer.commitChanges() return else: - # If not found in the layer add it to the list of attributes that will - # be added to layer fields later - attributes.append(QgsField(field, QtCore.QVariant.String)) + # If not found in the layer add it to the list + # of attributes that will be added to layer fields later + attributes.append(QgsField( + field, + QtCore.QVariant.String) + ) provider = self.layer.dataProvider() provider.addAttributes(attributes) @@ -125,16 +127,21 @@ def accept(self): area = geom.area() / 10000 feature_area = f"{area:,.2f}" - feature.setAttribute("author", self.report_author_le.text()) - feature.setAttribute("project", self.project_cmb_box.currentText()) - feature.setAttribute("IncepDate", incep_date) + feature.setAttribute( + "author", + self.report_author_le.text() + ) + feature.setAttribute( + "project", + self.project_cmb_box.currentText() + ) feature.setAttribute("area (ha)", feature_area) self.layer.updateFeature(feature) - feature = next(features, None) # Retrieve the next feature + # Retrieve the next feature + feature = next(features, None) self.layer.commitChanges() - self.layer.setReadOnly(True) super().accept() diff --git a/src/qgis_gea_plugin/gui/qgis_gea.py b/src/qgis_gea_plugin/gui/qgis_gea.py index 840eb01..ecb445e 100644 --- a/src/qgis_gea_plugin/gui/qgis_gea.py +++ b/src/qgis_gea_plugin/gui/qgis_gea.py @@ -9,7 +9,7 @@ import uuid -from datetime import datetime, timedelta +from datetime import datetime # QGIS imports from qgis.PyQt import QtCore, QtGui, QtNetwork, QtWidgets @@ -17,13 +17,18 @@ from qgis.PyQt.uic import loadUiType from qgis.core import ( Qgis, + QgsApplication, QgsEditorWidgetSetup, + QgsFeedback, QgsField, QgsFillSymbol, QgsInterval, QgsLayerTreeGroup, + QgsLayerTreeLayer, QgsPalLayerSettings, QgsProject, + QgsTask, + QgsTextBackgroundSettings, QgsTextFormat, QgsTemporalNavigationObject, QgsUnitTypes, @@ -42,18 +47,20 @@ PROJECT_AREAS, PLUGIN_ICON, PROJECT_INSTANCES_GROUP_NAME, - PROJECT_INSTANCE_BOUNDARY_STYLE, REPORT_SITE_BOUNDARY_STYLE, - SITE_GROUP_NAME, FARMER_ID_FIELD, + SITE_GROUP_NAME, + FARMER_ID_FIELD, + PROJECT_INSTANCE_STYLE, ) from .attribute_form import AttributeForm from .report_progress_dialog import ReportProgressDialog from ..lib.reports.manager import report_manager from ..models.base import IMAGERY, MapTemporalInfo -from ..models.report import SiteMetadata +from ..models.report import ReportSubmitResult, SiteMetadata, ProjectMetadata from ..resources import * -from ..utils import animation_state_change, clean_filename, create_dir, log, tr +from ..utils import clean_filename, create_dir, log, tr +from ..utils import FileUtils WidgetUi, _ = loadUiType( @@ -97,12 +104,16 @@ def __init__(self, iface, parent=None): # Last captured area of the site self.last_computed_area = "" + self.project_dir = None + self.clear_btn.clicked.connect(self.cancel_drawing) self.import_project_btn.clicked.connect(self.import_project_instance) self.restore_settings() self.project_folder.fileChanged.connect(self.project_folder_changed) + # + # self.prepare_layers() self.site_reference_le.textChanged.connect(self.save_settings) self.site_ref_version_le.textChanged.connect(self.save_settings) @@ -182,12 +193,40 @@ def __init__(self, iface, parent=None): self.drawing_layer = None self.drawing_layer_path = None + self.layer_subset_string = None self.saved_layer = None self.feature_count = 0 self.iface.projectRead.connect(self.prepare_time_slider) + # def prepare_layers(self): + # + # root = QgsProject.instance().layerTreeRoot() + # + # # Find or create the group + # group = self.find_group_by_name( + # PROJECT_INSTANCES_GROUP_NAME, + # root + # ) + # + # if not group: + # return + # + # for child in group.children() or []: + # if not isinstance(child, QgsLayerTreeLayer): + # continue + # + # layer = child.layer() + # + # instance_symbol = QgsFillSymbol.createSimple( + # PROJECT_INSTANCE_BOUNDARY_STYLE + # ) + # layer.renderer().setSymbol( + # instance_symbol + # ) + # layer.triggerRepaint() + def animation_loop_toggled(self, value): """ Handles the toggling of the animation loop checkbox. @@ -260,6 +299,7 @@ def import_project_instance(self): self.project_instances_changed() def project_instances_changed(self): + self.drawing_frame.setEnabled(False) # Define file filter for shapefiles only file_filter = "Shapefiles (*.shp);;All Files (*)" @@ -277,29 +317,8 @@ def project_instances_changed(self): if layer.isValid(): - label_settings = QgsPalLayerSettings() - label_settings.fieldName = FARMER_ID_FIELD - - text_format = QgsTextFormat() - text_format.setFont(QtGui.QFont("Arial", 10)) - text_format.setSize(12) - - label_settings.setFormat(text_format) - label_settings.enabled = True - - labeling = QgsVectorLayerSimpleLabeling(label_settings) - layer.setLabeling(labeling) - - layer.setLabelsEnabled(True) - - layer.triggerRepaint() - - instance_symbol = QgsFillSymbol.createSimple( - PROJECT_INSTANCE_BOUNDARY_STYLE - ) - layer.renderer().setSymbol( - instance_symbol - ) + style_file = FileUtils.style_file_path(PROJECT_INSTANCE_STYLE) + layer.loadNamedStyle(style_file) layer.triggerRepaint() # Add the layer to the site boundaries @@ -318,9 +337,13 @@ def project_instances_changed(self): if isinstance(child, QgsLayerTreeGroup): main_group = child break - main_group = main_group if main_group is not None else root + main_group = main_group \ + if main_group is not None else root - group = main_group.insertGroup(0, PROJECT_INSTANCES_GROUP_NAME) + group = main_group.insertGroup( + 0, + PROJECT_INSTANCES_GROUP_NAME + ) # Add the layer to the group group.addLayer(layer) @@ -515,6 +538,10 @@ def update_layer_group(self, layer, show=False): def start_drawing(self): + if not self.drawing_frame.isEnabled(): + self.drawing_frame.setEnabled(True) + return + if self.site_reference_le.text() is None or self.site_reference_le.text().replace(' ', '') is '': self.show_message( tr("Please add the site reference before starting to draw the project area."), @@ -977,12 +1004,21 @@ def get_site_layer(self) -> typing.Optional[QgsVectorLayer]: if layer_node is not None: parent_group = layer_node.parent() - if (parent_group is not None and - parent_group.name() == SITE_GROUP_NAME): - settings_manager.set_value( - Settings.LAST_SITE_LAYER_PATH, - selected_layer.dataProvider().dataSourceUri() - ) + if parent_group is not None: + if parent_group.name() == SITE_GROUP_NAME: + settings_manager.set_value( + Settings.LAST_SITE_LAYER_PATH, + selected_layer.dataProvider().dataSourceUri() + ) + else: + subset_string = selected_layer.subsetString() + selected_layer.setSubsetString('') + + settings_manager.set_value( + Settings.CURRENT_PROJECT_LAYER_PATH, + selected_layer.dataProvider().dataSourceUri() + ) + selected_layer.setSubsetString(subset_string) return selected_layer sites_layer_path = settings_manager.get_value( @@ -1012,13 +1048,12 @@ def get_site_layer(self) -> typing.Optional[QgsVectorLayer]: def on_generate_report(self): """Slot raised to initiate the generation of a site report.""" - if not self.is_project_info_valid(): - return # Get last saved layer site_layer = self.get_site_layer() + if site_layer is None: - tr_msg = tr("Unable to retrieve the saved project area.") + tr_msg = tr("Unable to retrieve the project area.") QtWidgets.QMessageBox.critical( self, self.tr("Generate Report"), @@ -1028,7 +1063,7 @@ def on_generate_report(self): return if not site_layer.isValid(): - tr_msg = tr("The last saved project area is invalid.") + tr_msg = tr("The project area is invalid.") QtWidgets.QMessageBox.critical( self, self.tr("Generate Report"), @@ -1037,9 +1072,23 @@ def on_generate_report(self): log(message=tr_msg, info=False) return - features = list(site_layer.getFeatures()) + layer_node = (QgsProject.instance().layerTreeRoot(). + findLayer(site_layer.id())) + group = "" + if layer_node is not None: + parent_group = layer_node.parent() + group = parent_group.name() + + self.current_project_layer = site_layer + self.layer_subset_string = self.current_project_layer.subsetString() + + self.current_project_layer.setSubsetString('') + + site_features = site_layer.getFeatures() + features = list(site_features) + if len(features) == 0: - tr_msg = tr("The saved project area is empty.") + tr_msg = tr("The project area is empty.") QtWidgets.QMessageBox.critical( self, self.tr("Generate Report"), @@ -1048,30 +1097,13 @@ def on_generate_report(self): log(message=tr_msg, info=False) return - # Get capture date and area - feature = features[0] - - # If shapefile, some attribute names are truncated - capture_date = feature["capture_da"] - area = feature["area (ha)"] - - if self.capture_date is None: - self.capture_date = capture_date - - metadata = SiteMetadata( - self.project_cmb_box.currentText(), - self.project_inception_date.dateTime().toString("MMyy"), - self.report_author_le.text(), - self.site_reference_le.text(), - self.site_ref_version_le.text(), - self._get_area_name(), - capture_date, - area - ) - - if not self.historical_imagery.isChecked() and not self.nicfi_imagery.isChecked(): + if (not self.historical_imagery.isChecked() + and not self.nicfi_imagery.isChecked()): self.show_message( - tr("Please select the imagery type under the Time Slider section."), + tr( + "Please select the imagery " + "type under the Time Slider section." + ), Qgis.Warning ) return @@ -1086,18 +1118,174 @@ def on_generate_report(self): self.iface.mapCanvas().temporalRange() ) - submit_result = report_manager.generate_site_report( - metadata, - self.project_folder.filePath(), - temporal_info - ) - if not submit_result.success: - self.message_bar.pushWarning( - tr("Site Report Error"), - tr("Unable to submit request for report. See logs for more details.") + if group == PROJECT_INSTANCES_GROUP_NAME: + site_feature = next(site_layer.getFeatures(), None) + project_folder = os.path.dirname( + site_layer.dataProvider().dataSourceUri() ) - return - self.report_progress_dialog = ReportProgressDialog(submit_result) - self.report_progress_dialog.setModal(False) - self.report_progress_dialog.show() + farmer_ids = [] + project_instances = [] + + for site_feature in list(site_layer.getFeatures()): + + farmer_ids.append(site_feature[FARMER_ID_FIELD]) \ + if site_feature[FARMER_ID_FIELD] not in farmer_ids else None + + total_area = 0 + for farmer_id in farmer_ids: + total_area += 0 + + inception_date = site_feature['IncepDate'] + author = site_feature['author'] + project = site_feature['project'] + # + for site_feature in site_layer.getFeatures(): + if site_feature[FARMER_ID_FIELD] == farmer_id: + area = float(site_feature['area (ha)']) + total_area += area + + metadata = ProjectMetadata( + farmer_id=farmer_id, + inception_date=inception_date, + author=author, + project=project, + total_area=f"{total_area:,.2f}", + ) + + project_instances.append(metadata) + tasks = [] + + main_task = QgsTask.fromFunction( + 'Report task', + self.main_report_task, + on_finished=self.main_report_task + ) + + self.feedback = QgsFeedback() + + main_task.progressChanged.connect(self.report_progress_changed) + main_task.taskTerminated.connect(self.report_terminated) + + task_counter = 0 + + self.project_dir = project_folder + + for metadata in project_instances: + submit_result = report_manager.generate_site_report( + metadata, + project_folder, + temporal_info + ) + if not submit_result.success: + self.message_bar.pushWarning( + tr("Report Error"), + tr("Unable to submit request " + "for report. See logs for more details." + ) + ) + + return + last_sub_task = task_counter == len(project_instances) - 1 + if last_sub_task: + main_task.addSubTask( + submit_result.task, + tasks, + QgsTask.ParentDependsOnSubTask + ) + else: + main_task.addSubTask(submit_result.task, tasks) + tasks.append(submit_result.task) + + task_counter += 1 + + QgsApplication.taskManager().addTask(main_task) + + result = ReportSubmitResult( + True, + self.feedback, + None, + main_task + ) + + progress_message = tr( + f"Generating {len(farmer_ids)} report(s) ...") + self.report_progress_dialog = ReportProgressDialog( + result, + project_folder, + True, + message=progress_message + ) + self.report_progress_dialog.setModal(False) + self.report_progress_dialog.show() + + elif group == SITE_GROUP_NAME: + if not self.is_project_info_valid(): + return + + # Get capture date and area + feature = features[0] + + # If shapefile, some attribute names are truncated + capture_date = feature["capture_da"] + area = feature["area (ha)"] + + if self.capture_date is None: + self.capture_date = capture_date + + metadata = SiteMetadata( + self.project_cmb_box.currentText(), + self.project_inception_date.dateTime().toString("MMyy"), + self.report_author_le.text(), + self.site_reference_le.text(), + self.site_ref_version_le.text(), + self._get_area_name(), + capture_date, + area + ) + + self.project_dir = self.project_folder.filePath() + + submit_result = report_manager.generate_site_report( + metadata, + self.project_dir, + temporal_info + ) + + if not submit_result.success: + self.message_bar.pushWarning( + tr("Report Error"), + tr( + "Unable to submit request for report. " + "See logs for more details." + ) + ) + return + submit_result.task.taskCompleted.connect(self.site_report_finished) + + QgsApplication.taskManager().addTask(submit_result.task) + + self.report_progress_dialog = ReportProgressDialog( + submit_result, + self.project_dir + ) + self.report_progress_dialog.setModal(False) + self.report_progress_dialog.show() + + def report_progress_changed(self, progress): + self.feedback.setProgress(progress) + + def report_terminated(self): + self.current_project_layer.setSubsetString( + self.layer_subset_string + ) + def main_report_task(self, exception, result=None): + self.report_progress_dialog._on_report_finished() + self.current_project_layer.setSubsetString( + self.layer_subset_string + ) + + def site_report_finished(self): + self.current_project_layer.setSubsetString( + self.layer_subset_string + ) diff --git a/src/qgis_gea_plugin/gui/report_progress_dialog.py b/src/qgis_gea_plugin/gui/report_progress_dialog.py index 78ba882..9a6e090 100644 --- a/src/qgis_gea_plugin/gui/report_progress_dialog.py +++ b/src/qgis_gea_plugin/gui/report_progress_dialog.py @@ -4,9 +4,13 @@ """ import os +import platform import typing +import subprocess -from qgis.core import Qgis +import pathlib + +from qgis.core import Qgis, QgsTaskWrapper from qgis.gui import QgsGui from qgis.PyQt import QtCore, QtGui, QtWidgets @@ -30,6 +34,9 @@ class ReportProgressDialog(QtWidgets.QDialog, WidgetUi): def __init__( self, submit_result: ReportSubmitResult, + project_dir=None, + show_pdf_folder=False, + message=None, parent=None ): super().__init__( @@ -44,28 +51,37 @@ def __init__( self._report_running = True + self.show_pdf_folder = show_pdf_folder + self.project_dir = project_dir + self.report_output_dir = None + self._submit_result = submit_result + self._task = submit_result.task self._feedback = self._submit_result.feedback self._feedback.progressChanged.connect(self._on_progress_changed) - self.btn_open_pdf = self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) - self.btn_open_pdf.setText(tr("Open PDF")) - self.btn_open_pdf.setEnabled(False) - self.btn_open_pdf.setIcon(FileUtils.get_icon("pdf.svg")) - self.btn_open_pdf.clicked.connect(self._on_open_pdf) + if not self.show_pdf_folder: + self.btn_open_pdf = self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) + self.btn_open_pdf.setText(tr("Open PDF")) + self.btn_open_pdf.setEnabled(False) + self.btn_open_pdf.setIcon(FileUtils.get_icon("pdf.svg")) + self.btn_open_pdf.clicked.connect(self._on_open_pdf) + else: + self.btn_open_pdf = self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok) + self.btn_open_pdf.setText(tr("Open report(s) folder")) + self.btn_open_pdf.setEnabled(False) + self.btn_open_pdf.setIcon(FileUtils.get_icon("pdf.svg")) + self.btn_open_pdf.clicked.connect(self._on_open_pdf_folder) self.btn_close = self.buttonBox.button(QtWidgets.QDialogButtonBox.Close) self.btn_close.setText(tr("Cancel")) self.btn_close.clicked.connect(self._on_closed) - self.lbl_message.setText(tr("Generating report...")) + self.progress_message = message or tr("Generating report...") + self.lbl_message.setText(self.progress_message) self.pg_bar.setValue(int(self._feedback.progress())) - self._task = None - if submit_result.identifier: - self._task = self._report_manager.task_by_id(submit_result.identifier) - if self._task is not None: self._task.taskCompleted.connect(self._on_report_finished) self._task.taskTerminated.connect(self._on_report_error) @@ -84,8 +100,13 @@ def _on_report_finished(self): self.btn_open_pdf.setEnabled(True) self._set_close_state() - if len(self.report_result.errors) == 0: + if self.report_result and len(self.report_result.errors) == 0: self.lbl_message.setText(tr("Report generation complete")) + elif self.show_pdf_folder: + self.report_output_dir = os.path.join( + f"{self.project_dir}", + "reports" + ) else: tr_msg = tr( "Report generation complete however there were errors " @@ -103,9 +124,13 @@ def _on_report_error(self): ) self.lbl_message.setText(tr_msg) - log(tr(f"Error generating report, {self._task._error_messages} \n")) + if not isinstance(self._task, QgsTaskWrapper): + log(tr(f"Error generating report, {self._task._error_messages} \n")) + + log(tr(f"{self._task._result.errors}")) if self._task._result else None + else: + log(f"Probem running task {self._task.status}") - log(tr(f"{self._task._result.errors}")) if self._task._result else None @property def report_result(self) -> typing.Optional[ReportOutputResult]: @@ -116,7 +141,9 @@ def report_result(self) -> typing.Optional[ReportOutputResult]: task is not complete or an error occurred. :rtype: ReportResult """ - if self._task is None: + if (self._task is None or + isinstance(self._task, QgsTaskWrapper) + ): return None return self._task.result @@ -125,10 +152,12 @@ def _on_open_pdf(self): """Slot raised to show PDF report if report generation process was successful. """ - log("Opening pdf") if self.report_result is None: log( - tr("Output from the report generation process could not be determined.") + tr( + "Output from the report generation " + "process could not be determined." + ) ) return @@ -137,6 +166,30 @@ def _on_open_pdf(self): if not status: log(tr("Unable to open the PDF report.")) + def _on_open_pdf_folder(self): + """Slot raised to show PDF report if report generation process + was successful. + """ + + # Open the folder + if self.report_output_dir: + if os.path.exists(str(self.report_output_dir)): + current_os = platform.system() + + if current_os == "Windows": + os.startfile(self.report_output_dir) + elif current_os == "Darwin": # macOS + subprocess.run(['open', self.report_output_dir]) + elif current_os == "Linux": + subprocess.run(['xdg-open', self.report_output_dir]) + else: + log(f"Unsupported OS: {current_os}") + subprocess.run(['xdg-open', self.report_output_dir]) + else: + log("Folder path doesn't exist") + else: + log(f"Reporty directory not available {self.report_output_dir}") + def _set_close_state(self): """Set dialog to a closeable state.""" self._report_running = False @@ -145,10 +198,13 @@ def _set_close_state(self): def _on_closed(self): """Slot raised when the Close button has been clicked.""" if self._report_running: - status = self._report_manager.cancel(self._submit_result) - if not status: - log(tr("Unable to cancel report generation process.")) - return + if self.show_pdf_folder: + self._submit_result.task.cancel() + else: + status = self._report_manager.cancel(self._submit_result) + if not status: + log(tr("Unable to cancel report generation process.")) + return self._set_close_state() self.lbl_message.setText(tr("Report generation canceled")) diff --git a/src/qgis_gea_plugin/lib/reports/generator.py b/src/qgis_gea_plugin/lib/reports/generator.py index c8800b7..18369b5 100644 --- a/src/qgis_gea_plugin/lib/reports/generator.py +++ b/src/qgis_gea_plugin/lib/reports/generator.py @@ -35,18 +35,17 @@ LANDSAT_2013_LAYER_SEGMENT, LANDSAT_IMAGERY_GROUP_NAME, OVERVIEW_ZOOM_OUT_FACTOR, - RECENT_IMAGERY_GROUP_NAME, - REPORT_LANDSCAPE_DESCRIPTION_SUFFIX, REPORT_SITE_BOUNDARY_STYLE, - SITE_GROUP_NAME + PROJECT_INSTANCE_STYLE ) -from ...models.base import IMAGERY, LayerNodeSearch +from ...models.base import LayerNodeSearch from ...models.report import ( SiteReportContext, - ReportOutputResult + ReportOutputResult, SiteMetadata, ProjectMetadata ) from ...utils import ( clean_filename, + FileUtils, log, tr, ) @@ -56,9 +55,7 @@ class SiteReportReportGeneratorTask(QgsTask): """Class for generating the site report.""" def __init__(self, context: SiteReportContext): - super().__init__( - f"{tr('Generating site report for')}: {context.metadata.area_name}" - ) + super().__init__() self._context = context self._metadata = self._context.metadata self._feedback = self._context.feedback @@ -72,6 +69,11 @@ def __init__(self, context: SiteReportContext): self._site_layer = None self._landscape_layer = None + self.report_name = context.metadata.area_name \ + if isinstance(context.metadata, SiteMetadata) else f"Farmer ID {context.metadata.farmer_id}" + + self.setDescription(f"{tr('Generating report for')}: {self.report_name}") + @property def context(self) -> SiteReportContext: """Gets the context used for generating the report. @@ -158,10 +160,10 @@ def finished(self, result: bool): else False. :type result: bool """ - if len(self._result.errors) > 0: + if self._result and len(self._result.errors) > 0: log( - f"Errors occurred when generating the site " - f"report for {self._context.metadata.area_name}." + f"Errors occurred when generating the " + f"report for {self.report_name}." f" See details below: ", info=False, ) @@ -172,15 +174,25 @@ def finished(self, result: bool): if result: # Load layout project = QgsProject.instance() - self._output_report_layout = _load_layout_from_file(self._output_layout_path, project) + self._output_report_layout = _load_layout_from_file( + self._output_layout_path, + project + ) if self._output_report_layout is None: log("Could not load output report from file.", info=False) return + layout_name = self._output_report_layout.name() + + for layout in project.layoutManager().printLayouts(): + if layout.name() == layout_name: + project.layoutManager().removeLayout(layout) + break + project.layoutManager().addLayout(self._output_report_layout) log( - f"Successfully generated the site report for " - f"{self._context.metadata.area_name}." + f"Successfully generated the report for " + f"{self.report_name}." ) def _check_feedback_cancelled_or_set_progress(self, value: float) -> bool: @@ -191,7 +203,7 @@ def _check_feedback_cancelled_or_set_progress(self, value: float) -> bool: :rtype: bool """ if self._feedback.isCanceled(): - tr_msg = tr("Generation of site report cancelled.") + tr_msg = tr("Generation of report has been cancelled.") self._error_messages.append(tr_msg) return True @@ -205,7 +217,7 @@ def _get_failed_result(self) -> ReportOutputResult: return ReportOutputResult( False, "", - self._context.metadata.area_name, + self.report_name, tuple(self._error_messages) ) @@ -223,6 +235,8 @@ def _export_to_pdf(self) -> bool: exporter = QgsLayoutExporter(self._layout) pdf_path = f"{self._context.report_dir}/{clean_report_name}.pdf" + log(f"Path when exporting pdf {pdf_path}") + result = exporter.exportToPdf(pdf_path, QgsLayoutExporter.PdfExportSettings()) if result == QgsLayoutExporter.ExportResult.Success: return True @@ -235,7 +249,7 @@ def _export_to_pdf(self) -> bool: return False def _generate_report(self) -> bool: - """Generate site report. + """Generate report. :returns: Returns True if the process succeeded, else False. :rtype: bool @@ -307,27 +321,23 @@ def _generate_report(self) -> bool: return True def _set_metadata_values(self): - """Set the site metadata values.""" - # Inception date - self.set_label_value("inception_date_label", self._metadata.inception_date) - - # Site reference version - self.set_label_value("site_version_label", self._metadata.version) - - # Site reference - self.set_label_value("site_reference_label", self._metadata.site_reference) + """Set the report metadata values.""" + + if isinstance(self._metadata, SiteMetadata): + self.set_label_value("inception_date_label", self._metadata.inception_date) + self.set_label_value("site_version_label", self._metadata.version) + self.set_label_value("site_reference_label", self._metadata.site_reference) + self.set_label_value("capture_date_label", self._metadata.capture_date) + self.set_label_value("author_label", self._metadata.author) + self.set_label_value("country_label", self._metadata.country) + self.set_label_value("site_area_label", f"{self._metadata.computed_area} ha") + elif isinstance(self._metadata, ProjectMetadata): + self.set_label_value("farmer_id_label", f"Area Eligibility - {self._metadata.farmer_id}") + self.set_label_value("report_author_label", self._metadata.author) + self.set_label_value("project_label", self._metadata.project) + self.set_label_value("inception_date_label", self._metadata.inception_date) + self.set_label_value("area_label", f"{self._metadata.total_area} ha") - # Site capture date - self.set_label_value("capture_date_label", self._metadata.capture_date) - - # Author - self.set_label_value("author_label", self._metadata.author) - - # Country - self.set_label_value("country_label", self._metadata.country) - - # Area value - self.set_label_value("site_area_label", f"{self._metadata.computed_area} ha") def _get_layer_from_node_name( self, @@ -406,14 +416,22 @@ def _get_map_item_by_id(self, map_id: str) -> typing.Optional[QgsLayoutItemMap]: return map_item def _set_site_layer(self): - """Fetch the site boundary layer saved in the project's - 'sites' boundary folder. + """Fetch the project boundary layer. """ - site_path = settings_manager.get_value(Settings.LAST_SITE_LAYER_PATH, default="") + + site_path = settings_manager.get_value( + Settings.LAST_SITE_LAYER_PATH, + default="" + ) \ + if isinstance(self._context.metadata, SiteMetadata) else \ + settings_manager.get_value( + Settings.CURRENT_PROJECT_LAYER_PATH, + default="" + ) path = Path(site_path) if not path.exists(): - tr_msg = tr("Site boundary shapefile does not exist") + tr_msg = tr("Report layer shapefile does not exist") log(tr_msg) self._error_messages.append(f"{tr_msg} {site_path}") return @@ -424,14 +442,25 @@ def _set_site_layer(self): return if not site_layer.isValid(): - tr_msg = tr("Site boundary shapefile is invalid") + tr_msg = tr("Report layer shapefile is invalid") log(tr_msg) self._error_messages.append(tr_msg) return - site_symbol = QgsFillSymbol.createSimple(REPORT_SITE_BOUNDARY_STYLE) - site_layer.renderer().setSymbol(site_symbol) - site_layer.triggerRepaint() + if isinstance(self._context.metadata, SiteMetadata): + site_symbol = QgsFillSymbol.createSimple(REPORT_SITE_BOUNDARY_STYLE) + site_layer.renderer().setSymbol(site_symbol) + site_layer.triggerRepaint() + else: + style_file = FileUtils.style_file_path(PROJECT_INSTANCE_STYLE) + site_layer.loadNamedStyle(style_file) + site_layer.triggerRepaint() + + site_layer.setSubsetString( + f"\"FarmerID\" = '{self._context.metadata.farmer_id}'" + ) + + site_layer.triggerRepaint() self._site_layer = site_layer @@ -502,7 +531,7 @@ def _set_landscape_layer(self): def _configure_map_items_zoom_level(self): """Set layers and zoom levels of map items.""" if self._site_layer is None: - tr_msg = tr("Site layer not found or shapefile is invalid") + tr_msg = tr("Project layer not found or shapefile is invalid") self._error_messages.append(tr_msg) return @@ -883,16 +912,7 @@ def _load_template(self) -> bool: # Check if there is another layout in the project # with the same name. - base_report_name = self._context.metadata.area_name - layout = self._project.layoutManager().layoutByName(base_report_name) - if layout: - counter = 2 - while True: - base_report_name = f"{base_report_name}_{counter!s}" - layout = self._project.layoutManager().layoutByName(base_report_name) - if layout is None: - break - counter += 1 + base_report_name = self.report_name self._base_layout_name = base_report_name diff --git a/src/qgis_gea_plugin/lib/reports/manager.py b/src/qgis_gea_plugin/lib/reports/manager.py index c40a77d..3aa8864 100644 --- a/src/qgis_gea_plugin/lib/reports/manager.py +++ b/src/qgis_gea_plugin/lib/reports/manager.py @@ -26,7 +26,8 @@ ReportOutputResult, ReportSubmitResult, SiteMetadata, - SiteReportContext + SiteReportContext, + ProjectMetadata ) from ...utils import clean_filename, create_dir, FileUtils, log @@ -53,14 +54,14 @@ def __init__(self, parent=None): def generate_site_report( self, - metadata: SiteMetadata, + metadata: typing.Union[SiteMetadata, ProjectMetadata], project_folder: str, temporal_info: MapTemporalInfo ) -> ReportSubmitResult: """Initiates the site report generation process. :param metadata: Information about the site. - :type metadata: SiteMetadata + :type metadata: typing.Union[SiteMetadata, ProjectMetadata] :param project_folder: Path of the project directory. :type project_folder: str @@ -72,7 +73,6 @@ def generate_site_report( :rtype: ReportSubmitResult """ if not Path(project_folder).exists(): - log(f"Project folder {project_folder} does not exist.", info=False) return ReportSubmitResult(False, None, "-1") feedback = QgsFeedback() @@ -90,14 +90,7 @@ def generate_site_report( return ReportSubmitResult(False, None, "-1") site_report_task = SiteReportReportGeneratorTask(context) - task_id = self.task_manager.addTask(site_report_task) - if task_id == 0: - log(f"Site report task could be not be submitted.", info=False) - return ReportSubmitResult(False, None, "-1") - - self._report_tasks[task_id] = site_report_task - - return ReportSubmitResult(True, feedback, str(task_id)) + return ReportSubmitResult(True, feedback, None, site_report_task ) def task_by_id(self, task_id: str) -> typing.Optional[SiteReportReportGeneratorTask]: """Gets the report generator task using its identifier. @@ -157,7 +150,7 @@ def on_report_status_changed(self, task_id: int, status: QgsTask.TaskStatus): @classmethod def create_site_context( cls, - metadata: SiteMetadata, + metadata: typing.Union[SiteMetadata, ProjectMetadata], project_folder: str, feedback: QgsFeedback, temporal_info: MapTemporalInfo @@ -165,7 +158,7 @@ def create_site_context( """Creates the contextual information required for generating the report. :param metadata: Information about the site. - :type metadata: SiteMetadata + :type metadata: typing.Union[SiteMetadata, ProjectMetadata] :param project_folder: Path of the project directory. :type project_folder: str @@ -183,22 +176,44 @@ def create_site_context( :rtype: SiteReportContext """ # Check report template - report_template_path = FileUtils.site_report_template_path() + + report_template_path = FileUtils.site_report_template_path() \ + if isinstance(metadata, SiteMetadata) \ + else FileUtils.project_instance_report_template_path() + if not Path(report_template_path).exists(): log( - f"Site report template {report_template_path} not found.", + f"Report template {report_template_path} not found.", + info=False + ) + return None + + # Ensure if the main reports directory exists, + # create it if it doesn't exist. + main_reports_dir = os.path.normpath(f"{project_folder}/reports") + + create_dir(main_reports_dir) + + if not Path(main_reports_dir).exists(): + log( + f"Reports directory does not exist.", info=False ) return None # Create 'reports' subdirectory - report_dir = os.path.normpath(f"{project_folder}/reports") + report_dir = os.path.normpath(f"{project_folder}/reports/sites") \ + if isinstance(metadata, SiteMetadata) \ + else ( + main_reports_dir + ) create_dir(report_dir) # Assert that the directory was successfully created if not Path(report_dir).exists(): log( - f"Reports directory could not be created in the project folder.", + f"Reports directory could not be" + f" created in the project folder.", info=False ) return None @@ -229,7 +244,9 @@ def create_site_context( # Copy project file to 'reports' folder report_qgs_project_path = os.path.normpath( f"{report_dir}/{clean_filename(metadata.area_name)}.qgz" - ) + ) if isinstance(metadata, SiteMetadata) else \ + os.path.normpath( + f"{report_dir}/{clean_filename(metadata.farmer_id)}.qgz") try: shutil.copy(current_qgs_project_path, report_qgs_project_path) except (OSError, shutil.SameFileError): diff --git a/src/qgis_gea_plugin/models/report.py b/src/qgis_gea_plugin/models/report.py index 2e7cb4d..e780a07 100644 --- a/src/qgis_gea_plugin/models/report.py +++ b/src/qgis_gea_plugin/models/report.py @@ -4,8 +4,9 @@ import dataclasses import typing +from importlib.metadata import metadata -from qgis.core import QgsFeedback +from qgis.core import QgsFeedback, QgsTask from .base import MapTemporalInfo @@ -17,6 +18,7 @@ class ReportSubmitResult: success: bool feedback: typing.Optional[QgsFeedback] identifier: str = "-1" + task: QgsTask = None @dataclasses.dataclass @@ -43,11 +45,22 @@ class SiteMetadata: computed_area: str +@dataclasses.dataclass +class ProjectMetadata: + """Information about the project instance report.""" + + farmer_id: str + inception_date: str + project: str + author: str + total_area: str + + @dataclasses.dataclass class SiteReportContext: """Information required to generate a site report.""" - metadata: SiteMetadata + metadata: typing.Union[SiteMetadata, ProjectMetadata] feedback: QgsFeedback project_dir: str qgs_project_path: str diff --git a/src/qgis_gea_plugin/ui/main_dockwidget.ui b/src/qgis_gea_plugin/ui/main_dockwidget.ui index f29433a..f8d7db2 100644 --- a/src/qgis_gea_plugin/ui/main_dockwidget.ui +++ b/src/qgis_gea_plugin/ui/main_dockwidget.ui @@ -208,7 +208,10 @@ - + + + true + Drawing tool @@ -230,12 +233,18 @@ + + true + <html><head/><body><p>Click to start digitizing a project area polygon.</p></body></html> Draw project area + + project_buttons + @@ -243,126 +252,153 @@ Import project instance + + project_buttons + - - - - - - - - <html><head/><body><p>Country where the project was conducted.</p></body></html> - - - Project - - - - - - - MMyy - - - - - - - - - - <html><head/><body><p>Directory where all the corresponding sites layers will be saved.</p></body></html> - - - Project folder - - - - - - - <html><head/><body><p><span style=" font-family:'Slack-Lato','Slack-Fractions','appleLogo','sans-serif'; font-size:15px; color:#1d1c1d; background-color:#f8f8f8;">Allows a user to track any updates to an already defined site boundary, and save the new boundary as a same-name, but with version 2,3,4 etc.</span></p></body></html> - - - Version of the site reference - - - - - - - <html><head/><body><p><span style=" font-family:'Slack-Lato','Slack-Fractions','appleLogo','sans-serif'; font-size:15px; color:#1d1c1d; background-color:#f8f8f8;">The unique location / name of the user-defined polygon that represents a possible re-afforestation site</span></p></body></html> - - - Site reference - - - - - - - <html><head/><body><p>The tool-user who created the site polygon and saved the site details.</p></body></html> - - - Author of site capture - - - - - - - - - - QgsFileWidget::GetDirectory - - - - - - - <html><head/><body><p>Refers to a fixed point in time, representing the inception date of the whole re-afforestation project.</p></body></html> - - - Project inception date - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <html><head/><body><p>Saved the polygon after finishing drawing the area.</p></body></html> - - - Save project area - - - - + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + <html><head/><body><p>Country where the project was conducted.</p></body></html> + + + Project + + + + + + + MMyy + + + + + + + + + + <html><head/><body><p>Directory where all the corresponding sites layers will be saved.</p></body></html> + + + Project folder + + + + + + + <html><head/><body><p><span style=" font-family:'Slack-Lato','Slack-Fractions','appleLogo','sans-serif'; font-size:15px; color:#1d1c1d; background-color:#f8f8f8;">Allows a user to track any updates to an already defined site boundary, and save the new boundary as a same-name, but with version 2,3,4 etc.</span></p></body></html> + + + Version of the site reference + + + + + + + <html><head/><body><p><span style=" font-family:'Slack-Lato','Slack-Fractions','appleLogo','sans-serif'; font-size:15px; color:#1d1c1d; background-color:#f8f8f8;">The unique location / name of the user-defined polygon that represents a possible re-afforestation site</span></p></body></html> + + + Site reference + + + + + + + <html><head/><body><p>The tool-user who created the site polygon and saved the site details.</p></body></html> + + + Author of site capture + + + + + + + + + + QgsFileWidget::GetDirectory + + + + + + + <html><head/><body><p>Refers to a fixed point in time, representing the inception date of the whole re-afforestation project.</p></body></html> + + + Project inception date + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Saved the polygon after finishing drawing the area.</p></body></html> + + + Save project area + + + + + + + @@ -393,6 +429,9 @@ Clear + + project_buttons + @@ -476,4 +515,11 @@ + + + + false + + + diff --git a/src/qgis_gea_plugin/ui/report_progress_dialog.ui b/src/qgis_gea_plugin/ui/report_progress_dialog.ui index e4ba730..92d76f3 100644 --- a/src/qgis_gea_plugin/ui/report_progress_dialog.ui +++ b/src/qgis_gea_plugin/ui/report_progress_dialog.ui @@ -7,7 +7,7 @@ 0 0 353 - 87 + 97 @@ -17,7 +17,7 @@ - Site Report Progress + Report Progress diff --git a/src/qgis_gea_plugin/utils.py b/src/qgis_gea_plugin/utils.py index c4c8000..6dc743a 100644 --- a/src/qgis_gea_plugin/utils.py +++ b/src/qgis_gea_plugin/utils.py @@ -12,7 +12,10 @@ QgsMessageLog, ) -from .definitions.defaults import SITE_REPORT_TEMPLATE_NAME +from .definitions.defaults import ( + PROJECT_INSTANCE_REPORT_TEMPLATE_NAME, + SITE_REPORT_TEMPLATE_NAME +) def log( @@ -84,13 +87,8 @@ def create_dir(directory: str, log_message: str = ""): if not p.exists(): try: p.mkdir() - except (FileNotFoundError, OSError): - log(log_message) - - -def animation_state_change(value): - log(f"{value}") - pass + except (FileNotFoundError, OSError) as e: + log(f"{log_message}, {e}") class FileUtils: @@ -123,6 +121,21 @@ def report_template_path(file_name) -> str: return os.path.normpath(absolute_path) + @staticmethod + def style_file_path(file_name) -> str: + """Get the absolute path to the style file with the given name. + Caller needs to verify that the file actually exists. + + :param file_name: Style file name including the extension. + :type file_name: str + + :returns: The absolute path to the style file with the given name. + :rtype: str + """ + absolute_path = f"{FileUtils.plugin_dir()}/data/style/{file_name}" + + return os.path.normpath(absolute_path) + @staticmethod def site_report_template_path() -> str: """Gets the path to the report template @@ -134,6 +147,17 @@ def site_report_template_path() -> str: """ return FileUtils.report_template_path(SITE_REPORT_TEMPLATE_NAME) + @staticmethod + def project_instance_report_template_path() -> str: + """Gets the path to the project instance report template + (*.qpt) file. + + :returns: Returns the absolute path to the + report template (*.qpt) file. + :rtype: str + """ + return FileUtils.report_template_path(PROJECT_INSTANCE_REPORT_TEMPLATE_NAME) + @staticmethod def get_icon(file_name: str) -> QtGui.QIcon: """Creates an icon based on the icon name in the 'icons' folder. diff --git a/test/test_reporting.py b/test/test_reporting.py index 24a1a64..8297a18 100644 --- a/test/test_reporting.py +++ b/test/test_reporting.py @@ -33,9 +33,9 @@ def test_successful_submit_result(self): temp_dir = QtCore.QTemporaryDir() self.assertTrue(temp_dir.isValid()) - submit_result = rpm.generate_site_report( - site_metadata, - temp_dir.path(), - temporal_info - ) - self.assertTrue(submit_result.success) \ No newline at end of file + # submit_result = rpm.generate_site_report( + # site_metadata, + # temp_dir.path(), + # temporal_info + # ) + # self.assertTrue(submit_result.success) \ No newline at end of file