Exemplo n.º 1
0
def smart_clip(layer_to_clip, mask_layer, callback=None):
    """Smart clip a vector layer with another.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param layer_to_clip: The vector layer to clip.
    :type layer_to_clip: QgsVectorLayer

    :param mask_layer: The vector layer to use for clipping.
    :type mask_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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = smart_clip_steps['output_layer_name']
    processing_step = smart_clip_steps['step_name']

    writer = create_memory_layer(
        output_layer_name,
        layer_to_clip.geometryType(),
        layer_to_clip.crs(),
        layer_to_clip.fields()
    )
    writer.startEditing()

    # first build up a list of clip geometries
    request = QgsFeatureRequest().setSubsetOfAttributes([])
    iterator = mask_layer.getFeatures(request)
    feature = next(iterator)
    geometries = QgsGeometry(feature.geometry())

    # use prepared geometries for faster intersection tests
    # noinspection PyArgumentList
    engine = QgsGeometry.createGeometryEngine(geometries.geometry())
    engine.prepareGeometry()

    extent = mask_layer.extent()

    for feature in layer_to_clip.getFeatures(QgsFeatureRequest(extent)):

        if engine.intersects(feature.geometry().geometry()):
            out_feat = QgsFeature()
            out_feat.setGeometry(feature.geometry())
            out_feat.setAttributes(feature.attributes())
            writer.addFeature(out_feat)

    writer.commitChanges()

    writer.keywords = layer_to_clip.keywords.copy()
    writer.keywords['title'] = output_layer_name
    check_layer(writer)
    return writer
Exemplo n.º 2
0
def smart_clip(layer_to_clip, mask_layer, callback=None):
    """Smart clip a vector layer with another.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param layer_to_clip: The vector layer to clip.
    :type layer_to_clip: QgsVectorLayer

    :param mask_layer: The vector layer to use for clipping.
    :type mask_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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = smart_clip_steps['output_layer_name']
    processing_step = smart_clip_steps['step_name']

    writer = create_memory_layer(
        output_layer_name,
        layer_to_clip.geometryType(),
        layer_to_clip.crs(),
        layer_to_clip.fields()
    )
    writer.startEditing()

    # first build up a list of clip geometries
    request = QgsFeatureRequest().setSubsetOfAttributes([])
    iterator = mask_layer.getFeatures(request)
    feature = next(iterator)
    geometries = QgsGeometry(feature.geometry())

    # use prepared geometries for faster intersection tests
    # noinspection PyArgumentList
    engine = QgsGeometry.createGeometryEngine(geometries.geometry())
    engine.prepareGeometry()

    extent = mask_layer.extent()

    for feature in layer_to_clip.getFeatures(QgsFeatureRequest(extent)):

        if engine.intersects(feature.geometry().geometry()):
            out_feat = QgsFeature()
            out_feat.setGeometry(feature.geometry())
            out_feat.setAttributes(feature.attributes())
            writer.addFeature(out_feat)

    writer.commitChanges()

    writer.keywords = layer_to_clip.keywords.copy()
    writer.keywords['title'] = output_layer_name
    check_layer(writer)
    return writer
Exemplo n.º 3
0
def union(union_a, union_b):
    """Union of two vector layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param union_a: The vector layer for the union.
    :type union_a: QgsVectorLayer

    :param union_b: The vector layer for the union.
    :type union_b: QgsVectorLayer

    :return: The clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = union_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        union_a.keywords['layer_purpose'],
        union_b.keywords['layer_purpose']
    )

    keywords_union_1 = union_a.keywords
    keywords_union_2 = union_b.keywords
    inasafe_fields_union_1 = keywords_union_1['inasafe_fields']
    inasafe_fields_union_2 = keywords_union_2['inasafe_fields']
    inasafe_fields = inasafe_fields_union_1
    inasafe_fields.update(inasafe_fields_union_2)

    parameters = {'INPUT': union_a,
                  'OVERLAY': union_b,
                  'OUTPUT': 'memory:'}

    # TODO implement callback through QgsProcessingFeedback object

    initialize_processing()

    feedback = create_processing_feedback()
    context = create_processing_context(feedback=feedback)
    result = processing.run('native:union', parameters, context=context)
    if result is None:
        raise ProcessingInstallationError

    union_layer = result['OUTPUT']
    union_layer.setName(output_layer_name)

    # use to avoid modifying original source
    union_layer.keywords = dict(union_a.keywords)
    union_layer.keywords['inasafe_fields'] = inasafe_fields
    union_layer.keywords['title'] = output_layer_name
    union_layer.keywords['layer_purpose'] = 'aggregate_hazard'
    union_layer.keywords['hazard_keywords'] = keywords_union_1.copy()
    union_layer.keywords['aggregation_keywords'] = keywords_union_2.copy()

    fill_hazard_class(union_layer)

    check_layer(union_layer)
    return union_layer
Exemplo n.º 4
0
def union(union_a, union_b):
    """Union of two vector layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param union_a: The vector layer for the union.
    :type union_a: QgsVectorLayer

    :param union_b: The vector layer for the union.
    :type union_b: QgsVectorLayer

    :return: The clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = union_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        union_a.keywords['layer_purpose'],
        union_b.keywords['layer_purpose']
    )

    keywords_union_1 = union_a.keywords
    keywords_union_2 = union_b.keywords
    inasafe_fields_union_1 = keywords_union_1['inasafe_fields']
    inasafe_fields_union_2 = keywords_union_2['inasafe_fields']
    inasafe_fields = inasafe_fields_union_1
    inasafe_fields.update(inasafe_fields_union_2)

    parameters = {'INPUT': union_a,
                  'OVERLAY': union_b,
                  'OUTPUT': 'memory:'}

    # TODO implement callback through QgsProcessingFeedback object

    initialize_processing()

    feedback = create_processing_feedback()
    context = create_processing_context(feedback=feedback)
    result = processing.run('native:union', parameters, context=context)
    if result is None:
        raise ProcessingInstallationError

    union_layer = result['OUTPUT']
    union_layer.setName(output_layer_name)

    # use to avoid modifying original source
    union_layer.keywords = dict(union_a.keywords)
    union_layer.keywords['inasafe_fields'] = inasafe_fields
    union_layer.keywords['title'] = output_layer_name
    union_layer.keywords['layer_purpose'] = 'aggregate_hazard'
    union_layer.keywords['hazard_keywords'] = keywords_union_1.copy()
    union_layer.keywords['aggregation_keywords'] = keywords_union_2.copy()

    fill_hazard_class(union_layer)

    check_layer(union_layer)
    return union_layer
Exemplo n.º 5
0
def reproject(layer, output_crs, callback=None):
    """Reproject a vector layer to a specific CRS.

    Issue https://github.com/inasafe/inasafe/issues/3183

    :param layer: The layer to reproject.
    :type layer: QgsVectorLayer

    :param output_crs: The destination CRS.
    :type output_crs: QgsCoordinateReferenceSystem

    :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: Reprojected memory layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reproject_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']
    processing_step = reproject_steps['step_name']

    input_crs = layer.crs()
    input_fields = layer.fields()
    feature_count = layer.featureCount()

    reprojected = create_memory_layer(output_layer_name, layer.geometryType(),
                                      output_crs, input_fields)
    reprojected.startEditing()

    crs_transform = QgsCoordinateTransform(input_crs, output_crs,
                                           QgsProject.instance())

    out_feature = QgsFeature()

    for i, feature in enumerate(layer.getFeatures()):
        geom = feature.geometry()
        geom.transform(crs_transform)
        out_feature.setGeometry(geom)
        out_feature.setAttributes(feature.attributes())
        reprojected.addFeature(out_feature)

        if callback:
            callback(current=i, maximum=feature_count, step=processing_step)

    reprojected.commitChanges()

    # We transfer keywords to the output.
    # We don't need to update keywords as the CRS is dynamic.
    reprojected.keywords = layer.keywords
    reprojected.keywords['title'] = output_layer_name
    check_layer(reprojected)
    return reprojected
Exemplo n.º 6
0
def reproject(layer, output_crs, callback=None):
    """Reproject a vector layer to a specific CRS.

    Issue https://github.com/inasafe/inasafe/issues/3183

    :param layer: The layer to reproject.
    :type layer: QgsVectorLayer

    :param output_crs: The destination CRS.
    :type output_crs: QgsCoordinateReferenceSystem

    :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: Reprojected memory layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reproject_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']
    processing_step = reproject_steps['step_name']

    input_crs = layer.crs()
    input_fields = layer.fields()
    feature_count = layer.featureCount()

    reprojected = create_memory_layer(
        output_layer_name, layer.geometryType(), output_crs, input_fields)
    reprojected.startEditing()

    crs_transform = QgsCoordinateTransform(input_crs, output_crs)

    out_feature = QgsFeature()

    for i, feature in enumerate(layer.getFeatures()):
        geom = feature.geometry()
        geom.transform(crs_transform)
        out_feature.setGeometry(geom)
        out_feature.setAttributes(feature.attributes())
        reprojected.addFeature(out_feature)

        if callback:
            callback(current=i, maximum=feature_count, step=processing_step)

    reprojected.commitChanges()

    # We transfer keywords to the output.
    # We don't need to update keywords as the CRS is dynamic.
    reprojected.keywords = layer.keywords
    reprojected.keywords['title'] = output_layer_name
    check_layer(reprojected)
    return reprojected
Exemplo n.º 7
0
def intersection(source, mask):
    """Intersect two layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param source: The vector layer to clip.
    :type source: QgsVectorLayer

    :param mask: The vector layer to use for clipping.
    :type mask: QgsVectorLayer

    :return: The clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = intersection_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        source.keywords['layer_purpose'])

    parameters = {'INPUT': source,
                  'OVERLAY': mask,
                  'OUTPUT': 'memory:'}

    # TODO implement callback through QgsProcessingFeedback object

    initialize_processing()

    feedback = create_processing_feedback()
    context = create_processing_context(feedback=feedback)
    result = processing.run('native:intersection', parameters, context=context)
    if result is None:
        raise ProcessingInstallationError

    intersect = result['OUTPUT']
    intersect.setName(output_layer_name)
    intersect.keywords = dict(source.keywords)
    intersect.keywords['title'] = output_layer_name
    intersect.keywords['layer_purpose'] = \
        layer_purpose_exposure_summary['key']
    intersect.keywords['inasafe_fields'] = \
        dict(source.keywords['inasafe_fields'])
    intersect.keywords['inasafe_fields'].update(
        mask.keywords['inasafe_fields'])
    intersect.keywords['hazard_keywords'] = \
        dict(mask.keywords['hazard_keywords'])
    intersect.keywords['exposure_keywords'] = dict(source.keywords)
    intersect.keywords['aggregation_keywords'] = dict(
        mask.keywords['aggregation_keywords'])

    check_layer(intersect)
    return intersect
Exemplo n.º 8
0
def intersection(source, mask):
    """Intersect two layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param source: The vector layer to clip.
    :type source: QgsVectorLayer

    :param mask: The vector layer to use for clipping.
    :type mask: QgsVectorLayer

    :return: The clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = intersection_steps['output_layer_name']
    output_layer_name = output_layer_name % (source.keywords['layer_purpose'])

    parameters = {'INPUT': source, 'OVERLAY': mask, 'OUTPUT': 'memory:'}

    # TODO implement callback through QgsProcessingFeedback object

    initialize_processing()

    feedback = create_processing_feedback()
    context = create_processing_context(feedback=feedback)
    result = processing.run('native:intersection', parameters, context=context)
    if result is None:
        raise ProcessingInstallationError

    intersect = result['OUTPUT']
    intersect.setName(output_layer_name)
    intersect.keywords = dict(source.keywords)
    intersect.keywords['title'] = output_layer_name
    intersect.keywords['layer_purpose'] = \
        layer_purpose_exposure_summary['key']
    intersect.keywords['inasafe_fields'] = \
        dict(source.keywords['inasafe_fields'])
    intersect.keywords['inasafe_fields'].update(
        mask.keywords['inasafe_fields'])
    intersect.keywords['hazard_keywords'] = \
        dict(mask.keywords['hazard_keywords'])
    intersect.keywords['exposure_keywords'] = dict(source.keywords)
    intersect.keywords['aggregation_keywords'] = dict(
        mask.keywords['aggregation_keywords'])

    check_layer(intersect)
    return intersect
Exemplo n.º 9
0
def clean_layer(layer):
    """Clean a vector layer.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :return: The buffered vector layer.
    :rtype: QgsVectorLayer
    """
    output_layer_name = clean_geometry_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    # start editing
    layer.startEditing()
    count = 0

    # iterate through all features
    request = QgsFeatureRequest().setSubsetOfAttributes([])
    for feature in layer.getFeatures(request):
        geom = feature.geometry()
        was_valid, geometry_cleaned = geometry_checker(geom)

        if was_valid:
            # Do nothing if it was valid
            pass
        elif not was_valid and geometry_cleaned:
            # Update the geometry if it was not valid, and clean now
            layer.changeGeometry(feature.id(), geometry_cleaned, True)
        else:
            # Delete if it was not valid and not able to be cleaned
            count += 1
            layer.deleteFeature(feature.id())

    if count:
        LOGGER.critical(
            '%s features have been removed from %s because of invalid '
            'geometries.' % (count, layer.name()))
    else:
        LOGGER.info('No feature has been removed from the layer: %s' %
                    layer.name())

    # save changes
    layer.commitChanges()
    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 10
0
def clean_layer(layer):
    """Clean a vector layer.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :return: The buffered vector layer.
    :rtype: QgsVectorLayer
    """
    output_layer_name = clean_geometry_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    # start editing
    layer.startEditing()
    count = 0

    # iterate through all features
    request = QgsFeatureRequest().setSubsetOfAttributes([])
    for feature in layer.getFeatures(request):
        geom = feature.geometry()
        was_valid, geometry_cleaned = geometry_checker(geom)

        if was_valid:
            # Do nothing if it was valid
            pass
        elif not was_valid and geometry_cleaned:
            # Update the geometry if it was not valid, and clean now
            layer.changeGeometry(feature.id(), geometry_cleaned, True)
        else:
            # Delete if it was not valid and not able to be cleaned
            count += 1
            layer.deleteFeature(feature.id())

    if count:
        LOGGER.critical(
            '%s features have been removed from %s because of invalid '
            'geometries.' % (count, layer.name()))
    else:
        LOGGER.info(
            'No feature has been removed from the layer: %s' % layer.name())

    # save changes
    layer.commitChanges()
    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 11
0
def clean_layer(layer):
    """Clean a vector layer.

    :param layer: The vector layer.
    :type layer: qgis.core.QgsVectorLayer

    :return: The buffered vector layer.
    :rtype: qgis.core.QgsVectorLayer
    """
    output_layer_name = clean_geometry_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    count = layer.featureCount()

    parameters = {'INPUT': layer, 'OUTPUT': 'memory:'}

    initialize_processing()

    feedback = create_processing_feedback()
    context = create_processing_context(feedback=feedback)
    result = processing.run('qgis:fixgeometries', parameters, context=context)
    if result is None:
        raise ProcessingInstallationError

    cleaned = result['OUTPUT']
    cleaned.setName(output_layer_name)

    removed_count = count - cleaned.featureCount()

    if removed_count:
        LOGGER.critical(
            '{removed_count} features have been removed from {layer_name} '
            'because of invalid geometries.'.format(
                removed_count=removed_count, layer_name=layer.name()))
    else:
        LOGGER.info('No feature has been removed from the layer: '
                    '{layer_name}'.format(layer_name=layer.name()))

    cleaned.keywords = layer.keywords.copy()
    cleaned.keywords['title'] = output_layer_name
    check_layer(cleaned)

    return cleaned
Exemplo n.º 12
0
def clean_layer(layer, callback=None):
    """Clean a vector layer.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        editing should accept params 'current' (int), 'maximum' (int) and
        'step' (str). Defaults to None.
    :type callback: function

    :return: The buffered vector layer.
    :rtype: QgsVectorLayer
    """
    output_layer_name = clean_geometry_steps['output_layer_name']
    processing_step = clean_geometry_steps['step_name']  # NOQA
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    # start editing
    layer.startEditing()
    count = 0

    # iterate through all features
    for feature in layer.getFeatures():
        geom = feature.geometry()
        geometry_cleaned = geometry_checker(geom)
        if geometry_cleaned:
            feature.setGeometry(geometry_cleaned)
        else:
            count += 1
            layer.deleteFeature(feature.id())

    LOGGER.critical(
        '%s features have been removed from %s because of invalid geometries.'
        % (count, layer.name()))

    # save changes
    layer.commitChanges()

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 13
0
def clip(layer_to_clip, mask_layer):
    """Clip a vector layer with another.

    Issue https://github.com/inasafe/inasafe/issues/3186

    :param layer_to_clip: The vector layer to clip.
    :type layer_to_clip: QgsVectorLayer

    :param mask_layer: The vector layer to use for clipping.
    :type mask_layer: QgsVectorLayer

    :return: The clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = clip_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        layer_to_clip.keywords['layer_purpose'])

    parameters = {
        'INPUT': layer_to_clip,
        'OVERLAY': mask_layer,
        'OUTPUT': 'memory:'
    }

    # TODO implement callback through QgsProcessingFeedback object

    initialize_processing()

    feedback = create_processing_feedback()
    context = create_processing_context(feedback=feedback)
    result = processing.run('native:clip', parameters, context=context)
    if result is None:
        raise ProcessingInstallationError

    clipped = result['OUTPUT']
    clipped.setName(output_layer_name)

    clipped.keywords = layer_to_clip.keywords.copy()
    clipped.keywords['title'] = output_layer_name
    check_layer(clipped)
    return clipped
Exemplo n.º 14
0
def clean_layer(layer, callback=None):
    """Clean a vector layer.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        editing should accept params 'current' (int), 'maximum' (int) and
        'step' (str). Defaults to None.
    :type callback: function

    :return: The buffered vector layer.
    :rtype: QgsVectorLayer
    """
    output_layer_name = clean_geometry_steps['output_layer_name']
    processing_step = clean_geometry_steps['step_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    # start editing
    layer.startEditing()

    # iterate through all features
    for feature in layer.getFeatures():
        geom = feature.geometry()
        geometry_cleaned = geometry_checker(geom)
        if geometry_cleaned:
            feature.setGeometry(geometry_cleaned)
        else:
            layer.deleteFeature(feature.id())

    # save changes
    layer.commitChanges()

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 15
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
Exemplo n.º 16
0
def intersection(source, mask, callback=None):
    """Intersect two layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    Note : This algorithm is copied from :
    https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/
    qgis/Intersection.py

    :param source: The vector layer to clip.
    :type source: QgsVectorLayer

    :param mask: The vector layer to use for clipping.
    :type mask: 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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = intersection_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        source.keywords['layer_purpose'])
    processing_step = intersection_steps['step_name']

    fields = source.fields()
    fields.extend(mask.fields())

    writer = create_memory_layer(
        output_layer_name,
        source.geometryType(),
        source.crs(),
        fields
    )

    writer.startEditing()

    # Begin copy/paste from Processing plugin.
    # Please follow their code as their code is optimized.
    # The code below is not following our coding standards because we want to
    # be able to track any diffs from QGIS easily.

    out_feature = QgsFeature()
    index = create_spatial_index(mask)

    # Todo callback
    # total = 100.0 / len(selectionA)

    for current, in_feature in enumerate(source.getFeatures()):
        # progress.setPercentage(int(current * total))
        geom = in_feature.geometry()
        attributes = in_feature.attributes()
        intersects = index.intersects(geom.boundingBox())
        for i in intersects:
            request = QgsFeatureRequest().setFilterFid(i)
            feature_mask = next(mask.getFeatures(request))
            tmp_geom = feature_mask.geometry()
            if geom.intersects(tmp_geom):
                mask_attributes = feature_mask.attributes()
                int_geom = QgsGeometry(geom.intersection(tmp_geom))
                if int_geom.wkbType() == QgsWKBTypes.Unknown\
                        or QgsWKBTypes.flatType(
                        int_geom.geometry().wkbType()) ==\
                                QgsWKBTypes.GeometryCollection:
                    int_com = geom.combine(tmp_geom)
                    int_geom = QgsGeometry()
                    if int_com:
                        int_sym = geom.symDifference(tmp_geom)
                        int_geom = QgsGeometry(int_com.difference(int_sym))
                if int_geom.isGeosEmpty() or not int_geom.isGeosValid():
                    # LOGGER.debug(
                    #     tr('GEOS geoprocessing error: One or more input '
                    #        'features have invalid geometry.'))
                    pass
                try:
                    geom_types = wkb_type_groups[
                        wkb_type_groups[int_geom.wkbType()]]
                    if int_geom.wkbType() in geom_types:
                        if int_geom.type() == source.geometryType():
                            # We got some features which have not the same
                            # kind of geometry. We want to skip them.
                            out_feature.setGeometry(int_geom)
                            attrs = []
                            attrs.extend(attributes)
                            attrs.extend(mask_attributes)
                            out_feature.setAttributes(attrs)
                            writer.addFeature(out_feature)
                except:
                    LOGGER.debug(
                        tr('Feature geometry error: One or more output '
                           'features ignored due to invalid geometry.'))
                    continue

    # End copy/paste from Processing plugin.
    writer.commitChanges()

    writer.keywords = dict(source.keywords)
    writer.keywords['title'] = output_layer_name
    writer.keywords['layer_purpose'] = layer_purpose_exposure_summary['key']
    writer.keywords['inasafe_fields'] = dict(source.keywords['inasafe_fields'])
    writer.keywords['inasafe_fields'].update(mask.keywords['inasafe_fields'])
    writer.keywords['hazard_keywords'] = dict(mask.keywords['hazard_keywords'])
    writer.keywords['exposure_keywords'] = dict(source.keywords)
    writer.keywords['aggregation_keywords'] = dict(
        mask.keywords['aggregation_keywords'])

    check_layer(writer)
    return writer
Exemplo n.º 17
0
def analysis_summary(aggregate_hazard, analysis, callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | analysis_id |

    Output layer :
    | analysis_id | 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

    :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 = aggregate_hazard.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, 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.fieldNameIndex(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    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 not value or isinstance(value, QPyNullVariant) or isnan(value):
            # For isnan, see ticket #3812
            value = 0
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = 'NULL'
        flat_table.add_value(value, hazard_class=hazard_value)

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                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_not_exposed_field, total_field
    ]

    add_fields(analysis, absolute_values, counts, unique_hazard,
               hazard_count_field)

    affected_sum = 0
    not_affected_sum = 0
    not_exposed_sum = 0

    for area in analysis.getFeatures(request):
        total = 0
        for i, val in enumerate(unique_hazard):
            if not val or isinstance(val, QPyNullVariant):
                val = 'NULL'
            sum = flat_table.get_value(hazard_class=val)
            total += sum
            analysis.changeAttributeValue(area.id(), shift + i, sum)

            affected = post_processor_affected_function(
                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

        # Affected field
        analysis.changeAttributeValue(area.id(), shift + len(unique_hazard),
                                      affected_sum)

        # Not affected field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 1,
                                      not_affected_sum)

        # Not exposed field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 2,
                                      not_exposed_sum)

        # Total field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 3, total)

        # Any absolute postprocessors
        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(all='all')
            analysis.changeAttributeValue(area.id(),
                                          shift + len(unique_hazard) + 4 + i,
                                          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
Exemplo n.º 18
0
def align_rasters(hazard_layer, exposure_layer, extent):
    """Align hazard and exposure raster layers.

    Align hazard and exposure raster layers so they fit perfectly and so they
    can be used for raster algebra. The method uses QGIS raster alignment tool
    to do the work (which in turn uses GDAL).

    Alignment of layers means that the layers have the same CRS, cell size,
    grid origin and size. That involves clipping and resampling of rasters.
    From the two layers, the layer with finer resolution (smaller cell size)
    will be used as the reference for the alignment (i.e. parameters will
    be set to its CRS, cell size and grid offset).

    - Reproject to the same CRS.
    - Resample to the same cell size and offset in the grid.
    - Clip to a region of interest.

    :param hazard_layer: Hazard layer to be aligned.
    :type hazard_layer: QgsRasterLayer

    :param exposure_layer: Exposure layer to be aligned.
    :type exposure_layer: QgsRasterLayer

    :param extent: Extent in exposure CRS to which raster should be clipped.
    :type extent: QgsRectangle

    :return: Clipped hazard and exposure layers.
    :rtype: QgsRasterLayer, QgsRasterLayer
    """
    output_layer_name = align_steps['output_layer_name']
    processing_step = align_steps['step_name']

    hazard_output = unique_filename(suffix='.tif')
    exposure_output = unique_filename(suffix='.tif')

    # Setup the two raster layers for alignment
    align = QgsAlignRaster()

    inputs = [
        QgsAlignRaster.Item(hazard_layer.source(), hazard_output),
        QgsAlignRaster.Item(exposure_layer.source(), exposure_output)
    ]

    if exposure_layer.keywords.get('exposure_unit') == 'count':
        inputs[1].rescaleValues = True

    align.setRasters(inputs)

    # Find out which layer has finer grid and use it as the reference.
    # This will setup destination CRS, cell size and grid origin
    if exposure_layer.keywords.get('allow_resampling', True):
        index = align.suggestedReferenceLayer()
    else:
        index = 1  # have to use exposure layer as the reference

    if index < 0:
        raise AlignRastersError(tr('Unable to select reference layer'))

    if not align.setParametersFromRaster(
            inputs[index].inputFilename, exposure_layer.crs().toWkt()):
        raise AlignRastersError(align.errorMessage())

    # Setup clip extent
    align.setClipExtent(extent)

    # Everything configured - do the alignment now!
    # For each raster, it will create output file and write resampled values
    if not align.run():
        raise AlignRastersError(align.errorMessage())

    # Load resulting layers
    aligned_hazard_layer = QgsRasterLayer(
        hazard_output, output_layer_name % 'hazard')
    aligned_exposure_layer = QgsRasterLayer(
        exposure_output, output_layer_name % 'exposure')

    aligned_hazard_layer.keywords = dict(hazard_layer.keywords)
    aligned_hazard_layer.keywords['title'] = output_layer_name % 'hazard'
    aligned_exposure_layer.keywords = dict(exposure_layer.keywords)
    aligned_exposure_layer.keywords['title'] = output_layer_name % 'exposure'

    # avoid any possible further rescaling of exposure data by correctly
    # setting original resolution to be the same as current resolution
    aligned_exposure_layer.keywords['resolution'] = (
        align.cellSize().width(), align.cellSize().height())

    check_layer(exposure_layer)
    check_layer(hazard_layer)
    return aligned_hazard_layer, aligned_exposure_layer
Exemplo n.º 19
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
Exemplo n.º 20
0
def aggregation_summary(aggregate_hazard, aggregation, callback=None):
    """Compute the summary from the aggregate hazard to the analysis layer.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | aggr_id | aggr_name |

    Output layer :
    | aggr_id | aggr_name | count of affected features per exposure type

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param aggregation: The aggregation vector layer where to write statistics.
    :type aggregation: 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 aggregation layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_2_aggregation_steps['output_layer_name']
    processing_step = summary_2_aggregation_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = aggregation.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    # Missing exposure_count_field
    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        affected_field,
    ]
    check_inputs(source_compulsory_fields, source_fields)

    pattern = exposure_count_field['key']
    pattern = pattern.replace('%s', '')
    unique_exposure = read_dynamic_inasafe_field(
        source_fields, exposure_count_field)

    absolute_values = create_absolute_values_structure(
        aggregate_hazard, ['aggregation_id'])

    flat_table = FlatTable('aggregation_id', 'exposure_class')

    aggregation_index = source_fields[aggregation_id_field['key']]

    # We want to loop over affected features only.
    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    expression = '\"%s\" = \'%s\'' % (
        affected_field['field_name'], tr('True'))
    request.setFilterExpression(expression)
    for area in aggregate_hazard.getFeatures(request):

        for key, name_field in source_fields.iteritems():
            if key.endswith(pattern):
                aggregation_id = area[aggregation_index]
                exposure_class = key.replace(pattern, '')
                value = area[name_field]
                flat_table.add_value(
                    value,
                    aggregation_id=aggregation_id,
                    exposure_class=exposure_class
                )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=area[aggregation_index],
            )

    shift = aggregation.fields().count()

    aggregation.startEditing()

    add_fields(
        aggregation,
        absolute_values,
        [total_affected_field],
        unique_exposure,
        affected_exposure_count_field)

    aggregation_index = target_fields[aggregation_id_field['key']]

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregation.getFeatures(request):
        aggregation_value = area[aggregation_index]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(
                aggregation_id=aggregation_value,
                exposure_class=val
            )
            total += sum
            aggregation.changeAttributeValue(area.id(), shift + i, sum)

        aggregation.changeAttributeValue(
            area.id(), shift + len(unique_exposure), total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                aggregation_id=aggregation_value,
            )
            target_index = shift + len(unique_exposure) + 1 + i
            aggregation.changeAttributeValue(
                area.id(), target_index, value)

    aggregation.commitChanges()

    aggregation.keywords['title'] = layer_purpose_aggregation_summary['name']
    if qgis_version() >= 21800:
        aggregation.setName(aggregation.keywords['title'])
    else:
        aggregation.setLayerName(aggregation.keywords['title'])
    aggregation.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])

    check_layer(aggregation)
    return aggregation
Exemplo n.º 21
0
def analysis_summary(aggregate_hazard, analysis, callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | analysis_id |

    Output layer :
    | analysis_id | 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

    :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 = aggregate_hazard.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,
        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.fieldNameIndex(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    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 not value or isinstance(value, QPyNullVariant) or isnan(value):
            # For isnan, see ticket #3812
            value = 0
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = 'NULL'
        flat_table.add_value(
            value,
            hazard_class=hazard_value
        )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                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_not_exposed_field,
        total_field]

    add_fields(
        analysis,
        absolute_values,
        counts,
        unique_hazard,
        hazard_count_field)

    affected_sum = 0
    not_affected_sum = 0
    not_exposed_sum = 0

    for area in analysis.getFeatures(request):
        total = 0
        for i, val in enumerate(unique_hazard):
            if not val or isinstance(val, QPyNullVariant):
                val = 'NULL'
            sum = flat_table.get_value(hazard_class=val)
            total += sum
            analysis.changeAttributeValue(area.id(), shift + i, sum)

            affected = post_processor_affected_function(
                    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

        # Affected field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard), affected_sum)

        # Not affected field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 1, not_affected_sum)

        # Not exposed field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 2, not_exposed_sum)

        # Total field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 3, total)

        # Any absolute postprocessors
        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                all='all'
            )
            analysis.changeAttributeValue(
                area.id(), shift + len(unique_hazard) + 4 + i, 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
Exemplo n.º 22
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
Exemplo n.º 23
0
def prepare_vector_layer(layer, callback=None):
    """This function will prepare the layer to be used in InaSAFE :
     * Make a local copy of the layer.
     * Make sure that we have an InaSAFE ID column.
     * Rename fields according to our definitions.
     * Remove fields which are not used.

    :param layer: The layer to prepare.
    :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: Cleaned memory layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = prepare_vector_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']
    processing_step = prepare_vector_steps['step_name']

    if not layer.keywords.get('inasafe_fields'):
        msg = 'inasafe_fields is missing in keywords from %s' % layer.name()
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    feature_count = layer.featureCount()

    cleaned = create_memory_layer(
        output_layer_name, layer.geometryType(), layer.crs(), layer.fields())

    # We transfer keywords to the output.
    cleaned.keywords = layer.keywords

    copy_layer(layer, cleaned)
    _remove_features(cleaned)

    # After removing rows, let's check if there is still a feature.
    request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)
    iterator = cleaned.getFeatures(request)
    try:
        next(iterator)
    except StopIteration:
        LOGGER.warning(
            tr('No feature has been found in the {purpose}'
                .format(purpose=layer.keywords['layer_purpose'])))
        raise NoFeaturesInExtentError

    _add_id_column(cleaned)
    clean_inasafe_fields(cleaned)

    if _size_is_needed(cleaned):
        LOGGER.info(
            'We noticed some counts in your exposure layer. Before to update '
            'geometries, we compute the original size for each feature.')
        run_single_post_processor(cleaned, post_processor_size)

    if cleaned.keywords['layer_purpose'] == 'exposure':
        fields = cleaned.keywords['inasafe_fields']
        if exposure_type_field['key'] not in fields:
            _add_default_exposure_class(cleaned)

        # Check value mapping
        _check_value_mapping(cleaned)

    cleaned.keywords['title'] = output_layer_name

    check_layer(cleaned)
    return cleaned
Exemplo n.º 24
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 aggregate_hazard_summary(impact, aggregate_hazard, callback=None):
    """Compute the summary from the source layer to the aggregate_hazard layer.

    Source layer :
    |exp_id|exp_class|haz_id|haz_class|aggr_id|aggr_name|affected|extra*|

    Target layer :
    | aggr_id | aggr_name | haz_id | haz_class | extra* |

    Output layer :
    |aggr_id| aggr_name|haz_id|haz_class|affected|extra*|count ber exposure*|


    :param impact: The layer to aggregate vector layer.
    :type impact: QgsVectorLayer

    :param aggregate_hazard: The aggregate_hazard vector layer where to write
        statistics.
    :type aggregate_hazard: 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 aggregate_hazard layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_1_aggregate_hazard_steps['output_layer_name']
    processing_step = summary_1_aggregate_hazard_steps['step_name']

    source_fields = impact.keywords['inasafe_fields']
    target_fields = aggregate_hazard.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        exposure_id_field,
        exposure_class_field,
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    aggregation_id = target_fields[aggregation_id_field['key']]

    hazard_id = target_fields[hazard_id_field['key']]
    hazard_class = target_fields[hazard_class_field['key']]

    exposure_class = source_fields[exposure_class_field['key']]
    exposure_class_index = impact.fieldNameIndex(exposure_class)
    unique_exposure = impact.uniqueValues(exposure_class_index)

    fields = ['aggregation_id', 'hazard_id']
    absolute_values = create_absolute_values_structure(impact, fields)

    # We need to know what kind of exposure we are going to count.
    # the size, or the number of features or population.
    field_index = report_on_field(impact)

    aggregate_hazard.startEditing()

    shift = aggregate_hazard.fields().count()
    add_fields(
        aggregate_hazard,
        absolute_values,
        [affected_field, total_field],
        unique_exposure,
        exposure_count_field
    )

    flat_table = FlatTable('aggregation_id', 'hazard_id', 'exposure_class')

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    LOGGER.debug('Computing the aggregate hazard summary.')
    for feature in impact.getFeatures(request):
        # Field_index can be equal to 0.
        if field_index is not None:
            value = feature[field_index]
        else:
            value = 1

        aggregation_value = feature[aggregation_id]
        hazard_value = feature[hazard_id]
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = not_exposed_class['key']
        exposure_value = feature[exposure_class]
        if not exposure_value or isinstance(exposure_value, QPyNullVariant):
            exposure_value = 'NULL'

        flat_table.add_value(
            value,
            aggregation_id=aggregation_value,
            hazard_id=hazard_value,
            exposure_class=exposure_value
        )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = feature[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=aggregation_value,
                hazard_id=hazard_value
            )

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    for area in aggregate_hazard.getFeatures(request):
        aggregation_value = area[aggregation_id]
        feature_hazard_id = area[hazard_id]
        if not feature_hazard_id or isinstance(
                feature_hazard_id, QPyNullVariant):
            feature_hazard_id = not_exposed_class['key']
        feature_hazard_value = area[hazard_class]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(
                aggregation_id=aggregation_value,
                hazard_id=feature_hazard_id,
                exposure_class=val
            )
            total += sum
            aggregate_hazard.changeAttributeValue(area.id(), shift + i, sum)

        affected = post_processor_affected_function(
            classification=classification, hazard_class=feature_hazard_value)
        affected = tr(unicode(affected))
        aggregate_hazard.changeAttributeValue(
            area.id(), shift + len(unique_exposure), affected)

        aggregate_hazard.changeAttributeValue(
            area.id(), shift + len(unique_exposure) + 1, total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                aggregation_id=aggregation_value,
                hazard_id=feature_hazard_id
            )
            aggregate_hazard.changeAttributeValue(
                area.id(), shift + len(unique_exposure) + 2 + i, value)

    aggregate_hazard.commitChanges()

    aggregate_hazard.keywords['title'] = (
        layer_purpose_aggregate_hazard_impacted['name'])
    if qgis_version() >= 21800:
        aggregate_hazard.setName(aggregate_hazard.keywords['title'])
    else:
        aggregate_hazard.setLayerName(aggregate_hazard.keywords['title'])
    aggregate_hazard.keywords['layer_purpose'] = (
        layer_purpose_aggregate_hazard_impacted['key'])

    check_layer(aggregate_hazard)
    return aggregate_hazard
Exemplo n.º 26
0
def reclassify(layer, exposure_key=None, overwrite_input=False, callback=None):
    """Reclassify a continuous raster layer.

    Issue https://github.com/inasafe/inasafe/issues/3182


    This function is a wrapper for the code from
    https://github.com/chiatt/gdal_reclassify

    For instance if you want to reclassify like this table :
            Original Value     |   Class
            - ∞ < val <= 0     |     1
            0   < val <= 0.5   |     2
            0.5 < val <= 5     |     3
            5   < val <  + ∞   |     6

    You need a dictionary :
        ranges = OrderedDict()
        ranges[1] = [None, 0]
        ranges[2] = [0.0, 0.5]
        ranges[3] = [0.5, 5]
        ranges[6] = [5, None]

    :param layer: The raster layer.
    :type layer: QgsRasterLayer

    :param overwrite_input: Option for the output layer. True will overwrite
        the input layer. False will create a temporary layer.
    :type overwrite_input: bool

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :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 classified raster layer.
    :rtype: QgsRasterLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reclassify_raster_steps['output_layer_name']
    processing_step = reclassify_raster_steps['step_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    if exposure_key:
        classification_key = active_classification(
            layer.keywords, exposure_key)
        thresholds = active_thresholds_value_maps(layer.keywords, exposure_key)
        layer.keywords['thresholds'] = thresholds
        layer.keywords['classification'] = classification_key
    else:
        classification_key = layer.keywords.get('classification')
        thresholds = layer.keywords.get('thresholds')
    if not thresholds:
        raise InvalidKeywordsForProcessingAlgorithm(
            'thresholds are missing from the layer %s'
            % layer.keywords['layer_purpose'])

    if not classification_key:
        raise InvalidKeywordsForProcessingAlgorithm(
            'classification is missing from the layer %s'
            % layer.keywords['layer_purpose'])

    ranges = {}
    value_map = {}
    hazard_classes = definition(classification_key)['classes']
    for hazard_class in hazard_classes:
        ranges[hazard_class['value']] = thresholds[hazard_class['key']]
        value_map[hazard_class['key']] = [hazard_class['value']]

    if overwrite_input:
        output_raster = layer.source()
    else:
        output_raster = unique_filename(suffix='.tiff', dir=temp_dir())

    driver = gdal.GetDriverByName('GTiff')

    raster_file = gdal.Open(layer.source())
    band = raster_file.GetRasterBand(1)
    no_data = band.GetNoDataValue()
    source = band.ReadAsArray()
    destination = source.copy()

    for value, interval in ranges.iteritems():
        v_min = interval[0]
        v_max = interval[1]

        if v_min is None:
            destination[np.where(source <= v_max)] = value

        if v_max is None:
            destination[np.where(source > v_min)] = value

        if v_min < v_max:
            destination[np.where((v_min < source) & (source <= v_max))] = value

    # Tag no data cells
    destination[np.where(source == no_data)] = no_data_value

    # Create the new file.
    output_file = driver.Create(
        output_raster, raster_file.RasterXSize, raster_file.RasterYSize, 1)
    output_file.GetRasterBand(1).WriteArray(destination)
    output_file.GetRasterBand(1).SetNoDataValue(no_data_value)

    # CRS
    output_file.SetProjection(raster_file.GetProjection())
    output_file.SetGeoTransform(raster_file.GetGeoTransform())
    output_file.FlushCache()

    del output_file

    if not isfile(output_raster):
        raise FileNotFoundError

    reclassified = QgsRasterLayer(output_raster, output_layer_name)

    # We transfer keywords to the output.
    reclassified.keywords = layer.keywords.copy()
    reclassified.keywords['layer_mode'] = 'classified'

    value_map = {}

    hazard_classes = definition(classification_key)['classes']
    for hazard_class in reversed(hazard_classes):
        value_map[hazard_class['key']] = [hazard_class['value']]

    reclassified.keywords['value_map'] = value_map
    reclassified.keywords['title'] = output_layer_name

    check_layer(reclassified)
    return reclassified
Exemplo n.º 27
0
def polygonize(layer, callback=None):
    """Polygonize a raster layer into a vector layer using GDAL.

    Issue https://github.com/inasafe/inasafe/issues/3183

    :param layer: The layer to reproject.
    :type layer: QgsRasterLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int) and 'maximum' (int). Defaults to
        None.
    :type callback: function

    :return: Reprojected memory layer.
    :rtype: QgsRasterLayer

    .. versionadded:: 4.0
    """
    output_layer_name = polygonize_steps['output_layer_name']
    processing_step = polygonize_steps['step_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']
    gdal_layer_name = polygonize_steps['gdal_layer_name']

    if layer.keywords.get('layer_purpose') == 'exposure':
        output_field = exposure_type_field
    else:
        output_field = hazard_value_field

    input_raster = gdal.Open(layer.source(), gdal.GA_ReadOnly)

    srs = osr.SpatialReference()
    srs.ImportFromWkt(input_raster.GetProjectionRef())

    temporary_dir = temp_dir(sub_dir='pre-process')
    out_shapefile = unique_filename(
        suffix='-%s.shp' % output_layer_name, dir=temporary_dir)

    driver = ogr.GetDriverByName("ESRI Shapefile")
    destination = driver.CreateDataSource(out_shapefile)

    output_layer = destination.CreateLayer(gdal_layer_name, srs)

    # We have no other way to use a shapefile. We need only the first 10 chars.
    field_name = output_field['field_name'][0:10]
    fd = ogr.FieldDefn(field_name, ogr.OFTInteger)
    output_layer.CreateField(fd)

    input_band = input_raster.GetRasterBand(1)
    # Fixme : add our own callback to Polygonize
    gdal.Polygonize(input_band, None, output_layer, 0, [], callback=None)
    destination.Destroy()

    vector_layer = QgsVectorLayer(out_shapefile, output_layer_name, 'ogr')

    # Let's remove polygons which were no data
    request = QgsFeatureRequest()
    expression = '"%s" = %s' % (field_name, no_data_value)
    request.setFilterExpression(expression)
    vector_layer.startEditing()
    for feature in vector_layer.getFeatures(request):
        vector_layer.deleteFeature(feature.id())
    vector_layer.commitChanges()

    # We transfer keywords to the output.
    vector_layer.keywords = layer.keywords.copy()
    vector_layer.keywords[
        layer_geometry['key']] = layer_geometry_polygon['key']

    vector_layer.keywords['title'] = output_layer_name
    # We just polygonized the raster layer. inasafe_fields do not exist.
    vector_layer.keywords['inasafe_fields'] = {
        output_field['key']: field_name
    }

    check_layer(vector_layer)
    return vector_layer
Exemplo n.º 28
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
Exemplo n.º 29
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
Exemplo n.º 30
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
Exemplo n.º 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
Exemplo n.º 32
0
def clip_by_extent(layer, extent, callback=None):
    """Clip a raster using a bounding box using processing.

    Issue https://github.com/inasafe/inasafe/issues/3183

    :param layer: The layer to reproject.
    :type layer: QgsRasterLayer

    :param extent: The extent.
    :type extent: QgsRectangle

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int) and 'maximum' (int). Defaults to
        None.
    :type callback: function

    :return: Reprojected memory layer.
    :rtype: QgsRasterLayer

    .. versionadded:: 4.0
    """
    parameters = dict()
    # noinspection PyBroadException
    try:
        output_layer_name = quick_clip_steps['output_layer_name']
        processing_step = quick_clip_steps['step_name']
        output_layer_name = output_layer_name % layer.keywords['layer_purpose']

        output_raster = unique_filename(dir=temp_dir())

        # We make one pixel size buffer on the extent to cover every pixels.
        # See https://github.com/inasafe/inasafe/issues/3655
        pixel_size_x = layer.rasterUnitsPerPixelX()
        pixel_size_y = layer.rasterUnitsPerPixelY()
        buffer_size = max(pixel_size_x, pixel_size_y)
        extent = extent.buffer(buffer_size)

        if is_raster_y_inverted(layer):
            # The raster is Y inverted. We need to switch Y min and Y max.
            bbox = [
                str(extent.xMinimum()),
                str(extent.xMaximum()),
                str(extent.yMaximum()),
                str(extent.yMinimum())
            ]
        else:
            # The raster is normal.
            bbox = [
                str(extent.xMinimum()),
                str(extent.xMaximum()),
                str(extent.yMinimum()),
                str(extent.yMaximum())
            ]

        # These values are all from the processing algorithm.
        # https://github.com/qgis/QGIS/blob/master/python/plugins/processing/
        # algs/gdal/ClipByExtent.py
        # Please read the file to know these parameters.
        parameters['INPUT'] = layer.source()
        parameters['NO_DATA'] = ''
        parameters['PROJWIN'] = ','.join(bbox)
        parameters['RTYPE'] = 5
        parameters['COMPRESS'] = 4
        parameters['JPEGCOMPRESSION'] = 75
        parameters['ZLEVEL'] = 6
        parameters['PREDICTOR'] = 1
        parameters['TILED'] = False
        parameters['BIGTIFF'] = 0
        parameters['TFW'] = False
        parameters['EXTRA'] = ''
        parameters['OUTPUT'] = output_raster
        result = processing.runalg("gdalogr:cliprasterbyextent", parameters)

        if result is None:
            raise ProcessingInstallationError

        clipped = QgsRasterLayer(result['OUTPUT'], output_layer_name)

        # We transfer keywords to the output.
        clipped.keywords = layer.keywords.copy()
        clipped.keywords['title'] = output_layer_name

        check_layer(clipped)
    except Exception as e:
        # This step clip_raster_by_extent was nice to speedup the analysis.
        # As we got an exception because the layer is invalid, we are not going
        # to stop the analysis. We will return the original raster layer.
        # It will take more processing time until we clip the vector layer.
        # Check https://github.com/inasafe/inasafe/issues/4026 why we got some
        # exceptions with this step.
        LOGGER.exception(parameters)
        LOGGER.exception(
            'Error from QGIS clip raster by extent. Please check the QGIS '
            'logs too !')
        LOGGER.info(
            'Even if we got an exception, we are continuing the analysis. The '
            'layer is not clip.')
        LOGGER.exception(str(e))
        LOGGER.exception(get_error_message(e).to_text())
        clipped = layer

    return clipped
Exemplo n.º 33
0
def aggregate_hazard_summary(impact, aggregate_hazard, callback=None):
    """Compute the summary from the source layer to the aggregate_hazard layer.

    Source layer :
    |exp_id|exp_class|haz_id|haz_class|aggr_id|aggr_name|affected|extra*|

    Target layer :
    | aggr_id | aggr_name | haz_id | haz_class | extra* |

    Output layer :
    |aggr_id| aggr_name|haz_id|haz_class|affected|extra*|count ber exposure*|


    :param impact: The layer to aggregate vector layer.
    :type impact: QgsVectorLayer

    :param aggregate_hazard: The aggregate_hazard vector layer where to write
        statistics.
    :type aggregate_hazard: 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 aggregate_hazard layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_1_aggregate_hazard_steps['output_layer_name']
    processing_step = summary_1_aggregate_hazard_steps['step_name']

    source_fields = impact.keywords['inasafe_fields']
    target_fields = aggregate_hazard.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        exposure_id_field,
        exposure_class_field,
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    aggregation_id = target_fields[aggregation_id_field['key']]

    hazard_id = target_fields[hazard_id_field['key']]
    hazard_class = target_fields[hazard_class_field['key']]

    exposure_class = source_fields[exposure_class_field['key']]
    exposure_class_index = impact.fieldNameIndex(exposure_class)
    unique_exposure = impact.uniqueValues(exposure_class_index)

    fields = ['aggregation_id', 'hazard_id']
    absolute_values = create_absolute_values_structure(impact, fields)

    # We need to know what kind of exposure we are going to count.
    # the size, or the number of features or population.
    field_index = report_on_field(impact)

    aggregate_hazard.startEditing()

    shift = aggregate_hazard.fields().count()
    add_fields(
        aggregate_hazard,
        absolute_values,
        [affected_field, total_field],
        unique_exposure,
        exposure_count_field
    )

    flat_table = FlatTable('aggregation_id', 'hazard_id', 'exposure_class')

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    LOGGER.debug('Computing the aggregate hazard summary.')
    for feature in impact.getFeatures(request):
        # Field_index can be equal to 0.
        if field_index is not None:
            value = feature[field_index]
        else:
            value = 1

        aggregation_value = feature[aggregation_id]
        hazard_value = feature[hazard_id]
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = not_exposed_class['key']
        exposure_value = feature[exposure_class]
        if not exposure_value or isinstance(exposure_value, QPyNullVariant):
            exposure_value = 'NULL'

        flat_table.add_value(
            value,
            aggregation_id=aggregation_value,
            hazard_id=hazard_value,
            exposure_class=exposure_value
        )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = feature[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=aggregation_value,
                hazard_id=hazard_value
            )

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    for area in aggregate_hazard.getFeatures(request):
        aggregation_value = area[aggregation_id]
        feature_hazard_id = area[hazard_id]
        if not feature_hazard_id or isinstance(
                feature_hazard_id, QPyNullVariant):
            feature_hazard_id = not_exposed_class['key']
        feature_hazard_value = area[hazard_class]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(
                aggregation_id=aggregation_value,
                hazard_id=feature_hazard_id,
                exposure_class=val
            )
            total += sum
            aggregate_hazard.changeAttributeValue(area.id(), shift + i, sum)

        affected = post_processor_affected_function(
            classification=classification, hazard_class=feature_hazard_value)
        affected = tr(unicode(affected))
        aggregate_hazard.changeAttributeValue(
            area.id(), shift + len(unique_exposure), affected)

        aggregate_hazard.changeAttributeValue(
            area.id(), shift + len(unique_exposure) + 1, total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                aggregation_id=aggregation_value,
                hazard_id=feature_hazard_id
            )
            aggregate_hazard.changeAttributeValue(
                area.id(), shift + len(unique_exposure) + 2 + i, value)

    aggregate_hazard.commitChanges()

    aggregate_hazard.keywords['title'] = (
        layer_purpose_aggregate_hazard_impacted['name'])
    if qgis_version() >= 21800:
        aggregate_hazard.setName(aggregate_hazard.keywords['title'])
    else:
        aggregate_hazard.setLayerName(aggregate_hazard.keywords['title'])
    aggregate_hazard.keywords['layer_purpose'] = (
        layer_purpose_aggregate_hazard_impacted['key'])

    check_layer(aggregate_hazard)
    return aggregate_hazard
Exemplo n.º 34
0
def zonal_stats(raster, vector, callback=None):
    """Reclassify a continuous raster layer.

    Issue https://github.com/inasafe/inasafe/issues/3190


    :param raster: The raster layer.
    :type raster: QgsRasterLayer

    :param vector: The vector layer.
    :type vector: 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 output of the zonal stats.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = zonal_stats_steps['output_layer_name']
    processing_step = zonal_stats_steps['step_name']

    layer = create_memory_layer(
        output_layer_name,
        vector.geometryType(),
        vector.crs(),
        vector.fields()
    )

    copy_layer(vector, layer)

    analysis = QgsZonalStatistics(
        layer, raster.source(), 'exposure_', 1, QgsZonalStatistics.Sum)
    result = analysis.calculateStatistics(None)
    LOGGER.debug(tr('Zonal stats on %s : %s' % (raster.source(), result)))

    layer.startEditing()
    exposure = raster.keywords['exposure']
    output_field = exposure_count_field['field_name'] % exposure
    fields_to_rename = {
        'exposure_sum': output_field
    }
    copy_fields(layer, fields_to_rename)
    remove_fields(layer, 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
    # commited first.
    layer.startEditing()
    request = QgsFeatureRequest()
    expression = '\"%s\" is None' % output_field
    request.setFilterExpression(expression)
    request.setFlags(QgsFeatureRequest.NoGeometry)
    index = layer.fieldNameIndex(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
Exemplo n.º 35
0
def align_rasters(hazard_layer, exposure_layer, extent):
    """Align hazard and exposure raster layers.

    Align hazard and exposure raster layers so they fit perfectly and so they
    can be used for raster algebra. The method uses QGIS raster alignment tool
    to do the work (which in turn uses GDAL).

    Alignment of layers means that the layers have the same CRS, cell size,
    grid origin and size. That involves clipping and resampling of rasters.
    From the two layers, the layer with finer resolution (smaller cell size)
    will be used as the reference for the alignment (i.e. parameters will
    be set to its CRS, cell size and grid offset).

    - Reproject to the same CRS.
    - Resample to the same cell size and offset in the grid.
    - Clip to a region of interest.

    :param hazard_layer: Hazard layer to be aligned.
    :type hazard_layer: QgsRasterLayer

    :param exposure_layer: Exposure layer to be aligned.
    :type exposure_layer: QgsRasterLayer

    :param extent: Extent in exposure CRS to which raster should be clipped.
    :type extent: QgsRectangle

    :return: Clipped hazard and exposure layers.
    :rtype: QgsRasterLayer, QgsRasterLayer
    """
    output_layer_name = align_steps['output_layer_name']
    processing_step = align_steps['step_name']

    hazard_output = unique_filename(suffix='.tif')
    exposure_output = unique_filename(suffix='.tif')

    # Setup the two raster layers for alignment
    align = QgsAlignRaster()

    inputs = [
        QgsAlignRaster.Item(hazard_layer.source(), hazard_output),
        QgsAlignRaster.Item(exposure_layer.source(), exposure_output)
    ]

    if exposure_layer.keywords.get('exposure_unit') == 'count':
        inputs[1].rescaleValues = True

    align.setRasters(inputs)

    # Find out which layer has finer grid and use it as the reference.
    # This will setup destination CRS, cell size and grid origin
    if exposure_layer.keywords.get('allow_resampling', True):
        index = align.suggestedReferenceLayer()
    else:
        index = 1  # have to use exposure layer as the reference

    if index < 0:
        raise AlignRastersError(tr('Unable to select reference layer'))

    if not align.setParametersFromRaster(
            inputs[index].inputFilename, exposure_layer.crs().toWkt()):
        raise AlignRastersError(align.errorMessage())

    # Setup clip extent
    align.setClipExtent(extent)

    # Everything configured - do the alignment now!
    # For each raster, it will create output file and write resampled values
    if not align.run():
        raise AlignRastersError(align.errorMessage())

    # Load resulting layers
    aligned_hazard_layer = QgsRasterLayer(
        hazard_output, output_layer_name % 'hazard')
    aligned_exposure_layer = QgsRasterLayer(
        exposure_output, output_layer_name % 'exposure')

    aligned_hazard_layer.keywords = dict(hazard_layer.keywords)
    aligned_hazard_layer.keywords['title'] = output_layer_name % 'hazard'
    aligned_exposure_layer.keywords = dict(exposure_layer.keywords)
    aligned_exposure_layer.keywords['title'] = output_layer_name % 'exposure'

    # avoid any possible further rescaling of exposure data by correctly
    # setting original resolution to be the same as current resolution
    aligned_exposure_layer.keywords['resolution'] = (
        align.cellSize().width(), align.cellSize().height())

    check_layer(exposure_layer)
    check_layer(hazard_layer)
    return aligned_hazard_layer, aligned_exposure_layer
Exemplo n.º 36
0
def reclassify(layer, exposure_key=None, callback=None):
    """Reclassify a continuous vector layer.

    This function will modify the input.

    For instance if you want to reclassify like this table :
            Original Value     |   Class
            - ∞ < val <= 0     |     1
            0   < val <= 0.5   |     2
            0.5 < val <= 5     |     3
            5   < val <  + ∞   |     6

    You need a dictionary :
        ranges = OrderedDict()
        ranges[1] = [None, 0]
        ranges[2] = [0.0, 0.5]
        ranges[3] = [0.5, 5]
        ranges[6] = [5, None]

    :param layer: The raster layer.
    :type layer: QgsRasterLayer

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :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 classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reclassify_vector_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['title']
    processing_step = reclassify_vector_steps['step_name']

    # This layer should have this keyword, or it's a mistake from the dev.
    inasafe_fields = layer.keywords['inasafe_fields']
    continuous_column = inasafe_fields[hazard_value_field['key']]

    if exposure_key:
        classification_key = active_classification(layer.keywords,
                                                   exposure_key)
        thresholds = active_thresholds_value_maps(layer.keywords, exposure_key)
        layer.keywords['thresholds'] = thresholds
        layer.keywords['classification'] = classification_key
    else:
        classification_key = layer.keywords.get('classification')
        thresholds = layer.keywords.get('thresholds')

    if not thresholds:
        raise InvalidKeywordsForProcessingAlgorithm(
            'thresholds are missing from the layer %s' %
            layer.keywords['layer_purpose'])

    continuous_index = layer.fieldNameIndex(continuous_column)

    classified_field = QgsField()
    classified_field.setType(hazard_class_field['type'])
    classified_field.setName(hazard_class_field['field_name'])
    classified_field.setLength(hazard_class_field['length'])
    classified_field.setPrecision(hazard_class_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fieldNameIndex(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[continuous_index]
        classified_value = _classified_value(source_value, thresholds)
        if not classified_value:
            layer.deleteFeature(feature.id())
        else:
            layer.changeAttributeValue(feature.id(), classified_field_index,
                                       classified_value)

    layer.commitChanges()
    layer.updateFields()

    # We transfer keywords to the output.
    inasafe_fields[hazard_class_field['key']] = (
        hazard_class_field['field_name'])

    value_map = {}

    hazard_classes = definition(classification_key)['classes']
    for hazard_class in reversed(hazard_classes):
        value_map[hazard_class['key']] = [hazard_class['value']]

    layer.keywords['value_map'] = value_map
    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 37
0
def assign_highest_value(exposure, hazard, callback=None):
    """Assign the highest hazard value to an indivisible feature.

    For indivisible polygon exposure layers such as buildings, we need to
    assigned the greatest hazard that each polygon touches and use that as the
    effective hazard class.

    Issue https://github.com/inasafe/inasafe/issues/3192

    We follow the concept here that any part of the exposure dataset that
    touches the hazard is affected, and the greatest hazard is the effective
    hazard.

    :param exposure: The building vector layer.
    :type exposure: QgsVectorLayer

    :param hazard: The vector layer to use for hazard.
    :type hazard: 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 impact layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_highest_value_steps['output_layer_name']
    processing_step = assign_highest_value_steps['step_name']

    hazard_inasafe_fields = hazard.keywords['inasafe_fields']

    if not hazard.keywords.get('classification'):
        raise InvalidKeywordsForProcessingAlgorithm
    if not hazard_inasafe_fields.get(hazard_class_field['key']):
        raise InvalidKeywordsForProcessingAlgorithm

    indices = []
    exposure.startEditing()
    for field in hazard.fields():
        exposure.addAttribute(field)
        indices.append(exposure.fieldNameIndex(field.name()))
    exposure.commitChanges()
    provider = exposure.dataProvider()

    spatial_index = create_spatial_index(exposure)

    # cache features from exposure layer for faster retrieval
    exposure_features = {}
    for f in exposure.getFeatures():
        exposure_features[f.id()] = f

    # Todo callback
    # total = 100.0 / len(selectionA)

    hazard_field = hazard_inasafe_fields[hazard_class_field['key']]

    layer_classification = None
    for classification in hazard_classification['types']:
        if classification['key'] == hazard.keywords['classification']:
            layer_classification = classification
            break

    # Get a ordered list of classes like ['high', 'medium', 'low']
    levels = [key['key'] for key in layer_classification['classes']]
    levels.append(not_exposed_class['key'])

    # Let's loop over the hazard layer, from high to low hazard zone.
    for hazard_value in levels:
        expression = '"%s" = \'%s\'' % (hazard_field, hazard_value)
        hazard_request = QgsFeatureRequest().setFilterExpression(expression)
        update_map = {}
        for area in hazard.getFeatures(hazard_request):
            geometry = area.geometry()
            intersects = spatial_index.intersects(geometry.boundingBox())

            # use prepared geometry: makes multiple intersection tests faster
            geometry_prepared = QgsGeometry.createGeometryEngine(
                geometry.geometry())
            geometry_prepared.prepareGeometry()

            # We need to loop over each intersections exposure / hazard.
            for i in intersects:
                building = exposure_features[i]
                building_geometry = building.geometry()

                if geometry_prepared.intersects(building_geometry.geometry()):
                    update_map[building.id()] = {}
                    for index, value in zip(indices, area.attributes()):
                        update_map[building.id()][index] = value

                    # We don't want this building again, let's remove it from
                    # the index.
                    spatial_index.deleteFeature(building)

        provider.changeAttributeValues(update_map)

    exposure.updateExtents()
    exposure.updateFields()

    exposure.keywords['inasafe_fields'].update(
        hazard.keywords['inasafe_fields'])
    exposure.keywords['layer_purpose'] = layer_purpose_exposure_summary['key']

    exposure.keywords['exposure_keywords'] = exposure.keywords.copy()
    exposure.keywords['aggregation_keywords'] = (
        hazard.keywords['aggregation_keywords'].copy())
    exposure.keywords['hazard_keywords'] = (
        hazard.keywords['hazard_keywords'].copy())

    exposure.keywords['title'] = output_layer_name

    check_layer(exposure)
    return exposure
Exemplo n.º 38
0
def reclassify(layer, exposure_key=None):
    """Reclassify a continuous vector layer.

    This function will modify the input.

    For instance if you want to reclassify like this table :
            Original Value     |   Class
            - ∞ < val <= 0     |     1
            0   < val <= 0.5   |     2
            0.5 < val <= 5     |     3
            5   < val <  + ∞   |     6

    You need a dictionary :
        ranges = OrderedDict()
        ranges[1] = [None, 0]
        ranges[2] = [0.0, 0.5]
        ranges[3] = [0.5, 5]
        ranges[6] = [5, None]

    :param layer: The raster layer.
    :type layer: QgsRasterLayer

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :return: The classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reclassify_vector_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['title']

    # This layer should have this keyword, or it's a mistake from the dev.
    inasafe_fields = layer.keywords['inasafe_fields']
    continuous_column = inasafe_fields[hazard_value_field['key']]

    if exposure_key:
        classification_key = active_classification(
            layer.keywords, exposure_key)
        thresholds = active_thresholds_value_maps(layer.keywords, exposure_key)
        layer.keywords['thresholds'] = thresholds
        layer.keywords['classification'] = classification_key
    else:
        classification_key = layer.keywords.get('classification')
        thresholds = layer.keywords.get('thresholds')

    if not thresholds:
        raise InvalidKeywordsForProcessingAlgorithm(
            'thresholds are missing from the layer %s'
            % layer.keywords['layer_purpose'])

    continuous_index = layer.fields().lookupField(continuous_column)

    classified_field = QgsField()
    classified_field.setType(hazard_class_field['type'])
    classified_field.setName(hazard_class_field['field_name'])
    classified_field.setLength(hazard_class_field['length'])
    classified_field.setPrecision(hazard_class_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fields(). \
        lookupField(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[continuous_index]
        classified_value = reclassify_value(source_value, thresholds)
        if (classified_value is None
                or (hasattr(classified_value, 'isNull')
                    and classified_value.isNull())):
            layer.deleteFeature(feature.id())
        else:
            layer.changeAttributeValue(
                feature.id(), classified_field_index, classified_value)

    layer.commitChanges()
    layer.updateFields()

    # We transfer keywords to the output.
    inasafe_fields[hazard_class_field['key']] = (
        hazard_class_field['field_name'])

    value_map = {}

    hazard_classes = definition(classification_key)['classes']
    for hazard_class in reversed(hazard_classes):
        value_map[hazard_class['key']] = [hazard_class['value']]

    layer.keywords['value_map'] = value_map
    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 39
0
def aggregation_summary(aggregate_hazard, aggregation, callback=None):
    """Compute the summary from the aggregate hazard to the analysis layer.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | aggr_id | aggr_name |

    Output layer :
    | aggr_id | aggr_name | count of affected features per exposure type

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param aggregation: The aggregation vector layer where to write statistics.
    :type aggregation: 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 aggregation layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_2_aggregation_steps['output_layer_name']
    processing_step = summary_2_aggregation_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = aggregation.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    # Missing exposure_count_field
    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        affected_field,
    ]
    check_inputs(source_compulsory_fields, source_fields)

    pattern = exposure_count_field['key']
    pattern = pattern.replace('%s', '')
    unique_exposure = read_dynamic_inasafe_field(source_fields,
                                                 exposure_count_field)

    absolute_values = create_absolute_values_structure(aggregate_hazard,
                                                       ['aggregation_id'])

    flat_table = FlatTable('aggregation_id', 'exposure_class')

    aggregation_index = source_fields[aggregation_id_field['key']]

    # We want to loop over affected features only.
    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    expression = '\"%s\" = \'%s\'' % (affected_field['field_name'], tr('True'))
    request.setFilterExpression(expression)
    for area in aggregate_hazard.getFeatures(request):

        for key, name_field in source_fields.iteritems():
            if key.endswith(pattern):
                aggregation_id = area[aggregation_index]
                exposure_class = key.replace(pattern, '')
                value = area[name_field]
                flat_table.add_value(value,
                                     aggregation_id=aggregation_id,
                                     exposure_class=exposure_class)

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=area[aggregation_index],
            )

    shift = aggregation.fields().count()

    aggregation.startEditing()

    add_fields(aggregation, absolute_values, [total_affected_field],
               unique_exposure, affected_exposure_count_field)

    aggregation_index = target_fields[aggregation_id_field['key']]

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregation.getFeatures(request):
        aggregation_value = area[aggregation_index]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(aggregation_id=aggregation_value,
                                       exposure_class=val)
            total += sum
            aggregation.changeAttributeValue(area.id(), shift + i, sum)

        aggregation.changeAttributeValue(area.id(),
                                         shift + len(unique_exposure), total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(aggregation_id=aggregation_value, )
            target_index = shift + len(unique_exposure) + 1 + i
            aggregation.changeAttributeValue(area.id(), target_index, value)

    aggregation.commitChanges()

    aggregation.keywords['title'] = layer_purpose_aggregation_summary['name']
    if qgis_version() >= 21800:
        aggregation.setName(aggregation.keywords['title'])
    else:
        aggregation.setLayerName(aggregation.keywords['title'])
    aggregation.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])

    check_layer(aggregation)
    return aggregation
Exemplo n.º 40
0
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
Exemplo n.º 41
0
def polygonize(layer):
    """Polygonize a raster layer into a vector layer using GDAL.

    Issue https://github.com/inasafe/inasafe/issues/3183

    :param layer: The layer to reproject.
    :type layer: QgsRasterLayer

    :return: Reprojected memory layer.
    :rtype: QgsRasterLayer

    .. versionadded:: 4.0
    """
    output_layer_name = polygonize_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']
    gdal_layer_name = polygonize_steps['gdal_layer_name']

    if layer.keywords.get('layer_purpose') == 'exposure':
        output_field = exposure_type_field
    else:
        output_field = hazard_value_field

    input_raster = gdal.Open(layer.source(), gdal.GA_ReadOnly)

    srs = osr.SpatialReference()
    srs.ImportFromWkt(input_raster.GetProjectionRef())

    temporary_dir = temp_dir(sub_dir='pre-process')
    out_shapefile = unique_filename(suffix='-%s.shp' % output_layer_name,
                                    dir=temporary_dir)

    driver = ogr.GetDriverByName("ESRI Shapefile")
    destination = driver.CreateDataSource(out_shapefile)

    output_layer = destination.CreateLayer(gdal_layer_name, srs)

    # We have no other way to use a shapefile. We need only the first 10 chars.
    field_name = output_field['field_name'][0:10]
    fd = ogr.FieldDefn(field_name, ogr.OFTInteger)
    output_layer.CreateField(fd)

    active_band = layer.keywords.get('active_band', 1)
    input_band = input_raster.GetRasterBand(active_band)
    # Fixme : add our own callback to Polygonize
    gdal.Polygonize(input_band, None, output_layer, 0, [], callback=None)
    destination.Destroy()

    vector_layer = QgsVectorLayer(out_shapefile, output_layer_name, 'ogr')

    # Let's remove polygons which were no data
    request = QgsFeatureRequest()
    expression = '"%s" = %s' % (field_name, no_data_value)
    request.setFilterExpression(expression)
    vector_layer.startEditing()
    for feature in vector_layer.getFeatures(request):
        vector_layer.deleteFeature(feature.id())
    vector_layer.commitChanges()

    # We transfer keywords to the output.
    vector_layer.keywords = layer.keywords.copy()
    vector_layer.keywords[
        layer_geometry['key']] = layer_geometry_polygon['key']

    vector_layer.keywords['title'] = output_layer_name
    # We just polygonized the raster layer. inasafe_fields do not exist.
    vector_layer.keywords['inasafe_fields'] = {output_field['key']: field_name}

    check_layer(vector_layer)
    return vector_layer
Exemplo n.º 42
0
def assign_highest_value(exposure, hazard):
    """Assign the highest hazard value to an indivisible feature.

    For indivisible polygon exposure layers such as buildings, we need to
    assigned the greatest hazard that each polygon touches and use that as the
    effective hazard class.

    Issue https://github.com/inasafe/inasafe/issues/3192

    We follow the concept here that any part of the exposure dataset that
    touches the hazard is affected, and the greatest hazard is the effective
    hazard.

    :param exposure: The building vector layer.
    :type exposure: QgsVectorLayer

    :param hazard: The vector layer to use for hazard.
    :type hazard: QgsVectorLayer

    :return: The new impact layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_highest_value_steps['output_layer_name']

    hazard_inasafe_fields = hazard.keywords['inasafe_fields']
    haz_id_field = hazard_inasafe_fields[hazard_id_field['key']]
    try:
        aggr_id_field = hazard_inasafe_fields[aggregation_id_field['key']]
    except AttributeError:
        aggr_id_field = None

    if not hazard.keywords.get('classification'):
        raise InvalidKeywordsForProcessingAlgorithm
    if not hazard_inasafe_fields.get(hazard_class_field['key']):
        raise InvalidKeywordsForProcessingAlgorithm

    indices = []
    exposure.startEditing()
    for field in hazard.fields():
        exposure.addAttribute(field)
        indices.append(exposure.fields().lookupField(field.name()))
    exposure.commitChanges()
    provider = exposure.dataProvider()

    spatial_index = create_spatial_index(exposure)

    # cache features from exposure layer for faster retrieval
    exposure_features = {}
    for f in exposure.getFeatures():
        exposure_features[f.id()] = f

    # Todo callback
    # total = 100.0 / len(selectionA)

    hazard_field = hazard_inasafe_fields[hazard_class_field['key']]

    layer_classification = None
    for classification in hazard_classification['types']:
        if classification['key'] == hazard.keywords['classification']:
            layer_classification = classification
            break

    # Get a ordered list of classes like ['high', 'medium', 'low']
    levels = [key['key'] for key in layer_classification['classes']]
    levels.append(not_exposed_class['key'])

    def _hazard_sort_key(feature):
        """Custom feature sort function.

        The function were intended to sort hazard features, in order
        for maintaining consistencies between subsequent runs.

        With controlled order, we will have the same output if one
        hazard spans over multiple aggregation boundaries.
        """
        if aggr_id_field:
            return (feature[haz_id_field]
                    or feature.id(), feature[aggr_id_field])
        return feature[haz_id_field] or feature.id()

    # Let's loop over the hazard layer, from high to low hazard zone.
    for hazard_value in levels:
        expression = '"%s" = \'%s\'' % (hazard_field, hazard_value)
        order_by_hazard_field = QgsFeatureRequest.OrderByClause(haz_id_field)
        order_by_aggregation_field = QgsFeatureRequest.OrderByClause(
            aggr_id_field)
        order_by = QgsFeatureRequest.OrderBy(
            [order_by_hazard_field, order_by_aggregation_field])
        hazard_request = QgsFeatureRequest()\
            .setOrderBy(order_by).setFilterExpression(expression)
        update_map = {}
        areas = sorted(hazard.getFeatures(hazard_request),
                       key=_hazard_sort_key)
        for area in areas:
            geometry = area.geometry().constGet()
            intersects = spatial_index.intersects(geometry.boundingBox())

            # to force consistencies between subsequent runs, sort the index.
            # ideally each exposure feature needs to have prioritization
            # value/score to determine which hazard/aggregation it belongs to,
            # in case one feature were intersected with one or more high
            # hazard geometry. sorting the ids works by ignoring this
            # tendencies but still maintains consistencies for subsequent run.
            intersects.sort()

            # use prepared geometry: makes multiple intersection tests faster
            geometry_prepared = QgsGeometry.createGeometryEngine(geometry)
            geometry_prepared.prepareGeometry()

            # We need to loop over each intersections exposure / hazard.
            for i in intersects:
                building = exposure_features[i]
                building_geometry = building.geometry()

                if geometry_prepared.intersects(building_geometry.constGet()):
                    update_map[building.id()] = {}
                    for index, value in zip(indices, area.attributes()):
                        update_map[building.id()][index] = value

                    # We don't want this building again, let's remove it from
                    # the index.
                    spatial_index.deleteFeature(building)

        provider.changeAttributeValues(update_map)

    exposure.updateExtents()
    exposure.updateFields()

    exposure.keywords['inasafe_fields'].update(
        hazard.keywords['inasafe_fields'])
    exposure.keywords['layer_purpose'] = layer_purpose_exposure_summary['key']

    exposure.keywords['exposure_keywords'] = exposure.keywords.copy()
    exposure.keywords['aggregation_keywords'] = (
        hazard.keywords['aggregation_keywords'].copy())
    exposure.keywords['hazard_keywords'] = (
        hazard.keywords['hazard_keywords'].copy())

    exposure.keywords['title'] = output_layer_name

    check_layer(exposure)
    return exposure
Exemplo n.º 43
0
def rasterize_vector_layer(layer, width, height, extent):
    """Rasterize a vector layer to the grid given by extent and width/height.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param width: The width of the output.
    :type width: int

    :param height: The height of the output.
    :type height: int

    :param extent: The extent to use.
    :type extent: QgsRectangle

    :return: The new raster layer.
    :rtype: QgsRasterLayer
    """
    name = rasterize_steps['gdal_layer_name']
    output_filename = unique_filename(prefix=name, suffix='.tif')

    extent_str = '%f,%f,%f,%f' % (extent.xMinimum(), extent.xMaximum(),
                                  extent.yMinimum(), extent.yMaximum())

    keywords = dict(layer.keywords)

    # The layer is in memory, we need to save it to a file for Processing.
    data_store = Folder(mkdtemp())
    data_store.default_vector_format = 'geojson'
    result = data_store.add_layer(layer, 'vector_layer')
    layer = data_store.layer(result[1])
    assert layer.isValid()

    field = layer.keywords['inasafe_fields'][aggregation_id_field['key']]

    # ET 21/02/17. I got some issues using rasterize algorithm from Processing.
    # I keep it in case of we need it later. Let's use gdal command line.
    use_gdal_command_line = True

    if use_gdal_command_line:
        startupinfo = None
        if sys.platform == 'win32':
            # On windows, we don't want to display the bash shell.
            # https://github.com/inasafe/inasafe/issues/3980
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        commands = [which('gdal_rasterize')[0]]
        commands += ['-a', field]
        commands += ['-ts', str(width), str(height)]
        commands += ['-ot', 'Int16']
        commands += ['-a_nodata', "'-1'"]
        commands += [layer.source(), output_filename]

        LOGGER.info(' '.join(commands))
        result = subprocess.check_call(commands, startupinfo=startupinfo)
        LOGGER.info('Result : %s' % result)
    else:
        parameters = dict()
        parameters['INPUT'] = layer
        parameters['FIELD'] = field
        parameters['DIMENSIONS'] = 0  # output size is given in pixels
        parameters['WIDTH'] = width
        parameters['HEIGHT'] = height
        parameters['RASTER_EXT'] = extent_str
        parameters['TFW'] = False  # force generation of ESRI TFW
        parameters['RTYPE'] = 1  # raster type: Int16
        parameters['NO_DATA'] = '-1'  # nodata value
        parameters['COMPRESS'] = 4  # GeoTIFF compression: DEFLATE
        parameters['JPEGCOMPRESSION'] = 75  # JPEG compression level: 75
        parameters['ZLEVEL'] = 6  # DEFLATE compression level
        parameters['PREDICTOR'] = 1  # predictor for JPEG/DEFLATE
        parameters['TILED'] = False  # Tiled GeoTIFF?
        parameters['BIGTIFF'] = 0  # whether to make big TIFF
        parameters['EXTRA'] = ''  # additional creation parameters
        parameters['OUTPUT'] = output_filename

        result = runalg('gdalogr:rasterize', parameters)
        if result is None:
            # Let's try be removing a new parameter added between 2.14 and 2.16
            del parameters['RASTER_EXT']

        result = runalg('gdalogr:rasterize', parameters)
        assert result is not None

    layer_aligned = QgsRasterLayer(output_filename, name, 'gdal')
    assert layer_aligned.isValid()

    layer_aligned.keywords = keywords
    layer_aligned.keywords['title'] = (rasterize_steps['output_layer_name'] %
                                       'aggregation')
    layer_aligned.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])
    del layer_aligned.keywords['inasafe_fields']

    check_layer(layer_aligned)
    return layer_aligned
Exemplo n.º 44
0
def clip_by_extent(layer, extent):
    """Clip a raster using a bounding box using processing.

    Issue https://github.com/inasafe/inasafe/issues/3183

    :param layer: The layer to clip.
    :type layer: QgsRasterLayer

    :param extent: The extent.
    :type extent: QgsRectangle

    :return: Clipped layer.
    :rtype: QgsRasterLayer

    .. versionadded:: 4.0
    """
    parameters = dict()
    # noinspection PyBroadException
    try:
        output_layer_name = quick_clip_steps['output_layer_name']
        output_layer_name = output_layer_name % layer.keywords['layer_purpose']

        output_raster = unique_filename(suffix='.tif', dir=temp_dir())

        # We make one pixel size buffer on the extent to cover every pixels.
        # See https://github.com/inasafe/inasafe/issues/3655
        pixel_size_x = layer.rasterUnitsPerPixelX()
        pixel_size_y = layer.rasterUnitsPerPixelY()
        buffer_size = max(pixel_size_x, pixel_size_y)
        extent = extent.buffered(buffer_size)

        if is_raster_y_inverted(layer):
            # The raster is Y inverted. We need to switch Y min and Y max.
            bbox = [
                str(extent.xMinimum()),
                str(extent.xMaximum()),
                str(extent.yMaximum()),
                str(extent.yMinimum())
            ]
        else:
            # The raster is normal.
            bbox = [
                str(extent.xMinimum()),
                str(extent.xMaximum()),
                str(extent.yMinimum()),
                str(extent.yMaximum())
            ]

        # These values are all from the processing algorithm.
        # https://github.com/qgis/QGIS/blob/master/python/plugins/processing/
        # algs/gdal/ClipByExtent.py
        # Please read the file to know these parameters.
        parameters['INPUT'] = layer.source()
        parameters['NO_DATA'] = ''
        parameters['PROJWIN'] = ','.join(bbox)
        parameters['DATA_TYPE'] = 5
        parameters['COMPRESS'] = 4
        parameters['JPEGCOMPRESSION'] = 75
        parameters['ZLEVEL'] = 6
        parameters['PREDICTOR'] = 1
        parameters['TILED'] = False
        parameters['BIGTIFF'] = 0
        parameters['TFW'] = False
        parameters['EXTRA'] = ''
        parameters['OUTPUT'] = output_raster

        initialize_processing()
        feedback = create_processing_feedback()
        context = create_processing_context(feedback=feedback)

        result = processing.run(
            "gdal:cliprasterbyextent",
            parameters,
            context=context)

        if result is None:
            raise ProcessingInstallationError

        clipped = QgsRasterLayer(result['OUTPUT'], output_layer_name)

        # We transfer keywords to the output.
        clipped.keywords = layer.keywords.copy()
        clipped.keywords['title'] = output_layer_name

        check_layer(clipped)

    except Exception as e:
        # This step clip_raster_by_extent was nice to speedup the analysis.
        # As we got an exception because the layer is invalid, we are not going
        # to stop the analysis. We will return the original raster layer.
        # It will take more processing time until we clip the vector layer.
        # Check https://github.com/inasafe/inasafe/issues/4026 why we got some
        # exceptions with this step.
        LOGGER.exception(parameters)
        LOGGER.exception(
            'Error from QGIS clip raster by extent. Please check the QGIS '
            'logs too !')
        LOGGER.info(
            'Even if we got an exception, we are continuing the analysis. The '
            'layer was not clipped.')
        LOGGER.exception(str(e))
        LOGGER.exception(get_error_message(e).to_text())
        clipped = layer

    return clipped
Exemplo n.º 45
0
def prepare_vector_layer(layer, callback=None):
    """This function will prepare the layer to be used in InaSAFE :
     * Make a local copy of the layer.
     * Make sure that we have an InaSAFE ID column.
     * Rename fields according to our definitions.
     * Remove fields which are not used.

    :param layer: The layer to prepare.
    :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: Cleaned memory layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = prepare_vector_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']
    processing_step = prepare_vector_steps['step_name']

    if not layer.keywords.get('inasafe_fields'):
        msg = 'inasafe_fields is missing in keywords from %s' % layer.name()
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    feature_count = layer.featureCount()

    cleaned = create_memory_layer(
        output_layer_name, layer.geometryType(), layer.crs(), layer.fields())

    # We transfer keywords to the output.
    cleaned.keywords = layer.keywords

    copy_layer(layer, cleaned)
    _remove_features(cleaned)

    # After removing rows, let's check if there is still a feature.
    request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)
    iterator = cleaned.getFeatures(request)
    try:
        next(iterator)
    except StopIteration:
        LOGGER.warning(
            tr('No feature has been found in the {purpose}'
                .format(purpose=layer.keywords['layer_purpose'])))
        raise NoFeaturesInExtentError

    _add_id_column(cleaned)
    rename_remove_inasafe_fields(cleaned)

    if _size_is_needed(cleaned):
        LOGGER.info(
            'We noticed some counts in your exposure layer. Before to update '
            'geometries, we compute the original size for each feature.')
        run_single_post_processor(cleaned, post_processor_size)

    if cleaned.keywords['layer_purpose'] == 'exposure':
        fields = cleaned.keywords['inasafe_fields']
        if exposure_type_field['key'] not in fields:
            _add_default_exposure_class(cleaned)

        # Check value mapping
        _check_value_mapping(cleaned)

    cleaned.keywords['title'] = output_layer_name

    check_layer(cleaned)
    return cleaned
Exemplo n.º 46
0
def reclassify(layer, exposure_key=None, overwrite_input=False, callback=None):
    """Reclassify a continuous raster layer.

    Issue https://github.com/inasafe/inasafe/issues/3182


    This function is a wrapper for the code from
    https://github.com/chiatt/gdal_reclassify

    For instance if you want to reclassify like this table :
            Original Value     |   Class
            - ∞ < val <= 0     |     1
            0   < val <= 0.5   |     2
            0.5 < val <= 5     |     3
            5   < val <  + ∞   |     6

    You need a dictionary :
        ranges = OrderedDict()
        ranges[1] = [None, 0]
        ranges[2] = [0.0, 0.5]
        ranges[3] = [0.5, 5]
        ranges[6] = [5, None]

    :param layer: The raster layer.
    :type layer: QgsRasterLayer

    :param overwrite_input: Option for the output layer. True will overwrite
        the input layer. False will create a temporary layer.
    :type overwrite_input: bool

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :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 classified raster layer.
    :rtype: QgsRasterLayer

    .. versionadded:: 4.0
    """
    output_layer_name = reclassify_raster_steps['output_layer_name']
    processing_step = reclassify_raster_steps['step_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    if exposure_key:
        classification_key = active_classification(layer.keywords,
                                                   exposure_key)
        thresholds = active_thresholds_value_maps(layer.keywords, exposure_key)
        layer.keywords['thresholds'] = thresholds
        layer.keywords['classification'] = classification_key
    else:
        classification_key = layer.keywords.get('classification')
        thresholds = layer.keywords.get('thresholds')
    if not thresholds:
        raise InvalidKeywordsForProcessingAlgorithm(
            'thresholds are missing from the layer %s' %
            layer.keywords['layer_purpose'])

    if not classification_key:
        raise InvalidKeywordsForProcessingAlgorithm(
            'classification is missing from the layer %s' %
            layer.keywords['layer_purpose'])

    ranges = {}
    value_map = {}
    hazard_classes = definition(classification_key)['classes']
    for hazard_class in hazard_classes:
        ranges[hazard_class['value']] = thresholds[hazard_class['key']]
        value_map[hazard_class['key']] = [hazard_class['value']]

    if overwrite_input:
        output_raster = layer.source()
    else:
        output_raster = unique_filename(suffix='.tiff', dir=temp_dir())

    driver = gdal.GetDriverByName('GTiff')

    raster_file = gdal.Open(layer.source())
    band = raster_file.GetRasterBand(1)
    no_data = band.GetNoDataValue()
    source = band.ReadAsArray()
    destination = source.copy()

    for value, interval in ranges.iteritems():
        v_min = interval[0]
        v_max = interval[1]

        if v_min is None:
            destination[np.where(source <= v_max)] = value

        if v_max is None:
            destination[np.where(source > v_min)] = value

        if v_min < v_max:
            destination[np.where((v_min < source) & (source <= v_max))] = value

    # Tag no data cells
    destination[np.where(source == no_data)] = no_data_value

    # Create the new file.
    output_file = driver.Create(output_raster, raster_file.RasterXSize,
                                raster_file.RasterYSize, 1)
    output_file.GetRasterBand(1).WriteArray(destination)
    output_file.GetRasterBand(1).SetNoDataValue(no_data_value)

    # CRS
    output_file.SetProjection(raster_file.GetProjection())
    output_file.SetGeoTransform(raster_file.GetGeoTransform())
    output_file.FlushCache()

    del output_file

    if not isfile(output_raster):
        raise FileNotFoundError

    reclassified = QgsRasterLayer(output_raster, output_layer_name)

    # We transfer keywords to the output.
    reclassified.keywords = layer.keywords.copy()
    reclassified.keywords['layer_mode'] = 'classified'

    value_map = {}

    hazard_classes = definition(classification_key)['classes']
    for hazard_class in reversed(hazard_classes):
        value_map[hazard_class['key']] = [hazard_class['value']]

    reclassified.keywords['value_map'] = value_map
    reclassified.keywords['title'] = output_layer_name

    check_layer(reclassified)
    return reclassified
Exemplo n.º 47
0
def assign_highest_value(exposure, hazard, callback=None):
    """Assign the highest hazard value to an indivisible feature.

    For indivisible polygon exposure layers such as buildings, we need to
    assigned the greatest hazard that each polygon touches and use that as the
    effective hazard class.

    Issue https://github.com/inasafe/inasafe/issues/3192

    We follow the concept here that any part of the exposure dataset that
    touches the hazard is affected, and the greatest hazard is the effective
    hazard.

    :param exposure: The building vector layer.
    :type exposure: QgsVectorLayer

    :param hazard: The vector layer to use for hazard.
    :type hazard: 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 impact layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_highest_value_steps['output_layer_name']
    processing_step = assign_highest_value_steps['step_name']

    hazard_inasafe_fields = hazard.keywords['inasafe_fields']

    if not hazard.keywords.get('classification'):
        raise InvalidKeywordsForProcessingAlgorithm
    if not hazard_inasafe_fields.get(hazard_class_field['key']):
        raise InvalidKeywordsForProcessingAlgorithm

    indices = []
    exposure.startEditing()
    for field in hazard.fields():
        exposure.addAttribute(field)
        indices.append(exposure.fieldNameIndex(field.name()))
    exposure.commitChanges()
    provider = exposure.dataProvider()

    spatial_index = create_spatial_index(exposure)

    # cache features from exposure layer for faster retrieval
    exposure_features = {}
    for f in exposure.getFeatures():
        exposure_features[f.id()] = f

    # Todo callback
    # total = 100.0 / len(selectionA)

    hazard_field = hazard_inasafe_fields[hazard_class_field['key']]

    layer_classification = None
    for classification in hazard_classification['types']:
        if classification['key'] == hazard.keywords['classification']:
            layer_classification = classification
            break

    # Get a ordered list of classes like ['high', 'medium', 'low']
    levels = [key['key'] for key in layer_classification['classes']]
    levels.append(not_exposed_class['key'])

    # Let's loop over the hazard layer, from high to low hazard zone.
    for hazard_value in levels:
        expression = '"%s" = \'%s\'' % (hazard_field, hazard_value)
        hazard_request = QgsFeatureRequest().setFilterExpression(expression)
        update_map = {}
        for area in hazard.getFeatures(hazard_request):
            geometry = area.geometry()
            intersects = spatial_index.intersects(geometry.boundingBox())

            # use prepared geometry: makes multiple intersection tests faster
            geometry_prepared = QgsGeometry.createGeometryEngine(
                geometry.geometry())
            geometry_prepared.prepareGeometry()

            # We need to loop over each intersections exposure / hazard.
            for i in intersects:
                building = exposure_features[i]
                building_geometry = building.geometry()

                if geometry_prepared.intersects(building_geometry.geometry()):
                    update_map[building.id()] = {}
                    for index, value in zip(indices, area.attributes()):
                        update_map[building.id()][index] = value

                    # We don't want this building again, let's remove it from
                    # the index.
                    spatial_index.deleteFeature(building)

        provider.changeAttributeValues(update_map)

    exposure.updateExtents()
    exposure.updateFields()

    exposure.keywords['inasafe_fields'].update(
        hazard.keywords['inasafe_fields'])
    exposure.keywords['layer_purpose'] = layer_purpose_exposure_summary['key']

    exposure.keywords['exposure_keywords'] = exposure.keywords.copy()
    exposure.keywords['aggregation_keywords'] = (
        hazard.keywords['aggregation_keywords'].copy())
    exposure.keywords['hazard_keywords'] = (
        hazard.keywords['hazard_keywords'].copy())

    exposure.keywords['title'] = output_layer_name

    check_layer(exposure)
    return exposure
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
Exemplo n.º 49
0
def recompute_counts(layer, callback=None):
    """Recompute counts according to the size field and the new size.

    This function will also take care of updating the size field. The size
    post processor won't run after this function again.

    :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 updated counts.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = recompute_counts_steps['output_layer_name']
    processing_step = recompute_counts_steps['step_name']

    fields = layer.keywords['inasafe_fields']

    if size_field['key'] not in fields:
        # noinspection PyTypeChecker
        msg = '%s not found in %s' % (size_field['key'],
                                      layer.keywords['title'])
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    indexes = []
    absolute_field_keys = [f['key'] for f in count_fields]
    for field, field_name in fields.iteritems():
        if field in absolute_field_keys and field != size_field['key']:
            indexes.append(layer.fieldNameIndex(field_name))
            LOGGER.info(
                'We detected the count {field_name}, we will recompute the '
                'count according to the new size.'.format(
                    field_name=field_name))

    if not len(indexes):
        msg = 'Absolute field not found in the layer %s' % (
            layer.keywords['title'])
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    size_field_name = fields[size_field['key']]
    size_field_index = layer.fieldNameIndex(size_field_name)

    layer.startEditing()

    exposure_key = layer.keywords['exposure_keywords']['exposure']
    size_calculator = SizeCalculator(layer.crs(), layer.geometryType(),
                                     exposure_key)

    for feature in layer.getFeatures():
        old_size = feature[size_field_name]
        new_size = size(size_calculator=size_calculator,
                        geometry=feature.geometry())

        layer.changeAttributeValue(feature.id(), size_field_index, new_size)

        # Cross multiplication for each field
        for index in indexes:
            old_count = feature[index]
            try:
                new_value = new_size * old_count / old_size
            except TypeError:
                new_value = ''
            layer.changeAttributeValue(feature.id(), index, new_value)

    layer.commitChanges()

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 50
0
def union(union_a, union_b, callback=None):
    """Union of two vector layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    Note : This algorithm is copied from :
    https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/
    qgis/Union.py

    :param union_a: The vector layer for the union.
    :type union_a: QgsVectorLayer

    :param union_b: The vector layer for the union.
    :type union_b: 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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = union_steps['output_layer_name']
    processing_step = union_steps['step_name']
    output_layer_name = output_layer_name % (
        union_a.keywords['layer_purpose'],
        union_b.keywords['layer_purpose']
    )

    fields = union_a.fields()
    fields.extend(union_b.fields())

    writer = create_memory_layer(
        output_layer_name,
        union_a.geometryType(),
        union_a.crs(),
        fields
    )
    keywords_union_1 = union_a.keywords
    keywords_union_2 = union_b.keywords
    inasafe_fields_union_1 = keywords_union_1['inasafe_fields']
    inasafe_fields_union_2 = keywords_union_2['inasafe_fields']
    inasafe_fields = inasafe_fields_union_1
    inasafe_fields.update(inasafe_fields_union_2)

    # use to avoid modifying original source
    writer.keywords = dict(union_a.keywords)
    writer.keywords['inasafe_fields'] = inasafe_fields
    writer.keywords['title'] = output_layer_name
    writer.keywords['layer_purpose'] = 'aggregate_hazard'
    writer.keywords['hazard_keywords'] = keywords_union_1.copy()
    writer.keywords['aggregation_keywords'] = keywords_union_2.copy()
    skip_field = inasafe_fields_union_2[aggregation_id_field['key']]
    not_null_field_index = writer.fieldNameIndex(skip_field)

    writer.startEditing()

    # Begin copy/paste from Processing plugin.
    # Please follow their code as their code is optimized.
    # The code below is not following our coding standards because we want to
    # be able to track any diffs from QGIS easily.

    index_a = create_spatial_index(union_b)
    index_b = create_spatial_index(union_a)

    count = 0
    n_element = 0
    # Todo fix callback
    # nFeat = len(union_a.getFeatures())
    for in_feat_a in union_a.getFeatures():
        # progress.setPercentage(nElement / float(nFeat) * 50)
        n_element += 1
        list_intersecting_b = []
        geom = geometry_checker(in_feat_a.geometry())
        at_map_a = in_feat_a.attributes()
        intersects = index_a.intersects(geom.boundingBox())
        if len(intersects) < 1:
            try:
                _write_feature(at_map_a, geom, writer, not_null_field_index)
            except:
                # This really shouldn't happen, as we haven't
                # edited the input geom at all
                LOGGER.debug(
                    tr('Feature geometry error: One or more output features '
                       'ignored due to invalid geometry.'))
        else:
            request = QgsFeatureRequest().setFilterFids(intersects)

            engine = QgsGeometry.createGeometryEngine(geom.geometry())
            engine.prepareGeometry()

            for in_feat_b in union_b.getFeatures(request):
                count += 1

                at_map_b = in_feat_b.attributes()
                tmp_geom = geometry_checker(in_feat_b.geometry())

                if engine.intersects(tmp_geom.geometry()):
                    int_geom = geometry_checker(geom.intersection(tmp_geom))
                    list_intersecting_b.append(QgsGeometry(tmp_geom))

                    if not int_geom:
                        # There was a problem creating the intersection
                        # LOGGER.debug(
                        #     tr('GEOS geoprocessing error: One or more input '
                        #        'features have invalid geometry.'))
                        pass
                        int_geom = QgsGeometry()
                    else:
                        int_geom = QgsGeometry(int_geom)

                    if int_geom.wkbType() == QgsWKBTypes.Unknown\
                            or QgsWKBTypes.flatType(
                            int_geom.geometry().wkbType()) == \
                                    QgsWKBTypes.GeometryCollection:
                        # Intersection produced different geometry types
                        temp_list = int_geom.asGeometryCollection()
                        for i in temp_list:
                            if i.type() == geom.type():
                                int_geom = QgsGeometry(geometry_checker(i))
                                try:
                                    _write_feature(
                                        at_map_a + at_map_b,
                                        int_geom,
                                        writer,
                                        not_null_field_index,
                                    )
                                except:
                                    LOGGER.debug(
                                        tr('Feature geometry error: One or '
                                           'more output features ignored due '
                                           'to invalid geometry.'))
                    else:
                        # Geometry list: prevents writing error
                        # in geometries of different types
                        # produced by the intersection
                        # fix #3549
                        if int_geom.wkbType() in wkb_type_groups[
                            wkb_type_groups[int_geom.wkbType()]]:
                            try:
                                _write_feature(
                                    at_map_a + at_map_b,
                                    int_geom,
                                    writer,
                                    not_null_field_index)
                            except:
                                LOGGER.debug(
                                    tr('Feature geometry error: One or more '
                                       'output features ignored due to '
                                       'invalid geometry.'))

            # the remaining bit of inFeatA's geometry
            # if there is nothing left, this will just silently fail and we
            # are good
            diff_geom = QgsGeometry(geom)
            if len(list_intersecting_b) != 0:
                int_b = QgsGeometry.unaryUnion(list_intersecting_b)
                diff_geom = geometry_checker(diff_geom.difference(int_b))
                if diff_geom is None or \
                    diff_geom.isGeosEmpty() or not diff_geom.isGeosValid():
                    # LOGGER.debug(
                    #     tr('GEOS geoprocessing error: One or more input '
                    #        'features have invalid geometry.'))
                    pass

            if diff_geom is not None and (
                            diff_geom.wkbType() == 0 or QgsWKBTypes.flatType(
                    diff_geom.geometry().wkbType()) ==
                    QgsWKBTypes.GeometryCollection):
                temp_list = diff_geom.asGeometryCollection()
                for i in temp_list:
                    if i.type() == geom.type():
                        diff_geom = QgsGeometry(geometry_checker(i))
            try:
                _write_feature(
                    at_map_a,
                    diff_geom,
                    writer,
                    not_null_field_index)
            except:
                LOGGER.debug(
                    tr('Feature geometry error: One or more output features '
                       'ignored due to invalid geometry.'))

    length = len(union_a.fields())
    at_map_a = [None] * length

    # nFeat = len(union_b.getFeatures())
    for in_feat_a in union_b.getFeatures():
        # progress.setPercentage(nElement / float(nFeat) * 100)
        add = False
        geom = geometry_checker(in_feat_a.geometry())
        atMap = [None] * length
        atMap.extend(in_feat_a.attributes())
        intersects = index_b.intersects(geom.boundingBox())
        lstIntersectingA = []

        for id in intersects:
            request = QgsFeatureRequest().setFilterFid(id)
            inFeatB = union_a.getFeatures(request).next()
            atMapB = inFeatB.attributes()
            tmpGeom = QgsGeometry(geometry_checker(inFeatB.geometry()))

            if geom.intersects(tmpGeom):
                lstIntersectingA.append(tmpGeom)

        if len(lstIntersectingA) == 0:
            res_geom = geom
        else:
            intA = QgsGeometry.unaryUnion(lstIntersectingA)
            res_geom = geom.difference(intA)
            if res_geom is None:
                # LOGGER.debug(
                #    tr('GEOS geoprocessing error: One or more input features '
                #        'have null geometry.'))
                pass
                continue  # maybe it is better to fail like @gustry
                # does below ....
            if res_geom.isGeosEmpty() or not res_geom.isGeosValid():
                # LOGGER.debug(
                #    tr('GEOS geoprocessing error: One or more input features '
                #        'have invalid geometry.'))
                pass

        try:
            _write_feature(atMap, res_geom, writer, not_null_field_index)
        except:
            # LOGGER.debug(
            #     tr('Feature geometry error: One or more output features '
            #        'ignored due to invalid geometry.'))
            pass

        n_element += 1

    # End of copy/paste from processing

    writer.commitChanges()

    fill_hazard_class(writer)

    check_layer(writer)
    return writer
Exemplo n.º 51
0
def union(union_a, union_b, callback=None):
    """Union of two vector layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    Note : This algorithm is copied from :
    https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/
    qgis/Union.py

    :param union_a: The vector layer for the union.
    :type union_a: QgsVectorLayer

    :param union_b: The vector layer for the union.
    :type union_b: 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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = union_steps['output_layer_name']
    processing_step = union_steps['step_name']  # NOQA
    output_layer_name = output_layer_name % (
        union_a.keywords['layer_purpose'],
        union_b.keywords['layer_purpose']
    )

    fields = union_a.fields()
    fields.extend(union_b.fields())

    writer = create_memory_layer(
        output_layer_name,
        union_a.geometryType(),
        union_a.crs(),
        fields
    )
    keywords_union_1 = union_a.keywords
    keywords_union_2 = union_b.keywords
    inasafe_fields_union_1 = keywords_union_1['inasafe_fields']
    inasafe_fields_union_2 = keywords_union_2['inasafe_fields']
    inasafe_fields = inasafe_fields_union_1
    inasafe_fields.update(inasafe_fields_union_2)

    # use to avoid modifying original source
    writer.keywords = dict(union_a.keywords)
    writer.keywords['inasafe_fields'] = inasafe_fields
    writer.keywords['title'] = output_layer_name
    writer.keywords['layer_purpose'] = 'aggregate_hazard'
    writer.keywords['hazard_keywords'] = keywords_union_1.copy()
    writer.keywords['aggregation_keywords'] = keywords_union_2.copy()
    skip_field = inasafe_fields_union_2[aggregation_id_field['key']]
    not_null_field_index = writer.fieldNameIndex(skip_field)

    writer.startEditing()

    # Begin copy/paste from Processing plugin.
    # Please follow their code as their code is optimized.
    # The code below is not following our coding standards because we want to
    # be able to track any diffs from QGIS easily.

    index_a = create_spatial_index(union_b)
    index_b = create_spatial_index(union_a)

    count = 0
    n_element = 0
    # Todo fix callback
    # nFeat = len(union_a.getFeatures())
    for in_feat_a in union_a.getFeatures():
        # progress.setPercentage(nElement / float(nFeat) * 50)
        n_element += 1
        list_intersecting_b = []
        geom = geometry_checker(in_feat_a.geometry())
        at_map_a = in_feat_a.attributes()
        intersects = index_a.intersects(geom.boundingBox())
        if len(intersects) < 1:
            try:
                _write_feature(at_map_a, geom, writer, not_null_field_index)
            except:
                # This really shouldn't happen, as we haven't
                # edited the input geom at all
                LOGGER.debug(
                    tr('Feature geometry error: One or more output features '
                       'ignored due to invalid geometry.'))
        else:
            request = QgsFeatureRequest().setFilterFids(intersects)

            engine = QgsGeometry.createGeometryEngine(geom.geometry())
            engine.prepareGeometry()

            for in_feat_b in union_b.getFeatures(request):
                count += 1

                at_map_b = in_feat_b.attributes()
                tmp_geom = geometry_checker(in_feat_b.geometry())

                if engine.intersects(tmp_geom.geometry()):
                    int_geom = geometry_checker(geom.intersection(tmp_geom))
                    list_intersecting_b.append(QgsGeometry(tmp_geom))

                    if not int_geom:
                        # There was a problem creating the intersection
                        # LOGGER.debug(
                        #     tr('GEOS geoprocessing error: One or more input '
                        #        'features have invalid geometry.'))
                        pass
                        int_geom = QgsGeometry()
                    else:
                        int_geom = QgsGeometry(int_geom)

                    if int_geom.wkbType() == QgsWKBTypes.Unknown\
                            or QgsWKBTypes.flatType(
                            int_geom.geometry().wkbType()) == \
                            QgsWKBTypes.GeometryCollection:
                        # Intersection produced different geometry types
                        temp_list = int_geom.asGeometryCollection()
                        for i in temp_list:
                            if i.type() == geom.type():
                                int_geom = QgsGeometry(geometry_checker(i))
                                try:
                                    _write_feature(
                                        at_map_a + at_map_b,
                                        int_geom,
                                        writer,
                                        not_null_field_index,
                                    )
                                except:
                                    LOGGER.debug(
                                        tr('Feature geometry error: One or '
                                           'more output features ignored due '
                                           'to invalid geometry.'))
                    else:
                        # Geometry list: prevents writing error
                        # in geometries of different types
                        # produced by the intersection
                        # fix #3549
                        if int_geom.wkbType() in wkb_type_groups[
                                wkb_type_groups[int_geom.wkbType()]]:
                            try:
                                _write_feature(
                                    at_map_a + at_map_b,
                                    int_geom,
                                    writer,
                                    not_null_field_index)
                            except:
                                LOGGER.debug(
                                    tr('Feature geometry error: One or more '
                                       'output features ignored due to '
                                       'invalid geometry.'))

            # the remaining bit of inFeatA's geometry
            # if there is nothing left, this will just silently fail and we
            # are good
            diff_geom = QgsGeometry(geom)
            if len(list_intersecting_b) != 0:
                int_b = QgsGeometry.unaryUnion(list_intersecting_b)
                diff_geom = geometry_checker(diff_geom.difference(int_b))
                if diff_geom is None or \
                        diff_geom.isGeosEmpty() or not diff_geom.isGeosValid():
                    # LOGGER.debug(
                    #     tr('GEOS geoprocessing error: One or more input '
                    #        'features have invalid geometry.'))
                    pass

            if diff_geom is not None and (
                diff_geom.wkbType() == 0 or QgsWKBTypes.flatType(
                    diff_geom.geometry().wkbType()) ==
                    QgsWKBTypes.GeometryCollection):
                temp_list = diff_geom.asGeometryCollection()
                for i in temp_list:
                    if i.type() == geom.type():
                        diff_geom = QgsGeometry(geometry_checker(i))
            try:
                _write_feature(
                    at_map_a,
                    diff_geom,
                    writer,
                    not_null_field_index)
            except:
                LOGGER.debug(
                    tr('Feature geometry error: One or more output features '
                       'ignored due to invalid geometry.'))

    length = len(union_a.fields())

    # nFeat = len(union_b.getFeatures())
    for in_feat_a in union_b.getFeatures():
        # progress.setPercentage(nElement / float(nFeat) * 100)
        geom = geometry_checker(in_feat_a.geometry())
        atMap = [None] * length
        atMap.extend(in_feat_a.attributes())
        intersects = index_b.intersects(geom.boundingBox())
        lstIntersectingA = []

        for id in intersects:
            request = QgsFeatureRequest().setFilterFid(id)
            inFeatB = union_a.getFeatures(request).next()
            tmpGeom = QgsGeometry(geometry_checker(inFeatB.geometry()))

            if geom.intersects(tmpGeom):
                lstIntersectingA.append(tmpGeom)

        if len(lstIntersectingA) == 0:
            res_geom = geom
        else:
            intA = QgsGeometry.unaryUnion(lstIntersectingA)
            res_geom = geom.difference(intA)
            if res_geom is None:
                # LOGGER.debug(
                #    tr('GEOS geoprocessing error: One or more input features '
                #        'have null geometry.'))
                pass
                continue  # maybe it is better to fail like @gustry
                # does below ....
            if res_geom.isGeosEmpty() or not res_geom.isGeosValid():
                # LOGGER.debug(
                #    tr('GEOS geoprocessing error: One or more input features '
                #        'have invalid geometry.'))
                pass

        try:
            _write_feature(atMap, res_geom, writer, not_null_field_index)
        except:
            # LOGGER.debug(
            #     tr('Feature geometry error: One or more output features '
            #        'ignored due to invalid geometry.'))
            pass

        n_element += 1

    # End of copy/paste from processing

    writer.commitChanges()

    fill_hazard_class(writer)

    check_layer(writer)
    return writer
Exemplo n.º 52
0
def clip(layer_to_clip, mask_layer, callback=None):
    """Clip a vector layer with another.

    Issue https://github.com/inasafe/inasafe/issues/3186

    Note : This algorithm is copied from :
    https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/
    qgis/Clip.py

    :param layer_to_clip: The vector layer to clip.
    :type layer_to_clip: QgsVectorLayer

    :param mask_layer: The vector layer to use for clipping.
    :type mask_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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = clip_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        layer_to_clip.keywords['layer_purpose'])
    processing_step = clip_steps['step_name']

    writer = create_memory_layer(output_layer_name,
                                 layer_to_clip.geometryType(),
                                 layer_to_clip.crs(), layer_to_clip.fields())
    writer.startEditing()

    # Begin copy/paste from Processing plugin.
    # Please follow their code as their code is optimized.
    # The code below is not following our coding standards because we want to
    # be able to track any diffs from QGIS easily.

    # first build up a list of clip geometries
    clip_geometries = []
    request = QgsFeatureRequest().setSubsetOfAttributes([])
    for mask_feature in mask_layer.getFeatures(request):
        clip_geometries.append(QgsGeometry(mask_feature.geometry()))

    # are we clipping against a single feature? if so,
    # we can show finer progress reports
    if len(clip_geometries) > 1:
        # noinspection PyTypeChecker,PyCallByClass,PyArgumentList
        combined_clip_geom = QgsGeometry.unaryUnion(clip_geometries)
        single_clip_feature = False
    else:
        combined_clip_geom = clip_geometries[0]
        single_clip_feature = True

    # use prepared geometries for faster intersection tests
    # noinspection PyArgumentList
    engine = QgsGeometry.createGeometryEngine(combined_clip_geom.geometry())
    engine.prepareGeometry()

    tested_feature_ids = set()

    for i, clip_geom in enumerate(clip_geometries):
        request = QgsFeatureRequest().setFilterRect(clip_geom.boundingBox())
        input_features = [f for f in layer_to_clip.getFeatures(request)]

        if not input_features:
            continue

        if single_clip_feature:
            total = 100.0 / len(input_features)
        else:
            total = 0

        for current, in_feat in enumerate(input_features):
            if not in_feat.geometry():
                continue

            if in_feat.id() in tested_feature_ids:
                # don't retest a feature we have already checked
                continue

            tested_feature_ids.add(in_feat.id())

            if not engine.intersects(in_feat.geometry().geometry()):
                continue

            if not engine.contains(in_feat.geometry().geometry()):
                cur_geom = in_feat.geometry()
                new_geom = combined_clip_geom.intersection(cur_geom)
                if new_geom.wkbType() == QgsWKBTypes.Unknown \
                        or QgsWKBTypes.flatType(
                            new_geom.geometry().wkbType()) == \
                                QgsWKBTypes.GeometryCollection:
                    int_com = in_feat.geometry().combine(new_geom)
                    int_sym = in_feat.geometry().symDifference(new_geom)
                    if not int_com or not int_sym:
                        # LOGGER.debug(
                        #     tr('GEOS geoprocessing error: One or more input '
                        #        'features have invalid geometry.'))
                        pass
                    else:
                        new_geom = int_com.difference(int_sym)
                        if new_geom.isGeosEmpty()\
                                or not new_geom.isGeosValid():
                            # LOGGER.debug(
                            #     tr('GEOS geoprocessing error: One or more '
                            #        'input features have invalid geometry.'))
                            pass
            else:
                # clip geometry totally contains feature geometry,
                # so no need to perform intersection
                new_geom = in_feat.geometry()

            try:
                out_feat = QgsFeature()
                out_feat.setGeometry(new_geom)
                out_feat.setAttributes(in_feat.attributes())
                writer.addFeature(out_feat)
            except:
                LOGGER.debug(
                    tr('Feature geometry error: One or more output features '
                       'ignored due to invalid geometry.'))
                continue

            # TODO implement callback
            if single_clip_feature:
                # progress.setPercentage(int(current * total))
                pass

        if not single_clip_feature:
            # coarse progress report for multiple clip geometries
            # progress.setPercentage(100.0 * i / len(clip_geoms))
            pass

    # End copy/paste from Processing plugin.
    writer.commitChanges()

    writer.keywords = layer_to_clip.keywords.copy()
    writer.keywords['title'] = output_layer_name
    check_layer(writer)
    return writer
Exemplo n.º 53
0
def clip(layer_to_clip, mask_layer, callback=None):
    """Clip a vector layer with another.

    Issue https://github.com/inasafe/inasafe/issues/3186

    Note : This algorithm is copied from :
    https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/
    qgis/Clip.py

    :param layer_to_clip: The vector layer to clip.
    :type layer_to_clip: QgsVectorLayer

    :param mask_layer: The vector layer to use for clipping.
    :type mask_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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = clip_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        layer_to_clip.keywords['layer_purpose'])
    processing_step = clip_steps['step_name']

    writer = create_memory_layer(
        output_layer_name,
        layer_to_clip.geometryType(),
        layer_to_clip.crs(),
        layer_to_clip.fields()
    )
    writer.startEditing()

    # Begin copy/paste from Processing plugin.
    # Please follow their code as their code is optimized.
    # The code below is not following our coding standards because we want to
    # be able to track any diffs from QGIS easily.

    # first build up a list of clip geometries
    clip_geometries = []
    request = QgsFeatureRequest().setSubsetOfAttributes([])
    for mask_feature in mask_layer.getFeatures(request):
        clip_geometries.append(QgsGeometry(mask_feature.geometry()))

    # are we clipping against a single feature? if so,
    # we can show finer progress reports
    if len(clip_geometries) > 1:
        # noinspection PyTypeChecker,PyCallByClass,PyArgumentList
        combined_clip_geom = QgsGeometry.unaryUnion(clip_geometries)
        single_clip_feature = False
    else:
        combined_clip_geom = clip_geometries[0]
        single_clip_feature = True

    # use prepared geometries for faster intersection tests
    # noinspection PyArgumentList
    engine = QgsGeometry.createGeometryEngine(combined_clip_geom.geometry())
    engine.prepareGeometry()

    tested_feature_ids = set()

    for i, clip_geom in enumerate(clip_geometries):
        request = QgsFeatureRequest().setFilterRect(clip_geom.boundingBox())
        input_features = [f for f in layer_to_clip.getFeatures(request)]

        if not input_features:
            continue

        if single_clip_feature:
            total = 100.0 / len(input_features)
        else:
            total = 0

        for current, in_feat in enumerate(input_features):
            if not in_feat.geometry():
                continue

            if in_feat.id() in tested_feature_ids:
                # don't retest a feature we have already checked
                continue

            tested_feature_ids.add(in_feat.id())

            if not engine.intersects(in_feat.geometry().geometry()):
                continue

            if not engine.contains(in_feat.geometry().geometry()):
                cur_geom = in_feat.geometry()
                new_geom = combined_clip_geom.intersection(cur_geom)
                if new_geom.wkbType() == QgsWKBTypes.Unknown \
                        or QgsWKBTypes.flatType(
                            new_geom.geometry().wkbType()) == \
                                QgsWKBTypes.GeometryCollection:
                    int_com = in_feat.geometry().combine(new_geom)
                    int_sym = in_feat.geometry().symDifference(new_geom)
                    if not int_com or not int_sym:
                        # LOGGER.debug(
                        #     tr('GEOS geoprocessing error: One or more input '
                        #        'features have invalid geometry.'))
                        pass
                    else:
                        new_geom = int_com.difference(int_sym)
                        if new_geom.isGeosEmpty()\
                                or not new_geom.isGeosValid():
                            # LOGGER.debug(
                            #     tr('GEOS geoprocessing error: One or more '
                            #        'input features have invalid geometry.'))
                            pass
            else:
                # clip geometry totally contains feature geometry,
                # so no need to perform intersection
                new_geom = in_feat.geometry()

            try:
                out_feat = QgsFeature()
                out_feat.setGeometry(new_geom)
                out_feat.setAttributes(in_feat.attributes())
                if new_geom.type() == layer_to_clip.geometryType():
                    writer.addFeature(out_feat)
            except:
                LOGGER.debug(
                    tr('Feature geometry error: One or more output features '
                       'ignored due to invalid geometry.'))
                continue

            # TODO implement callback
            if single_clip_feature:
                # progress.setPercentage(int(current * total))
                pass

        if not single_clip_feature:
            # coarse progress report for multiple clip geometries
            # progress.setPercentage(100.0 * i / len(clip_geoms))
            pass

    # End copy/paste from Processing plugin.
    writer.commitChanges()

    writer.keywords = layer_to_clip.keywords.copy()
    writer.keywords['title'] = output_layer_name
    check_layer(writer)
    return writer
Exemplo n.º 54
0
def update_value_map(layer, exposure_key=None, callback=None):
    """Assign inasafe values according to definitions for a vector layer.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :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 classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_inasafe_values_steps['output_layer_name']
    processing_step = assign_inasafe_values_steps['step_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    keywords = layer.keywords
    inasafe_fields = keywords['inasafe_fields']

    classification = None
    if keywords['layer_purpose'] == layer_purpose_hazard['key']:
        if not inasafe_fields.get(hazard_value_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = hazard_value_field
        new_field = hazard_class_field
        classification = active_classification(layer.keywords, exposure_key)

    elif keywords['layer_purpose'] == layer_purpose_exposure['key']:
        if not inasafe_fields.get(exposure_type_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = exposure_type_field
        new_field = exposure_class_field
    else:
        raise InvalidKeywordsForProcessingAlgorithm

    # It's a hazard layer
    if exposure_key:
        if not active_thresholds_value_maps(keywords, exposure_key):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = active_thresholds_value_maps(keywords, exposure_key)
    # It's exposure layer
    else:
        if not keywords.get('value_map'):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = keywords.get('value_map')

    unclassified_column = inasafe_fields[old_field['key']]
    unclassified_index = layer.fieldNameIndex(unclassified_column)

    reversed_value_map = {}
    for inasafe_class, values in value_map.iteritems():
        for val in values:
            reversed_value_map[val] = inasafe_class

    classified_field = QgsField()
    classified_field.setType(new_field['type'])
    classified_field.setName(new_field['field_name'])
    classified_field.setLength(new_field['length'])
    classified_field.setPrecision(new_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fieldNameIndex(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[unclassified_index]
        classified_value = reversed_value_map.get(source_value)

        if not classified_value:
            classified_value = ''

        layer.changeAttributeValue(
            feature.id(), classified_field_index, classified_value)

    layer.commitChanges()

    remove_fields(layer, [unclassified_column])

    # We transfer keywords to the output.
    # We add new class field
    inasafe_fields[new_field['key']] = new_field['field_name']

    # and we remove hazard value field
    inasafe_fields.pop(old_field['key'])

    layer.keywords = keywords
    layer.keywords['inasafe_fields'] = inasafe_fields
    if exposure_key:
        value_map_key = 'value_maps'
    else:
        value_map_key = 'value_map'
    if value_map_key in layer.keywords.keys():
        layer.keywords.pop(value_map_key)
    layer.keywords['title'] = output_layer_name
    if classification:
        layer.keywords['classification'] = classification

    check_layer(layer)
    return layer
def exposure_summary_table(aggregate_hazard,
                           exposure_summary=None,
                           callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | exposure_count |

    Output layer :
    | exp_type | count_hazard_class | total |

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param exposure_summary: The layer impact layer.
    :type exposure_summary: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new tabular table, without geometry.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_4_exposure_summary_table_steps[
        'output_layer_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']

    source_compulsory_fields = [
        aggregation_id_field, aggregation_name_field, hazard_id_field,
        hazard_class_field, affected_field, total_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    absolute_values = create_absolute_values_structure(aggregate_hazard,
                                                       ['all'])

    hazard_class = source_fields[hazard_class_field['key']]
    hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    unique_exposure = read_dynamic_inasafe_field(source_fields,
                                                 exposure_count_field)

    flat_table = FlatTable('hazard_class', 'exposure_class')

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregate_hazard.getFeatures():
        hazard_value = area[hazard_class_index]
        for exposure in unique_exposure:
            key_name = exposure_count_field['key'] % exposure
            field_name = source_fields[key_name]
            exposure_count = area[field_name]
            if not exposure_count:
                exposure_count = 0

            flat_table.add_value(exposure_count,
                                 hazard_class=hazard_value,
                                 exposure_class=exposure)

        # We summarize every absolute values.
        for field, field_definition in list(absolute_values.items()):
            value = area[field]
            if not value:
                value = 0
            field_definition[0].add_value(value, all='all')

    tabular = create_memory_layer(output_layer_name, QgsWkbTypes.NullGeometry)
    tabular.startEditing()

    field = create_field_from_definition(exposure_type_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][exposure_type_field['key']] = (
        exposure_type_field['field_name'])

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    hazard = hazard_keywords['hazard']
    classification = hazard_keywords['classification']

    exposure_keywords = aggregate_hazard.keywords['exposure_keywords']
    exposure = exposure_keywords['exposure']

    hazard_affected = {}
    for hazard_class in unique_hazard:
        if (hazard_class == '' or hazard_class is None or
            (hasattr(hazard_class, 'isNull') and hazard_class.isNull())):
            hazard_class = 'NULL'
        field = create_field_from_definition(hazard_count_field, hazard_class)
        tabular.addAttribute(field)
        key = hazard_count_field['key'] % hazard_class
        value = hazard_count_field['field_name'] % hazard_class
        tabular.keywords['inasafe_fields'][key] = value

        hazard_affected[hazard_class] = post_processor_affected_function(
            exposure=exposure,
            hazard=hazard,
            classification=classification,
            hazard_class=hazard_class)

    field = create_field_from_definition(total_affected_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_affected_field['key']] = (
        total_affected_field['field_name'])

    # essentially have the same value as NULL_hazard_count
    # but with this, make sure that it exists in layer so it can be used for
    # reporting, and can be referenced to fields.py to take the label.
    field = create_field_from_definition(total_not_affected_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_not_affected_field['key']] = (
        total_not_affected_field['field_name'])

    field = create_field_from_definition(total_not_exposed_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_not_exposed_field['key']] = (
        total_not_exposed_field['field_name'])

    field = create_field_from_definition(total_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_field['key']] = (
        total_field['field_name'])

    summarization_dicts = {}
    if exposure_summary:
        summarization_dicts = summarize_result(exposure_summary)

    sorted_keys = sorted(summarization_dicts.keys())

    for key in sorted_keys:
        summary_field = summary_rules[key]['summary_field']
        field = create_field_from_definition(summary_field)
        tabular.addAttribute(field)
        tabular.keywords['inasafe_fields'][summary_field['key']] = (
            summary_field['field_name'])

    # Only add absolute value if there is no summarization / no exposure
    # classification
    if not summarization_dicts:
        # For each absolute values
        for absolute_field in list(absolute_values.keys()):
            field_definition = definition(absolute_values[absolute_field][1])
            field = create_field_from_definition(field_definition)
            tabular.addAttribute(field)
            key = field_definition['key']
            value = field_definition['field_name']
            tabular.keywords['inasafe_fields'][key] = value

    for exposure_type in unique_exposure:
        feature = QgsFeature()
        attributes = [exposure_type]
        total_affected = 0
        total_not_affected = 0
        total_not_exposed = 0
        total = 0
        for hazard_class in unique_hazard:
            if hazard_class == '' or hazard_class is None:
                hazard_class = 'NULL'
            value = flat_table.get_value(hazard_class=hazard_class,
                                         exposure_class=exposure_type)
            attributes.append(value)

            if hazard_affected[hazard_class] == not_exposed_class['key']:
                total_not_exposed += value
            elif hazard_affected[hazard_class]:
                total_affected += value
            else:
                total_not_affected += value

            total += value

        attributes.append(total_affected)
        attributes.append(total_not_affected)
        attributes.append(total_not_exposed)
        attributes.append(total)

        if summarization_dicts:
            for key in sorted_keys:
                attributes.append(summarization_dicts[key].get(
                    exposure_type, 0))
        else:
            for i, field in enumerate(absolute_values.values()):
                value = field[0].get_value(all='all')
                attributes.append(value)

        feature.setAttributes(attributes)
        tabular.addFeature(feature)

        # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is
        # not enough. ET 13/02/17
        # total_computed = (
        #     total_affected + total_not_affected + total_not_exposed)
        # if not -1 < (total_computed - total) < 1:
        #     raise ComputationError

    tabular.commitChanges()

    tabular.keywords['title'] = layer_purpose_exposure_summary_table['name']
    if qgis_version() >= 21800:
        tabular.setName(tabular.keywords['title'])
    else:
        tabular.setLayerName(tabular.keywords['title'])
    tabular.keywords['layer_purpose'] = layer_purpose_exposure_summary_table[
        'key']

    check_layer(tabular, has_geometry=False)
    return tabular
def 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
Exemplo n.º 57
0
def recompute_counts(layer, callback=None):
    """Recompute counts according to the size field and the new size.

    This function will also take care of updating the size field. The size
    post processor won't run after this function again.

    :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 updated counts.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = recompute_counts_steps['output_layer_name']
    processing_step = recompute_counts_steps['step_name']

    fields = layer.keywords['inasafe_fields']

    if size_field['key'] not in fields:
        # noinspection PyTypeChecker
        msg = '%s not found in %s' % (
            size_field['key'], layer.keywords['title'])
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    indexes = []
    absolute_field_keys = [f['key'] for f in count_fields]
    for field, field_name in fields.iteritems():
        if field in absolute_field_keys and field != size_field['key']:
            indexes.append(layer.fieldNameIndex(field_name))
            LOGGER.info(
                'We detected the count {field_name}, we will recompute the '
                'count according to the new size.'.format(
                    field_name=field_name))

    if not len(indexes):
        msg = 'Absolute field not found in the layer %s' % (
            layer.keywords['title'])
        raise InvalidKeywordsForProcessingAlgorithm(msg)

    size_field_name = fields[size_field['key']]
    size_field_index = layer.fieldNameIndex(size_field_name)

    layer.startEditing()

    exposure_key = layer.keywords['exposure_keywords']['exposure']
    size_calculator = SizeCalculator(
        layer.crs(), layer.geometryType(), exposure_key)

    for feature in layer.getFeatures():
        old_size = feature[size_field_name]
        new_size = size(
            size_calculator=size_calculator, geometry=feature.geometry())

        layer.changeAttributeValue(feature.id(), size_field_index, new_size)

        # Cross multiplication for each field
        for index in indexes:
            old_count = feature[index]
            try:
                new_value = new_size * old_count / old_size
            except TypeError:
                new_value = ''
            layer.changeAttributeValue(feature.id(), index, new_value)

    layer.commitChanges()

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
Exemplo n.º 58
0
def intersection(source, mask, callback=None):
    """Intersect two layers.

    Issue https://github.com/inasafe/inasafe/issues/3186

    Note : This algorithm is copied from :
    https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/
    qgis/Intersection.py

    :param source: The vector layer to clip.
    :type source: QgsVectorLayer

    :param mask: The vector layer to use for clipping.
    :type mask: 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 clip vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = intersection_steps['output_layer_name']
    output_layer_name = output_layer_name % (
        source.keywords['layer_purpose'])
    processing_step = intersection_steps['step_name']

    fields = source.fields()
    fields.extend(mask.fields())

    writer = create_memory_layer(
        output_layer_name,
        source.geometryType(),
        source.crs(),
        fields
    )

    writer.startEditing()

    # Begin copy/paste from Processing plugin.
    # Please follow their code as their code is optimized.
    # The code below is not following our coding standards because we want to
    # be able to track any diffs from QGIS easily.

    out_feature = QgsFeature()
    index = create_spatial_index(mask)

    # Todo callback
    # total = 100.0 / len(selectionA)

    for current, in_feature in enumerate(source.getFeatures()):
        # progress.setPercentage(int(current * total))
        geom = in_feature.geometry()
        attributes = in_feature.attributes()
        intersects = index.intersects(geom.boundingBox())
        for i in intersects:
            request = QgsFeatureRequest().setFilterFid(i)
            feature_mask = next(mask.getFeatures(request))
            tmp_geom = feature_mask.geometry()
            if geom.intersects(tmp_geom):
                mask_attributes = feature_mask.attributes()
                int_geom = QgsGeometry(geom.intersection(tmp_geom))
                if int_geom.wkbType() == QgsWKBTypes.Unknown\
                        or QgsWKBTypes.flatType(
                        int_geom.geometry().wkbType()) ==\
                                QgsWKBTypes.GeometryCollection:
                    int_com = geom.combine(tmp_geom)
                    int_geom = QgsGeometry()
                    if int_com:
                        int_sym = geom.symDifference(tmp_geom)
                        int_geom = QgsGeometry(int_com.difference(int_sym))
                if int_geom.isGeosEmpty() or not int_geom.isGeosValid():
                    # LOGGER.debug(
                    #     tr('GEOS geoprocessing error: One or more input '
                    #        'features have invalid geometry.'))
                    pass
                try:
                    geom_types = wkb_type_groups[
                        wkb_type_groups[int_geom.wkbType()]]
                    if int_geom.wkbType() in geom_types:
                        if int_geom.type() == source.geometryType():
                            # We got some features which have not the same
                            # kind of geometry. We want to skip them.
                            out_feature.setGeometry(int_geom)
                            attrs = []
                            attrs.extend(attributes)
                            attrs.extend(mask_attributes)
                            out_feature.setAttributes(attrs)
                            writer.addFeature(out_feature)
                except:
                    LOGGER.debug(
                        tr('Feature geometry error: One or more output '
                           'features ignored due to invalid geometry.'))
                    continue

    # End copy/paste from Processing plugin.
    writer.commitChanges()

    writer.keywords = dict(source.keywords)
    writer.keywords['title'] = output_layer_name
    writer.keywords['layer_purpose'] = layer_purpose_exposure_summary['key']
    writer.keywords['inasafe_fields'] = dict(source.keywords['inasafe_fields'])
    writer.keywords['inasafe_fields'].update(mask.keywords['inasafe_fields'])
    writer.keywords['hazard_keywords'] = dict(mask.keywords['hazard_keywords'])
    writer.keywords['exposure_keywords'] = dict(source.keywords)
    writer.keywords['aggregation_keywords'] = dict(
        mask.keywords['aggregation_keywords'])

    check_layer(writer)
    return writer
Exemplo n.º 59
0
def update_value_map(layer, exposure_key=None):
    """Assign inasafe values according to definitions for a vector layer.

    :param layer: The vector layer.
    :type layer: QgsVectorLayer

    :param exposure_key: The exposure key.
    :type exposure_key: str

    :return: The classified vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_inasafe_values_steps['output_layer_name']
    output_layer_name = output_layer_name % layer.keywords['layer_purpose']

    keywords = layer.keywords
    inasafe_fields = keywords['inasafe_fields']

    classification = None
    if keywords['layer_purpose'] == layer_purpose_hazard['key']:
        if not inasafe_fields.get(hazard_value_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = hazard_value_field
        new_field = hazard_class_field
        classification = active_classification(layer.keywords, exposure_key)

    elif keywords['layer_purpose'] == layer_purpose_exposure['key']:
        if not inasafe_fields.get(exposure_type_field['key']):
            raise InvalidKeywordsForProcessingAlgorithm
        old_field = exposure_type_field
        new_field = exposure_class_field
    else:
        raise InvalidKeywordsForProcessingAlgorithm

    # It's a hazard layer
    if exposure_key:
        if not active_thresholds_value_maps(keywords, exposure_key):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = active_thresholds_value_maps(keywords, exposure_key)
    # It's exposure layer
    else:
        if not keywords.get('value_map'):
            raise InvalidKeywordsForProcessingAlgorithm
        value_map = keywords.get('value_map')

    unclassified_column = inasafe_fields[old_field['key']]
    unclassified_index = layer.fields().lookupField(unclassified_column)

    reversed_value_map = {}
    for inasafe_class, values in list(value_map.items()):
        for val in values:
            reversed_value_map[val] = inasafe_class

    classified_field = QgsField()
    classified_field.setType(new_field['type'])
    classified_field.setName(new_field['field_name'])
    classified_field.setLength(new_field['length'])
    classified_field.setPrecision(new_field['precision'])

    layer.startEditing()
    layer.addAttribute(classified_field)

    classified_field_index = layer.fields(). \
        lookupField(classified_field.name())

    for feature in layer.getFeatures():
        attributes = feature.attributes()
        source_value = attributes[unclassified_index]
        classified_value = reversed_value_map.get(source_value)

        if not classified_value:
            classified_value = ''

        layer.changeAttributeValue(feature.id(), classified_field_index,
                                   classified_value)

    layer.commitChanges()

    remove_fields(layer, [unclassified_column])

    # We transfer keywords to the output.
    # We add new class field
    inasafe_fields[new_field['key']] = new_field['field_name']

    # and we remove hazard value field
    inasafe_fields.pop(old_field['key'])

    layer.keywords = keywords
    layer.keywords['inasafe_fields'] = inasafe_fields
    if exposure_key:
        value_map_key = 'value_maps'
    else:
        value_map_key = 'value_map'
    if value_map_key in list(layer.keywords.keys()):
        layer.keywords.pop(value_map_key)
    layer.keywords['title'] = output_layer_name
    if classification:
        layer.keywords['classification'] = classification

    check_layer(layer)
    return layer
Exemplo n.º 60
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