From 821bb0640e408cd7a89d3ea031e39df1f01761f4 Mon Sep 17 00:00:00 2001 From: daniel Date: Tue, 15 Sep 2026 08:48:14 +0200 Subject: [PATCH] Doppeltes Laden von Flurstuecken beim Anhaengen verhindert (#28) Beim Anhaengen an einen bestehenden Flurstueck-Layer wurden alle neu geladenen Objekte unabhaengig davon hinzugefuegt, ob sie ueber flurstueckskennzeichen bereits im Layer vorhanden waren. Jetzt werden nur noch neue Kennzeichen angehaengt; fehlt das Feld, bleibt das bisherige Verhalten als Rueckfall erhalten. Co-Authored-By: Claude Sonnet 5 --- changelog.txt | 1 + functions/flurstueck_alkis.py | 36 +++++++++- tests/test_flurstueck_alkis.py | 117 ++++++++++++++++++++++++++++++++- 3 files changed, 152 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index c948f93..0ba2c60 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,4 @@ +- Bugfix: Flurstücke aus ALKIS an bestehenden Layer anhängen überspringt jetzt bereits vorhandene Flurstücke (Abgleich über flurstueckskennzeichen), statt sie doppelt zu laden (#28) - Fortschrittsdialog beim Laden aus ALKIS schließt jetzt zuverlässig bei jedem Abbruch (Bugfix) - Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt - Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte) diff --git a/functions/flurstueck_alkis.py b/functions/flurstueck_alkis.py index 4f2c199..d9888fe 100644 --- a/functions/flurstueck_alkis.py +++ b/functions/flurstueck_alkis.py @@ -37,6 +37,7 @@ from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs" FLST_TYPENAME = "adv:AX_Flurstueck" FLST_LAYER_NAME = "Flurstücke" +FELD_FLURSTUECKSKENNZEICHEN = "flurstueckskennzeichen" def _check_existing_flurstueck_layer(tab_widget): @@ -74,6 +75,24 @@ def _check_existing_flurstueck_layer(tab_widget): return None, LoadStatus.NONE +def _vorhandene_kennzeichen(layer) -> set: + """Liefert die ``flurstueckskennzeichen``-Werte, die ``layer`` bereits enthält. + + Fehlt das Feld, wird eine leere Menge zurückgegeben (kein Abgleich möglich). + """ + kennzeichen: set = set() + if layer.fields().indexFromName(FELD_FLURSTUECKSKENNZEICHEN) < 0: + return kennzeichen + for feat in layer.getFeatures(): + try: + kz = str(feat[FELD_FLURSTUECKSKENNZEICHEN] or "").strip() + except Exception: + kz = "" + if kz: + kennzeichen.add(kz) + return kennzeichen + + def lade_flurstuecke_per_gemarkung(schluessel_liste: List[str]): """Lädt Flurstücke für mehrere Gemarkungen, eine WFS-Anfrage pro Schlüssel. @@ -250,7 +269,22 @@ def flurstueck_alkis( # --- Anhängen an vorhandenen Layer ------------------------------------ if status == LoadStatus.APPEND and append_target is not None: - append_target.dataProvider().addFeatures(list(layer.getFeatures())) + alle_feats = list(layer.getFeatures()) + neue_feats = alle_feats + if layer.fields().indexFromName(FELD_FLURSTUECKSKENNZEICHEN) >= 0: + vorhandene = _vorhandene_kennzeichen(append_target) + neue_feats = [ + f for f in alle_feats + if str(f[FELD_FLURSTUECKSKENNZEICHEN] or "").strip() not in vorhandene + ] + uebersprungen = len(alle_feats) - len(neue_feats) + if uebersprungen: + _info( + "Flurstücke", + f"{uebersprungen} bereits vorhandene Flurstücke übersprungen.", + duration=3, + ) + append_target.dataProvider().addFeatures(neue_feats) append_target.updateExtents() append_target.triggerRepaint() return append_target, LoadStatus.APPEND diff --git a/tests/test_flurstueck_alkis.py b/tests/test_flurstueck_alkis.py index 63471dd..57640b8 100644 --- a/tests/test_flurstueck_alkis.py +++ b/tests/test_flurstueck_alkis.py @@ -30,7 +30,11 @@ if "qgis" not in sys.modules: sys.modules["qgis.core"] = qgis_core sys.modules["qgis.utils"] = qgis_utils -from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis, FLST_LAYER_NAME +from sn_verfahrensgebiet.functions.flurstueck_alkis import ( + flurstueck_alkis, + FLST_LAYER_NAME, + FELD_FLURSTUECKSKENNZEICHEN, +) from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus @@ -308,5 +312,116 @@ class TestFlurstueckAlkis(unittest.TestCase): self.assertEqual(status, LoadStatus.FIRST) + +# --------------------------------------------------------------------------- +# Dummies für den Anhängen-Deduplizierungstest (#28) +# --------------------------------------------------------------------------- + +class _DummyFlstFeature: + def __init__(self, kennzeichen): + self._kennzeichen = kennzeichen + + def __getitem__(self, key): + if key == FELD_FLURSTUECKSKENNZEICHEN: + return self._kennzeichen + return None + + +class _DummyFlstFields: + def __init__(self, hat_kennzeichen_feld: bool): + self._hat_feld = hat_kennzeichen_feld + + def indexFromName(self, name): + if name == FELD_FLURSTUECKSKENNZEICHEN and self._hat_feld: + return 0 + return -1 + + +class _DummyFlstLayer: + """Flurstück-Layer-Dummy mit Feature-Liste und aufzeichnendem ``dataProvider``.""" + + def __init__(self, features, hat_kennzeichen_feld: bool = True): + self._features = list(features) + self._hat_feld = hat_kennzeichen_feld + self.added = None + + def getFeatures(self): + return list(self._features) + + def fields(self): + return _DummyFlstFields(self._hat_feld) + + def dataProvider(self): + layer = self + + class _Provider: + def addFeatures(self, feats): + layer.added = list(feats) + layer._features.extend(feats) + return True + + return _Provider() + + def updateExtents(self): + pass + + def triggerRepaint(self): + pass + + def isValid(self): + return True + + def featureCount(self): + return len(self._features) + + +class TestFlurstueckAlkisAnhaengenDedup(unittest.TestCase): + """Anhängen an einen bestehenden Flurstück-Layer (Issue #28).""" + + @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung") + @patch("sn_verfahrensgebiet.functions.flurstueck_alkis._check_existing_flurstueck_layer") + @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") + def test_append_ueberspringt_bereits_vorhandene_flurstuecke( + self, mock_ask_text, mock_check, mock_lade + ): + """Prüft, dass beim Anhängen bereits vorhandene flurstueckskennzeichen nicht erneut hinzugefügt werden.""" + mock_ask_text.return_value = ("0001, 0002", True) + bestehend = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")]) + neu_geladen = _DummyFlstLayer([ + _DummyFlstFeature("110001_0001__00001"), # bereits im Layer vorhanden + _DummyFlstFeature("110001_0002__00007"), # noch nicht vorhanden + ]) + mock_check.return_value = (bestehend, LoadStatus.APPEND) + mock_lade.return_value = neu_geladen + + layer, status = flurstueck_alkis(None) + + self.assertEqual(status, LoadStatus.APPEND) + self.assertIs(layer, bestehend) + self.assertEqual(len(bestehend.added), 1) + self.assertEqual(bestehend.added[0][FELD_FLURSTUECKSKENNZEICHEN], "110001_0002__00007") + + @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.lade_flurstuecke_per_gemarkung") + @patch("sn_verfahrensgebiet.functions.flurstueck_alkis._check_existing_flurstueck_layer") + @patch("sn_verfahrensgebiet.functions.flurstueck_alkis.ask_text") + def test_append_ohne_kennzeichen_feld_haengt_alles_an( + self, mock_ask_text, mock_check, mock_lade + ): + """Prüft, dass ohne flurstueckskennzeichen-Feld weiterhin alle Features angehängt werden (Rückfallverhalten).""" + mock_ask_text.return_value = ("0001", True) + bestehend = _DummyFlstLayer([_DummyFlstFeature("110001_0001__00001")], hat_kennzeichen_feld=False) + neu_geladen = _DummyFlstLayer( + [_DummyFlstFeature("110001_0001__00001"), _DummyFlstFeature("110001_0002__00007")], + hat_kennzeichen_feld=False, + ) + mock_check.return_value = (bestehend, LoadStatus.APPEND) + mock_lade.return_value = neu_geladen + + layer, status = flurstueck_alkis(None) + + self.assertEqual(status, LoadStatus.APPEND) + self.assertEqual(len(bestehend.added), 2) + + if __name__ == "__main__": unittest.main()