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 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
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
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 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
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
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