def create_profile_layer(profiling): """Create a tabular layer with the profiling. :param profiling: A dict containing benchmarking data. :type profiling: safe.messaging.message.Message :return: A tabular layer. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(profiling_function_field), create_field_from_definition(profiling_time_field) ] if setting(key='memory_profile', expected_type=bool): fields.append(create_field_from_definition(profiling_memory_field)) tabular = create_memory_layer( 'profiling', QgsWkbTypes.NullGeometry, fields=fields) # Generate profiling keywords tabular.keywords['layer_purpose'] = layer_purpose_profiling['key'] tabular.keywords['title'] = layer_purpose_profiling['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['inasafe_fields'] = { profiling_function_field['key']: profiling_function_field['field_name'], profiling_time_field['key']: profiling_time_field['field_name'], } if setting(key='memory_profile', expected_type=bool): tabular.keywords['inasafe_fields'][ profiling_memory_field['key']] = profiling_memory_field[ 'field_name'] tabular.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) table = profiling.to_text().splitlines()[3:] tabular.startEditing() for line in table: feature = QgsFeature() items = line.split(', ') time = items[1].replace('-', '') if setting(key='memory_profile', expected_type=bool): memory = items[2].replace('-', '') feature.setAttributes([items[0], time, memory]) else: feature.setAttributes([items[0], time]) tabular.addFeature(feature) tabular.commitChanges() return tabular
def create_profile_layer(profiling): """Create a tabular layer with the profiling. :param profiling: A dict containing benchmarking data. :type profiling: safe.messaging.message.Message :return: A tabular layer. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(profiling_function_field), create_field_from_definition(profiling_time_field) ] if setting(key='memory_profile', expected_type=bool): fields.append(create_field_from_definition(profiling_memory_field)) tabular = create_memory_layer( 'profiling', QgsWkbTypes.NullGeometry, fields=fields) # Generate profiling keywords tabular.keywords['layer_purpose'] = layer_purpose_profiling['key'] tabular.keywords['title'] = layer_purpose_profiling['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['inasafe_fields'] = { profiling_function_field['key']: profiling_function_field['field_name'], profiling_time_field['key']: profiling_time_field['field_name'], } if setting(key='memory_profile', expected_type=bool): tabular.keywords['inasafe_fields'][ profiling_memory_field['key']] = profiling_memory_field[ 'field_name'] tabular.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) table = profiling.to_text().splitlines()[3:] tabular.startEditing() for line in table: feature = QgsFeature() items = line.split(', ') time = items[1].replace('-', '') if setting(key='memory_profile', expected_type=bool): memory = items[2].replace('-', '') feature.setAttributes([items[0], time, memory]) else: feature.setAttributes([items[0], time]) tabular.addFeature(feature) tabular.commitChanges() return tabular
def add_fields( layer, absolute_values, static_fields, dynamic_structure): """Function to add fields needed in the output layer. :param layer: The vector layer. :type layer: QgsVectorLayer :param absolute_values: The absolute value structure. :type absolute_values: dict :param static_fields: The list of static fields to add. :type static_fields: list :param dynamic_structure: The list of dynamic fields to add to the layer. The list must be structured like this: dynamic_structure = [ [exposure_count_field, unique_exposure] ] where "exposure_count_field" is the dynamic to field to add and "unique_exposure" is the list of unique values to associate with this dynamic field. Because dynamic_structure is a ordered list, you can add many dynamic fields. :type dynamic_structure: list """ for new_dynamic_field in dynamic_structure: field_definition = new_dynamic_field[0] unique_values = new_dynamic_field[1] for column in unique_values: if (column == '' or (hasattr(column, 'isNull') and column.isNull())): column = 'NULL' field = create_field_from_definition(field_definition, column) layer.addAttribute(field) key = field_definition['key'] % column value = field_definition['field_name'] % column layer.keywords['inasafe_fields'][key] = value for static_field in static_fields: field = create_field_from_definition(static_field) layer.addAttribute(field) # noinspection PyTypeChecker layer.keywords['inasafe_fields'][static_field['key']] = ( static_field['field_name']) # For each absolute values for absolute_field in list(absolute_values.keys()): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) layer.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] layer.keywords['inasafe_fields'][key] = value
def add_fields( layer, absolute_values, static_fields, dynamic_structure): """Function to add fields needed in the output layer. :param layer: The vector layer. :type layer: QgsVectorLayer :param absolute_values: The absolute value structure. :type absolute_values: dict :param static_fields: The list of static fields to add. :type static_fields: list :param dynamic_structure: The list of dynamic fields to add to the layer. The list must be structured like this: dynamic_structure = [ [exposure_count_field, unique_exposure] ] where "exposure_count_field" is the dynamic to field to add and "unique_exposure" is the list of unique values to associate with this dynamic field. Because dynamic_structure is a ordered list, you can add many dynamic fields. :type dynamic_structure: list """ for new_dynamic_field in dynamic_structure: field_definition = new_dynamic_field[0] unique_values = new_dynamic_field[1] for column in unique_values: if (column == '' or (hasattr(column, 'isNull') and column.isNull())): column = 'NULL' field = create_field_from_definition(field_definition, column) layer.addAttribute(field) key = field_definition['key'] % column value = field_definition['field_name'] % column layer.keywords['inasafe_fields'][key] = value for static_field in static_fields: field = create_field_from_definition(static_field) layer.addAttribute(field) # noinspection PyTypeChecker layer.keywords['inasafe_fields'][static_field['key']] = ( static_field['field_name']) # For each absolute values for absolute_field in list(absolute_values.keys()): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) layer.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] layer.keywords['inasafe_fields'][key] = value
def add_fields( layer, absolute_values, static_fields, dynamic_values, dynamic_field): """Function to add fields needed in the output layer. :param layer: The vector layer. :type layer: QgsVectorLayer :param absolute_values: The absolute value structure. :type absolute_values: dict :param static_fields: The list of static fields to add. :type static_fields: list :param dynamic_values: The list of unique field to create. :type dynamic_values: list :param dynamic_field: The dynamic field to add. :type dynamic_field: safe.definitions.fields :param static_fields """ for column in dynamic_values: if not column or isinstance(column, QPyNullVariant): column = 'NULL' field = create_field_from_definition(dynamic_field, column) layer.addAttribute(field) key = dynamic_field['key'] % column value = dynamic_field['field_name'] % column layer.keywords['inasafe_fields'][key] = value for static_field in static_fields: field = create_field_from_definition(static_field) layer.addAttribute(field) # noinspection PyTypeChecker layer.keywords['inasafe_fields'][static_field['key']] = ( static_field['field_name']) # For each absolute values for absolute_field in absolute_values.iterkeys(): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) layer.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] layer.keywords['inasafe_fields'][key] = value
def add_fields( layer, absolute_values, static_fields, dynamic_values, dynamic_field): """Function to add fields needed in the output layer. :param layer: The vector layer. :type layer: QgsVectorLayer :param absolute_values: The absolute value structure. :type absolute_values: dict :param static_fields: The list of static fields to add. :type static_fields: list :param dynamic_values: The list of unique field to create. :type dynamic_values: list :param dynamic_field: The dynamic field to add. :type dynamic_field: safe.definitions.fields :param static_fields """ for column in dynamic_values: if not column or isinstance(column, QPyNullVariant): column = 'NULL' field = create_field_from_definition(dynamic_field, column) layer.addAttribute(field) key = dynamic_field['key'] % column value = dynamic_field['field_name'] % column layer.keywords['inasafe_fields'][key] = value for static_field in static_fields: field = create_field_from_definition(static_field) layer.addAttribute(field) # noinspection PyTypeChecker layer.keywords['inasafe_fields'][static_field['key']] = ( static_field['field_name']) # For each absolute values for absolute_field in absolute_values.iterkeys(): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) layer.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] layer.keywords['inasafe_fields'][key] = value
def create_analysis_layer(analysis_extent, crs, name): """Create the analysis layer. :param analysis_extent: The analysis extent. :type analysis_extent: QgsGeometry :param crs: The CRS to use. :type crs: QgsCoordinateReferenceSystem :param name: The name of the analysis. :type name: basestring :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(analysis_id_field), create_field_from_definition(analysis_name_field) ] analysis_layer = create_memory_layer( 'analysis', QGis.Polygon, crs, fields) analysis_layer.startEditing() feature = QgsFeature() # noinspection PyCallByClass,PyArgumentList,PyTypeChecker feature.setGeometry(analysis_extent) feature.setAttributes([1, name]) analysis_layer.addFeature(feature) analysis_layer.commitChanges() # Generate analysis keywords analysis_layer.keywords['layer_purpose'] = ( layer_purpose_analysis_impacted['key']) analysis_layer.keywords['title'] = 'analysis' analysis_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) analysis_layer.keywords['inasafe_fields'] = { analysis_id_field['key']: analysis_id_field['field_name'], analysis_name_field['key']: analysis_name_field['field_name'] } return analysis_layer
def create_analysis_layer(analysis_extent, crs, name): """Create the analysis layer. :param analysis_extent: The analysis extent. :type analysis_extent: QgsGeometry :param crs: The CRS to use. :type crs: QgsCoordinateReferenceSystem :param name: The name of the analysis. :type name: basestring :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(analysis_id_field), create_field_from_definition(analysis_name_field) ] analysis_layer = create_memory_layer( 'analysis', QGis.Polygon, crs, fields) analysis_layer.startEditing() feature = QgsFeature() # noinspection PyCallByClass,PyArgumentList,PyTypeChecker feature.setGeometry(analysis_extent) feature.setAttributes([1, name]) analysis_layer.addFeature(feature) analysis_layer.commitChanges() # Generate analysis keywords analysis_layer.keywords['layer_purpose'] = ( layer_purpose_analysis_impacted['key']) analysis_layer.keywords['title'] = 'analysis' analysis_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) analysis_layer.keywords['inasafe_fields'] = { analysis_id_field['key']: analysis_id_field['field_name'], analysis_name_field['key']: analysis_name_field['field_name'] } return analysis_layer
def sum_fields(layer, output_field_key, input_fields): """Sum the value of input_fields and put it as output_field. :param layer: The vector layer. :type layer: QgsVectorLayer :param output_field_key: The output field definition key. :type output_field_key: basestring :param input_fields: List of input fields' name. :type input_fields: list """ field_definition = definition(output_field_key) output_field_name = field_definition['field_name'] # If the fields only has one element if len(input_fields) == 1: # Name is different, copy it if input_fields[0] != output_field_name: to_rename = {input_fields[0]: output_field_name} # We copy only, it will be deleted later. # We can't rename the field, we need to copy it as the same # field might be used many times in the FMT tool. copy_fields(layer, to_rename) else: # Name is same, do nothing return else: # Creating expression # Put field name in a double quote. See #4248 input_fields = ['"%s"' % f for f in input_fields] string_expression = ' + '.join(input_fields) sum_expression = QgsExpression(string_expression) context = QgsExpressionContext() context.setFields(layer.pendingFields()) sum_expression.prepare(context) # Get the output field index output_idx = layer.fieldNameIndex(output_field_name) # Output index is not found if output_idx == -1: output_field = create_field_from_definition(field_definition) layer.startEditing() layer.addAttribute(output_field) layer.commitChanges() output_idx = layer.fieldNameIndex(output_field_name) layer.startEditing() # Iterate to all features for feature in layer.getFeatures(): context.setFeature(feature) result = sum_expression.evaluate(context) feature[output_idx] = result layer.updateFeature(feature) layer.commitChanges()
def create_virtual_aggregation(geometry, crs): """Function to create aggregation layer based on extent. :param geometry: The geometry to use as an extent. :type geometry: QgsGeometry :param crs: The Coordinate Reference System to use for the layer. :type crs: QgsCoordinateReferenceSystem :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(aggregation_id_field), create_field_from_definition(aggregation_name_field) ] aggregation_layer = create_memory_layer('aggregation', QgsWkbTypes.PolygonGeometry, crs, fields) aggregation_layer.startEditing() feature = QgsFeature() feature.setGeometry(geometry) feature.setAttributes([1, tr('Entire Area')]) aggregation_layer.addFeature(feature) aggregation_layer.commitChanges() # Generate aggregation keywords aggregation_layer.keywords['layer_purpose'] = ( layer_purpose_aggregation['key']) aggregation_layer.keywords['title'] = 'aggr_from_bbox' aggregation_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) aggregation_layer.keywords['inasafe_fields'] = { aggregation_id_field['key']: aggregation_id_field['field_name'], aggregation_name_field['key']: aggregation_name_field['field_name'] } # We will fill default values later, according to the exposure. aggregation_layer.keywords['inasafe_default_values'] = {} return aggregation_layer
def create_virtual_aggregation(geometry, crs): """Function to create aggregation layer based on extent. :param geometry: The geometry to use as an extent. :type geometry: QgsGeometry :param crs: The Coordinate Reference System to use for the layer. :type crs: QgsCoordinateReferenceSystem :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(aggregation_id_field), create_field_from_definition(aggregation_name_field) ] aggregation_layer = create_memory_layer( 'aggregation', QgsWkbTypes.PolygonGeometry, crs, fields) aggregation_layer.startEditing() feature = QgsFeature() feature.setGeometry(geometry) feature.setAttributes([1, tr('Entire Area')]) aggregation_layer.addFeature(feature) aggregation_layer.commitChanges() # Generate aggregation keywords aggregation_layer.keywords['layer_purpose'] = ( layer_purpose_aggregation['key']) aggregation_layer.keywords['title'] = 'aggr_from_bbox' aggregation_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) aggregation_layer.keywords['inasafe_fields'] = { aggregation_id_field['key']: aggregation_id_field['field_name'], aggregation_name_field['key']: aggregation_name_field['field_name'] } # We will fill default values later, according to the exposure. aggregation_layer.keywords['inasafe_default_values'] = {} return aggregation_layer
def sum_fields(layer, output_field_key, input_fields): """Sum the value of input_fields and put it as output_field. :param layer: The vector layer. :type layer: QgsVectorLayer :param output_field_key: The output field definition key. :type output_field_key: basestring :param input_fields: List of input fields' name. :type input_fields: list """ field_definition = definition(output_field_key) output_field_name = field_definition['field_name'] # If the fields only has one element if len(input_fields) == 1: # Name is different, copy it if input_fields[0] != output_field_name: to_rename = {input_fields[0]: output_field_name} # We copy only, it will be deleted later. # We can't rename the field, we need to copy it as the same # field might be used many times in the FMT tool. copy_fields(layer, to_rename) else: # Name is same, do nothing return else: # Creating expression # Put field name in a double quote. See #4248 input_fields = ['"%s"' % f for f in input_fields] string_expression = ' + '.join(input_fields) sum_expression = QgsExpression(string_expression) context = QgsExpressionContext() context.setFields(layer.fields()) sum_expression.prepare(context) # Get the output field index output_idx = layer.fields().lookupField(output_field_name) # Output index is not found layer.startEditing() if output_idx == -1: output_field = create_field_from_definition(field_definition) layer.addAttribute(output_field) output_idx = layer.fields().lookupField(output_field_name) # Iterate to all features for feature in layer.getFeatures(): context.setFeature(feature) result = sum_expression.evaluate(context) feature[output_idx] = result layer.updateFeature(feature) layer.commitChanges()
def sum_fields(layer, output_field_key, input_fields): """Sum the value of input_fields and put it as output_field. :param layer: The vector layer. :type layer: QgsVectorLayer :param output_field_key: The output field definition key. :type output_field_key: basestring :param input_fields: List of input fields' name. :type input_fields: list """ field_definition = definition(output_field_key) output_field_name = field_definition['field_name'] # If the fields only has one element if len(input_fields) == 1: # Name is different, copy it if input_fields[0] != output_field_name: copy_fields(layer, { input_fields[0]: output_field_name}) # Name is same, do nothing else: return else: # Creating expression # Put field name in a double quote. See #4248 input_fields = ['"%s"' % f for f in input_fields] string_expression = ' + '.join(input_fields) sum_expression = QgsExpression(string_expression) context = QgsExpressionContext() context.setFields(layer.pendingFields()) sum_expression.prepare(context) # Get the output field index output_idx = layer.fieldNameIndex(output_field_name) # Output index is not found if output_idx == -1: output_field = create_field_from_definition(field_definition) layer.startEditing() layer.addAttribute(output_field) layer.commitChanges() output_idx = layer.fieldNameIndex(output_field_name) layer.startEditing() # Iterate to all features for feature in layer.getFeatures(): context.setFeature(feature) result = sum_expression.evaluate(context) feature[output_idx] = result layer.updateFeature(feature) layer.commitChanges()
def _add_default_exposure_class(layer): """The layer doesn't have an exposure class, we need to add it. :param layer: The vector layer. :type layer: QgsVectorLayer """ layer.startEditing() field = create_field_from_definition(exposure_class_field) layer.keywords['inasafe_fields'][exposure_class_field['key']] = ( exposure_class_field['field_name']) layer.addAttribute(field) index = layer.fieldNameIndex(exposure_class_field['field_name']) exposure = layer.keywords['exposure'] request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for feature in layer.getFeatures(request): layer.changeAttributeValue(feature.id(), index, exposure) layer.commitChanges() return
def _add_default_exposure_class(layer): """The layer doesn't have an exposure class, we need to add it. :param layer: The vector layer. :type layer: QgsVectorLayer """ layer.startEditing() field = create_field_from_definition(exposure_class_field) layer.keywords['inasafe_fields'][exposure_class_field['key']] = ( exposure_class_field['field_name']) layer.addAttribute(field) index = layer.fieldNameIndex(exposure_class_field['field_name']) exposure = layer.keywords['exposure'] request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for feature in layer.getFeatures(request): layer.changeAttributeValue(feature.id(), index, exposure) layer.commitChanges() return
def multi_exposure_analysis_summary(analysis, intermediate_analysis): """Merge intermediate analysis into one analysis summary. List of analysis layers like: | analysis_id | count_hazard_class | affected_count | total | Target layer : | analysis_id | Output layer : | analysis_id | count_hazard_class | affected_count | total | :param analysis: The target vector layer where to write statistics. :type analysis: QgsVectorLayer :param intermediate_analysis: List of analysis layer for a single exposure. :type intermediate_analysis: list :return: The new target layer with summary. :rtype: QgsVectorLayer .. versionadded:: 4.3 """ analysis.startEditing() request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) target_id = next(analysis.getFeatures(request)).id() for analysis_result in intermediate_analysis: exposure = analysis_result.keywords['exposure_keywords']['exposure'] iterator = analysis_result.getFeatures(request) feature = next(iterator) source_fields = analysis_result.keywords['inasafe_fields'] # Dynamic fields hazards = read_dynamic_inasafe_field(source_fields, hazard_count_field) for hazard_zone in hazards: field = create_field_from_definition(exposure_hazard_count_field, exposure, hazard_zone) analysis.addAttribute(field) index = analysis.fields().lookupField(field.name()) value = feature[analysis_result.fields().lookupField( hazard_count_field['field_name'] % hazard_zone)] analysis.changeAttributeValue(target_id, index, value) # keywords key = exposure_hazard_count_field['key'] % (exposure, hazard_zone) value = exposure_hazard_count_field['field_name'] % (exposure, hazard_zone) analysis.keywords['inasafe_fields'][key] = value # total affected source_index = analysis_result.fields().lookupField( total_affected_field['field_name']) field = create_field_from_definition(exposure_total_affected_field, exposure) analysis.addAttribute(field) index = analysis.fields().lookupField(field.name()) analysis.changeAttributeValue(target_id, index, feature[source_index]) # keywords key = exposure_total_affected_field['key'] % exposure value = exposure_total_affected_field['field_name'] % exposure analysis.keywords['inasafe_fields'][key] = value # total not affected source_index = analysis_result.fields().lookupField( total_not_affected_field['field_name']) field = create_field_from_definition(exposure_total_not_affected_field, exposure) analysis.addAttribute(field) index = analysis.fields().lookupField(field.name()) analysis.changeAttributeValue(target_id, index, feature[source_index]) # keywords key = exposure_total_not_affected_field['key'] % exposure value = exposure_total_not_affected_field['field_name'] % exposure analysis.keywords['inasafe_fields'][key] = value # total exposed source_index = analysis_result.fields().lookupField( total_exposed_field['field_name']) field = create_field_from_definition(exposure_total_exposed_field, exposure) analysis.addAttribute(field) index = analysis.fields().lookupField(field.name()) analysis.changeAttributeValue(target_id, index, feature[source_index]) # keywords key = exposure_total_exposed_field['key'] % exposure value = exposure_total_exposed_field['field_name'] % exposure analysis.keywords['inasafe_fields'][key] = value # total not exposed source_index = analysis_result.fields().lookupField( total_not_exposed_field['field_name']) field = create_field_from_definition(exposure_total_not_exposed_field, exposure) analysis.addAttribute(field) index = analysis.fields().lookupField(field.name()) analysis.changeAttributeValue(target_id, index, feature[source_index]) # keywords key = exposure_total_not_exposed_field['key'] % exposure value = exposure_total_not_exposed_field['field_name'] % exposure analysis.keywords['inasafe_fields'][key] = value # total source_index = analysis_result.fields().lookupField( total_field['field_name']) field = create_field_from_definition(exposure_total_field, exposure) analysis.addAttribute(field) index = analysis.fields().lookupField(field.name()) analysis.changeAttributeValue(target_id, index, feature[source_index]) # keywords key = exposure_total_field['key'] % exposure value = exposure_total_field['field_name'] % exposure analysis.keywords['inasafe_fields'][key] = value analysis.commitChanges() analysis.keywords['title'] = ( layer_purpose_analysis_impacted['multi_exposure_name']) analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key'] # Set up the extra keywords so everyone knows it's a # multi exposure analysis result. extra_keywords = { extra_keyword_analysis_type['key']: MULTI_EXPOSURE_ANALYSIS_FLAG } analysis.keywords['extra_keywords'] = extra_keywords if qgis_version() >= 21600: analysis.setName(analysis.keywords['title']) else: analysis.setLayerName(analysis.keywords['title']) return analysis
def multi_exposure_aggregation_summary(aggregation, intermediate_layers): """Merge intermediate aggregations into one aggregation summary. Source layer : | aggr_id | aggr_name | count of affected features per exposure type Target layer : | aggregation_id | aggregation_name | Output layer : | aggr_id | aggr_name | count of affected per exposure type for each :param aggregation: The target vector layer where to write statistics. :type aggregation: QgsVectorLayer :param intermediate_layers: List of aggregation layer for a single exposure :type intermediate_layers: list :return: The new target layer with summary. :rtype: QgsVectorLayer .. versionadded:: 4.3 """ target_index_field_name = ( aggregation.keywords['inasafe_fields'][aggregation_id_field['key']]) aggregation.startEditing() request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for layer in intermediate_layers: source_fields = layer.keywords['inasafe_fields'] exposure = layer.keywords['exposure_keywords']['exposure'] unique_exposure = read_dynamic_inasafe_field( source_fields, affected_exposure_count_field, [total_affected_field]) field_map = {} for exposure_class in unique_exposure: field = create_field_from_definition( exposure_affected_exposure_type_count_field, name=exposure, sub_name=exposure_class) aggregation.addAttribute(field) source_field_index = layer.fields().lookupField( affected_exposure_count_field['field_name'] % exposure_class) target_field_index = aggregation.fields().lookupField(field.name()) field_map[source_field_index] = target_field_index # Total affected field field = create_field_from_definition(exposure_total_not_affected_field, exposure) aggregation.addAttribute(field) source_field_index = layer.fields().lookupField( total_affected_field['field_name']) target_field_index = aggregation.fields().lookupField(field.name()) field_map[source_field_index] = target_field_index # Get Aggregation ID from original feature index = (layer.fields().lookupField( source_fields[aggregation_id_field['key']])) for source_feature in layer.getFeatures(request): target_expression = QgsFeatureRequest() target_expression.setFlags(QgsFeatureRequest.NoGeometry) expression = '\"{field_name}\" = {id_value}'.format( field_name=target_index_field_name, id_value=source_feature[index]) target_expression.setFilterExpression(expression) iterator = aggregation.getFeatures(target_expression) target_feature = next(iterator) # It must return only 1 feature. for source_field, target_field in list(field_map.items()): aggregation.changeAttributeValue(target_feature.id(), target_field, source_feature[source_field]) try: next(iterator) except StopIteration: # Everything is fine, it's normal. pass else: # This should never happen ! IDs are duplicated in the # aggregation layer. raise Exception( 'Aggregation IDs are duplicated in the aggregation layer. ' 'We can\'t make any joins.') aggregation.commitChanges() aggregation.keywords['title'] = ( layer_purpose_aggregation_summary['multi_exposure_name']) aggregation.keywords['layer_purpose'] = ( layer_purpose_aggregation_summary['key']) # Set up the extra keywords so everyone knows it's a # multi exposure analysis result. extra_keywords = { extra_keyword_analysis_type['key']: MULTI_EXPOSURE_ANALYSIS_FLAG } aggregation.keywords['extra_keywords'] = extra_keywords if qgis_version() >= 21600: aggregation.setName(aggregation.keywords['title']) else: aggregation.setLayerName(aggregation.keywords['title']) return aggregation
def add_default_values(layer, callback=None): """Add or fill default values to the layer, see #3325. 1. It doesn't have inasafe_field and it doesn't have inasafe_default_value --> Do nothing. 2. It has inasafe_field and it does not have inasafe_default_value --> Do nothing. 3. It does not have inasafe_field but it has inasafe_default_value --> Create new field, and fill with the default value for all features 4. It has inasafe_field and it has inasafe_default_value --> Replace the null value with the default one. :param layer: The vector layer. :type layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The vector layer with the default values. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = assign_default_values_steps['output_layer_name'] output_layer_name = output_layer_name % layer.keywords['layer_purpose'] processing_step = assign_default_values_steps['step_name'] fields = layer.keywords.get('inasafe_fields') if not isinstance(fields, dict): msg = 'inasafe_fields is missing in keywords from %s' % layer.name() raise InvalidKeywordsForProcessingAlgorithm(msg) defaults = layer.keywords.get('inasafe_default_values') if not defaults: # Case 1 and 2. LOGGER.info( 'inasafe_default_value is not present, we can not fill default ' 'ratios for this layer.') return layer for default in defaults.keys(): field = fields.get(default) target_field = definition(default) layer.startEditing() if not field: # Case 3 LOGGER.info( '{field} is not present but the layer has {value} as a ' 'default for {field}. We create the new field ' '{new_field} with this value.'.format( field=target_field['key'], value=defaults[default], new_field=target_field['field_name'])) new_field = create_field_from_definition(target_field) layer.addAttribute(new_field) new_index = layer.fieldNameIndex(new_field.name()) for feature in layer.getFeatures(): layer.changeAttributeValue(feature.id(), new_index, defaults[default]) layer.keywords['inasafe_fields'][target_field['key']] = ( target_field['field_name']) else: # Case 4 LOGGER.info('{field} is present and the layer has {value} as a ' 'default for {field}, we MUST do nothing.'.format( field=target_field['key'], value=defaults[default])) index = layer.fieldNameIndex(field) for feature in layer.getFeatures(): if isinstance(feature.attributes()[index], QPyNullVariant): layer.changeAttributeValue(feature.id(), index, defaults[default]) continue if feature.attributes()[index] == '': layer.changeAttributeValue(feature.id(), index, defaults[default]) continue layer.commitChanges() layer.keywords['title'] = output_layer_name check_layer(layer) return layer
def from_counts_to_ratios(layer): """Transform counts to ratios. Formula: ratio = subset count / total count :param layer: The vector layer. :type layer: QgsVectorLayer :return: The layer with new ratios. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = recompute_counts_steps['output_layer_name'] exposure = definition(layer.keywords['exposure']) inasafe_fields = layer.keywords['inasafe_fields'] layer.keywords['title'] = output_layer_name if not population_count_field['key'] in inasafe_fields: # There is not a population count field. Let's skip this layer. LOGGER.info( 'Population count field {population_count_field} is not detected ' 'in the exposure. We will not compute a ratio from this field ' 'because the formula needs Population count field. Formula: ' 'ratio = subset count / total count.'.format( population_count_field=population_count_field['key'])) return layer layer.startEditing() mapping = {} non_compulsory_fields = get_non_compulsory_fields( layer_purpose_exposure['key'], exposure['key']) for count_field in non_compulsory_fields: exists = count_field['key'] in inasafe_fields if count_field['key'] in list(count_ratio_mapping.keys()) and exists: ratio_field = definition(count_ratio_mapping[count_field['key']]) field = create_field_from_definition(ratio_field) layer.addAttribute(field) name = ratio_field['field_name'] layer.keywords['inasafe_fields'][ratio_field['key']] = name mapping[count_field['field_name'] ] = layer.fields().lookupField(name) LOGGER.info( 'Count field {count_field} detected in the exposure, we are ' 'going to create a equivalent field {ratio_field} in the ' 'exposure layer.'.format( count_field=count_field['key'], ratio_field=ratio_field['key'])) else: LOGGER.info( 'Count field {count_field} not detected in the exposure. We ' 'will not compute a ratio from this field.'.format( count_field=count_field['key'])) if len(mapping) == 0: # There is not a subset count field. Let's skip this layer. layer.commitChanges() return layer for feature in layer.getFeatures(): total_count = feature[inasafe_fields[population_count_field['key']]] for count_field, index in list(mapping.items()): count = feature[count_field] try: # For #4669, fix always get 0 new_value = count / float(total_count) except TypeError: new_value = '' except ZeroDivisionError: new_value = 0 layer.changeAttributeValue(feature.id(), index, new_value) layer.commitChanges() check_layer(layer) return layer
def add_default_values(layer, callback=None): """Add or fill default values to the layer, see #3325. 1. It doesn't have inasafe_field and it doesn't have inasafe_default_value --> Do nothing. 2. It has inasafe_field and it does not have inasafe_default_value --> Do nothing. 3. It does not have inasafe_field but it has inasafe_default_value --> Create new field, and fill with the default value for all features 4. It has inasafe_field and it has inasafe_default_value --> Replace the null value with the default one. :param layer: The vector layer. :type layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The vector layer with the default values. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = assign_default_values_steps['output_layer_name'] output_layer_name = output_layer_name % layer.keywords['layer_purpose'] processing_step = assign_default_values_steps['step_name'] fields = layer.keywords.get('inasafe_fields') if not isinstance(fields, dict): msg = 'inasafe_fields is missing in keywords from %s' % layer.name() raise InvalidKeywordsForProcessingAlgorithm(msg) defaults = layer.keywords.get('inasafe_default_values') if not defaults: # Case 1 and 2. LOGGER.info( 'inasafe_default_value is not present, we can not fill default ' 'ratios for this layer.') return layer for default in defaults.keys(): field = fields.get(default) target_field = definition(default) layer.startEditing() if not field: # Case 3 LOGGER.info( '{field} is not present but the layer has {value} as a ' 'default for {field}. We create the new field ' '{new_field} with this value.'.format( field=target_field['key'], value=defaults[default], new_field=target_field['field_name'])) new_field = create_field_from_definition(target_field) layer.addAttribute(new_field) new_index = layer.fieldNameIndex(new_field.name()) for feature in layer.getFeatures(): layer.changeAttributeValue( feature.id(), new_index, defaults[default]) layer.keywords['inasafe_fields'][target_field['key']] = ( target_field['field_name']) else: # Case 4 LOGGER.info( '{field} is present and the layer has {value} as a ' 'default for {field}, we MUST do nothing.'.format( field=target_field['key'], value=defaults[default])) index = layer.fieldNameIndex(field) for feature in layer.getFeatures(): if isinstance(feature.attributes()[index], QPyNullVariant): layer.changeAttributeValue( feature.id(), index, defaults[default]) continue if feature.attributes()[index] == '': layer.changeAttributeValue( feature.id(), index, defaults[default]) continue layer.commitChanges() layer.keywords['title'] = output_layer_name check_layer(layer) return layer
def multi_buffering(layer, radii, callback=None): """Buffer a vector layer using many buffers (for volcanoes or rivers). This processing algorithm will keep the original attribute table and will add a new one for the hazard class name according to safe.definitions.fields.hazard_value_field. radii = OrderedDict() radii[500] = 'high' radii[1000] = 'medium' radii[2000] = 'low' Issue https://github.com/inasafe/inasafe/issues/3185 :param layer: The layer to polygonize. :type layer: QgsVectorLayer :param radii: A dictionary of radius. :type radii: OrderedDict :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The buffered vector layer. :rtype: QgsVectorLayer """ # Layer output output_layer_name = buffer_steps['output_layer_name'] processing_step = buffer_steps['step_name'] input_crs = layer.crs() feature_count = layer.featureCount() fields = layer.fields() # Set the new hazard class field. new_field = create_field_from_definition(hazard_class_field) fields.append(new_field) # Set the new buffer distances field. new_field = create_field_from_definition(buffer_distance_field) fields.append(new_field) buffered = create_memory_layer( output_layer_name, QGis.Polygon, input_crs, fields) data_provider = buffered.dataProvider() # Reproject features if needed into UTM if the layer is in 4326. if layer.crs().authid() == 'EPSG:4326': center = layer.extent().center() utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(layer.crs(), utm) reverse_transform = QgsCoordinateTransform(utm, layer.crs()) else: transform = None reverse_transform = None for i, feature in enumerate(layer.getFeatures()): geom = QgsGeometry(feature.geometry()) if transform: geom.transform(transform) inner_ring = None for radius in radii: attributes = feature.attributes() # We add the hazard value name to the attribute table. attributes.append(radii[radius]) # We add the value of buffer distance to the attribute table. attributes.append(radius) circle = geom.buffer(radius, 30) if inner_ring: circle.addRing(inner_ring) inner_ring = circle.asPolygon()[0] new_feature = QgsFeature() if reverse_transform: circle.transform(reverse_transform) new_feature.setGeometry(circle) new_feature.setAttributes(attributes) data_provider.addFeatures([new_feature]) if callback: callback(current=i, maximum=feature_count, step=processing_step) # We transfer keywords to the output. buffered.keywords = layer.keywords buffered.keywords['layer_geometry'] = 'polygon' buffered.keywords['layer_purpose'] = layer_purpose_hazard['key'] buffered.keywords['inasafe_fields'][hazard_class_field['key']] = ( hazard_class_field['field_name']) check_layer(buffered) return buffered
def analysis_summary(aggregate_hazard, analysis): """Compute the summary from the aggregate hazard to analysis. Source layer : | haz_id | haz_class | aggr_id | aggr_name | total_feature | Target layer : | analysis_name | Output layer : | analysis_name | count_hazard_class | affected_count | total | :param aggregate_hazard: The layer to aggregate vector layer. :type aggregate_hazard: QgsVectorLayer :param analysis: The target vector layer where to write statistics. :type analysis: QgsVectorLayer :return: The new target layer with summary. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ source_fields = aggregate_hazard.keywords['inasafe_fields'] target_fields = analysis.keywords['inasafe_fields'] target_compulsory_fields = [ analysis_name_field, ] check_inputs(target_compulsory_fields, target_fields) source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, hazard_id_field, hazard_class_field, total_field ] check_inputs(source_compulsory_fields, source_fields) absolute_values = create_absolute_values_structure( aggregate_hazard, ['all']) hazard_class = source_fields[hazard_class_field['key']] hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class) unique_hazard = list(aggregate_hazard.uniqueValues(hazard_class_index)) hazard_keywords = aggregate_hazard.keywords['hazard_keywords'] hazard = hazard_keywords['hazard'] classification = hazard_keywords['classification'] exposure_keywords = aggregate_hazard.keywords['exposure_keywords'] exposure = exposure_keywords['exposure'] total = source_fields[total_field['key']] flat_table = FlatTable('hazard_class') # First loop over the aggregate_hazard layer request = QgsFeatureRequest() request.setSubsetOfAttributes( [hazard_class, total], aggregate_hazard.fields()) request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregate_hazard.getFeatures(): hazard_value = area[hazard_class_index] value = area[total] if (value == '' or value is None or isnan(value) or (hasattr(value, 'isNull') and value.isNull())): # For isnan, see ticket #3812 value = 0 if (hazard_value == '' or hazard_value is None or (hasattr(hazard_value, 'isNull') and hazard_value.isNull())): hazard_value = 'NULL' flat_table.add_value( value, hazard_class=hazard_value ) # We summarize every absolute values. for field, field_definition in list(absolute_values.items()): value = area[field] if (value == '' or value is None or (hasattr(value, 'isNull') and value.isNull())): value = 0 field_definition[0].add_value( value, all='all' ) analysis.startEditing() shift = analysis.fields().count() counts = [ total_affected_field, total_not_affected_field, total_exposed_field, total_not_exposed_field, total_field] dynamic_structure = [ [hazard_count_field, unique_hazard], ] add_fields( analysis, absolute_values, counts, dynamic_structure) affected_sum = 0 not_affected_sum = 0 not_exposed_sum = 0 # Summarization summary_values = {} for key, summary_rule in list(summary_rules.items()): input_field = summary_rule['input_field'] case_field = summary_rule['case_field'] if aggregate_hazard.fields().lookupField(input_field['field_name']) \ == -1: continue if aggregate_hazard.fields().lookupField(case_field['field_name']) \ == -1: continue summary_value = 0 for area in aggregate_hazard.getFeatures(): case_value = area[case_field['field_name']] if case_value in summary_rule['case_values']: summary_value += area[input_field['field_name']] summary_values[key] = summary_value for area in analysis.getFeatures(request): total = 0 for i, val in enumerate(unique_hazard): if (val == '' or val is None or (hasattr(val, 'isNull') and val.isNull())): val = 'NULL' sum = flat_table.get_value(hazard_class=val) total += sum analysis.changeAttributeValue(area.id(), shift + i, sum) affected = post_processor_affected_function( exposure=exposure, hazard=hazard, classification=classification, hazard_class=val) if affected == not_exposed_class['key']: not_exposed_sum += sum elif affected: affected_sum += sum else: not_affected_sum += sum # Total Affected field analysis.changeAttributeValue( area.id(), shift + len(unique_hazard), affected_sum) # Total Not affected field analysis.changeAttributeValue( area.id(), shift + len(unique_hazard) + 1, not_affected_sum) # Total Exposed field analysis.changeAttributeValue( area.id(), shift + len(unique_hazard) + 2, total - not_exposed_sum) # Total Not exposed field analysis.changeAttributeValue( area.id(), shift + len(unique_hazard) + 3, not_exposed_sum) # Total field analysis.changeAttributeValue( area.id(), shift + len(unique_hazard) + 4, total) # Any absolute postprocessors for i, field in enumerate(absolute_values.values()): value = field[0].get_value( all='all' ) analysis.changeAttributeValue( area.id(), shift + len(unique_hazard) + 5 + i, value) # Summarizer of custom attributes for key, summary_value in list(summary_values.items()): summary_field = summary_rules[key]['summary_field'] field = create_field_from_definition(summary_field) analysis.addAttribute(field) field_index = analysis.fields().lookupField(field.name()) # noinspection PyTypeChecker analysis.keywords['inasafe_fields'][summary_field['key']] = ( summary_field['field_name']) analysis.changeAttributeValue( area.id(), field_index, summary_value) # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is not # enough. ET 13/02/17 # total_computed = ( # affected_sum + not_affected_sum + not_exposed_sum) # if not -1 < (total_computed - total) < 1: # raise ComputationError analysis.commitChanges() analysis.keywords['title'] = layer_purpose_analysis_impacted['name'] if qgis_version() >= 21600: analysis.setName(analysis.keywords['title']) else: analysis.setLayerName(analysis.keywords['title']) analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key'] check_layer(analysis) return analysis
def analysis_summary(aggregate_hazard, analysis): """Compute the summary from the aggregate hazard to analysis. Source layer : | haz_id | haz_class | aggr_id | aggr_name | total_feature | Target layer : | analysis_name | Output layer : | analysis_name | count_hazard_class | affected_count | total | :param aggregate_hazard: The layer to aggregate vector layer. :type aggregate_hazard: QgsVectorLayer :param analysis: The target vector layer where to write statistics. :type analysis: QgsVectorLayer :return: The new target layer with summary. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ source_fields = aggregate_hazard.keywords['inasafe_fields'] target_fields = analysis.keywords['inasafe_fields'] target_compulsory_fields = [ analysis_name_field, ] check_inputs(target_compulsory_fields, target_fields) source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, hazard_id_field, hazard_class_field, total_field ] check_inputs(source_compulsory_fields, source_fields) absolute_values = create_absolute_values_structure(aggregate_hazard, ['all']) hazard_class = source_fields[hazard_class_field['key']] hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class) unique_hazard = list(aggregate_hazard.uniqueValues(hazard_class_index)) hazard_keywords = aggregate_hazard.keywords['hazard_keywords'] hazard = hazard_keywords['hazard'] classification = hazard_keywords['classification'] exposure_keywords = aggregate_hazard.keywords['exposure_keywords'] exposure = exposure_keywords['exposure'] total = source_fields[total_field['key']] flat_table = FlatTable('hazard_class') # First loop over the aggregate_hazard layer request = QgsFeatureRequest() request.setSubsetOfAttributes([hazard_class, total], aggregate_hazard.fields()) request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregate_hazard.getFeatures(): hazard_value = area[hazard_class_index] value = area[total] if (value == '' or value is None or isnan(value) or (hasattr(value, 'isNull') and value.isNull())): # For isnan, see ticket #3812 value = 0 if (hazard_value == '' or hazard_value is None or (hasattr(hazard_value, 'isNull') and hazard_value.isNull())): hazard_value = 'NULL' flat_table.add_value(value, hazard_class=hazard_value) # We summarize every absolute values. for field, field_definition in list(absolute_values.items()): value = area[field] if (value == '' or value is None or (hasattr(value, 'isNull') and value.isNull())): value = 0 field_definition[0].add_value(value, all='all') analysis.startEditing() shift = analysis.fields().count() counts = [ total_affected_field, total_not_affected_field, total_exposed_field, total_not_exposed_field, total_field ] dynamic_structure = [ [hazard_count_field, unique_hazard], ] add_fields(analysis, absolute_values, counts, dynamic_structure) affected_sum = 0 not_affected_sum = 0 not_exposed_sum = 0 # Summarization summary_values = {} for key, summary_rule in list(summary_rules.items()): input_field = summary_rule['input_field'] case_field = summary_rule['case_field'] if aggregate_hazard.fields().lookupField(input_field['field_name']) \ == -1: continue if aggregate_hazard.fields().lookupField(case_field['field_name']) \ == -1: continue summary_value = 0 for area in aggregate_hazard.getFeatures(): case_value = area[case_field['field_name']] if case_value in summary_rule['case_values']: summary_value += area[input_field['field_name']] summary_values[key] = summary_value for area in analysis.getFeatures(request): total = 0 for i, val in enumerate(unique_hazard): if (val == '' or val is None or (hasattr(val, 'isNull') and val.isNull())): val = 'NULL' sum = flat_table.get_value(hazard_class=val) total += sum analysis.changeAttributeValue(area.id(), shift + i, sum) affected = post_processor_affected_function( exposure=exposure, hazard=hazard, classification=classification, hazard_class=val) if affected == not_exposed_class['key']: not_exposed_sum += sum elif affected: affected_sum += sum else: not_affected_sum += sum # Total Affected field analysis.changeAttributeValue(area.id(), shift + len(unique_hazard), affected_sum) # Total Not affected field analysis.changeAttributeValue(area.id(), shift + len(unique_hazard) + 1, not_affected_sum) # Total Exposed field analysis.changeAttributeValue(area.id(), shift + len(unique_hazard) + 2, total - not_exposed_sum) # Total Not exposed field analysis.changeAttributeValue(area.id(), shift + len(unique_hazard) + 3, not_exposed_sum) # Total field analysis.changeAttributeValue(area.id(), shift + len(unique_hazard) + 4, total) # Any absolute postprocessors for i, field in enumerate(absolute_values.values()): value = field[0].get_value(all='all') analysis.changeAttributeValue(area.id(), shift + len(unique_hazard) + 5 + i, value) # Summarizer of custom attributes for key, summary_value in list(summary_values.items()): summary_field = summary_rules[key]['summary_field'] field = create_field_from_definition(summary_field) analysis.addAttribute(field) field_index = analysis.fields().lookupField(field.name()) # noinspection PyTypeChecker analysis.keywords['inasafe_fields'][summary_field['key']] = ( summary_field['field_name']) analysis.changeAttributeValue(area.id(), field_index, summary_value) # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is not # enough. ET 13/02/17 # total_computed = ( # affected_sum + not_affected_sum + not_exposed_sum) # if not -1 < (total_computed - total) < 1: # raise ComputationError analysis.commitChanges() analysis.keywords['title'] = layer_purpose_analysis_impacted['name'] if qgis_version() >= 21600: analysis.setName(analysis.keywords['title']) else: analysis.setLayerName(analysis.keywords['title']) analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key'] check_layer(analysis) return analysis
def exposure_summary_table( aggregate_hazard, exposure_summary=None, callback=None): """Compute the summary from the aggregate hazard to analysis. Source layer : | haz_id | haz_class | aggr_id | aggr_name | exposure_count | Output layer : | exp_type | count_hazard_class | total | :param aggregate_hazard: The layer to aggregate vector layer. :type aggregate_hazard: QgsVectorLayer :param exposure_summary: The layer impact layer. :type exposure_summary: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The new tabular table, without geometry. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = summary_4_exposure_summary_table_steps[ 'output_layer_name'] source_fields = aggregate_hazard.keywords['inasafe_fields'] source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, hazard_id_field, hazard_class_field, affected_field, total_field ] check_inputs(source_compulsory_fields, source_fields) absolute_values = create_absolute_values_structure( aggregate_hazard, ['all']) hazard_class = source_fields[hazard_class_field['key']] hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class) unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index) unique_exposure = read_dynamic_inasafe_field( source_fields, exposure_count_field) flat_table = FlatTable('hazard_class', 'exposure_class') request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregate_hazard.getFeatures(): hazard_value = area[hazard_class_index] for exposure in unique_exposure: key_name = exposure_count_field['key'] % exposure field_name = source_fields[key_name] exposure_count = area[field_name] if not exposure_count: exposure_count = 0 flat_table.add_value( exposure_count, hazard_class=hazard_value, exposure_class=exposure ) # We summarize every absolute values. for field, field_definition in list(absolute_values.items()): value = area[field] if not value: value = 0 field_definition[0].add_value( value, all='all' ) tabular = create_memory_layer(output_layer_name, QgsWkbTypes.NullGeometry) tabular.startEditing() field = create_field_from_definition(exposure_type_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][exposure_type_field['key']] = ( exposure_type_field['field_name']) hazard_keywords = aggregate_hazard.keywords['hazard_keywords'] hazard = hazard_keywords['hazard'] classification = hazard_keywords['classification'] exposure_keywords = aggregate_hazard.keywords['exposure_keywords'] exposure = exposure_keywords['exposure'] hazard_affected = {} for hazard_class in unique_hazard: if (hazard_class == '' or hazard_class is None or (hasattr(hazard_class, 'isNull') and hazard_class.isNull())): hazard_class = 'NULL' field = create_field_from_definition(hazard_count_field, hazard_class) tabular.addAttribute(field) key = hazard_count_field['key'] % hazard_class value = hazard_count_field['field_name'] % hazard_class tabular.keywords['inasafe_fields'][key] = value hazard_affected[hazard_class] = post_processor_affected_function( exposure=exposure, hazard=hazard, classification=classification, hazard_class=hazard_class ) field = create_field_from_definition(total_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_affected_field['key']] = ( total_affected_field['field_name']) # essentially have the same value as NULL_hazard_count # but with this, make sure that it exists in layer so it can be used for # reporting, and can be referenced to fields.py to take the label. field = create_field_from_definition(total_not_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_affected_field['key']] = ( total_not_affected_field['field_name']) field = create_field_from_definition(total_not_exposed_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_exposed_field['key']] = ( total_not_exposed_field['field_name']) field = create_field_from_definition(total_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_field['key']] = ( total_field['field_name']) summarization_dicts = {} if exposure_summary: summarization_dicts = summarize_result(exposure_summary) sorted_keys = sorted(summarization_dicts.keys()) for key in sorted_keys: summary_field = summary_rules[key]['summary_field'] field = create_field_from_definition(summary_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][ summary_field['key']] = ( summary_field['field_name']) # Only add absolute value if there is no summarization / no exposure # classification if not summarization_dicts: # For each absolute values for absolute_field in list(absolute_values.keys()): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) tabular.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] tabular.keywords['inasafe_fields'][key] = value for exposure_type in unique_exposure: feature = QgsFeature() attributes = [exposure_type] total_affected = 0 total_not_affected = 0 total_not_exposed = 0 total = 0 for hazard_class in unique_hazard: if hazard_class == '' or hazard_class is None: hazard_class = 'NULL' value = flat_table.get_value( hazard_class=hazard_class, exposure_class=exposure_type ) attributes.append(value) if hazard_affected[hazard_class] == not_exposed_class['key']: total_not_exposed += value elif hazard_affected[hazard_class]: total_affected += value else: total_not_affected += value total += value attributes.append(total_affected) attributes.append(total_not_affected) attributes.append(total_not_exposed) attributes.append(total) if summarization_dicts: for key in sorted_keys: attributes.append(summarization_dicts[key].get( exposure_type, 0)) else: for i, field in enumerate(absolute_values.values()): value = field[0].get_value( all='all' ) attributes.append(value) feature.setAttributes(attributes) tabular.addFeature(feature) # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is # not enough. ET 13/02/17 # total_computed = ( # total_affected + total_not_affected + total_not_exposed) # if not -1 < (total_computed - total) < 1: # raise ComputationError tabular.commitChanges() tabular.keywords['title'] = layer_purpose_exposure_summary_table['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['layer_purpose'] = layer_purpose_exposure_summary_table[ 'key'] check_layer(tabular, has_geometry=False) return tabular
def analysis_eartquake_summary(aggregation, analysis, callback=None): """Compute the summary from the aggregation to the analysis. Source layer : | aggr_id | aggr_name | total_feature | total_displaced | total_fatalities Target layer : | analysis_id | Output layer : | analysis_id | total_feature | total_displaced | total_fatalities | :param aggregation: The layer to aggregate vector layer. :type aggregation: QgsVectorLayer :param analysis: The target vector layer where to write statistics. :type analysis: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The new target layer with summary. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = summary_3_analysis_steps['output_layer_name'] processing_step = summary_3_analysis_steps['step_name'] source_fields = aggregation.keywords['inasafe_fields'] target_fields = analysis.keywords['inasafe_fields'] target_compulsory_fields = [ analysis_id_field, analysis_name_field, ] check_inputs(target_compulsory_fields, target_fields) source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, population_count_field, fatalities_field, displaced_field, ] check_inputs(source_compulsory_fields, source_fields) analysis.startEditing() count_hazard_level = {} index_hazard_level = {} classification = definition( aggregation.keywords['hazard_keywords']['classification']) hazard_classes = classification['classes'] for hazard_class in hazard_classes: key = hazard_class['key'] new_field = create_field_from_definition(hazard_count_field, key) analysis.addAttribute(new_field) new_index = analysis.fieldNameIndex(new_field.name()) count_hazard_level[key] = 0 index_hazard_level[key] = new_index target_fields[hazard_count_field['key'] % key] = (hazard_count_field['field_name'] % key) summaries = {} # Summary is a dictionary with a tuple a key and the value to write in # the output layer as a value. # tuple (index in the aggregation layer, index in the analysis layer) : val for layer_field in source_fields: for definition_field in count_fields: if layer_field == definition_field['key']: field_name = source_fields[layer_field] index = aggregation.fieldNameIndex(field_name) new_field = create_field_from_definition(definition_field) analysis.addAttribute(new_field) new_index = analysis.fieldNameIndex(new_field.name()) summaries[(index, new_index)] = 0 target_fields[definition_field['key']] = ( definition_field['field_name']) request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregation.getFeatures(): for index in summaries.keys(): value = area[index[0]] if not value or isinstance(value, QPyNullVariant): value = 0 summaries[index] += value for mmi_level in range(2, 11): field_name = (population_exposed_per_mmi_field['field_name'] % mmi_level) index = aggregation.fieldNameIndex(field_name) if index > 0: value = area[index] if not value or isinstance(value, QPyNullVariant): value = 0 hazard_class = from_mmi_to_hazard_class( mmi_level, classification['key']) if hazard_class: count_hazard_level[hazard_class] += value for row in analysis.getFeatures(): # We should have only one row in the analysis layer. for field in summaries.keys(): analysis.changeAttributeValue(row.id(), field[1], summaries[field]) for hazard_level, index in index_hazard_level.iteritems(): analysis.changeAttributeValue(row.id(), index, count_hazard_level[hazard_level]) analysis.commitChanges() analysis.keywords['title'] = output_layer_name analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key'] analysis.keywords['hazard_keywords'] = dict( aggregation.keywords['hazard_keywords']) analysis.keywords['exposure_keywords'] = dict( aggregation.keywords['exposure_keywords']) check_layer(analysis) return analysis
def zonal_stats(raster, vector): """Reclassify a continuous raster layer. Issue https://github.com/inasafe/inasafe/issues/3190 The algorithm will take care about projections. We don't want to reproject the raster layer. So if CRS are different, we reproject the vector layer and then we do a lookup from the reprojected layer to the original vector layer. :param raster: The raster layer. :type raster: QgsRasterLayer :param vector: The vector layer. :type vector: QgsVectorLayer :return: The output of the zonal stats. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = zonal_stats_steps['output_layer_name'] exposure = raster.keywords['exposure'] if raster.crs().authid() != vector.crs().authid(): layer = reproject(vector, raster.crs()) # We prepare the copy output_layer = create_memory_layer( output_layer_name, vector.geometryType(), vector.crs(), vector.fields() ) copy_layer(vector, output_layer) else: layer = create_memory_layer( output_layer_name, vector.geometryType(), vector.crs(), vector.fields() ) copy_layer(vector, layer) input_band = layer.keywords.get('active_band', 1) analysis = QgsZonalStatistics( layer, raster, 'exposure_', input_band, QgsZonalStatistics.Sum) result = analysis.calculateStatistics(None) LOGGER.debug(tr('Zonal stats on %s : %s' % (raster.source(), result))) output_field = exposure_count_field['field_name'] % exposure if raster.crs().authid() != vector.crs().authid(): output_layer.startEditing() field = create_field_from_definition( exposure_count_field, exposure) output_layer.addAttribute(field) new_index = output_layer.fields().lookupField(field.name()) old_index = layer.fields().lookupField('exposure_sum') for feature_input, feature_output in zip( layer.getFeatures(), output_layer.getFeatures()): output_layer.changeAttributeValue( feature_input.id(), new_index, feature_input[old_index]) output_layer.commitChanges() layer = output_layer else: fields_to_rename = { 'exposure_sum': output_field } if qgis_version() >= 21600: rename_fields(layer, fields_to_rename) else: copy_fields(layer, fields_to_rename) remove_fields(layer, list(fields_to_rename.keys())) layer.commitChanges() # The zonal stats is producing some None values. We need to fill these # with 0. See issue : #3778 # We should start a new editing session as previous fields need to be # committed first. layer.startEditing() request = QgsFeatureRequest() expression = '\"%s\" is None' % output_field request.setFilterExpression(expression) request.setFlags(QgsFeatureRequest.NoGeometry) index = layer.fields().lookupField(output_field) for feature in layer.getFeatures(): if feature[output_field] is None: layer.changeAttributeValue(feature.id(), index, 0) layer.commitChanges() layer.keywords = raster.keywords.copy() layer.keywords['inasafe_fields'] = vector.keywords['inasafe_fields'].copy() layer.keywords['inasafe_default_values'] = ( raster.keywords['inasafe_default_values'].copy()) key = exposure_count_field['key'] % raster.keywords['exposure'] # Special case here, one field is the exposure count and the total. layer.keywords['inasafe_fields'][key] = output_field layer.keywords['inasafe_fields'][total_field['key']] = output_field layer.keywords['exposure_keywords'] = raster.keywords.copy() layer.keywords['hazard_keywords'] = vector.keywords[ 'hazard_keywords'].copy() layer.keywords['aggregation_keywords'] = ( vector.keywords['aggregation_keywords']) layer.keywords['layer_purpose'] = ( layer_purpose_aggregate_hazard_impacted['key']) layer.keywords['title'] = output_layer_name check_layer(layer) return layer
def from_counts_to_ratios(layer): """Transform counts to ratios. Formula: ratio = subset count / total count :param layer: The vector layer. :type layer: QgsVectorLayer :return: The layer with new ratios. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = recompute_counts_steps['output_layer_name'] exposure = definition(layer.keywords['exposure']) inasafe_fields = layer.keywords['inasafe_fields'] layer.keywords['title'] = output_layer_name if not population_count_field['key'] in inasafe_fields: # There is not a population count field. Let's skip this layer. LOGGER.info( 'Population count field {population_count_field} is not detected ' 'in the exposure. We will not compute a ratio from this field ' 'because the formula needs Population count field. Formula: ' 'ratio = subset count / total count.'.format( population_count_field=population_count_field['key'])) return layer layer.startEditing() mapping = {} non_compulsory_fields = get_non_compulsory_fields( layer_purpose_exposure['key'], exposure['key']) for count_field in non_compulsory_fields: exists = count_field['key'] in inasafe_fields if count_field['key'] in list(count_ratio_mapping.keys()) and exists: ratio_field = definition(count_ratio_mapping[count_field['key']]) field = create_field_from_definition(ratio_field) layer.addAttribute(field) name = ratio_field['field_name'] layer.keywords['inasafe_fields'][ratio_field['key']] = name mapping[count_field['field_name']] = layer.fields().lookupField( name) LOGGER.info( 'Count field {count_field} detected in the exposure, we are ' 'going to create a equivalent field {ratio_field} in the ' 'exposure layer.'.format(count_field=count_field['key'], ratio_field=ratio_field['key'])) else: LOGGER.info( 'Count field {count_field} not detected in the exposure. We ' 'will not compute a ratio from this field.'.format( count_field=count_field['key'])) if len(mapping) == 0: # There is not a subset count field. Let's skip this layer. layer.commitChanges() return layer for feature in layer.getFeatures(): total_count = feature[inasafe_fields[population_count_field['key']]] for count_field, index in list(mapping.items()): count = feature[count_field] try: # For #4669, fix always get 0 new_value = count / float(total_count) except TypeError: new_value = '' except ZeroDivisionError: new_value = 0 layer.changeAttributeValue(feature.id(), index, new_value) layer.commitChanges() check_layer(layer) return layer
def exposure_summary_table(aggregate_hazard, exposure_summary=None, callback=None): """Compute the summary from the aggregate hazard to analysis. Source layer : | haz_id | haz_class | aggr_id | aggr_name | exposure_count | Output layer : | exp_type | count_hazard_class | total | :param aggregate_hazard: The layer to aggregate vector layer. :type aggregate_hazard: QgsVectorLayer :param exposure_summary: The layer impact layer. :type exposure_summary: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The new tabular table, without geometry. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = summary_4_exposure_summary_table_steps[ 'output_layer_name'] source_fields = aggregate_hazard.keywords['inasafe_fields'] source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, hazard_id_field, hazard_class_field, affected_field, total_field ] check_inputs(source_compulsory_fields, source_fields) absolute_values = create_absolute_values_structure(aggregate_hazard, ['all']) hazard_class = source_fields[hazard_class_field['key']] hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class) unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index) unique_exposure = read_dynamic_inasafe_field(source_fields, exposure_count_field) flat_table = FlatTable('hazard_class', 'exposure_class') request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregate_hazard.getFeatures(): hazard_value = area[hazard_class_index] for exposure in unique_exposure: key_name = exposure_count_field['key'] % exposure field_name = source_fields[key_name] exposure_count = area[field_name] if not exposure_count: exposure_count = 0 flat_table.add_value(exposure_count, hazard_class=hazard_value, exposure_class=exposure) # We summarize every absolute values. for field, field_definition in list(absolute_values.items()): value = area[field] if not value: value = 0 field_definition[0].add_value(value, all='all') tabular = create_memory_layer(output_layer_name, QgsWkbTypes.NullGeometry) tabular.startEditing() field = create_field_from_definition(exposure_type_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][exposure_type_field['key']] = ( exposure_type_field['field_name']) hazard_keywords = aggregate_hazard.keywords['hazard_keywords'] hazard = hazard_keywords['hazard'] classification = hazard_keywords['classification'] exposure_keywords = aggregate_hazard.keywords['exposure_keywords'] exposure = exposure_keywords['exposure'] hazard_affected = {} for hazard_class in unique_hazard: if (hazard_class == '' or hazard_class is None or (hasattr(hazard_class, 'isNull') and hazard_class.isNull())): hazard_class = 'NULL' field = create_field_from_definition(hazard_count_field, hazard_class) tabular.addAttribute(field) key = hazard_count_field['key'] % hazard_class value = hazard_count_field['field_name'] % hazard_class tabular.keywords['inasafe_fields'][key] = value hazard_affected[hazard_class] = post_processor_affected_function( exposure=exposure, hazard=hazard, classification=classification, hazard_class=hazard_class) field = create_field_from_definition(total_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_affected_field['key']] = ( total_affected_field['field_name']) # essentially have the same value as NULL_hazard_count # but with this, make sure that it exists in layer so it can be used for # reporting, and can be referenced to fields.py to take the label. field = create_field_from_definition(total_not_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_affected_field['key']] = ( total_not_affected_field['field_name']) field = create_field_from_definition(total_not_exposed_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_exposed_field['key']] = ( total_not_exposed_field['field_name']) field = create_field_from_definition(total_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_field['key']] = ( total_field['field_name']) summarization_dicts = {} if exposure_summary: summarization_dicts = summarize_result(exposure_summary) sorted_keys = sorted(summarization_dicts.keys()) for key in sorted_keys: summary_field = summary_rules[key]['summary_field'] field = create_field_from_definition(summary_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][summary_field['key']] = ( summary_field['field_name']) # Only add absolute value if there is no summarization / no exposure # classification if not summarization_dicts: # For each absolute values for absolute_field in list(absolute_values.keys()): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) tabular.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] tabular.keywords['inasafe_fields'][key] = value for exposure_type in unique_exposure: feature = QgsFeature() attributes = [exposure_type] total_affected = 0 total_not_affected = 0 total_not_exposed = 0 total = 0 for hazard_class in unique_hazard: if hazard_class == '' or hazard_class is None: hazard_class = 'NULL' value = flat_table.get_value(hazard_class=hazard_class, exposure_class=exposure_type) attributes.append(value) if hazard_affected[hazard_class] == not_exposed_class['key']: total_not_exposed += value elif hazard_affected[hazard_class]: total_affected += value else: total_not_affected += value total += value attributes.append(total_affected) attributes.append(total_not_affected) attributes.append(total_not_exposed) attributes.append(total) if summarization_dicts: for key in sorted_keys: attributes.append(summarization_dicts[key].get( exposure_type, 0)) else: for i, field in enumerate(absolute_values.values()): value = field[0].get_value(all='all') attributes.append(value) feature.setAttributes(attributes) tabular.addFeature(feature) # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is # not enough. ET 13/02/17 # total_computed = ( # total_affected + total_not_affected + total_not_exposed) # if not -1 < (total_computed - total) < 1: # raise ComputationError tabular.commitChanges() tabular.keywords['title'] = layer_purpose_exposure_summary_table['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['layer_purpose'] = layer_purpose_exposure_summary_table[ 'key'] check_layer(tabular, has_geometry=False) return tabular
def make_summary_layer(exposed, aggregation): """Add fields to the aggregation given the dictionary of affected people. The dictionary contains affected people counts per hazard and aggregation zones. :param exposed: The dictionary with affected people counts per hazard and aggregation zones. :type exposed: dict :param aggregation: The aggregation layer where we write statistics. :type aggregation: QgsVectorLayer :return: Tuple with the aggregation layer and a dictionary with totals. :rtype: tuple(QgsVectorLayer, dict) """ field_mapping = {} classification_key = ( aggregation.keywords['hazard_keywords']['classification']) aggregation.startEditing() inasafe_fields = aggregation.keywords['inasafe_fields'] id_field = inasafe_fields[aggregation_id_field['key']] field = create_field_from_definition(population_count_field) aggregation.addAttribute(field) field_mapping[field.name()] = aggregation.fieldNameIndex(field.name()) inasafe_fields[population_count_field['key']] = ( population_count_field['field_name']) field = create_field_from_definition(fatalities_field) aggregation.addAttribute(field) field_mapping[field.name()] = aggregation.fieldNameIndex(field.name()) inasafe_fields[fatalities_field['key']] = fatalities_field['field_name'] field = create_field_from_definition(displaced_field) aggregation.addAttribute(field) field_mapping[field.name()] = aggregation.fieldNameIndex(field.name()) inasafe_fields[displaced_field['key']] = displaced_field['field_name'] for mmi in xrange(2, 11): field = create_field_from_definition(population_exposed_per_mmi_field, mmi) aggregation.addAttribute(field) field_mapping[field.name()] = aggregation.fieldNameIndex(field.name()) value = population_exposed_per_mmi_field['field_name'] % mmi inasafe_fields[population_exposed_per_mmi_field['key'] % mmi] = value field = create_field_from_definition(fatalities_per_mmi_field, mmi) aggregation.addAttribute(field) field_mapping[field.name()] = aggregation.fieldNameIndex(field.name()) value = fatalities_per_mmi_field['field_name'] % mmi inasafe_fields[fatalities_per_mmi_field['key'] % mmi] = value field = create_field_from_definition(population_displaced_per_mmi, mmi) aggregation.addAttribute(field) field_mapping[field.name()] = aggregation.fieldNameIndex(field.name()) value = population_displaced_per_mmi['field_name'] % mmi inasafe_fields[population_displaced_per_mmi['key'] % mmi] = value exposed_per_agg_zone = {} for (mmi, agg), count in exposed.iteritems(): if agg not in exposed_per_agg_zone: exposed_per_agg_zone[agg] = {} exposed_per_agg_zone[agg][mmi] = count for agg_feature in aggregation.getFeatures(): agg_zone = agg_feature[id_field] total_exposed = 0 total_fatalities = 0 total_displaced = 0 LOGGER.debug('Aggregation %s is being processed by EQ IF' % agg_zone) try: stats_aggregation = exposed_per_agg_zone[agg_zone] except KeyError: # 1. We might have aggregation area outside of the hazard and # exposure extent. # 2. We got some broken datatset with an empty geometry for the # aggregation. # See ticket : https://github.com/inasafe/inasafe/issues/3803 continue for mmi, mmi_exposed in stats_aggregation.iteritems(): mmi_fatalities = int( # rounding down mmi_exposed * fatality_rate(mmi, classification_key)) mmi_displaced = ((mmi_exposed - mmi_fatalities) * displacement_rate(mmi, classification_key)) aggregation.changeAttributeValue( agg_feature.id(), field_mapping['mmi_%d_exposed' % mmi], mmi_exposed) aggregation.changeAttributeValue( agg_feature.id(), field_mapping['mmi_%d_fatalities' % mmi], mmi_fatalities) aggregation.changeAttributeValue( agg_feature.id(), field_mapping['mmi_%d_displaced' % mmi], mmi_displaced) total_exposed += mmi_exposed total_fatalities += mmi_fatalities total_displaced += mmi_displaced aggregation.changeAttributeValue( agg_feature.id(), field_mapping[population_count_field['field_name']], total_exposed) aggregation.changeAttributeValue( agg_feature.id(), field_mapping[fatalities_field['field_name']], total_fatalities) aggregation.changeAttributeValue( agg_feature.id(), field_mapping[displaced_field['field_name']], total_displaced) aggregation.keywords['layer_purpose'] = ( layer_purpose_aggregation_summary['key']) aggregation.keywords['title'] = layer_purpose_aggregation_summary['key'] return aggregation
def from_counts_to_ratios(layer, callback=None): """Transform counts to ratios. Formula: ratio = subset count / total count :param layer: The vector layer. :type layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The layer with new ratios. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = recompute_counts_steps['output_layer_name'] processing_step = recompute_counts_steps['step_name'] exposure = definition(layer.keywords['exposure']) inasafe_fields = layer.keywords['inasafe_fields'] layer.keywords['title'] = output_layer_name if not population_count_field['key'] in inasafe_fields: # There is not a population count field. Let's skip this layer. LOGGER.info( 'Population count field {population_count_field} is not detected ' 'in the exposure. We will not compute a ratio from this field ' 'because the formula needs Population count field. Formula: ' 'ratio = subset count / total count.'.format( population_count_field=population_count_field['key'])) return layer layer.startEditing() mapping = {} non_compulsory_fields = get_non_compulsory_fields( layer_purpose_exposure['key'], exposure['key']) for count_field in non_compulsory_fields: exists = count_field['key'] in inasafe_fields if count_field['key'] in count_ratio_mapping.keys() and exists: ratio_field = definition(count_ratio_mapping[count_field['key']]) field = create_field_from_definition(ratio_field) layer.addAttribute(field) name = ratio_field['field_name'] layer.keywords['inasafe_fields'][ratio_field['key']] = name mapping[count_field['field_name']] = layer.fieldNameIndex(name) LOGGER.info( 'Count field {count_field} detected in the exposure, we are ' 'going to create a equivalent field {ratio_field} in the ' 'exposure layer.'.format( count_field=count_field['key'], ratio_field=ratio_field['key'])) else: LOGGER.info( 'Count field {count_field} not detected in the exposure. We ' 'will not compute a ratio from this field.'.format( count_field=count_field['key'])) if len(mapping) == 0: # There is not a subset count field. Let's skip this layer. layer.commitChanges() return layer for feature in layer.getFeatures(): total_count = feature[inasafe_fields[population_count_field['key']]] for count_field, index in mapping.iteritems(): count = feature[count_field] try: new_value = count / total_count except TypeError: new_value = '' layer.changeAttributeValue(feature.id(), index, new_value) layer.commitChanges() check_layer(layer) return layer
def zonal_stats(raster, vector): """Reclassify a continuous raster layer. Issue https://github.com/inasafe/inasafe/issues/3190 The algorithm will take care about projections. We don't want to reproject the raster layer. So if CRS are different, we reproject the vector layer and then we do a lookup from the reprojected layer to the original vector layer. :param raster: The raster layer. :type raster: QgsRasterLayer :param vector: The vector layer. :type vector: QgsVectorLayer :return: The output of the zonal stats. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = zonal_stats_steps['output_layer_name'] exposure = raster.keywords['exposure'] if raster.crs().authid() != vector.crs().authid(): layer = reproject(vector, raster.crs()) # We prepare the copy output_layer = create_memory_layer( output_layer_name, vector.geometryType(), vector.crs(), vector.fields() ) copy_layer(vector, output_layer) else: layer = create_memory_layer( output_layer_name, vector.geometryType(), vector.crs(), vector.fields() ) copy_layer(vector, layer) input_band = layer.keywords.get('active_band', 1) analysis = QgsZonalStatistics( layer, raster, 'exposure_', input_band, QgsZonalStatistics.Sum) result = analysis.calculateStatistics(None) LOGGER.debug(tr('Zonal stats on %s : %s' % (raster.source(), result))) output_field = exposure_count_field['field_name'] % exposure if raster.crs().authid() != vector.crs().authid(): output_layer.startEditing() field = create_field_from_definition( exposure_count_field, exposure) output_layer.addAttribute(field) new_index = output_layer.fields().lookupField(field.name()) old_index = layer.fields().lookupField('exposure_sum') for feature_input, feature_output in zip( layer.getFeatures(), output_layer.getFeatures()): output_layer.changeAttributeValue( feature_input.id(), new_index, feature_input[old_index]) output_layer.commitChanges() layer = output_layer else: fields_to_rename = { 'exposure_sum': output_field } if qgis_version() >= 21600: rename_fields(layer, fields_to_rename) else: copy_fields(layer, fields_to_rename) remove_fields(layer, list(fields_to_rename.keys())) layer.commitChanges() # The zonal stats is producing some None values. We need to fill these # with 0. See issue : #3778 # We should start a new editing session as previous fields need to be # committed first. layer.startEditing() request = QgsFeatureRequest() expression = '\"%s\" is None' % output_field request.setFilterExpression(expression) request.setFlags(QgsFeatureRequest.NoGeometry) index = layer.fields().lookupField(output_field) for feature in layer.getFeatures(): if feature[output_field] is None: layer.changeAttributeValue(feature.id(), index, 0) layer.commitChanges() layer.keywords = raster.keywords.copy() layer.keywords['inasafe_fields'] = vector.keywords['inasafe_fields'].copy() layer.keywords['inasafe_default_values'] = ( raster.keywords['inasafe_default_values'].copy()) key = exposure_count_field['key'] % raster.keywords['exposure'] # Special case here, one field is the exposure count and the total. layer.keywords['inasafe_fields'][key] = output_field layer.keywords['inasafe_fields'][total_field['key']] = output_field layer.keywords['exposure_keywords'] = raster.keywords.copy() layer.keywords['hazard_keywords'] = vector.keywords[ 'hazard_keywords'].copy() layer.keywords['aggregation_keywords'] = ( vector.keywords['aggregation_keywords']) layer.keywords['layer_purpose'] = ( layer_purpose_aggregate_hazard_impacted['key']) layer.keywords['title'] = output_layer_name check_layer(layer) return layer
def from_counts_to_ratios(layer, callback=None): """Transform counts to ratios. Formula: ratio = subset count / total count :param layer: The vector layer. :type layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The layer with new ratios. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = recompute_counts_steps['output_layer_name'] processing_step = recompute_counts_steps['step_name'] exposure = definition(layer.keywords['exposure']) inasafe_fields = layer.keywords['inasafe_fields'] layer.keywords['title'] = output_layer_name if not population_count_field['key'] in inasafe_fields: # There is not a population count field. Let's skip this layer. LOGGER.info( 'Population count field {population_count_field} is not detected ' 'in the exposure. We will not compute a ratio from this field ' 'because the formula needs Population count field. Formula: ' 'ratio = subset count / total count.'.format( population_count_field=population_count_field['key'])) return layer layer.startEditing() mapping = {} non_compulsory_fields = get_non_compulsory_fields( layer_purpose_exposure['key'], exposure['key']) for count_field in non_compulsory_fields: exists = count_field['key'] in inasafe_fields if count_field['key'] in count_ratio_mapping.keys() and exists: ratio_field = definition(count_ratio_mapping[count_field['key']]) field = create_field_from_definition(ratio_field) layer.addAttribute(field) name = ratio_field['field_name'] layer.keywords['inasafe_fields'][ratio_field['key']] = name mapping[count_field['field_name']] = layer.fieldNameIndex(name) LOGGER.info( 'Count field {count_field} detected in the exposure, we are ' 'going to create a equivalent field {ratio_field} in the ' 'exposure layer.'.format(count_field=count_field['key'], ratio_field=ratio_field['key'])) else: LOGGER.info( 'Count field {count_field} not detected in the exposure. We ' 'will not compute a ratio from this field.'.format( count_field=count_field['key'])) if len(mapping) == 0: # There is not a subset count field. Let's skip this layer. layer.commitChanges() return layer for feature in layer.getFeatures(): total_count = feature[inasafe_fields[population_count_field['key']]] for count_field, index in mapping.iteritems(): count = feature[count_field] try: new_value = count / total_count except TypeError: new_value = '' layer.changeAttributeValue(feature.id(), index, new_value) layer.commitChanges() check_layer(layer) return layer
def run_single_post_processor(layer, post_processor): """Run single post processor. If the layer has the output field, it will pass the post processor calculation. :param layer: The vector layer to use for post processing. :type layer: QgsVectorLayer :param post_processor: A post processor definition. :type post_processor: dict :returns: Tuple with True if success, else False with an error message. :rtype: (bool, str) """ if not layer.editBuffer(): # Turn on the editing mode. if not layer.startEditing(): msg = tr('The impact layer could not start the editing mode.') return False, msg # Calculate based on formula # Iterate all possible output and create the correct field. for output_key, output_value in post_processor['output'].items(): # Get output attribute name key = output_value['value']['key'] output_field_name = output_value['value']['field_name'] layer.keywords['inasafe_fields'][key] = output_field_name # If there is already the output field, don't proceed if layer.fieldNameIndex(output_field_name) > -1: msg = tr( 'The field name %s already exists.' % output_field_name) layer.rollBack() return False, msg # Add output attribute name to the layer field = create_field_from_definition(output_value['value']) result = layer.addAttribute(field) if not result: msg = tr( 'Error while creating the field %s.' % output_field_name) layer.rollBack() return False, msg # Get the index of output attribute output_field_index = layer.fieldNameIndex(output_field_name) if layer.fieldNameIndex(output_field_name) == -1: msg = tr( 'The field name %s has not been created.' % output_field_name) layer.rollBack() return False, msg # Get the input field's indexes for input input_indexes = {} input_properties = {} # Default parameters default_parameters = {} msg = None # Iterate over every inputs. for key, values in post_processor['input'].items(): values = values if isinstance(values, list) else [values] for value in values: is_constant_input = ( value['type'] == constant_input_type) is_field_input = ( value['type'] == field_input_type or value['type'] == dynamic_field_input_type) is_geometry_input = ( value['type'] == geometry_property_input_type) is_keyword_input = ( value['type'] == keyword_input_type) is_needs_input = ( value['type'] == needs_profile_input_type) is_layer_property_input = ( value['type'] == layer_property_input_type) if value['type'] == keyword_value_expected: break if is_constant_input: default_parameters[key] = value['value'] break elif is_field_input: if value['type'] == dynamic_field_input_type: key_template = value['value']['key'] field_param = value['field_param'] field_key = key_template % field_param else: field_key = value['value']['key'] inasafe_fields = layer.keywords['inasafe_fields'] name_field = inasafe_fields.get(field_key) if not name_field: msg = tr( '%s has not been found in inasafe fields.' % value['value']['key']) continue index = layer.fieldNameIndex(name_field) if index == -1: fields = layer.fields().toList() msg = tr( 'The field name %s has not been found in %s' % ( name_field, [f.name() for f in fields] )) continue input_indexes[key] = index break # For geometry, create new field that contain the value elif is_geometry_input: input_properties[key] = geometry_property_input_type['key'] break # for keyword elif is_keyword_input: # See http://stackoverflow.com/questions/14692690/ # access-python-nested-dictionary-items-via-a-list-of-keys value = reduce( lambda d, k: d[k], value['value'], layer.keywords) default_parameters[key] = value break # for needs profile elif is_needs_input: need_parameter = minimum_needs_parameter( parameter_name=value['value']) value = need_parameter.value default_parameters[key] = value break # for layer property elif is_layer_property_input: if value['value'] == layer_crs_input_value: default_parameters[key] = layer.crs() if value['value'] == size_calculator_input_value: exposure = layer.keywords.get('exposure') if not exposure: keywords = layer.keywords.get('exposure_keywords') exposure = keywords.get('exposure') default_parameters[key] = SizeCalculator( layer.crs(), layer.geometryType(), exposure) break else: # executed when we can't find all the inputs layer.rollBack() return False, msg # Create iterator for feature request = QgsFeatureRequest().setSubsetOfAttributes( input_indexes.values()) iterator = layer.getFeatures(request) inputs = input_indexes.copy() inputs.update(input_properties) # Iterate all feature for feature in iterator: attributes = feature.attributes() # Create dictionary to store the input parameters = {} parameters.update(default_parameters) # Fill up the input from fields for key, value in inputs.items(): if value == geometry_property_input_type['key']: parameters[key] = feature.geometry() else: parameters[key] = attributes[value] # Fill up the input from geometry property # Evaluate the function python_function = output_value.get('function') if python_function: # Launch the python function post_processor_result = python_function(**parameters) else: # Evaluate the function formula = output_value['formula'] post_processor_result = evaluate_formula( formula, parameters) # The affected postprocessor returns a boolean. if isinstance(post_processor_result, bool): post_processor_result = tr(unicode(post_processor_result)) layer.changeAttributeValue( feature.id(), output_field_index, post_processor_result ) layer.commitChanges() return True, None
def multi_buffering(layer, radii, callback=None): """Buffer a vector layer using many buffers (for volcanoes or rivers). This processing algorithm will keep the original attribute table and will add a new one for the hazard class name according to safe.definitions.fields.hazard_value_field. radii = OrderedDict() radii[500] = 'high' radii[1000] = 'medium' radii[2000] = 'low' Issue https://github.com/inasafe/inasafe/issues/3185 :param layer: The layer to polygonize. :type layer: QgsVectorLayer :param radii: A dictionary of radius. :type radii: OrderedDict :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The buffered vector layer. :rtype: QgsVectorLayer """ # Layer output output_layer_name = buffer_steps['output_layer_name'] processing_step = buffer_steps['step_name'] input_crs = layer.crs() feature_count = layer.featureCount() fields = layer.fields() # Set the new hazard class field. new_field = create_field_from_definition(hazard_class_field) fields.append(new_field) # Set the new buffer distances field. new_field = create_field_from_definition(buffer_distance_field) fields.append(new_field) buffered = create_memory_layer( output_layer_name, QGis.Polygon, input_crs, fields) data_provider = buffered.dataProvider() # Reproject features if needed into UTM if the layer is in 4326. if layer.crs().authid() == 'EPSG:4326': center = layer.extent().center() utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(layer.crs(), utm) reverse_transform = QgsCoordinateTransform(utm, layer.crs()) else: transform = None reverse_transform = None for i, feature in enumerate(layer.getFeatures()): geom = QgsGeometry(feature.geometry()) if transform: geom.transform(transform) inner_ring = None for radius in radii: attributes = feature.attributes() # We add the hazard value name to the attribute table. attributes.append(radii[radius]) # We add the value of buffer distance to the attribute table. attributes.append(radius) circle = geom.buffer(radius, 30) if inner_ring: circle.addRing(inner_ring) inner_ring = circle.asPolygon()[0] new_feature = QgsFeature() if reverse_transform: circle.transform(reverse_transform) new_feature.setGeometry(circle) new_feature.setAttributes(attributes) data_provider.addFeatures([new_feature]) if callback: callback(current=i, maximum=feature_count, step=processing_step) # We transfer keywords to the output. buffered.keywords = layer.keywords buffered.keywords['layer_geometry'] = 'polygon' buffered.keywords['layer_purpose'] = layer_purpose_hazard['key'] buffered.keywords['inasafe_fields'][hazard_class_field['key']] = ( hazard_class_field['field_name']) check_layer(buffered) return buffered
def add_default_values(layer): """Add or fill default values to the layer, see #3325. 1. It doesn't have inasafe_field and it doesn't have inasafe_default_value --> Do nothing. 2. It has inasafe_field and it does not have inasafe_default_value --> Do nothing. 3. It does not have inasafe_field but it has inasafe_default_value --> Create new field, and fill with the default value for all features 4. It has inasafe_field and it has inasafe_default_value --> Replace the null value with the default one. :param layer: The vector layer. :type layer: QgsVectorLayer :return: The vector layer with the default values. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = assign_default_values_steps['output_layer_name'] output_layer_name = output_layer_name % layer.keywords['layer_purpose'] fields = layer.keywords.get('inasafe_fields') if not isinstance(fields, dict): msg = 'inasafe_fields is missing in keywords from %s' % layer.name() raise InvalidKeywordsForProcessingAlgorithm(msg) defaults = layer.keywords.get('inasafe_default_values') if not defaults: # Case 1 and 2. LOGGER.info( 'inasafe_default_value is not present, we can not fill default ' 'ratios for this layer.') return layer for default in list(defaults.keys()): field = fields.get(default) target_field = definition(default) layer.startEditing() if not field: # Case 3 LOGGER.info( '{field} is not present but the layer has {value} as a ' 'default for {field}. We create the new field ' '{new_field} with this value.'.format( field=target_field['key'], value=defaults[default], new_field=target_field['field_name'])) new_field = create_field_from_definition(target_field) layer.addAttribute(new_field) new_index = layer.fields().lookupField(new_field.name()) for feature in layer.getFeatures(): layer.changeAttributeValue(feature.id(), new_index, defaults[default]) layer.keywords['inasafe_fields'][target_field['key']] = ( target_field['field_name']) else: # Case 4 LOGGER.info('{field} is present and the layer has {value} as a ' 'default for {field}, we MUST do nothing.'.format( field=target_field['key'], value=defaults[default])) index = layer.fields().lookupField(field) for feature in layer.getFeatures(): attr_val = feature.attributes()[index] if (attr_val is None or attr_val == '' or (hasattr(attr_val, 'isNull') and attr_val.isNull())): layer.changeAttributeValue(feature.id(), index, defaults[default]) layer.commitChanges() layer.keywords['title'] = output_layer_name check_layer(layer) return layer
def run_single_post_processor(layer, post_processor): """Run single post processor. If the layer has the output field, it will pass the post processor calculation. :param layer: The vector layer to use for post processing. :type layer: QgsVectorLayer :param post_processor: A post processor definition. :type post_processor: dict :returns: Tuple with True if success, else False with an error message. :rtype: (bool, str) """ if not layer.editBuffer(): # Turn on the editing mode. if not layer.startEditing(): msg = tr('The impact layer could not start the editing mode.') return False, msg # Calculate based on formula # Iterate all possible output and create the correct field. for output_key, output_value in list(post_processor['output'].items()): # Get output attribute name key = output_value['value']['key'] output_field_name = output_value['value']['field_name'] layer.keywords['inasafe_fields'][key] = output_field_name # If there is already the output field, don't proceed if layer.fields().lookupField(output_field_name) > -1: msg = tr('The field name %s already exists.' % output_field_name) layer.rollBack() return False, msg # Add output attribute name to the layer field = create_field_from_definition(output_value['value']) result = layer.addAttribute(field) if not result: msg = tr('Error while creating the field %s.' % output_field_name) layer.rollBack() return False, msg # Get the index of output attribute output_field_index = layer.fields().lookupField(output_field_name) if layer.fields().lookupField(output_field_name) == -1: msg = tr('The field name %s has not been created.' % output_field_name) layer.rollBack() return False, msg # Get the input field's indexes for input input_indexes = {} input_properties = {} # Default parameters default_parameters = {} msg = None # Iterate over every inputs. for key, values in list(post_processor['input'].items()): values = values if isinstance(values, list) else [values] for value in values: is_constant_input = (value['type'] == constant_input_type) is_field_input = (value['type'] == field_input_type or value['type'] == dynamic_field_input_type) is_geometry_input = ( value['type'] == geometry_property_input_type) is_keyword_input = (value['type'] == keyword_input_type) is_needs_input = (value['type'] == needs_profile_input_type) is_layer_property_input = ( value['type'] == layer_property_input_type) if value['type'] == keyword_value_expected: break if is_constant_input: default_parameters[key] = value['value'] break elif is_field_input: if value['type'] == dynamic_field_input_type: key_template = value['value']['key'] field_param = value['field_param'] field_key = key_template % field_param else: field_key = value['value']['key'] inasafe_fields = layer.keywords['inasafe_fields'] name_field = inasafe_fields.get(field_key) if not name_field: msg = tr('%s has not been found in inasafe fields.' % value['value']['key']) continue index = layer.fields().lookupField(name_field) if index == -1: fields = layer.fields().toList() msg = tr('The field name %s has not been found in %s' % (name_field, [f.name() for f in fields])) continue input_indexes[key] = index break # For geometry, create new field that contain the value elif is_geometry_input: input_properties[key] = geometry_property_input_type['key'] break # for keyword elif is_keyword_input: # See http://stackoverflow.com/questions/14692690/ # access-python-nested-dictionary-items-via-a-list-of-keys value = reduce(lambda d, k: d[k], value['value'], layer.keywords) default_parameters[key] = value break # for needs profile elif is_needs_input: need_parameter = minimum_needs_parameter( parameter_name=value['value']) value = need_parameter.value default_parameters[key] = value break # for layer property elif is_layer_property_input: if value['value'] == layer_crs_input_value: default_parameters[key] = layer.crs() if value['value'] == size_calculator_input_value: exposure = layer.keywords.get('exposure') if not exposure: keywords = layer.keywords.get('exposure_keywords') exposure = keywords.get('exposure') default_parameters[key] = SizeCalculator( layer.crs(), layer.geometryType(), exposure) break else: # executed when we can't find all the inputs layer.rollBack() return False, msg # Create iterator for feature request = QgsFeatureRequest().setSubsetOfAttributes( list(input_indexes.values())) iterator = layer.getFeatures(request) inputs = input_indexes.copy() inputs.update(input_properties) # Iterate all feature for feature in iterator: attributes = feature.attributes() # Create dictionary to store the input parameters = {} parameters.update(default_parameters) # Fill up the input from fields for key, value in list(inputs.items()): if value == geometry_property_input_type['key']: parameters[key] = feature.geometry() else: parameters[key] = attributes[value] # Fill up the input from geometry property # Evaluate the function python_function = output_value.get('function') if python_function: # Launch the python function post_processor_result = python_function(**parameters) else: # Evaluate the function formula = output_value['formula'] post_processor_result = evaluate_formula(formula, parameters) # The affected postprocessor returns a boolean. if isinstance(post_processor_result, bool): post_processor_result = tr(str(post_processor_result)) layer.changeAttributeValue(feature.id(), output_field_index, post_processor_result) layer.commitChanges() return True, None
def analysis_eartquake_summary(aggregation, analysis, callback=None): """Compute the summary from the aggregation to the analysis. Source layer : | aggr_id | aggr_name | total_feature | total_displaced | total_fatalities Target layer : | analysis_id | Output layer : | analysis_id | total_feature | total_displaced | total_fatalities | :param aggregation: The layer to aggregate vector layer. :type aggregation: QgsVectorLayer :param analysis: The target vector layer where to write statistics. :type analysis: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The new target layer with summary. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = summary_3_analysis_steps['output_layer_name'] processing_step = summary_3_analysis_steps['step_name'] source_fields = aggregation.keywords['inasafe_fields'] target_fields = analysis.keywords['inasafe_fields'] target_compulsory_fields = [ analysis_id_field, analysis_name_field, ] check_inputs(target_compulsory_fields, target_fields) source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, population_count_field, fatalities_field, displaced_field, ] check_inputs(source_compulsory_fields, source_fields) analysis.startEditing() count_hazard_level = {} index_hazard_level = {} classification = definition( aggregation.keywords['hazard_keywords']['classification']) hazard_classes = classification['classes'] for hazard_class in hazard_classes: key = hazard_class['key'] new_field = create_field_from_definition(hazard_count_field, key) analysis.addAttribute(new_field) new_index = analysis.fieldNameIndex(new_field.name()) count_hazard_level[key] = 0 index_hazard_level[key] = new_index target_fields[hazard_count_field['key'] % key] = ( hazard_count_field['field_name'] % key) summaries = {} # Summary is a dictionary with a tuple a key and the value to write in # the output layer as a value. # tuple (index in the aggregation layer, index in the analysis layer) : val for layer_field in source_fields: for definition_field in count_fields: if layer_field == definition_field['key']: field_name = source_fields[layer_field] index = aggregation.fieldNameIndex(field_name) new_field = create_field_from_definition(definition_field) analysis.addAttribute(new_field) new_index = analysis.fieldNameIndex(new_field.name()) summaries[(index, new_index)] = 0 target_fields[definition_field['key']] = ( definition_field['field_name']) request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregation.getFeatures(): for index in summaries.keys(): value = area[index[0]] if not value or isinstance(value, QPyNullVariant): value = 0 summaries[index] += value for mmi_level in range(2, 11): field_name = ( population_exposed_per_mmi_field['field_name'] % mmi_level) index = aggregation.fieldNameIndex(field_name) if index > 0: value = area[index] if not value or isinstance(value, QPyNullVariant): value = 0 hazard_class = from_mmi_to_hazard_class( mmi_level, classification['key']) if hazard_class: count_hazard_level[hazard_class] += value for row in analysis.getFeatures(): # We should have only one row in the analysis layer. for field in summaries.keys(): analysis.changeAttributeValue( row.id(), field[1], summaries[field]) for hazard_level, index in index_hazard_level.iteritems(): analysis.changeAttributeValue( row.id(), index, count_hazard_level[hazard_level]) analysis.commitChanges() analysis.keywords['title'] = output_layer_name analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key'] analysis.keywords['hazard_keywords'] = dict( aggregation.keywords['hazard_keywords']) analysis.keywords['exposure_keywords'] = dict( aggregation.keywords['exposure_keywords']) check_layer(analysis) return analysis