コード例 #1
0
    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."))
コード例 #2
0
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
コード例 #3
0
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)