def save_hazard_layer(self, hazard_path): # download or copy hazard path/url # It is a single tif file if not hazard_path and not os.path.exists(self.hazard_path): raise IOError("Hazard file not specified") if hazard_path: temp_hazard = download_file(hazard_path) shutil.copy(temp_hazard, self.hazard_path) # copy qml and metadata shutil.copy(self.ash_fixtures_dir("hazard.qml"), self.working_dir_path("hazard.qml")) keyword_io = KeywordIO() keywords = { "hazard_category": u"single_event", "keyword_version": u"3.5", "title": u"Ash Fall", "hazard": u"volcanic_ash", "continuous_hazard_unit": u"centimetres", "layer_geometry": u"raster", "layer_purpose": u"hazard", "layer_mode": u"continuous", } hazard_layer = read_qgis_layer(self.hazard_path, "Ash Fall") keyword_io.write_keywords(hazard_layer, keywords)
def save_hazard_layer(self, hazard_path): # download or copy hazard path/url # It is a single tif file if not hazard_path and not os.path.exists(self.hazard_path): raise IOError('Hazard file not specified') if hazard_path: temp_hazard = download_file(hazard_path) shutil.copy(temp_hazard, self.hazard_path) # copy qml and metadata shutil.copy(self.ash_fixtures_dir('hazard.qml'), self.working_dir_path('hazard.qml')) keyword_io = KeywordIO() keywords = { 'hazard_category': u'single_event', 'keyword_version': u'3.5', 'title': u'Ash Fall', 'hazard': u'volcanic_ash', 'continuous_hazard_unit': u'centimetres', 'layer_geometry': u'raster', 'layer_purpose': u'hazard', 'layer_mode': u'continuous' } hazard_layer = read_qgis_layer(self.hazard_path, 'Ash Fall') keyword_io.write_keywords(hazard_layer, keywords)
def create_keyword_file(self, algorithm): """Create keyword file for the raster file created. Basically copy a template from keyword file in converter data and add extra keyword (usually a title) :param algorithm: Which re-sampling algorithm to use. valid options are 'nearest' (for nearest neighbour), 'invdist' (for inverse distance), 'average' (for moving average). Defaults to 'nearest' if not specified. Note that passing re-sampling alg parameters is currently not supported. If None is passed it will be replaced with 'nearest'. :type algorithm: str """ keyword_io = KeywordIO() classes = {} for item in earthquake_mmi_scale['classes']: classes[item['key']] = [ item['numeric_default_min'], item['numeric_default_max']] keywords = { 'hazard': hazard_earthquake['key'], 'hazard_category': hazard_category_single_event['key'], 'keyword_version': inasafe_keyword_version, 'layer_geometry': layer_geometry_raster['key'], 'layer_mode': layer_mode_continuous['key'], 'layer_purpose': layer_purpose_hazard['key'], 'continuous_hazard_unit': unit_mmi['key'], 'classification': earthquake_mmi_scale['key'], 'thresholds': classes } if self.algorithm_name: layer_path = os.path.join( self.output_dir, '%s-%s.tif' % ( self.output_basename, algorithm)) else: layer_path = os.path.join( self.output_dir, '%s.tif' % self.output_basename) # append title and source to the keywords file if len(self.title.strip()) == 0: keyword_title = self.output_basename else: keyword_title = self.title keywords['title'] = keyword_title hazard_layer = QgsRasterLayer(layer_path, keyword_title) if not hazard_layer.isValid(): raise InvalidLayerError() keyword_io.write_keywords(hazard_layer, keywords)
def buffer_points(point_layer, radii, hazard_zone_attribute, output_crs): """Buffer points for each point with defined radii. This function is used for making buffer of volcano point hazard. :param point_layer: A point layer to buffer. :type point_layer: QgsVectorLayer :param radii: Desired approximate radii in kilometers (must be monotonically ascending). Can be either one number or list of numbers :type radii: int, list :param hazard_zone_attribute: The name of the attributes representing hazard zone. :type hazard_zone_attribute: str :param output_crs: The output CRS. :type output_crs: QgsCoordinateReferenceSystem :return: Vector polygon layer representing circle in point layer CRS. :rtype: QgsVectorLayer """ if not isinstance(radii, list): radii = [radii] if not is_point_layer(point_layer): message = ('Input hazard must be a vector point layer. I got %s ' 'with layer type %s' % (point_layer.name(), point_layer.type())) raise Exception(message) # Check that radii are monotonically increasing monotonically_increasing_flag = all(x < y for x, y in zip(radii, radii[1:])) if not monotonically_increasing_flag: raise RadiiException(RadiiException.suggestion) hazard_file_path = unique_filename(suffix='-polygon-volcano.shp') fields = point_layer.pendingFields() fields.append(QgsField(hazard_zone_attribute, QVariant.Double)) writer = QgsVectorFileWriter(hazard_file_path, 'utf-8', fields, QGis.WKBPolygon, output_crs, 'ESRI Shapefile') input_crs = point_layer.crs() center = point_layer.extent().center() utm = None if output_crs.authid() == 'EPSG:4326': utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(point_layer.crs(), utm) else: transform = QgsCoordinateTransform(point_layer.crs(), output_crs) for point in point_layer.getFeatures(): geom = point.geometry() geom.transform(transform) inner_rings = None for radius in radii: attributes = point.attributes() # Generate circle polygon circle = geom.buffer(radius * 1000.0, 30) if inner_rings: circle.addRing(inner_rings) inner_rings = circle.asPolygon()[0] new_buffer = QgsFeature() if output_crs.authid() == 'EPSG:4326': circle.transform(QgsCoordinateTransform(utm, output_crs)) new_buffer.setGeometry(circle) attributes.append(radius) new_buffer.setAttributes(attributes) writer.addFeature(new_buffer) del writer vector_layer = QgsVectorLayer(hazard_file_path, 'Polygons', 'ogr') keyword_io = KeywordIO() try: keywords = keyword_io.read_keywords(point_layer) keyword_io.write_keywords(vector_layer, keywords) except NoKeywordsFoundError: pass return vector_layer
def create_keyword_file(self, algorithm): """Create keyword file for the raster file created. Basically copy a template from keyword file in converter data and add extra keyword (usually a title) :param algorithm: Which re-sampling algorithm to use. valid options are 'nearest' (for nearest neighbour), 'invdist' (for inverse distance), 'average' (for moving average). Defaults to 'nearest' if not specified. Note that passing re-sampling alg parameters is currently not supported. If None is passed it will be replaced with 'nearest'. :type algorithm: str """ keyword_io = KeywordIO() # Set thresholds for each exposure mmi_default_classes = default_classification_thresholds( earthquake_mmi_scale) mmi_default_threshold = { earthquake_mmi_scale['key']: { 'active': True, 'classes': mmi_default_classes } } generic_default_classes = default_classification_thresholds( generic_hazard_classes) generic_default_threshold = { generic_hazard_classes['key']: { 'active': True, 'classes': generic_default_classes } } threshold_keyword = {} for exposure in exposure_all: # Not all exposure is supported by earthquake_mmi_scale if exposure in earthquake_mmi_scale['exposures']: threshold_keyword[exposure['key']] = mmi_default_threshold else: threshold_keyword[exposure['key']] = generic_default_threshold extra_keywords = { extra_keyword_earthquake_latitude['key']: self.latitude, extra_keyword_earthquake_longitude['key']: self.longitude, extra_keyword_earthquake_magnitude['key']: self.magnitude, extra_keyword_earthquake_depth['key']: self.depth, extra_keyword_earthquake_description['key']: self.description, extra_keyword_earthquake_location['key']: self.location, extra_keyword_earthquake_event_time['key']: self.time.strftime('%Y-%m-%dT%H:%M:%S'), extra_keyword_time_zone['key']: self.time_zone, extra_keyword_earthquake_x_minimum['key']: self.x_minimum, extra_keyword_earthquake_x_maximum['key']: self.x_maximum, extra_keyword_earthquake_y_minimum['key']: self.y_minimum, extra_keyword_earthquake_y_maximum['key']: self.y_maximum, extra_keyword_earthquake_event_id['key']: self.event_id } for key, value in self.extra_keywords.items(): extra_keywords[key] = value # Delete empty element. empty_keys = [] for key, value in extra_keywords.items(): if value is None: empty_keys.append(key) for empty_key in empty_keys: extra_keywords.pop(empty_key) keywords = { 'hazard': hazard_earthquake['key'], 'hazard_category': hazard_category_single_event['key'], 'keyword_version': inasafe_keyword_version, 'layer_geometry': layer_geometry_raster['key'], 'layer_mode': layer_mode_continuous['key'], 'layer_purpose': layer_purpose_hazard['key'], 'continuous_hazard_unit': unit_mmi['key'], 'classification': earthquake_mmi_scale['key'], 'thresholds': threshold_keyword, 'extra_keywords': extra_keywords, 'active_band': 1 } if self.algorithm_name: layer_path = os.path.join( self.output_dir, '%s-%s.tif' % (self.output_basename, algorithm)) else: layer_path = os.path.join(self.output_dir, '%s.tif' % self.output_basename) # append title and source to the keywords file if len(self.title.strip()) == 0: keyword_title = self.output_basename else: keyword_title = self.title keywords['title'] = keyword_title hazard_layer = QgsRasterLayer(layer_path, keyword_title) if not hazard_layer.isValid(): raise InvalidLayerError() keyword_io.write_keywords(hazard_layer, keywords)
class FieldMappingDialog(QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE field mapping tool.""" def __init__(self, parent=None, iface=None, setting=None): """Constructor.""" QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Field Mapping Tool')) icon = resources_path('img', 'icons', 'show-mapping-tool.svg') self.setWindowIcon(QIcon(icon)) self.parent = parent self.iface = iface if setting is None: setting = QSettings() self.setting = setting self.keyword_io = KeywordIO() self.layer = None self.metadata = {} self.layer_input_layout = QHBoxLayout() self.layer_label = QLabel(tr('Layer')) self.layer_combo_box = QgsMapLayerComboBox() # Filter only for Polygon and Point self.layer_combo_box.setFilters( QgsMapLayerProxyModel.PolygonLayer | QgsMapLayerProxyModel.PointLayer) # Filter out a layer that don't have layer groups excepted_layers = [] for i in range(self.layer_combo_box.count()): layer = self.layer_combo_box.layer(i) try: keywords = self.keyword_io.read_keywords(layer) except (KeywordNotFoundError, NoKeywordsFoundError): excepted_layers.append(layer) continue layer_purpose = keywords.get('layer_purpose') if not layer_purpose: excepted_layers.append(layer) continue if layer_purpose == layer_purpose_exposure['key']: layer_subcategory = keywords.get('exposure') elif layer_purpose == layer_purpose_hazard['key']: layer_subcategory = keywords.get('hazard') else: layer_subcategory = None field_groups = get_field_groups(layer_purpose, layer_subcategory) if len(field_groups) == 0: excepted_layers.append(layer) continue self.layer_combo_box.setExceptedLayerList(excepted_layers) # Select the active layer. if self.iface.activeLayer(): found = self.layer_combo_box.findText( self.iface.activeLayer().name()) if found > -1: self.layer_combo_box.setLayer(self.iface.activeLayer()) self.field_mapping_widget = None self.main_stacked_widget.setCurrentIndex(1) # Input self.layer_input_layout.addWidget(self.layer_label) self.layer_input_layout.addWidget(self.layer_combo_box) self.header_label = QLabel() self.header_label.setWordWrap(True) self.main_layout.addWidget(self.header_label) self.main_layout.addLayout(self.layer_input_layout) # Signal self.layer_combo_box.layerChanged.connect(self.set_layer) if self.layer_combo_box.currentLayer(): self.set_layer(self.layer_combo_box.currentLayer()) # Set up things for context help self.help_button = self.button_box.button(QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) # Set up things for ok button self.ok_button = self.button_box.button(QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up things for cancel button self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) self.cancel_button.clicked.connect(self.reject) def set_layer(self, layer=None, keywords=None): """Set layer and update UI accordingly. :param layer: A QgsVectorLayer. :type layer: QgsVectorLayer :param keywords: Keywords for the layer. :type keywords: dict, None """ if self.field_mapping_widget is not None: self.field_mapping_widget.setParent(None) self.field_mapping_widget.close() self.field_mapping_widget.deleteLater() self.main_layout.removeWidget(self.field_mapping_widget) if layer: self.layer = layer else: self.layer = self.layer_combo_box.currentLayer() if not self.layer: return if keywords is not None: self.metadata = keywords else: # Always read from metadata file. try: self.metadata = self.keyword_io.read_keywords(self.layer) except ( NoKeywordsFoundError, KeywordNotFoundError, MetadataReadError) as e: raise e if 'inasafe_default_values' not in self.metadata: self.metadata['inasafe_default_values'] = {} if 'inasafe_fields' not in self.metadata: self.metadata['inasafe_fields'] = {} self.field_mapping_widget = FieldMappingWidget( parent=self, iface=self.iface) self.field_mapping_widget.set_layer(self.layer, self.metadata) self.field_mapping_widget.show() self.main_layout.addWidget(self.field_mapping_widget) # Set header label group_names = [ self.field_mapping_widget.tabText(i) for i in range( self.field_mapping_widget.count())] if len(group_names) == 0: header_text = tr( 'There is no field group for this layer. Please select ' 'another layer.') self.header_label.setText(header_text) return elif len(group_names) == 1: pretty_group_name = group_names[0] elif len(group_names) == 2: pretty_group_name = group_names[0] + tr(' and ') + group_names[1] else: pretty_group_name = ', '.join(group_names[:-1]) pretty_group_name += tr(', and {0}').format(group_names[-1]) header_text = tr( 'Please fill the information for every tab to determine the ' 'attribute for {0} group.').format(pretty_group_name) self.header_label.setText(header_text) @pyqtSlot() @pyqtSignature('bool') # prevents actions being handled twice def help_toggled(self, flag): """Show or hide the help tab in the stacked widget. .. versionadded: 3.2.1 :param flag: Flag indicating whether help should be shown or hidden. :type flag: bool """ if flag: self.help_button.setText(self.tr('Hide Help')) self.show_help() else: self.help_button.setText(self.tr('Show Help')) self.hide_help() def hide_help(self): """Hide the usage info from the user. .. versionadded: 3.2.1 """ self.main_stacked_widget.setCurrentIndex(1) def show_help(self): """Show usage info to the user.""" # Read the header and footer html snippets self.main_stacked_widget.setCurrentIndex(0) header = html_header() footer = html_footer() string = header message = field_mapping_help() string += message.to_html() string += footer self.help_web_view.setHtml(string) def save_metadata(self): """Save metadata based on the field mapping state.""" metadata = self.field_mapping_widget.get_field_mapping() for key, value in metadata['fields'].items(): # Delete the key if it's set to None if key in self.metadata['inasafe_default_values']: self.metadata['inasafe_default_values'].pop(key) if value is None or value == []: if key in self.metadata['inasafe_fields']: self.metadata['inasafe_fields'].pop(key) else: self.metadata['inasafe_fields'][key] = value for key, value in metadata['values'].items(): # Delete the key if it's set to None if key in self.metadata['inasafe_fields']: self.metadata['inasafe_fields'].pop(key) if value is None: if key in self.metadata['inasafe_default_values']: self.metadata['inasafe_default_values'].pop(key) else: self.metadata['inasafe_default_values'][key] = value # Save metadata try: self.keyword_io.write_keywords( layer=self.layer, keywords=self.metadata) except InaSAFEError, e: error_message = get_error_message(e) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QMessageBox.warning( self, self.tr('InaSAFE'), ((self.tr( 'An error was encountered when saving the following ' 'keywords:\n %s') % error_message.to_html()))) # Update setting fir recent value if self.metadata.get('inasafe_default_values'): for key, value in \ self.metadata['inasafe_default_values'].items(): set_inasafe_default_value_qsetting( self.setting, key, RECENT, value)
class KeywordsDialog(QtGui.QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE keywords editor.""" def __init__(self, parent, iface, dock=None, layer=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: Quantum GIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(self.tr( 'InaSAFE %s Keywords Editor' % get_version())) # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.defaults = None # string constants self.global_default_string = metadata.global_default_attribute['name'] self.do_not_use_string = metadata.do_not_use_attribute['name'] self.global_default_data = metadata.global_default_attribute['id'] self.do_not_use_data = metadata.do_not_use_attribute['id'] if layer is None: self.layer = self.iface.activeLayer() else: self.layer = layer self.keyword_io = KeywordIO() # note the keys should remain untranslated as we need to write # english to the keywords file. The keys will be written as user data # in the combo entries. # .. seealso:: http://www.voidspace.org.uk/python/odict.html self.standard_exposure_list = OrderedDict( [('population', self.tr('population')), ('structure', self.tr('structure')), ('road', self.tr('road')), ('Not Set', self.tr('Not Set'))]) self.standard_hazard_list = OrderedDict( [('earthquake [MMI]', self.tr('earthquake [MMI]')), ('tsunami [m]', self.tr('tsunami [m]')), ('tsunami [wet/dry]', self.tr('tsunami [wet/dry]')), ('tsunami [feet]', self.tr('tsunami [feet]')), ('flood [m]', self.tr('flood [m]')), ('flood [wet/dry]', self.tr('flood [wet/dry]')), ('flood [feet]', self.tr('flood [feet]')), ('tephra [kg2/m2]', self.tr('tephra [kg2/m2]')), ('volcano', self.tr('volcano')), ('generic [categorised]', self.tr('generic [categorised]')), ('Not Set', self.tr('Not Set'))]) # noinspection PyUnresolvedReferences self.lstKeywords.itemClicked.connect(self.edit_key_value_pair) # Set up help dialog showing logic. help_button = self.buttonBox.button(QtGui.QDialogButtonBox.Help) help_button.clicked.connect(self.show_help) if self.layer is not None and is_polygon_layer(self.layer): # set some initial ui state: self.defaults = get_defaults() self.radPredefined.setChecked(True) self.dsbFemaleRatioDefault.blockSignals(True) self.dsbFemaleRatioDefault.setValue(self.defaults['FEMALE_RATIO']) self.dsbFemaleRatioDefault.blockSignals(False) self.dsbYouthRatioDefault.blockSignals(True) self.dsbYouthRatioDefault.setValue(self.defaults['YOUTH_RATIO']) self.dsbYouthRatioDefault.blockSignals(False) self.dsbAdultRatioDefault.blockSignals(True) self.dsbAdultRatioDefault.setValue(self.defaults['ADULT_RATIO']) self.dsbAdultRatioDefault.blockSignals(False) self.dsbElderlyRatioDefault.blockSignals(True) self.dsbElderlyRatioDefault.setValue( self.defaults['ELDERLY_RATIO']) self.dsbElderlyRatioDefault.blockSignals(False) else: self.radPostprocessing.hide() self.tab_widget.removeTab(1) if self.layer: self.load_state_from_keywords() # add a reload from keywords button reload_button = self.buttonBox.addButton( self.tr('Reload'), QtGui.QDialogButtonBox.ActionRole) reload_button.clicked.connect(self.load_state_from_keywords) self.resize_dialog() self.tab_widget.setCurrentIndex(0) # TODO No we should not have test related stuff in prod code. TS self.test = False def set_layer(self, layer): """Set the layer associated with the keyword editor. :param layer: Layer whose keywords should be edited. :type layer: QgsMapLayer """ self.layer = layer self.load_state_from_keywords() # noinspection PyMethodMayBeStatic def show_help(self): """Load the help text for the keywords dialog.""" show_context_help(context='keywords_editor') def toggle_postprocessing_widgets(self): """Hide or show the post processing widgets depending on context.""" LOGGER.debug('togglePostprocessingWidgets') # TODO Too much baggage here. Can't we just disable/enable the tab? TS postprocessing_flag = self.radPostprocessing.isChecked() self.cboSubcategory.setVisible(not postprocessing_flag) self.lblSubcategory.setVisible(not postprocessing_flag) self.show_aggregation_attribute() self.show_female_ratio_attribute() self.show_female_ratio_default() self.show_youth_ratio_attribute() self.show_youth_ratio_default() self.show_adult_ratio_attribute() self.show_adult_ratio_default() self.show_elderly_ratio_attribute() self.show_elderly_ratio_default() # Also enable/disable the aggregation tab self.aggregation_tab.setEnabled(postprocessing_flag) def show_aggregation_attribute(self): """Hide or show the aggregation attribute in the keyword editor dialog. """ box = self.cboAggregationAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['AGGR_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Int, QtCore.QVariant.String], current_keyword) box.addItems(fields) if attribute_position is None: box.setCurrentIndex(0) else: box.setCurrentIndex(attribute_position) def show_female_ratio_attribute(self): """Hide or show the female ratio attribute in the dialog. """ box = self.cboFemaleRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['FEMALE_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_female_ratio_default(self): """Hide or show the female ratio default attribute in the dialog. """ box = self.dsbFemaleRatioDefault current_value = self.get_value_for_key( self.defaults['FEMALE_RATIO_KEY']) if current_value is None: val = self.defaults['FEMALE_RATIO'] else: val = float(current_value) box.setValue(val) def show_youth_ratio_attribute(self): """Hide or show the youth ratio attribute in the dialog. """ box = self.cboYouthRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['YOUTH_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_youth_ratio_default(self): """Hide or show the youth ratio default attribute in the dialog. """ box = self.dsbYouthRatioDefault current_value = self.get_value_for_key( self.defaults['YOUTH_RATIO_KEY']) if current_value is None: val = self.defaults['YOUTH_RATIO'] else: val = float(current_value) box.setValue(val) def show_adult_ratio_attribute(self): """Hide or show the adult ratio attribute in the dialog. """ box = self.cboAdultRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['ADULT_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_adult_ratio_default(self): """Hide or show the adult ratio default attribute in the dialog. """ box = self.dsbAdultRatioDefault current_value = self.get_value_for_key( self.defaults['ADULT_RATIO_KEY']) if current_value is None: val = self.defaults['ADULT_RATIO'] else: val = float(current_value) box.setValue(val) def show_elderly_ratio_attribute(self): """Show the elderly ratio attribute in the dialog. """ box = self.cboElderlyRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['ELDERLY_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_elderly_ratio_default(self): """Show the elderly ratio default attribute in the dialog. """ box = self.dsbElderlyRatioDefault current_value = self.get_value_for_key( self.defaults['ELDERLY_RATIO_KEY']) if current_value is None: val = self.defaults['ELDERLY_RATIO'] else: val = float(current_value) box.setValue(val) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('int') def on_cboAggregationAttribute_currentIndexChanged(self, index=None): """Handler for aggregation attribute combo change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return self.add_list_entry( self.defaults['AGGR_ATTR_KEY'], self.cboAggregationAttribute.currentText()) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('int') def on_cboFemaleRatioAttribute_currentIndexChanged(self, index=None): """Handler for female ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboFemaleRatioAttribute.currentIndex() data = self.cboFemaleRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbFemaleRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['FEMALE_RATIO_KEY']) if current_default is None: self.add_list_entry( self.defaults['FEMALE_RATIO_KEY'], self.dsbFemaleRatioDefault.value()) else: self.dsbFemaleRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY']) self.add_list_entry(self.defaults['FEMALE_RATIO_ATTR_KEY'], data) # noinspection PyPep8Naming def on_cboYouthRatioAttribute_currentIndexChanged(self, index=None): """Handler for youth ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboYouthRatioAttribute.currentIndex() data = self.cboYouthRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbYouthRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['YOUTH_RATIO_KEY']) if current_default is None: self.add_list_entry( self.defaults['YOUTH_RATIO_KEY'], self.dsbYouthRatioDefault.value()) else: self.dsbYouthRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY']) self.add_list_entry(self.defaults['YOUTH_RATIO_ATTR_KEY'], data) # noinspection PyPep8Naming def on_cboAdultRatioAttribute_currentIndexChanged(self, index=None): """Handler for adult ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboAdultRatioAttribute.currentIndex() data = self.cboAdultRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbAdultRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['ADULT_RATIO_KEY']) if current_default is None: self.add_list_entry( self.defaults['ADULT_RATIO_KEY'], self.dsbAdultRatioDefault.value()) else: self.dsbAdultRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY']) self.add_list_entry(self.defaults['ADULT_RATIO_ATTR_KEY'], data) # noinspection PyPep8Naming def on_cboElderlyRatioAttribute_currentIndexChanged(self, index=None): """Handler for elderly ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboElderlyRatioAttribute.currentIndex() data = self.cboElderlyRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbElderlyRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['ELDERLY_RATIO_KEY']) if current_default is None: self.add_list_entry( self.defaults['ELDERLY_RATIO_KEY'], self.dsbElderlyRatioDefault.value()) else: self.dsbElderlyRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY']) self.add_list_entry(self.defaults['ELDERLY_RATIO_ATTR_KEY'], data) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('double') def on_dsbFemaleRatioDefault_valueChanged(self, value): """Handler for female ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbFemaleRatioDefault if box.isEnabled(): self.add_list_entry( self.defaults['FEMALE_RATIO_KEY'], box.value()) # noinspection PyPep8Naming def on_dsbYouthRatioDefault_valueChanged(self, value): """Handler for youth ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbYouthRatioDefault if box.isEnabled(): self.add_list_entry( self.defaults['YOUTH_RATIO_KEY'], box.value()) # noinspection PyPep8Naming def on_dsbAdultRatioDefault_valueChanged(self, value): """Handler for adult ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbAdultRatioDefault if box.isEnabled(): self.add_list_entry( self.defaults['ADULT_RATIO_KEY'], box.value()) # noinspection PyPep8Naming def on_dsbElderlyRatioDefault_valueChanged(self, value): """Handler for elderly ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbElderlyRatioDefault if box.isEnabled(): self.add_list_entry( self.defaults['ELDERLY_RATIO_KEY'], box.value()) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('bool') def on_radHazard_toggled(self, flag): """Automatic slot executed when the hazard radio is toggled. :param flag: Flag indicating the new checked state of the button. :type flag: bool """ if not flag: return self.set_category('hazard') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('bool') def on_radExposure_toggled(self, theFlag): """Automatic slot executed when the hazard radio is toggled on. :param theFlag: Flag indicating the new checked state of the button. :type theFlag: bool """ if not theFlag: return self.set_category('exposure') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('bool') def on_radPostprocessing_toggled(self, flag): """Automatic slot executed when the hazard radio is toggled on. :param flag: Flag indicating the new checked state of the button. :type flag: bool """ if flag: self.set_category('postprocessing') self.update_controls_from_list() return if self.defaults is not None: self.remove_item_by_key(self.defaults['AGGR_ATTR_KEY']) self.remove_item_by_key(self.defaults['FEMALE_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY']) self.remove_item_by_key(self.defaults['YOUTH_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY']) self.remove_item_by_key(self.defaults['ADULT_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY']) self.remove_item_by_key(self.defaults['ELDERLY_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY']) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('int') def on_cboSubcategory_currentIndexChanged(self, index=None): """Automatic slot executed when the subcategory is changed. When the user changes the subcategory, we will extract the subcategory and dataype or unit (depending on if it is a hazard or exposure subcategory) from the [] after the name. :param index: Not used but required for Qt slot. """ if index == -1: self.remove_item_by_key('subcategory') return text = self.cboSubcategory.itemData( self.cboSubcategory.currentIndex()) # I found that myText is 'Not Set' for every language if text == self.tr('Not Set') or text == 'Not Set': self.remove_item_by_key('subcategory') return tokens = text.split(' ') if len(tokens) < 1: self.remove_item_by_key('subcategory') return subcategory = tokens[0] self.add_list_entry('subcategory', subcategory) # Some subcategories e.g. roads have no units or datatype if len(tokens) == 1: return if tokens[1].find('[') < 0: return category = self.get_value_for_key('category') if 'hazard' == category: units = tokens[1].replace('[', '').replace(']', '') self.add_list_entry('unit', units) if 'exposure' == category: data_type = tokens[1].replace('[', '').replace(']', '') self.add_list_entry('datatype', data_type) # prevents actions being handled twice def set_subcategory_list(self, entries, selected_item=None): """Helper to populate the subcategory list based on category context. :param entries: An OrderedDict of subcategories. The dict entries should be in the form ('earthquake', self.tr('earthquake')). See http://www.voidspace.org.uk/python/odict.html for info on OrderedDict. :type entries: OrderedDict :param selected_item: Which item should be selected in the combo. If the selected item is not in entries, it will be appended to it. This is optional. :type selected_item: str """ # To avoid triggering on_cboSubcategory_currentIndexChanged # we block signals from the combo while updating it self.cboSubcategory.blockSignals(True) self.cboSubcategory.clear() item_selected_flag = selected_item is not None selected_item_values = selected_item not in entries.values() selected_item_keys = selected_item not in entries.keys() if (item_selected_flag and selected_item_values and selected_item_keys): # Add it to the OrderedList entries[selected_item] = selected_item index = 0 selected_index = 0 for key, value in entries.iteritems(): if value == selected_item or key == selected_item: selected_index = index index += 1 self.cboSubcategory.addItem(value, key) self.cboSubcategory.setCurrentIndex(selected_index) self.cboSubcategory.blockSignals(False) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnAddToList1_clicked(self): """Automatic slot executed when the pbnAddToList1 button is pressed. """ if (self.lePredefinedValue.text() != "" and self.cboKeyword.currentText() != ""): current_key = self.tr(self.cboKeyword.currentText()) current_value = self.lePredefinedValue.text() self.add_list_entry(current_key, current_value) self.lePredefinedValue.setText('') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnAddToList2_clicked(self): """Automatic slot executed when the pbnAddToList2 button is pressed. """ current_key = self.leKey.text() current_value = self.leValue.text() if current_key == 'category' and current_value == 'hazard': self.radHazard.blockSignals(True) self.radHazard.setChecked(True) self.set_subcategory_list(self.standard_hazard_list) self.radHazard.blockSignals(False) elif current_key == 'category' and current_value == 'exposure': self.radExposure.blockSignals(True) self.radExposure.setChecked(True) self.set_subcategory_list(self.standard_exposure_list) self.radExposure.blockSignals(False) elif current_key == 'category': # .. todo:: notify the user their category is invalid pass self.add_list_entry(current_key, current_value) self.leKey.setText('') self.leValue.setText('') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnRemove_clicked(self): """Automatic slot executed when the pbnRemove button is pressed. Any selected items in the keywords list will be removed. """ for item in self.lstKeywords.selectedItems(): self.lstKeywords.takeItem(self.lstKeywords.row(item)) self.leKey.setText('') self.leValue.setText('') self.update_controls_from_list() def add_list_entry(self, key, value): """Add an item to the keywords list given its key/value. The key and value must both be valid, non empty strings or an InvalidKVPError will be raised. If an entry with the same key exists, it's value will be replaced with value. It will add the current key/value pair to the list if it is not already present. The kvp will also be stored in the data of the listwidgetitem as a simple string delimited with a bar ('|'). :param key: The key part of the key value pair (kvp). :type key: str :param value: Value part of the key value pair (kvp). :type value: str """ if key is None or key == '': return if value is None or value == '': return # make sure that both key and value is string key = str(key) value = str(value) message = '' if ':' in key: key = key.replace(':', '.') message = self.tr('Colons are not allowed, replaced with "."') if ':' in value: value = value.replace(':', '.') message = self.tr('Colons are not allowed, replaced with "."') if message == '': self.lblMessage.setText('') self.lblMessage.hide() else: self.lblMessage.setText(message) self.lblMessage.show() item = QtGui.QListWidgetItem(key + ':' + value) # We are going to replace, so remove it if it exists already self.remove_item_by_key(key) data = key + '|' + value item.setData(QtCore.Qt.UserRole, data) self.lstKeywords.insertItem(0, item) def set_category(self, category): """Set the category radio button based on category. :param category: Either 'hazard', 'exposure' or 'postprocessing'. :type category: str :returns: False if radio button could not be updated, otherwise True. :rtype: bool """ # convert from QString if needed category = str(category) if self.get_value_for_key('category') == category: # nothing to do, go home return True if category not in ['hazard', 'exposure', 'postprocessing']: # .. todo:: report an error to the user return False # Special case when category changes, we start on a new slate! if category == 'hazard': # only cause a toggle if we actually changed the category # This will only really be apparent if user manually enters # category as a keyword self.reset() self.radHazard.blockSignals(True) self.radHazard.setChecked(True) self.radHazard.blockSignals(False) self.remove_item_by_key('subcategory') self.remove_item_by_key('datatype') self.add_list_entry('category', 'hazard') hazard_list = self.standard_hazard_list self.set_subcategory_list(hazard_list) elif category == 'exposure': self.reset() self.radExposure.blockSignals(True) self.radExposure.setChecked(True) self.radExposure.blockSignals(False) self.remove_item_by_key('subcategory') self.remove_item_by_key('unit') self.add_list_entry('category', 'exposure') exposure_list = self.standard_exposure_list self.set_subcategory_list(exposure_list) else: self.reset() self.radPostprocessing.blockSignals(True) self.radPostprocessing.setChecked(True) self.radPostprocessing.blockSignals(False) self.remove_item_by_key('subcategory') self.add_list_entry('category', 'postprocessing') return True def reset(self, primary_keywords_only=True): """Reset all controls to a blank state. :param primary_keywords_only: If True (the default), only reset Subcategory, datatype and units. :type primary_keywords_only: bool """ self.cboSubcategory.clear() self.remove_item_by_key('subcategory') self.remove_item_by_key('datatype') self.remove_item_by_key('unit') self.remove_item_by_key('source') if not primary_keywords_only: # Clear everything else too self.lstKeywords.clear() self.leKey.clear() self.leValue.clear() self.lePredefinedValue.clear() self.leTitle.clear() self.leSource.clear() def remove_item_by_key(self, removal_key): """Remove an item from the kvp list given its key. :param removal_key: Key of item to be removed. :type removal_key: str """ for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') if len(tokens) < 2: break key = tokens[0] if removal_key == key: # remove it since the removal_key is already present self.blockSignals(True) self.lstKeywords.takeItem(counter) self.blockSignals(False) break def remove_item_by_value(self, removal_value): """Remove an item from the kvp list given its key. :param removal_value: Value of item to be removed. :type removal_value: str """ for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') value = tokens[1] if removal_value == value: # remove it since the key is already present self.blockSignals(True) self.lstKeywords.takeItem(counter) self.blockSignals(False) break def get_value_for_key(self, lookup_key): """If key list contains a specific key, return its value. :param lookup_key: The key to search for :type lookup_key: str :returns: Value of key if matched otherwise none. :rtype: str """ for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') key = str(tokens[0]).strip() value = str(tokens[1]).strip() if lookup_key == key: return value return None def load_state_from_keywords(self): """Set the ui state to match the keywords of the active layer. In case the layer has no keywords or any problem occurs reading them, start with a blank state so that subcategory gets populated nicely & we will assume exposure to start with. Also if only title is set we use similar logic (title is added by default in dock and other defaults need to be explicitly added when opening this dialog). See #751 """ keywords = {'category': 'exposure'} try: # Now read the layer with sub layer if needed keywords = self.keyword_io.read_keywords(self.layer) except (InvalidParameterError, HashNotFoundError, NoKeywordsFoundError): pass layer_name = self.layer.name() if 'title' not in keywords: self.leTitle.setText(layer_name) self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name)) if 'source' in keywords: self.leSource.setText(keywords['source']) else: self.leSource.setText('') # if we have a category key, unpack it first # so radio button etc get set if 'category' in keywords: self.set_category(keywords['category']) keywords.pop('category') else: # assume exposure to match ui. See issue #751 self.add_list_entry('category', 'exposure') aggregation_attributes = [ DEFAULTS['FEMALE_RATIO_ATTR_KEY'], DEFAULTS['YOUTH_RATIO_ATTR_KEY'], DEFAULTS['ADULT_RATIO_ATTR_KEY'], DEFAULTS['ELDERLY_RATIO_ATTR_KEY'], ] for key in keywords.iterkeys(): if key in aggregation_attributes: if str(keywords[key]) == 'Use default': self.add_list_entry(key, self.global_default_data) continue self.add_list_entry(key, str(keywords[key])) # now make the rest of the safe_qgis reflect the list entries self.update_controls_from_list() def update_controls_from_list(self): """Set the ui state to match the keywords of the active layer.""" subcategory = self.get_value_for_key('subcategory') units = self.get_value_for_key('unit') data_type = self.get_value_for_key('datatype') title = self.get_value_for_key('title') if title is not None: self.leTitle.setText(title) elif self.layer is not None: layer_name = self.layer.name() self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name)) else: self.lblLayerName.setText('') if not is_polygon_layer(self.layer): self.radPostprocessing.setEnabled(False) else: # adapt gui if we are in postprocessing category self.toggle_postprocessing_widgets() if self.radExposure.isChecked(): if subcategory is not None and data_type is not None: self.set_subcategory_list( self.standard_exposure_list, subcategory + ' [' + data_type + ']') elif subcategory is not None: self.set_subcategory_list( self.standard_exposure_list, subcategory) else: self.set_subcategory_list( self.standard_exposure_list, self.tr('Not Set')) elif self.radHazard.isChecked(): if subcategory is not None and units is not None: self.set_subcategory_list( self.standard_hazard_list, subcategory + ' [' + units + ']') elif subcategory is not None: self.set_subcategory_list( self.standard_hazard_list, subcategory) else: self.set_subcategory_list( self.standard_hazard_list, self.tr('Not Set')) self.resize_dialog() def resize_dialog(self): """Resize the dialog to fit its contents.""" # noinspection PyArgumentList QtCore.QCoreApplication.processEvents() LOGGER.debug('adjust ing dialog size') self.adjustSize() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('QString') def on_leTitle_textEdited(self, title): """Update the keywords list whenever the user changes the title. This slot is not called if the title is changed programmatically. :param title: New title keyword for the layer. :type title: str """ self.add_list_entry('title', str(title)) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('QString') def on_leSource_textEdited(self, source): """Update the keywords list whenever the user changes the source. This slot is not called if the source is changed programmatically. :param source: New source keyword for the layer. :type source: str """ if source is None or source == '': self.remove_item_by_key('source') else: self.add_list_entry('source', str(source)) def get_keywords(self): """Obtain the state of the dialog as a keywords dict. :returns: Keywords reflecting the state of the dialog. :rtype: dict """ # make sure title is listed if str(self.leTitle.text()) != '': self.add_list_entry('title', str(self.leTitle.text())) # make sure the source is listed too if str(self.leSource.text()) != '': self.add_list_entry('source', str(self.leSource.text())) keywords = {} for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') key = str(tokens[0]).strip() value = str(tokens[1]).strip() keywords[key] = value return keywords def accept(self): """Automatic slot executed when the ok button is pressed. It will write out the keywords for the layer that is active. """ self.apply_changes() keywords = self.get_keywords() # If it's postprocessing layer, we need to check if age ratio is valid if self.radPostprocessing.isChecked(): valid_age_ratio, sum_age_ratios = self.age_ratios_are_valid( keywords) if not valid_age_ratio: message = self.tr( 'The sum of age ratios is %s which exceeds 1. Please ' 'adjust the age ration defaults so that their cumulative ' 'value is not greater than 1.' % sum_age_ratios) if not self.test: # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self, self.tr('InaSAFE'), message) return try: self.keyword_io.write_keywords(layer=self.layer, keywords=keywords) except InaSAFEError, e: error_message = get_error_message(e) message = self.tr( 'An error was encountered when saving the keywords:\n' '%s' % error_message.to_html()) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning(self, self.tr('InaSAFE'), message) if self.dock is not None: self.dock.get_layers() self.done(QtGui.QDialog.Accepted)
def buffer_points(point_layer, radii, hazard_zone_attribute, output_crs): """Buffer points for each point with defined radii. This function is used for making buffer of volcano point hazard. :param point_layer: A point layer to buffer. :type point_layer: QgsVectorLayer :param radii: Desired approximate radii in kilometers (must be monotonically ascending). Can be either one number or list of numbers :type radii: int, list :param hazard_zone_attribute: The name of the attributes representing hazard zone. :type hazard_zone_attribute: str :param output_crs: The output CRS. :type output_crs: QgsCoordinateReferenceSystem :return: Vector polygon layer representing circle in point layer CRS. :rtype: QgsVectorLayer """ if not isinstance(radii, list): radii = [radii] if not is_point_layer(point_layer): message = ( 'Input hazard must be a vector point layer. I got %s ' 'with layer type %s' % (point_layer.name(), point_layer.type())) raise Exception(message) # Check that radii are monotonically increasing monotonically_increasing_flag = all( x < y for x, y in zip(radii, radii[1:])) if not monotonically_increasing_flag: raise RadiiException(RadiiException.suggestion) hazard_file_path = unique_filename(suffix='-polygon-volcano.shp') fields = point_layer.pendingFields() fields.append(QgsField(hazard_zone_attribute, QVariant.Double)) writer = QgsVectorFileWriter( hazard_file_path, 'utf-8', fields, QGis.WKBPolygon, output_crs, 'ESRI Shapefile') input_crs = point_layer.crs() center = point_layer.extent().center() utm = None if output_crs.authid() == 'EPSG:4326': utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(point_layer.crs(), utm) else: transform = QgsCoordinateTransform(point_layer.crs(), output_crs) for point in point_layer.getFeatures(): geom = point.geometry() geom.transform(transform) inner_rings = None for radius in radii: attributes = point.attributes() # Generate circle polygon circle = geom.buffer(radius * 1000.0, 30) if inner_rings: circle.addRing(inner_rings) inner_rings = circle.asPolygon()[0] new_buffer = QgsFeature() if output_crs.authid() == 'EPSG:4326': circle.transform(QgsCoordinateTransform(utm, output_crs)) new_buffer.setGeometry(circle) attributes.append(radius) new_buffer.setAttributes(attributes) writer.addFeature(new_buffer) del writer vector_layer = QgsVectorLayer(hazard_file_path, 'Polygons', 'ogr') keyword_io = KeywordIO() try: keywords = keyword_io.read_keywords(point_layer) keyword_io.write_keywords(vector_layer, keywords) except NoKeywordsFoundError: pass return vector_layer
class KeywordsDialog(QtGui.QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE keywords editor.""" def __init__(self, parent, iface, dock=None, layer=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: Quantum GIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle( self.tr('InaSAFE %s Keywords Editor' % get_version())) # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.defaults = None # string constants self.global_default_string = definitions.global_default_attribute[ 'name'] self.do_not_use_string = definitions.do_not_use_attribute['name'] self.global_default_data = definitions.global_default_attribute['id'] self.do_not_use_data = definitions.do_not_use_attribute['id'] if layer is None: self.layer = self.iface.activeLayer() else: self.layer = layer self.keyword_io = KeywordIO() # note the keys should remain untranslated as we need to write # english to the keywords file. The keys will be written as user data # in the combo entries. # .. seealso:: http://www.voidspace.org.uk/python/odict.html self.standard_exposure_list = OrderedDict([ ('population', self.tr('population')), ('structure', self.tr('structure')), ('road', self.tr('road')), ('Not Set', self.tr('Not Set')) ]) self.standard_hazard_list = OrderedDict([ ('earthquake [MMI]', self.tr('earthquake [MMI]')), ('tsunami [m]', self.tr('tsunami [m]')), ('tsunami [wet/dry]', self.tr('tsunami [wet/dry]')), ('tsunami [feet]', self.tr('tsunami [feet]')), ('flood [m]', self.tr('flood [m]')), ('flood [wet/dry]', self.tr('flood [wet/dry]')), ('flood [feet]', self.tr('flood [feet]')), ('tephra [kg2/m2]', self.tr('tephra [kg2/m2]')), ('volcano', self.tr('volcano')), ('generic [categorised]', self.tr('generic [categorised]')), ('Not Set', self.tr('Not Set')) ]) # noinspection PyUnresolvedReferences self.lstKeywords.itemClicked.connect(self.edit_key_value_pair) # Set up help dialog showing logic. help_button = self.buttonBox.button(QtGui.QDialogButtonBox.Help) help_button.clicked.connect(self.show_help) if self.layer is not None and is_polygon_layer(self.layer): # set some initial ui state: self.defaults = get_defaults() self.radPredefined.setChecked(True) self.dsbFemaleRatioDefault.blockSignals(True) self.dsbFemaleRatioDefault.setValue(self.defaults['FEMALE_RATIO']) self.dsbFemaleRatioDefault.blockSignals(False) self.dsbYouthRatioDefault.blockSignals(True) self.dsbYouthRatioDefault.setValue(self.defaults['YOUTH_RATIO']) self.dsbYouthRatioDefault.blockSignals(False) self.dsbAdultRatioDefault.blockSignals(True) self.dsbAdultRatioDefault.setValue(self.defaults['ADULT_RATIO']) self.dsbAdultRatioDefault.blockSignals(False) self.dsbElderlyRatioDefault.blockSignals(True) self.dsbElderlyRatioDefault.setValue( self.defaults['ELDERLY_RATIO']) self.dsbElderlyRatioDefault.blockSignals(False) else: self.radPostprocessing.hide() self.tab_widget.removeTab(1) if self.layer: self.load_state_from_keywords() # add a reload from keywords button reload_button = self.buttonBox.addButton( self.tr('Reload'), QtGui.QDialogButtonBox.ActionRole) reload_button.clicked.connect(self.load_state_from_keywords) self.resize_dialog() self.tab_widget.setCurrentIndex(0) # TODO No we should not have test related stuff in prod code. TS self.test = False def set_layer(self, layer): """Set the layer associated with the keyword editor. :param layer: Layer whose keywords should be edited. :type layer: QgsMapLayer """ self.layer = layer self.load_state_from_keywords() # noinspection PyMethodMayBeStatic def show_help(self): """Load the help text for the keywords dialog.""" show_context_help(context='keywords_editor') def toggle_postprocessing_widgets(self): """Hide or show the post processing widgets depending on context.""" LOGGER.debug('togglePostprocessingWidgets') # TODO Too much baggage here. Can't we just disable/enable the tab? TS postprocessing_flag = self.radPostprocessing.isChecked() self.cboSubcategory.setVisible(not postprocessing_flag) self.lblSubcategory.setVisible(not postprocessing_flag) self.show_aggregation_attribute() self.show_female_ratio_attribute() self.show_female_ratio_default() self.show_youth_ratio_attribute() self.show_youth_ratio_default() self.show_adult_ratio_attribute() self.show_adult_ratio_default() self.show_elderly_ratio_attribute() self.show_elderly_ratio_default() # Also enable/disable the aggregation tab self.aggregation_tab.setEnabled(postprocessing_flag) def show_aggregation_attribute(self): """Hide or show the aggregation attribute in the keyword editor dialog. """ box = self.cboAggregationAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['AGGR_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Int, QtCore.QVariant.String], current_keyword) box.addItems(fields) if attribute_position is None: box.setCurrentIndex(0) else: box.setCurrentIndex(attribute_position) def show_female_ratio_attribute(self): """Hide or show the female ratio attribute in the dialog. """ box = self.cboFemaleRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['FEMALE_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_female_ratio_default(self): """Hide or show the female ratio default attribute in the dialog. """ box = self.dsbFemaleRatioDefault current_value = self.get_value_for_key( self.defaults['FEMALE_RATIO_KEY']) if current_value is None: val = self.defaults['FEMALE_RATIO'] else: val = float(current_value) box.setValue(val) def show_youth_ratio_attribute(self): """Hide or show the youth ratio attribute in the dialog. """ box = self.cboYouthRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['YOUTH_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_youth_ratio_default(self): """Hide or show the youth ratio default attribute in the dialog. """ box = self.dsbYouthRatioDefault current_value = self.get_value_for_key( self.defaults['YOUTH_RATIO_KEY']) if current_value is None: val = self.defaults['YOUTH_RATIO'] else: val = float(current_value) box.setValue(val) def show_adult_ratio_attribute(self): """Hide or show the adult ratio attribute in the dialog. """ box = self.cboAdultRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['ADULT_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_adult_ratio_default(self): """Hide or show the adult ratio default attribute in the dialog. """ box = self.dsbAdultRatioDefault current_value = self.get_value_for_key( self.defaults['ADULT_RATIO_KEY']) if current_value is None: val = self.defaults['ADULT_RATIO'] else: val = float(current_value) box.setValue(val) def show_elderly_ratio_attribute(self): """Show the elderly ratio attribute in the dialog. """ box = self.cboElderlyRatioAttribute box.blockSignals(True) box.clear() box.blockSignals(False) current_keyword = self.get_value_for_key( self.defaults['ELDERLY_RATIO_ATTR_KEY']) fields, attribute_position = layer_attribute_names( self.layer, [QtCore.QVariant.Double], current_keyword) box.addItem(self.global_default_string, self.global_default_data) box.addItem(self.do_not_use_string, self.do_not_use_data) for field in fields: box.addItem(field, field) if current_keyword == self.global_default_data: box.setCurrentIndex(0) elif current_keyword == self.do_not_use_data: box.setCurrentIndex(1) elif attribute_position is None: # current_keyword was not found in the attribute table. # Use default box.setCurrentIndex(0) else: # + 2 is because we add use defaults and don't use box.setCurrentIndex(attribute_position + 2) def show_elderly_ratio_default(self): """Show the elderly ratio default attribute in the dialog. """ box = self.dsbElderlyRatioDefault current_value = self.get_value_for_key( self.defaults['ELDERLY_RATIO_KEY']) if current_value is None: val = self.defaults['ELDERLY_RATIO'] else: val = float(current_value) box.setValue(val) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('int') def on_cboAggregationAttribute_currentIndexChanged(self, index=None): """Handler for aggregation attribute combo change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return self.add_list_entry(self.defaults['AGGR_ATTR_KEY'], self.cboAggregationAttribute.currentText()) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('int') def on_cboFemaleRatioAttribute_currentIndexChanged(self, index=None): """Handler for female ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboFemaleRatioAttribute.currentIndex() data = self.cboFemaleRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbFemaleRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['FEMALE_RATIO_KEY']) if current_default is None: self.add_list_entry(self.defaults['FEMALE_RATIO_KEY'], self.dsbFemaleRatioDefault.value()) else: self.dsbFemaleRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY']) self.add_list_entry(self.defaults['FEMALE_RATIO_ATTR_KEY'], data) # noinspection PyPep8Naming def on_cboYouthRatioAttribute_currentIndexChanged(self, index=None): """Handler for youth ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboYouthRatioAttribute.currentIndex() data = self.cboYouthRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbYouthRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['YOUTH_RATIO_KEY']) if current_default is None: self.add_list_entry(self.defaults['YOUTH_RATIO_KEY'], self.dsbYouthRatioDefault.value()) else: self.dsbYouthRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY']) self.add_list_entry(self.defaults['YOUTH_RATIO_ATTR_KEY'], data) # noinspection PyPep8Naming def on_cboAdultRatioAttribute_currentIndexChanged(self, index=None): """Handler for adult ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboAdultRatioAttribute.currentIndex() data = self.cboAdultRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbAdultRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['ADULT_RATIO_KEY']) if current_default is None: self.add_list_entry(self.defaults['ADULT_RATIO_KEY'], self.dsbAdultRatioDefault.value()) else: self.dsbAdultRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY']) self.add_list_entry(self.defaults['ADULT_RATIO_ATTR_KEY'], data) # noinspection PyPep8Naming def on_cboElderlyRatioAttribute_currentIndexChanged(self, index=None): """Handler for elderly ratio attribute change. :param index: Not used but required for slot. """ del index if not self.radPostprocessing.isChecked(): return current_index = self.cboElderlyRatioAttribute.currentIndex() data = self.cboElderlyRatioAttribute.itemData(current_index) if data == self.global_default_data: self.dsbElderlyRatioDefault.setEnabled(True) current_default = self.get_value_for_key( self.defaults['ELDERLY_RATIO_KEY']) if current_default is None: self.add_list_entry(self.defaults['ELDERLY_RATIO_KEY'], self.dsbElderlyRatioDefault.value()) else: self.dsbElderlyRatioDefault.setEnabled(False) self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY']) self.add_list_entry(self.defaults['ELDERLY_RATIO_ATTR_KEY'], data) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('double') def on_dsbFemaleRatioDefault_valueChanged(self, value): """Handler for female ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbFemaleRatioDefault if box.isEnabled(): self.add_list_entry(self.defaults['FEMALE_RATIO_KEY'], box.value()) # noinspection PyPep8Naming def on_dsbYouthRatioDefault_valueChanged(self, value): """Handler for youth ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbYouthRatioDefault if box.isEnabled(): self.add_list_entry(self.defaults['YOUTH_RATIO_KEY'], box.value()) # noinspection PyPep8Naming def on_dsbAdultRatioDefault_valueChanged(self, value): """Handler for adult ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbAdultRatioDefault if box.isEnabled(): self.add_list_entry(self.defaults['ADULT_RATIO_KEY'], box.value()) # noinspection PyPep8Naming def on_dsbElderlyRatioDefault_valueChanged(self, value): """Handler for elderly ration default value changing. :param value: Not used but required for slot. """ del value if not self.radPostprocessing.isChecked(): return box = self.dsbElderlyRatioDefault if box.isEnabled(): self.add_list_entry(self.defaults['ELDERLY_RATIO_KEY'], box.value()) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('bool') def on_radHazard_toggled(self, flag): """Automatic slot executed when the hazard radio is toggled. :param flag: Flag indicating the new checked state of the button. :type flag: bool """ if not flag: return self.set_category('hazard') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('bool') def on_radExposure_toggled(self, theFlag): """Automatic slot executed when the hazard radio is toggled on. :param theFlag: Flag indicating the new checked state of the button. :type theFlag: bool """ if not theFlag: return self.set_category('exposure') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('bool') def on_radPostprocessing_toggled(self, flag): """Automatic slot executed when the hazard radio is toggled on. :param flag: Flag indicating the new checked state of the button. :type flag: bool """ if flag: self.set_category('postprocessing') self.update_controls_from_list() return if self.defaults is not None: self.remove_item_by_key(self.defaults['AGGR_ATTR_KEY']) self.remove_item_by_key(self.defaults['FEMALE_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY']) self.remove_item_by_key(self.defaults['YOUTH_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY']) self.remove_item_by_key(self.defaults['ADULT_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY']) self.remove_item_by_key(self.defaults['ELDERLY_RATIO_ATTR_KEY']) self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY']) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('int') def on_cboSubcategory_currentIndexChanged(self, index=None): """Automatic slot executed when the subcategory is changed. When the user changes the subcategory, we will extract the subcategory and dataype or unit (depending on if it is a hazard or exposure subcategory) from the [] after the name. :param index: Not used but required for Qt slot. """ if index == -1: self.remove_item_by_key('subcategory') return text = self.cboSubcategory.itemData(self.cboSubcategory.currentIndex()) # I found that myText is 'Not Set' for every language if text == self.tr('Not Set') or text == 'Not Set': self.remove_item_by_key('subcategory') return tokens = text.split(' ') if len(tokens) < 1: self.remove_item_by_key('subcategory') return subcategory = tokens[0] self.add_list_entry('subcategory', subcategory) # Some subcategories e.g. roads have no units or datatype if len(tokens) == 1: return if tokens[1].find('[') < 0: return category = self.get_value_for_key('category') if 'hazard' == category: units = tokens[1].replace('[', '').replace(']', '') self.add_list_entry('unit', units) if 'exposure' == category: data_type = tokens[1].replace('[', '').replace(']', '') self.add_list_entry('datatype', data_type) # prevents actions being handled twice def set_subcategory_list(self, entries, selected_item=None): """Helper to populate the subcategory list based on category context. :param entries: An OrderedDict of subcategories. The dict entries should be in the form ('earthquake', self.tr('earthquake')). See http://www.voidspace.org.uk/python/odict.html for info on OrderedDict. :type entries: OrderedDict :param selected_item: Which item should be selected in the combo. If the selected item is not in entries, it will be appended to it. This is optional. :type selected_item: str """ # To avoid triggering on_cboSubcategory_currentIndexChanged # we block signals from the combo while updating it self.cboSubcategory.blockSignals(True) self.cboSubcategory.clear() item_selected_flag = selected_item is not None selected_item_values = selected_item not in entries.values() selected_item_keys = selected_item not in entries.keys() if (item_selected_flag and selected_item_values and selected_item_keys): # Add it to the OrderedList entries[selected_item] = selected_item index = 0 selected_index = 0 for key, value in entries.iteritems(): if value == selected_item or key == selected_item: selected_index = index index += 1 self.cboSubcategory.addItem(value, key) self.cboSubcategory.setCurrentIndex(selected_index) self.cboSubcategory.blockSignals(False) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnAddToList1_clicked(self): """Automatic slot executed when the pbnAddToList1 button is pressed. """ if (self.lePredefinedValue.text() != "" and self.cboKeyword.currentText() != ""): current_key = self.tr(self.cboKeyword.currentText()) current_value = self.lePredefinedValue.text() self.add_list_entry(current_key, current_value) self.lePredefinedValue.setText('') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnAddToList2_clicked(self): """Automatic slot executed when the pbnAddToList2 button is pressed. """ current_key = self.leKey.text() current_value = self.leValue.text() if current_key == 'category' and current_value == 'hazard': self.radHazard.blockSignals(True) self.radHazard.setChecked(True) self.set_subcategory_list(self.standard_hazard_list) self.radHazard.blockSignals(False) elif current_key == 'category' and current_value == 'exposure': self.radExposure.blockSignals(True) self.radExposure.setChecked(True) self.set_subcategory_list(self.standard_exposure_list) self.radExposure.blockSignals(False) elif current_key == 'category': # .. todo:: notify the user their category is invalid pass self.add_list_entry(current_key, current_value) self.leKey.setText('') self.leValue.setText('') self.update_controls_from_list() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnRemove_clicked(self): """Automatic slot executed when the pbnRemove button is pressed. Any selected items in the keywords list will be removed. """ for item in self.lstKeywords.selectedItems(): self.lstKeywords.takeItem(self.lstKeywords.row(item)) self.leKey.setText('') self.leValue.setText('') self.update_controls_from_list() def add_list_entry(self, key, value): """Add an item to the keywords list given its key/value. The key and value must both be valid, non empty strings or an InvalidKVPError will be raised. If an entry with the same key exists, it's value will be replaced with value. It will add the current key/value pair to the list if it is not already present. The kvp will also be stored in the data of the listwidgetitem as a simple string delimited with a bar ('|'). :param key: The key part of the key value pair (kvp). :type key: str, unicode :param value: Value part of the key value pair (kvp). :type value: str, unicode """ if key is None or key == '': return # make sure that both key and value is unicode key = get_unicode(key) value = get_unicode(value) message = '' if ':' in key: key = key.replace(':', '.') message = self.tr('Colons are not allowed, replaced with "."') if ':' in value: value = value.replace(':', '.') message = self.tr('Colons are not allowed, replaced with "."') if message == '': self.lblMessage.setText('') self.lblMessage.hide() else: self.lblMessage.setText(message) self.lblMessage.show() item = QtGui.QListWidgetItem(key + ':' + value) # We are going to replace, so remove it if it exists already self.remove_item_by_key(key) data = key + '|' + value item.setData(QtCore.Qt.UserRole, data) self.lstKeywords.insertItem(0, item) def set_category(self, category): """Set the category radio button based on category. :param category: Either 'hazard', 'exposure' or 'postprocessing'. :type category: str :returns: False if radio button could not be updated, otherwise True. :rtype: bool """ if self.get_value_for_key('category') == category: # nothing to do, go home return True if category not in ['hazard', 'exposure', 'postprocessing']: # .. todo:: report an error to the user return False # Special case when category changes, we start on a new slate! if category == 'hazard': # only cause a toggle if we actually changed the category # This will only really be apparent if user manually enters # category as a keyword self.reset() self.radHazard.blockSignals(True) self.radHazard.setChecked(True) self.radHazard.blockSignals(False) self.remove_item_by_key('subcategory') self.remove_item_by_key('datatype') self.add_list_entry('category', 'hazard') hazard_list = self.standard_hazard_list self.set_subcategory_list(hazard_list) elif category == 'exposure': self.reset() self.radExposure.blockSignals(True) self.radExposure.setChecked(True) self.radExposure.blockSignals(False) self.remove_item_by_key('subcategory') self.remove_item_by_key('unit') self.add_list_entry('category', 'exposure') exposure_list = self.standard_exposure_list self.set_subcategory_list(exposure_list) else: self.reset() self.radPostprocessing.blockSignals(True) self.radPostprocessing.setChecked(True) self.radPostprocessing.blockSignals(False) self.remove_item_by_key('subcategory') self.add_list_entry('category', 'postprocessing') return True def reset(self, primary_keywords_only=True): """Reset all controls to a blank state. :param primary_keywords_only: If True (the default), only reset Subcategory, datatype and units. :type primary_keywords_only: bool """ self.cboSubcategory.clear() self.remove_item_by_key('subcategory') self.remove_item_by_key('datatype') self.remove_item_by_key('unit') self.remove_item_by_key('source') if not primary_keywords_only: # Clear everything else too self.lstKeywords.clear() self.leKey.clear() self.leValue.clear() self.lePredefinedValue.clear() self.leTitle.clear() self.leSource.clear() def remove_item_by_key(self, removal_key): """Remove an item from the kvp list given its key. :param removal_key: Key of item to be removed. :type removal_key: str """ for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') if len(tokens) < 2: break key = tokens[0] if removal_key == key: # remove it since the removal_key is already present self.blockSignals(True) self.lstKeywords.takeItem(counter) self.blockSignals(False) break def remove_item_by_value(self, removal_value): """Remove an item from the kvp list given its key. :param removal_value: Value of item to be removed. :type removal_value: str """ for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') value = tokens[1] if removal_value == value: # remove it since the key is already present self.blockSignals(True) self.lstKeywords.takeItem(counter) self.blockSignals(False) break def get_value_for_key(self, lookup_key): """If key list contains a specific key, return its value. :param lookup_key: The key to search for :type lookup_key: str :returns: Value of key if matched otherwise none. :rtype: str """ for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') key = tokens[0].strip() value = tokens[1].strip() if lookup_key == key: return value return None def load_state_from_keywords(self): """Set the ui state to match the keywords of the active layer. In case the layer has no keywords or any problem occurs reading them, start with a blank state so that subcategory gets populated nicely & we will assume exposure to start with. Also if only title is set we use similar logic (title is added by default in dock and other defaults need to be explicitly added when opening this dialog). See #751 """ keywords = {'category': 'exposure'} try: # Now read the layer with sub layer if needed keywords = self.keyword_io.read_keywords(self.layer) except (InvalidParameterError, HashNotFoundError, NoKeywordsFoundError): pass layer_name = self.layer.name() if 'title' not in keywords: self.leTitle.setText(layer_name) self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name)) if 'source' in keywords: self.leSource.setText(keywords['source']) else: self.leSource.setText('') # if we have a category key, unpack it first # so radio button etc get set if 'category' in keywords: self.set_category(keywords['category']) keywords.pop('category') else: # assume exposure to match ui. See issue #751 self.add_list_entry('category', 'exposure') aggregation_attributes = [ DEFAULTS['FEMALE_RATIO_ATTR_KEY'], DEFAULTS['YOUTH_RATIO_ATTR_KEY'], DEFAULTS['ADULT_RATIO_ATTR_KEY'], DEFAULTS['ELDERLY_RATIO_ATTR_KEY'], ] for key in keywords.iterkeys(): if key in aggregation_attributes: if keywords[key] == 'Use default': self.add_list_entry(key, self.global_default_data) continue self.add_list_entry(key, keywords[key]) # now make the rest of the safe_qgis reflect the list entries self.update_controls_from_list() def update_controls_from_list(self): """Set the ui state to match the keywords of the active layer.""" subcategory = self.get_value_for_key('subcategory') units = self.get_value_for_key('unit') data_type = self.get_value_for_key('datatype') title = self.get_value_for_key('title') if title is not None: self.leTitle.setText(title) elif self.layer is not None: layer_name = self.layer.name() self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name)) else: self.lblLayerName.setText('') if not is_polygon_layer(self.layer): self.radPostprocessing.setEnabled(False) else: # adapt gui if we are in postprocessing category self.toggle_postprocessing_widgets() if self.radExposure.isChecked(): if subcategory is not None and data_type is not None: self.set_subcategory_list(self.standard_exposure_list, subcategory + ' [' + data_type + ']') elif subcategory is not None: self.set_subcategory_list(self.standard_exposure_list, subcategory) else: self.set_subcategory_list(self.standard_exposure_list, self.tr('Not Set')) elif self.radHazard.isChecked(): if subcategory is not None and units is not None: self.set_subcategory_list(self.standard_hazard_list, subcategory + ' [' + units + ']') elif subcategory is not None: self.set_subcategory_list(self.standard_hazard_list, subcategory) else: self.set_subcategory_list(self.standard_hazard_list, self.tr('Not Set')) self.resize_dialog() def resize_dialog(self): """Resize the dialog to fit its contents.""" # noinspection PyArgumentList QtCore.QCoreApplication.processEvents() LOGGER.debug('adjust ing dialog size') self.adjustSize() # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('QString') def on_leTitle_textEdited(self, title): """Update the keywords list whenever the user changes the title. This slot is not called if the title is changed programmatically. :param title: New title keyword for the layer. :type title: str """ self.add_list_entry('title', title) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('QString') def on_leSource_textEdited(self, source): """Update the keywords list whenever the user changes the source. This slot is not called if the source is changed programmatically. :param source: New source keyword for the layer. :type source: str """ if source is None or source == '': self.remove_item_by_key('source') else: self.add_list_entry('source', source) def get_keywords(self): """Obtain the state of the dialog as a keywords dict. :returns: Keywords reflecting the state of the dialog. :rtype: dict """ # make sure title is listed if self.leTitle.text() != '': self.add_list_entry('title', self.leTitle.text()) # make sure the source is listed too if self.leSource.text() != '': self.add_list_entry('source', self.leSource.text()) keywords = {} for counter in range(self.lstKeywords.count()): existing_item = self.lstKeywords.item(counter) text = existing_item.text() tokens = text.split(':') key = tokens[0].strip() value = tokens[1].strip() keywords[key] = value return keywords def accept(self): """Automatic slot executed when the ok button is pressed. It will write out the keywords for the layer that is active. """ self.apply_changes() keywords = self.get_keywords() # If it's postprocessing layer, we need to check if age ratio is valid if self.radPostprocessing.isChecked(): valid_age_ratio, sum_age_ratios = self.age_ratios_are_valid( keywords) if not valid_age_ratio: message = self.tr( 'The sum of age ratios is %s which exceeds 1. Please ' 'adjust the age ration defaults so that their cumulative ' 'value is not greater than 1.' % sum_age_ratios) if not self.test: # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning(self, self.tr('InaSAFE'), message) return try: self.keyword_io.write_keywords(layer=self.layer, keywords=keywords) except InaSAFEError, e: error_message = get_error_message(e) message = self.tr( 'An error was encountered when saving the keywords:\n' '%s' % error_message.to_html()) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning(self, self.tr('InaSAFE'), message) if self.dock is not None: self.dock.get_layers() self.done(QtGui.QDialog.Accepted)
class FieldMappingDialog(QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE field mapping tool.""" def __init__(self, parent=None, iface=None, setting=None): """Constructor.""" QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Field Mapping Tool')) icon = resources_path('img', 'icons', 'show-mapping-tool.svg') self.setWindowIcon(QIcon(icon)) self.parent = parent self.iface = iface if setting is None: setting = QSettings() self.setting = setting self.keyword_io = KeywordIO() self.layer = None self.metadata = {} self.layer_input_layout = QHBoxLayout() self.layer_label = QLabel(tr('Layer')) self.layer_combo_box = QgsMapLayerComboBox() # Filter only for Polygon and Point self.layer_combo_box.setFilters( QgsMapLayerProxyModel.PolygonLayer | QgsMapLayerProxyModel.PointLayer) # Filter out a layer that don't have layer groups excepted_layers = [] for i in range(self.layer_combo_box.count()): layer = self.layer_combo_box.layer(i) try: keywords = self.keyword_io.read_keywords(layer) except (KeywordNotFoundError, NoKeywordsFoundError): excepted_layers.append(layer) continue layer_purpose = keywords.get('layer_purpose') if not layer_purpose: excepted_layers.append(layer) continue if layer_purpose == layer_purpose_exposure['key']: layer_subcategory = keywords.get('exposure') elif layer_purpose == layer_purpose_hazard['key']: layer_subcategory = keywords.get('hazard') else: layer_subcategory = None field_groups = get_field_groups(layer_purpose, layer_subcategory) if len(field_groups) == 0: excepted_layers.append(layer) continue self.layer_combo_box.setExceptedLayerList(excepted_layers) # Select the active layer. if self.iface.activeLayer(): found = self.layer_combo_box.findText( self.iface.activeLayer().name()) if found > -1: self.layer_combo_box.setLayer(self.iface.activeLayer()) self.field_mapping_widget = None self.main_stacked_widget.setCurrentIndex(1) # Input self.layer_input_layout.addWidget(self.layer_label) self.layer_input_layout.addWidget(self.layer_combo_box) self.header_label = QLabel() self.header_label.setWordWrap(True) self.main_layout.addWidget(self.header_label) self.main_layout.addLayout(self.layer_input_layout) # Signal self.layer_combo_box.layerChanged.connect(self.set_layer) if self.layer_combo_box.currentLayer(): self.set_layer(self.layer_combo_box.currentLayer()) # Set up things for context help self.help_button = self.button_box.button(QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) # Set up things for ok button self.ok_button = self.button_box.button(QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up things for cancel button self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) self.cancel_button.clicked.connect(self.reject) def set_layer(self, layer=None, keywords=None): """Set layer and update UI accordingly. :param layer: A QgsVectorLayer. :type layer: QgsVectorLayer :param keywords: Keywords for the layer. :type keywords: dict, None """ if self.field_mapping_widget is not None: self.field_mapping_widget.setParent(None) self.field_mapping_widget.close() self.field_mapping_widget.deleteLater() self.main_layout.removeWidget(self.field_mapping_widget) self.field_mapping_widget = None if layer: self.layer = layer else: self.layer = self.layer_combo_box.currentLayer() if not self.layer: return if keywords is not None: self.metadata = keywords else: # Always read from metadata file. try: self.metadata = self.keyword_io.read_keywords(self.layer) except ( NoKeywordsFoundError, KeywordNotFoundError, MetadataReadError) as e: raise e if 'inasafe_default_values' not in self.metadata: self.metadata['inasafe_default_values'] = {} if 'inasafe_fields' not in self.metadata: self.metadata['inasafe_fields'] = {} self.field_mapping_widget = FieldMappingWidget( parent=self, iface=self.iface) self.field_mapping_widget.set_layer(self.layer, self.metadata) self.field_mapping_widget.show() self.main_layout.addWidget(self.field_mapping_widget) # Set header label group_names = [ self.field_mapping_widget.tabText(i) for i in range( self.field_mapping_widget.count())] if len(group_names) == 0: header_text = tr( 'There is no field group for this layer. Please select ' 'another layer.') self.header_label.setText(header_text) return elif len(group_names) == 1: pretty_group_name = group_names[0] elif len(group_names) == 2: pretty_group_name = group_names[0] + tr(' and ') + group_names[1] else: pretty_group_name = ', '.join(group_names[:-1]) pretty_group_name += tr(', and {0}').format(group_names[-1]) header_text = tr( 'Please fill the information for every tab to determine the ' 'attribute for {0} group.').format(pretty_group_name) self.header_label.setText(header_text) @pyqtSlot(bool) # prevents actions being handled twice def help_toggled(self, flag): """Show or hide the help tab in the stacked widget. .. versionadded: 3.2.1 :param flag: Flag indicating whether help should be shown or hidden. :type flag: bool """ if flag: self.help_button.setText(self.tr('Hide Help')) self.show_help() else: self.help_button.setText(self.tr('Show Help')) self.hide_help() def hide_help(self): """Hide the usage info from the user. .. versionadded: 3.2.1 """ self.main_stacked_widget.setCurrentIndex(1) def show_help(self): """Show usage info to the user.""" # Read the header and footer html snippets self.main_stacked_widget.setCurrentIndex(0) header = html_header() footer = html_footer() string = header message = field_mapping_help() string += message.to_html() string += footer self.help_web_view.setHtml(string) def save_metadata(self): """Save metadata based on the field mapping state.""" metadata = self.field_mapping_widget.get_field_mapping() for key, value in list(metadata['fields'].items()): # Delete the key if it's set to None if key in self.metadata['inasafe_default_values']: self.metadata['inasafe_default_values'].pop(key) if value is None or value == []: if key in self.metadata['inasafe_fields']: self.metadata['inasafe_fields'].pop(key) else: self.metadata['inasafe_fields'][key] = value for key, value in list(metadata['values'].items()): # Delete the key if it's set to None if key in self.metadata['inasafe_fields']: self.metadata['inasafe_fields'].pop(key) if value is None: if key in self.metadata['inasafe_default_values']: self.metadata['inasafe_default_values'].pop(key) else: self.metadata['inasafe_default_values'][key] = value # Save metadata try: self.keyword_io.write_keywords( layer=self.layer, keywords=self.metadata) except InaSAFEError as e: error_message = get_error_message(e) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QMessageBox.warning( self, self.tr('InaSAFE'), ((self.tr( 'An error was encountered when saving the following ' 'keywords:\n %s') % error_message.to_html()))) # Update setting fir recent value if self.metadata.get('inasafe_default_values'): for key, value in \ list(self.metadata['inasafe_default_values'].items()): set_inasafe_default_value_qsetting( self.setting, key, RECENT, value) def accept(self): """Method invoked when OK button is clicked.""" try: self.save_metadata() except InvalidValidationException as e: display_warning_message_box( self, tr('Invalid Field Mapping'), str(e)) return super(FieldMappingDialog, self).accept()
class WizardDialog(QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE wizard.""" def __init__(self, parent=None, iface=None, dock=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('InaSAFE') # Constants self.keyword_creation_wizard_name = tr( 'InaSAFE Keywords Creation Wizard') self.ifcw_name = tr('InaSAFE Impact Function Centric Wizard') # Note the keys should remain untranslated as we need to write # english to the keywords file. # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.suppress_warning_dialog = False self.lblStep.clear() # Set icons self.lblMainIcon.setPixmap( QPixmap(resources_path('img', 'icons', 'icon-white.svg'))) self.keyword_io = KeywordIO() self.is_selected_layer_keywordless = False self.parent_step = None self.pbnBack.setEnabled(False) self.pbnNext.setEnabled(False) self.pbnCancel.released.connect(self.reject) # Initialize attributes self.existing_keywords = None self.layer = None self.hazard_layer = None self.exposure_layer = None self.aggregation_layer = None self.step_kw_purpose = StepKwPurpose(self) self.step_kw_subcategory = StepKwSubcategory(self) self.step_kw_hazard_category = StepKwHazardCategory(self) self.step_kw_band_selector = StepKwBandSelector(self) self.step_kw_layermode = StepKwLayerMode(self) self.step_kw_unit = StepKwUnit(self) self.step_kw_classification = StepKwClassification(self) self.step_kw_field = StepKwField(self) self.step_kw_multi_classifications = StepKwMultiClassifications(self) self.step_kw_classify = StepKwClassify(self) self.step_kw_threshold = StepKwThreshold(self) self.step_kw_fields_mapping = StepKwFieldsMapping(self) self.step_kw_inasafe_fields = StepKwInaSAFEFields(self) self.step_kw_default_inasafe_fields = StepKwDefaultInaSAFEFields(self) self.step_kw_inasafe_raster_default_values = \ StepKwInaSAFERasterDefaultValues(self) self.step_kw_source = StepKwSource(self) self.step_kw_title = StepKwTitle(self) self.step_kw_summary = StepKwSummary(self) self.step_fc_functions1 = StepFcFunctions1(self) self.step_fc_functions2 = StepFcFunctions2(self) self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self) self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self) self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self) self.step_fc_explayer_origin = StepFcExpLayerOrigin(self) self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self) self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self) self.step_fc_disjoint_layers = StepFcDisjointLayers(self) self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self) self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self) self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self) self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self) self.step_fc_extent = StepFcExtent(self) self.step_fc_extent_disjoint = StepFcExtentDisjoint(self) self.step_fc_summary = StepFcSummary(self) self.step_fc_analysis = StepFcAnalysis(self) self.wizard_help = WizardHelp(self) self.stackedWidget.addWidget(self.step_kw_purpose) self.stackedWidget.addWidget(self.step_kw_subcategory) self.stackedWidget.addWidget(self.step_kw_hazard_category) self.stackedWidget.addWidget(self.step_kw_band_selector) self.stackedWidget.addWidget(self.step_kw_layermode) self.stackedWidget.addWidget(self.step_kw_unit) self.stackedWidget.addWidget(self.step_kw_classification) self.stackedWidget.addWidget(self.step_kw_field) self.stackedWidget.addWidget(self.step_kw_multi_classifications) self.stackedWidget.addWidget(self.step_kw_classify) self.stackedWidget.addWidget(self.step_kw_threshold) self.stackedWidget.addWidget(self.step_kw_fields_mapping) self.stackedWidget.addWidget(self.step_kw_inasafe_fields) self.stackedWidget.addWidget(self.step_kw_default_inasafe_fields) self.stackedWidget.addWidget( self.step_kw_inasafe_raster_default_values) self.stackedWidget.addWidget(self.step_kw_source) self.stackedWidget.addWidget(self.step_kw_title) self.stackedWidget.addWidget(self.step_kw_summary) self.stackedWidget.addWidget(self.step_fc_functions1) self.stackedWidget.addWidget(self.step_fc_functions2) self.stackedWidget.addWidget(self.step_fc_hazlayer_origin) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser) self.stackedWidget.addWidget(self.step_fc_explayer_origin) self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_explayer_from_browser) self.stackedWidget.addWidget(self.step_fc_disjoint_layers) self.stackedWidget.addWidget(self.step_fc_agglayer_origin) self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser) self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint) self.stackedWidget.addWidget(self.step_fc_extent) self.stackedWidget.addWidget(self.step_fc_extent_disjoint) self.stackedWidget.addWidget(self.step_fc_summary) self.stackedWidget.addWidget(self.step_fc_analysis) self.stackedWidget.addWidget(self.wizard_help) # QSetting self.setting = QSettings() # Wizard Steps self.impact_function_steps = [] self.keyword_steps = [] self.on_help = False def set_mode_label_to_keywords_creation(self): """Set the mode label to the Keywords Creation/Update mode.""" self.setWindowTitle(self.keyword_creation_wizard_name) if self.get_existing_keyword('layer_purpose'): mode_name = tr( 'Keywords update wizard for layer <b>{layer_name}</b>').format( layer_name=self.layer.name()) else: mode_name = tr( 'Keywords creation wizard for layer <b>%s</b>').format( layer_name=self.layer.name()) self.lblSubtitle.setText(mode_name) def set_mode_label_to_ifcw(self): """Set the mode label to the IFCW.""" self.setWindowTitle(self.ifcw_name) self.lblSubtitle.setText(tr( 'Use this wizard to run a guided impact assessment')) def set_keywords_creation_mode(self, layer=None, keywords=None): """Set the Wizard to the Keywords Creation mode. :param layer: Layer to set the keywords for :type layer: QgsMapLayer :param keywords: Keywords for the layer. :type keywords: dict, None """ self.layer = layer or self.iface.mapCanvas().currentLayer() if keywords is not None: self.existing_keywords = keywords else: # Always read from metadata file. try: self.existing_keywords = self.keyword_io.read_keywords( self.layer) except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError, MetadataReadError): self.existing_keywords = None self.set_mode_label_to_keywords_creation() step = self.step_kw_purpose step.set_widgets() self.go_to_step(step) def set_function_centric_mode(self): """Set the Wizard to the Function Centric mode.""" self.set_mode_label_to_ifcw() step = self.step_fc_functions1 step.set_widgets() self.go_to_step(step) def field_keyword_for_the_layer(self): """Return the proper keyword for field for the current layer. :returns: the field keyword :rtype: str """ layer_purpose_key = self.step_kw_purpose.selected_purpose()['key'] if layer_purpose_key == layer_purpose_aggregation['key']: return get_compulsory_fields(layer_purpose_key)['key'] elif layer_purpose_key in [ layer_purpose_exposure['key'], layer_purpose_hazard['key']]: layer_subcategory_key = \ self.step_kw_subcategory.selected_subcategory()['key'] return get_compulsory_fields( layer_purpose_key, layer_subcategory_key)['key'] else: raise InvalidParameterError def get_parent_mode_constraints(self): """Return the category and subcategory keys to be set in the subordinate mode. :returns: (the category definition, the hazard/exposure definition) :rtype: (dict, dict) """ h, e, _hc, _ec = self.selected_impact_function_constraints() if self.parent_step in [self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser]: category = layer_purpose_hazard subcategory = h elif self.parent_step in [self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser]: category = layer_purpose_exposure subcategory = e elif self.parent_step: category = layer_purpose_aggregation subcategory = None else: category = None subcategory = None return category, subcategory def selected_impact_function_constraints(self): """Obtain impact function constraints selected by user. :returns: Tuple of metadata of hazard, exposure, hazard layer geometry and exposure layer geometry :rtype: tuple """ hazard = self.step_fc_functions1.selected_value( layer_purpose_hazard['key']) exposure = self.step_fc_functions1.selected_value( layer_purpose_exposure['key']) hazard_geometry = self.step_fc_functions2.selected_value( layer_purpose_hazard['key']) exposure_geometry = self.step_fc_functions2.selected_value( layer_purpose_exposure['key']) return hazard, exposure, hazard_geometry, exposure_geometry def is_layer_compatible(self, layer, layer_purpose=None, keywords=None): """Validate if a given layer is compatible for selected IF as a given layer_purpose :param layer: The layer to be validated :type layer: QgsVectorLayer | QgsRasterLayer :param layer_purpose: The layer_purpose the layer is validated for :type layer_purpose: None, string :param keywords: The layer keywords :type keywords: None, dict :returns: True if layer is appropriate for the selected role :rtype: boolean """ # If not explicitly stated, find the desired purpose # from the parent step if not layer_purpose: layer_purpose = self.get_parent_mode_constraints()[0]['key'] # If not explicitly stated, read the layer's keywords if not keywords: try: keywords = self.keyword_io.read_keywords(layer) if ('layer_purpose' not in keywords and 'impact_summary' not in keywords): keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # Get allowed subcategory and layer_geometry from IF constraints h, e, hc, ec = self.selected_impact_function_constraints() if layer_purpose == 'hazard': subcategory = h['key'] layer_geometry = hc['key'] elif layer_purpose == 'exposure': subcategory = e['key'] layer_geometry = ec['key'] else: # For aggregation layers, use a simplified test and return if (keywords and 'layer_purpose' in keywords and keywords['layer_purpose'] == layer_purpose): return True if not keywords and is_polygon_layer(layer): return True return False # Compare layer properties with explicitly set constraints # Reject if layer geometry doesn't match if layer_geometry != self.get_layer_geometry_key(layer): return False # If no keywords, there's nothing more we can check. # The same if the keywords version doesn't match if not keywords or 'keyword_version' not in keywords: return True keyword_version = str(keywords['keyword_version']) if not is_keyword_version_supported(keyword_version): return True # Compare layer keywords with explicitly set constraints # Reject if layer purpose missing or doesn't match if ('layer_purpose' not in keywords or keywords['layer_purpose'] != layer_purpose): return False # Reject if layer subcategory doesn't match if (layer_purpose in keywords and keywords[layer_purpose] != subcategory): return False return True def get_compatible_canvas_layers(self, category): """Collect layers from map canvas, compatible for the given category and selected impact function .. note:: Returns layers with keywords and layermode matching the category and compatible with the selected impact function. Also returns layers without keywords with layermode compatible with the selected impact function. :param category: The category to filter for. :type category: string :returns: Metadata of found layers. :rtype: list of dicts """ # Collect compatible layers layers = [] for layer in self.iface.mapCanvas().layers(): try: keywords = self.keyword_io.read_keywords(layer) if ('layer_purpose' not in keywords and 'impact_summary' not in keywords): keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None if self.is_layer_compatible(layer, category, keywords): layers += [ {'id': layer.id(), 'name': layer.name(), 'keywords': keywords}] # Move layers without keywords to the end l1 = [l for l in layers if l['keywords']] l2 = [l for l in layers if not l['keywords']] layers = l1 + l2 return layers def get_layer_geometry_key(self, layer=None): """Obtain layer mode of a given layer. If no layer specified, the current layer is used :param layer : layer to examine :type layer: QgsMapLayer or None :returns: The layer mode. :rtype: str """ if not layer: layer = self.layer if is_raster_layer(layer): return layer_geometry_raster['key'] elif is_point_layer(layer): return layer_geometry_point['key'] elif is_polygon_layer(layer): return layer_geometry_polygon['key'] else: return layer_geometry_line['key'] def get_existing_keyword(self, keyword): """Obtain an existing keyword's value. :param keyword: A keyword from keywords. :type keyword: str :returns: The value of the keyword. :rtype: str, QUrl """ if self.existing_keywords is None: return {} if keyword is not None: return self.existing_keywords.get(keyword, {}) else: return {} def get_layer_description_from_canvas(self, layer, purpose): """Obtain the description of a canvas layer selected by user. :param layer: The QGIS layer. :type layer: QgsMapLayer :param purpose: The layer purpose of the layer to get the description. :type purpose: string :returns: description of the selected layer. :rtype: string """ if not layer: return "" try: keywords = self.keyword_io.read_keywords(layer) if 'layer_purpose' not in keywords: keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # set the current layer (e.g. for the keyword creation sub-thread) self.layer = layer if purpose == layer_purpose_hazard['key']: self.hazard_layer = layer elif purpose == layer_purpose_exposure['key']: self.exposure_layer = layer else: self.aggregation_layer = layer # Check if the layer is keywordless if keywords and 'keyword_version' in keywords: kw_ver = str(keywords['keyword_version']) self.is_selected_layer_keywordless = ( not is_keyword_version_supported(kw_ver)) else: self.is_selected_layer_keywordless = True description = layer_description_html(layer, keywords) return description # =========================== # NAVIGATION # =========================== def go_to_step(self, step): """Set the stacked widget to the given step, set up the buttons, and run all operations that should start immediately after entering the new step. :param step: The step widget to be moved to. :type step: WizardStep """ self.stackedWidget.setCurrentWidget(step) # Disable the Next button unless new data already entered self.pbnNext.setEnabled(step.is_ready_to_next_step()) # Enable the Back button unless it's not the first step self.pbnBack.setEnabled( step not in [self.step_kw_purpose, self.step_fc_functions1] or self.parent_step is not None) # Set Next button label if (step in [self.step_kw_summary, self.step_fc_analysis] and self.parent_step is None): self.pbnNext.setText(tr('Finish')) elif step == self.step_fc_summary: self.pbnNext.setText(tr('Run')) else: self.pbnNext.setText(tr('Next')) # Run analysis after switching to the new step if step == self.step_fc_analysis: self.step_fc_analysis.setup_and_run_analysis() # Set lblSelectCategory label if entering the kw mode # from the ifcw mode if step == self.step_kw_purpose and self.parent_step: if self.parent_step in [self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser]: text_label = category_question_hazard elif self.parent_step in [self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser]: text_label = category_question_exposure else: text_label = category_question_aggregation self.step_kw_purpose.lblSelectCategory.setText(text_label) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnNext_released(self): """Handle the Next button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ current_step = self.get_current_step() if current_step == self.step_kw_fields_mapping: try: self.step_kw_fields_mapping.get_field_mapping() except InvalidValidationException as e: display_warning_message_box( self, tr('Invalid Field Mapping'), get_string(e.message)) return if current_step.step_type == STEP_FC: self.impact_function_steps.append(current_step) elif current_step.step_type == STEP_KW: self.keyword_steps.append(current_step) else: LOGGER.debug(current_step.step_type) raise InvalidWizardStep # Save keywords if it's the end of the keyword creation mode if current_step == self.step_kw_summary: self.save_current_keywords() # After any step involving Browser, add selected layer to map canvas if current_step in [self.step_fc_hazlayer_from_browser, self.step_fc_explayer_from_browser, self.step_fc_agglayer_from_browser]: if not QgsMapLayerRegistry.instance().mapLayersByName( self.layer.name()): QgsMapLayerRegistry.instance().addMapLayers([self.layer]) # Make the layer visible. Might be hidden by default. See #2925 legend = self.iface.legendInterface() legend.setLayerVisible(self.layer, True) # After the extent selection, save the extent and disconnect signals if current_step == self.step_fc_extent: self.step_fc_extent.write_extent() # Determine the new step to be switched new_step = current_step.get_next_step() if new_step is not None: # Prepare the next tab new_step.set_widgets() else: # Wizard complete self.accept() return self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnBack_released(self): """Handle the Back button release. .. note:: This is an automatic Qt slot executed when the Back button is released. """ current_step = self.get_current_step() if current_step.step_type == STEP_FC: new_step = self.impact_function_steps.pop() elif current_step.step_type == STEP_KW: try: new_step = self.keyword_steps.pop() except IndexError: new_step = self.impact_function_steps.pop() else: raise InvalidWizardStep # set focus to table widgets, as the inactive selection style is gray if new_step == self.step_fc_functions1: self.step_fc_functions1.tblFunctions1.setFocus() if new_step == self.step_fc_functions2: self.step_fc_functions2.tblFunctions2.setFocus() # Re-connect disconnected signals when coming back to the Extent step if new_step == self.step_fc_extent: self.step_fc_extent.set_widgets() # Set Next button label self.pbnNext.setText(tr('Next')) self.pbnNext.setEnabled(True) self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnHelp_released(self): if self.on_help: self.pbnHelp.setText(tr('Show help')) self.wizard_help.restore_button_state() self.stackedWidget.setCurrentWidget(self.wizard_help.wizard_step) else: self.pbnHelp.setText(tr('Hide help')) self.wizard_help.show_help(self.get_current_step()) self.stackedWidget.setCurrentWidget(self.wizard_help) self.on_help = not self.on_help def get_current_step(self): """Return current step of the wizard. :returns: Current step of the wizard. :rtype: WizardStep instance """ return self.stackedWidget.currentWidget() def get_keywords(self): """Obtain the state of the dialog as a keywords dict. :returns: Keywords reflecting the state of the dialog. :rtype: dict """ keywords = {} inasafe_fields = {} keywords['layer_geometry'] = self.get_layer_geometry_key() if self.step_kw_purpose.selected_purpose(): keywords['layer_purpose'] = self.step_kw_purpose.\ selected_purpose()['key'] if self.step_kw_subcategory.selected_subcategory(): key = self.step_kw_purpose.selected_purpose()['key'] keywords[key] = self.step_kw_subcategory.\ selected_subcategory()['key'] if self.get_layer_geometry_key() == layer_geometry_raster['key']: if self.step_kw_band_selector.selected_band(): keywords['active_band'] = self.step_kw_band_selector.\ selected_band() if keywords['layer_purpose'] == layer_purpose_hazard['key']: if self.step_kw_hazard_category.selected_hazard_category(): keywords['hazard_category'] \ = self.step_kw_hazard_category.\ selected_hazard_category()['key'] if self.step_kw_layermode.selected_layermode(): keywords['layer_mode'] = self.step_kw_layermode.\ selected_layermode()['key'] if self.step_kw_unit.selected_unit(): if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard: key = continuous_hazard_unit['key'] else: key = exposure_unit['key'] keywords[key] = self.step_kw_unit.selected_unit()['key'] if self.step_kw_field.selected_fields(): field_key = self.field_keyword_for_the_layer() inasafe_fields[field_key] = self.step_kw_field.selected_fields() if self.step_kw_classification.selected_classification(): keywords['classification'] = self.step_kw_classification.\ selected_classification()['key'] if keywords['layer_purpose'] == layer_purpose_hazard['key']: multi_classifications = self.step_kw_multi_classifications.\ get_current_state() value_maps = multi_classifications.get('value_maps') if value_maps is not None: keywords['value_maps'] = value_maps thresholds = multi_classifications.get('thresholds') if thresholds is not None: keywords['thresholds'] = thresholds else: if self.step_kw_layermode.selected_layermode(): layer_mode = self.step_kw_layermode.selected_layermode() if layer_mode == layer_mode_continuous: thresholds = self.step_kw_threshold.get_threshold() if thresholds: keywords['thresholds'] = thresholds elif layer_mode == layer_mode_classified: value_map = self.step_kw_classify.selected_mapping() if value_map: keywords['value_map'] = value_map if self.step_kw_source.leSource.text(): keywords['source'] = get_unicode( self.step_kw_source.leSource.text()) if self.step_kw_source.leSource_url.text(): keywords['url'] = get_unicode( self.step_kw_source.leSource_url.text()) if self.step_kw_source.leSource_scale.text(): keywords['scale'] = get_unicode( self.step_kw_source.leSource_scale.text()) if self.step_kw_source.ckbSource_date.isChecked(): keywords['date'] = self.step_kw_source.dtSource_date.dateTime() if self.step_kw_source.leSource_license.text(): keywords['license'] = get_unicode( self.step_kw_source.leSource_license.text()) if self.step_kw_title.leTitle.text(): keywords['title'] = get_unicode(self.step_kw_title.leTitle.text()) inasafe_fields.update(self.step_kw_inasafe_fields.get_inasafe_fields()) inasafe_fields.update( self.step_kw_default_inasafe_fields.get_inasafe_fields()) inasafe_fields.update( self.step_kw_fields_mapping.get_field_mapping()['fields']) if inasafe_fields: keywords['inasafe_fields'] = inasafe_fields inasafe_default_values = {} if keywords['layer_geometry'] == layer_geometry_raster['key']: pass # Notes(IS): Skipped assigning raster inasafe default value for # now. # inasafe_default_values = self.\ # step_kw_inasafe_raster_default_values.\ # get_inasafe_default_values() else: inasafe_default_values.update( self.step_kw_default_inasafe_fields.get_inasafe_default_values( )) inasafe_default_values.update( self.step_kw_fields_mapping.get_field_mapping()['values']) if inasafe_default_values: keywords['inasafe_default_values'] = inasafe_default_values return keywords def save_current_keywords(self): """Save keywords to the layer. It will write out the keywords for the current layer. This method is based on the KeywordsDialog class. """ current_keywords = self.get_keywords() try: self.keyword_io.write_keywords( layer=self.layer, keywords=current_keywords) except InaSAFEError, e: error_message = get_error_message(e) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self, tr('InaSAFE'), tr('An error was encountered when saving the following ' 'keywords:\n {error_message}').format( error_message=error_message.to_html())) if self.dock is not None: # noinspection PyUnresolvedReferences self.dock.get_layers() # Save default value to QSetting if current_keywords.get('inasafe_default_values'): for key, value in ( current_keywords['inasafe_default_values'].items()): set_inasafe_default_value_qsetting( self.setting, RECENT, key, value)
class WizardDialog(QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE wizard.""" def __init__(self, parent=None, iface=None, dock=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('InaSAFE') # Constants self.keyword_creation_wizard_name = 'InaSAFE Keywords Creation Wizard' self.ifcw_name = 'InaSAFE Impact Function Centric Wizard' # Note the keys should remain untranslated as we need to write # english to the keywords file. # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.suppress_warning_dialog = False self.lblStep.clear() # Set icons self.lblMainIcon.setPixmap( QPixmap(resources_path('img', 'icons', 'icon-white.svg'))) self.keyword_io = KeywordIO() self.impact_function_manager = ImpactFunctionManager() self.is_selected_layer_keywordless = False self.parent_step = None self.pbnBack.setEnabled(False) self.pbnNext.setEnabled(False) self.pbnCancel.released.connect(self.reject) # Initialize attributes self.existing_keywords = None self.layer = None self.hazard_layer = None self.exposure_layer = None self.aggregation_layer = None self.analysis_handler = None self.step_kw_purpose = StepKwPurpose(self) self.step_kw_subcategory = StepKwSubcategory(self) self.step_kw_hazard_category = StepKwHazardCategory(self) self.step_kw_layermode = StepKwLayerMode(self) self.step_kw_unit = StepKwUnit(self) self.step_kw_classification = StepKwClassification(self) self.step_kw_field = StepKwField(self) self.step_kw_resample = StepKwResample(self) self.step_kw_classify = StepKwClassify(self) self.step_kw_name_field = StepKwNameField(self) self.step_kw_population_field = StepKwPopulationField(self) self.step_kw_extrakeywords = StepKwExtraKeywords(self) self.step_kw_aggregation = StepKwAggregation(self) self.step_kw_source = StepKwSource(self) self.step_kw_title = StepKwTitle(self) self.step_kw_summary = StepKwSummary(self) self.step_fc_functions1 = StepFcFunctions1(self) self.step_fc_functions2 = StepFcFunctions2(self) self.step_fc_function = StepFcFunction(self) self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self) self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self) self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self) self.step_fc_explayer_origin = StepFcExpLayerOrigin(self) self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self) self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self) self.step_fc_disjoint_layers = StepFcDisjointLayers(self) self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self) self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self) self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self) self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self) self.step_fc_extent = StepFcExtent(self) self.step_fc_extent_disjoint = StepFcExtentDisjoint(self) self.step_fc_params = StepFcParams(self) self.step_fc_summary = StepFcSummary(self) self.step_fc_analysis = StepFcAnalysis(self) self.stackedWidget.addWidget(self.step_kw_purpose) self.stackedWidget.addWidget(self.step_kw_subcategory) self.stackedWidget.addWidget(self.step_kw_hazard_category) self.stackedWidget.addWidget(self.step_kw_layermode) self.stackedWidget.addWidget(self.step_kw_unit) self.stackedWidget.addWidget(self.step_kw_classification) self.stackedWidget.addWidget(self.step_kw_field) self.stackedWidget.addWidget(self.step_kw_resample) self.stackedWidget.addWidget(self.step_kw_classify) self.stackedWidget.addWidget(self.step_kw_name_field) self.stackedWidget.addWidget(self.step_kw_population_field) self.stackedWidget.addWidget(self.step_kw_extrakeywords) self.stackedWidget.addWidget(self.step_kw_aggregation) self.stackedWidget.addWidget(self.step_kw_source) self.stackedWidget.addWidget(self.step_kw_title) self.stackedWidget.addWidget(self.step_kw_summary) self.stackedWidget.addWidget(self.step_fc_functions1) self.stackedWidget.addWidget(self.step_fc_functions2) self.stackedWidget.addWidget(self.step_fc_function) self.stackedWidget.addWidget(self.step_fc_hazlayer_origin) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser) self.stackedWidget.addWidget(self.step_fc_explayer_origin) self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_explayer_from_browser) self.stackedWidget.addWidget(self.step_fc_disjoint_layers) self.stackedWidget.addWidget(self.step_fc_agglayer_origin) self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser) self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint) self.stackedWidget.addWidget(self.step_fc_extent) self.stackedWidget.addWidget(self.step_fc_extent_disjoint) self.stackedWidget.addWidget(self.step_fc_params) self.stackedWidget.addWidget(self.step_fc_summary) self.stackedWidget.addWidget(self.step_fc_analysis) def set_mode_label_to_keywords_creation(self): """Set the mode label to the Keywords Creation/Update mode """ self.setWindowTitle(self.keyword_creation_wizard_name) if self.get_existing_keyword('layer_purpose'): mode_name = ( self.tr('Keywords update wizard for layer <b>%s</b>') % self.layer.name()) else: mode_name = ( self.tr('Keywords creation wizard for layer <b>%s</b>') % self.layer.name()) self.lblSubtitle.setText(mode_name) def set_mode_label_to_ifcw(self): """Set the mode label to the IFCW """ self.setWindowTitle(self.ifcw_name) self.lblSubtitle.setText( self.tr('Use this wizard to run a guided impact assessment')) def set_keywords_creation_mode(self, layer=None): """Set the Wizard to the Keywords Creation mode :param layer: Layer to set the keywords for :type layer: QgsMapLayer """ self.layer = layer or self.iface.mapCanvas().currentLayer() try: self.existing_keywords = self.keyword_io.read_keywords(self.layer) # if 'layer_purpose' not in self.existing_keywords: # self.existing_keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError, MetadataReadError): self.existing_keywords = None self.set_mode_label_to_keywords_creation() step = self.step_kw_purpose step.set_widgets() self.go_to_step(step) def set_function_centric_mode(self): """Set the Wizard to the Function Centric mode""" self.set_mode_label_to_ifcw() step = self.step_fc_functions1 step.set_widgets() self.go_to_step(step) def field_keyword_for_the_layer(self): """Return the proper keyword for field for the current layer. Expected values are: 'field', 'structure_class_field', road_class_field :returns: the field keyword :rtype: string """ if self.step_kw_purpose.selected_purpose() == \ layer_purpose_aggregation: # purpose: aggregation return 'aggregation attribute' elif self.step_kw_purpose.selected_purpose() == layer_purpose_hazard: # purpose: hazard if (self.step_kw_layermode.selected_layermode() == layer_mode_classified and is_point_layer(self.layer)): # No field for classified point hazards return '' else: # purpose: exposure layer_mode_key = self.step_kw_layermode.selected_layermode()['key'] layer_geometry_key = self.get_layer_geometry_id() exposure_key = self.step_kw_subcategory.\ selected_subcategory()['key'] exposure_class_fields = self.impact_function_manager.\ exposure_class_fields( layer_mode_key, layer_geometry_key, exposure_key) if exposure_class_fields and len(exposure_class_fields) == 1: return exposure_class_fields[0]['key'] # Fallback to default return 'field' def get_parent_mode_constraints(self): """Return the category and subcategory keys to be set in the subordinate mode. :returns: (the category definition, the hazard/exposure definition) :rtype: (dict, dict) """ h, e, _hc, _ec = self.selected_impact_function_constraints() if self.parent_step in [ self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser ]: category = layer_purpose_hazard subcategory = h elif self.parent_step in [ self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser ]: category = layer_purpose_exposure subcategory = e elif self.parent_step: category = layer_purpose_aggregation subcategory = None else: category = None subcategory = None return category, subcategory def selected_impact_function_constraints(self): """Obtain impact function constraints selected by user. :returns: Tuple of metadata of hazard, exposure, hazard layer constraints and exposure layer constraints :rtype: tuple """ selection = self.step_fc_functions1.tblFunctions1.selectedItems() if len(selection) != 1: return None, None, None, None h = selection[0].data(RoleHazard) e = selection[0].data(RoleExposure) selection = self.step_fc_functions2.tblFunctions2.selectedItems() if len(selection) != 1: return h, e, None, None hc = selection[0].data(RoleHazardConstraint) ec = selection[0].data(RoleExposureConstraint) return h, e, hc, ec def is_layer_compatible(self, layer, layer_purpose=None, keywords=None): """Validate if a given layer is compatible for selected IF as a given layer_purpose :param layer: The layer to be validated :type layer: QgsVectorLayer | QgsRasterLayer :param layer_purpose: The layer_purpose the layer is validated for :type layer_purpose: None, string :param keywords: The layer keywords :type keywords: None, dict :returns: True if layer is appropriate for the selected role :rtype: boolean """ # If not explicitly stated, find the desired purpose # from the parent step if not layer_purpose: layer_purpose = self.get_parent_mode_constraints()[0]['key'] # If not explicitly stated, read the layer's keywords if not keywords: try: keywords = self.keyword_io.read_keywords(layer) if ('layer_purpose' not in keywords and 'impact_summary' not in keywords): keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # Get allowed subcategory and layer_geometry from IF constraints h, e, hc, ec = self.selected_impact_function_constraints() if layer_purpose == 'hazard': subcategory = h['key'] layer_geometry = hc['key'] elif layer_purpose == 'exposure': subcategory = e['key'] layer_geometry = ec['key'] else: # For aggregation layers, use a simplified test and return if (keywords and 'layer_purpose' in keywords and keywords['layer_purpose'] == layer_purpose): return True if not keywords and is_polygon_layer(layer): return True return False # Compare layer properties with explicitly set constraints # Reject if layer geometry doesn't match if layer_geometry != self.get_layer_geometry_id(layer): return False # If no keywords, there's nothing more we can check. # The same if the keywords version doesn't match if not keywords or 'keyword_version' not in keywords: return True keyword_version = str(keywords['keyword_version']) if not is_keyword_version_supported(keyword_version): return True # Compare layer keywords with explicitly set constraints # Reject if layer purpose missing or doesn't match if ('layer_purpose' not in keywords or keywords['layer_purpose'] != layer_purpose): return False # Reject if layer subcategory doesn't match if (layer_purpose in keywords and keywords[layer_purpose] != subcategory): return False # Compare layer keywords with the chosen function's constraints imfunc = self.step_fc_function.selected_function() lay_req = imfunc['layer_requirements'][layer_purpose] # Reject if layer mode doesn't match if ('layer_mode' in keywords and lay_req['layer_mode']['key'] != keywords['layer_mode']): return False # Reject if classification doesn't match classification_key = '%s_%s_classification' % ( 'raster' if is_raster_layer(layer) else 'vector', layer_purpose) classification_keys = classification_key + 's' if (lay_req['layer_mode'] == layer_mode_classified and classification_key in keywords and classification_keys in lay_req): allowed_classifications = [ c['key'] for c in lay_req[classification_keys] ] if keywords[classification_key] not in allowed_classifications: return False # Reject if unit doesn't match unit_key = ('continuous_hazard_unit' if layer_purpose == layer_purpose_hazard['key'] else 'exposure_unit') unit_keys = unit_key + 's' if (lay_req['layer_mode'] == layer_mode_continuous and unit_key in keywords and unit_keys in lay_req): allowed_units = [c['key'] for c in lay_req[unit_keys]] if keywords[unit_key] not in allowed_units: return False # Finally return True return True def get_compatible_canvas_layers(self, category): """Collect layers from map canvas, compatible for the given category and selected impact function .. note:: Returns layers with keywords and layermode matching the category and compatible with the selected impact function. Also returns layers without keywords with layermode compatible with the selected impact function. :param category: The category to filter for. :type category: string :returns: Metadata of found layers. :rtype: list of dicts """ # Collect compatible layers layers = [] for layer in self.iface.mapCanvas().layers(): try: keywords = self.keyword_io.read_keywords(layer) if ('layer_purpose' not in keywords and 'impact_summary' not in keywords): keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None if self.is_layer_compatible(layer, category, keywords): layers += [{ 'id': layer.id(), 'name': layer.name(), 'keywords': keywords }] # Move layers without keywords to the end l1 = [l for l in layers if l['keywords']] l2 = [l for l in layers if not l['keywords']] layers = l1 + l2 return layers def get_layer_geometry_id(self, layer=None): """Obtain layer mode of a given layer. If no layer specified, the current layer is used :param layer : layer to examine :type layer: QgsMapLayer or None :returns: The layer mode. :rtype: str """ if not layer: layer = self.layer if is_raster_layer(layer): return 'raster' elif is_point_layer(layer): return 'point' elif is_polygon_layer(layer): return 'polygon' else: return 'line' def get_existing_keyword(self, keyword): """Obtain an existing keyword's value. :param keyword: A keyword from keywords. :type keyword: str :returns: The value of the keyword. :rtype: str, QUrl """ if self.existing_keywords is None: return None if keyword is not None: return self.existing_keywords.get(keyword, None) else: return None def get_layer_description_from_canvas(self, layer, purpose): """Obtain the description of a canvas layer selected by user. :param layer: The QGIS layer. :type layer: QgsMapLayer :param category: The category of the layer to get the description. :type category: string :returns: description of the selected layer. :rtype: string """ if not layer: return "" try: keywords = self.keyword_io.read_keywords(layer) if 'layer_purpose' not in keywords: keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # set the current layer (e.g. for the keyword creation sub-thread) self.layer = layer if purpose == 'hazard': self.hazard_layer = layer elif purpose == 'exposure': self.exposure_layer = layer else: self.aggregation_layer = layer # Check if the layer is keywordless if keywords and 'keyword_version' in keywords: kw_ver = str(keywords['keyword_version']) self.is_selected_layer_keywordless = ( not is_keyword_version_supported(kw_ver)) else: self.is_selected_layer_keywordless = True desc = layer_description_html(layer, keywords) return desc # =========================== # NAVIGATION # =========================== def go_to_step(self, step): """Set the stacked widget to the given step, set up the buttons, and run all operations that should start immediately after entering the new step. :param step: The step widget to be moved to. :type step: QWidget """ self.stackedWidget.setCurrentWidget(step) # Disable the Next button unless new data already entered self.pbnNext.setEnabled(step.is_ready_to_next_step()) # Enable the Back button unless it's not the first step self.pbnBack.setEnabled( step not in [self.step_kw_purpose, self.step_fc_functions1] or self.parent_step is not None) # Set Next button label if (step in [self.step_kw_summary, self.step_fc_analysis] and self.parent_step is None): self.pbnNext.setText(self.tr('Finish')) elif step == self.step_fc_summary: self.pbnNext.setText(self.tr('Run')) else: self.pbnNext.setText(self.tr('Next')) # Run analysis after switching to the new step if step == self.step_fc_analysis: self.step_fc_analysis.setup_and_run_analysis() # Set lblSelectCategory label if entering the kw mode # from the ifcw mode if step == self.step_kw_purpose and self.parent_step: if self.parent_step in [ self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser ]: text_label = category_question_hazard elif self.parent_step in [ self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser ]: text_label = category_question_exposure else: text_label = category_question_aggregation self.step_kw_purpose.lblSelectCategory.setText(text_label) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnNext_released(self): """Handle the Next button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ current_step = self.get_current_step() # Save keywords if it's the end of the keyword creation mode if current_step == self.step_kw_summary: self.save_current_keywords() if current_step == self.step_kw_aggregation: good_age_ratio, sum_age_ratios = self.step_kw_aggregation.\ age_ratios_are_valid() if not good_age_ratio: message = self.tr( 'The sum of age ratio default is %s and it is more ' 'than 1. Please adjust the age ratio default so that they ' 'will not more than 1.' % sum_age_ratios) if not self.suppress_warning_dialog: # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning(self, self.tr('InaSAFE'), message) return # After any step involving Browser, add selected layer to map canvas if current_step in [ self.step_fc_hazlayer_from_browser, self.step_fc_explayer_from_browser, self.step_fc_agglayer_from_browser ]: if not QgsMapLayerRegistry.instance().mapLayersByName( self.layer.name()): QgsMapLayerRegistry.instance().addMapLayers([self.layer]) # Make the layer visible. Might be hidden by default. See #2925 legend = self.iface.legendInterface() legend.setLayerVisible(self.layer, True) # After the extent selection, save the extent and disconnect signals if current_step == self.step_fc_extent: self.step_fc_extent.write_extent() # Determine the new step to be switched new_step = current_step.get_next_step() if (new_step == self.step_kw_extrakeywords and not self. step_kw_extrakeywords.additional_keywords_for_the_layer()): # Skip the extra_keywords tab if no extra keywords available: new_step = self.step_kw_source if new_step is not None: # Prepare the next tab new_step.set_widgets() else: # Wizard complete self.accept() return self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnBack_released(self): """Handle the Back button release. .. note:: This is an automatic Qt slot executed when the Back button is released. """ current_step = self.get_current_step() new_step = current_step.get_previous_step() # set focus to table widgets, as the inactive selection style is gray if new_step == self.step_fc_functions1: self.step_fc_functions1.tblFunctions1.setFocus() if new_step == self.step_fc_functions2: self.step_fc_functions2.tblFunctions2.setFocus() # Re-connect disconnected signals when coming back to the Extent step if new_step == self.step_fc_extent: self.step_fc_extent.set_widgets() # Set Next button label self.pbnNext.setText(self.tr('Next')) self.pbnNext.setEnabled(True) self.go_to_step(new_step) def get_current_step(self): """Return current step of the wizard. :returns: Current step of the wizard. :rtype: WizardStep instance """ return self.stackedWidget.currentWidget() def get_keywords(self): """Obtain the state of the dialog as a keywords dict. :returns: Keywords reflecting the state of the dialog. :rtype: dict """ keywords = {} keywords['layer_geometry'] = self.get_layer_geometry_id() if self.step_kw_purpose.selected_purpose(): keywords['layer_purpose'] = self.step_kw_purpose.\ selected_purpose()['key'] if keywords['layer_purpose'] == 'aggregation': keywords.update( self.step_kw_aggregation.get_aggregation_attributes()) if self.step_kw_subcategory.selected_subcategory(): key = self.step_kw_purpose.selected_purpose()['key'] keywords[key] = self.step_kw_subcategory.\ selected_subcategory()['key'] if self.step_kw_hazard_category.selected_hazard_category(): keywords['hazard_category'] \ = self.step_kw_hazard_category.\ selected_hazard_category()['key'] if self.step_kw_layermode.selected_layermode(): keywords['layer_mode'] = self.step_kw_layermode.\ selected_layermode()['key'] if self.step_kw_unit.selected_unit(): if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard: key = continuous_hazard_unit['key'] else: key = exposure_unit['key'] keywords[key] = self.step_kw_unit.selected_unit()['key'] if self.step_kw_resample.selected_allowresampling() is not None: keywords['allow_resampling'] = ( self.step_kw_resample.selected_allowresampling() and 'true' or 'false') if self.step_kw_field.lstFields.currentItem(): field_keyword = self.field_keyword_for_the_layer() keywords[field_keyword] = self.step_kw_field.\ lstFields.currentItem().text() if self.step_kw_classification.selected_classification(): geom = 'raster' if is_raster_layer(self.layer) else 'vector' key = '%s_%s_classification' % ( geom, self.step_kw_purpose.selected_purpose()['key']) keywords[key] = self.step_kw_classification.\ selected_classification()['key'] value_map = self.step_kw_classify.selected_mapping() if value_map: if self.step_kw_classification.selected_classification(): # hazard mapping keyword = 'value_map' else: # exposure mapping keyword = 'value_mapping' keywords[keyword] = json.dumps(value_map) name_field = self.step_kw_name_field.selected_field() if name_field: keywords['name_field'] = name_field population_field = self.step_kw_population_field.selected_field() if population_field: keywords['population_field'] = population_field extra_keywords = self.step_kw_extrakeywords.selected_extra_keywords() for key in extra_keywords: keywords[key] = extra_keywords[key] if self.step_kw_source.leSource.text(): keywords['source'] = get_unicode( self.step_kw_source.leSource.text()) if self.step_kw_source.leSource_url.text(): keywords['url'] = get_unicode( self.step_kw_source.leSource_url.text()) if self.step_kw_source.leSource_scale.text(): keywords['scale'] = get_unicode( self.step_kw_source.leSource_scale.text()) if self.step_kw_source.ckbSource_date.isChecked(): keywords['date'] = self.step_kw_source.dtSource_date.dateTime() if self.step_kw_source.leSource_license.text(): keywords['license'] = get_unicode( self.step_kw_source.leSource_license.text()) if self.step_kw_title.leTitle.text(): keywords['title'] = get_unicode(self.step_kw_title.leTitle.text()) return keywords def save_current_keywords(self): """Save keywords to the layer. It will write out the keywords for the current layer. This method is based on the KeywordsDialog class. """ current_keywords = self.get_keywords() try: self.keyword_io.write_keywords(layer=self.layer, keywords=current_keywords) except InaSAFEError, e: error_message = get_error_message(e) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self, self.tr('InaSAFE'), ((self.tr('An error was encountered when saving the following ' 'keywords:\n %s') % error_message.to_html()))) if self.dock is not None: # noinspection PyUnresolvedReferences self.dock.get_layers()
def accept(self): """Handler for when OK is clicked.""" input_path = self.input_path.text() input_title = self.line_edit_title.text() input_source = self.line_edit_source.text() output_path = self.output_path.text() if not output_path.endswith('.tif'): # noinspection PyArgumentList,PyCallByClass,PyTypeChecker QMessageBox.warning( self, self.tr('InaSAFE'), (self.tr('Output file name must be tif file'))) if not os.path.exists(input_path): # noinspection PyArgumentList,PyCallByClass,PyTypeChecker QMessageBox.warning( self, self.tr('InaSAFE'), (self.tr('Input file does not exist'))) return if self.nearest_mode.isChecked(): algorithm = 'nearest' else: algorithm = 'invdist' QtGui.qApp.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) file_name = convert_mmi_data( input_path, input_title, input_source, output_path, algorithm=algorithm, algorithm_filename_flag=True) # reclassify raster file_info = QFileInfo(file_name) base_name = file_info.baseName() self.output_layer = QgsRasterLayer(file_name, base_name) self.output_layer.keywords = KeywordIO.read_keywords(self.output_layer) self.output_layer.keywords['classification'] = ( earthquake_mmi_scale['key']) keywords = self.output_layer.keywords if self.output_layer.isValid(): self.output_layer = reclassify( self.output_layer, overwrite_input=True) KeywordIO.write_keywords(self.output_layer, keywords) else: LOGGER.debug("Failed to load") QtGui.qApp.restoreOverrideCursor() if self.load_result.isChecked(): # noinspection PyTypeChecker mmi_ramp_roman(self.output_layer) self.output_layer.saveDefaultStyle() if not self.output_layer.isValid(): LOGGER.debug("Failed to load") else: # noinspection PyArgumentList QgsMapLayerRegistry.instance().addMapLayer(self.output_layer) iface.zoomToActiveLayer() if (self.keyword_wizard_checkbox.isChecked() and self.keyword_wizard_checkbox.isEnabled()): self.launch_keyword_wizard() self.done(self.Accepted)
def save_hazard_data(self): if self.dummy_report_folder: filename = os.path.join( self.working_dir, self.dummy_report_folder, 'flood_data.json') hazard_geojson = DummySourceAPI.get_aggregate_report(filename) else: hazard_geojson = PetaJakartaAPI.get_aggregate_report( self.duration, self.level) if not hazard_geojson: raise PetaJakartaAPIError("Can't access PetaJakarta REST API") with open(self.hazard_path, 'w+') as f: f.write(hazard_geojson) # Save the layer as shp file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer( self.hazard_path, file_info.baseName(), 'ogr', False) target_name = 'flood_data.shp' self.hazard_path = os.path.join(self.report_path, target_name) QgsVectorFileWriter.writeAsVectorFormat( hazard_layer, self.hazard_path, 'CP1250', None, 'ESRI Shapefile') file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer( self.hazard_path, file_info.baseName(), 'ogr') # hazard_layer.startEditing() # field = QgsField('flooded', QVariant.Int) # hazard_layer.dataProvider().addAttributes([field]) # hazard_layer.commitChanges() # idx = hazard_layer.fieldNameIndex('flooded') # expression = QgsExpression('count > 0') # expression.prepare(hazard_layer.pendingFields()) # # hazard_layer.startEditing() # for feature in hazard_layer.getFeatures(): # feature[idx] = expression.evaluate(feature) # hazard_layer.updateFeature(feature) # # hazard_layer.commitChanges() # writing keywords keyword_io = KeywordIO() keywords = { 'field': 'state', 'hazard': 'generic', 'hazard_category': 'single_event', 'keyword_version': '3.5', 'layer_geometry': 'polygon', 'layer_mode': 'classified', 'layer_purpose': 'hazard', 'title': 'Flood', 'value_map': '{"high": [4], "medium": [3], ' '"low": [2], ' '"unaffected": ["None","","NULL",0,1]}', 'vector_hazard_classification': 'generic_vector_hazard_classes' } keyword_io.write_keywords(hazard_layer, keywords) # copy layer styles style_path = self.flood_fixtures_dir( 'flood_data_classified_state.qml') target_style_path = os.path.join( self.report_path, 'flood_data.qml') shutil.copy(style_path, target_style_path) # archiving hazard layer with ZipFile(self.hazard_zip_path, 'w') as zf: for root, dirs, files in os.walk(self.report_path): for f in files: _, ext = os.path.splitext(f) if 'flood_data' in f: filename = os.path.join(root, f) zf.write(filename, arcname=f)
def create_keyword_file(self, algorithm): """Create keyword file for the raster file created. Basically copy a template from keyword file in converter data and add extra keyword (usually a title) :param algorithm: Which re-sampling algorithm to use. valid options are 'nearest' (for nearest neighbour), 'invdist' (for inverse distance), 'average' (for moving average). Defaults to 'nearest' if not specified. Note that passing re-sampling alg parameters is currently not supported. If None is passed it will be replaced with 'nearest'. :type algorithm: str """ keyword_io = KeywordIO() # Set thresholds for each exposure mmi_default_classes = default_classification_thresholds( earthquake_mmi_scale ) mmi_default_threshold = { earthquake_mmi_scale['key']: { 'active': True, 'classes': mmi_default_classes } } generic_default_classes = default_classification_thresholds( generic_hazard_classes ) generic_default_threshold = { generic_hazard_classes['key']: { 'active': True, 'classes': generic_default_classes } } threshold_keyword = {} for exposure in exposure_all: # Not all exposure is supported by earthquake_mmi_scale if exposure in earthquake_mmi_scale['exposures']: threshold_keyword[exposure['key']] = mmi_default_threshold else: threshold_keyword[ exposure['key']] = generic_default_threshold extra_keywords = { extra_keyword_earthquake_latitude['key']: self.latitude, extra_keyword_earthquake_longitude['key']: self.longitude, extra_keyword_earthquake_magnitude['key']: self.magnitude, extra_keyword_earthquake_depth['key']: self.depth, extra_keyword_earthquake_description['key']: self.description, extra_keyword_earthquake_location['key']: self.location, extra_keyword_earthquake_event_time['key']: self.time.strftime( '%Y-%m-%dT%H:%M:%S'), extra_keyword_time_zone['key']: self.time_zone, extra_keyword_earthquake_x_minimum['key']: self.x_minimum, extra_keyword_earthquake_x_maximum['key']: self.x_maximum, extra_keyword_earthquake_y_minimum['key']: self.y_minimum, extra_keyword_earthquake_y_maximum['key']: self.y_maximum, extra_keyword_earthquake_event_id['key']: self.event_id } for key, value in list(self.extra_keywords.items()): extra_keywords[key] = value # Delete empty element. empty_keys = [] for key, value in list(extra_keywords.items()): if value is None: empty_keys.append(key) for empty_key in empty_keys: extra_keywords.pop(empty_key) keywords = { 'hazard': hazard_earthquake['key'], 'hazard_category': hazard_category_single_event['key'], 'keyword_version': inasafe_keyword_version, 'layer_geometry': layer_geometry_raster['key'], 'layer_mode': layer_mode_continuous['key'], 'layer_purpose': layer_purpose_hazard['key'], 'continuous_hazard_unit': unit_mmi['key'], 'classification': earthquake_mmi_scale['key'], 'thresholds': threshold_keyword, 'extra_keywords': extra_keywords, 'active_band': 1 } if self.algorithm_name: layer_path = os.path.join( self.output_dir, '%s-%s.tif' % ( self.output_basename, algorithm)) else: layer_path = os.path.join( self.output_dir, '%s.tif' % self.output_basename) # append title and source to the keywords file if len(self.title.strip()) == 0: keyword_title = self.output_basename else: keyword_title = self.title keywords['title'] = keyword_title hazard_layer = QgsRasterLayer(layer_path, keyword_title) if not hazard_layer.isValid(): raise InvalidLayerError() keyword_io.write_keywords(hazard_layer, keywords)
class WizardDialog(QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE wizard.""" def __init__(self, parent=None, iface=None, dock=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('InaSAFE') # Constants self.keyword_creation_wizard_name = 'InaSAFE Keywords Creation Wizard' self.ifcw_name = 'InaSAFE Impact Function Centric Wizard' # Note the keys should remain untranslated as we need to write # english to the keywords file. # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.suppress_warning_dialog = False self.lblStep.clear() # Set icons self.lblMainIcon.setPixmap( QPixmap(resources_path('img', 'icons', 'icon-white.svg'))) self.keyword_io = KeywordIO() self.impact_function_manager = ImpactFunctionManager() self.is_selected_layer_keywordless = False self.parent_step = None self.pbnBack.setEnabled(False) self.pbnNext.setEnabled(False) self.pbnCancel.released.connect(self.reject) # Initialize attributes self.existing_keywords = None self.layer = None self.hazard_layer = None self.exposure_layer = None self.aggregation_layer = None self.analysis_handler = None self.step_kw_purpose = StepKwPurpose(self) self.step_kw_subcategory = StepKwSubcategory(self) self.step_kw_hazard_category = StepKwHazardCategory(self) self.step_kw_layermode = StepKwLayerMode(self) self.step_kw_unit = StepKwUnit(self) self.step_kw_classification = StepKwClassification(self) self.step_kw_field = StepKwField(self) self.step_kw_resample = StepKwResample(self) self.step_kw_classify = StepKwClassify(self) self.step_kw_name_field = StepKwNameField(self) self.step_kw_population_field = StepKwPopulationField(self) self.step_kw_extrakeywords = StepKwExtraKeywords(self) self.step_kw_aggregation = StepKwAggregation(self) self.step_kw_source = StepKwSource(self) self.step_kw_title = StepKwTitle(self) self.step_kw_summary = StepKwSummary(self) self.step_fc_functions1 = StepFcFunctions1(self) self.step_fc_functions2 = StepFcFunctions2(self) self.step_fc_function = StepFcFunction(self) self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self) self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self) self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self) self.step_fc_explayer_origin = StepFcExpLayerOrigin(self) self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self) self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self) self.step_fc_disjoint_layers = StepFcDisjointLayers(self) self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self) self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self) self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self) self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self) self.step_fc_extent = StepFcExtent(self) self.step_fc_extent_disjoint = StepFcExtentDisjoint(self) self.step_fc_params = StepFcParams(self) self.step_fc_summary = StepFcSummary(self) self.step_fc_analysis = StepFcAnalysis(self) self.stackedWidget.addWidget(self.step_kw_purpose) self.stackedWidget.addWidget(self.step_kw_subcategory) self.stackedWidget.addWidget(self.step_kw_hazard_category) self.stackedWidget.addWidget(self.step_kw_layermode) self.stackedWidget.addWidget(self.step_kw_unit) self.stackedWidget.addWidget(self.step_kw_classification) self.stackedWidget.addWidget(self.step_kw_field) self.stackedWidget.addWidget(self.step_kw_resample) self.stackedWidget.addWidget(self.step_kw_classify) self.stackedWidget.addWidget(self.step_kw_name_field) self.stackedWidget.addWidget(self.step_kw_population_field) self.stackedWidget.addWidget(self.step_kw_extrakeywords) self.stackedWidget.addWidget(self.step_kw_aggregation) self.stackedWidget.addWidget(self.step_kw_source) self.stackedWidget.addWidget(self.step_kw_title) self.stackedWidget.addWidget(self.step_kw_summary) self.stackedWidget.addWidget(self.step_fc_functions1) self.stackedWidget.addWidget(self.step_fc_functions2) self.stackedWidget.addWidget(self.step_fc_function) self.stackedWidget.addWidget(self.step_fc_hazlayer_origin) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser) self.stackedWidget.addWidget(self.step_fc_explayer_origin) self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_explayer_from_browser) self.stackedWidget.addWidget(self.step_fc_disjoint_layers) self.stackedWidget.addWidget(self.step_fc_agglayer_origin) self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser) self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint) self.stackedWidget.addWidget(self.step_fc_extent) self.stackedWidget.addWidget(self.step_fc_extent_disjoint) self.stackedWidget.addWidget(self.step_fc_params) self.stackedWidget.addWidget(self.step_fc_summary) self.stackedWidget.addWidget(self.step_fc_analysis) def set_mode_label_to_keywords_creation(self): """Set the mode label to the Keywords Creation/Update mode """ self.setWindowTitle(self.keyword_creation_wizard_name) if self.get_existing_keyword('layer_purpose'): mode_name = (self.tr( 'Keywords update wizard for layer <b>%s</b>' ) % self.layer.name()) else: mode_name = (self.tr( 'Keywords creation wizard for layer <b>%s</b>' ) % self.layer.name()) self.lblSubtitle.setText(mode_name) def set_mode_label_to_ifcw(self): """Set the mode label to the IFCW """ self.setWindowTitle(self.ifcw_name) self.lblSubtitle.setText(self.tr( 'Use this wizard to run a guided impact assessment')) def set_keywords_creation_mode(self, layer=None): """Set the Wizard to the Keywords Creation mode :param layer: Layer to set the keywords for :type layer: QgsMapLayer """ self.layer = layer or self.iface.mapCanvas().currentLayer() try: self.existing_keywords = self.keyword_io.read_keywords(self.layer) # if 'layer_purpose' not in self.existing_keywords: # self.existing_keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError, MetadataReadError): self.existing_keywords = None self.set_mode_label_to_keywords_creation() step = self.step_kw_purpose step.set_widgets() self.go_to_step(step) def set_function_centric_mode(self): """Set the Wizard to the Function Centric mode""" self.set_mode_label_to_ifcw() step = self.step_fc_functions1 step.set_widgets() self.go_to_step(step) def field_keyword_for_the_layer(self): """Return the proper keyword for field for the current layer. Expected values are: 'field', 'structure_class_field', road_class_field :returns: the field keyword :rtype: string """ if self.step_kw_purpose.selected_purpose() == \ layer_purpose_aggregation: # purpose: aggregation return 'aggregation attribute' elif self.step_kw_purpose.selected_purpose() == layer_purpose_hazard: # purpose: hazard if (self.step_kw_layermode.selected_layermode() == layer_mode_classified and is_point_layer(self.layer)): # No field for classified point hazards return '' else: # purpose: exposure layer_mode_key = self.step_kw_layermode.selected_layermode()['key'] layer_geometry_key = self.get_layer_geometry_id() exposure_key = self.step_kw_subcategory.\ selected_subcategory()['key'] exposure_class_fields = self.impact_function_manager.\ exposure_class_fields( layer_mode_key, layer_geometry_key, exposure_key) if exposure_class_fields and len(exposure_class_fields) == 1: return exposure_class_fields[0]['key'] # Fallback to default return 'field' def get_parent_mode_constraints(self): """Return the category and subcategory keys to be set in the subordinate mode. :returns: (the category definition, the hazard/exposure definition) :rtype: (dict, dict) """ h, e, _hc, _ec = self.selected_impact_function_constraints() if self.parent_step in [self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser]: category = layer_purpose_hazard subcategory = h elif self.parent_step in [self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser]: category = layer_purpose_exposure subcategory = e elif self.parent_step: category = layer_purpose_aggregation subcategory = None else: category = None subcategory = None return category, subcategory def selected_impact_function_constraints(self): """Obtain impact function constraints selected by user. :returns: Tuple of metadata of hazard, exposure, hazard layer constraints and exposure layer constraints :rtype: tuple """ selection = self.step_fc_functions1.tblFunctions1.selectedItems() if len(selection) != 1: return None, None, None, None h = selection[0].data(RoleHazard) e = selection[0].data(RoleExposure) selection = self.step_fc_functions2.tblFunctions2.selectedItems() if len(selection) != 1: return h, e, None, None hc = selection[0].data(RoleHazardConstraint) ec = selection[0].data(RoleExposureConstraint) return h, e, hc, ec def is_layer_compatible(self, layer, layer_purpose=None, keywords=None): """Validate if a given layer is compatible for selected IF as a given layer_purpose :param layer: The layer to be validated :type layer: QgsVectorLayer | QgsRasterLayer :param layer_purpose: The layer_purpose the layer is validated for :type layer_purpose: None, string :param keywords: The layer keywords :type keywords: None, dict :returns: True if layer is appropriate for the selected role :rtype: boolean """ # If not explicitly stated, find the desired purpose # from the parent step if not layer_purpose: layer_purpose = self.get_parent_mode_constraints()[0]['key'] # If not explicitly stated, read the layer's keywords if not keywords: try: keywords = self.keyword_io.read_keywords(layer) if ('layer_purpose' not in keywords and 'impact_summary' not in keywords): keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # Get allowed subcategory and layer_geometry from IF constraints h, e, hc, ec = self.selected_impact_function_constraints() if layer_purpose == 'hazard': subcategory = h['key'] layer_geometry = hc['key'] elif layer_purpose == 'exposure': subcategory = e['key'] layer_geometry = ec['key'] else: # For aggregation layers, use a simplified test and return if (keywords and 'layer_purpose' in keywords and keywords['layer_purpose'] == layer_purpose): return True if not keywords and is_polygon_layer(layer): return True return False # Compare layer properties with explicitly set constraints # Reject if layer geometry doesn't match if layer_geometry != self.get_layer_geometry_id(layer): return False # If no keywords, there's nothing more we can check. # The same if the keywords version doesn't match if not keywords or 'keyword_version' not in keywords: return True keyword_version = str(keywords['keyword_version']) if not is_keyword_version_supported(keyword_version): return True # Compare layer keywords with explicitly set constraints # Reject if layer purpose missing or doesn't match if ('layer_purpose' not in keywords or keywords['layer_purpose'] != layer_purpose): return False # Reject if layer subcategory doesn't match if (layer_purpose in keywords and keywords[layer_purpose] != subcategory): return False # Compare layer keywords with the chosen function's constraints imfunc = self.step_fc_function.selected_function() lay_req = imfunc['layer_requirements'][layer_purpose] # Reject if layer mode doesn't match if ('layer_mode' in keywords and lay_req['layer_mode']['key'] != keywords['layer_mode']): return False # Reject if classification doesn't match classification_key = '%s_%s_classification' % ( 'raster' if is_raster_layer(layer) else 'vector', layer_purpose) classification_keys = classification_key + 's' if (lay_req['layer_mode'] == layer_mode_classified and classification_key in keywords and classification_keys in lay_req): allowed_classifications = [ c['key'] for c in lay_req[classification_keys]] if keywords[classification_key] not in allowed_classifications: return False # Reject if unit doesn't match unit_key = ('continuous_hazard_unit' if layer_purpose == layer_purpose_hazard['key'] else 'exposure_unit') unit_keys = unit_key + 's' if (lay_req['layer_mode'] == layer_mode_continuous and unit_key in keywords and unit_keys in lay_req): allowed_units = [ c['key'] for c in lay_req[unit_keys]] if keywords[unit_key] not in allowed_units: return False # Finally return True return True def get_compatible_canvas_layers(self, category): """Collect layers from map canvas, compatible for the given category and selected impact function .. note:: Returns layers with keywords and layermode matching the category and compatible with the selected impact function. Also returns layers without keywords with layermode compatible with the selected impact function. :param category: The category to filter for. :type category: string :returns: Metadata of found layers. :rtype: list of dicts """ # Collect compatible layers layers = [] for layer in self.iface.mapCanvas().layers(): try: keywords = self.keyword_io.read_keywords(layer) if ('layer_purpose' not in keywords and 'impact_summary' not in keywords): keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None if self.is_layer_compatible(layer, category, keywords): layers += [ {'id': layer.id(), 'name': layer.name(), 'keywords': keywords}] # Move layers without keywords to the end l1 = [l for l in layers if l['keywords']] l2 = [l for l in layers if not l['keywords']] layers = l1 + l2 return layers def get_layer_geometry_id(self, layer=None): """Obtain layer mode of a given layer. If no layer specified, the current layer is used :param layer : layer to examine :type layer: QgsMapLayer or None :returns: The layer mode. :rtype: str """ if not layer: layer = self.layer if is_raster_layer(layer): return 'raster' elif is_point_layer(layer): return 'point' elif is_polygon_layer(layer): return 'polygon' else: return 'line' def get_existing_keyword(self, keyword): """Obtain an existing keyword's value. :param keyword: A keyword from keywords. :type keyword: str :returns: The value of the keyword. :rtype: str, QUrl """ if self.existing_keywords is None: return None if keyword is not None: return self.existing_keywords.get(keyword, None) else: return None def get_layer_description_from_canvas(self, layer, purpose): """Obtain the description of a canvas layer selected by user. :param layer: The QGIS layer. :type layer: QgsMapLayer :param category: The category of the layer to get the description. :type category: string :returns: description of the selected layer. :rtype: string """ if not layer: return "" try: keywords = self.keyword_io.read_keywords(layer) if 'layer_purpose' not in keywords: keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # set the current layer (e.g. for the keyword creation sub-thread) self.layer = layer if purpose == 'hazard': self.hazard_layer = layer elif purpose == 'exposure': self.exposure_layer = layer else: self.aggregation_layer = layer # Check if the layer is keywordless if keywords and 'keyword_version' in keywords: kw_ver = str(keywords['keyword_version']) self.is_selected_layer_keywordless = ( not is_keyword_version_supported(kw_ver)) else: self.is_selected_layer_keywordless = True desc = layer_description_html(layer, keywords) return desc # =========================== # NAVIGATION # =========================== def go_to_step(self, step): """Set the stacked widget to the given step, set up the buttons, and run all operations that should start immediately after entering the new step. :param step: The step widget to be moved to. :type step: QWidget """ self.stackedWidget.setCurrentWidget(step) # Disable the Next button unless new data already entered self.pbnNext.setEnabled(step.is_ready_to_next_step()) # Enable the Back button unless it's not the first step self.pbnBack.setEnabled( step not in [self.step_kw_purpose, self.step_fc_functions1] or self.parent_step is not None) # Set Next button label if (step in [self.step_kw_summary, self.step_fc_analysis] and self.parent_step is None): self.pbnNext.setText(self.tr('Finish')) elif step == self.step_fc_summary: self.pbnNext.setText(self.tr('Run')) else: self.pbnNext.setText(self.tr('Next')) # Run analysis after switching to the new step if step == self.step_fc_analysis: self.step_fc_analysis.setup_and_run_analysis() # Set lblSelectCategory label if entering the kw mode # from the ifcw mode if step == self.step_kw_purpose and self.parent_step: if self.parent_step in [self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser]: text_label = category_question_hazard elif self.parent_step in [self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser]: text_label = category_question_exposure else: text_label = category_question_aggregation self.step_kw_purpose.lblSelectCategory.setText(text_label) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnNext_released(self): """Handle the Next button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ current_step = self.get_current_step() # Save keywords if it's the end of the keyword creation mode if current_step == self.step_kw_summary: self.save_current_keywords() if current_step == self.step_kw_aggregation: good_age_ratio, sum_age_ratios = self.step_kw_aggregation.\ age_ratios_are_valid() if not good_age_ratio: message = self.tr( 'The sum of age ratio default is %s and it is more ' 'than 1. Please adjust the age ratio default so that they ' 'will not more than 1.' % sum_age_ratios) if not self.suppress_warning_dialog: # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self, self.tr('InaSAFE'), message) return # After any step involving Browser, add selected layer to map canvas if current_step in [self.step_fc_hazlayer_from_browser, self.step_fc_explayer_from_browser, self.step_fc_agglayer_from_browser]: if not QgsMapLayerRegistry.instance().mapLayersByName( self.layer.name()): QgsMapLayerRegistry.instance().addMapLayers([self.layer]) # Make the layer visible. Might be hidden by default. See #2925 legend = self.iface.legendInterface() legend.setLayerVisible(self.layer, True) # After the extent selection, save the extent and disconnect signals if current_step == self.step_fc_extent: self.step_fc_extent.write_extent() # Determine the new step to be switched new_step = current_step.get_next_step() if (new_step == self.step_kw_extrakeywords and not self.step_kw_extrakeywords. additional_keywords_for_the_layer()): # Skip the extra_keywords tab if no extra keywords available: new_step = self.step_kw_source if new_step is not None: # Prepare the next tab new_step.set_widgets() else: # Wizard complete self.accept() return self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnBack_released(self): """Handle the Back button release. .. note:: This is an automatic Qt slot executed when the Back button is released. """ current_step = self.get_current_step() new_step = current_step.get_previous_step() # set focus to table widgets, as the inactive selection style is gray if new_step == self.step_fc_functions1: self.step_fc_functions1.tblFunctions1.setFocus() if new_step == self.step_fc_functions2: self.step_fc_functions2.tblFunctions2.setFocus() # Re-connect disconnected signals when coming back to the Extent step if new_step == self.step_fc_extent: self.step_fc_extent.set_widgets() # Set Next button label self.pbnNext.setText(self.tr('Next')) self.pbnNext.setEnabled(True) self.go_to_step(new_step) def get_current_step(self): """Return current step of the wizard. :returns: Current step of the wizard. :rtype: WizardStep instance """ return self.stackedWidget.currentWidget() def get_keywords(self): """Obtain the state of the dialog as a keywords dict. :returns: Keywords reflecting the state of the dialog. :rtype: dict """ keywords = {} keywords['layer_geometry'] = self.get_layer_geometry_id() if self.step_kw_purpose.selected_purpose(): keywords['layer_purpose'] = self.step_kw_purpose.\ selected_purpose()['key'] if keywords['layer_purpose'] == 'aggregation': keywords.update( self.step_kw_aggregation.get_aggregation_attributes()) if self.step_kw_subcategory.selected_subcategory(): key = self.step_kw_purpose.selected_purpose()['key'] keywords[key] = self.step_kw_subcategory.\ selected_subcategory()['key'] if self.step_kw_hazard_category.selected_hazard_category(): keywords['hazard_category'] \ = self.step_kw_hazard_category.\ selected_hazard_category()['key'] if self.step_kw_layermode.selected_layermode(): keywords['layer_mode'] = self.step_kw_layermode.\ selected_layermode()['key'] if self.step_kw_unit.selected_unit(): if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard: key = continuous_hazard_unit['key'] else: key = exposure_unit['key'] keywords[key] = self.step_kw_unit.selected_unit()['key'] if self.step_kw_resample.selected_allowresampling() is not None: keywords['allow_resampling'] = ( self.step_kw_resample.selected_allowresampling() and 'true' or 'false') if self.step_kw_field.lstFields.currentItem(): field_keyword = self.field_keyword_for_the_layer() keywords[field_keyword] = self.step_kw_field.\ lstFields.currentItem().text() if self.step_kw_classification.selected_classification(): geom = 'raster' if is_raster_layer(self.layer) else 'vector' key = '%s_%s_classification' % ( geom, self.step_kw_purpose.selected_purpose()['key']) keywords[key] = self.step_kw_classification.\ selected_classification()['key'] value_map = self.step_kw_classify.selected_mapping() if value_map: if self.step_kw_classification.selected_classification(): # hazard mapping keyword = 'value_map' else: # exposure mapping keyword = 'value_mapping' keywords[keyword] = json.dumps(value_map) name_field = self.step_kw_name_field.selected_field() if name_field: keywords['name_field'] = name_field population_field = self.step_kw_population_field.selected_field() if population_field: keywords['population_field'] = population_field extra_keywords = self.step_kw_extrakeywords.selected_extra_keywords() for key in extra_keywords: keywords[key] = extra_keywords[key] if self.step_kw_source.leSource.text(): keywords['source'] = get_unicode( self.step_kw_source.leSource.text()) if self.step_kw_source.leSource_url.text(): keywords['url'] = get_unicode( self.step_kw_source.leSource_url.text()) if self.step_kw_source.leSource_scale.text(): keywords['scale'] = get_unicode( self.step_kw_source.leSource_scale.text()) if self.step_kw_source.ckbSource_date.isChecked(): keywords['date'] = self.step_kw_source.dtSource_date.dateTime() if self.step_kw_source.leSource_license.text(): keywords['license'] = get_unicode( self.step_kw_source.leSource_license.text()) if self.step_kw_title.leTitle.text(): keywords['title'] = get_unicode(self.step_kw_title.leTitle.text()) return keywords def save_current_keywords(self): """Save keywords to the layer. It will write out the keywords for the current layer. This method is based on the KeywordsDialog class. """ current_keywords = self.get_keywords() try: self.keyword_io.write_keywords( layer=self.layer, keywords=current_keywords) except InaSAFEError, e: error_message = get_error_message(e) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self, self.tr('InaSAFE'), ((self.tr( 'An error was encountered when saving the following ' 'keywords:\n %s') % error_message.to_html()))) if self.dock is not None: # noinspection PyUnresolvedReferences self.dock.get_layers()
def save_hazard_data(self): if self.dummy_report_folder: filename = os.path.join(self.working_dir, self.dummy_report_folder, 'flood_data.json') hazard_geojson = DummySourceAPI.get_aggregate_report(filename) else: hazard_geojson = PetaJakartaAPI.get_aggregate_report( self.duration, self.level) if not hazard_geojson: raise PetaJakartaAPIError("Can't access PetaJakarta REST API") with open(self.hazard_path, 'w+') as f: f.write(hazard_geojson) # Save the layer as shp file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr', False) target_name = 'flood_data.shp' self.hazard_path = os.path.join(self.report_path, target_name) QgsVectorFileWriter.writeAsVectorFormat(hazard_layer, self.hazard_path, 'CP1250', None, 'ESRI Shapefile') file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr') # hazard_layer.startEditing() # field = QgsField('flooded', QVariant.Int) # hazard_layer.dataProvider().addAttributes([field]) # hazard_layer.commitChanges() # idx = hazard_layer.fieldNameIndex('flooded') # expression = QgsExpression('count > 0') # expression.prepare(hazard_layer.pendingFields()) # # hazard_layer.startEditing() # for feature in hazard_layer.getFeatures(): # feature[idx] = expression.evaluate(feature) # hazard_layer.updateFeature(feature) # # hazard_layer.commitChanges() # writing keywords keyword_io = KeywordIO() keywords = { 'field': 'state', 'hazard': 'generic', 'hazard_category': 'single_event', 'keyword_version': '3.5', 'layer_geometry': 'polygon', 'layer_mode': 'classified', 'layer_purpose': 'hazard', 'title': 'Flood', 'value_map': '{"high": [4], "medium": [3], ' '"low": [2], ' '"unaffected": ["None","","NULL",0,1]}', 'vector_hazard_classification': 'generic_vector_hazard_classes' } keyword_io.write_keywords(hazard_layer, keywords) # copy layer styles style_path = self.flood_fixtures_dir('flood_data_classified_state.qml') target_style_path = os.path.join(self.report_path, 'flood_data.qml') shutil.copy(style_path, target_style_path) # archiving hazard layer with ZipFile(self.hazard_zip_path, 'w') as zf: for root, dirs, files in os.walk(self.report_path): for f in files: _, ext = os.path.splitext(f) if 'flood_data' in f: filename = os.path.join(root, f) zf.write(filename, arcname=f)
class WizardDialog(QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE wizard.""" resized = pyqtSignal() def __init__(self, parent=None, iface=None, dock=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('InaSAFE') # Constants self.keyword_creation_wizard_name = tr( 'InaSAFE Keywords Creation Wizard') self.ifcw_name = tr('InaSAFE Impact Function Centric Wizard') # Note the keys should remain untranslated as we need to write # english to the keywords file. # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.suppress_warning_dialog = False self.lblStep.clear() # Set icons self.lblMainIcon.setPixmap( QPixmap(resources_path('img', 'icons', 'icon-white.svg'))) self.keyword_io = KeywordIO() self.is_selected_layer_keywordless = False self.parent_step = None self.pbnBack.setEnabled(False) self.pbnNext.setEnabled(False) self.pbnCancel.released.connect(self.reject) # Initialize attributes self.existing_keywords = None self.layer = None self.hazard_layer = None self.exposure_layer = None self.aggregation_layer = None self.step_kw_purpose = StepKwPurpose(self) self.step_kw_subcategory = StepKwSubcategory(self) self.step_kw_hazard_category = StepKwHazardCategory(self) self.step_kw_band_selector = StepKwBandSelector(self) self.step_kw_layermode = StepKwLayerMode(self) self.step_kw_unit = StepKwUnit(self) self.step_kw_classification = StepKwClassification(self) self.step_kw_field = StepKwField(self) self.step_kw_multi_classifications = StepKwMultiClassifications(self) self.step_kw_classify = StepKwClassify(self) self.step_kw_threshold = StepKwThreshold(self) self.step_kw_fields_mapping = StepKwFieldsMapping(self) self.step_kw_inasafe_fields = StepKwInaSAFEFields(self) self.step_kw_default_inasafe_fields = StepKwDefaultInaSAFEFields(self) self.step_kw_inasafe_raster_default_values = \ StepKwInaSAFERasterDefaultValues(self) self.step_kw_source = StepKwSource(self) self.step_kw_title = StepKwTitle(self) self.step_kw_summary = StepKwSummary(self) self.step_fc_functions1 = StepFcFunctions1(self) self.step_fc_functions2 = StepFcFunctions2(self) self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self) self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self) self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self) self.step_fc_explayer_origin = StepFcExpLayerOrigin(self) self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self) self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self) self.step_fc_disjoint_layers = StepFcDisjointLayers(self) self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self) self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self) self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self) self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self) self.step_fc_extent = StepFcExtent(self) self.step_fc_extent_disjoint = StepFcExtentDisjoint(self) self.step_fc_summary = StepFcSummary(self) self.step_fc_analysis = StepFcAnalysis(self) self.wizard_help = WizardHelp(self) self.stackedWidget.addWidget(self.step_kw_purpose) self.stackedWidget.addWidget(self.step_kw_subcategory) self.stackedWidget.addWidget(self.step_kw_hazard_category) self.stackedWidget.addWidget(self.step_kw_band_selector) self.stackedWidget.addWidget(self.step_kw_layermode) self.stackedWidget.addWidget(self.step_kw_unit) self.stackedWidget.addWidget(self.step_kw_classification) self.stackedWidget.addWidget(self.step_kw_field) self.stackedWidget.addWidget(self.step_kw_multi_classifications) self.stackedWidget.addWidget(self.step_kw_classify) self.stackedWidget.addWidget(self.step_kw_threshold) self.stackedWidget.addWidget(self.step_kw_fields_mapping) self.stackedWidget.addWidget(self.step_kw_inasafe_fields) self.stackedWidget.addWidget(self.step_kw_default_inasafe_fields) self.stackedWidget.addWidget( self.step_kw_inasafe_raster_default_values) self.stackedWidget.addWidget(self.step_kw_source) self.stackedWidget.addWidget(self.step_kw_title) self.stackedWidget.addWidget(self.step_kw_summary) self.stackedWidget.addWidget(self.step_fc_functions1) self.stackedWidget.addWidget(self.step_fc_functions2) self.stackedWidget.addWidget(self.step_fc_hazlayer_origin) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser) self.stackedWidget.addWidget(self.step_fc_explayer_origin) self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_explayer_from_browser) self.stackedWidget.addWidget(self.step_fc_disjoint_layers) self.stackedWidget.addWidget(self.step_fc_agglayer_origin) self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser) self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint) self.stackedWidget.addWidget(self.step_fc_extent) self.stackedWidget.addWidget(self.step_fc_extent_disjoint) self.stackedWidget.addWidget(self.step_fc_summary) self.stackedWidget.addWidget(self.step_fc_analysis) self.stackedWidget.addWidget(self.wizard_help) # QSetting self.setting = QSettings() # Wizard Steps self.impact_function_steps = [] self.keyword_steps = [] self.on_help = False self.resized.connect(self.after_resize) def set_mode_label_to_keywords_creation(self): """Set the mode label to the Keywords Creation/Update mode.""" self.setWindowTitle(self.keyword_creation_wizard_name) if self.get_existing_keyword('layer_purpose'): mode_name = tr( 'Keywords update wizard for layer <b>{layer_name}</b>').format( layer_name=self.layer.name()) else: mode_name = tr( 'Keywords creation wizard for layer <b>{layer_name}</b>' ).format(layer_name=self.layer.name()) self.lblSubtitle.setText(mode_name) def set_mode_label_to_ifcw(self): """Set the mode label to the IFCW.""" self.setWindowTitle(self.ifcw_name) self.lblSubtitle.setText( tr('Use this wizard to run a guided impact assessment')) def set_keywords_creation_mode(self, layer=None, keywords=None): """Set the Wizard to the Keywords Creation mode. :param layer: Layer to set the keywords for :type layer: QgsMapLayer :param keywords: Keywords for the layer. :type keywords: dict, None """ self.layer = layer or self.iface.mapCanvas().currentLayer() if keywords is not None: self.existing_keywords = keywords else: # Always read from metadata file. try: self.existing_keywords = self.keyword_io.read_keywords( self.layer) except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError, MetadataReadError): self.existing_keywords = None self.set_mode_label_to_keywords_creation() step = self.step_kw_purpose step.set_widgets() self.go_to_step(step) def set_function_centric_mode(self): """Set the Wizard to the Function Centric mode.""" self.set_mode_label_to_ifcw() step = self.step_fc_functions1 step.set_widgets() self.go_to_step(step) def field_keyword_for_the_layer(self): """Return the proper keyword for field for the current layer. :returns: the field keyword :rtype: str """ layer_purpose_key = self.step_kw_purpose.selected_purpose()['key'] if layer_purpose_key == layer_purpose_aggregation['key']: return get_compulsory_fields(layer_purpose_key)['key'] elif layer_purpose_key in [ layer_purpose_exposure['key'], layer_purpose_hazard['key'] ]: layer_subcategory_key = \ self.step_kw_subcategory.selected_subcategory()['key'] return get_compulsory_fields(layer_purpose_key, layer_subcategory_key)['key'] else: raise InvalidParameterError def get_parent_mode_constraints(self): """Return the category and subcategory keys to be set in the subordinate mode. :returns: (the category definition, the hazard/exposure definition) :rtype: (dict, dict) """ h, e, _hc, _ec = self.selected_impact_function_constraints() if self.parent_step in [ self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser ]: category = layer_purpose_hazard subcategory = h elif self.parent_step in [ self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser ]: category = layer_purpose_exposure subcategory = e elif self.parent_step: category = layer_purpose_aggregation subcategory = None else: category = None subcategory = None return category, subcategory def selected_impact_function_constraints(self): """Obtain impact function constraints selected by user. :returns: Tuple of metadata of hazard, exposure, hazard layer geometry and exposure layer geometry :rtype: tuple """ hazard = self.step_fc_functions1.selected_value( layer_purpose_hazard['key']) exposure = self.step_fc_functions1.selected_value( layer_purpose_exposure['key']) hazard_geometry = self.step_fc_functions2.selected_value( layer_purpose_hazard['key']) exposure_geometry = self.step_fc_functions2.selected_value( layer_purpose_exposure['key']) return hazard, exposure, hazard_geometry, exposure_geometry def is_layer_compatible(self, layer, layer_purpose=None, keywords=None): """Validate if a given layer is compatible for selected IF as a given layer_purpose :param layer: The layer to be validated :type layer: QgsVectorLayer | QgsRasterLayer :param layer_purpose: The layer_purpose the layer is validated for :type layer_purpose: None, string :param keywords: The layer keywords :type keywords: None, dict :returns: True if layer is appropriate for the selected role :rtype: boolean """ # If not explicitly stated, find the desired purpose # from the parent step if not layer_purpose: layer_purpose = self.get_parent_mode_constraints()[0]['key'] # If not explicitly stated, read the layer's keywords if not keywords: try: keywords = self.keyword_io.read_keywords(layer) if 'layer_purpose' not in keywords: keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # Get allowed subcategory and layer_geometry from IF constraints h, e, hc, ec = self.selected_impact_function_constraints() if layer_purpose == 'hazard': subcategory = h['key'] layer_geometry = hc['key'] elif layer_purpose == 'exposure': subcategory = e['key'] layer_geometry = ec['key'] else: # For aggregation layers, use a simplified test and return if (keywords and 'layer_purpose' in keywords and keywords['layer_purpose'] == layer_purpose): return True if not keywords and is_polygon_layer(layer): return True return False # Compare layer properties with explicitly set constraints # Reject if layer geometry doesn't match if layer_geometry != self.get_layer_geometry_key(layer): return False # If no keywords, there's nothing more we can check. # The same if the keywords version doesn't match if not keywords or 'keyword_version' not in keywords: return True keyword_version = str(keywords['keyword_version']) if not is_keyword_version_supported(keyword_version): return True # Compare layer keywords with explicitly set constraints # Reject if layer purpose missing or doesn't match if ('layer_purpose' not in keywords or keywords['layer_purpose'] != layer_purpose): return False # Reject if layer subcategory doesn't match if (layer_purpose in keywords and keywords[layer_purpose] != subcategory): return False return True def get_compatible_canvas_layers(self, category): """Collect layers from map canvas, compatible for the given category and selected impact function .. note:: Returns layers with keywords and layermode matching the category and compatible with the selected impact function. Also returns layers without keywords with layermode compatible with the selected impact function. :param category: The category to filter for. :type category: string :returns: Metadata of found layers. :rtype: list of dicts """ # Collect compatible layers layers = [] for layer in self.iface.mapCanvas().layers(): try: keywords = self.keyword_io.read_keywords(layer) if 'layer_purpose' not in keywords: keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None if self.is_layer_compatible(layer, category, keywords): layers += [{ 'id': layer.id(), 'name': layer.name(), 'keywords': keywords }] # Move layers without keywords to the end l1 = [l for l in layers if l['keywords']] l2 = [l for l in layers if not l['keywords']] layers = l1 + l2 return layers def get_layer_geometry_key(self, layer=None): """Obtain layer mode of a given layer. If no layer specified, the current layer is used :param layer : layer to examine :type layer: QgsMapLayer or None :returns: The layer mode. :rtype: str """ if not layer: layer = self.layer if is_raster_layer(layer): return layer_geometry_raster['key'] elif is_point_layer(layer): return layer_geometry_point['key'] elif is_polygon_layer(layer): return layer_geometry_polygon['key'] else: return layer_geometry_line['key'] def get_existing_keyword(self, keyword): """Obtain an existing keyword's value. :param keyword: A keyword from keywords. :type keyword: str :returns: The value of the keyword. :rtype: str, QUrl """ if self.existing_keywords is None: return {} if keyword is not None: return self.existing_keywords.get(keyword, {}) else: return {} def get_layer_description_from_canvas(self, layer, purpose): """Obtain the description of a canvas layer selected by user. :param layer: The QGIS layer. :type layer: QgsMapLayer :param purpose: The layer purpose of the layer to get the description. :type purpose: string :returns: description of the selected layer. :rtype: string """ if not layer: return "" try: keywords = self.keyword_io.read_keywords(layer) if 'layer_purpose' not in keywords: keywords = None except (HashNotFoundError, OperationalError, NoKeywordsFoundError, KeywordNotFoundError, InvalidParameterError, UnsupportedProviderError): keywords = None # set the current layer (e.g. for the keyword creation sub-thread) self.layer = layer if purpose == layer_purpose_hazard['key']: self.hazard_layer = layer elif purpose == layer_purpose_exposure['key']: self.exposure_layer = layer else: self.aggregation_layer = layer # Check if the layer is keywordless if keywords and 'keyword_version' in keywords: kw_ver = str(keywords['keyword_version']) self.is_selected_layer_keywordless = ( not is_keyword_version_supported(kw_ver)) else: self.is_selected_layer_keywordless = True description = layer_description_html(layer, keywords) return description # =========================== # NAVIGATION # =========================== def go_to_step(self, step): """Set the stacked widget to the given step, set up the buttons, and run all operations that should start immediately after entering the new step. :param step: The step widget to be moved to. :type step: WizardStep """ self.stackedWidget.setCurrentWidget(step) # Disable the Next button unless new data already entered self.pbnNext.setEnabled(step.is_ready_to_next_step()) # Enable the Back button unless it's not the first step self.pbnBack.setEnabled( step not in [self.step_kw_purpose, self.step_fc_functions1] or self.parent_step is not None) # Set Next button label if (step in [self.step_kw_summary, self.step_fc_analysis] and self.parent_step is None): self.pbnNext.setText(tr('Finish')) elif step == self.step_fc_summary: self.pbnNext.setText(tr('Run')) else: self.pbnNext.setText(tr('Next')) # Run analysis after switching to the new step if step == self.step_fc_analysis: self.step_fc_analysis.setup_and_run_analysis() # Set lblSelectCategory label if entering the kw mode # from the ifcw mode if step == self.step_kw_purpose and self.parent_step: if self.parent_step in [ self.step_fc_hazlayer_from_canvas, self.step_fc_hazlayer_from_browser ]: text_label = category_question_hazard elif self.parent_step in [ self.step_fc_explayer_from_canvas, self.step_fc_explayer_from_browser ]: text_label = category_question_exposure else: text_label = category_question_aggregation self.step_kw_purpose.lblSelectCategory.setText(text_label) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnNext_released(self): """Handle the Next button release. .. note:: This is an automatic Qt slot executed when the Next button is released. """ current_step = self.get_current_step() if current_step == self.step_kw_fields_mapping: try: self.step_kw_fields_mapping.get_field_mapping() except InvalidValidationException as e: display_warning_message_box(self, tr('Invalid Field Mapping'), get_string(e.message)) return if current_step.step_type == STEP_FC: self.impact_function_steps.append(current_step) elif current_step.step_type == STEP_KW: self.keyword_steps.append(current_step) else: LOGGER.debug(current_step.step_type) raise InvalidWizardStep # Save keywords if it's the end of the keyword creation mode if current_step == self.step_kw_summary: self.save_current_keywords() # After any step involving Browser, add selected layer to map canvas if current_step in [ self.step_fc_hazlayer_from_browser, self.step_fc_explayer_from_browser, self.step_fc_agglayer_from_browser ]: if not QgsMapLayerRegistry.instance().mapLayersByName( self.layer.name()): QgsMapLayerRegistry.instance().addMapLayers([self.layer]) # Make the layer visible. Might be hidden by default. See #2925 legend = self.iface.legendInterface() legend.setLayerVisible(self.layer, True) # After the extent selection, save the extent and disconnect signals if current_step == self.step_fc_extent: self.step_fc_extent.write_extent() # Determine the new step to be switched new_step = current_step.get_next_step() if new_step is not None: # Prepare the next tab new_step.set_widgets() else: # Wizard complete self.accept() return self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnBack_released(self): """Handle the Back button release. .. note:: This is an automatic Qt slot executed when the Back button is released. """ current_step = self.get_current_step() if current_step.step_type == STEP_FC: new_step = self.impact_function_steps.pop() elif current_step.step_type == STEP_KW: try: new_step = self.keyword_steps.pop() except IndexError: new_step = self.impact_function_steps.pop() else: raise InvalidWizardStep # set focus to table widgets, as the inactive selection style is gray if new_step == self.step_fc_functions1: self.step_fc_functions1.tblFunctions1.setFocus() if new_step == self.step_fc_functions2: self.step_fc_functions2.tblFunctions2.setFocus() # Re-connect disconnected signals when coming back to the Extent step if new_step == self.step_fc_extent: self.step_fc_extent.set_widgets() # Set Next button label self.pbnNext.setText(tr('Next')) self.pbnNext.setEnabled(True) self.go_to_step(new_step) # prevents actions being handled twice # noinspection PyPep8Naming @pyqtSignature('') def on_pbnHelp_released(self): if self.on_help: self.pbnHelp.setText(tr('Show help')) self.wizard_help.restore_button_state() self.stackedWidget.setCurrentWidget(self.wizard_help.wizard_step) else: self.pbnHelp.setText(tr('Hide help')) self.wizard_help.show_help(self.get_current_step()) self.stackedWidget.setCurrentWidget(self.wizard_help) self.on_help = not self.on_help def get_current_step(self): """Return current step of the wizard. :returns: Current step of the wizard. :rtype: WizardStep instance """ return self.stackedWidget.currentWidget() def get_keywords(self): """Obtain the state of the dialog as a keywords dict. :returns: Keywords reflecting the state of the dialog. :rtype: dict """ keywords = {} inasafe_fields = {} keywords['layer_geometry'] = self.get_layer_geometry_key() if self.step_kw_purpose.selected_purpose(): keywords['layer_purpose'] = self.step_kw_purpose.\ selected_purpose()['key'] if self.step_kw_subcategory.selected_subcategory(): key = self.step_kw_purpose.selected_purpose()['key'] keywords[key] = self.step_kw_subcategory.\ selected_subcategory()['key'] if self.get_layer_geometry_key() == layer_geometry_raster['key']: if self.step_kw_band_selector.selected_band(): keywords['active_band'] = self.step_kw_band_selector.\ selected_band() if keywords['layer_purpose'] == layer_purpose_hazard['key']: if self.step_kw_hazard_category.selected_hazard_category(): keywords['hazard_category'] \ = self.step_kw_hazard_category.\ selected_hazard_category()['key'] if self.step_kw_layermode.selected_layermode(): keywords['layer_mode'] = self.step_kw_layermode.\ selected_layermode()['key'] if self.step_kw_unit.selected_unit(): if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard: key = continuous_hazard_unit['key'] else: key = exposure_unit['key'] keywords[key] = self.step_kw_unit.selected_unit()['key'] if self.step_kw_field.selected_fields(): field_key = self.field_keyword_for_the_layer() inasafe_fields[field_key] = self.step_kw_field.selected_fields() if self.step_kw_classification.selected_classification(): keywords['classification'] = self.step_kw_classification.\ selected_classification()['key'] if keywords['layer_purpose'] == layer_purpose_hazard['key']: multi_classifications = self.step_kw_multi_classifications.\ get_current_state() value_maps = multi_classifications.get('value_maps') if value_maps is not None: keywords['value_maps'] = value_maps thresholds = multi_classifications.get('thresholds') if thresholds is not None: keywords['thresholds'] = thresholds else: if self.step_kw_layermode.selected_layermode(): layer_mode = self.step_kw_layermode.selected_layermode() if layer_mode == layer_mode_continuous: thresholds = self.step_kw_threshold.get_threshold() if thresholds: keywords['thresholds'] = thresholds elif layer_mode == layer_mode_classified: value_map = self.step_kw_classify.selected_mapping() if value_map: keywords['value_map'] = value_map if self.step_kw_source.leSource.text(): keywords['source'] = get_unicode( self.step_kw_source.leSource.text()) if self.step_kw_source.leSource_url.text(): keywords['url'] = get_unicode( self.step_kw_source.leSource_url.text()) if self.step_kw_source.leSource_scale.text(): keywords['scale'] = get_unicode( self.step_kw_source.leSource_scale.text()) if self.step_kw_source.ckbSource_date.isChecked(): keywords['date'] = self.step_kw_source.dtSource_date.dateTime() if self.step_kw_source.leSource_license.text(): keywords['license'] = get_unicode( self.step_kw_source.leSource_license.text()) if self.step_kw_title.leTitle.text(): keywords['title'] = get_unicode(self.step_kw_title.leTitle.text()) inasafe_fields.update(self.step_kw_inasafe_fields.get_inasafe_fields()) inasafe_fields.update( self.step_kw_default_inasafe_fields.get_inasafe_fields()) inasafe_fields.update( self.step_kw_fields_mapping.get_field_mapping()['fields']) if inasafe_fields: keywords['inasafe_fields'] = inasafe_fields inasafe_default_values = {} if keywords['layer_geometry'] == layer_geometry_raster['key']: pass # Notes(IS): Skipped assigning raster inasafe default value for # now. # inasafe_default_values = self.\ # step_kw_inasafe_raster_default_values.\ # get_inasafe_default_values() else: inasafe_default_values.update(self.step_kw_default_inasafe_fields. get_inasafe_default_values()) inasafe_default_values.update( self.step_kw_fields_mapping.get_field_mapping()['values']) if inasafe_default_values: keywords['inasafe_default_values'] = inasafe_default_values return keywords def save_current_keywords(self): """Save keywords to the layer. It will write out the keywords for the current layer. This method is based on the KeywordsDialog class. """ current_keywords = self.get_keywords() try: self.keyword_io.write_keywords(layer=self.layer, keywords=current_keywords) except InaSAFEError, e: error_message = get_error_message(e) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self, tr('InaSAFE'), tr('An error was encountered when saving the following ' 'keywords:\n {error_message}').format( error_message=error_message.to_html())) if self.dock is not None: # noinspection PyUnresolvedReferences self.dock.get_layers() # Save default value to QSetting if current_keywords.get('inasafe_default_values'): for key, value in ( current_keywords['inasafe_default_values'].items()): set_inasafe_default_value_qsetting(self.setting, RECENT, key, value)
def save_hazard_data(self): hazard_geojson = PetaJakartaAPI.get_aggregate_report( self.duration, self.level) if not hazard_geojson: raise PetaJakartaAPIError("Can't access PetaJakarta REST API") with open(self.hazard_path, 'w+') as f: f.write(hazard_geojson) # Save the layer as shp file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr', False) target_name = 'flood_data.shp' self.hazard_path = os.path.join(self.report_path, target_name) QgsVectorFileWriter.writeAsVectorFormat(hazard_layer, self.hazard_path, 'CP1250', None, 'ESRI Shapefile') file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr') hazard_layer.startEditing() field = QgsField('flooded', QVariant.Int) hazard_layer.dataProvider().addAttributes([field]) hazard_layer.commitChanges() idx = hazard_layer.fieldNameIndex('flooded') expression = QgsExpression('count > 0') expression.prepare(hazard_layer.pendingFields()) hazard_layer.startEditing() for feature in hazard_layer.getFeatures(): feature[idx] = expression.evaluate(feature) hazard_layer.updateFeature(feature) hazard_layer.commitChanges() # writing keywords keyword_io = KeywordIO() keywords = { 'field': 'flooded', 'hazard': 'flood', 'hazard_category': 'single_event', 'keyword_version': '3.3', 'layer_geometry': 'polygon', 'layer_mode': 'classified', 'layer_purpose': 'hazard', 'title': 'Flood', 'value_map': '{"wet": [1], "dry": [0]}', 'vector_hazard_classification': 'flood_vector_hazard_classes' } keyword_io.write_keywords(hazard_layer, keywords) # archiving hazard layer with ZipFile(self.hazard_zip_path, 'w') as zf: for root, dirs, files in os.walk(self.report_path): for f in files: _, ext = os.path.splitext(f) if 'flood_data' in f: filename = os.path.join(root, f) zf.write(filename, arcname=f)