def test_copy_vector_layer(self): """Test we can copy a vector layer.""" layer = load_test_vector_layer('exposure', 'buildings.shp') new_layer = create_memory_layer( 'New layer', layer.geometryType(), layer.crs(), layer.fields()) new_layer.keywords = layer.keywords copy_layer(layer, new_layer) self.assertEqual(layer.featureCount(), new_layer.featureCount()) self.assertEqual( len(layer.fields().toList()), len(new_layer.fields().toList())) expected = len(new_layer.fields().toList()) + 1 new_fields = {'STRUCTURE': 'my_new_field'} copy_fields(new_layer, new_fields) self.assertEqual( len(new_layer.fields().toList()), expected) self.assertGreater(new_layer.fieldNameIndex('my_new_field'), -1) remove_fields(new_layer, ['STRUCTURE', 'OSM_TYPE']) self.assertEqual( len(new_layer.fields().toList()), expected - 2) self.assertEqual(new_layer.fieldNameIndex('STRUCTURE'), -1) self.assertEqual(new_layer.fieldNameIndex('OSM_TYPE'), -1) _add_id_column(new_layer) field_name = exposure_id_field['field_name'] self.assertGreater(new_layer.fieldNameIndex(field_name), -1)
def test_empty_layer(self): """Test if we import an empty layer.""" layer = create_memory_layer( 'test', QgsWkbTypes.PolygonGeometry, QgsCoordinateReferenceSystem(3857), [ QgsField('my_field_1', QVariant.Int), QgsField('my_field_2', QVariant.Double), QgsField('my_field_3', QVariant.String) ]) self.assertTrue(layer.isValid()) self.assertEqual(len(layer.fields()), 3) # These following tests doesn't work if we add 'geosjon' in the formats # list. https://issues.qgis.org/issues/18370 formats = ['shp'] for extension in formats: path = QDir(mkdtemp()) data_store = Folder(path) data_store.default_vector_format = extension name = 'test' result, message = data_store.add_layer(layer, name) self.assertTrue(result, message) # Fetch the layer imported_layer = data_store.layer(name) self.assertTrue(imported_layer.isValid()) self.assertListEqual([f.name() for f in imported_layer.fields()], ['my_field_1', 'my_field_2', 'my_field_3'])
def test_copy_vector_layer(self): """Test we can copy a vector layer.""" layer = load_test_vector_layer('exposure', 'buildings.shp') new_layer = create_memory_layer('New layer', layer.geometryType(), layer.crs(), layer.fields()) new_layer.keywords = layer.keywords copy_layer(layer, new_layer) self.assertEqual(layer.featureCount(), new_layer.featureCount()) self.assertEqual(len(layer.fields().toList()), len(new_layer.fields().toList())) expected = len(new_layer.fields().toList()) + 1 new_fields = {'STRUCTURE': 'my_new_field'} copy_fields(new_layer, new_fields) self.assertEqual(len(new_layer.fields().toList()), expected) self.assertGreater(new_layer.fieldNameIndex('my_new_field'), -1) remove_fields(new_layer, ['STRUCTURE', 'OSM_TYPE']) self.assertEqual(len(new_layer.fields().toList()), expected - 2) self.assertEqual(new_layer.fieldNameIndex('STRUCTURE'), -1) self.assertEqual(new_layer.fieldNameIndex('OSM_TYPE'), -1) _add_id_column(new_layer) field_name = exposure_id_field['field_name'] self.assertGreater(new_layer.fieldNameIndex(field_name), -1)
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
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
def smart_clip(layer_to_clip, mask_layer, callback=None): """Smart clip a vector layer with another. Issue https://github.com/inasafe/inasafe/issues/3186 :param layer_to_clip: The vector layer to clip. :type layer_to_clip: QgsVectorLayer :param mask_layer: The vector layer to use for clipping. :type mask_layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The clip vector layer. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = smart_clip_steps['output_layer_name'] processing_step = smart_clip_steps['step_name'] writer = create_memory_layer( output_layer_name, layer_to_clip.geometryType(), layer_to_clip.crs(), layer_to_clip.fields() ) writer.startEditing() # first build up a list of clip geometries request = QgsFeatureRequest().setSubsetOfAttributes([]) iterator = mask_layer.getFeatures(request) feature = next(iterator) geometries = QgsGeometry(feature.geometry()) # use prepared geometries for faster intersection tests # noinspection PyArgumentList engine = QgsGeometry.createGeometryEngine(geometries.geometry()) engine.prepareGeometry() extent = mask_layer.extent() for feature in layer_to_clip.getFeatures(QgsFeatureRequest(extent)): if engine.intersects(feature.geometry().geometry()): out_feat = QgsFeature() out_feat.setGeometry(feature.geometry()) out_feat.setAttributes(feature.attributes()) writer.addFeature(out_feat) writer.commitChanges() writer.keywords = layer_to_clip.keywords.copy() writer.keywords['title'] = output_layer_name check_layer(writer) return writer
def smart_clip(layer_to_clip, mask_layer, callback=None): """Smart clip a vector layer with another. Issue https://github.com/inasafe/inasafe/issues/3186 :param layer_to_clip: The vector layer to clip. :type layer_to_clip: QgsVectorLayer :param mask_layer: The vector layer to use for clipping. :type mask_layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The clip vector layer. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = smart_clip_steps['output_layer_name'] processing_step = smart_clip_steps['step_name'] writer = create_memory_layer( output_layer_name, layer_to_clip.geometryType(), layer_to_clip.crs(), layer_to_clip.fields() ) writer.startEditing() # first build up a list of clip geometries request = QgsFeatureRequest().setSubsetOfAttributes([]) iterator = mask_layer.getFeatures(request) feature = next(iterator) geometries = QgsGeometry(feature.geometry()) # use prepared geometries for faster intersection tests # noinspection PyArgumentList engine = QgsGeometry.createGeometryEngine(geometries.geometry()) engine.prepareGeometry() extent = mask_layer.extent() for feature in layer_to_clip.getFeatures(QgsFeatureRequest(extent)): if engine.intersects(feature.geometry().geometry()): out_feat = QgsFeature() out_feat.setGeometry(feature.geometry()) out_feat.setAttributes(feature.attributes()) writer.addFeature(out_feat) writer.commitChanges() writer.keywords = layer_to_clip.keywords.copy() writer.keywords['title'] = output_layer_name check_layer(writer) return writer
def reproject(layer, output_crs, callback=None): """Reproject a vector layer to a specific CRS. Issue https://github.com/inasafe/inasafe/issues/3183 :param layer: The layer to reproject. :type layer: QgsVectorLayer :param output_crs: The destination CRS. :type output_crs: QgsCoordinateReferenceSystem :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: Reprojected memory layer. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = reproject_steps['output_layer_name'] output_layer_name = output_layer_name % layer.keywords['layer_purpose'] processing_step = reproject_steps['step_name'] input_crs = layer.crs() input_fields = layer.fields() feature_count = layer.featureCount() reprojected = create_memory_layer(output_layer_name, layer.geometryType(), output_crs, input_fields) reprojected.startEditing() crs_transform = QgsCoordinateTransform(input_crs, output_crs, QgsProject.instance()) out_feature = QgsFeature() for i, feature in enumerate(layer.getFeatures()): geom = feature.geometry() geom.transform(crs_transform) out_feature.setGeometry(geom) out_feature.setAttributes(feature.attributes()) reprojected.addFeature(out_feature) if callback: callback(current=i, maximum=feature_count, step=processing_step) reprojected.commitChanges() # We transfer keywords to the output. # We don't need to update keywords as the CRS is dynamic. reprojected.keywords = layer.keywords reprojected.keywords['title'] = output_layer_name check_layer(reprojected) return reprojected
def reproject(layer, output_crs, callback=None): """Reproject a vector layer to a specific CRS. Issue https://github.com/inasafe/inasafe/issues/3183 :param layer: The layer to reproject. :type layer: QgsVectorLayer :param output_crs: The destination CRS. :type output_crs: QgsCoordinateReferenceSystem :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: Reprojected memory layer. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = reproject_steps['output_layer_name'] output_layer_name = output_layer_name % layer.keywords['layer_purpose'] processing_step = reproject_steps['step_name'] input_crs = layer.crs() input_fields = layer.fields() feature_count = layer.featureCount() reprojected = create_memory_layer( output_layer_name, layer.geometryType(), output_crs, input_fields) reprojected.startEditing() crs_transform = QgsCoordinateTransform(input_crs, output_crs) out_feature = QgsFeature() for i, feature in enumerate(layer.getFeatures()): geom = feature.geometry() geom.transform(crs_transform) out_feature.setGeometry(geom) out_feature.setAttributes(feature.attributes()) reprojected.addFeature(out_feature) if callback: callback(current=i, maximum=feature_count, step=processing_step) reprojected.commitChanges() # We transfer keywords to the output. # We don't need to update keywords as the CRS is dynamic. reprojected.keywords = layer.keywords reprojected.keywords['title'] = output_layer_name check_layer(reprojected) return reprojected
def create_profile_layer(profiling): """Create a tabular layer with the profiling. :param profiling: A dict containing benchmarking data. :type profiling: safe.messaging.message.Message :return: A tabular layer. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(profiling_function_field), create_field_from_definition(profiling_time_field) ] if setting(key='memory_profile', expected_type=bool): fields.append(create_field_from_definition(profiling_memory_field)) tabular = create_memory_layer( 'profiling', QgsWkbTypes.NullGeometry, fields=fields) # Generate profiling keywords tabular.keywords['layer_purpose'] = layer_purpose_profiling['key'] tabular.keywords['title'] = layer_purpose_profiling['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['inasafe_fields'] = { profiling_function_field['key']: profiling_function_field['field_name'], profiling_time_field['key']: profiling_time_field['field_name'], } if setting(key='memory_profile', expected_type=bool): tabular.keywords['inasafe_fields'][ profiling_memory_field['key']] = profiling_memory_field[ 'field_name'] tabular.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) table = profiling.to_text().splitlines()[3:] tabular.startEditing() for line in table: feature = QgsFeature() items = line.split(', ') time = items[1].replace('-', '') if setting(key='memory_profile', expected_type=bool): memory = items[2].replace('-', '') feature.setAttributes([items[0], time, memory]) else: feature.setAttributes([items[0], time]) tabular.addFeature(feature) tabular.commitChanges() return tabular
def create_profile_layer(profiling): """Create a tabular layer with the profiling. :param profiling: A dict containing benchmarking data. :type profiling: safe.messaging.message.Message :return: A tabular layer. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(profiling_function_field), create_field_from_definition(profiling_time_field) ] if setting(key='memory_profile', expected_type=bool): fields.append(create_field_from_definition(profiling_memory_field)) tabular = create_memory_layer( 'profiling', QgsWkbTypes.NullGeometry, fields=fields) # Generate profiling keywords tabular.keywords['layer_purpose'] = layer_purpose_profiling['key'] tabular.keywords['title'] = layer_purpose_profiling['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['inasafe_fields'] = { profiling_function_field['key']: profiling_function_field['field_name'], profiling_time_field['key']: profiling_time_field['field_name'], } if setting(key='memory_profile', expected_type=bool): tabular.keywords['inasafe_fields'][ profiling_memory_field['key']] = profiling_memory_field[ 'field_name'] tabular.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) table = profiling.to_text().splitlines()[3:] tabular.startEditing() for line in table: feature = QgsFeature() items = line.split(', ') time = items[1].replace('-', '') if setting(key='memory_profile', expected_type=bool): memory = items[2].replace('-', '') feature.setAttributes([items[0], time, memory]) else: feature.setAttributes([items[0], time]) tabular.addFeature(feature) tabular.commitChanges() return tabular
def test_copy_vector_layer(self): """Test we can create a memory layer.""" layer = load_test_vector_layer('exposure', 'buildings.shp') new_layer = create_memory_layer( 'New layer', layer.geometryType(), layer.crs(), layer.fields()) self.assertTrue(new_layer.isValid()) self.assertEqual(new_layer.name(), 'New layer') self.assertEqual(new_layer.fields(), layer.fields()) self.assertEqual(new_layer.crs(), layer.crs()) self.assertEqual(new_layer.wkbType(), QgsWkbTypes.MultiPolygon) # create_memory_layer should also accept a list of fields, not a # QgsFields object field_list = [f for f in layer.fields()] new_layer = create_memory_layer( 'New layer', layer.geometryType(), layer.crs(), field_list) self.assertTrue(new_layer.isValid()) self.assertEqual(new_layer.name(), 'New layer') self.assertEqual(new_layer.fields(), layer.fields()) self.assertEqual(new_layer.crs(), layer.crs()) self.assertEqual(new_layer.wkbType(), QgsWkbTypes.MultiPolygon)
def test_copy_vector_layer(self): """Test we can create a memory layer.""" layer = load_test_vector_layer('exposure', 'buildings.shp') new_layer = create_memory_layer('New layer', layer.geometryType(), layer.crs(), layer.fields()) self.assertTrue(new_layer.isValid()) self.assertEqual(new_layer.name(), 'New layer') self.assertEqual(new_layer.fields(), layer.fields()) self.assertEqual(new_layer.crs(), layer.crs()) self.assertEqual(new_layer.wkbType(), QgsWkbTypes.MultiPolygon) # create_memory_layer should also accept a list of fields, not a # QgsFields object field_list = [f for f in layer.fields()] new_layer = create_memory_layer('New layer', layer.geometryType(), layer.crs(), field_list) self.assertTrue(new_layer.isValid()) self.assertEqual(new_layer.name(), 'New layer') self.assertEqual(new_layer.fields(), layer.fields()) self.assertEqual(new_layer.crs(), layer.crs()) self.assertEqual(new_layer.wkbType(), QgsWkbTypes.MultiPolygon)
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
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
def create_analysis_layer(analysis_extent, crs, name): """Create the analysis layer. :param analysis_extent: The analysis extent. :type analysis_extent: QgsGeometry :param crs: The CRS to use. :type crs: QgsCoordinateReferenceSystem :param name: The name of the analysis. :type name: basestring :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(analysis_id_field), create_field_from_definition(analysis_name_field) ] analysis_layer = create_memory_layer( 'analysis', QGis.Polygon, crs, fields) analysis_layer.startEditing() feature = QgsFeature() # noinspection PyCallByClass,PyArgumentList,PyTypeChecker feature.setGeometry(analysis_extent) feature.setAttributes([1, name]) analysis_layer.addFeature(feature) analysis_layer.commitChanges() # Generate analysis keywords analysis_layer.keywords['layer_purpose'] = ( layer_purpose_analysis_impacted['key']) analysis_layer.keywords['title'] = 'analysis' analysis_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) analysis_layer.keywords['inasafe_fields'] = { analysis_id_field['key']: analysis_id_field['field_name'], analysis_name_field['key']: analysis_name_field['field_name'] } return analysis_layer
def create_analysis_layer(analysis_extent, crs, name): """Create the analysis layer. :param analysis_extent: The analysis extent. :type analysis_extent: QgsGeometry :param crs: The CRS to use. :type crs: QgsCoordinateReferenceSystem :param name: The name of the analysis. :type name: basestring :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(analysis_id_field), create_field_from_definition(analysis_name_field) ] analysis_layer = create_memory_layer( 'analysis', QGis.Polygon, crs, fields) analysis_layer.startEditing() feature = QgsFeature() # noinspection PyCallByClass,PyArgumentList,PyTypeChecker feature.setGeometry(analysis_extent) feature.setAttributes([1, name]) analysis_layer.addFeature(feature) analysis_layer.commitChanges() # Generate analysis keywords analysis_layer.keywords['layer_purpose'] = ( layer_purpose_analysis_impacted['key']) analysis_layer.keywords['title'] = 'analysis' analysis_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) analysis_layer.keywords['inasafe_fields'] = { analysis_id_field['key']: analysis_id_field['field_name'], analysis_name_field['key']: analysis_name_field['field_name'] } return analysis_layer
def create_virtual_aggregation(geometry, crs): """Function to create aggregation layer based on extent. :param geometry: The geometry to use as an extent. :type geometry: QgsGeometry :param crs: The Coordinate Reference System to use for the layer. :type crs: QgsCoordinateReferenceSystem :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(aggregation_id_field), create_field_from_definition(aggregation_name_field) ] aggregation_layer = create_memory_layer('aggregation', QgsWkbTypes.PolygonGeometry, crs, fields) aggregation_layer.startEditing() feature = QgsFeature() feature.setGeometry(geometry) feature.setAttributes([1, tr('Entire Area')]) aggregation_layer.addFeature(feature) aggregation_layer.commitChanges() # Generate aggregation keywords aggregation_layer.keywords['layer_purpose'] = ( layer_purpose_aggregation['key']) aggregation_layer.keywords['title'] = 'aggr_from_bbox' aggregation_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) aggregation_layer.keywords['inasafe_fields'] = { aggregation_id_field['key']: aggregation_id_field['field_name'], aggregation_name_field['key']: aggregation_name_field['field_name'] } # We will fill default values later, according to the exposure. aggregation_layer.keywords['inasafe_default_values'] = {} return aggregation_layer
def create_virtual_aggregation(geometry, crs): """Function to create aggregation layer based on extent. :param geometry: The geometry to use as an extent. :type geometry: QgsGeometry :param crs: The Coordinate Reference System to use for the layer. :type crs: QgsCoordinateReferenceSystem :returns: A polygon layer with exposure's crs. :rtype: QgsVectorLayer """ fields = [ create_field_from_definition(aggregation_id_field), create_field_from_definition(aggregation_name_field) ] aggregation_layer = create_memory_layer( 'aggregation', QgsWkbTypes.PolygonGeometry, crs, fields) aggregation_layer.startEditing() feature = QgsFeature() feature.setGeometry(geometry) feature.setAttributes([1, tr('Entire Area')]) aggregation_layer.addFeature(feature) aggregation_layer.commitChanges() # Generate aggregation keywords aggregation_layer.keywords['layer_purpose'] = ( layer_purpose_aggregation['key']) aggregation_layer.keywords['title'] = 'aggr_from_bbox' aggregation_layer.keywords[inasafe_keyword_version_key] = ( inasafe_keyword_version) aggregation_layer.keywords['inasafe_fields'] = { aggregation_id_field['key']: aggregation_id_field['field_name'], aggregation_name_field['key']: aggregation_name_field['field_name'] } # We will fill default values later, according to the exposure. aggregation_layer.keywords['inasafe_default_values'] = {} return aggregation_layer
def show_qgis_feature(feature, crs=None): """Show a QGIS feature. :param feature: The feature to display. :type feature: QgsFeature :param crs: The CRS of the geometry. 4326 by default. :type crs: QgsCoordinateReferenceSystem """ if crs is None: crs = QgsCoordinateReferenceSystem(4326) layer = create_memory_layer('Debug', feature.geometry().type(), crs) data_provider = layer.dataProvider() for i, attr in enumerate(feature.attributes()): data_provider.addAttributes( [QgsField('attribute %s' % i, QVariant.String)]) layer.updateFields() data_provider.addFeatures([feature]) layer.updateExtents() show_qgis_layer(layer)
def show_qgis_feature(feature, crs=None): """Show a QGIS feature. :param feature: The feature to display. :type feature: QgsFeature :param crs: The CRS of the geometry. 4326 by default. :type crs: QgsCoordinateReferenceSystem """ if crs is None: crs = QgsCoordinateReferenceSystem(4326) layer = create_memory_layer('Debug', feature.geometry().type(), crs) data_provider = layer.dataProvider() for i, attr in enumerate(feature.attributes()): data_provider.addAttributes( [QgsField('attribute %s' % i, QVariant.String)]) layer.updateFields() data_provider.addFeatures([feature]) layer.updateExtents() show_qgis_layer(layer)
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
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 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 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
def multi_buffering(layer, radii, callback=None): """Buffer a vector layer using many buffers (for volcanoes or rivers). This processing algorithm will keep the original attribute table and will add a new one for the hazard class name according to safe.definitions.fields.hazard_value_field. radii = OrderedDict() radii[500] = 'high' radii[1000] = 'medium' radii[2000] = 'low' Issue https://github.com/inasafe/inasafe/issues/3185 :param layer: The layer to polygonize. :type layer: QgsVectorLayer :param radii: A dictionary of radius. :type radii: OrderedDict :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The buffered vector layer. :rtype: QgsVectorLayer """ # Layer output output_layer_name = buffer_steps['output_layer_name'] processing_step = buffer_steps['step_name'] input_crs = layer.crs() feature_count = layer.featureCount() fields = layer.fields() # Set the new hazard class field. new_field = create_field_from_definition(hazard_class_field) fields.append(new_field) # Set the new buffer distances field. new_field = create_field_from_definition(buffer_distance_field) fields.append(new_field) buffered = create_memory_layer( output_layer_name, QGis.Polygon, input_crs, fields) data_provider = buffered.dataProvider() # Reproject features if needed into UTM if the layer is in 4326. if layer.crs().authid() == 'EPSG:4326': center = layer.extent().center() utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(layer.crs(), utm) reverse_transform = QgsCoordinateTransform(utm, layer.crs()) else: transform = None reverse_transform = None for i, feature in enumerate(layer.getFeatures()): geom = QgsGeometry(feature.geometry()) if transform: geom.transform(transform) inner_ring = None for radius in radii: attributes = feature.attributes() # We add the hazard value name to the attribute table. attributes.append(radii[radius]) # We add the value of buffer distance to the attribute table. attributes.append(radius) circle = geom.buffer(radius, 30) if inner_ring: circle.addRing(inner_ring) inner_ring = circle.asPolygon()[0] new_feature = QgsFeature() if reverse_transform: circle.transform(reverse_transform) new_feature.setGeometry(circle) new_feature.setAttributes(attributes) data_provider.addFeatures([new_feature]) if callback: callback(current=i, maximum=feature_count, step=processing_step) # We transfer keywords to the output. buffered.keywords = layer.keywords buffered.keywords['layer_geometry'] = 'polygon' buffered.keywords['layer_purpose'] = layer_purpose_hazard['key'] buffered.keywords['inasafe_fields'][hazard_class_field['key']] = ( hazard_class_field['field_name']) check_layer(buffered) return buffered
def clip(layer_to_clip, mask_layer, callback=None): """Clip a vector layer with another. Issue https://github.com/inasafe/inasafe/issues/3186 Note : This algorithm is copied from : https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/ qgis/Clip.py :param layer_to_clip: The vector layer to clip. :type layer_to_clip: QgsVectorLayer :param mask_layer: The vector layer to use for clipping. :type mask_layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The clip vector layer. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = clip_steps['output_layer_name'] output_layer_name = output_layer_name % ( layer_to_clip.keywords['layer_purpose']) processing_step = clip_steps['step_name'] writer = create_memory_layer(output_layer_name, layer_to_clip.geometryType(), layer_to_clip.crs(), layer_to_clip.fields()) writer.startEditing() # Begin copy/paste from Processing plugin. # Please follow their code as their code is optimized. # The code below is not following our coding standards because we want to # be able to track any diffs from QGIS easily. # first build up a list of clip geometries clip_geometries = [] request = QgsFeatureRequest().setSubsetOfAttributes([]) for mask_feature in mask_layer.getFeatures(request): clip_geometries.append(QgsGeometry(mask_feature.geometry())) # are we clipping against a single feature? if so, # we can show finer progress reports if len(clip_geometries) > 1: # noinspection PyTypeChecker,PyCallByClass,PyArgumentList combined_clip_geom = QgsGeometry.unaryUnion(clip_geometries) single_clip_feature = False else: combined_clip_geom = clip_geometries[0] single_clip_feature = True # use prepared geometries for faster intersection tests # noinspection PyArgumentList engine = QgsGeometry.createGeometryEngine(combined_clip_geom.geometry()) engine.prepareGeometry() tested_feature_ids = set() for i, clip_geom in enumerate(clip_geometries): request = QgsFeatureRequest().setFilterRect(clip_geom.boundingBox()) input_features = [f for f in layer_to_clip.getFeatures(request)] if not input_features: continue if single_clip_feature: total = 100.0 / len(input_features) else: total = 0 for current, in_feat in enumerate(input_features): if not in_feat.geometry(): continue if in_feat.id() in tested_feature_ids: # don't retest a feature we have already checked continue tested_feature_ids.add(in_feat.id()) if not engine.intersects(in_feat.geometry().geometry()): continue if not engine.contains(in_feat.geometry().geometry()): cur_geom = in_feat.geometry() new_geom = combined_clip_geom.intersection(cur_geom) if new_geom.wkbType() == QgsWKBTypes.Unknown \ or QgsWKBTypes.flatType( new_geom.geometry().wkbType()) == \ QgsWKBTypes.GeometryCollection: int_com = in_feat.geometry().combine(new_geom) int_sym = in_feat.geometry().symDifference(new_geom) if not int_com or not int_sym: # LOGGER.debug( # tr('GEOS geoprocessing error: One or more input ' # 'features have invalid geometry.')) pass else: new_geom = int_com.difference(int_sym) if new_geom.isGeosEmpty()\ or not new_geom.isGeosValid(): # LOGGER.debug( # tr('GEOS geoprocessing error: One or more ' # 'input features have invalid geometry.')) pass else: # clip geometry totally contains feature geometry, # so no need to perform intersection new_geom = in_feat.geometry() try: out_feat = QgsFeature() out_feat.setGeometry(new_geom) out_feat.setAttributes(in_feat.attributes()) writer.addFeature(out_feat) except: LOGGER.debug( tr('Feature geometry error: One or more output features ' 'ignored due to invalid geometry.')) continue # TODO implement callback if single_clip_feature: # progress.setPercentage(int(current * total)) pass if not single_clip_feature: # coarse progress report for multiple clip geometries # progress.setPercentage(100.0 * i / len(clip_geoms)) pass # End copy/paste from Processing plugin. writer.commitChanges() writer.keywords = layer_to_clip.keywords.copy() writer.keywords['title'] = output_layer_name check_layer(writer) return writer
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
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 exposure_summary_table(aggregate_hazard, exposure_summary=None, callback=None): """Compute the summary from the aggregate hazard to analysis. Source layer : | haz_id | haz_class | aggr_id | aggr_name | exposure_count | Output layer : | exp_type | count_hazard_class | total | :param aggregate_hazard: The layer to aggregate vector layer. :type aggregate_hazard: QgsVectorLayer :param exposure_summary: The layer impact layer. :type exposure_summary: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The new tabular table, without geometry. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = summary_4_exposure_summary_table_steps[ 'output_layer_name'] source_fields = aggregate_hazard.keywords['inasafe_fields'] source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, hazard_id_field, hazard_class_field, affected_field, total_field ] check_inputs(source_compulsory_fields, source_fields) absolute_values = create_absolute_values_structure(aggregate_hazard, ['all']) hazard_class = source_fields[hazard_class_field['key']] hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class) unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index) unique_exposure = read_dynamic_inasafe_field(source_fields, exposure_count_field) flat_table = FlatTable('hazard_class', 'exposure_class') request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregate_hazard.getFeatures(): hazard_value = area[hazard_class_index] for exposure in unique_exposure: key_name = exposure_count_field['key'] % exposure field_name = source_fields[key_name] exposure_count = area[field_name] if not exposure_count: exposure_count = 0 flat_table.add_value(exposure_count, hazard_class=hazard_value, exposure_class=exposure) # We summarize every absolute values. for field, field_definition in list(absolute_values.items()): value = area[field] if not value: value = 0 field_definition[0].add_value(value, all='all') tabular = create_memory_layer(output_layer_name, QgsWkbTypes.NullGeometry) tabular.startEditing() field = create_field_from_definition(exposure_type_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][exposure_type_field['key']] = ( exposure_type_field['field_name']) hazard_keywords = aggregate_hazard.keywords['hazard_keywords'] hazard = hazard_keywords['hazard'] classification = hazard_keywords['classification'] exposure_keywords = aggregate_hazard.keywords['exposure_keywords'] exposure = exposure_keywords['exposure'] hazard_affected = {} for hazard_class in unique_hazard: if (hazard_class == '' or hazard_class is None or (hasattr(hazard_class, 'isNull') and hazard_class.isNull())): hazard_class = 'NULL' field = create_field_from_definition(hazard_count_field, hazard_class) tabular.addAttribute(field) key = hazard_count_field['key'] % hazard_class value = hazard_count_field['field_name'] % hazard_class tabular.keywords['inasafe_fields'][key] = value hazard_affected[hazard_class] = post_processor_affected_function( exposure=exposure, hazard=hazard, classification=classification, hazard_class=hazard_class) field = create_field_from_definition(total_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_affected_field['key']] = ( total_affected_field['field_name']) # essentially have the same value as NULL_hazard_count # but with this, make sure that it exists in layer so it can be used for # reporting, and can be referenced to fields.py to take the label. field = create_field_from_definition(total_not_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_affected_field['key']] = ( total_not_affected_field['field_name']) field = create_field_from_definition(total_not_exposed_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_exposed_field['key']] = ( total_not_exposed_field['field_name']) field = create_field_from_definition(total_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_field['key']] = ( total_field['field_name']) summarization_dicts = {} if exposure_summary: summarization_dicts = summarize_result(exposure_summary) sorted_keys = sorted(summarization_dicts.keys()) for key in sorted_keys: summary_field = summary_rules[key]['summary_field'] field = create_field_from_definition(summary_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][summary_field['key']] = ( summary_field['field_name']) # Only add absolute value if there is no summarization / no exposure # classification if not summarization_dicts: # For each absolute values for absolute_field in list(absolute_values.keys()): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) tabular.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] tabular.keywords['inasafe_fields'][key] = value for exposure_type in unique_exposure: feature = QgsFeature() attributes = [exposure_type] total_affected = 0 total_not_affected = 0 total_not_exposed = 0 total = 0 for hazard_class in unique_hazard: if hazard_class == '' or hazard_class is None: hazard_class = 'NULL' value = flat_table.get_value(hazard_class=hazard_class, exposure_class=exposure_type) attributes.append(value) if hazard_affected[hazard_class] == not_exposed_class['key']: total_not_exposed += value elif hazard_affected[hazard_class]: total_affected += value else: total_not_affected += value total += value attributes.append(total_affected) attributes.append(total_not_affected) attributes.append(total_not_exposed) attributes.append(total) if summarization_dicts: for key in sorted_keys: attributes.append(summarization_dicts[key].get( exposure_type, 0)) else: for i, field in enumerate(absolute_values.values()): value = field[0].get_value(all='all') attributes.append(value) feature.setAttributes(attributes) tabular.addFeature(feature) # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is # not enough. ET 13/02/17 # total_computed = ( # total_affected + total_not_affected + total_not_exposed) # if not -1 < (total_computed - total) < 1: # raise ComputationError tabular.commitChanges() tabular.keywords['title'] = layer_purpose_exposure_summary_table['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['layer_purpose'] = layer_purpose_exposure_summary_table[ 'key'] check_layer(tabular, has_geometry=False) return tabular
def 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 exposure_summary_table( aggregate_hazard, exposure_summary=None, callback=None): """Compute the summary from the aggregate hazard to analysis. Source layer : | haz_id | haz_class | aggr_id | aggr_name | exposure_count | Output layer : | exp_type | count_hazard_class | total | :param aggregate_hazard: The layer to aggregate vector layer. :type aggregate_hazard: QgsVectorLayer :param exposure_summary: The layer impact layer. :type exposure_summary: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The new tabular table, without geometry. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = summary_4_exposure_summary_table_steps[ 'output_layer_name'] source_fields = aggregate_hazard.keywords['inasafe_fields'] source_compulsory_fields = [ aggregation_id_field, aggregation_name_field, hazard_id_field, hazard_class_field, affected_field, total_field ] check_inputs(source_compulsory_fields, source_fields) absolute_values = create_absolute_values_structure( aggregate_hazard, ['all']) hazard_class = source_fields[hazard_class_field['key']] hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class) unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index) unique_exposure = read_dynamic_inasafe_field( source_fields, exposure_count_field) flat_table = FlatTable('hazard_class', 'exposure_class') request = QgsFeatureRequest() request.setFlags(QgsFeatureRequest.NoGeometry) for area in aggregate_hazard.getFeatures(): hazard_value = area[hazard_class_index] for exposure in unique_exposure: key_name = exposure_count_field['key'] % exposure field_name = source_fields[key_name] exposure_count = area[field_name] if not exposure_count: exposure_count = 0 flat_table.add_value( exposure_count, hazard_class=hazard_value, exposure_class=exposure ) # We summarize every absolute values. for field, field_definition in list(absolute_values.items()): value = area[field] if not value: value = 0 field_definition[0].add_value( value, all='all' ) tabular = create_memory_layer(output_layer_name, QgsWkbTypes.NullGeometry) tabular.startEditing() field = create_field_from_definition(exposure_type_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][exposure_type_field['key']] = ( exposure_type_field['field_name']) hazard_keywords = aggregate_hazard.keywords['hazard_keywords'] hazard = hazard_keywords['hazard'] classification = hazard_keywords['classification'] exposure_keywords = aggregate_hazard.keywords['exposure_keywords'] exposure = exposure_keywords['exposure'] hazard_affected = {} for hazard_class in unique_hazard: if (hazard_class == '' or hazard_class is None or (hasattr(hazard_class, 'isNull') and hazard_class.isNull())): hazard_class = 'NULL' field = create_field_from_definition(hazard_count_field, hazard_class) tabular.addAttribute(field) key = hazard_count_field['key'] % hazard_class value = hazard_count_field['field_name'] % hazard_class tabular.keywords['inasafe_fields'][key] = value hazard_affected[hazard_class] = post_processor_affected_function( exposure=exposure, hazard=hazard, classification=classification, hazard_class=hazard_class ) field = create_field_from_definition(total_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_affected_field['key']] = ( total_affected_field['field_name']) # essentially have the same value as NULL_hazard_count # but with this, make sure that it exists in layer so it can be used for # reporting, and can be referenced to fields.py to take the label. field = create_field_from_definition(total_not_affected_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_affected_field['key']] = ( total_not_affected_field['field_name']) field = create_field_from_definition(total_not_exposed_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_not_exposed_field['key']] = ( total_not_exposed_field['field_name']) field = create_field_from_definition(total_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][total_field['key']] = ( total_field['field_name']) summarization_dicts = {} if exposure_summary: summarization_dicts = summarize_result(exposure_summary) sorted_keys = sorted(summarization_dicts.keys()) for key in sorted_keys: summary_field = summary_rules[key]['summary_field'] field = create_field_from_definition(summary_field) tabular.addAttribute(field) tabular.keywords['inasafe_fields'][ summary_field['key']] = ( summary_field['field_name']) # Only add absolute value if there is no summarization / no exposure # classification if not summarization_dicts: # For each absolute values for absolute_field in list(absolute_values.keys()): field_definition = definition(absolute_values[absolute_field][1]) field = create_field_from_definition(field_definition) tabular.addAttribute(field) key = field_definition['key'] value = field_definition['field_name'] tabular.keywords['inasafe_fields'][key] = value for exposure_type in unique_exposure: feature = QgsFeature() attributes = [exposure_type] total_affected = 0 total_not_affected = 0 total_not_exposed = 0 total = 0 for hazard_class in unique_hazard: if hazard_class == '' or hazard_class is None: hazard_class = 'NULL' value = flat_table.get_value( hazard_class=hazard_class, exposure_class=exposure_type ) attributes.append(value) if hazard_affected[hazard_class] == not_exposed_class['key']: total_not_exposed += value elif hazard_affected[hazard_class]: total_affected += value else: total_not_affected += value total += value attributes.append(total_affected) attributes.append(total_not_affected) attributes.append(total_not_exposed) attributes.append(total) if summarization_dicts: for key in sorted_keys: attributes.append(summarization_dicts[key].get( exposure_type, 0)) else: for i, field in enumerate(absolute_values.values()): value = field[0].get_value( all='all' ) attributes.append(value) feature.setAttributes(attributes) tabular.addFeature(feature) # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is # not enough. ET 13/02/17 # total_computed = ( # total_affected + total_not_affected + total_not_exposed) # if not -1 < (total_computed - total) < 1: # raise ComputationError tabular.commitChanges() tabular.keywords['title'] = layer_purpose_exposure_summary_table['name'] if qgis_version() >= 21800: tabular.setName(tabular.keywords['title']) else: tabular.setLayerName(tabular.keywords['title']) tabular.keywords['layer_purpose'] = layer_purpose_exposure_summary_table[ 'key'] check_layer(tabular, has_geometry=False) return tabular
def multi_buffering(layer, radii, callback=None): """Buffer a vector layer using many buffers (for volcanoes or rivers). This processing algorithm will keep the original attribute table and will add a new one for the hazard class name according to safe.definitions.fields.hazard_value_field. radii = OrderedDict() radii[500] = 'high' radii[1000] = 'medium' radii[2000] = 'low' Issue https://github.com/inasafe/inasafe/issues/3185 :param layer: The layer to polygonize. :type layer: QgsVectorLayer :param radii: A dictionary of radius. :type radii: OrderedDict :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The buffered vector layer. :rtype: QgsVectorLayer """ # Layer output output_layer_name = buffer_steps['output_layer_name'] processing_step = buffer_steps['step_name'] input_crs = layer.crs() feature_count = layer.featureCount() fields = layer.fields() # Set the new hazard class field. new_field = create_field_from_definition(hazard_class_field) fields.append(new_field) # Set the new buffer distances field. new_field = create_field_from_definition(buffer_distance_field) fields.append(new_field) buffered = create_memory_layer( output_layer_name, QGis.Polygon, input_crs, fields) data_provider = buffered.dataProvider() # Reproject features if needed into UTM if the layer is in 4326. if layer.crs().authid() == 'EPSG:4326': center = layer.extent().center() utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(layer.crs(), utm) reverse_transform = QgsCoordinateTransform(utm, layer.crs()) else: transform = None reverse_transform = None for i, feature in enumerate(layer.getFeatures()): geom = QgsGeometry(feature.geometry()) if transform: geom.transform(transform) inner_ring = None for radius in radii: attributes = feature.attributes() # We add the hazard value name to the attribute table. attributes.append(radii[radius]) # We add the value of buffer distance to the attribute table. attributes.append(radius) circle = geom.buffer(radius, 30) if inner_ring: circle.addRing(inner_ring) inner_ring = circle.asPolygon()[0] new_feature = QgsFeature() if reverse_transform: circle.transform(reverse_transform) new_feature.setGeometry(circle) new_feature.setAttributes(attributes) data_provider.addFeatures([new_feature]) if callback: callback(current=i, maximum=feature_count, step=processing_step) # We transfer keywords to the output. buffered.keywords = layer.keywords buffered.keywords['layer_geometry'] = 'polygon' buffered.keywords['layer_purpose'] = layer_purpose_hazard['key'] buffered.keywords['inasafe_fields'][hazard_class_field['key']] = ( hazard_class_field['field_name']) check_layer(buffered) return buffered
def clip(layer_to_clip, mask_layer, callback=None): """Clip a vector layer with another. Issue https://github.com/inasafe/inasafe/issues/3186 Note : This algorithm is copied from : https://github.com/qgis/QGIS/blob/master/python/plugins/processing/algs/ qgis/Clip.py :param layer_to_clip: The vector layer to clip. :type layer_to_clip: QgsVectorLayer :param mask_layer: The vector layer to use for clipping. :type mask_layer: QgsVectorLayer :param callback: A function to all to indicate progress. The function should accept params 'current' (int), 'maximum' (int) and 'step' (str). Defaults to None. :type callback: function :return: The clip vector layer. :rtype: QgsVectorLayer .. versionadded:: 4.0 """ output_layer_name = clip_steps['output_layer_name'] output_layer_name = output_layer_name % ( layer_to_clip.keywords['layer_purpose']) processing_step = clip_steps['step_name'] writer = create_memory_layer( output_layer_name, layer_to_clip.geometryType(), layer_to_clip.crs(), layer_to_clip.fields() ) writer.startEditing() # Begin copy/paste from Processing plugin. # Please follow their code as their code is optimized. # The code below is not following our coding standards because we want to # be able to track any diffs from QGIS easily. # first build up a list of clip geometries clip_geometries = [] request = QgsFeatureRequest().setSubsetOfAttributes([]) for mask_feature in mask_layer.getFeatures(request): clip_geometries.append(QgsGeometry(mask_feature.geometry())) # are we clipping against a single feature? if so, # we can show finer progress reports if len(clip_geometries) > 1: # noinspection PyTypeChecker,PyCallByClass,PyArgumentList combined_clip_geom = QgsGeometry.unaryUnion(clip_geometries) single_clip_feature = False else: combined_clip_geom = clip_geometries[0] single_clip_feature = True # use prepared geometries for faster intersection tests # noinspection PyArgumentList engine = QgsGeometry.createGeometryEngine(combined_clip_geom.geometry()) engine.prepareGeometry() tested_feature_ids = set() for i, clip_geom in enumerate(clip_geometries): request = QgsFeatureRequest().setFilterRect(clip_geom.boundingBox()) input_features = [f for f in layer_to_clip.getFeatures(request)] if not input_features: continue if single_clip_feature: total = 100.0 / len(input_features) else: total = 0 for current, in_feat in enumerate(input_features): if not in_feat.geometry(): continue if in_feat.id() in tested_feature_ids: # don't retest a feature we have already checked continue tested_feature_ids.add(in_feat.id()) if not engine.intersects(in_feat.geometry().geometry()): continue if not engine.contains(in_feat.geometry().geometry()): cur_geom = in_feat.geometry() new_geom = combined_clip_geom.intersection(cur_geom) if new_geom.wkbType() == QgsWKBTypes.Unknown \ or QgsWKBTypes.flatType( new_geom.geometry().wkbType()) == \ QgsWKBTypes.GeometryCollection: int_com = in_feat.geometry().combine(new_geom) int_sym = in_feat.geometry().symDifference(new_geom) if not int_com or not int_sym: # LOGGER.debug( # tr('GEOS geoprocessing error: One or more input ' # 'features have invalid geometry.')) pass else: new_geom = int_com.difference(int_sym) if new_geom.isGeosEmpty()\ or not new_geom.isGeosValid(): # LOGGER.debug( # tr('GEOS geoprocessing error: One or more ' # 'input features have invalid geometry.')) pass else: # clip geometry totally contains feature geometry, # so no need to perform intersection new_geom = in_feat.geometry() try: out_feat = QgsFeature() out_feat.setGeometry(new_geom) out_feat.setAttributes(in_feat.attributes()) if new_geom.type() == layer_to_clip.geometryType(): writer.addFeature(out_feat) except: LOGGER.debug( tr('Feature geometry error: One or more output features ' 'ignored due to invalid geometry.')) continue # TODO implement callback if single_clip_feature: # progress.setPercentage(int(current * total)) pass if not single_clip_feature: # coarse progress report for multiple clip geometries # progress.setPercentage(100.0 * i / len(clip_geoms)) pass # End copy/paste from Processing plugin. writer.commitChanges() writer.keywords = layer_to_clip.keywords.copy() writer.keywords['title'] = output_layer_name check_layer(writer) return writer
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
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 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) 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
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