def _validate(self, db, db_qr, layer_dict, tolerance, **kwargs): self.progress_changed.emit(5) point_layer = self._get_layer(layer_dict) pre_res, res_obj = self._check_prerrequisite_layer( QCoreApplication.translate("QualityRules", "Boundary point"), point_layer) if not pre_res: return res_obj overlapping = GeometryUtils.get_overlapping_points(point_layer) flat_overlapping = [fid for items in overlapping for fid in items] # Build a flat list of ids self.progress_changed.emit(70) dict_uuids = { f.id(): f[db.names.T_ILI_TID_F] for f in point_layer.getFeatures(flat_overlapping) } error_state = LADMData().get_domain_code_from_value( db_qr, db_qr.names.ERR_ERROR_STATE_D, LADMNames.ERR_ERROR_STATE_D_ERROR_V) errors = {'geometries': list(), 'data': list()} for items in overlapping: # We need a feature geometry, pick the first id to get it feature = point_layer.getFeature(items[0]) errors['geometries'].append(feature.geometry()) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] [str(dict_uuids.get(i)) for i in items], None, json.dumps({"conteo": len(items)}), None, error_state ] errors['data'].append(error_data) self._save_errors(db_qr, self._ERROR_01, errors) self.progress_changed.emit(100) if len(flat_overlapping) > 0: return QualityRuleExecutionResult( EnumQualityRuleResult.ERRORS, QCoreApplication.translate( "QualityRules", "{} overlapping boundary points were found!").format( len(flat_overlapping)), len(errors['data'])) else: return QualityRuleExecutionResult( EnumQualityRuleResult.SUCCESS, QCoreApplication.translate( "QualityRules", "There are no overlapping boundary points."))
class TestCopy(unittest.TestCase): @classmethod def setUpClass(cls): print("\nINFO: Setting up copy CSV points to DB validation...") print("INFO: Restoring databases to be used") import_asistente_ladm_col() cls.app = AppInterface() cls.app.core.initialize_ctm12() # We need to initialize CTM12 def setUp(self): restore_schema(SCHEMA_LADM_COL_EMPTY, True) self.db_pg = get_pg_conn(SCHEMA_LADM_COL_EMPTY) self.names = self.db_pg.names self.ladm_data = LADMData() self.geometry = GeometryUtils() res, code, msg = self.db_pg.test_connection() self.assertTrue(res, msg) self.assertIsNotNone(self.names.LC_BOUNDARY_POINT_T, 'Names is None') def test_copy_csv_to_db(self): print("\nINFO: Validating copy CSV points to DB...") clean_table(SCHEMA_LADM_COL_EMPTY, self.names.LC_BOUNDARY_POINT_T) layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, True) self.app.core.disable_automatic_fields(layer) csv_path = get_test_path('csv/puntos_fixed_ladm_v1_1.csv') txt_delimiter = ';' cbo_longitude = 'x' cbo_latitude = 'y' csv_layer = self.app.core.csv_to_layer(csv_path, txt_delimiter, cbo_longitude, cbo_latitude, "EPSG:9377", reproject=False) self.upload_points_from_csv(csv_layer, SCHEMA_LADM_COL_EMPTY) self.validate_points_in_db(SCHEMA_LADM_COL_EMPTY) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) delete_features(test_layer) self.assertEqual(test_layer.featureCount(), 0) def boundary_point_layer_resolve_domains_for_test(self, csv_layer): data_provider = csv_layer.dataProvider() data_provider.addAttributes([ QgsField('acuerdo', QVariant.Int), QgsField('puntotipo', QVariant.Int), QgsField('metodoproduccion', QVariant.Int) ]) csv_layer.updateFields() idx_agreement_field = data_provider.fieldNameIndex('acuerdo') idx_point_type_field = data_provider.fieldNameIndex('puntotipo') idx_production_method_field = data_provider.fieldNameIndex( 'metodoproduccion') with edit(csv_layer): for feature in csv_layer.getFeatures(): feature.setAttribute( idx_agreement_field, self.ladm_data.get_domain_code_from_value( self.db_pg, self.names.LC_AGREEMENT_TYPE_D, feature['_acuerdo'])) feature.setAttribute( idx_point_type_field, self.ladm_data.get_domain_code_from_value( self.db_pg, self.names.COL_POINT_TYPE_D, feature['_puntotipo'])) feature.setAttribute( idx_production_method_field, self.ladm_data.get_domain_code_from_value( self.db_pg, self.names.COL_PRODUCTION_METHOD_TYPE_D, feature['_metodoproduccion'])) csv_layer.updateFeature(feature) def upload_points_from_csv(self, csv_layer, schema): print("Copying CSV data with no elevation...") self.boundary_point_layer_resolve_domains_for_test(csv_layer) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) run_etl_model(self.names, csv_layer, test_layer, self.names.LC_BOUNDARY_POINT_T) self.assertEqual(test_layer.featureCount(), 51) self.validate_number_of_boundary_points_in_db(schema, 51) def test_upload_points_from_csv_crs_wgs84(self): print("\nINFO: Copying CSV data with EPSG:4326...") layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, True) self.app.core.disable_automatic_fields(layer) csv_path = get_test_path('csv/puntos_crs_4326_wgs84_ladm_v1_1.csv') txt_delimiter = ';' cbo_longitude = 'x' cbo_latitude = 'y' crs = 'EPSG:4326' csv_layer = self.app.core.csv_to_layer(csv_path, txt_delimiter, cbo_longitude, cbo_latitude, crs, reproject=False) csv_layer = reproject_to_ctm12(csv_layer) self.upload_points_from_csv_crs_wgs84(csv_layer, SCHEMA_LADM_COL_EMPTY) self.validate_points_in_db_from_wgs84(SCHEMA_LADM_COL_EMPTY) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) delete_features(test_layer) self.assertEqual(test_layer.featureCount(), 0) def upload_points_from_csv_crs_wgs84(self, csv_layer, schema): print("Copying CSV data in WGS84...") self.boundary_point_layer_resolve_domains_for_test(csv_layer) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) run_etl_model(self.names, csv_layer, test_layer, self.names.LC_BOUNDARY_POINT_T) self.validate_number_of_boundary_points_in_db(schema, 3) def validate_points_in_db_from_wgs84(self, schema): print('\nINFO: Validating points in db from wgs84') cur = self.db_pg.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cur.execute( """SELECT st_x(geometria), st_y(geometria) FROM {}.{};""".format( schema, self.names.LC_BOUNDARY_POINT_T)) results = cur.fetchall() self.assertEqual(len(results), 3) self.assertEqual( [round(result, 3) for result in results[0]], [round(item_test, 3) for item_test in [4843984.711, 2143385.632]]) self.assertEqual( [round(result, 3) for result in results[1]], [round(item_test, 3) for item_test in [4843918.478, 2143442.584]]) self.assertEqual( [round(result, 3) for result in results[2]], [round(item_test, 3) for item_test in [4843979.173, 2143379.773]]) def test_copy_csv_with_z_to_db(self): print("\nINFO: Validating copy CSV points with Z to DB...") clean_table(SCHEMA_LADM_COL_EMPTY, self.names.LC_BOUNDARY_POINT_T) layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, True) self.app.core.disable_automatic_fields(layer) csv_path = get_test_path('csv/puntos_fixed_ladm_v1_1.csv') txt_delimiter = ';' cbo_longitude = 'x' cbo_latitude = 'y' elevation = 'z' csv_layer = self.app.core.csv_to_layer(csv_path, txt_delimiter, cbo_longitude, cbo_latitude, "EPSG:9377", elevation, reproject=False) self.upload_points_from_csv_with_elevation(csv_layer, SCHEMA_LADM_COL_EMPTY) self.validate_points_in_db(SCHEMA_LADM_COL_EMPTY, with_z=True) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) delete_features(test_layer) self.assertEqual(test_layer.featureCount(), 0) def upload_points_from_csv_with_elevation(self, csv_layer, schema): print("\nINFO: Copying CSV data with elevation...") self.boundary_point_layer_resolve_domains_for_test(csv_layer) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) run_etl_model(self.names, csv_layer, test_layer, self.names.LC_BOUNDARY_POINT_T) self.assertEqual(test_layer.featureCount(), 51) self.validate_number_of_boundary_points_in_db(schema, 51) def validate_points_in_db(self, schema, with_z=False): cur = self.db_pg.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) print( '\nValidating points {}(both spatial and alphanumeric attributes)'. format('with Z ' if with_z else '')) cur.execute("""SELECT * FROM {}.{};""".format( schema, self.names.LC_BOUNDARY_POINT_T)) results = cur.fetchall() colnames = { desc[0]: cur.description.index(desc) for desc in cur.description } self.assertEqual(len(results), 51) for row in results: if row[colnames['id_punto_lindero']] == '50': break self.assertEqual(row[colnames['id_punto_lindero']], '50') self.assertEqual(row[colnames['puntotipo']], 17) self.assertEqual(row[colnames['acuerdo']], 596) self.assertEqual(row[colnames['fotoidentificacion']], None) self.assertEqual(row[colnames['exactitud_horizontal']], 1.000) self.assertEqual(row[colnames['exactitud_vertical']], None) self.assertEqual(row[colnames['posicion_interpolacion']], None) self.assertEqual(row[colnames['metodoproduccion']], 1) self.assertEqual(row[colnames['espacio_de_nombres']], 'LC_PUNTOLINDERO') self.assertIsNotNone(row[colnames['local_id']]) self.assertIsNone(row[colnames['ue_lc_servidumbretransito']]) self.assertIsNone(row[colnames['ue_lc_construccion']]) self.assertIsNone(row[colnames['ue_lc_terreno']]) self.assertIsNone(row[colnames['ue_lc_unidadconstruccion']]) self.assertIsNone(row[colnames['fin_vida_util_version']]) geom = '01010000A0A1240000EC51B836A57A5241CDCCCC7C8D5A40410000000000000000' if with_z: geom = '01010000A0A1240000EC51B836A57A5241CDCCCC7C8D5A404123DBF97EEA2E9640' self.assertEqual(row[colnames['geometria']], geom) def test_copy_csv_overlapping_to_db(self): print('\nINFO: Validating copy csv overlapping to db') clean_table(SCHEMA_LADM_COL_EMPTY, self.names.LC_BOUNDARY_POINT_T) csv_path = get_test_path('csv/puntos_overlapping_ladm_v1_1.csv') txt_delimiter = ';' cbo_longitude = 'x' cbo_latitude = 'y' csv_layer = self.app.core.csv_to_layer(csv_path, txt_delimiter, cbo_longitude, cbo_latitude, "EPSG:9377", reproject=False) self.upload_points_from_csv_overlapping(csv_layer, SCHEMA_LADM_COL_EMPTY) self.validate_number_of_boundary_points_in_db(SCHEMA_LADM_COL_EMPTY, 0) test_layer = self.app.core.get_layer(self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) delete_features(test_layer) self.assertEqual(test_layer.featureCount(), 0) def upload_points_from_csv_overlapping(self, csv_layer, schema): print('Uploading points from csv overlapping...') overlapping = self.geometry.get_overlapping_points(csv_layer) if not overlapping: self.boundary_point_layer_resolve_domains_for_test(csv_layer) test_layer = self.app.core.get_layer( self.db_pg, self.names.LC_BOUNDARY_POINT_T, load=True) run_etl_model(self.names, csv_layer, test_layer, self.names.LC_BOUNDARY_POINT_T) self.validate_number_of_boundary_points_in_db(schema, 0) def validate_number_of_boundary_points_in_db(self, schema, num=0): print( '\nINFO: Validating number of boundary points in schema {}'.format( schema)) cur = self.db_pg.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cur.execute("""SELECT count(t_id) FROM {}.{};""".format( schema, self.names.LC_BOUNDARY_POINT_T)) result = cur.fetchone() self.assertEqual(result[0], num) def tearDown(self): print("INFO: Closing open connections to databases") self.db_pg.conn.close() @classmethod def tearDownClass(cls): pass
class PointQualityRules: def __init__(self): self.quality_rules_manager = QualityRuleManager() self.logger = Logger() self.app = AppInterface() self.geometry = GeometryUtils() def check_overlapping_boundary_point(self, db, layers): rule = self.quality_rules_manager.get_quality_rule( EnumQualityRule.Point.OVERLAPS_IN_BOUNDARY_POINTS) return self.__check_overlapping_points( db, rule, layers, QUALITY_RULE_ERROR_CODE_E100101) def check_overlapping_control_point(self, db, layers): rule = self.quality_rules_manager.get_quality_rule( EnumQualityRule.Point.OVERLAPS_IN_CONTROL_POINTS) return self.__check_overlapping_points( db, rule, layers, QUALITY_RULE_ERROR_CODE_E100201) def __check_overlapping_points(self, db, rule, layer_dict, error_code): """ Shows which points are overlapping :param db: db connection instance :param layer_dict: Dict with layer name and layer object :return: msg, Qgis.MessageLevel """ features = [] layer_name = list(layer_dict[QUALITY_RULE_LAYERS].keys() )[0] if layer_dict[QUALITY_RULE_LAYERS] else None point_layer = list(layer_dict[QUALITY_RULE_LAYERS].values() )[0] if layer_dict[QUALITY_RULE_LAYERS] else None if not point_layer: return QCoreApplication.translate( "PointQualityRules", "'{}' layer not found!").format(layer_name), Qgis.Critical if point_layer.featureCount() == 0: return (QCoreApplication.translate( "PointQualityRules", "There are no points in layer '{}' to check for overlaps!"). format(layer_name), Qgis.Warning) else: error_layer = QgsVectorLayer( "Point?crs={}".format(point_layer.sourceCrs().authid()), rule.error_table_name, "memory") data_provider = error_layer.dataProvider() data_provider.addAttributes(rule.error_table_fields) error_layer.updateFields() overlapping = self.geometry.get_overlapping_points(point_layer) flat_overlapping = [id for items in overlapping for id in items] # Build a flat list of ids dict_uuids = { f.id(): f[db.names.T_ILI_TID_F] for f in point_layer.getFeatures(flat_overlapping) } for items in overlapping: # We need a feature geometry, pick the first id to get it feature = point_layer.getFeature(items[0]) point = feature.geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, point, { 0: ", ".join([str(dict_uuids.get(i)) for i in items]), 1: len(items), 2: self.quality_rules_manager.get_error_message( error_code), 3: error_code }) features.append(new_feature) error_layer.dataProvider().addFeatures(features) if error_layer.featureCount() > 0: added_layer = self.app.gui.add_error_layer(db, error_layer) return (QCoreApplication.translate( "PointQualityRules", "A memory layer with {} overlapping points in '{}' has been added to the map!" ).format(added_layer.featureCount(), layer_name), Qgis.Critical) else: return (QCoreApplication.translate( "PointQualityRules", "There are no overlapping points in layer '{}'!").format( layer_name), Qgis.Success) def check_boundary_points_covered_by_boundary_nodes(self, db, layer_dict): rule = self.quality_rules_manager.get_quality_rule( EnumQualityRule.Point.BOUNDARY_POINTS_COVERED_BY_BOUNDARY_NODES) layers = layer_dict[QUALITY_RULE_LAYERS] if not layers: return QCoreApplication.translate( "PointQualityRules", "At least one required layer (boundary, boundary point, point_bfs) was not found!" ), Qgis.Critical elif layers[db.names.LC_BOUNDARY_POINT_T].featureCount() == 0: return (QCoreApplication.translate( "PointQualityRules", "There are no boundary points to check 'boundary points should be covered by boundary nodes'." ), Qgis.Warning) else: error_layer = QgsVectorLayer( "Point?crs={}".format( layers[db.names.LC_BOUNDARY_POINT_T].sourceCrs().authid()), rule.error_table_name, "memory") data_provider = error_layer.dataProvider() data_provider.addAttributes(rule.error_table_fields) error_layer.updateFields() features = self.get_boundary_points_not_covered_by_boundary_nodes( db, layers[db.names.LC_BOUNDARY_POINT_T], layers[db.names.LC_BOUNDARY_T], layers[db.names.POINT_BFS_T], error_layer, db.names.T_ID_F) error_layer.dataProvider().addFeatures(features) if error_layer.featureCount() > 0: added_layer = self.app.gui.add_error_layer(db, error_layer) return (QCoreApplication.translate( "PointQualityRules", "A memory layer with {} boundary points not covered by boundary nodes has been added to the map!" ).format(added_layer.featureCount()), Qgis.Critical) else: return (QCoreApplication.translate( "PointQualityRules", "All boundary points are covered by boundary nodes!"), Qgis.Success) def check_boundary_points_covered_by_plot_nodes(self, db, layer_dict): rule = self.quality_rules_manager.get_quality_rule( EnumQualityRule.Point.BOUNDARY_POINTS_COVERED_BY_PLOT_NODES) layers = layer_dict[QUALITY_RULE_LAYERS] if not layers: return QCoreApplication.translate( "PointQualityRules", "At least one required layer (plot, boundary point) was not found!" ), Qgis.Critical if layers[db.names.LC_BOUNDARY_POINT_T].featureCount() == 0: return (QCoreApplication.translate( "PointQualityRules", "There are no boundary points to check 'boundary points should be covered by Plot nodes'." ), Qgis.Warning) else: error_layer = QgsVectorLayer( "Point?crs={}".format( layers[db.names.LC_BOUNDARY_POINT_T].sourceCrs().authid()), rule.error_table_name, "memory") data_provider = error_layer.dataProvider() data_provider.addAttributes(rule.error_table_fields) error_layer.updateFields() point_list = self.get_boundary_points_features_not_covered_by_plot_nodes( layers[db.names.LC_BOUNDARY_POINT_T], layers[db.names.LC_PLOT_T], db.names.T_ILI_TID_F) features = list() for point in point_list: new_feature = QgsVectorLayerUtils().createFeature( error_layer, point[1], # Geometry { 0: point[0], # feature uuid 1: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E100401), 2: QUALITY_RULE_ERROR_CODE_E100401 }) features.append(new_feature) error_layer.dataProvider().addFeatures(features) if error_layer.featureCount() > 0: added_layer = self.app.gui.add_error_layer(db, error_layer) return (QCoreApplication.translate( "PointQualityRules", "A memory layer with {} boundary points not covered by plot nodes has been added to the map!" ).format(added_layer.featureCount()), Qgis.Critical) else: return (QCoreApplication.translate( "PointQualityRules", "All boundary points are covered by plot nodes!"), Qgis.Success) # UTILITY METHODS def get_boundary_points_not_covered_by_boundary_nodes( self, db, boundary_point_layer, boundary_layer, point_bfs_layer, error_layer, id_field): dict_uuid_boundary = { f[id_field]: f[db.names.T_ILI_TID_F] for f in boundary_layer.getFeatures() } dict_uuid_boundary_point = { f[id_field]: f[db.names.T_ILI_TID_F] for f in boundary_point_layer.getFeatures() } tmp_boundary_nodes_layer = processing.run("native:extractvertices", { 'INPUT': boundary_layer, 'OUTPUT': 'memory:' })['OUTPUT'] # layer is created with unique vertices # It is necessary because 'remove duplicate vertices' processing algorithm does not filter the data as we need them boundary_nodes_layer = QgsVectorLayer( "Point?crs={}".format(boundary_layer.sourceCrs().authid()), 'unique boundary nodes', "memory") data_provider = boundary_nodes_layer.dataProvider() data_provider.addAttributes([QgsField(id_field, QVariant.Int)]) boundary_nodes_layer.updateFields() id_field_idx = tmp_boundary_nodes_layer.fields().indexFromName( id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) filter_fs = [] fs = [] for f in tmp_boundary_nodes_layer.getFeatures(request): item = [f[id_field], f.geometry().asWkt()] if item not in filter_fs: filter_fs.append(item) fs.append(f) del filter_fs boundary_nodes_layer.dataProvider().addFeatures(fs) # Spatial Join between boundary_points and boundary_nodes spatial_join_layer = processing.run( "qgis:joinattributesbylocation", { 'INPUT': boundary_point_layer, 'JOIN': boundary_nodes_layer, 'PREDICATE': [0], # Intersects 'JOIN_FIELDS': [db.names.T_ID_F], 'METHOD': 0, 'DISCARD_NONMATCHING': False, 'PREFIX': '', 'OUTPUT': 'memory:' })['OUTPUT'] # create dict with layer data id_field_idx = boundary_point_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_boundary_point = { feature[id_field]: feature for feature in boundary_point_layer.getFeatures(request) } exp_point_bfs = '"{}" is not null and "{}" is not null'.format( db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F, db.names.POINT_BFS_T_LC_BOUNDARY_F) list_point_bfs = [{ 'boundary_point_id': feature[db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F], 'boundary_id': feature[db.names.POINT_BFS_T_LC_BOUNDARY_F] } for feature in point_bfs_layer.getFeatures(exp_point_bfs)] spatial_join_boundary_point_boundary_node = [{ 'boundary_point_id': feature[id_field], 'boundary_id': feature[id_field + '_2'] } for feature in spatial_join_layer.getFeatures()] boundary_point_without_boundary_node = list() no_register_point_bfs = list() duplicate_in_point_bfs = list() # point_bfs topology check for item_sj in spatial_join_boundary_point_boundary_node: boundary_point_id = item_sj['boundary_point_id'] boundary_id = item_sj['boundary_id'] if boundary_id != NULL: if item_sj not in list_point_bfs: no_register_point_bfs.append( (boundary_point_id, boundary_id)) # no registered in point bfs elif list_point_bfs.count(item_sj) > 1: duplicate_in_point_bfs.append( (boundary_point_id, boundary_id)) # duplicate in point bfs else: boundary_point_without_boundary_node.append( boundary_point_id) # boundary point without boundary node features = list() # boundary point without boundary node if boundary_point_without_boundary_node is not None: for item in boundary_point_without_boundary_node: boundary_point_id = item # boundary_point_id boundary_point_geom = dict_boundary_point[ boundary_point_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, boundary_point_geom, { 0: dict_uuid_boundary_point.get(boundary_point_id), 1: None, 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E100301), 3: QUALITY_RULE_ERROR_CODE_E100301 }) features.append(new_feature) # No registered in point_bfs if no_register_point_bfs is not None: for error_no_register in set(no_register_point_bfs): boundary_point_id = error_no_register[0] # boundary_point_id boundary_id = error_no_register[1] # boundary_id boundary_point_geom = dict_boundary_point[ boundary_point_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, boundary_point_geom, { 0: dict_uuid_boundary_point.get(boundary_point_id), 1: dict_uuid_boundary.get(boundary_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E100302), 3: QUALITY_RULE_ERROR_CODE_E100302 }) features.append(new_feature) # Duplicate in point_bfs if duplicate_in_point_bfs is not None: for error_duplicate in set(duplicate_in_point_bfs): boundary_point_id = error_duplicate[0] # boundary_point_id boundary_id = error_duplicate[1] # boundary_id boundary_point_geom = dict_boundary_point[ boundary_point_id].geometry() new_feature = QgsVectorLayerUtils().createFeature( error_layer, boundary_point_geom, { 0: dict_uuid_boundary_point.get(boundary_point_id), 1: dict_uuid_boundary.get(boundary_id), 2: self.quality_rules_manager.get_error_message( QUALITY_RULE_ERROR_CODE_E100303), 3: QUALITY_RULE_ERROR_CODE_E100303 }) features.append(new_feature) return features def get_missing_boundary_points_in_boundaries(self, db, boundary_point_layer, boundary_layer): res = dict() feedback = QgsProcessingFeedback() extracted_vertices = processing.run("native:extractvertices", { 'INPUT': boundary_layer, 'OUTPUT': 'memory:' }, feedback=feedback) extracted_vertices_layer = extracted_vertices['OUTPUT'] # From vertices layer, get points with no overlap overlapping_points = self.geometry.get_overlapping_points( extracted_vertices_layer) extracted_vertices_ids = [ feature.id() for feature in extracted_vertices_layer.getFeatures() ] # Unpack list of lists into single list overlapping_point_ids = [ item for sublist in overlapping_points for item in sublist ] # Unpack list of lists into single list selecting only the first point # per list. That means, discard overlapping points, and only keep one cleaned_point_ids = [sublist[0] for sublist in overlapping_points] # All vertices (even duplicated, due to the alg we use), minus all # overlapping ids, plus only one of the overlapping ids # This gets a list of all vertex ids with no duplicates no_duplicate_ids = list( set(extracted_vertices_ids) - set(overlapping_point_ids)) + cleaned_point_ids if boundary_point_layer.featureCount() == 0: # Return all extracted and cleaned vertices for feature in extracted_vertices_layer.getFeatures( no_duplicate_ids): if feature[db.names.T_ID_F] in res: res[feature[db.names.T_ID_F]].append(feature.geometry()) else: res[feature[db.names.T_ID_F]] = [feature.geometry()] return res index = QgsSpatialIndex( boundary_point_layer.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([])), feedback) for feature in extracted_vertices_layer.getFeatures(no_duplicate_ids): if feature.hasGeometry(): geom = feature.geometry() diff_geom = QgsGeometry(geom) # Use a custom bbox to include very near but not exactly equal points point_vert = { 'x': diff_geom.asPoint().x(), 'y': diff_geom.asPoint().y() } bbox = QgsRectangle( QgsPointXY(point_vert['x'] - 0.0001, point_vert['y'] - 0.0001), QgsPointXY(point_vert['x'] + 0.0001, point_vert['y'] + 0.0001)) intersects = index.intersects(bbox) if not intersects: if feature[db.names.T_ID_F] in res: res[feature[db.names.T_ID_F]].append(diff_geom) else: res[feature[db.names.T_ID_F]] = [diff_geom] return res @staticmethod def get_boundary_points_features_not_covered_by_plot_nodes( boundary_point_layer, plot_layer, id_field): plot_nodes_layer = GeometryUtils.get_polygon_nodes_layer( plot_layer, id_field) return GeometryUtils.get_non_intersecting_geometries( boundary_point_layer, plot_nodes_layer, id_field)