def layer_changed(self, layer): """Enable or disable keywords editor icon when active layer changes. :param layer: The layer that is now active. :type layer: QgsMapLayer """ if not layer: enable_keyword_wizard = False elif not hasattr(layer, 'providerType'): enable_keyword_wizard = False elif layer.providerType() == 'wms': enable_keyword_wizard = False elif is_raster_layer(layer) and layer.bandCount() > 1: enable_keyword_wizard = False else: enable_keyword_wizard = True try: if layer: if is_raster_layer(layer): enable_field_mapping_tool = False else: keywords = KeywordIO().read_keywords(layer) layer_purpose = keywords.get('layer_purpose') if not layer_purpose: enable_field_mapping_tool = False 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: # No field group, disable field mapping tool. enable_field_mapping_tool = False else: enable_field_mapping_tool = True else: enable_field_mapping_tool = False except (KeywordNotFoundError, NoKeywordsFoundError, MetadataReadError): # No keywords, disable field mapping tool. enable_field_mapping_tool = False self.action_keywords_wizard.setEnabled(enable_keyword_wizard) self.action_field_mapping.setEnabled(enable_field_mapping_tool)
def layer_changed(self, layer): """Enable or disable keywords editor icon when active layer changes. :param layer: The layer that is now active. :type layer: QgsMapLayer """ if not layer: enable_keyword_wizard = False elif not hasattr(layer, 'providerType'): enable_keyword_wizard = False elif layer.providerType() == 'wms': enable_keyword_wizard = False elif is_raster_layer(layer) and layer.bandCount() > 1: enable_keyword_wizard = False else: enable_keyword_wizard = True try: if layer: if is_raster_layer(layer): enable_field_mapping_tool = False else: keywords = KeywordIO().read_keywords(layer) layer_purpose = keywords.get('layer_purpose') if not layer_purpose: enable_field_mapping_tool = False 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: # No field group, disable field mapping tool. enable_field_mapping_tool = False else: enable_field_mapping_tool = True else: enable_field_mapping_tool = False except (KeywordNotFoundError, NoKeywordsFoundError, MetadataReadError): # No keywords, disable field mapping tool. enable_field_mapping_tool = False self.action_keywords_wizard.setEnabled(enable_keyword_wizard) self.action_field_mapping.setEnabled(enable_field_mapping_tool)
def on_lstFields_itemSelectionChanged(self): """Update field description label and unlock the Next button. .. note:: This is an automatic Qt slot executed when the field selection changes. """ self.clear_further_steps() field = self.selected_field() # Exit if no selection if not field: return # Exit if the selected field comes from a previous wizard run (vector) if is_raster_layer(self.parent.layer): return fields = self.parent.layer.dataProvider().fields() field_index = fields.indexFromName(field) # Exit if the selected field comes from a previous wizard run if field_index < 0: return field_type = fields.field(field).typeName() field_index = fields.indexFromName(self.selected_field()) unique_values = self.parent.layer.uniqueValues(field_index)[0:48] unique_values_str = [ i is not None and unicode(i) or 'NULL' for i in unique_values] if unique_values != self.parent.layer.uniqueValues(field_index): unique_values_str += ['...'] desc = '<br/>%s: %s<br/><br/>' % (self.tr('Field type'), field_type) desc += self.tr('Unique values: %s') % ', '.join(unique_values_str) self.lblDescribeField.setText(desc) # Enable the next buttonlayer_purpose_aggregation self.parent.pbnNext.setEnabled(True)
def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ is_raster = is_raster_layer(self.parent.layer) subcategory = self.parent.step_kw_subcategory.selected_subcategory() has_unit = subcategory.get('units') or subcategory.get( 'continuous_hazard_units') selected_layer_mode = self.selected_layermode() # continuous if selected_layer_mode == layer_mode_continuous and has_unit: new_step = self.parent.step_kw_unit # no unit and vector elif not is_raster: new_step = self.parent.step_kw_field # no unit and raster elif is_raster: new_step = self.parent.step_kw_multi_classifications else: raise InvalidWizardStep return new_step
def on_lstFields_itemSelectionChanged(self): """Update field description label and unlock the Next button. .. note:: This is an automatic Qt slot executed when the field selection changes. """ field = self.selected_field() # Exit if no selection if not field: return # Exit if the selected field comes from a previous wizard run (vector) if is_raster_layer(self.parent.layer): return fields = self.parent.layer.dataProvider().fields() field_index = fields.indexFromName(field) # Exit if the selected field comes from a previous wizard run if field_index < 0: return field_type = fields.field(field).typeName() field_index = fields.indexFromName(self.selected_field()) unique_values = self.parent.layer.uniqueValues(field_index)[0:48] unique_values_str = [ i is not None and unicode(i) or 'NULL' for i in unique_values ] if unique_values != self.parent.layer.uniqueValues(field_index): unique_values_str += ['...'] desc = '<br/>%s: %s<br/><br/>' % (self.tr('Field type'), field_type) desc += self.tr('Unique values: %s') % ', '.join(unique_values_str) self.lblDescribeField.setText(desc) # Enable the next buttonlayer_purpose_aggregation self.parent.pbnNext.setEnabled(True)
def classifications_for_layer(self): """Return a list of valid classifications for a layer. :returns: A list where each value represents a valid classification. :rtype: list """ layer_geometry_id = self.parent.get_layer_geometry_id() layer_mode_id = self.parent.step_kw_layermode.\ selected_layermode()['key'] subcategory_id = self.parent.step_kw_subcategory.\ selected_subcategory()['key'] if self.parent.step_kw_purpose.\ selected_purpose() == layer_purpose_hazard: hazard_category_id = self.parent.step_kw_hazard_category.\ selected_hazard_category()['key'] if is_raster_layer(self.parent.layer): return self.impact_function_manager.\ raster_hazards_classifications_for_layer( subcategory_id, layer_geometry_id, layer_mode_id, hazard_category_id) else: return self.impact_function_manager\ .vector_hazards_classifications_for_layer( subcategory_id, layer_geometry_id, layer_mode_id, hazard_category_id) else: # There are no classifications for exposures defined yet, apart # from postprocessor_classification, processed paralelly return []
def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to. :rtype: WizardStep instance or None """ if self.layer_purpose != layer_purpose_aggregation: subcategory = self.parent.step_kw_subcategory.\ selected_subcategory() else: subcategory = {'key': None} if is_raster_layer(self.parent.layer): return self.parent.step_kw_source # Check if it can go to inasafe field step inasafe_fields = get_non_compulsory_fields( self.layer_purpose['key'], subcategory['key']) if not skip_inasafe_field(self.parent.layer, inasafe_fields): return self.parent.step_kw_inasafe_fields # Check if it can go to inasafe default field step default_inasafe_fields = get_fields( self.layer_purpose['key'], subcategory['key'], replace_null=True, in_group=False ) if default_inasafe_fields: return self.parent.step_kw_default_inasafe_fields # Any other case return self.parent.step_kw_source
def set_widgets(self): """Set widgets on the Classification tab.""" self.clear_further_steps() purpose = self.parent.step_kw_purpose.selected_purpose()['name'] subcategory = self.parent.step_kw_subcategory.\ selected_subcategory()['name'] self.lstClassifications.clear() self.lblDescribeClassification.setText('') self.lblSelectClassification.setText(classification_question % (subcategory, purpose)) classifications = self.classifications_for_layer() for classification in classifications: if not isinstance(classification, dict): classification = definition(classification) item = QListWidgetItem(classification['name'], self.lstClassifications) item.setData(QtCore.Qt.UserRole, classification['key']) self.lstClassifications.addItem(item) # Set values based on existing keywords (if already assigned) geom = 'raster' if is_raster_layer(self.parent.layer) else 'vector' key = '%s_%s_classification' % ( geom, self.parent.step_kw_purpose.selected_purpose()['key']) classification_keyword = self.parent.get_existing_keyword(key) if classification_keyword: classifications = [] for index in xrange(self.lstClassifications.count()): item = self.lstClassifications.item(index) classifications.append(item.data(QtCore.Qt.UserRole)) if classification_keyword in classifications: self.lstClassifications.setCurrentRow( classifications.index(classification_keyword)) self.auto_select_one_item(self.lstClassifications)
def set_widgets(self): """Set widgets on the Classification tab.""" self.clear_further_steps() purpose = self.parent.step_kw_purpose.selected_purpose()['name'] subcategory = self.parent.step_kw_subcategory.\ selected_subcategory()['name'] self.lstClassifications.clear() self.lblDescribeClassification.setText('') self.lblSelectClassification.setText( classification_question % (subcategory, purpose)) classifications = self.classifications_for_layer() for classification in classifications: if not isinstance(classification, dict): classification = definition(classification) item = QListWidgetItem( classification['name'], self.lstClassifications) item.setData(QtCore.Qt.UserRole, classification['key']) self.lstClassifications.addItem(item) # Set values based on existing keywords (if already assigned) geom = 'raster' if is_raster_layer(self.parent.layer) else 'vector' key = '%s_%s_classification' % ( geom, self.parent.step_kw_purpose.selected_purpose()['key']) classification_keyword = self.parent.get_existing_keyword(key) if classification_keyword: classifications = [] for index in xrange(self.lstClassifications.count()): item = self.lstClassifications.item(index) classifications.append(item.data(QtCore.Qt.UserRole)) if classification_keyword in classifications: self.lstClassifications.setCurrentRow( classifications.index(classification_keyword)) self.auto_select_one_item(self.lstClassifications)
def set_widgets(self): """Set widgets on the LayerMode tab.""" self.clear_further_steps() # Set widgets purpose = self.parent.step_kw_purpose.selected_purpose() subcategory = self.parent.step_kw_subcategory.selected_subcategory() layer_mode_question = ( layer_mode_raster_question if is_raster_layer(self.parent.layer) else layer_mode_vector_question) self.lblDescribeLayerMode.setText('') self.lstLayerModes.clear() layer_modes = get_layer_modes(subcategory['key']) if is_raster_layer(self.parent.layer): layer_mode_question = layer_mode_raster_question else: if len(layer_modes) == 2: layer_mode_question = layer_mode_vector_question elif len(layer_modes) == 1: if layer_modes[0]['key'] == 'classified': layer_mode_question = layer_mode_vector_classified_confirm elif layer_modes[0]['key'] == 'continuous': layer_mode_question = layer_mode_vector_continuous_confirm else: layer_mode_question = layer_mode_vector_question self.lblSelectLayerMode.setText( layer_mode_question % (subcategory['name'], purpose['name'])) for layer_mode in layer_modes: item = QListWidgetItem(layer_mode['name'], self.lstLayerModes) item.setData(QtCore.Qt.UserRole, layer_mode['key']) self.lstLayerModes.addItem(item) # Set value to existing keyword or default value layer_mode_keys = [m['key'] for m in layer_modes] layer_mode_keyword = self.parent.get_existing_keyword('layer_mode') if layer_mode_keyword in layer_mode_keys: index = layer_mode_keys.index(layer_mode_keyword) elif layer_mode_continuous['key'] in layer_mode_keys: # Set default value index = layer_mode_keys.index(layer_mode_continuous['key']) else: index = -1 self.lstLayerModes.setCurrentRow(index) self.auto_select_one_item(self.lstLayerModes)
def check_layer(layer, has_geometry=True): """Helper to check layer validity. This function wil; raise InvalidLayerError if the layer is invalid. :param layer: The layer to check. :type layer: QgsMapLayer :param has_geometry: If the layer must have a geometry. True by default. If it's a raster layer, we will no check this parameter. If we do not want to check the geometry type, we can set it to None. :type has_geometry: bool,None :raise: InvalidLayerError :return: Return True if the layer is valid. :rtype: bool """ if is_vector_layer(layer) or is_raster_layer(layer): if not layer.isValid(): raise InvalidLayerError('The layer is invalid : %s' % layer.publicSource()) if is_vector_layer(layer): sub_layers = layer.dataProvider().subLayers() if len(sub_layers) > 1: names = ';'.join(sub_layers) source = layer.source() raise InvalidLayerError( tr('The layer should not have many sublayers : {source} : ' '{names}').format(source=source, names=names)) # We only check the geometry if we have at least one feature. if layer.geometryType() == QgsWkbTypes.UnknownGeometry and ( layer.featureCount() != 0): raise InvalidLayerError( tr('The layer has not a valid geometry type.')) if layer.wkbType() == QgsWkbTypes.Unknown and ( layer.featureCount() != 0): raise InvalidLayerError( tr('The layer has not a valid geometry type.')) if isinstance(has_geometry, bool) and layer.featureCount() != 0: if layer.isSpatial() != has_geometry: raise InvalidLayerError( tr('The layer has not a correct geometry type.')) else: raise InvalidLayerError( tr('The layer is neither a raster nor a vector : {type}').format( type=type(layer))) return True
def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ if is_raster_layer(self.parent.layer): new_step = self.parent.step_kw_band_selector else: new_step = self.parent.step_kw_layermode return new_step
def get_previous_step(self): """Find the proper step when user clicks the Previous button. :returns: The step to be switched to :rtype: WizardStep instance or None """ if is_raster_layer(self.parent.layer): new_step = self.parent.step_kw_classification else: new_step = self.parent.step_kw_field return new_step
def check_layer(layer, has_geometry=True): """Helper to check layer validity. This function wil; raise InvalidLayerError if the layer is invalid. :param layer: The layer to check. :type layer: QgsMapLayer :param has_geometry: If the layer must have a geometry. True by default. If it's a raster layer, we will no check this parameter. If we do not want to check the geometry type, we can set it to None. :type has_geometry: bool :raise: InvalidLayerError :return: Return True if the layer is valid. :rtype: bool """ if is_vector_layer(layer) or is_raster_layer(layer): if not layer.isValid(): raise InvalidLayerError( 'The layer is invalid : %s' % layer.publicSource()) if is_vector_layer(layer): sub_layers = layer.dataProvider().subLayers() if len(sub_layers) > 1: names = ';'.join(sub_layers) source = layer.source() raise InvalidLayerError( tr('The layer should not have many sublayers : {source} : ' '{names}').format(source=source, names=names)) if layer.geometryType() == QGis.UnknownGeometry: raise InvalidLayerError( tr('The layer has not a valid geometry type.')) if layer.wkbType() == QgsWKBTypes.Unknown: raise InvalidLayerError( tr('The layer has not a valid geometry type.')) if isinstance(has_geometry, bool): if layer.hasGeometryType() != has_geometry: raise InvalidLayerError( tr('The layer has not a correct geometry type.')) else: raise InvalidLayerError( tr('The layer is neither a raster nor a vector : {type}').format( type=type(layer))) return True
def check_earthquake_contour_preprocessor(impact_function): """Checker for the contour preprocessor. :param impact_function: Impact function to check. :type impact_function: ImpactFunction :return: If the preprocessor can run. :rtype: bool """ hazard_key = impact_function.hazard.keywords.get('hazard') is_earthquake = hazard_key == hazard_earthquake['key'] if is_earthquake and is_raster_layer(impact_function.hazard): return True else: return False
def add_impact_layers_to_canvas(impact_function, iface): """Helper method to add impact layer to QGIS from impact function. :param impact_function: The impact function used. :type impact_function: ImpactFunction :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface """ layers = impact_function.outputs name = impact_function.name # noinspection PyArgumentList root = QgsProject.instance().layerTreeRoot() group_analysis = root.insertGroup(0, name) group_analysis.setVisible(Qt.Checked) for layer in layers: # noinspection PyArgumentList QgsMapLayerRegistry.instance().addMapLayer(layer, False) layer_node = group_analysis.addLayer(layer) # set layer title if any try: title = layer.keywords['title'] if qgis_version() >= 21800: layer.setName(title) else: layer.setLayerName(title) except KeyError: pass visible_layers = [impact_function.impact.id()] print_atlas = setting('print_atlas_report', False, bool) if print_atlas: visible_layers.append(impact_function.aggregation_summary.id()) # Let's enable only the more detailed layer. See #2925 if layer.id() in visible_layers: layer_node.setVisible(Qt.Checked) elif is_raster_layer(layer): layer_node.setVisible(Qt.Checked) else: layer_node.setVisible(Qt.Unchecked) # we need to set analysis_impacted as an active layer because we need # to get all qgis variables that we need from this layer for # infographic. iface.setActiveLayer(impact_function.analysis_impacted)
def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ if is_raster_layer(self.parent.layer): if self.parent.step_kw_purpose.\ selected_purpose() == layer_purpose_exposure: # Only go to resample for continuous raster exposures new_step = self.parent.step_kw_resample else: new_step = self.parent.step_kw_extrakeywords else: # Currently not used, as we don't have continuous vectors new_step = self.parent.step_kw_field return new_step
def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to. :rtype: WizardStep instance or None """ subcategory = self.parent.step_kw_subcategory.selected_subcategory() is_raster = is_raster_layer(self.parent.layer) has_classifications = get_classifications(subcategory['key']) # Vector if not is_raster: return self.parent.step_kw_field # Raster and has classifications elif has_classifications: return self.parent.step_kw_multi_classifications # else go to source return self.parent.step_kw_source
def geometry_type(layer): """Retrieve the geometry type: point, line, polygon or raster for a layer. :param layer: The layer. :type layer: QgsMapLayer :return: The definition key. :rtype: basestring """ if is_raster_layer(layer): return layer_geometry_raster['key'] elif is_point_layer(layer): return layer_geometry_point['key'] elif is_line_layer(layer): return layer_geometry_line['key'] elif is_polygon_layer(layer): return layer_geometry_polygon['key'] else: return None
def selected_allowresampling(self): """Obtain the allow_resampling state selected by user. .. note:: Returns none if not set or not relevant :returns: Value of the allow_resampling or None for not-set. :rtype: boolean or None """ if not is_raster_layer(self.parent.layer): return None if self.parent.step_kw_purpose.\ selected_purpose() != layer_purpose_exposure: return None # Only return false if checked, otherwise None for not-set. if self.chkAllowResample.isChecked(): return False else: return None
def layer_changed(self, layer): """Enable or disable keywords editor icon when active layer changes. :param layer: The layer that is now active. :type layer: QgsMapLayer """ if not layer: self._disable_keyword_tools() return if not hasattr(layer, 'providerType'): self._disable_keyword_tools() return if layer.providerType() == 'wms': self._disable_keyword_tools() return if is_raster_layer(layer) and layer.bandCount() > 1: self._disable_keyword_tools() return self.action_keywords_wizard.setEnabled(True)
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_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 add_impact_layers_to_canvas(impact_function, iface): """Helper method to add impact layer to QGIS from impact function. :param impact_function: The impact function used. :type impact_function: ImpactFunction :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface """ layers = impact_function.outputs name = impact_function.name # noinspection PyArgumentList root = QgsProject.instance().layerTreeRoot() group_analysis = root.insertGroup(0, name) group_analysis.setVisible(Qt.Checked) for layer in layers: # noinspection PyArgumentList QgsMapLayerRegistry.instance().addMapLayer(layer, False) layer_node = group_analysis.addLayer(layer) # set layer title if any try: title = layer.keywords['title'] if qgis_version() >= 21800: layer.setName(title) else: layer.setLayerName(title) except KeyError: pass # Let's enable only the more detailed layer. See #2925 if layer.id() == impact_function.impact.id(): layer_node.setVisible(Qt.Checked) iface.setActiveLayer(layer) elif is_raster_layer(layer): layer_node.setVisible(Qt.Checked) else: layer_node.setVisible(Qt.Unchecked)
def set_wizard_step_description(self): """Set the text for description.""" subcategory = self.parent.step_kw_subcategory.selected_subcategory() field = self.parent.step_kw_field.selected_fields() is_raster = is_raster_layer(self.parent.layer) if is_raster: if self.layer_mode == layer_mode_continuous: text_label = multiple_continuous_hazard_classifications_raster else: text_label = multiple_classified_hazard_classifications_raster # noinspection PyAugmentAssignment text_label = text_label % ( subcategory['name'], self.layer_purpose['name']) else: if self.layer_mode == layer_mode_continuous: text_label = multiple_continuous_hazard_classifications_vector else: text_label = multiple_classified_hazard_classifications_vector # noinspection PyAugmentAssignment text_label = text_label % ( subcategory['name'], self.layer_purpose['name'], field) self.multi_classifications_label.setText(text_label)
def get_next_step(self): """Find the proper step when user clicks the Next button. :returns: The step to be switched to :rtype: WizardStep instance or None """ if self.parent.step_kw_layermode.\ selected_layermode() == layer_mode_classified: if is_point_layer(self.parent.layer) \ and self.parent.step_kw_purpose.\ selected_purpose() == layer_purpose_hazard: # Skip FIELD and CLASSIFICATION for point volcanos new_step = self.parent.step_kw_extrakeywords elif self.parent.step_kw_classification.\ classifications_for_layer(): new_step = self.parent.step_kw_classification elif is_raster_layer(self.parent.layer): new_step = self.parent.step_kw_extrakeywords else: new_step = self.parent.step_kw_field else: # CONTINUOUS DATA, ALL GEOMETRIES new_step = self.parent.step_kw_unit return new_step
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 setup_left_panel(self): """Setup the UI for left panel. Generate all exposure, combobox, and edit button. """ hazard = self.parent.step_kw_subcategory.selected_subcategory() left_panel_heading = QLabel(tr('Classifications')) left_panel_heading.setFont(big_font) self.left_layout.addWidget(left_panel_heading) inner_left_layout = QGridLayout() row = 0 for exposure in exposure_all: special_case = False # Filter out unsupported exposure for the hazard if exposure in hazard['disabled_exposures']: # Remove from the storage if the exposure is disabled if self.layer_mode == layer_mode_continuous: if exposure['key'] in self.thresholds: self.thresholds.pop(exposure['key']) else: if exposure['key'] in self.value_maps: self.value_maps.pop(exposure['key']) continue # Trick for EQ raster for population #3853 if exposure == exposure_population and hazard == hazard_earthquake: if is_raster_layer(self.parent.layer): if self.layer_mode == layer_mode_continuous: self.use_default_thresholds = True special_case = True # Set classification for EQ Raster for Population self.thresholds[exposure_population['key']] = { earthquake_mmi_scale['key']: { 'classes': default_classification_thresholds( earthquake_mmi_scale), 'active': True } } # Add label # Hazard on Exposure Classifications label = tr( '{hazard_name} on {exposure_name} Classifications').format( hazard_name=hazard['name'], exposure_name=exposure['name'] ) exposure_label = QLabel(label) # Add combo box exposure_combo_box = QComboBox() hazard_classifications = hazard.get('classifications') exposure_combo_box.addItem(tr('No classifications')) exposure_combo_box.setItemData( 0, None, Qt.UserRole) current_index = 0 i = 0 # Iterate through all available hazard classifications for hazard_classification in hazard_classifications: # Skip if the classification is not for the exposure if 'exposures' in hazard_classification: if exposure not in hazard_classification['exposures']: continue exposure_combo_box.addItem(hazard_classification['name']) exposure_combo_box.setItemData( i + 1, hazard_classification, Qt.UserRole) if self.layer_mode == layer_mode_continuous: current_hazard_classifications = self.thresholds.get( exposure['key']) else: current_hazard_classifications = self.value_maps.get( exposure['key']) if current_hazard_classifications: current_hazard_classification = \ current_hazard_classifications.get( hazard_classification['key']) if current_hazard_classification: is_active = current_hazard_classification.get('active') if is_active: current_index = i + 1 i += 1 # Set current classification exposure_combo_box.setCurrentIndex(current_index) # Add edit button exposure_edit_button = QPushButton(tr('Edit')) # For special case. Raster EQ on Population. if special_case: mmi_index = exposure_combo_box.findText( earthquake_mmi_scale['name']) exposure_combo_box.setCurrentIndex(mmi_index) exposure_combo_box.setEnabled(False) exposure_edit_button.setEnabled(False) tool_tip_message = tr( 'InaSAFE use default classification for Raster Earthquake ' 'hazard on population.') exposure_label.setToolTip(tool_tip_message) exposure_combo_box.setToolTip(tool_tip_message) exposure_edit_button.setToolTip(tool_tip_message) else: if current_index == 0: # Disable if there is no classification chosen. exposure_edit_button.setEnabled(False) exposure_edit_button.clicked.connect( partial(self.edit_button_clicked, edit_button=exposure_edit_button, exposure_combo_box=exposure_combo_box, exposure=exposure)) exposure_combo_box.currentIndexChanged.connect( partial( self.classifications_combo_box_changed, exposure=exposure, exposure_combo_box=exposure_combo_box, edit_button=exposure_edit_button)) # Arrange in layout inner_left_layout.addWidget(exposure_label, row, 0) inner_left_layout.addWidget(exposure_combo_box, row, 1) inner_left_layout.addWidget(exposure_edit_button, row, 2) # Adding to step's attribute self.exposures.append(exposure) self.exposure_combo_boxes.append(exposure_combo_box) self.exposure_edit_buttons.append(exposure_edit_button) self.exposure_labels.append(label) if special_case: self.special_case_index = len(self.exposures) - 1 row += 1 self.left_layout.addLayout(inner_left_layout) # To push the inner_left_layout up self.left_layout.addStretch(1)
def layer_description_html(layer, keywords=None): """Form a html description of a given layer based on the layer parameters and keywords if provided :param layer: The layer to get the description :type layer: QgsMapLayer :param keywords: The layer keywords :type keywords: None, dict :returns: The html description in tabular format, ready to use in a label or tool tip. :rtype: str """ if keywords and 'keyword_version' in keywords: keyword_version = str(keywords['keyword_version']) else: keyword_version = None if (keywords and keyword_version and is_keyword_version_supported(keyword_version)): # The layer has valid keywords purpose = keywords.get('layer_purpose') if purpose == layer_purpose_hazard['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Hazard'), keywords.get(purpose)) unit = keywords.get('continuous_hazard_unit') elif purpose == layer_purpose_exposure['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Exposure'), keywords.get(purpose)) unit = keywords.get('exposure_unit') else: subcategory = '' unit = None if keywords.get('layer_mode') == layer_mode_classified['key']: unit = tr('classified data') if unit: unit = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (tr('Unit'), unit) description = """ <table border="0" width="100%%"> <tr><td><b>%s</b>: </td><td>%s</td></tr> <tr><td><b>%s</b>: </td><td>%s</td></tr> %s %s <tr><td><b>%s</b>: </td><td>%s</td></tr> </table> """ % (tr('Title'), keywords.get('title'), tr('Purpose'), keywords.get('layer_purpose'), subcategory, unit, tr('Source'), keywords.get('source')) elif keywords: # The layer has keywords, but the version is wrong layer_version = keyword_version or tr('No Version') description = tr( 'Your layer\'s keyword\'s version ({layer_version}) does not ' 'match with your InaSAFE version ({inasafe_version}). If you wish ' 'to use it as an exposure, hazard, or aggregation layer in an ' 'analysis, please update the keywords. Click Next if you want to ' 'assign keywords now.').format(layer_version=layer_version, inasafe_version=get_version()) else: # The layer is keywordless if is_point_layer(layer): geom_type = layer_geometry_point['key'] elif is_polygon_layer(layer): geom_type = layer_geometry_polygon['key'] else: geom_type = layer_geometry_line['key'] # hide password in the layer source source = layer.publicSource() description = """ %s<br/><br/> <b>%s</b>: %s<br/> <b>%s</b>: %s<br/><br/> %s """ % (tr('This layer has no valid keywords assigned'), tr('SOURCE'), source, tr('TYPE'), is_raster_layer(layer) and 'raster' or 'vector (%s)' % geom_type, tr('In the next step you will be able' + ' to assign keywords to this layer.')) return description
def unsuitable_layer_description_html( self, layer, layer_purpose, keywords=None): """Form a html description of a given non-matching layer based on the currently selected impact function requirements vs layer\'s parameters and keywords if provided, as :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: string :param keywords: The layer keywords :type keywords: None, dict :returns: The html description in tabular format, ready to use in a label or tool tip. :rtype: str """ def emphasize(str1, str2): """ Compare two strings and emphasize both if differ """ if str1 != str2: str1 = '<i>%s</i>' % str1 str2 = '<i>%s</i>' % str2 return (str1, str2) # Get allowed subcategory and layer_geometry from IF constraints h, e, hc, ec = self.parent.selected_impact_function_constraints() imfunc = self.parent.step_fc_function.selected_function() lay_req = imfunc['layer_requirements'][layer_purpose] if layer_purpose == layer_purpose_hazard['key']: layer_purpose_key_name = layer_purpose_hazard['name'] req_subcategory = h['key'] req_geometry = hc['key'] elif layer_purpose == layer_purpose_exposure['key']: layer_purpose_key_name = layer_purpose_exposure['name'] req_subcategory = e['key'] req_geometry = ec['key'] else: layer_purpose_key_name = layer_purpose_aggregation['name'] req_subcategory = '' # For aggregation layers, only accept polygons req_geometry = layer_geometry_polygon['key'] req_layer_mode = lay_req['layer_mode']['key'] lay_geometry = self.parent.get_layer_geometry_id(layer) lay_purpose = ' -' lay_subcategory = ' -' lay_layer_mode = ' -' if keywords: if 'layer_purpose' in keywords: lay_purpose = keywords['layer_purpose'] if layer_purpose in keywords: lay_subcategory = keywords[layer_purpose] if 'layer_mode' in keywords: lay_layer_mode = keywords['layer_mode'] lay_geometry, req_geometry = emphasize(lay_geometry, req_geometry) lay_purpose, layer_purpose = emphasize(lay_purpose, layer_purpose) lay_subcategory, req_subcategory = emphasize( lay_subcategory, req_subcategory) lay_layer_mode, req_layer_mode = emphasize( lay_layer_mode, req_layer_mode) # Classification classification_row = '' if (lay_req['layer_mode'] == layer_mode_classified and layer_purpose == layer_purpose_hazard['key']): # Determine the keyword key for the classification classification_obj = ( raster_hazard_classification if is_raster_layer(layer) else vector_hazard_classification) classification_key = classification_obj['key'] classification_key_name = classification_obj['name'] classification_keys = classification_key + 's' if classification_keys in lay_req: allowed_classifications = [ c['key'] for c in lay_req[classification_keys]] req_classifications = ', '.join(allowed_classifications) lay_classification = ' -' if classification_key in keywords: lay_classification = keywords[classification_key] if lay_classification not in allowed_classifications: # We already know we want to emphasize them and the test # inside the function will always pass. lay_classification, req_classifications = emphasize( lay_classification, req_classifications) classification_row = ( ( '<tr><td><b>%s</b></td>' + '<td>%s</td><td>%s</td></tr>') % ( classification_key_name, lay_classification, req_classifications)) # Unit units_row = '' if lay_req['layer_mode'] == layer_mode_continuous: # Determine the keyword key for the unit unit_obj = ( continuous_hazard_unit if layer_purpose == layer_purpose_hazard['key'] else exposure_unit) unit_key = unit_obj['key'] unit_key_name = unit_obj['name'] unit_keys = unit_key + 's' if unit_keys in lay_req: allowed_units = [c['key'] for c in lay_req[unit_keys]] req_units = ', '.join(allowed_units) lay_unit = ' -' if unit_key in keywords: lay_unit = keywords[unit_key] if lay_unit not in allowed_units: # We already know we want to emphasize them and the test # inside the function will always pass. lay_unit, req_units = emphasize(lay_unit, req_units) units_row = ( ( '<tr><td><b>%s</b></td>' + '<td>%s</td><td>%s</td></tr>') % (unit_key_name, lay_unit, req_units)) html = ''' <table border="0" width="100%%" cellpadding="2"> <tr><td width="33%%"></td> <td width="33%%"><b>%s</b></td> <td width="33%%"><b>%s</b></td> </tr> <tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr> <tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr> <tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr> <tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr> %s %s </table> ''' % (self.tr('Layer'), self.tr('Required'), safe.definitions.layer_geometry['name'], lay_geometry, req_geometry, safe.definitions.layer_purpose['name'], lay_purpose, layer_purpose, layer_purpose_key_name, lay_subcategory, req_subcategory, safe.definitions.layer_mode['name'], lay_layer_mode, req_layer_mode, classification_row, units_row) return html
def setup_value_mapping_panels(self, classification): """Setup value mapping panel in the right panel. :param classification: Classification definition. :type classification: dict """ # Set text in the label layer_purpose = self.parent.step_kw_purpose.selected_purpose() layer_subcategory = self.parent.step_kw_subcategory. \ selected_subcategory() if is_raster_layer(self.parent.layer): description_text = classify_raster_question % ( layer_subcategory['name'], layer_purpose['name'], classification['name']) dataset = gdal.Open(self.parent.layer.source(), GA_ReadOnly) active_band = self.parent.step_kw_band_selector.selected_band() unique_values = numpy.unique(numpy.array( dataset.GetRasterBand(active_band).ReadAsArray())) field_type = 0 # Convert datatype to a json serializable type if numpy.issubdtype(unique_values.dtype, float): unique_values = [float(i) for i in unique_values] else: unique_values = [int(i) for i in unique_values] else: field = self.parent.step_kw_field.selected_fields() field_index = self.parent.layer.dataProvider().fields(). \ indexFromName(field) field_type = self.parent.layer.dataProvider(). \ fields()[field_index].type() description_text = classify_vector_question % ( layer_subcategory['name'], layer_purpose['name'], classification['name'], field.upper()) unique_values = self.parent.layer.uniqueValues(field_index) # Set description description_label = QLabel(description_text) description_label.setWordWrap(True) self.right_layout.addWidget(description_label) self.list_unique_values = QListWidget() self.list_unique_values.setDragDropMode(QAbstractItemView.DragDrop) self.list_unique_values.setDefaultDropAction(Qt.MoveAction) self.tree_mapping_widget = QTreeWidget() self.tree_mapping_widget.setDragDropMode(QAbstractItemView.DragDrop) self.tree_mapping_widget.setDefaultDropAction(Qt.MoveAction) self.tree_mapping_widget.header().hide() self.tree_mapping_widget.itemChanged.connect( self.update_dragged_item_flags) value_mapping_layout = QHBoxLayout() value_mapping_layout.addWidget(self.list_unique_values) value_mapping_layout.addWidget(self.tree_mapping_widget) self.right_layout.addLayout(value_mapping_layout) default_classes = classification['classes'] # Assign unique values to classes (according to default) unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() for unique_value in unique_values: if unique_value is None or isinstance( unique_value, QPyNullVariant): # Don't classify features with NULL value continue # Capitalization of the value and removing '_' (raw OSM data). value_as_string = unicode(unique_value).upper().replace('_', ' ') assigned = False for default_class in default_classes: if 'string_defaults' in default_class: condition_1 = ( field_type > 9 and value_as_string in [ c.upper() for c in default_class['string_defaults']]) else: condition_1 = False condition_2 = ( field_type < 10 and 'numeric_default_min' in default_class and 'numeric_default_max' in default_class and ( default_class['numeric_default_min'] <= unique_value < default_class['numeric_default_max'])) if condition_1 or condition_2: assigned_values[default_class['key']] += [unique_value] assigned = True if not assigned: # add to unassigned values list otherwise unassigned_values += [unique_value] self.populate_classified_values( unassigned_values, assigned_values, default_classes, self.list_unique_values, self.tree_mapping_widget ) # Current value map for exposure and classification available_classifications = self.value_maps.get( self.active_exposure['key']) if not available_classifications: return # Get active one current_classification = available_classifications.get( classification['key']) if not current_classification: return current_value_map = current_classification.get('classes') if not current_value_map: return unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() for unique_value in unique_values: if unique_value is None or isinstance( unique_value, QPyNullVariant): # Don't classify features with NULL value continue # check in value map assigned = False for key, value_list in current_value_map.items(): if unique_value in value_list and key in assigned_values: assigned_values[key] += [unique_value] assigned = True if not assigned: unassigned_values += [unique_value] self.populate_classified_values( unassigned_values, assigned_values, default_classes, self.list_unique_values, self.tree_mapping_widget )
def set_widgets(self): """Set widgets on the Threshold tab.""" clear_layout(self.gridLayoutThreshold) # Set text in the label layer_purpose = self.parent.step_kw_purpose.selected_purpose() layer_subcategory = self.parent.step_kw_subcategory.\ selected_subcategory() classification = self.parent.step_kw_classification. \ selected_classification() if is_raster_layer(self.parent.layer): statistics = self.parent.layer.dataProvider().bandStatistics( 1, QgsRasterBandStats.All, self.parent.layer.extent(), 0) text = continuous_raster_question % ( layer_purpose['name'], layer_subcategory['name'], classification['name'], statistics.minimumValue, statistics.maximumValue) else: field_name = self.parent.step_kw_field.selected_fields() field_index = self.parent.layer.fields().lookupField(field_name) min_value_layer = self.parent.layer.minimumValue(field_index) max_value_layer = self.parent.layer.maximumValue(field_index) text = continuous_vector_question % ( layer_purpose['name'], layer_subcategory['name'], field_name, classification['name'], min_value_layer, max_value_layer) self.lblThreshold.setText(text) thresholds = self.parent.get_existing_keyword('thresholds') selected_unit = self.parent.step_kw_unit.selected_unit()['key'] self.classes = OrderedDict() classes = classification.get('classes') # Sort by value, put the lowest first classes = sorted(classes, key=lambda k: k['value']) for i, the_class in enumerate(classes): class_layout = QHBoxLayout() # Class label class_label = QLabel(the_class['name']) # Min label min_label = QLabel(tr('Min >')) # Min value as double spin min_value_input = QDoubleSpinBox() # TODO(IS) We can set the min and max depends on the unit, later min_value_input.setMinimum(0) min_value_input.setMaximum(999999) if thresholds.get(the_class['key']): min_value_input.setValue(thresholds[the_class['key']][0]) else: default_min = the_class['numeric_default_min'] if isinstance(default_min, dict): default_min = the_class[ 'numeric_default_min'][selected_unit] min_value_input.setValue(default_min) min_value_input.setSingleStep(0.1) # Max label max_label = QLabel(tr('Max <=')) # Max value as double spin max_value_input = QDoubleSpinBox() # TODO(IS) We can set the min and max depends on the unit, later max_value_input.setMinimum(0) max_value_input.setMaximum(999999) if thresholds.get(the_class['key']): max_value_input.setValue(thresholds[the_class['key']][1]) else: default_max = the_class['numeric_default_max'] if isinstance(default_max, dict): default_max = the_class[ 'numeric_default_max'][selected_unit] max_value_input.setValue(default_max) max_value_input.setSingleStep(0.1) # Add to class_layout class_layout.addWidget(min_label) class_layout.addWidget(min_value_input) # class_layout.addStretch(1) class_layout.addWidget(max_label) class_layout.addWidget(max_value_input) # Add to grid_layout self.gridLayoutThreshold.addWidget(class_label, i, 0) self.gridLayoutThreshold.addLayout(class_layout, i, 1) self.classes[the_class['key']] = [min_value_input, max_value_input] self.gridLayoutThreshold.setSpacing(0) def min_max_changed(index, the_string): """Slot when min or max value change. :param index: The index of the double spin. :type index: int :param the_string: The flag to indicate the min or max value. :type the_string: str """ if the_string == 'Max value': current_max_value = list(self.classes.values())[index][1] target_min_value = list(self.classes.values())[index + 1][0] if current_max_value.value() != target_min_value.value(): target_min_value.setValue(current_max_value.value()) elif the_string == 'Min value': current_min_value = list(self.classes.values())[index][0] target_max_value = list(self.classes.values())[index - 1][1] if current_min_value.value() != target_max_value.value(): target_max_value.setValue(current_min_value.value()) # Set behaviour for k, v in list(self.classes.items()): index = list(self.classes.keys()).index(k) if index < len(self.classes) - 1: # Max value changed v[1].valueChanged.connect(partial( min_max_changed, index=index, the_string='Max value')) if index > 0: # Min value v[0].valueChanged.connect(partial( min_max_changed, index=index, the_string='Min value'))
def set_widgets(self): """Set widgets on the Classify tab.""" purpose = self.parent.step_kw_purpose.selected_purpose() subcategory = self.parent.step_kw_subcategory.selected_subcategory() classification = self.parent.step_kw_classification.\ selected_classification() classification_name = classification['name'] if is_raster_layer(self.parent.layer): self.lblClassify.setText(classify_raster_question % ( subcategory['name'], purpose['name'], classification_name)) dataset = gdal.Open(self.parent.layer.source(), GA_ReadOnly) active_band = self.parent.step_kw_band_selector.selected_band() unique_values = numpy.unique(numpy.array( dataset.GetRasterBand(active_band).ReadAsArray())) field_type = 0 # Convert datatype to a json serializable type if numpy.issubdtype(unique_values.dtype, float): unique_values = [float(i) for i in unique_values] else: unique_values = [int(i) for i in unique_values] else: field = self.parent.step_kw_field.selected_fields() field_index = self.parent.layer.dataProvider().fields().\ indexFromName(field) field_type = self.parent.layer.dataProvider().\ fields()[field_index].type() self.lblClassify.setText(classify_vector_question % ( subcategory['name'], purpose['name'], classification_name, field.upper())) unique_values = self.parent.layer.uniqueValues(field_index) clean_unique_values = [] for unique_value in unique_values: if unique_value is None or isinstance( unique_value, QPyNullVariant): # Don't classify features with NULL value continue clean_unique_values.append(unique_value) # get default classes default_classes = classification['classes'] if classification['key'] == 'data_driven_classes': for unique_value in clean_unique_values: name = unicode(unique_value).upper().replace('_', ' ') default_class = {'key': unique_value, 'name': name, # 'description': tr('Settlement'), 'string_defaults': [name]} default_classes.append(default_class) # Assign unique values to classes (according to default) unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() for unique_value in clean_unique_values: # Capitalization of the value and removing '_' (raw OSM data). value_as_string = unicode(unique_value).upper().replace('_', ' ') assigned = False for default_class in default_classes: if 'string_defaults' in default_class: condition_1 = ( field_type > 9 and value_as_string in [ c.upper() for c in default_class['string_defaults']]) else: condition_1 = False condition_2 = ( field_type < 10 and 'numeric_default_min' in default_class and 'numeric_default_max' in default_class and ( default_class['numeric_default_min'] <= unique_value <= default_class['numeric_default_max'])) if condition_1 or condition_2: assigned_values[default_class['key']] += [unique_value] assigned = True if not assigned: # add to unassigned values list otherwise unassigned_values += [unique_value] self.populate_classified_values( unassigned_values, assigned_values, default_classes) # Overwrite assigned values according to existing keyword (if present). # Note the default_classes and unique_values are already loaded! value_map = self.parent.get_existing_keyword('value_map') value_map_classification_name = self.parent.get_existing_keyword( 'classification') # Do not continue if there is no value_map in existing keywords if (value_map is None or value_map_classification_name != classification['key']): return # Do not continue if user selected different field field_keyword = self.parent.field_keyword_for_the_layer() field = self.parent.get_existing_keyword('inasafe_fields').get( field_keyword) if (not is_raster_layer(self.parent.layer) and field != self.parent.step_kw_field.selected_fields()): return unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() if isinstance(value_map, str): try: value_map = json.loads(value_map) except ValueError: return for unique_value in clean_unique_values: # check in value map assigned = False for key, value_list in value_map.iteritems(): if unique_value in value_list and key in assigned_values: assigned_values[key] += [unique_value] assigned = True if not assigned: unassigned_values += [unique_value] self.populate_classified_values( unassigned_values, assigned_values, default_classes)
def set_widgets(self): """Set widgets on the Classify tab.""" purpose = self.parent.step_kw_purpose.selected_purpose() subcategory = self.parent.step_kw_subcategory.selected_subcategory() classification = self.parent.step_kw_classification.\ selected_classification() classification_name = classification['name'] if is_raster_layer(self.parent.layer): self.lblClassify.setText( classify_raster_question % (subcategory['name'], purpose['name'], classification_name)) dataset = gdal.Open(self.parent.layer.source(), GA_ReadOnly) active_band = self.parent.step_kw_band_selector.selected_band() unique_values = numpy.unique( numpy.array(dataset.GetRasterBand(active_band).ReadAsArray())) field_type = 0 # Convert datatype to a json serializable type if numpy.issubdtype(unique_values.dtype, float): unique_values = [float(i) for i in unique_values] else: unique_values = [int(i) for i in unique_values] else: field = self.parent.step_kw_field.selected_fields() field_index = self.parent.layer.fields().indexFromName(field) field_type = self.parent.layer.fields()[field_index].type() self.lblClassify.setText(classify_vector_question % (subcategory['name'], purpose['name'], classification_name, field.upper())) unique_values = self.parent.layer.uniqueValues(field_index) clean_unique_values = [] for unique_value in unique_values: if (unique_value is None or (hasattr(unique_value, 'isNull') and unique_value.isNull())): # Don't classify features with NULL value continue clean_unique_values.append(unique_value) # get default classes default_classes = deepcopy(classification['classes']) if classification['key'] == data_driven_classes['key']: for unique_value in clean_unique_values: name = str(unique_value).upper().replace('_', ' ') default_class = { 'key': unique_value, 'name': name, # 'description': tr('Settlement'), 'string_defaults': [name] } default_classes.append(default_class) # Assign unique values to classes (according to default) unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() for unique_value in clean_unique_values: # Capitalization of the value and removing '_' (raw OSM data). value_as_string = str(unique_value).upper().replace('_', ' ') assigned = False for default_class in default_classes: if 'string_defaults' in default_class: # To make it case insensitive upper_string_defaults = [ c.upper() for c in default_class['string_defaults'] ] in_string_default = (value_as_string in upper_string_defaults) condition_1 = field_type > 9 and in_string_default else: condition_1 = False condition_2 = ( field_type < 10 and 'numeric_default_min' in default_class and 'numeric_default_max' in default_class and (default_class['numeric_default_min'] <= unique_value <= default_class['numeric_default_max'])) if condition_1 or condition_2: assigned_values[default_class['key']] += [unique_value] assigned = True if not assigned: # add to unassigned values list otherwise unassigned_values += [unique_value] self.populate_classified_values(unassigned_values, assigned_values, default_classes) # Overwrite assigned values according to existing keyword (if present). # Note the default_classes and unique_values are already loaded! value_map = self.parent.get_existing_keyword('value_map') value_map_classification_name = self.parent.get_existing_keyword( 'classification') # Do not continue if there is no value_map in existing keywords if (value_map is None or value_map_classification_name != classification['key']): return # Do not continue if user selected different field field_keyword = self.parent.field_keyword_for_the_layer() field = self.parent.get_existing_keyword('inasafe_fields').get( field_keyword) if (not is_raster_layer(self.parent.layer) and field != self.parent.step_kw_field.selected_fields()): return unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() if isinstance(value_map, str): try: value_map = json.loads(value_map) except ValueError: return for unique_value in clean_unique_values: # check in value map assigned = False for key, value_list in list(value_map.items()): if unique_value in value_list and key in assigned_values: assigned_values[key] += [unique_value] assigned = True if not assigned: unassigned_values += [unique_value] self.populate_classified_values(unassigned_values, assigned_values, default_classes)
def layer_description_html(layer, keywords=None): """Form a html description of a given layer based on the layer parameters and keywords if provided :param layer: The layer to get the description :type layer: QgsMapLayer :param keywords: The layer keywords :type keywords: None, dict :returns: The html description in tabular format, ready to use in a label or tool tip. :rtype: str """ if keywords and 'keyword_version' in keywords: keyword_version = str(keywords['keyword_version']) else: keyword_version = None if (keywords and keyword_version and is_keyword_version_supported(keyword_version)): # The layer has valid keywords purpose = keywords.get('layer_purpose') if purpose == layer_purpose_hazard['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Hazard'), keywords.get(purpose)) unit = keywords.get('continuous_hazard_unit') elif purpose == layer_purpose_exposure['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Exposure'), keywords.get(purpose)) unit = keywords.get('exposure_unit') else: subcategory = '' unit = None if keywords.get('layer_mode') == layer_mode_classified['key']: unit = tr('classified data') if unit: unit = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Unit'), unit) desc = """ <table border="0" width="100%%"> <tr><td><b>%s</b>: </td><td>%s</td></tr> <tr><td><b>%s</b>: </td><td>%s</td></tr> %s %s <tr><td><b>%s</b>: </td><td>%s</td></tr> </table> """ % (tr('Title'), keywords.get('title'), tr('Purpose'), keywords.get('layer_purpose'), subcategory, unit, tr('Source'), keywords.get('source')) elif keywords: # The layer has keywords, but the version is wrong layer_version = keyword_version or tr('No Version') desc = tr( 'Your layer\'s keyword\'s version ({layer_version}) does not ' 'match with your InaSAFE version ({inasafe_version}). If you wish ' 'to use it as an exposure, hazard, or aggregation layer in an ' 'analysis, please update the keywords. Click Next if you want to ' 'assign keywords now.').format( layer_version=layer_version, inasafe_version=get_version()) else: # The layer is keywordless if is_point_layer(layer): geom_type = 'point' elif is_polygon_layer(layer): geom_type = 'polygon' else: geom_type = 'line' # hide password in the layer source source = layer.publicSource() desc = """ %s<br/><br/> <b>%s</b>: %s<br/> <b>%s</b>: %s<br/><br/> %s """ % (tr('This layer has no valid keywords assigned'), tr('SOURCE'), source, tr('TYPE'), is_raster_layer(layer) and 'raster' or 'vector (%s)' % geom_type, tr('In the next step you will be able' + ' to assign keywords to this layer.')) return desc
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 calculate_zonal_stats(raster_layer, polygon_layer): """Calculate zonal statics given two layers. :param raster_layer: A QGIS raster layer. :type raster_layer: QgsRasterLayer, QgsMapLayer :param polygon_layer: A QGIS vector layer containing polygons. :type polygon_layer: QgsVectorLayer, QgsMapLayer :returns: A data structure containing sum, mean, min, max, count of raster values for each polygonal area. :rtype: dict :raises: InvalidParameterError, InvalidGeometryError Note: * InvalidParameterError if incorrect inputs are received. * InvalidGeometryError if none geometry is found during calculations. * Any other exceptions are propagated. Example of output data structure: { 1: {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}, 2: {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}, 3 {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}} The key in the outer dict is the feature id .. note:: This is a python port of the zonal stats implementation in QGIS . See https://github.com/qgis/Quantum-GIS/blob/master/src/analysis/ vector/qgszonalstatistics.cpp .. note:: Currently not projection checks are made to ensure that both layers are in the same CRS - we assume they are. """ if not is_polygon_layer(polygon_layer): raise InvalidParameterError(tr( 'Zonal stats needs a polygon layer in order to compute ' 'statistics.')) if not is_raster_layer(raster_layer): raise InvalidParameterError(tr( 'Zonal stats needs a raster layer in order to compute statistics.' )) LOGGER.debug('Calculating zonal stats for:') LOGGER.debug('Raster: %s' % raster_layer.source()) LOGGER.debug('Vector: %s' % polygon_layer.source()) results = {} raster_source = raster_layer.source() feature_id = gdal.Open(str(raster_source), gdal.GA_ReadOnly) geo_transform = feature_id.GetGeoTransform() columns = feature_id.RasterXSize rows = feature_id.RasterYSize # Get first band. band = feature_id.GetRasterBand(1) no_data = band.GetNoDataValue() # print 'No data %s' % no_data cell_size_x = geo_transform[1] if cell_size_x < 0: cell_size_x = -cell_size_x cell_size_y = geo_transform[5] if cell_size_y < 0: cell_size_y = -cell_size_y raster_box = QgsRectangle( geo_transform[0], geo_transform[3] - (cell_size_y * rows), geo_transform[0] + (cell_size_x * columns), geo_transform[3]) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList raster_geometry = QgsGeometry.fromRect(raster_box) # Get vector layer provider = polygon_layer.dataProvider() if provider is None: message = tr( 'Could not obtain data provider from layer "%s"') % ( polygon_layer.source()) raise Exception(message) request = QgsFeatureRequest() crs = osr.SpatialReference() crs.ImportFromProj4(str(polygon_layer.crs().toProj4())) count = 0 for myFeature in provider.getFeatures(request): geometry = myFeature.geometry() if geometry is None: message = tr( 'Feature %d has no geometry or geometry is invalid') % ( myFeature.id()) raise InvalidGeometryError(message) count += 1 feature_box = geometry.boundingBox().intersect(raster_box) print 'NEW AGGR: %s' % myFeature.id() # print 'Raster Box: %s' % raster_box.asWktCoordinates() # print 'Feature Box: %s' % feature_box.asWktCoordinates() offset_x, offset_y, cells_x, cells_y = intersection_box( raster_box, feature_box, cell_size_x, cell_size_y) # If the poly does not intersect the raster just continue if None in [offset_x, offset_y, cells_x, cells_y]: continue # avoid access to cells outside of the raster (may occur because of # rounding) if (offset_x + cells_x) > columns: offset_x = columns - offset_x if (offset_y + cells_y) > rows: cells_y = rows - offset_y intersected_geometry = raster_geometry.intersection(geometry) geometry_sum, count = numpy_stats( band, intersected_geometry, geo_transform, no_data, crs) if count <= 1: # The cell resolution is probably larger than the polygon area. # We switch to precise pixel - polygon intersection in this case geometry_sum, count = precise_stats( band, geometry, offset_x, offset_y, cells_x, cells_y, cell_size_x, cell_size_y, raster_box, no_data) # print geometry_sum, count if count == 0: mean = 0 else: mean = geometry_sum / count results[myFeature.id()] = { 'sum': geometry_sum, 'count': count, 'mean': mean} # noinspection PyUnusedLocal feature_id = None # Close return results
def set_widgets(self): """Set widgets on the Classify tab.""" purpose = self.parent.step_kw_purpose.selected_purpose() subcategory = self.parent.step_kw_subcategory.selected_subcategory() # There may be two cases this tab is displayed: either # a classification or postprocessor_classification is available sel_cl = self.parent.step_kw_classification.selected_classification() if sel_cl: default_classes = sel_cl['classes'] mapping_keyword = 'value_map' classification_name = sel_cl['name'] else: default_classes = self.postprocessor_classification_for_layer() mapping_keyword = 'value_mapping' classification_name = '' if is_raster_layer(self.parent.layer): self.lblClassify.setText( classify_raster_question % (subcategory['name'], purpose['name'], classification_name)) ds = gdal.Open(self.parent.layer.source(), GA_ReadOnly) unique_values = numpy.unique( numpy.array(ds.GetRasterBand(1).ReadAsArray())) field_type = 0 # Convert datatype to a json serializable type if numpy.issubdtype(unique_values.dtype, float): unique_values = [float(i) for i in unique_values] else: unique_values = [int(i) for i in unique_values] else: field = self.parent.step_kw_field.selected_field() field_index = self.parent.layer.dataProvider().fields().\ indexFromName(field) field_type = self.parent.layer.dataProvider().\ fields()[field_index].type() if classification_name: self.lblClassify.setText(classify_vector_question % (subcategory['name'], purpose['name'], classification_name, field.upper())) else: self.lblClassify.setText( classify_vector_for_postprocessor_question % (subcategory['name'], purpose['name'], field.upper())) unique_values = self.parent.layer.uniqueValues(field_index) # Assign unique values to classes (according to default) unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() for unique_value in unique_values: if unique_value is None or isinstance(unique_value, QPyNullVariant): # Don't classify features with NULL value continue # Capitalization of the value and removing '_' (raw OSM data). value_as_string = unicode(unique_value).upper().replace('_', ' ') assigned = False for default_class in default_classes: condition_1 = (field_type > 9 and value_as_string in [ c.upper() for c in default_class['string_defaults'] ]) condition_2 = ( field_type < 10 and 'numeric_default_min' in default_class and 'numeric_default_max' in default_class and (default_class['numeric_default_min'] <= unique_value <= default_class['numeric_default_max'])) if condition_1 or condition_2: assigned_values[default_class['key']] += [unique_value] assigned = True if not assigned: # add to unassigned values list otherwise unassigned_values += [unique_value] self.populate_classified_values(unassigned_values, assigned_values, default_classes) # Overwrite assigned values according to existing keyword (if present). # Note the default_classes and unique_values are already loaded! value_map = self.parent.get_existing_keyword(mapping_keyword) # Do not continue if there is no value_map in existing keywords if value_map is None: return # Do not continue if user selected different field field_keyword = self.parent.field_keyword_for_the_layer() field = self.parent.get_existing_keyword(field_keyword) if (not is_raster_layer(self.parent.layer) and field != self.parent.step_kw_field.selected_field()): return unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() if isinstance(value_map, str): try: value_map = json.loads(value_map) except ValueError: return for unique_value in unique_values: if unique_value is None or isinstance(unique_value, QPyNullVariant): # Don't classify features with NULL value continue # check in value map assigned = False for key, value_list in value_map.iteritems(): if unique_value in value_list and key in assigned_values: assigned_values[key] += [unique_value] assigned = True if not assigned: unassigned_values += [unique_value] self.populate_classified_values(unassigned_values, assigned_values, default_classes)
def set_widgets(self): """Set widgets on the Classify tab.""" purpose = self.parent.step_kw_purpose.selected_purpose() subcategory = self.parent.step_kw_subcategory.selected_subcategory() # There may be two cases this tab is displayed: either # a classification or postprocessor_classification is available sel_cl = self.parent.step_kw_classification.selected_classification() if sel_cl: default_classes = sel_cl['classes'] mapping_keyword = 'value_map' classification_name = sel_cl['name'] else: default_classes = self.postprocessor_classification_for_layer() mapping_keyword = 'value_mapping' classification_name = '' if is_raster_layer(self.parent.layer): self.lblClassify.setText(classify_raster_question % ( subcategory['name'], purpose['name'], classification_name)) ds = gdal.Open(self.parent.layer.source(), GA_ReadOnly) unique_values = numpy.unique(numpy.array( ds.GetRasterBand(1).ReadAsArray())) field_type = 0 # Convert datatype to a json serializable type if numpy.issubdtype(unique_values.dtype, float): unique_values = [float(i) for i in unique_values] else: unique_values = [int(i) for i in unique_values] else: field = self.parent.step_kw_field.selected_field() field_index = self.parent.layer.dataProvider().fields().\ indexFromName(field) field_type = self.parent.layer.dataProvider().\ fields()[field_index].type() if classification_name: self.lblClassify.setText(classify_vector_question % ( subcategory['name'], purpose['name'], classification_name, field.upper())) else: self.lblClassify.setText( classify_vector_for_postprocessor_question % ( subcategory['name'], purpose['name'], field.upper())) unique_values = self.parent.layer.uniqueValues(field_index) # Assign unique values to classes (according to default) unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() for unique_value in unique_values: if unique_value is None or isinstance( unique_value, QPyNullVariant): # Don't classify features with NULL value continue # Capitalization of the value and removing '_' (raw OSM data). value_as_string = unicode(unique_value).upper().replace('_', ' ') assigned = False for default_class in default_classes: condition_1 = ( field_type > 9 and value_as_string in [ c.upper() for c in default_class['string_defaults']]) condition_2 = ( field_type < 10 and 'numeric_default_min' in default_class and 'numeric_default_max' in default_class and ( default_class['numeric_default_min'] <= unique_value <= default_class['numeric_default_max'])) if condition_1 or condition_2: assigned_values[default_class['key']] += [unique_value] assigned = True if not assigned: # add to unassigned values list otherwise unassigned_values += [unique_value] self.populate_classified_values( unassigned_values, assigned_values, default_classes) # Overwrite assigned values according to existing keyword (if present). # Note the default_classes and unique_values are already loaded! value_map = self.parent.get_existing_keyword(mapping_keyword) # Do not continue if there is no value_map in existing keywords if value_map is None: return # Do not continue if user selected different field field_keyword = self.parent.field_keyword_for_the_layer() field = self.parent.get_existing_keyword(field_keyword) if (not is_raster_layer(self.parent.layer) and field != self.parent.step_kw_field.selected_field()): return unassigned_values = list() assigned_values = dict() for default_class in default_classes: assigned_values[default_class['key']] = list() if isinstance(value_map, str): try: value_map = json.loads(value_map) except ValueError: return for unique_value in unique_values: if unique_value is None or isinstance( unique_value, QPyNullVariant): # Don't classify features with NULL value continue # check in value map assigned = False for key, value_list in value_map.iteritems(): if unique_value in value_list and key in assigned_values: assigned_values[key] += [unique_value] assigned = True if not assigned: unassigned_values += [unique_value] self.populate_classified_values( unassigned_values, assigned_values, default_classes)
def setup_thresholds_panel(self, classification): """Setup threshold panel in the right panel. :param classification: Classification definition. :type classification: dict """ # Set text in the label layer_purpose = self.parent.step_kw_purpose.selected_purpose() layer_subcategory = self.parent.step_kw_subcategory.\ selected_subcategory() if is_raster_layer(self.parent.layer): statistics = self.parent.layer.dataProvider().bandStatistics( 1, QgsRasterBandStats.All, self.parent.layer.extent(), 0) description_text = continuous_raster_question % ( layer_purpose['name'], layer_subcategory['name'], classification['name'], statistics.minimumValue, statistics.maximumValue) else: field_name = self.parent.step_kw_field.selected_fields() field_index = self.parent.layer.fieldNameIndex(field_name) min_value_layer = self.parent.layer.minimumValue(field_index) max_value_layer = self.parent.layer.maximumValue(field_index) description_text = continuous_vector_question % ( layer_purpose['name'], layer_subcategory['name'], field_name, classification['name'], min_value_layer, max_value_layer) # Set description description_label = QLabel(description_text) description_label.setWordWrap(True) self.right_layout.addWidget(description_label) if self.thresholds: thresholds = self.thresholds else: thresholds = self.parent.get_existing_keyword('thresholds') selected_unit = self.parent.step_kw_unit.selected_unit()['key'] self.threshold_classes = OrderedDict() classes = classification.get('classes') # Sort by value, put the lowest first classes = sorted(classes, key=lambda the_key: the_key['value']) grid_layout_thresholds = QGridLayout() for i, the_class in enumerate(classes): class_layout = QHBoxLayout() # Class label class_label = QLabel(the_class['name']) # Min label min_label = QLabel(tr('Min >')) # Min value as double spin min_value_input = QDoubleSpinBox() # TODO(IS) We can set the min and max depends on the unit, later min_value_input.setMinimum(0) min_value_input.setMaximum(999999) if thresholds.get(self.active_exposure['key']): exposure_thresholds = thresholds.get( self.active_exposure['key']) if exposure_thresholds.get(classification['key']): exposure_thresholds_classifications = exposure_thresholds\ .get(classification['key']) min_value_input.setValue( exposure_thresholds_classifications['classes'][ the_class['key']][0]) else: default_min = the_class['numeric_default_min'] if isinstance(default_min, dict): default_min = the_class[ 'numeric_default_min'][selected_unit] min_value_input.setValue(default_min) else: default_min = the_class['numeric_default_min'] if isinstance(default_min, dict): default_min = the_class[ 'numeric_default_min'][selected_unit] min_value_input.setValue(default_min) min_value_input.setSingleStep(0.1) # Max label max_label = QLabel(tr('Max <=')) # Max value as double spin max_value_input = QDoubleSpinBox() # TODO(IS) We can set the min and max depends on the unit, later max_value_input.setMinimum(0) max_value_input.setMaximum(999999) if thresholds.get(self.active_exposure['key']): exposure_thresholds = thresholds.get( self.active_exposure['key']) if exposure_thresholds.get(classification['key']): exposure_thresholds_classifications = exposure_thresholds \ .get(classification['key']) max_value_input.setValue( exposure_thresholds_classifications['classes'][ the_class['key']][1]) else: default_max = the_class['numeric_default_max'] if isinstance(default_max, dict): default_max = the_class[ 'numeric_default_max'][selected_unit] max_value_input.setValue(default_max) else: default_max = the_class['numeric_default_max'] if isinstance(default_max, dict): default_max = the_class[ 'numeric_default_max'][selected_unit] max_value_input.setValue(default_max) max_value_input.setSingleStep(0.1) # Add to class_layout class_layout.addWidget(min_label) class_layout.addWidget(min_value_input) class_layout.addWidget(max_label) class_layout.addWidget(max_value_input) class_layout.setStretch(0, 1) class_layout.setStretch(1, 2) class_layout.setStretch(2, 1) class_layout.setStretch(3, 2) # Add to grid_layout grid_layout_thresholds.addWidget(class_label, i, 0) grid_layout_thresholds.addLayout(class_layout, i, 1) self.threshold_classes[the_class['key']] = [ min_value_input, max_value_input] grid_layout_thresholds.setColumnStretch(0, 1) grid_layout_thresholds.setColumnStretch(0, 2) def min_max_changed(double_spin_index, mode): """Slot when min or max value change. :param double_spin_index: The index of the double spin. :type double_spin_index: int :param mode: The flag to indicate the min or max value. :type mode: int """ if mode == MAX_VALUE_MODE: current_max_value = self.threshold_classes.values()[ double_spin_index][1] target_min_value = self.threshold_classes.values()[ double_spin_index + 1][0] if current_max_value.value() != target_min_value.value(): target_min_value.setValue(current_max_value.value()) elif mode == MIN_VALUE_MODE: current_min_value = self.threshold_classes.values()[ double_spin_index][0] target_max_value = self.threshold_classes.values()[ double_spin_index - 1][1] if current_min_value.value() != target_max_value.value(): target_max_value.setValue(current_min_value.value()) # Set behaviour for k, v in self.threshold_classes.items(): index = self.threshold_classes.keys().index(k) if index < len(self.threshold_classes) - 1: # Max value changed v[1].valueChanged.connect(partial( min_max_changed, double_spin_index=index, mode=MAX_VALUE_MODE)) if index > 0: # Min value v[0].valueChanged.connect(partial( min_max_changed, double_spin_index=index, mode=MIN_VALUE_MODE)) grid_layout_thresholds.setSpacing(0) self.right_layout.addLayout(grid_layout_thresholds)