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 _validate(self, db, db_qr, layer_dict, tolerance, **kwargs): self.progress_changed.emit(5) building_layer = self._get_layer(layer_dict) pre_res, res_obj = self._check_prerrequisite_layer(QCoreApplication.translate("QualityRules", "Building"), building_layer) if not pre_res: return res_obj overlapping = GeometryUtils.get_overlapping_polygons(self, building_layer) flat_overlapping = [fid for items in overlapping for fid in items] # Build a flat list of ids flat_overlapping = list(set(flat_overlapping)) # unique values self.progress_changed.emit(70) dict_uuids = {f.id(): f[db.names.T_ILI_TID_F] for f in building_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: polygon_id_field = items[0] overlapping_id_field = items[1] polygon_intersection = GeometryUtils.get_intersection_polygons(building_layer, polygon_id_field, overlapping_id_field) if polygon_intersection is not None: errors['geometries'].append(polygon_intersection) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] [dict_uuids.get(polygon_id_field)], [dict_uuids.get(overlapping_id_field)], None, self._errors[self._ERROR_01], error_state] errors['data'].append(error_data) self._save_errors(db_qr, self._ERROR_01, errors) if len(flat_overlapping) > 0: res_type = EnumQualityRuleResult.ERRORS msg = QCoreApplication.translate("QualityRules", "{} overlapping buildings were found!").format(len(flat_overlapping)) else: res_type = EnumQualityRuleResult.SUCCESS msg = QCoreApplication.translate("QualityRules", "There are no overlapping buildings!") self.progress_changed.emit(100) return QualityRuleExecutionResult(res_type, msg, len(flat_overlapping))
def test_multiparts_in_right_of_way(self): print('\nINFO: Validating right_of_way for no multipart geometries...') gpkg_path = get_test_copy_path('db/static/gpkg/quality_validations.gpkg') uri = gpkg_path + '|layername={layername}'.format(layername='right_of_way') right_of_way = QgsVectorLayer(uri, 'right_of_way', 'ogr') self.assertEqual(right_of_way.featureCount(), 6) single_parts, single_ids = GeometryUtils.get_multipart_geoms(right_of_way) unique_single_ids = set(single_ids) self.assertEqual(len(single_parts), 8) self.assertEqual(len(unique_single_ids), 3) geometries = [g.asWkt() for g in single_parts] expected_list = [ 'Polygon ((1091145.85694021754898131 1121724.4278288041241467, 1091173.73775773262605071 1121765.70312499976716936, 1091206.17259239125996828 1121797.18358423886820674, 1091254.27673969138413668 1121818.85067654610611498, 1091289.43588917586021125 1121819.66833118535578251, 1091291.07119845412671566 1121759.97954252548515797, 1091260.00032216543331742 1121759.97954252548515797, 1091223.61469072219915688 1121742.80879510287195444, 1091197.44974226853810251 1121710.92026417492888868, 1091175.37306701089255512 1121673.30815077293664217, 1091177.00837628915905952 1121614.43701675231568515, 1091122.48462500004097819 1121616.61360054323449731, 1091119.46884239139035344 1121678.4371440215036273, 1091145.85694021754898131 1121724.4278288041241467))', 'Polygon ((1091336.04220360890030861 1121821.30364046362228692, 1091432.52545103151351213 1121818.85067654610611498, 1091433.34310567076317966 1121757.52657860796898603, 1091336.04220360890030861 1121762.4325064430013299, 1091336.04220360890030861 1121821.30364046362228692))', 'Polygon ((1091123.04317010380327702 1121561.28946520597673953, 1091175.37306701089255512 1121560.471810566727072, 1091172.10244845412671566 1121306.18121778313070536, 1091121.40786082530394197 1121307.81652706163004041, 1091123.04317010380327702 1121561.28946520597673953))', 'Polygon ((1091487.30831185611896217 1121755.89126932970248163, 1091747.3224871139973402 1121750.16768685542047024, 1091747.3224871139973402 1121813.94474871107377112, 1091487.30831185611896217 1121818.85067654610611498, 1091487.30831185611896217 1121755.89126932970248163))', 'Polygon ((1091747.3224871139973402 1121813.94474871107377112, 1091751.00193299050442874 1121954.58134664944373071, 1091806.6024484543595463 1121951.31072809267789125, 1091804.9671391760930419 1121217.05686211329884827, 1091749.36662371223792434 1121218.69217139179818332, 1091036.37177835148759186 1121226.86871778359636664, 1091039.64239690802060068 1121298.8223260308150202, 1091041.27770618651993573 1121310.26949097937904298, 1091121.40786082530394197 1121307.81652706163004041, 1091172.10244845412671566 1121306.18121778313070536, 1091425.57538659870624542 1121310.26949097937904298, 1091474.63466494926251471 1121316.81072809267789125, 1091749.36662371223792434 1121300.45763530931435525, 1091747.3224871139973402 1121750.16768685542047024, 1091747.3224871139973402 1121813.94474871107377112))', 'Polygon ((1091041.27770618651993573 1121310.26949097937904298, 1091039.64239690802060068 1121298.8223260308150202, 1091036.37177835148759186 1121226.86871778359636664, 1090936.91814493527635932 1121227.28393303113989532, 1090936.91814493527635932 1122080.91537633049301803, 1091813.44391813105903566 1122080.91537633049301803, 1091806.6024484543595463 1121951.31072809267789125, 1091751.00193299050442874 1121954.58134664944373071, 1091754.57278410973958671 1122030.62961602141149342, 1090996.1981062744744122 1122030.62961602141149342, 1090996.1981062744744122 1121309.86705158883705735, 1091033.88846642058342695 1121310.19713682751171291, 1091038.64362105960026383 1121310.19713682751171291, 1091041.27770618651993573 1121310.26949097937904298))', 'Polygon ((1090495.31252119154669344 1121959.44378872332163155, 1090784.3099831254221499 1121950.85970569564960897, 1090784.3099831254221499 1121676.16904880781657994, 1090501.03524321014992893 1121679.03040981711819768, 1090495.31252119154669344 1121959.44378872332163155),(1090604.04423954291269183 1121859.29615339962765574, 1090698.46915284800343215 1121856.434792390326038, 1090701.3305138573050499 1121753.4257960575632751, 1090615.48968357988633215 1121756.28715706686489284, 1090604.04423954291269183 1121859.29615339962765574))', 'Polygon ((1090592.59879550593905151 1121550.26916440110653639, 1090724.22140193125233054 1121547.40780339180491865, 1090724.22140193125233054 1121398.61703091091476381, 1090592.59879550593905151 1121407.20111393858678639, 1090592.59879550593905151 1121550.26916440110653639))' ] for expected in expected_list: self.assertIn(expected, geometries)
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."))
def _validate(self, db, db_qr, layer_dict, tolerance, **kwargs): self.progress_changed.emit(5) right_of_way_layer = self._get_layer(layer_dict) pre_res, res_obj = self._check_prerrequisite_layer( QCoreApplication.translate("QualityRules", "Right of way"), right_of_way_layer) if not pre_res: return res_obj self.progress_changed.emit(10) multi_parts, right_of_way_ids = GeometryUtils.get_multipart_geoms( right_of_way_layer) self.progress_changed.emit(70) 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()} if multi_parts: for right_of_way_geom, right_of_way_id in zip( multi_parts, right_of_way_ids): errors['geometries'].append(right_of_way_geom) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] [ right_of_way_layer.getFeature(right_of_way_id)[ db.names.T_ILI_TID_F] ], None, None, self._errors[self._ERROR_01], error_state ] errors['data'].append(error_data) self._save_errors(db_qr, self._ERROR_01, errors) self.progress_changed.emit(90) count = len(errors['data']) if count > 0: res_type = EnumQualityRuleResult.ERRORS msg = QCoreApplication.translate( "QualityRules", "{} rights of way with " "multipart geometry were found.").format(count) else: res_type = EnumQualityRuleResult.SUCCESS msg = QCoreApplication.translate( "QualityRules", "All rights of way have simple geometry!!") self.progress_changed.emit(100) return QualityRuleExecutionResult(res_type, msg, count)
def test_intersection_polygons_tolerance(self): print('\nINFO: Validating intersection in polygons (plots)...') gpkg_path = get_test_copy_path('db/static/gpkg/geometry_utils.gpkg') uri = gpkg_path + '|layername={layername}'.format( layername='overlapping_polygons') polygons_intersection_layer = QgsVectorLayer(uri, 'overlapping_polygons', 'ogr') polygon_id = 61 overlapping_id = 62 test_overlapping_polygon = 'MultiPolygon (((779846.53495819831732661 1225249.26543459924869239, 779783.95215819834265858 1225214.60283459979109466, 779755.42265819816384465 1225312.01313459943048656, 779751.20725819806102663 1225339.76893460075370967, 779753.92445819883141667 1225441.12693459982983768, 779750.35635819809976965 1225530.46543460036627948, 779822.77425819879863411 1225579.77523459936492145, 779989.38215819804463536 1225693.37413460086099803, 780035.07055819837842137 1225719.08503460022620857, 780048.81795819813851267 1225694.37763460050337017, 780107.8898581980029121 1225665.00753459963016212, 780145.38955819851253182 1225642.4988345995079726, 780156.81765819864813238 1225634.07963460008613765, 780178.43775819859001786 1225625.24393460038118064, 780098.00075819867197424 1225413.46203460055403411, 779976.68275819870177656 1225330.46993460017256439, 779918.56405819824431092 1225292.73203460010699928, 779846.53495819831732661 1225249.26543459924869239)))' polygon_intersection = GeometryUtils.get_intersection_polygons( polygons_intersection_layer, polygon_id, overlapping_id) self.assertEqual(polygon_intersection.asWkt(), test_overlapping_polygon)
def test_pair_boundary_plot(self): print('\nValidating pairs boundary-plot') gpkg_path = get_test_copy_path( 'db/static/gpkg/quality_validations.gpkg') self.db_gpkg = get_gpkg_conn('tests_quality_validations_gpkg') self.names = self.db_gpkg.names self.names.T_ID_F = 't_id' # Static label is set because the database does not have the ladm structure uri = gpkg_path + '|layername={layername}'.format( layername='topology_boundaries') boundary_layer = QgsVectorLayer(uri, 'topology_boundaries', 'ogr') uri = gpkg_path + '|layername={layername}'.format( layername='topology_plots') plot_layer = QgsVectorLayer(uri, 'topology_plots', 'ogr') result1, result2 = GeometryUtils().get_pair_boundary_plot( boundary_layer, plot_layer, self.names.T_ID_F, use_selection=False) self.assertEqual(result1, [(1, 3), (3, 3)]) self.assertEqual(result2, [(1, 4)])
def test_check_buildings_within_plots(self): print('\nINFO: Validating buldings are within plots...') gpkg_path = get_test_copy_path('db/static/gpkg/quality_validations.gpkg') uri = gpkg_path + '|layername={layername}'.format(layername='buildings') building_layer = QgsVectorLayer(uri, 'buildings', 'ogr') self.assertEqual(building_layer.featureCount(), 6) uri = gpkg_path + '|layername={layername}'.format(layername='plots') plot_layer = QgsVectorLayer(uri, 'plots', 'ogr') self.assertEqual(plot_layer.featureCount(), 5) buildings_disjoint, buildings_overlaps, building_within = GeometryUtils.get_relationships_among_polygons( building_layer, plot_layer) test_buildings_disjoint = [5] test_buildings_overlaps = [2, 6] test_building_within = [1, 3, 4] self.assertListEqual(list(set(buildings_disjoint)), test_buildings_disjoint) self.assertListEqual(list(set(buildings_overlaps)), test_buildings_overlaps) self.assertListEqual(list(set(building_within)), test_building_within)
def test_check_gaps_in_plots(self): gpkg_path = get_test_copy_path('db/static/gpkg/quality_validations.gpkg') uri = gpkg_path + '|layername={layername}'.format(layername='check_gaps_in_plots') test_plots_layer = QgsVectorLayer(uri, 'check_gaps_in_plots', 'ogr') print('\nINFO: Validating Gaps in Plots using roads and multiple geometries...') gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, True) geometries = [g.asWkt() for g in gaps] expected_list = [ 'Polygon ((1001839.42949045938439667 1013500.23419545334763825, 1001838.68766217899974436 1013479.83391774445772171, 1001839.42949045938439667 1013450.16078653128352016, 1001855.74971262644976377 1013449.78987239114940166, 1001858.3461116076214239 1013430.87325124291237444, 1001885.42284383939113468 1013430.87325124291237444, 1001901.72405463655013591 1013411.57209242216777056, 1001910.64500537037383765 1013418.26217047742102295, 1001917.32145989337004721 1013392.29818066605366766, 1001845.19794039404951036 1013415.08188382943626493, 1001851.47861975431442261 1013424.31817700632382184, 1001833.74493685469496995 1013433.92392191023100168, 1001829.49624199338722974 1013421.7320149167208001, 1001839.42949045938439667 1013500.23419545334763825))', 'Polygon ((1001935.86716690135654062 1013432.35690780356526375, 1001921.03060129494406283 1013446.08073098957538605, 1001920.28877301455941051 1013475.7538622027495876, 1001957.38018703076522797 1013429.01868054212536663, 1001935.86716690135654062 1013432.35690780356526375))', 'Polygon ((1001935.86716690135654062 1013432.35690780356526375, 1001921.03060129494406283 1013446.08073098957538605, 1001920.28877301455941051 1013475.7538622027495876, 1001957.38018703076522797 1013429.01868054212536663, 1001935.86716690135654062 1013432.35690780356526375))', 'Polygon ((1001920.28877301455941051 1013475.7538622027495876, 1001861.31342472892720252 1013477.9793470436707139, 1001862.05525300919543952 1013498.37962475256063044, 1001920.28877301455941051 1013475.7538622027495876))', 'Polygon ((1001895.43752562382724136 1013467.22283697873353958, 1001907.30677810893394053 1013464.25552385742776096, 1001907.67769224906805903 1013454.2408420731080696, 1001895.43752562382724136 1013454.2408420731080696, 1001895.43752562382724136 1013467.22283697873353958))', 'Polygon ((1001847.96051568305119872 1013470.1901501000393182, 1001867.98987925180699676 1013469.07740767952054739, 1001869.10262167232576758 1013455.72449863376095891, 1001847.58960154291708022 1013455.72449863376095891, 1001847.96051568305119872 1013470.1901501000393182))'] for expected in expected_list: self.assertIn(expected, geometries) self.assertEqual(len(geometries), 5) print('\nINFO: Validating Gaps in Plots using roads for one geometry...') test_plots_layer.startEditing() test_plots_layer.deleteFeature(2) gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, True) geometries = [g.asWkt() for g in gaps] self.assertIn( 'Polygon ((1001895.43752562382724136 1013467.22283697873353958, 1001907.30677810893394053 1013464.25552385742776096, 1001907.67769224906805903 1013454.2408420731080696, 1001895.43752562382724136 1013454.2408420731080696, 1001895.43752562382724136 1013467.22283697873353958))', geometries) self.assertIn( 'Polygon ((1001847.96051568305119872 1013470.1901501000393182, 1001867.98987925180699676 1013469.07740767952054739, 1001869.10262167232576758 1013455.72449863376095891, 1001847.58960154291708022 1013455.72449863376095891, 1001847.96051568305119872 1013470.1901501000393182))', geometries) self.assertEqual(len(geometries), 2) test_plots_layer.rollBack() print('\nINFO: Validating Gaps in Plots without using roads and multiple geometries...') gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, False) geometries = [g.asWkt() for g in gaps] self.assertIn( 'Polygon ((1001895.43752562382724136 1013467.22283697873353958, 1001907.30677810893394053 1013464.25552385742776096, 1001907.67769224906805903 1013454.2408420731080696, 1001895.43752562382724136 1013454.2408420731080696, 1001895.43752562382724136 1013467.22283697873353958))', geometries) self.assertIn( 'Polygon ((1001847.96051568305119872 1013470.1901501000393182, 1001867.98987925180699676 1013469.07740767952054739, 1001869.10262167232576758 1013455.72449863376095891, 1001847.58960154291708022 1013455.72449863376095891, 1001847.96051568305119872 1013470.1901501000393182))', geometries) self.assertEqual(len(geometries), 2) print('\nINFO: Validating Gaps in Plots without using roads for one geometry...') test_plots_layer.startEditing() test_plots_layer.deleteFeature(2) gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, False) geometries = [g.asWkt() for g in gaps] self.assertIn( 'Polygon ((1001895.43752562382724136 1013467.22283697873353958, 1001907.30677810893394053 1013464.25552385742776096, 1001907.67769224906805903 1013454.2408420731080696, 1001895.43752562382724136 1013454.2408420731080696, 1001895.43752562382724136 1013467.22283697873353958))', geometries) self.assertIn( 'Polygon ((1001847.96051568305119872 1013470.1901501000393182, 1001867.98987925180699676 1013469.07740767952054739, 1001869.10262167232576758 1013455.72449863376095891, 1001847.58960154291708022 1013455.72449863376095891, 1001847.96051568305119872 1013470.1901501000393182))', geometries) self.assertEqual(len(geometries), 2) test_plots_layer.rollBack() print('\nINFO: Validating Gaps in Plots using roads for only one geometry...') test_plots_layer.startEditing() test_plots_layer.deleteFeature(1) test_plots_layer.deleteFeature(2) test_plots_layer.deleteFeature(3) gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, True) geometries = [g.asWkt() for g in gaps] self.assertEqual([], geometries) self.assertEqual(len(geometries), 0) test_plots_layer.rollBack() print('\nINFO: Validating Gaps in Plots without using roads for only one geometry...') test_plots_layer.startEditing() test_plots_layer.deleteFeature(1) test_plots_layer.deleteFeature(2) test_plots_layer.deleteFeature(3) gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, False) geometries = [g.asWkt() for g in gaps] self.assertEqual([], geometries) self.assertEqual(len(geometries), 0) test_plots_layer.rollBack() print('\nINFO: Validating Gaps in Plots using roads for two geometries...') test_plots_layer.startEditing() test_plots_layer.deleteFeature(1) test_plots_layer.deleteFeature(3) gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, True) geometries = [g.asWkt() for g in gaps] self.assertIn( 'Polygon ((1001889.87381352134980261 1013447.93530169036239386, 1001885.42284383939113468 1013430.87325124291237444, 1001901.72405463655013591 1013411.57209242216777056, 1001845.19794039404951036 1013415.08188382943626493, 1001851.47861975431442261 1013424.31817700632382184, 1001833.74493685469496995 1013433.92392191023100168, 1001889.87381352134980261 1013447.93530169036239386))', geometries) self.assertEqual(len(geometries), 1) test_plots_layer.rollBack() print('\nINFO: Validating Gaps in Plots without using roads for two geometries...') test_plots_layer.startEditing() test_plots_layer.deleteFeature(1) test_plots_layer.deleteFeature(3 ) gaps = GeometryUtils.get_gaps_in_polygon_layer(test_plots_layer, False) geometries = [g.asWkt() for g in gaps] self.assertEqual([], geometries) self.assertEqual(len(geometries), 0) test_plots_layer.rollBack()
class ToolBar(QObject): def __init__(self, iface): QObject.__init__(self) self.iface = iface self.logger = Logger() self.app = AppInterface() self.geometry = GeometryUtils() def build_boundary(self, db): QgsProject.instance().setAutoTransaction(False) layer = self.app.core.get_ladm_layer_from_qgis( db, db.names.LC_BOUNDARY_T, EnumLayerRegistryType.IN_LAYER_TREE) use_selection = True if layer is None: self.logger.message_with_button_load_layer_emitted.emit( QCoreApplication.translate( "ToolBar", "First load the layer {} into QGIS!").format( db.names.LC_BOUNDARY_T), QCoreApplication.translate("ToolBar", "Load layer {} now").format( db.names.LC_BOUNDARY_T), db.names.LC_BOUNDARY_T, Qgis.Warning) return else: if layer.selectedFeatureCount() == 0: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are no selected boundaries. Do you want to use all the {} boundaries in the database?" ).format(layer.featureCount()), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = False elif reply == QMessageBox.Cancel: self.logger.warning_msg( __name__, QCoreApplication.translate( "ToolBar", "First select at least one boundary!")) return if use_selection: new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_selected_boundaries( db.names, layer, db.names.T_ID_F) num_boundaries = layer.selectedFeatureCount() else: new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_boundaries( layer, db.names.T_ID_F) num_boundaries = layer.featureCount() if len(new_boundary_geoms) > 0: layer.startEditing( ) # Safe, even if layer is already on editing state # the boundaries that are to be replaced are removed layer.deleteFeatures(boundaries_to_del_ids) # Create features based on segment geometries new_fix_boundary_features = list() for boundary_geom in new_boundary_geoms: feature = QgsVectorLayerUtils().createFeature( layer, boundary_geom) # TODO: Remove when local id and namespace are defined feature.setAttribute(db.names.OID_T_LOCAL_ID_F, 1) feature.setAttribute(db.names.OID_T_NAMESPACE_F, db.names.LC_BOUNDARY_T) new_fix_boundary_features.append(feature) layer.addFeatures(new_fix_boundary_features) self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} feature(s) was(were) analyzed generating {} boundary(ies)!" ).format(num_boundaries, len(new_fix_boundary_features))) self.iface.mapCanvas().refresh() else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "There are no boundaries to build.")) def fill_topology_table_pointbfs(self, db, use_selection=True): layers = { db.names.LC_BOUNDARY_T: None, db.names.POINT_BFS_T: None, db.names.LC_BOUNDARY_POINT_T: None } self.app.core.get_layers(db, layers, load=True) if not layers: return None if use_selection: if layers[db.names.LC_BOUNDARY_T].selectedFeatureCount() == 0: if self.app.core.get_ladm_layer_from_qgis( db, db.names.LC_BOUNDARY_T, EnumLayerRegistryType.IN_LAYER_TREE) is None: self.logger.message_with_button_load_layer_emitted.emit( QCoreApplication.translate( "ToolBar", "First load the layer {} into QGIS and select at least one boundary!" ).format(db.names.LC_BOUNDARY_T), QCoreApplication.translate("ToolBar", "Load layer {} now").format( db.names.LC_BOUNDARY_T), db.names.LC_BOUNDARY_T, Qgis.Warning) else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are no selected boundaries. Do you want to fill the '{}' table for all the {} boundaries in the database?" ).format( db.names.POINT_BFS_T, layers[db.names.LC_BOUNDARY_T].featureCount()), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = False elif reply == QMessageBox.Cancel: self.logger.warning_msg( __name__, QCoreApplication.translate( "ToolBar", "First select at least one boundary!")) return else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are {selected} boundaries selected. Do you want to fill the '{table}' table just for the selected boundaries?\n\nIf you say 'No', the '{table}' table will be filled for all boundaries in the database." ).format(selected=layers[ db.names.LC_BOUNDARY_T].selectedFeatureCount(), table=db.names.POINT_BFS_T), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = True elif reply == QMessageBox.No: use_selection = False elif reply == QMessageBox.Cancel: return bfs_features = layers[db.names.POINT_BFS_T].getFeatures() # Get unique pairs id_boundary-id_boundary_point existing_pairs = [ (bfs_feature[db.names.POINT_BFS_T_LC_BOUNDARY_F], bfs_feature[db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F]) for bfs_feature in bfs_features ] existing_pairs = set(existing_pairs) tolerance = self.app.settings.tolerance id_pairs = self.geometry.get_pair_boundary_boundary_point( layers[db.names.LC_BOUNDARY_T], layers[db.names.LC_BOUNDARY_POINT_T], db.names.T_ID_F, use_selection=use_selection, tolerance=tolerance) if id_pairs: layers[db.names.POINT_BFS_T].startEditing() features = list() for id_pair in id_pairs: if not id_pair in existing_pairs: # Avoid duplicated pairs in the DB # Create feature feature = QgsVectorLayerUtils().createFeature( layers[db.names.POINT_BFS_T]) feature.setAttribute(db.names.POINT_BFS_T_LC_BOUNDARY_F, id_pair[0]) feature.setAttribute( db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F, id_pair[1]) features.append(feature) layers[db.names.POINT_BFS_T].addFeatures(features) layers[db.names.POINT_BFS_T].commitChanges() self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} out of {} records were saved into {}! {} out of {} records already existed in the database." ).format(len(features), len(id_pairs), db.names.POINT_BFS_T, len(id_pairs) - len(features), len(id_pairs))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "No pairs id_boundary-id_boundary_point found.")) def fill_topology_tables_morebfs_less(self, db, use_selection=True): layers = { db.names.LC_PLOT_T: None, db.names.MORE_BFS_T: None, db.names.LESS_BFS_T: None, db.names.LC_BOUNDARY_T: None } self.app.core.get_layers(db, layers, load=True) if not layers: return None if use_selection: if layers[db.names.LC_PLOT_T].selectedFeatureCount() == 0: if self.app.core.get_ladm_layer_from_qgis( db, db.names.LC_PLOT_T, EnumLayerRegistryType.IN_LAYER_TREE) is None: self.logger.message_with_button_load_layer_emitted.emit( QCoreApplication.translate( "ToolBar", "First load the layer {} into QGIS and select at least one plot!" ).format(db.names.LC_PLOT_T), QCoreApplication.translate("ToolBar", "Load layer {} now").format( db.names.LC_PLOT_T), db.names.LC_PLOT_T, Qgis.Warning) else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are no selected plots. Do you want to fill the '{more}' and '{less}' tables for all the {all} plots in the database?" ).format( more=db.names.MORE_BFS_T, less=db.names.LESS_BFS_T, all=layers[db.names.LC_PLOT_T].featureCount()), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = False elif reply == QMessageBox.Cancel: self.logger.warning_msg( __name__, QCoreApplication.translate( "ToolBar", "First select at least one plot!")) return else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are {selected} plots selected. Do you want to fill the '{more}' and '{less}' tables just for the selected plots?\n\nIf you say 'No', the '{more}' and '{less}' tables will be filled for all plots in the database." ).format(selected=layers[ db.names.LC_PLOT_T].selectedFeatureCount(), more=db.names.MORE_BFS_T, less=db.names.LESS_BFS_T), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = True elif reply == QMessageBox.No: use_selection = False elif reply == QMessageBox.Cancel: return tolerance = self.app.settings.tolerance if tolerance: # We need to adjust input layers to take tolerance into account # Use the same configuration we use in quality rule 3004 (Plots should be covered by boundaries). layers[db.names.LC_PLOT_T] = self.app.core.adjust_layer( layers[db.names.LC_PLOT_T], layers[db.names.LC_PLOT_T], tolerance, True, use_selection) layers[db.names.LC_BOUNDARY_T] = self.app.core.adjust_layer( layers[db.names.LC_BOUNDARY_T], layers[db.names.LC_PLOT_T], tolerance, True) if use_selection: layers[db.names.LC_PLOT_T].selectAll( ) # Because this layer is already filtered by selected features # Get unique pairs id_boundary-id_plot in both tables existing_more_pairs = set([ (more_bfs_feature[db.names.MORE_BFS_T_LC_PLOT_F], more_bfs_feature[db.names.MORE_BFS_T_LC_BOUNDARY_F]) for more_bfs_feature in layers[db.names.MORE_BFS_T].getFeatures() ]) existing_less_pairs = set([ (less_feature[db.names.LESS_BFS_T_LC_PLOT_F], less_feature[db.names.LESS_BFS_T_LC_BOUNDARY_F]) for less_feature in layers[db.names.LESS_BFS_T].getFeatures() ]) id_more_pairs, id_less_pairs = self.geometry.get_pair_boundary_plot( layers[db.names.LC_BOUNDARY_T], layers[db.names.LC_PLOT_T], db.names.T_ID_F, use_selection=use_selection) if id_less_pairs: layers[db.names.LESS_BFS_T].startEditing() features = list() for id_pair in id_less_pairs: if not id_pair in existing_less_pairs: # Avoid duplicated pairs in the DB # Create feature feature = QgsVectorLayerUtils().createFeature( layers[db.names.LESS_BFS_T]) feature.setAttribute(db.names.LESS_BFS_T_LC_PLOT_F, id_pair[0]) feature.setAttribute(db.names.LESS_BFS_T_LC_BOUNDARY_F, id_pair[1]) features.append(feature) layers[db.names.LESS_BFS_T].addFeatures(features) layers[db.names.LESS_BFS_T].commitChanges() self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database." ).format(len(features), len(id_less_pairs), db.names.LESS_BFS_T, len(id_less_pairs) - len(features), len(id_less_pairs))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "No pairs id_boundary-id_plot found for '{}' table."). format(db.names.LESS_BFS_T)) if id_more_pairs: layers[db.names.MORE_BFS_T].startEditing() features = list() for id_pair in id_more_pairs: if not id_pair in existing_more_pairs: # Avoid duplicated pairs in the DB # Create feature feature = QgsVectorLayerUtils().createFeature( layers[db.names.MORE_BFS_T]) feature.setAttribute(db.names.MORE_BFS_T_LC_PLOT_F, id_pair[0]) feature.setAttribute(db.names.MORE_BFS_T_LC_BOUNDARY_F, id_pair[1]) features.append(feature) layers[db.names.MORE_BFS_T].addFeatures(features) layers[db.names.MORE_BFS_T].commitChanges() self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database." ).format(len(features), len(id_more_pairs), db.names.MORE_BFS_T, len(id_more_pairs) - len(features), len(id_more_pairs))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "No pairs id_boundary-id_plot found for '{}' table."). format(db.names.MORE_BFS_T))
def __init__(self): self.app = AppInterface() self.logger = Logger() self.geometry = GeometryUtils() self.quality_rules_manager = QualityRuleManager()
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) GeometryUtils.create_spatial_index(boundary_nodes_layer) # 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 __prepare_layers(self): """ Get layers from DB and prepare snapped layers for all rules """ self.logger.info( __name__, QCoreApplication.translate("QualityRuleLayerManager", "Preparing layers...")) # First go for ladm-col layers ladm_layers = dict() for rule_key, rule_layers_config in self.__quality_rule_layers_config.items( ): if rule_key in self.__rule_keys: # Only get selected rules' layers for layer_name in rule_layers_config[ QUALITY_RULE_LADM_COL_LAYERS]: ladm_layers[layer_name] = None self.logger.debug( __name__, QCoreApplication.translate("QualityRuleLayerManager", "Getting {} LADM-COL layers...").format( len(ladm_layers))) self.app.core.get_layers(self.__db, ladm_layers, load=True) if ladm_layers is None: # If there are errors with get_layers, ladm_layers is None self.logger.critical( __name__, QCoreApplication.translate( "QualityRuleLayerManager", "Couldn't finish preparing required layers!")) return False for name, layer in ladm_layers.items(): if layer.isSpatial(): GeometryUtils.create_spatial_index( layer) # To improve performance of quality rules # If tolerance > 0, prepare adjusted layers # We create an adjusted_layers dict to override ladm_layers per rule. # For that, we need to read the config and, if not yet calculated, # adjust the layers and store them in temporary cache. # {rule_key: {layer_name: layer}}, because each rule might need # different adjustments for the same layer, compared to other rules adjusted_layers = {rule_key: dict() for rule_key in self.__rule_keys} if self.__tolerance: self.logger.debug( __name__, QCoreApplication.translate( "QualityRuleLayerManager", "Tolerance > 0, adjusting layers...")) self.__adjusted_layers_cache = dict() # adjusted_layers_key: layer count_rules = 0 total_rules = len([ rk for rk in self.__rule_keys if rk in self.__quality_rule_layers_config ]) with ProcessWithStatus( QCoreApplication.translate( "QualityRuleLayerManager", "Preparing tolerance on layers...")): for rule_key, rule_layers_config in self.__quality_rule_layers_config.items( ): if rule_key in self.__rule_keys: # Only get selected rules' layers count_rules += 1 self.logger.status( QCoreApplication.translate( "QualityRuleLayerManager", "Preparing tolerance on layers... ({} out of {})" ).format(count_rules, total_rules)) if QUALITY_RULE_ADJUSTED_LAYERS in rule_layers_config: for layer_name, snap_config in rule_layers_config[ QUALITY_RULE_ADJUSTED_LAYERS].items(): # Read from config input_name = snap_config[ ADJUSTED_INPUT_LAYER] # input layer name reference_name = snap_config[ ADJUSTED_REFERENCE_LAYER] # reference layer name behavior = snap_config[ ADJUSTED_BEHAVIOR] if ADJUSTED_BEHAVIOR in snap_config else None fix = snap_config[ FIX_ADJUSTED_LAYER] if FIX_ADJUSTED_LAYER in snap_config else False add_topological_points = snap_config[ ADJUSTED_TOPOLOGICAL_POINTS] if ADJUSTED_TOPOLOGICAL_POINTS in snap_config else False # Get input and reference layers. Note that they could be adjusted layers and in that # case they would have a composed name (see get_key_for_quality_rule_adjusted_layer()) input = self.__adjusted_layers_cache[ input_name] if input_name in self.__adjusted_layers_cache else ladm_layers[ input_name] reference = self.__adjusted_layers_cache[ reference_name] if reference_name in self.__adjusted_layers_cache else ladm_layers[ reference_name] # Try to reuse if already calculated! adjusted_layers_key = get_key_for_quality_rule_adjusted_layer( input_name, reference_name, fix) if adjusted_layers_key not in self.__adjusted_layers_cache: self.__adjusted_layers_cache[ adjusted_layers_key] = self.app.core.adjust_layer( input, reference, self.__tolerance, behavior, fix, add_topological_points) adjusted_layers[rule_key][ layer_name] = self.__adjusted_layers_cache[ adjusted_layers_key] self.logger.debug( __name__, QCoreApplication.translate("QualityRuleLayerManager", "Layers adjusted...")) # Now that we have both ladm_layers and adjusted_layers, use them # in a single member dict of layers per rule (preserving original LADM-COL layers) self.__layers = { rule_key: { QUALITY_RULE_LAYERS: dict(), QUALITY_RULE_LADM_COL_LAYERS: dict() } for rule_key in self.__rule_keys } for rule_key, rule_layers_config in self.__quality_rule_layers_config.items( ): if rule_key in self.__rule_keys: # Only get selected rules' layers for layer_name in rule_layers_config[ QUALITY_RULE_LADM_COL_LAYERS]: # Fill both subdicts # In LADM-COL layers we send all original layers self.__layers[rule_key][QUALITY_RULE_LADM_COL_LAYERS][ layer_name] = ladm_layers[ layer_name] if layer_name in ladm_layers else None # In QR_Layers we store the best layer we have available (preferring adjusted over ladm-col) if layer_name in adjusted_layers[rule_key]: self.__layers[rule_key][QUALITY_RULE_LAYERS][ layer_name] = adjusted_layers[rule_key][layer_name] elif layer_name in ladm_layers: self.__layers[rule_key][QUALITY_RULE_LAYERS][ layer_name] = ladm_layers[layer_name] # Let QRs know if they should switch between dicts looking for original geometries self.__layers[rule_key][HAS_ADJUSTED_LAYERS] = bool( self.__tolerance) # Register adjusted layers so that Processing can properly find them if self.__adjusted_layers_cache: load_to_registry = [ layer for key, layer in self.__adjusted_layers_cache.items() if layer is not None ] self.logger.debug( __name__, "{} adjusted layers loaded to QGIS registry...".format( len(load_to_registry))) QgsProject.instance().addMapLayers(load_to_registry, False) return True
def _validate(self, db, db_qr, layer_dict, tolerance, **kwargs): self.progress_changed.emit(5) pre_res, pre_obj = self._check_prerrequisite_layers(layer_dict) if not pre_res: return pre_obj boundary_point_layer = self._get_layer(layer_dict, db.names.LC_BOUNDARY_POINT_T) boundary_layer = self._get_layer(layer_dict, db.names.LC_BOUNDARY_T) point_bfs_layer = self._get_layer(layer_dict, db.names.POINT_BFS_T) id_field = db.names.T_ID_F 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() } # 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) } result = GeometryUtils.get_boundary_points_not_covered_by_boundary_nodes( db, boundary_point_layer, boundary_layer, point_bfs_layer, id_field) boundary_point_without_boundary_node = result[0] no_register_point_bfs = result[1] duplicate_in_point_bfs = result[2] self.progress_changed.emit(70) 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_e01 = {'geometries': list(), 'data': list()} errors_e02 = {'geometries': list(), 'data': list()} errors_e03 = {'geometries': list(), 'data': list()} # boundary point without boundary node if boundary_point_without_boundary_node: for item in boundary_point_without_boundary_node: boundary_point_id = item # boundary_point_id errors_e01['geometries'].append( dict_boundary_point[boundary_point_id].geometry()) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] [dict_uuid_boundary_point.get(boundary_point_id)], None, None, self._errors[self._ERROR_01], error_state ] errors_e01['data'].append(error_data) self._save_errors(db_qr, self._ERROR_01, errors_e01) # No registered in point_bfs if no_register_point_bfs: 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 errors_e02['geometries'].append( dict_boundary_point[boundary_point_id].geometry()) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] [dict_uuid_boundary_point.get(boundary_point_id)], [dict_uuid_boundary.get(boundary_id)], None, self._errors[self._ERROR_02], error_state ] errors_e02['data'].append(error_data) self._save_errors(db_qr, self._ERROR_02, errors_e02) # Duplicate in point_bfs if duplicate_in_point_bfs: 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 errors_e03['geometries'].append( dict_boundary_point[boundary_point_id].geometry()) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] [dict_uuid_boundary_point.get(boundary_point_id)], [dict_uuid_boundary.get(boundary_id)], None, self._errors[self._ERROR_03], error_state ] errors_e03['data'].append(error_data) self._save_errors(db_qr, self._ERROR_03, errors_e03) self.progress_changed.emit(100) count = len(errors_e01['data']) + len(errors_e02['data']) + len( errors_e03['data']) if count > 0: res_type = EnumQualityRuleResult.ERRORS msg = QCoreApplication.translate( "QualityRules", "{} boundary points not covered by boundary nodes were found." ).format(count) else: res_type = EnumQualityRuleResult.SUCCESS msg = QCoreApplication.translate( "QualityRules", "All boundary points are covered by boundary nodes!") self.progress_changed.emit(100) return QualityRuleExecutionResult(res_type, msg, count)
def __init__(self, iface): QObject.__init__(self) self.iface = iface self.logger = Logger() self.app = AppInterface() self.geometry = GeometryUtils()
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)
class LineQualityRules: def __init__(self): self.app = AppInterface() self.logger = Logger() self.geometry = GeometryUtils() self.quality_rules_manager = QualityRuleManager() def check_overlaps_in_boundaries(self, db, layer_dict): rule = self.quality_rules_manager.get_quality_rule(EnumQualityRule.Line.OVERLAPS_IN_BOUNDARIES) boundary_layer = list(layer_dict[QUALITY_RULE_LAYERS].values())[0] if layer_dict[QUALITY_RULE_LAYERS] else None if not boundary_layer: return QCoreApplication.translate("LineQualityRules", "'Boundary' layer not found!"), Qgis.Critical # Create error layers structure error_point_layer = QgsVectorLayer("MultiPoint?crs={}".format(boundary_layer.sourceCrs().authid()), "{} (puntos)".format(rule.error_table_name), "memory") data_provider = error_point_layer.dataProvider() data_provider.addAttributes(rule.error_table_fields) error_point_layer.updateFields() error_line_layer = QgsVectorLayer("MultiLineString?crs={}".format(boundary_layer.sourceCrs().authid()), "{} (líneas)".format(rule.error_table_name), "memory") data_provider = error_line_layer.dataProvider() data_provider.addAttributes(rule.error_table_fields) error_line_layer.updateFields() if boundary_layer: overlapping = self.geometry.get_overlapping_lines(boundary_layer) if overlapping is None: return (QCoreApplication.translate("LineQualityRules", "There are no boundaries to check for overlaps!"), Qgis.Warning) else: points_intersected = overlapping['native:saveselectedfeatures_3:Intersected_Points'] lines_intersected = overlapping['native:saveselectedfeatures_2:Intersected_Lines'] if isinstance(points_intersected, QgsVectorLayer): point_features = list() if points_intersected.featureCount() > 0: for feature in points_intersected.getFeatures(): new_feature = QgsVectorLayerUtils().createFeature( error_point_layer, feature.geometry(), {0: feature[db.names.T_ILI_TID_F], 1: feature["{}_2".format(db.names.T_ILI_TID_F)], 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200101), 3: QUALITY_RULE_ERROR_CODE_E200101}) point_features.append(new_feature) error_point_layer.dataProvider().addFeatures(point_features) if isinstance(lines_intersected, QgsVectorLayer): line_features = list() if lines_intersected.featureCount() > 0: for feature in lines_intersected.getFeatures(): new_feature = QgsVectorLayerUtils().createFeature( error_line_layer, feature.geometry(), {0: feature[db.names.T_ILI_TID_F], 1: feature["{}_2".format(db.names.T_ILI_TID_F)], 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200101), 3: QUALITY_RULE_ERROR_CODE_E200101}) line_features.append(new_feature) error_line_layer.dataProvider().addFeatures(line_features) if error_point_layer.featureCount() == 0 and error_line_layer.featureCount() == 0: return QCoreApplication.translate("LineQualityRules", "There are no overlapping boundaries."), Qgis.Success else: msg = "" if error_point_layer.featureCount() and error_line_layer.featureCount(): self.app.gui.add_error_layer(db, error_point_layer) self.app.gui.add_error_layer(db, error_line_layer) msg = QCoreApplication.translate("LineQualityRules", "Two memory layers with overlapping boundaries ({} point intersections and {} line intersections) have been added to the map.").format(error_point_layer.featureCount(), error_line_layer.featureCount()) elif error_point_layer.featureCount(): self.app.gui.add_error_layer(db, error_point_layer) msg = QCoreApplication.translate("LineQualityRules", "A memory layer with {} overlapping boundaries (point intersections) has been added to the map.").format(error_point_layer.featureCount()) elif error_line_layer.featureCount(): self.app.gui.add_error_layer(db, error_line_layer) msg = QCoreApplication.translate("LineQualityRules", "A memory layer with {} overlapping boundaries (line intersections) has been added to the map.").format(error_line_layer.featureCount()) return msg, Qgis.Critical def check_boundaries_are_not_split(self, db, layer_dict): """ An split boundary is an incomplete boundary because it is connected to a single boundary and therefore, they don't represent a change in boundary (colindancia). """ rule = self.quality_rules_manager.get_quality_rule(EnumQualityRule.Line.BOUNDARIES_ARE_NOT_SPLIT) features = [] boundary_layer = list(layer_dict[QUALITY_RULE_LAYERS].values())[0] if layer_dict[QUALITY_RULE_LAYERS] else None if not boundary_layer: return QCoreApplication.translate("LineQualityRules", "'Boundary' layer not found!"), Qgis.Critical if boundary_layer.featureCount() == 0: return (QCoreApplication.translate("LineQualityRules", "There are no boundaries to check 'boundaries should not be split'!"), Qgis.Warning) else: wrong_boundaries = self.geometry.get_boundaries_connected_to_single_boundary(db.names, boundary_layer) if wrong_boundaries is None: return (QCoreApplication.translate("LineQualityRules", "There are no wrong boundaries!"), Qgis.Success) else: error_layer = QgsVectorLayer("LineString?crs={}".format(boundary_layer.sourceCrs().authid()), rule.error_table_name, "memory") pr = error_layer.dataProvider() pr.addAttributes(rule.error_table_fields) error_layer.updateFields() # Get geometries from LADM-COL original layer. If not HAS_ADJUSTED_LAYERS, it's OK to get # them from wrong_boundaries list, as it would have been created from original geoms if layer_dict[HAS_ADJUSTED_LAYERS]: ladm_col_boundary_layer = list(layer_dict[QUALITY_RULE_LADM_COL_LAYERS].values())[0] if layer_dict[QUALITY_RULE_LADM_COL_LAYERS] else boundary_layer wrong_tids = ",".join([str(f[db.names.T_ID_F]) for f in wrong_boundaries]) # Use t_id, which is indexed geoms = {f[db.names.T_ILI_TID_F]:f.geometry() for f in ladm_col_boundary_layer.getFeatures("{} IN ({})".format(db.names.T_ID_F, wrong_tids))} else: geoms = {f[db.names.T_ILI_TID_F]: f.geometry() for f in wrong_boundaries} for feature in wrong_boundaries: new_feature = QgsVectorLayerUtils().createFeature(error_layer, geoms[feature[db.names.T_ILI_TID_F]], {0: feature[db.names.T_ILI_TID_F], 1: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200201), 2: QUALITY_RULE_ERROR_CODE_E200201}) 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("LineQualityRules", "A memory layer with {} wrong boundaries has been added to the map!").format(added_layer.featureCount()), Qgis.Critical) else: return (QCoreApplication.translate("LineQualityRules", "There are no wrong boundaries."), Qgis.Success) def check_boundaries_covered_by_plots(self, db, layer_dict): rule = self.quality_rules_manager.get_quality_rule(EnumQualityRule.Line.BOUNDARIES_COVERED_BY_PLOTS) layers = layer_dict[QUALITY_RULE_LAYERS] if not layers: return QCoreApplication.translate("LineQualityRules", "At least one required layer (plot, boundary, more BFS, less BFS) was not found!"), Qgis.Critical # validate data if layers[db.names.LC_BOUNDARY_T].featureCount() == 0: return (QCoreApplication.translate("LineQualityRules", "There are no boundaries to check 'boundaries should be covered by plots'."), Qgis.Warning) else: error_layer = QgsVectorLayer("MultiLineString?crs={}".format(layers[db.names.LC_BOUNDARY_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_features_not_covered_by_plots(db, layers[db.names.LC_PLOT_T], layers[db.names.LC_BOUNDARY_T], layers[db.names.MORE_BFS_T], layers[db.names.LESS_BFS_T], error_layer, db.names.T_ID_F) if features: error_layer.dataProvider().addFeatures(features) added_layer = self.app.gui.add_error_layer(db, error_layer) return (QCoreApplication.translate("LineQualityRules", "A memory layer with {} boundaries not covered by plots has been added to the map!").format(added_layer.featureCount()), Qgis.Critical) else: return (QCoreApplication.translate("LineQualityRules", "All boundaries are covered by plots!"), Qgis.Success) def check_boundary_nodes_covered_by_boundary_points(self, db, layer_dict): rule = self.quality_rules_manager.get_quality_rule(EnumQualityRule.Line.BOUNDARY_NODES_COVERED_BY_BOUNDARY_POINTS) layers = layer_dict[QUALITY_RULE_LAYERS] if not layers: return QCoreApplication.translate("LineQualityRules", "At least one required layer (boundary point, boundary, point BFS) was not found!"), Qgis.Critical elif layers[db.names.LC_BOUNDARY_T].featureCount() == 0: return (QCoreApplication.translate("LineQualityRules", "There are no boundaries to check 'missing boundary points in boundaries'."), Qgis.Warning) else: error_layer = QgsVectorLayer("Point?crs={}".format(layers[db.names.LC_BOUNDARY_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_nodes_features_not_covered_by_boundary_points(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) data_provider.addFeatures(features) if error_layer.featureCount() > 0: added_layer = self.app.gui.add_error_layer(db, error_layer) return (QCoreApplication.translate("LineQualityRules", "A memory layer with {} boundary vertices with no associated boundary points or with boundary points wrongly registered in the PointBFS table been added to the map!").format(added_layer.featureCount()), Qgis.Critical) else: return (QCoreApplication.translate("LineQualityRules", "There are no missing boundary points in boundaries."), Qgis.Success) def check_dangles_in_boundaries(self, db, layer_dict): rule = self.quality_rules_manager.get_quality_rule(EnumQualityRule.Line.DANGLES_IN_BOUNDARIES) boundary_layer = list(layer_dict[QUALITY_RULE_LAYERS].values())[0] if layer_dict[QUALITY_RULE_LAYERS] else None if not boundary_layer: return QCoreApplication.translate("LineQualityRules", "'Boundary' layer was not found!"), Qgis.Critical if boundary_layer.featureCount() == 0: return (QCoreApplication.translate("LineQualityRules", "There are no boundaries to check for dangles."), Qgis.Warning) else: error_layer = QgsVectorLayer("Point?crs={}".format(boundary_layer.sourceCrs().authid()), rule.error_table_name, "memory") pr = error_layer.dataProvider() pr.addAttributes(rule.error_table_fields) error_layer.updateFields() end_points, dangle_ids = self.geometry.get_dangle_ids(boundary_layer) new_features = [] for dangle in end_points.getFeatures(dangle_ids): new_feature = QgsVectorLayerUtils().createFeature(end_points, dangle.geometry(), {0: dangle[db.names.T_ILI_TID_F], 1: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200501), 2: QUALITY_RULE_ERROR_CODE_E200501}) new_features.append(new_feature) error_layer.dataProvider().addFeatures(new_features) if error_layer.featureCount() > 0: added_layer = self.app.gui.add_error_layer(db, error_layer) return (QCoreApplication.translate("LineQualityRules", "A memory layer with {} boundary dangles has been added to the map!").format(added_layer.featureCount()), Qgis.Critical) else: return (QCoreApplication.translate("LineQualityRules", "Boundaries have no dangles!"), Qgis.Success) # UTILITY METHODS def get_boundary_nodes_features_not_covered_by_boundary_points(self, db, boundary_point_layer, boundary_layer, point_bfs_layer, error_layer, id_field): dict_uuid_boundary_point = get_uuid_dict(boundary_point_layer, db.names, db.names.T_ID_F) dict_uuid_boundary = get_uuid_dict(boundary_layer, db.names, db.names.T_ID_F) 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_unique_layer = QgsVectorLayer("Point?crs={}".format(boundary_layer.sourceCrs().authid()), 'unique boundary nodes', "memory") data_provider = boundary_nodes_unique_layer.dataProvider() data_provider.addAttributes([QgsField(id_field, QVariant.Int)]) boundary_nodes_unique_layer.updateFields() id_field_idx = tmp_boundary_nodes_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) filter_fs = list() fs = list() 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) boundary_nodes_unique_layer.dataProvider().addFeatures(fs) # Create an autoincremental field to have an identifying field boundary_nodes_layer = processing.run("native:addautoincrementalfield", {'INPUT': boundary_nodes_unique_layer, 'FIELD_NAME': 'AUTO', 'START': 0, 'GROUP_FIELDS': [], 'SORT_EXPRESSION': '', 'SORT_ASCENDING': True, 'SORT_NULLS_FIRST': False, 'OUTPUT': 'memory:'})['OUTPUT'] # Spatial Join between boundary_nodes and boundary_points spatial_join_layer = processing.run("qgis:joinattributesbylocation", {'INPUT': boundary_nodes_layer, 'JOIN': boundary_point_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_nodes_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_boundary_nodes = {feature['AUTO']: feature for feature in boundary_nodes_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)] list_spatial_join_boundary_node_boundary_point = [{'boundary_point_id': feature[id_field + '_2'], 'boundary_node_id': feature['AUTO']} for feature in spatial_join_layer.getFeatures()] boundary_node_without_boundary_point = list() no_register_point_bfs = list() duplicate_in_point_bfs = list() # point_bfs topology check for item_sj in list_spatial_join_boundary_node_boundary_point: boundary_node_id = item_sj['boundary_node_id'] boundary_point_id = item_sj['boundary_point_id'] if boundary_point_id != NULL: boundary_id = dict_boundary_nodes[boundary_node_id][id_field] # get boundary id item_sj_check = {'boundary_point_id': boundary_point_id, 'boundary_id': boundary_id} # dict to check if item_sj_check not in list_point_bfs: no_register_point_bfs.append((boundary_point_id, boundary_node_id)) # no registered in point bfs elif list_point_bfs.count(item_sj_check) > 1: duplicate_in_point_bfs.append((boundary_point_id, boundary_node_id)) # duplicate in point bfs else: boundary_node_without_boundary_point.append(boundary_node_id) # boundary node without boundary point features = list() # boundary node without boundary point if boundary_node_without_boundary_point is not None: for item in boundary_node_without_boundary_point: boundary_node_id = item boundary_node_geom = dict_boundary_nodes[boundary_node_id].geometry() boundary_id = dict_boundary_nodes[boundary_node_id][id_field] # get boundary id new_feature = QgsVectorLayerUtils().createFeature(error_layer, boundary_node_geom, {0: dict_uuid_boundary.get(boundary_id), 1: None, 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200401), 3: QUALITY_RULE_ERROR_CODE_E200401}) 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_node_id = error_duplicate[1] boundary_node_geom = dict_boundary_nodes[boundary_node_id].geometry() boundary_id = dict_boundary_nodes[boundary_node_id][id_field] # get boundary id new_feature = QgsVectorLayerUtils().createFeature(error_layer, boundary_node_geom, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_boundary_point.get(boundary_point_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200403), 3: QUALITY_RULE_ERROR_CODE_E200403}) 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_node_id = error_no_register[1] boundary_node_geom = dict_boundary_nodes[boundary_node_id].geometry() boundary_id = dict_boundary_nodes[boundary_node_id][id_field] # get boundary id new_feature = QgsVectorLayerUtils().createFeature(error_layer, boundary_node_geom, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_boundary_point.get(boundary_point_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200402), 3: QUALITY_RULE_ERROR_CODE_E200402}) features.append(new_feature) return features def get_boundary_features_not_covered_by_plots(self, db, plot_layer, boundary_layer, more_bfs_layer, less_layer, error_layer, id_field): """ Return all boundary features that have errors when checking if they are covered by plots. This takes into account both geometric and alphanumeric (topology table) errors. """ dict_uuid_plot = get_uuid_dict(plot_layer, db.names, db.names.T_ID_F) dict_uuid_boundary = get_uuid_dict(boundary_layer, db.names, db.names.T_ID_F) plot_as_lines_layer = processing.run("ladm_col:polygonstolines", {'INPUT': plot_layer, 'OUTPUT': 'memory:'})['OUTPUT'] # create dict with layer data id_field_idx = plot_as_lines_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_plot_as_lines = {feature[id_field]: feature for feature in plot_as_lines_layer.getFeatures(request)} id_field_idx = boundary_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_boundary = {feature[id_field]: feature for feature in boundary_layer.getFeatures(request)} exp_more = '"{}" is not null and "{}" is not null'.format(db.names.MORE_BFS_T_LC_BOUNDARY_F, db.names.MORE_BFS_T_LC_PLOT_F) list_more_bfs = [{'plot_id': feature[db.names.MORE_BFS_T_LC_PLOT_F], 'boundary_id': feature[db.names.MORE_BFS_T_LC_BOUNDARY_F]} for feature in more_bfs_layer.getFeatures(exp_more)] exp_less = '"{}" is not null and "{}" is not null'.format(db.names.LESS_BFS_T_LC_BOUNDARY_F, db.names.LESS_BFS_T_LC_PLOT_F) list_less = [{'plot_id': feature[db.names.LESS_BFS_T_LC_PLOT_F], 'boundary_id': feature[db.names.LESS_BFS_T_LC_BOUNDARY_F]} for feature in less_layer.getFeatures(exp_less)] tmp_inner_rings_layer = self.geometry.get_inner_rings_layer(db.names, plot_layer, db.names.T_ID_F) inner_rings_layer = processing.run("native:addautoincrementalfield", {'INPUT': tmp_inner_rings_layer, 'FIELD_NAME': 'AUTO', 'START': 0, 'GROUP_FIELDS': [], 'SORT_EXPRESSION': '', 'SORT_ASCENDING': True, 'SORT_NULLS_FIRST': False, 'OUTPUT': 'memory:'})['OUTPUT'] id_field_idx = inner_rings_layer.fields().indexFromName(id_field) auto_idx = inner_rings_layer.fields().indexFromName('AUTO') request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx, auto_idx]) dict_inner_rings = {'{}-{}'.format(feature[id_field], feature['AUTO']): feature for feature in inner_rings_layer.getFeatures(request)} # spatial joins between boundary and inner rings # we use as input layer the rings because it is the existing information # in the terrain polygons and filters better because they are less records spatial_join_inner_rings_boundary_layer = processing.run("qgis:joinattributesbylocation", {'INPUT': boundary_layer, 'JOIN': inner_rings_layer, 'PREDICATE': [0], # Intersects 'JOIN_FIELDS': [id_field, 'AUTO'], 'METHOD': 0, 'DISCARD_NONMATCHING': True, 'PREFIX': '', 'OUTPUT': 'memory:'})['OUTPUT'] list_spatial_join_boundary_inner_rings = list() list_spatial_join_boundary_plot_ring = list() for feature in spatial_join_inner_rings_boundary_layer.getFeatures(): # The id field has the same name for both layers # This list is only used to check plot's inner rings without boundaries list_spatial_join_boundary_inner_rings.append({'plot_ring_id': '{}-{}'.format(feature[id_field + '_2'], feature['AUTO']), 'boundary_id': feature[id_field]}) # list create for filter inner rings from spatial join with between plot and boundary list_spatial_join_boundary_plot_ring.append({'plot_id': feature[id_field + '_2'], 'boundary_id': feature[id_field]}) # Spatial join between boundary and plots as lines spatial_join_boundary_plot_layer = processing.run("qgis:joinattributesbylocation", {'INPUT': boundary_layer, 'JOIN': plot_as_lines_layer, 'PREDICATE': [0], 'JOIN_FIELDS': [id_field], 'METHOD': 0, 'DISCARD_NONMATCHING': True, 'PREFIX': '', 'OUTPUT': 'memory:'})['OUTPUT'] # The id field has the same name for both layers list_spatial_join_boundary_plot = [{'plot_id': feature[id_field + '_2'], 'boundary_id': feature[id_field]} for feature in spatial_join_boundary_plot_layer.getFeatures()] ##################################################### # Validation of geometric errors ##################################################### # Identify plots with geometry problems and remove coincidence in spatial join between plot as line and boundary # and inner_rings and boundary. If the geometry fails, there is no need to check further topological rules for # plots errors_boundary_plot_diffs = self.geometry.difference_boundary_plot(db.names, boundary_layer, plot_as_lines_layer, db.names.T_ID_F) for error_diff in errors_boundary_plot_diffs: boundary_id = error_diff['id'] # All boundaries with geometric errors are eliminated. It is not necessary check more # in spatial join between boundary and plot as line for item_sj in list_spatial_join_boundary_plot.copy(): if item_sj['boundary_id'] == boundary_id: list_spatial_join_boundary_plot.remove(item_sj) # All boundaries with geometric errors are eliminated. It is not necessary check more # in spatial join between boundary and inner_rings for item_sj in list_spatial_join_boundary_inner_rings.copy(): if item_sj['boundary_id'] == boundary_id: list_spatial_join_boundary_inner_rings.remove(item_sj) ###################################################### # Validation of errors in alphanumeric topology tables ###################################################### # start validation in alphanumeric topology tables for more_bfs # remove spatial join intersection with geometries that no contain lines. Because it is not necessary to check for item_sj in list_spatial_join_boundary_plot.copy(): boundary_id = item_sj['boundary_id'] plot_id = item_sj['plot_id'] if item_sj in list_spatial_join_boundary_plot_ring: # it is removed because it is registered in the spatial join between rings and boundaries # and it shouldn't be registered in the topology table of more_bfs list_spatial_join_boundary_plot.remove(item_sj) else: boundary_geom = dict_boundary[boundary_id].geometry() plot_geom = dict_plot_as_lines[plot_id].geometry() intersection = boundary_geom.intersection(plot_geom) if not intersection.isEmpty(): if intersection.type() != QgsWkbTypes.LineGeometry: if intersection.type() == QgsWkbTypes.UnknownGeometry: has_line = False for part in intersection.asGeometryCollection(): if part.isMultipart(): for i in range(part.numGeometries()): if QgsWkbTypes.geometryType( part.geometryN(i).wkbType()) == QgsWkbTypes.LineGeometry: has_line = True break else: if part.type() == QgsWkbTypes.LineGeometry: has_line = True break if not has_line: # Remove point intersections plot-boundary list_spatial_join_boundary_plot.remove(item_sj) else: list_spatial_join_boundary_plot.remove(item_sj) # Check relation between plot and boundary not registered in more_bfs errors_not_in_more_bfs = list() errors_duplicate_in_more_bfs = list() for item_sj_bp in list_spatial_join_boundary_plot: count_more_bfs = list_more_bfs.count(item_sj_bp) if count_more_bfs > 1: errors_duplicate_in_more_bfs.append((item_sj_bp['plot_id'], item_sj_bp['boundary_id'])) elif count_more_bfs == 0: # Check for the special case of two contiguous plots, one of them covers the common boundary, but the # other one does not! This should be still a geometry error but is not captured by the code above. Only # in this point of the whole checks we can validate between the individual boundary and the individual # plot. boundary_geom = dict_boundary[item_sj_bp['boundary_id']].geometry() plot_geom = dict_plot_as_lines[item_sj_bp['plot_id']].geometry() intersection = boundary_geom.intersection(plot_geom) if not intersection.isEmpty(): if intersection.isGeosEqual(boundary_geom): errors_not_in_more_bfs.append((item_sj_bp['plot_id'], item_sj_bp['boundary_id'])) else: errors_boundary_plot_diffs.append({ 'id': item_sj_bp['boundary_id'], 'id_plot': item_sj_bp['plot_id'], 'geometry': boundary_geom}) # finalize validation in more_bfs table # start validation in less table errors_not_in_less = list() errors_duplicate_in_less = list() # start validation in alphanumeric topology tables for less # remove spatial join intersection with geometries that do not contain lines. # Because it is not necessary to check topology register for inner_ring in list_spatial_join_boundary_inner_rings: boundary_id = inner_ring['boundary_id'] plot_ring_id = inner_ring['plot_ring_id'] boundary_geom = dict_boundary[boundary_id].geometry() inner_ring_geom = dict_inner_rings[plot_ring_id].geometry() # check intersections difference to line, we check that collections do not have lines parts intersection = boundary_geom.intersection(inner_ring_geom) has_line = False if not intersection.isEmpty(): if intersection.type() != QgsWkbTypes.LineGeometry: if intersection.type() == QgsWkbTypes.UnknownGeometry: for part in intersection.asGeometryCollection(): if part.isMultipart(): for i in range(part.numGeometries()): if QgsWkbTypes.geometryType(part.geometryN(i).wkbType()) == QgsWkbTypes.LineGeometry: has_line = True break else: if part.type() == QgsWkbTypes.LineGeometry: has_line = True break else: has_line = True if has_line: tmp_dict_plot_boundary = {'plot_id': int(plot_ring_id.split('-')[0]), 'boundary_id': boundary_id} count_less = list_less.count(tmp_dict_plot_boundary) if count_less >1: errors_duplicate_in_less.append((plot_ring_id, boundary_id)) # duplicate in less table elif count_less == 0: errors_not_in_less.append((plot_ring_id, boundary_id)) # no registered less table # finalize validation for less table features = list() # boundary not covered by plot for boundary_plot_diff in errors_boundary_plot_diffs: boundary_id = boundary_plot_diff['id'] boundary_geom = boundary_plot_diff['geometry'] plot_id = boundary_plot_diff['id_plot'] if 'id_plot' in boundary_plot_diff else None new_feature = QgsVectorLayerUtils().createFeature(error_layer, boundary_geom, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200301), 3: QUALITY_RULE_ERROR_CODE_E200301}) features.append(new_feature) # No registered more bfs if errors_not_in_more_bfs: for error_more_bfs in set(errors_not_in_more_bfs): plot_id = error_more_bfs[0] # plot_id boundary_id = error_more_bfs[1] # boundary_id geom_boundary = dict_boundary[boundary_id].geometry() new_feature = QgsVectorLayerUtils().createFeature(error_layer, geom_boundary, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200304), 3: QUALITY_RULE_ERROR_CODE_E200304}) features.append(new_feature) # Duplicate in more bfs if errors_duplicate_in_more_bfs: for error_more_bfs in set(errors_duplicate_in_more_bfs): plot_id = error_more_bfs[0] # plot_id boundary_id = error_more_bfs[1] # boundary_id geom_boundary = dict_boundary[boundary_id].geometry() new_feature = QgsVectorLayerUtils().createFeature(error_layer, geom_boundary, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200302), 3: QUALITY_RULE_ERROR_CODE_E200302}) features.append(new_feature) # No registered less if errors_not_in_less: for error_less in set(errors_not_in_less): plot_ring_id = error_less[0] # plot_ring_id plot_id = int(plot_ring_id.split('-')[0]) # plot_id boundary_id = error_less[1] # boundary_id geom_ring = dict_inner_rings[plot_ring_id].geometry() new_feature = QgsVectorLayerUtils().createFeature(error_layer, geom_ring, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200305), 3: QUALITY_RULE_ERROR_CODE_E200305}) features.append(new_feature) # Duplicate in less if errors_duplicate_in_less: for error_less in set(errors_duplicate_in_less): plot_ring_id = error_less[0] # plot_ring_id plot_id = int(plot_ring_id.split('-')[0]) # plot_id boundary_id = error_less[1] # boundary_id geom_ring = dict_inner_rings[plot_ring_id].geometry() new_feature = QgsVectorLayerUtils().createFeature(error_layer, geom_ring, {0: dict_uuid_boundary.get(boundary_id), 1: dict_uuid_plot.get(plot_id), 2: self.quality_rules_manager.get_error_message(QUALITY_RULE_ERROR_CODE_E200303), 3: QUALITY_RULE_ERROR_CODE_E200303}) features.append(new_feature) return features
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)
def setUpClass(cls): cls.app = AppInterface() cls.geometry = GeometryUtils()
class ToolBar(QObject): def __init__(self, iface, qgis_utils): QObject.__init__(self) self.iface = iface self.qgis_utils = qgis_utils self.logger = Logger() self.geometry = GeometryUtils() def build_boundary(self, db): QgsProject.instance().setAutoTransaction(False) layer = self.qgis_utils.get_layer_from_layer_tree( db, db.names.OP_BOUNDARY_T) use_selection = True if layer is None: self.logger.message_with_button_load_layer_emitted.emit( QCoreApplication.translate( "ToolBar", "First load the layer {} into QGIS!").format( db.names.OP_BOUNDARY_T), QCoreApplication.translate("ToolBar", "Load layer {} now").format( db.names.OP_BOUNDARY_T), db.names.OP_BOUNDARY_T, Qgis.Warning) return else: if layer.selectedFeatureCount() == 0: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are no selected boundaries. Do you want to use all the {} boundaries in the database?" ).format(layer.featureCount()), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = False elif reply == QMessageBox.Cancel: self.logger.warning_msg( __name__, QCoreApplication.translate( "ToolBar", "First select at least one boundary!")) return if use_selection: new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_selected_boundaries( db.names, layer, db.names.T_ID_F) num_boundaries = layer.selectedFeatureCount() else: new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_boundaries( layer, db.names.T_ID_F) num_boundaries = layer.featureCount() if len(new_boundary_geoms) > 0: layer.startEditing( ) # Safe, even if layer is already on editing state # the boundaries that are to be replaced are removed layer.deleteFeatures(boundaries_to_del_ids) # Create features based on segment geometries new_fix_boundary_features = list() for boundary_geom in new_boundary_geoms: feature = QgsVectorLayerUtils().createFeature( layer, boundary_geom) # TODO: Remove when local id and working space are defined feature.setAttribute(db.names.OID_T_LOCAL_ID_F, 1) feature.setAttribute(db.names.OID_T_NAMESPACE_F, db.names.OP_BOUNDARY_T) new_fix_boundary_features.append(feature) layer.addFeatures(new_fix_boundary_features) self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} feature(s) was(were) analyzed generating {} boundary(ies)!" ).format(num_boundaries, len(new_fix_boundary_features))) self.iface.mapCanvas().refresh() else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "There are no boundaries to build.")) def fill_topology_table_pointbfs(self, db, use_selection=True): layers = { db.names.OP_BOUNDARY_T: { 'name': db.names.OP_BOUNDARY_T, 'geometry': None, LAYER: None }, db.names.POINT_BFS_T: { 'name': db.names.POINT_BFS_T, 'geometry': None, LAYER: None }, db.names.OP_BOUNDARY_POINT_T: { 'name': db.names.OP_BOUNDARY_POINT_T, 'geometry': None, LAYER: None } } self.qgis_utils.get_layers(db, layers, load=True) if not layers: return None if use_selection: if layers[ db.names.OP_BOUNDARY_T][LAYER].selectedFeatureCount() == 0: if self.qgis_utils.get_layer_from_layer_tree( db, db.names.OP_BOUNDARY_T) is None: self.logger.message_with_button_load_layer_emitted.emit( QCoreApplication.translate( "ToolBar", "First load the layer {} into QGIS and select at least one boundary!" ).format(db.names.OP_BOUNDARY_T), QCoreApplication.translate("ToolBar", "Load layer {} now").format( db.names.OP_BOUNDARY_T), db.names.OP_BOUNDARY_T, Qgis.Warning) else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are no selected boundaries. Do you want to fill the '{}' table for all the {} boundaries in the database?" ).format( db.names.POINT_BFS_T, layers[ db.names.OP_BOUNDARY_T][LAYER].featureCount()), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = False elif reply == QMessageBox.Cancel: self.logger.warning_msg( __name__, QCoreApplication.translate( "ToolBar", "First select at least one boundary!")) return else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are {selected} boundaries selected. Do you want to fill the '{table}' table just for the selected boundaries?\n\nIf you say 'No', the '{table}' table will be filled for all boundaries in the database." ).format(selected=layers[db.names.OP_BOUNDARY_T] [LAYER].selectedFeatureCount(), table=db.names.POINT_BFS_T), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = True elif reply == QMessageBox.No: use_selection = False elif reply == QMessageBox.Cancel: return bfs_features = layers[db.names.POINT_BFS_T][LAYER].getFeatures() # Get unique pairs id_boundary-id_boundary_point existing_pairs = [ (bfs_feature[db.names.POINT_BFS_T_OP_BOUNDARY_F], bfs_feature[db.names.POINT_BFS_T_OP_BOUNDARY_POINT_F]) for bfs_feature in bfs_features ] existing_pairs = set(existing_pairs) id_pairs = self.geometry.get_pair_boundary_boundary_point( layers[db.names.OP_BOUNDARY_T][LAYER], layers[db.names.OP_BOUNDARY_POINT_T][LAYER], db.names.T_ID_F, use_selection=use_selection) if id_pairs: layers[db.names.POINT_BFS_T][LAYER].startEditing() features = list() for id_pair in id_pairs: if not id_pair in existing_pairs: # Avoid duplicated pairs in the DB # Create feature feature = QgsVectorLayerUtils().createFeature( layers[db.names.POINT_BFS_T][LAYER]) feature.setAttribute(db.names.POINT_BFS_T_OP_BOUNDARY_F, id_pair[0]) feature.setAttribute( db.names.POINT_BFS_T_OP_BOUNDARY_POINT_F, id_pair[1]) features.append(feature) layers[db.names.POINT_BFS_T][LAYER].addFeatures(features) layers[db.names.POINT_BFS_T][LAYER].commitChanges() self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} out of {} records were saved into {}! {} out of {} records already existed in the database." ).format(len(features), len(id_pairs), db.names.POINT_BFS_T, len(id_pairs) - len(features), len(id_pairs))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "No pairs id_boundary-id_boundary_point found.")) def fill_topology_tables_morebfs_less(self, db, use_selection=True): layers = { db.names.OP_PLOT_T: { 'name': db.names.OP_PLOT_T, 'geometry': QgsWkbTypes.PolygonGeometry, LAYER: None }, db.names.MORE_BFS_T: { 'name': db.names.MORE_BFS_T, 'geometry': None, LAYER: None }, db.names.LESS_BFS_T: { 'name': db.names.LESS_BFS_T, 'geometry': None, LAYER: None }, db.names.OP_BOUNDARY_T: { 'name': db.names.OP_BOUNDARY_T, 'geometry': None, LAYER: None } } self.qgis_utils.get_layers(db, layers, load=True) if not layers: return None if use_selection: if layers[db.names.OP_PLOT_T][LAYER].selectedFeatureCount() == 0: if self.qgis_utils.get_layer_from_layer_tree( db, db.names.OP_PLOT_T, geometry_type=QgsWkbTypes.PolygonGeometry) is None: self.logger.message_with_button_load_layer_emitted.emit( QCoreApplication.translate( "ToolBar", "First load the layer {} into QGIS and select at least one plot!" ).format(db.names.OP_PLOT_T), QCoreApplication.translate("ToolBar", "Load layer {} now").format( db.names.OP_PLOT_T), db.names.OP_PLOT_T, Qgis.Warning) else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are no selected plots. Do you want to fill the '{more}' and '{less}' tables for all the {all} plots in the database?" ).format(more=db.names.MORE_BFS_T, less=db.names.LESS_BFS_T, all=layers[db.names.OP_PLOT_T] [LAYER].featureCount()), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = False elif reply == QMessageBox.Cancel: self.logger.warning_msg( __name__, QCoreApplication.translate( "ToolBar", "First select at least one plot!")) return else: reply = QMessageBox.question( None, QCoreApplication.translate("ToolBar", "Continue?"), QCoreApplication.translate( "ToolBar", "There are {selected} plots selected. Do you want to fill the '{more}' and '{less}' tables just for the selected plots?\n\nIf you say 'No', the '{more}' and '{less}' tables will be filled for all plots in the database." ).format(selected=layers[db.names.OP_PLOT_T] [LAYER].selectedFeatureCount(), more=db.names.MORE_BFS_T, less=db.names.LESS_BFS_T), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.Cancel) if reply == QMessageBox.Yes: use_selection = True elif reply == QMessageBox.No: use_selection = False elif reply == QMessageBox.Cancel: return more_bfs_features = layers[db.names.MORE_BFS_T][LAYER].getFeatures() less_features = layers[db.names.LESS_BFS_T][LAYER].getFeatures() # Get unique pairs id_boundary-id_plot in both tables existing_more_pairs = [ (more_bfs_feature[db.names.MORE_BFS_T_OP_PLOT_F], more_bfs_feature[db.names.MORE_BFS_T_OP_BOUNDARY_F]) for more_bfs_feature in more_bfs_features ] existing_more_pairs = set(existing_more_pairs) # Todo: Update when ili2db issue is solved. # Todo: When an abstract class only implements a concrete class, the name of the attribute is different if two or more classes are implemented. existing_less_pairs = [ (less_feature[db.names.LESS_BFS_T_OP_PLOT_F], less_feature[db.names.LESS_BFS_T_OP_BOUNDARY_F]) for less_feature in less_features ] existing_less_pairs = set(existing_less_pairs) id_more_pairs, id_less_pairs = self.geometry.get_pair_boundary_plot( layers[db.names.OP_BOUNDARY_T][LAYER], layers[db.names.OP_PLOT_T][LAYER], db.names.T_ID_F, use_selection=use_selection) if id_less_pairs: layers[db.names.LESS_BFS_T][LAYER].startEditing() features = list() for id_pair in id_less_pairs: if not id_pair in existing_less_pairs: # Avoid duplicated pairs in the DB # Create feature feature = QgsVectorLayerUtils().createFeature( layers[db.names.LESS_BFS_T][LAYER]) feature.setAttribute(db.names.LESS_BFS_T_OP_PLOT_F, id_pair[0]) # Todo: Update LESS_BFS_T_OP_BOUNDARY_F by LESS_BFS_T_OP_BOUNDARY_F. # Todo: When an abstract class only implements a concrete class, the name of the attribute is different if two or more classes are implemented. feature.setAttribute(db.names.LESS_BFS_T_OP_BOUNDARY_F, id_pair[1]) features.append(feature) layers[db.names.LESS_BFS_T][LAYER].addFeatures(features) layers[db.names.LESS_BFS_T][LAYER].commitChanges() self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database." ).format(len(features), len(id_less_pairs), db.names.LESS_BFS_T, len(id_less_pairs) - len(features), len(id_less_pairs))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "No pairs id_boundary-id_plot found for '{}' table."). format(db.names.LESS_BFS_T)) if id_more_pairs: layers[db.names.MORE_BFS_T][LAYER].startEditing() features = list() for id_pair in id_more_pairs: if not id_pair in existing_more_pairs: # Avoid duplicated pairs in the DB # Create feature feature = QgsVectorLayerUtils().createFeature( layers[db.names.MORE_BFS_T][LAYER]) feature.setAttribute(db.names.MORE_BFS_T_OP_PLOT_F, id_pair[0]) feature.setAttribute(db.names.MORE_BFS_T_OP_BOUNDARY_F, id_pair[1]) features.append(feature) layers[db.names.MORE_BFS_T][LAYER].addFeatures(features) layers[db.names.MORE_BFS_T][LAYER].commitChanges() self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database." ).format(len(features), len(id_more_pairs), db.names.MORE_BFS_T, len(id_more_pairs) - len(features), len(id_more_pairs))) else: self.logger.info_msg( __name__, QCoreApplication.translate( "ToolBar", "No pairs id_boundary-id_plot found for '{}' table."). format(db.names.MORE_BFS_T))
def __init__(self, iface, qgis_utils): QObject.__init__(self) self.iface = iface self.qgis_utils = qgis_utils self.logger = Logger() self.geometry = GeometryUtils()
def _validate(self, db, db_qr, layer_dict, tolerance, **kwargs): self.progress_changed.emit(5) option_check_res, res_obj = self._check_qr_options(kwargs) if not option_check_res: return res_obj self._read_option_values(kwargs['options']) plot_layer = self._get_layer(layer_dict) pre_res, res_obj = self._check_prerrequisite_layer( QCoreApplication.translate("QualityRules", "Plot"), plot_layer) if not pre_res: return res_obj self.progress_changed.emit(10) gaps = GeometryUtils.get_gaps_in_polygon_layer(plot_layer, self.options.use_roads) self.progress_changed.emit(60) res_type, msg = EnumQualityRuleResult.UNDEFINED, "" if gaps: fids_list = GeometryUtils.get_intersection_features( plot_layer, gaps) # List of lists of qgis ids self.progress_changed.emit(80) uuids_list = list() for fids in fids_list: # Note this preserve order from gaps list, so we are able # to get gap geometry and its correspondant t_ili_tid list uuids_list.append([ f[db.names.T_ILI_TID_F] for f in plot_layer.getFeatures(fids) ]) 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 serial, geometry in enumerate(gaps): errors['geometries'].append(geometry) error_data = [ # [obj_uuids, rel_obj_uuids, values, details, state] uuids_list[serial], None, None, None, error_state ] errors['data'].append(error_data) self._save_errors(db_qr, self._ERROR_01, errors) self.progress_changed.emit(90) res_type = EnumQualityRuleResult.ERRORS msg = QCoreApplication.translate( "QualityRules", "{} gaps were found in 'Plot' layer.").format(len(gaps)) count = len(errors['data']) else: res_type = EnumQualityRuleResult.SUCCESS msg = QCoreApplication.translate( "QualityRules", "There are no gaps in layer Plot.") count = 0 self.progress_changed.emit(100) return QualityRuleExecutionResult(res_type, msg, count)