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:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user