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 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 08:48:14 +02:00
co-authored by Claude Sonnet 5
parent 04f2a49c13
commit 821bb0640e
3 changed files with 152 additions and 2 deletions
+1
View File
@@ -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) - 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 - 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) - Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte)
+35 -1
View File
@@ -37,6 +37,7 @@ from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs" ALKIS_SF_URL = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
FLST_TYPENAME = "adv:AX_Flurstueck" FLST_TYPENAME = "adv:AX_Flurstueck"
FLST_LAYER_NAME = "Flurstücke" FLST_LAYER_NAME = "Flurstücke"
FELD_FLURSTUECKSKENNZEICHEN = "flurstueckskennzeichen"
def _check_existing_flurstueck_layer(tab_widget): def _check_existing_flurstueck_layer(tab_widget):
@@ -74,6 +75,24 @@ def _check_existing_flurstueck_layer(tab_widget):
return None, LoadStatus.NONE 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]): def lade_flurstuecke_per_gemarkung(schluessel_liste: List[str]):
"""Lädt Flurstücke für mehrere Gemarkungen, eine WFS-Anfrage pro Schlüssel. """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 ------------------------------------ # --- Anhängen an vorhandenen Layer ------------------------------------
if status == LoadStatus.APPEND and append_target is not None: 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.updateExtents()
append_target.triggerRepaint() append_target.triggerRepaint()
return append_target, LoadStatus.APPEND return append_target, LoadStatus.APPEND
+116 -1
View File
@@ -30,7 +30,11 @@ if "qgis" not in sys.modules:
sys.modules["qgis.core"] = qgis_core sys.modules["qgis.core"] = qgis_core
sys.modules["qgis.utils"] = qgis_utils 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 from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
@@ -308,5 +312,116 @@ class TestFlurstueckAlkis(unittest.TestCase):
self.assertEqual(status, LoadStatus.FIRST) 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__": if __name__ == "__main__":
unittest.main() unittest.main()