Esempio n. 1
0
def create_valid_aggregation(layer):
    """Create a local copy of the aggregation layer and try to make it valid.

    We need to make the layer valid if we can. We got some issues with
    DKI Jakarta dataset : Districts and Subdistricts layers.
    See issue : https://github.com/inasafe/inasafe/issues/3713

    :param layer: The aggregation layer.
    :type layer: QgsVectorLayer

    :return: The new aggregation layer in memory.
    :rtype: QgsVectorLayer
    """
    cleaned = create_memory_layer('aggregation', layer.geometryType(),
                                  layer.crs(), layer.fields())

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

    try:
        use_selected_only = layer.use_selected_features_only
    except AttributeError:
        use_selected_only = False

    cleaned.use_selected_features_only = use_selected_only

    copy_layer(layer, cleaned)
    return cleaned
Esempio n. 2
0
def create_valid_aggregation(layer):
    """Create a local copy of the aggregation layer and try to make it valid.

    We need to make the layer valid if we can. We got some issues with
    DKI Jakarta dataset : Districts and Subdistricts layers.
    See issue : https://github.com/inasafe/inasafe/issues/3713

    :param layer: The aggregation layer.
    :type layer: QgsVectorLayer

    :return: The new aggregation layer in memory.
    :rtype: QgsVectorLayer
    """
    cleaned = create_memory_layer(
        'aggregation', layer.geometryType(), layer.crs(), layer.fields())

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

    try:
        use_selected_only = layer.use_selected_features_only
    except AttributeError:
        use_selected_only = False

    cleaned.use_selected_features_only = use_selected_only

    copy_layer(layer, cleaned)
    return cleaned
Esempio n. 3
0
    def prepare_new_layer(self, input_layer):
        """Prepare new layer for the output layer.

        :param input_layer: Vector layer.
        :type input_layer: QgsVectorLayer

        :return: New memory layer duplicated from input_layer.
        :rtype: QgsVectorLayer
        """
        # create memory layer
        output_layer_name = os.path.splitext(input_layer.name())[0]
        output_layer_name = unique_filename(
            prefix=('%s_minimum_needs_' % output_layer_name),
            dir='minimum_needs_calculator')
        output_layer = create_memory_layer(
            output_layer_name,
            input_layer.geometryType(),
            input_layer.crs(),
            input_layer.fields())

        # monkey patching input layer to make it work with
        # prepare vector layer function
        temp_layer = input_layer
        temp_layer.keywords = {
            'layer_purpose': layer_purpose_aggregation['key']}

        # copy features to output layer
        copy_layer(temp_layer, output_layer)

        # Monkey patching output layer to make it work with
        # minimum needs calculator
        output_layer.keywords['layer_purpose'] = (
            layer_purpose_aggregation['key'])
        output_layer.keywords['inasafe_fields'] = {
            displaced_field['key']: self.displaced.currentField()
        }
        if self.aggregation_name.currentField():
            output_layer.keywords['inasafe_fields'][
                aggregation_name_field['key']] = (
                    self.aggregation_name.currentField())

        # remove unnecessary fields & rename inasafe fields
        clean_inasafe_fields(output_layer)

        return output_layer
Esempio n. 4
0
    def prepare_new_layer(self, input_layer):
        """Prepare new layer for the output layer.

        :param input_layer: Vector layer.
        :type input_layer: QgsVectorLayer

        :return: New memory layer duplicated from input_layer.
        :rtype: QgsVectorLayer
        """
        # create memory layer
        output_layer_name = os.path.splitext(input_layer.name())[0]
        output_layer_name = unique_filename(
            prefix=('%s_minimum_needs_' % output_layer_name),
            dir='minimum_needs_calculator')
        output_layer = create_memory_layer(
            output_layer_name,
            input_layer.geometryType(),
            input_layer.crs(),
            input_layer.fields())

        # monkey patching input layer to make it work with
        # prepare vector layer function
        temp_layer = input_layer
        temp_layer.keywords = {
            'layer_purpose': layer_purpose_aggregation['key']}

        # copy features to output layer
        copy_layer(temp_layer, output_layer)

        # Monkey patching output layer to make it work with
        # minimum needs calculator
        output_layer.keywords['layer_purpose'] = (
            layer_purpose_aggregation['key'])
        output_layer.keywords['inasafe_fields'] = {
            displaced_field['key']: self.displaced.currentField()
        }
        if self.aggregation_name.currentField():
            output_layer.keywords['inasafe_fields'][
                aggregation_name_field['key']] = (
                    self.aggregation_name.currentField())

        # remove unnecessary fields & rename inasafe fields
        clean_inasafe_fields(output_layer)

        return output_layer
Esempio n. 5
0
def load_path_vector_layer(path, **kwargs):
    """Return the test vector layer.

    :param path: Path to the vector layer.
    :type path: str

    :param kwargs: It can be :
        clone=True if you want to copy the layer first to a temporary file.

        clone_to_memory=True if you want to create a memory layer.

        with_keywords=False if you do not want keywords. "clone_to_memory" is
            required.

    :type kwargs: dict

    :return: The vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    if not exists(path):
        raise Exception('%s do not exist.' % path)

    path = os.path.normcase(os.path.abspath(path))
    name = splitext(basename(path))[0]
    extension = splitext(path)[1]

    extensions = [
        '.shp', '.shx', '.dbf', '.prj', '.gpkg', '.geojson', '.xml', '.qml'
    ]

    if kwargs.get('with_keywords'):
        if not kwargs.get('clone_to_memory'):
            raise Exception('with_keywords needs a clone_to_memory')

    if kwargs.get('clone', False):
        target_directory = mkdtemp()
        current_path = splitext(path)[0]
        path = join(target_directory, name + extension)

        for ext in extensions:
            src_path = current_path + ext
            if exists(src_path):
                target_path = join(target_directory, name + ext)
                shutil.copy2(src_path, target_path)

    if path.endswith('.csv'):
        # Explicitly use URI with delimiter or tests fail in Windows. TS.
        uri = 'file:///%s?delimiter=%s' % (path, ',')
        layer = QgsVectorLayer(uri, name, 'delimitedtext')
    else:
        layer = QgsVectorLayer(path, name, 'ogr')

    if not layer.isValid():
        raise Exception('%s is not a valid layer.' % name)

    monkey_patch_keywords(layer)

    if kwargs.get('clone_to_memory', False):
        keywords = layer.keywords.copy()
        memory_layer = create_memory_layer(name, layer.geometryType(),
                                           layer.crs(), layer.fields())
        copy_layer(layer, memory_layer)
        if kwargs.get('with_keywords', True):
            memory_layer.keywords = keywords
        return memory_layer
    else:
        return layer
Esempio n. 6
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 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
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)

    input_band = layer.keywords.get('active_band', 1)
    analysis = QgsZonalStatistics(
        layer,
        raster.source(),
        'exposure_',
        input_band,
        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
Esempio n. 9
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
Esempio n. 10
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
Esempio n. 11
0
def load_path_vector_layer(path, **kwargs):
    """Return the test vector layer.

    :param path: Path to the vector layer.
    :type path: str

    :param kwargs: It can be :
        clone=True if you want to copy the layer first to a temporary file.

        clone_to_memory=True if you want to create a memory layer.

        with_keywords=False if you do not want keywords. "clone_to_memory" is
            required.

    :type kwargs: dict

    :return: The vector layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    if not exists(path):
        raise Exception('%s do not exist.' % path)

    path = os.path.normcase(os.path.abspath(path))
    name = splitext(basename(path))[0]
    extension = splitext(path)[1]

    extensions = [
        '.shp', '.shx', '.dbf', '.prj', '.gpkg', '.geojson', '.xml', '.qml']

    if kwargs.get('with_keywords'):
        if not kwargs.get('clone_to_memory'):
            raise Exception('with_keywords needs a clone_to_memory')

    if kwargs.get('clone', False):
        target_directory = mkdtemp()
        current_path = splitext(path)[0]
        path = join(target_directory, name + extension)

        for ext in extensions:
            src_path = current_path + ext
            if exists(src_path):
                target_path = join(target_directory, name + ext)
                shutil.copy2(src_path, target_path)

    if path.endswith('.csv'):
        # Explicitly use URI with delimiter or tests fail in Windows. TS.
        uri = 'file:///%s?delimiter=%s' % (path, ',')
        layer = QgsVectorLayer(uri, name, 'delimitedtext')
    else:
        layer = QgsVectorLayer(path, name, 'ogr')

    if not layer.isValid():
        raise Exception('%s is not a valid layer.' % name)

    monkey_patch_keywords(layer)

    if kwargs.get('clone_to_memory', False):
        keywords = layer.keywords.copy()
        memory_layer = create_memory_layer(
            name, layer.geometryType(), layer.crs(), layer.fields())
        copy_layer(layer, memory_layer)
        if kwargs.get('with_keywords', True):
            memory_layer.keywords = keywords
        return memory_layer
    else:
        return layer
Esempio n. 12
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