class StepFcAnalysis(WizardStep, FORM_CLASS): """InaSAFE Wizard Step Analysis""" def __init__(self, parent): """Init method.""" WizardStep.__init__(self, parent) enable_messaging(self.results_webview) self.iface = parent.iface self.impact_function = None self.extent = Extent(self.iface) self.zoom_to_impact_flag = None self.hide_exposure_flag = None def is_ready_to_next_step(self): """Check if the step is complete. If so, there is no reason to block the Next button. :returns: True if new step may be enabled. :rtype: bool """ return True def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ return None # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnReportWeb_released(self): """Handle the Open Report in Web Browser button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ self.results_webview.open_current_in_browser() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnReportPDF_released(self): """Handle the Generate PDF button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ LOGGER.debug('Generate PDF Button is not implemented') # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnReportComposer_released(self): """Handle the Open in composer button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ LOGGER.debug('Open in composer Button is not implemented') def setup_and_run_analysis(self): """Execute analysis after the tab is displayed. Please check the code in dock.py accept(). It should follow approximately the same code. """ self.show_busy() # Read user's settings self.read_settings() # Prepare impact function from wizard dialog user input self.impact_function = self.prepare_impact_function() # Prepare impact function status, message = self.impact_function.prepare() # Check status if status == PREPARE_FAILED_BAD_INPUT: self.hide_busy() LOGGER.info( tr('The impact function will not be able to run because of the ' 'inputs.')) LOGGER.info(message.to_text()) send_error_message(self, message) return status, message if status == PREPARE_FAILED_BAD_CODE: self.hide_busy() LOGGER.exception( tr('The impact function was not able to be prepared because of a ' 'bug.')) LOGGER.info(message.to_text()) send_error_message(self, message) return status, message # Start the analysis status, message = self.impact_function.run() # Check status if status == ANALYSIS_FAILED_BAD_INPUT: self.hide_busy() LOGGER.info( tr('The impact function could not run because of the inputs.')) LOGGER.info(message.to_text()) send_error_message(self, message) return status, message elif status == ANALYSIS_FAILED_BAD_CODE: self.hide_busy() LOGGER.exception( tr('The impact function could not run because of a bug.')) LOGGER.exception(message.to_text()) send_error_message(self, message) return status, message LOGGER.info(tr('The impact function could run without errors.')) # Add result layer to QGIS add_impact_layers_to_canvas(self.impact_function, self.parent.iface) # Some if-s i.e. zoom, debug, hide exposure if self.zoom_to_impact_flag: self.iface.zoomToActiveLayer() qgis_exposure = (QgsMapLayerRegistry.instance().mapLayer( self.parent.exposure_layer.id())) if self.hide_exposure_flag: legend = self.iface.legendInterface() legend.setLayerVisible(qgis_exposure, False) # Generate impact report error_code, message = generate_impact_report(self.impact_function, self.parent.iface) if error_code == ImpactReport.REPORT_GENERATION_FAILED: self.hide_busy() LOGGER.info(tr('The impact report could not be generated.')) send_error_message(self, message) LOGGER.info(message.to_text()) return ANALYSIS_FAILED_BAD_CODE, message # Generate Impact Map Report error_code, message = generate_impact_map_report( self.impact_function, self.iface) if error_code == ImpactReport.REPORT_GENERATION_FAILED: self.hide_busy() LOGGER.info(tr('The impact report could not be generated.')) send_error_message(self, message) LOGGER.info(message.to_text()) return ANALYSIS_FAILED_BAD_CODE, message self.extent.set_last_analysis_extent( self.impact_function.analysis_extent, qgis_exposure.crs()) # Hide busy self.hide_busy() # Setup gui if analysis is done self.setup_gui_analysis_done() return ANALYSIS_SUCCESS, None def set_widgets(self): """Set widgets on the Progress tab.""" self.progress_bar.setValue(0) self.results_webview.setHtml('') self.pbnReportWeb.hide() self.pbnReportPDF.hide() self.pbnReportComposer.hide() self.lblAnalysisStatus.setText(tr('Running analysis...')) def read_settings(self): """Set the IF state from QSettings.""" extent = setting('user_extent', None, str) if extent: extent = QgsGeometry.fromWkt(extent) if not extent.isGeosValid(): extent = None crs = setting('user_extent_crs', None, str) if crs: crs = QgsCoordinateReferenceSystem(crs) if not crs.isValid(): crs = None mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW) if crs and extent and mode == HAZARD_EXPOSURE_BOUNDINGBOX: self.extent.set_user_extent(extent, crs) self.extent.show_rubber_bands = setting('showRubberBands', False, bool) self.zoom_to_impact_flag = setting('setZoomToImpactFlag', True, bool) # whether exposure layer should be hidden after model completes self.hide_exposure_flag = setting('setHideExposureFlag', False, bool) def prepare_impact_function(self): """Create analysis as a representation of current situation of IFCW.""" # Impact Functions impact_function = ImpactFunction() impact_function.callback = self.progress_callback # Layers impact_function.hazard = self.parent.hazard_layer impact_function.exposure = self.parent.exposure_layer aggregation = self.parent.aggregation_layer if aggregation: impact_function.aggregation = aggregation impact_function.use_selected_features_only = (setting( 'useSelectedFeaturesOnly', False, bool)) else: mode = setting('analysis_extents_mode') if self.extent.user_extent: # This like a hack to transform a geometry to a rectangle. # self.extent.user_extent is a QgsGeometry. # impact_function.requested_extent needs a QgsRectangle. wkt = self.extent.user_extent.exportToWkt() impact_function.requested_extent = wkt_to_rectangle(wkt) impact_function.requested_extent_crs = self.extent.crs elif mode == HAZARD_EXPOSURE_VIEW: impact_function.requested_extent = ( self.iface.mapCanvas().extent()) impact_function.requested_extent_crs = self.extent.crs elif mode == EXPOSURE: impact_function.use_exposure_view_only = True # We don't have any checkbox in the wizard for the debug mode. impact_function.debug_mode = False return impact_function def setup_gui_analysis_done(self): """Helper method to setup gui if analysis is done.""" self.progress_bar.hide() self.lblAnalysisStatus.setText(tr('Analysis done.')) self.pbnReportWeb.show() self.pbnReportPDF.show() # self.pbnReportComposer.show() # Hide until it works again. self.pbnReportPDF.clicked.connect(self.print_map) def show_busy(self): """Lock buttons and enable the busy cursor.""" self.progress_bar.show() self.parent.pbnNext.setEnabled(False) self.parent.pbnBack.setEnabled(False) self.parent.pbnCancel.setEnabled(False) self.parent.repaint() enable_busy_cursor() QtGui.qApp.processEvents() def hide_busy(self): """Unlock buttons A helper function to indicate processing is done.""" self.progress_bar.hide() self.parent.pbnNext.setEnabled(True) self.parent.pbnBack.setEnabled(True) self.parent.pbnCancel.setEnabled(True) self.parent.repaint() disable_busy_cursor() def progress_callback(self, current_value, maximum_value, message=None): """GUI based callback implementation for showing progress. :param current_value: Current progress. :type current_value: int :param maximum_value: Maximum range (point at which task is complete. :type maximum_value: int :param message: Optional message dictionary to containing content we can display to the user. See safe.definitions.analysis_steps for an example of the expected format :type message: dict """ report = m.Message() report.add(LOGO_ELEMENT) report.add(m.Heading(tr('Analysis status'), **INFO_STYLE)) if message is not None: report.add(m.ImportantText(message['name'])) report.add(m.Paragraph(message['description'])) report.add(self.impact_function.performance_log_message()) send_static_message(self, report) self.progress_bar.setMaximum(maximum_value) self.progress_bar.setValue(current_value) QtGui.QApplication.processEvents() def print_map(self): """Open impact report dialog used to tune report when printing.""" # Check if selected layer is valid impact_layer = self.parent.iface.activeLayer() if impact_layer is None: # noinspection PyCallByClass,PyTypeChecker QtGui.QMessageBox.warning( self, 'InaSAFE', tr('Please select a valid impact layer before ' 'trying to print.')) return # Get output path from datastore # Fetch report for pdfs report report_path = os.path.dirname(impact_layer.source()) output_paths = [ os.path.join(report_path, 'output/impact-report-output.pdf'), os.path.join(report_path, 'output/inasafe-map-report-portrait.pdf'), os.path.join(report_path, 'output/inasafe-map-report-landscape.pdf'), ] # Make sure the file paths can wrap nicely: wrapped_output_paths = [ path.replace(os.sep, '<wbr>' + os.sep) for path in output_paths ] # create message to user status = m.Message( m.Heading(tr('Map Creator'), **INFO_STYLE), m.Paragraph( tr('Your PDF was created....opening using the default PDF ' 'viewer on your system. The generated pdfs were saved ' 'as:'))) for path in wrapped_output_paths: status.add(m.Paragraph(path)) send_static_message(self, status) for path in output_paths: # noinspection PyCallByClass,PyTypeChecker,PyTypeChecker QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path)) @property def step_name(self): """Get the human friendly name for the wizard step. :returns: The name of the wizard step. :rtype: str """ # noinspection SqlDialectInspection,SqlNoDataSourceInspection return tr('Analysis') def help_content(self): """Return the content of help for this step wizard. We only needs to re-implement this method in each wizard step. :returns: A message object contains help. :rtype: m.Message """ message = m.Message() message.add( m.Paragraph( tr('In this wizard step: {step_name}, you will see the summary of ' 'the analysis that you have run. You can get your PDF report or ' 'show the report in the web browser by clicking the <b>Generate ' 'PDF</b> and <b>Open in web browser</b> respectively. You can ' 'also click the <b>Finish</b> button to end the wizard session.' ).format(step_name=self.step_name))) return message
class StepFcAnalysis(WizardStep, FORM_CLASS): """InaSAFE Wizard Step Analysis""" def __init__(self, parent): """Init method.""" WizardStep.__init__(self, parent) enable_messaging(self.results_webview) self.iface = parent.iface self.impact_function = None self.extent = Extent(self.iface) self.zoom_to_impact_flag = None self.hide_exposure_flag = None def is_ready_to_next_step(self): """Check if the step is complete. If so, there is no reason to block the Next button. :returns: True if new step may be enabled. :rtype: bool """ return True def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ return None # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnReportWeb_released(self): """Handle the Open Report in Web Browser button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ self.results_webview.open_current_in_browser() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnReportPDF_released(self): """Handle the Generate PDF button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ LOGGER.debug('Generate PDF Button is not implemented') # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnReportComposer_released(self): """Handle the Open in composer button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ LOGGER.debug('Open in composer Button is not implemented') def setup_and_run_analysis(self): """Execute analysis after the tab is displayed. Please check the code in dock.py accept(). It should follow approximately the same code. """ self.show_busy() # Read user's settings self.read_settings() # Prepare impact function from wizard dialog user input self.impact_function = self.prepare_impact_function() # Prepare impact function status, message = self.impact_function.prepare() # Check status if status == PREPARE_FAILED_BAD_INPUT: self.hide_busy() LOGGER.info(tr( 'The impact function will not be able to run because of the ' 'inputs.')) LOGGER.info(message.to_text()) send_error_message(self, message) return status, message if status == PREPARE_FAILED_BAD_CODE: self.hide_busy() LOGGER.exception(tr( 'The impact function was not able to be prepared because of a ' 'bug.')) LOGGER.info(message.to_text()) send_error_message(self, message) return status, message # Start the analysis status, message = self.impact_function.run() # Check status if status == ANALYSIS_FAILED_BAD_INPUT: self.hide_busy() LOGGER.info(tr( 'The impact function could not run because of the inputs.')) LOGGER.info(message.to_text()) send_error_message(self, message) return status, message elif status == ANALYSIS_FAILED_BAD_CODE: self.hide_busy() LOGGER.exception(tr( 'The impact function could not run because of a bug.')) LOGGER.exception(message.to_text()) send_error_message(self, message) return status, message LOGGER.info(tr('The impact function could run without errors.')) # Add result layer to QGIS add_impact_layers_to_canvas(self.impact_function, self.parent.iface) # Some if-s i.e. zoom, debug, hide exposure if self.zoom_to_impact_flag: self.iface.zoomToActiveLayer() qgis_exposure = ( QgsMapLayerRegistry.instance().mapLayer( self.parent.exposure_layer.id())) if self.hide_exposure_flag: legend = self.iface.legendInterface() legend.setLayerVisible(qgis_exposure, False) # Generate impact report error_code, message = generate_impact_report( self.impact_function, self.parent.iface) if error_code == ImpactReport.REPORT_GENERATION_FAILED: self.hide_busy() LOGGER.info(tr( 'The impact report could not be generated.')) send_error_message(self, message) LOGGER.info(message.to_text()) return ANALYSIS_FAILED_BAD_CODE, message # Generate Impact Map Report error_code, message = generate_impact_map_report( self.impact_function, self.iface) if error_code == ImpactReport.REPORT_GENERATION_FAILED: self.hide_busy() LOGGER.info(tr( 'The impact report could not be generated.')) send_error_message(self, message) LOGGER.info(message.to_text()) return ANALYSIS_FAILED_BAD_CODE, message self.extent.set_last_analysis_extent( self.impact_function.analysis_extent, qgis_exposure.crs()) # Hide busy self.hide_busy() # Setup gui if analysis is done self.setup_gui_analysis_done() return ANALYSIS_SUCCESS, None def set_widgets(self): """Set widgets on the Progress tab.""" self.progress_bar.setValue(0) self.results_webview.setHtml('') self.pbnReportWeb.hide() self.pbnReportPDF.hide() self.pbnReportComposer.hide() self.lblAnalysisStatus.setText(tr('Running analysis...')) def read_settings(self): """Set the IF state from QSettings.""" extent = setting('user_extent', None, str) if extent: extent = QgsGeometry.fromWkt(extent) if not extent.isGeosValid(): extent = None crs = setting('user_extent_crs', None, str) if crs: crs = QgsCoordinateReferenceSystem(crs) if not crs.isValid(): crs = None mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW) if crs and extent and mode == HAZARD_EXPOSURE_BOUNDINGBOX: self.extent.set_user_extent(extent, crs) self.extent.show_rubber_bands = setting( 'showRubberBands', False, bool) self.zoom_to_impact_flag = setting('setZoomToImpactFlag', True, bool) # whether exposure layer should be hidden after model completes self.hide_exposure_flag = setting('setHideExposureFlag', False, bool) def prepare_impact_function(self): """Create analysis as a representation of current situation of IFCW.""" # Impact Functions impact_function = ImpactFunction() impact_function.callback = self.progress_callback # Layers impact_function.hazard = self.parent.hazard_layer impact_function.exposure = self.parent.exposure_layer aggregation = self.parent.aggregation_layer if aggregation: impact_function.aggregation = aggregation impact_function.use_selected_features_only = ( setting('useSelectedFeaturesOnly', False, bool)) else: mode = setting('analysis_extents_mode') if self.extent.user_extent: # This like a hack to transform a geometry to a rectangle. # self.extent.user_extent is a QgsGeometry. # impact_function.requested_extent needs a QgsRectangle. wkt = self.extent.user_extent.exportToWkt() impact_function.requested_extent = wkt_to_rectangle(wkt) impact_function.requested_extent_crs = self.extent.crs elif mode == HAZARD_EXPOSURE_VIEW: impact_function.requested_extent = ( self.iface.mapCanvas().extent()) impact_function.requested_extent_crs = self.extent.crs elif mode == EXPOSURE: impact_function.use_exposure_view_only = True # We don't have any checkbox in the wizard for the debug mode. impact_function.debug_mode = False return impact_function def setup_gui_analysis_done(self): """Helper method to setup gui if analysis is done.""" self.progress_bar.hide() self.lblAnalysisStatus.setText(tr('Analysis done.')) self.pbnReportWeb.show() self.pbnReportPDF.show() # self.pbnReportComposer.show() # Hide until it works again. self.pbnReportPDF.clicked.connect(self.print_map) def show_busy(self): """Lock buttons and enable the busy cursor.""" self.progress_bar.show() self.parent.pbnNext.setEnabled(False) self.parent.pbnBack.setEnabled(False) self.parent.pbnCancel.setEnabled(False) self.parent.repaint() enable_busy_cursor() QtGui.qApp.processEvents() def hide_busy(self): """Unlock buttons A helper function to indicate processing is done.""" self.progress_bar.hide() self.parent.pbnNext.setEnabled(True) self.parent.pbnBack.setEnabled(True) self.parent.pbnCancel.setEnabled(True) self.parent.repaint() disable_busy_cursor() def progress_callback(self, current_value, maximum_value, message=None): """GUI based callback implementation for showing progress. :param current_value: Current progress. :type current_value: int :param maximum_value: Maximum range (point at which task is complete. :type maximum_value: int :param message: Optional message dictionary to containing content we can display to the user. See safe.definitions.analysis_steps for an example of the expected format :type message: dict """ report = m.Message() report.add(LOGO_ELEMENT) report.add(m.Heading( tr('Analysis status'), **INFO_STYLE)) if message is not None: report.add(m.ImportantText(message['name'])) report.add(m.Paragraph(message['description'])) report.add(self.impact_function.performance_log_message()) send_static_message(self, report) self.progress_bar.setMaximum(maximum_value) self.progress_bar.setValue(current_value) QtGui.QApplication.processEvents() def print_map(self): """Open impact report dialog used to tune report when printing.""" # Check if selected layer is valid impact_layer = self.parent.iface.activeLayer() if impact_layer is None: # noinspection PyCallByClass,PyTypeChecker QtGui.QMessageBox.warning( self, 'InaSAFE', tr('Please select a valid impact layer before ' 'trying to print.')) return # Get output path from datastore # Fetch report for pdfs report report_path = os.path.dirname(impact_layer.source()) output_paths = [ os.path.join( report_path, 'output/impact-report-output.pdf'), os.path.join( report_path, 'output/inasafe-map-report-portrait.pdf'), os.path.join( report_path, 'output/inasafe-map-report-landscape.pdf'), ] # Make sure the file paths can wrap nicely: wrapped_output_paths = [ path.replace(os.sep, '<wbr>' + os.sep) for path in output_paths] # create message to user status = m.Message( m.Heading(tr('Map Creator'), **INFO_STYLE), m.Paragraph(tr( 'Your PDF was created....opening using the default PDF ' 'viewer on your system. The generated pdfs were saved ' 'as:'))) for path in wrapped_output_paths: status.add(m.Paragraph(path)) send_static_message(self, status) for path in output_paths: # noinspection PyCallByClass,PyTypeChecker,PyTypeChecker QtGui.QDesktopServices.openUrl( QtCore.QUrl.fromLocalFile(path)) @property def step_name(self): """Get the human friendly name for the wizard step. :returns: The name of the wizard step. :rtype: str """ # noinspection SqlDialectInspection,SqlNoDataSourceInspection return tr('Analysis') def help_content(self): """Return the content of help for this step wizard. We only needs to re-implement this method in each wizard step. :returns: A message object contains help. :rtype: m.Message """ message = m.Message() message.add(m.Paragraph(tr( 'In this wizard step: {step_name}, you will see the summary of ' 'the analysis that you have run. You can get your PDF report or ' 'show the report in the web browser by clicking the <b>Generate ' 'PDF</b> and <b>Open in web browser</b> respectively. You can ' 'also click the <b>Finish</b> button to end the wizard session.' ).format(step_name=self.step_name))) return message
class MultiExposureDialog(QDialog, FORM_CLASS): """Dialog for multi exposure tool.""" def __init__(self, parent=None, iface=iface_object): """Constructor for the multi exposure dialog. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: An instance of QgisInterface :type iface: QgisInterface """ QDialog.__init__(self, parent) self.use_selected_only = setting('useSelectedFeaturesOnly', expected_type=bool) self.parent = parent self.iface = iface self.setupUi(self) icon = resources_path('img', 'icons', 'show-multi-exposure.svg') self.setWindowIcon(QIcon(icon)) self.tab_widget.setCurrentIndex(0) self.combos_exposures = OrderedDict() self.keyword_io = KeywordIO() self._create_exposure_combos() self._multi_exposure_if = None self._extent = Extent(iface) self._extent.show_rubber_bands = setting('showRubberBands', False, bool) enable_messaging(self.message_viewer, self) self.btn_back.clicked.connect(self.back_clicked) self.btn_next.clicked.connect(self.next_clicked) self.btn_cancel.clicked.connect(self.reject) self.btn_run.clicked.connect(self.accept) self.validate_impact_function() self.tab_widget.currentChanged.connect(self._tab_changed) self.tree.itemSelectionChanged.connect(self._tree_selection_changed) self.list_layers_in_map_report.itemSelectionChanged.connect( self._list_selection_changed) self.add_layer.clicked.connect(self._add_layer_clicked) self.remove_layer.clicked.connect(self._remove_layer_clicked) self.move_up.clicked.connect(self.move_layer_up) self.move_down.clicked.connect(self.move_layer_down) self.cbx_hazard.currentIndexChanged.connect( self.validate_impact_function) self.cbx_aggregation.currentIndexChanged.connect( self.validate_impact_function) # Keep track of the current panel self._current_index = 0 self.tab_widget.setCurrentIndex(self._current_index) def _tab_changed(self): """Triggered when the current tab is changed.""" current = self.tab_widget.currentWidget() if current == self.analysisTab: self.btn_back.setEnabled(False) self.btn_next.setEnabled(True) elif current == self.reportingTab: if self._current_index == 0: # Only if the user is coming from the first tab self._populate_reporting_tab() self.reporting_options_layout.setEnabled( self._multi_exposure_if is not None) self.btn_back.setEnabled(True) self.btn_next.setEnabled(True) else: self.btn_back.setEnabled(True) self.btn_next.setEnabled(False) self._current_index = current def back_clicked(self): """Back button clicked.""" self.tab_widget.setCurrentIndex(self.tab_widget.currentIndex() - 1) def next_clicked(self): """Next button clicked.""" self.tab_widget.setCurrentIndex(self.tab_widget.currentIndex() + 1) def ordered_expected_layers(self): """Get an ordered list of layers according to users input. From top to bottom in the legend: [ ('FromCanvas', layer name, full layer URI, QML), ('FromAnalysis', layer purpose, layer group, None), ... ] The full layer URI is coming from our helper. :return: An ordered list of layers following a structure. :rtype: list """ registry = QgsProject.instance() layers = [] count = self.list_layers_in_map_report.count() for i in range(count): layer = self.list_layers_in_map_report.item(i) origin = layer.data(LAYER_ORIGIN_ROLE) if origin == FROM_ANALYSIS['key']: key = layer.data(LAYER_PURPOSE_KEY_OR_ID_ROLE) parent = layer.data(LAYER_PARENT_ANALYSIS_ROLE) layers.append((FROM_ANALYSIS['key'], key, parent, None)) else: layer_id = layer.data(LAYER_PURPOSE_KEY_OR_ID_ROLE) layer = registry.mapLayer(layer_id) style_document = QDomDocument() layer.exportNamedStyle(style_document) layers.append( (FROM_CANVAS['key'], layer.name(), full_layer_uri(layer), style_document.toString())) return layers def _add_layer_clicked(self): """Add layer clicked.""" layer = self.tree.selectedItems()[0] origin = layer.data(0, LAYER_ORIGIN_ROLE) if origin == FROM_ANALYSIS['key']: parent = layer.data(0, LAYER_PARENT_ANALYSIS_ROLE) key = layer.data(0, LAYER_PURPOSE_KEY_OR_ID_ROLE) item = QListWidgetItem('%s - %s' % (layer.text(0), parent)) item.setData(LAYER_PARENT_ANALYSIS_ROLE, parent) item.setData(LAYER_PURPOSE_KEY_OR_ID_ROLE, key) else: item = QListWidgetItem(layer.text(0)) layer_id = layer.data(0, LAYER_PURPOSE_KEY_OR_ID_ROLE) item.setData(LAYER_PURPOSE_KEY_OR_ID_ROLE, layer_id) item.setData(LAYER_ORIGIN_ROLE, origin) self.list_layers_in_map_report.addItem(item) self.tree.invisibleRootItem().removeChild(layer) self.tree.clearSelection() def _remove_layer_clicked(self): """Remove layer clicked.""" layer = self.list_layers_in_map_report.selectedItems()[0] origin = layer.data(LAYER_ORIGIN_ROLE) if origin == FROM_ANALYSIS['key']: key = layer.data(LAYER_PURPOSE_KEY_OR_ID_ROLE) parent = layer.data(LAYER_PARENT_ANALYSIS_ROLE) parent_item = self.tree.findItems( parent, Qt.MatchContains | Qt.MatchRecursive, 0)[0] item = QTreeWidgetItem(parent_item, [definition(key)['name']]) item.setData(0, LAYER_PARENT_ANALYSIS_ROLE, parent) else: parent_item = self.tree.findItems( FROM_CANVAS['name'], Qt.MatchContains | Qt.MatchRecursive, 0)[0] item = QTreeWidgetItem(parent_item, [layer.text()]) layer_id = layer.data(LAYER_PURPOSE_KEY_OR_ID_ROLE) item.setData(0, LAYER_PURPOSE_KEY_OR_ID_ROLE, layer_id) item.setData(0, LAYER_ORIGIN_ROLE, origin) index = self.list_layers_in_map_report.indexFromItem(layer) self.list_layers_in_map_report.takeItem(index.row()) self.list_layers_in_map_report.clearSelection() def move_layer_up(self): """Move the layer up.""" layer = self.list_layers_in_map_report.selectedItems()[0] index = self.list_layers_in_map_report.indexFromItem(layer).row() item = self.list_layers_in_map_report.takeItem(index) self.list_layers_in_map_report.insertItem(index - 1, item) self.list_layers_in_map_report.item(index - 1).setSelected(True) def move_layer_down(self): """Move the layer down.""" layer = self.list_layers_in_map_report.selectedItems()[0] index = self.list_layers_in_map_report.indexFromItem(layer).row() item = self.list_layers_in_map_report.takeItem(index) self.list_layers_in_map_report.insertItem(index + 1, item) self.list_layers_in_map_report.item(index + 1).setSelected(True) def _list_selection_changed(self): """Selection has changed in the list.""" items = self.list_layers_in_map_report.selectedItems() self.remove_layer.setEnabled(len(items) >= 1) if len(items) == 1 and self.list_layers_in_map_report.count() >= 2: index = self.list_layers_in_map_report.indexFromItem(items[0]) index = index.row() if index == 0: self.move_up.setEnabled(False) self.move_down.setEnabled(True) elif index == self.list_layers_in_map_report.count() - 1: self.move_up.setEnabled(True) self.move_down.setEnabled(False) else: self.move_up.setEnabled(True) self.move_down.setEnabled(True) else: self.move_up.setEnabled(False) self.move_down.setEnabled(False) def _tree_selection_changed(self): """Selection has changed in the tree.""" self.add_layer.setEnabled(len(self.tree.selectedItems()) >= 1) def _populate_reporting_tab(self): """Populate trees about layers.""" self.tree.clear() self.add_layer.setEnabled(False) self.remove_layer.setEnabled(False) self.move_up.setEnabled(False) self.move_down.setEnabled(False) self.tree.setColumnCount(1) self.tree.setRootIsDecorated(False) self.tree.setHeaderHidden(True) analysis_branch = QTreeWidgetItem(self.tree.invisibleRootItem(), [FROM_ANALYSIS['name']]) analysis_branch.setFont(0, bold_font) analysis_branch.setExpanded(True) analysis_branch.setFlags(Qt.ItemIsEnabled) if self._multi_exposure_if: expected = self._multi_exposure_if.output_layers_expected() for group, layers in list(expected.items()): group_branch = QTreeWidgetItem(analysis_branch, [group]) group_branch.setFont(0, bold_font) group_branch.setExpanded(True) group_branch.setFlags(Qt.ItemIsEnabled) for layer in layers: layer = definition(layer) if layer.get('allowed_geometries', None): item = QTreeWidgetItem(group_branch, [layer.get('name')]) item.setData(0, LAYER_ORIGIN_ROLE, FROM_ANALYSIS['key']) item.setData(0, LAYER_PARENT_ANALYSIS_ROLE, group) item.setData(0, LAYER_PURPOSE_KEY_OR_ID_ROLE, layer['key']) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) canvas_branch = QTreeWidgetItem(self.tree.invisibleRootItem(), [FROM_CANVAS['name']]) canvas_branch.setFont(0, bold_font) canvas_branch.setExpanded(True) canvas_branch.setFlags(Qt.ItemIsEnabled) # List layers from the canvas loaded_layers = list(QgsProject.instance().mapLayers().values()) canvas_layers = self.iface.mapCanvas().layers() flag = setting('visibleLayersOnlyFlag', expected_type=bool) for loaded_layer in loaded_layers: if flag and loaded_layer not in canvas_layers: continue title = loaded_layer.name() item = QTreeWidgetItem(canvas_branch, [title]) item.setData(0, LAYER_ORIGIN_ROLE, FROM_CANVAS['key']) item.setData(0, LAYER_PURPOSE_KEY_OR_ID_ROLE, loaded_layer.id()) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.tree.resizeColumnToContents(0) def _create_exposure_combos(self): """Create one combobox for each exposure and insert them in the UI.""" # Map registry may be invalid if QGIS is shutting down project = QgsProject.instance() canvas_layers = self.iface.mapCanvas().layers() # MapLayers returns a QMap<QString id, QgsMapLayer layer> layers = list(project.mapLayers().values()) # Sort by name for tests layers.sort(key=lambda x: x.name()) show_only_visible_layers = setting('visibleLayersOnlyFlag', expected_type=bool) # For issue #618 if len(layers) == 0: # self.message_viewer.setHtml(getting_started_message()) return for one_exposure in exposure_all: label = QLabel(one_exposure['name']) combo = QComboBox() combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) combo.addItem(tr('Do not use'), None) self.form_layout.addRow(label, combo) self.combos_exposures[one_exposure['key']] = combo for layer in layers: if (show_only_visible_layers and (layer not in canvas_layers)): continue try: layer_purpose = self.keyword_io.read_keywords( layer, 'layer_purpose') keyword_version = str( self.keyword_io.read_keywords(layer, inasafe_keyword_version_key)) if not is_keyword_version_supported(keyword_version): continue except BaseException: # pylint: disable=W0702 # continue ignoring this layer continue # See if there is a title for this layer, if not, # fallback to the layer's filename # noinspection PyBroadException try: title = self.keyword_io.read_keywords(layer, 'title') except (NoKeywordsFoundError, KeywordNotFoundError, MetadataReadError): # Skip if there are no keywords at all, or missing keyword continue except BaseException: # pylint: disable=W0702 pass else: # Lookup internationalised title if available title = self.tr(title) # Register title with layer set_layer_from_title = setting('set_layer_from_title_flag', True, bool) if title and set_layer_from_title: if qgis_version() >= 21800: layer.setName(title) else: # QGIS 2.14 layer.setLayerName(title) source = layer.id() icon = layer_icon(layer) if layer_purpose == layer_purpose_hazard['key']: add_ordered_combo_item(self.cbx_hazard, title, source, icon=icon) elif layer_purpose == layer_purpose_aggregation['key']: if self.use_selected_only: count_selected = layer.selectedFeatureCount() if count_selected > 0: add_ordered_combo_item(self.cbx_aggregation, title, source, count_selected, icon=icon) else: add_ordered_combo_item(self.cbx_aggregation, title, source, None, icon) else: add_ordered_combo_item(self.cbx_aggregation, title, source, None, icon) elif layer_purpose == layer_purpose_exposure['key']: # fetching the exposure try: exposure_type = self.keyword_io.read_keywords( layer, layer_purpose_exposure['key']) except BaseException: # pylint: disable=W0702 # continue ignoring this layer continue for key, combo in list(self.combos_exposures.items()): if key == exposure_type: add_ordered_combo_item(combo, title, source, icon=icon) self.cbx_aggregation.addItem(entire_area_item_aggregation, None) for combo in list(self.combos_exposures.values()): combo.currentIndexChanged.connect(self.validate_impact_function) def progress_callback(self, current_value, maximum_value, message=None): """GUI based callback implementation for showing progress. :param current_value: Current progress. :type current_value: int :param maximum_value: Maximum range (point at which task is complete. :type maximum_value: int :param message: Optional message dictionary to containing content we can display to the user. See safe.definitions.analysis_steps for an example of the expected format :type message: dict """ report = m.Message() report.add(LOGO_ELEMENT) report.add(m.Heading(self.tr('Analysis status'), **INFO_STYLE)) if message is not None: report.add(m.ImportantText(message['name'])) report.add(m.Paragraph(message['description'])) report.add(self._multi_exposure_if.current_impact_function. performance_log_message()) send_static_message(self, report) self.progress_bar.setMaximum(maximum_value) self.progress_bar.setValue(current_value) QgsApplication.processEvents() def validate_impact_function(self): """Check validity of the current impact function.""" # Always set it to False self.btn_run.setEnabled(False) for combo in list(self.combos_exposures.values()): if combo.count() == 1: combo.setEnabled(False) hazard = layer_from_combo(self.cbx_hazard) aggregation = layer_from_combo(self.cbx_aggregation) exposures = [] for combo in list(self.combos_exposures.values()): exposures.append(layer_from_combo(combo)) exposures = [layer for layer in exposures if layer] multi_exposure_if = MultiExposureImpactFunction() multi_exposure_if.hazard = hazard multi_exposure_if.exposures = exposures multi_exposure_if.debug = False multi_exposure_if.callback = self.progress_callback if aggregation: multi_exposure_if.use_selected_features_only = ( self.use_selected_only) multi_exposure_if.aggregation = aggregation else: multi_exposure_if.crs = ( self.iface.mapCanvas().mapSettings().destinationCrs()) if len(self.ordered_expected_layers()) != 0: self._multi_exposure_if.output_layers_ordered = ( self.ordered_expected_layers()) status, message = multi_exposure_if.prepare() if status == PREPARE_SUCCESS: self._multi_exposure_if = multi_exposure_if self.btn_run.setEnabled(True) send_static_message(self, ready_message()) self.list_layers_in_map_report.clear() return else: disable_busy_cursor() send_error_message(self, message) self._multi_exposure_if = None def accept(self): """Launch the multi exposure analysis.""" if not isinstance(self._multi_exposure_if, MultiExposureImpactFunction): # This should not happen as the "accept" button must be disabled if # the impact function is not ready. return ANALYSIS_FAILED_BAD_CODE, None self.tab_widget.setCurrentIndex(2) self.set_enabled_buttons(False) enable_busy_cursor() try: code, message, exposure = self._multi_exposure_if.run() message = basestring_to_message(message) if code == ANALYSIS_FAILED_BAD_INPUT: LOGGER.warning( tr('The impact function could not run because of the inputs.' )) send_error_message(self, message) LOGGER.warning(message.to_text()) disable_busy_cursor() self.set_enabled_buttons(True) return code, message elif code == ANALYSIS_FAILED_BAD_CODE: LOGGER.warning( tr('The impact function could not run because of a bug.')) LOGGER.exception(message.to_text()) send_error_message(self, message) disable_busy_cursor() self.set_enabled_buttons(True) return code, message if setting('generate_report', True, bool): LOGGER.info( 'Reports are going to be generated for the multiexposure.') # Report for the multi exposure report = [standard_multi_exposure_impact_report_metadata_html] error_code, message = ( self._multi_exposure_if.generate_report(report)) message = basestring_to_message(message) if error_code == ImpactReport.REPORT_GENERATION_FAILED: LOGGER.warning('The impact report could not be generated.') send_error_message(self, message) LOGGER.exception(message.to_text()) disable_busy_cursor() self.set_enabled_buttons(True) return error_code, message else: LOGGER.warning( 'Reports are not generated because of your settings.') display_warning_message_bar( tr('Reports'), tr('Reports are not going to be generated because of your ' 'InaSAFE settings.'), duration=10, iface_object=self.iface) # We always create the multi exposure group because we need # reports to be generated. root = QgsProject.instance().layerTreeRoot() if len(self.ordered_expected_layers()) == 0: group_analysis = root.insertGroup(0, self._multi_exposure_if.name) group_analysis.setItemVisibilityChecked(True) group_analysis.setCustomProperty(MULTI_EXPOSURE_ANALYSIS_FLAG, True) for layer in self._multi_exposure_if.outputs: QgsProject.instance().addMapLayer(layer, False) layer_node = group_analysis.addLayer(layer) layer_node.setItemVisibilityChecked(False) # set layer title if any try: title = layer.keywords['title'] if qgis_version() >= 21800: layer.setName(title) else: layer.setLayerName(title) except KeyError: pass for analysis in self._multi_exposure_if.impact_functions: detailed_group = group_analysis.insertGroup( 0, analysis.name) detailed_group.setItemVisibilityChecked(True) add_impact_layers_to_canvas(analysis, group=detailed_group) if self.iface: self.iface.setActiveLayer( self._multi_exposure_if.analysis_impacted) else: add_layers_to_canvas_with_custom_orders( self.ordered_expected_layers(), self._multi_exposure_if, self.iface) if setting('generate_report', True, bool): LOGGER.info( 'Reports are going to be generated for each single ' 'exposure.') # Report for the single exposure with hazard for analysis in self._multi_exposure_if.impact_functions: # we only want to generate non pdf/qpt report html_components = [standard_impact_report_metadata_html] error_code, message = ( analysis.generate_report(html_components)) message = basestring_to_message(message) if error_code == (ImpactReport.REPORT_GENERATION_FAILED): LOGGER.info( 'The impact report could not be generated.') send_error_message(self, message) LOGGER.info(message.to_text()) disable_busy_cursor() self.set_enabled_buttons(True) return error_code, message else: LOGGER.info( 'Reports are not generated because of your settings.') display_warning_message_bar( tr('Reports'), tr('Reports are not going to be generated because of your ' 'InaSAFE settings.'), duration=10, iface_object=self.iface) # If zoom to impact is enabled if setting('setZoomToImpactFlag', expected_type=bool): self.iface.zoomToActiveLayer() # If hide exposure layers if setting('setHideExposureFlag', expected_type=bool): treeroot = QgsProject.instance().layerTreeRoot() for combo in list(self.combos_exposures.values()): layer = layer_from_combo(combo) if layer is not None: treelayer = treeroot.findLayer(layer.id()) if treelayer: treelayer.setItemVisibilityChecked(False) # Set last analysis extent self._extent.set_last_analysis_extent( self._multi_exposure_if.analysis_extent, self._multi_exposure_if.crs) self.done(QDialog.Accepted) except Exception as e: error_message = get_error_message(e) send_error_message(self, error_message) LOGGER.exception(e) LOGGER.debug(error_message.to_text()) finally: disable_busy_cursor() self.set_enabled_buttons(True) def reject(self): """Redefinition of the reject method.""" self._populate_reporting_tab() super(MultiExposureDialog, self).reject() def set_enabled_buttons(self, enabled): self.btn_cancel.setEnabled(enabled) self.btn_back.setEnabled(enabled) self.btn_next.setEnabled(enabled) self.btn_run.setEnabled(enabled)