Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #8
0
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()
Beispiel #10
0
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
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #18
0
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
Beispiel #20
0
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
Beispiel #21
0
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
Beispiel #22
0
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
Beispiel #23
0
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
Beispiel #26
0
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
Beispiel #29
0
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
Beispiel #31
0
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
Beispiel #32
0
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
Beispiel #33
0
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
Beispiel #34
0
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
Beispiel #35
0
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
Beispiel #36
0
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