Ejemplo n.º 1
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
Ejemplo n.º 2
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
Ejemplo n.º 3
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
Ejemplo n.º 4
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
Ejemplo n.º 5
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
Ejemplo n.º 6
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
Ejemplo n.º 7
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