try2 und try3 laden das Verfahrensgebiet mit Detailprüfung anstelle der Flurstücke vom wfs

This commit is contained in:
2026-08-26 16:08:01 +02:00
parent 2cb6193b98
commit 6bc5256d5f
4 changed files with 287 additions and 148 deletions
+117 -115
View File
@@ -7,7 +7,7 @@ Schwesterplugins für andere Bundesländer dieselbe Logik mit einer anderen
Länderkennung wiederverwenden können. Länderkennung wiederverwenden können.
Gleicht eine CSV-Exportliste des BI-Servers (Kopfzeile Gleicht eine CSV-Exportliste des BI-Servers (Kopfzeile
``FLA_VEF_VKZF,FLA_Beteiligung,FLURSTUECK``) mit dem Verfahrensgebiet-Bestand ``VEF_VKZF,FLA_Beteiligung,FLURSTUECK``) mit dem Verfahrensgebiet-Bestand
ab. Aus ``FLURSTUECK`` (Format ``<Gemarkung>/<Zähler>/<Nenner>/<Folge>``) wird ab. Aus ``FLURSTUECK`` (Format ``<Gemarkung>/<Zähler>/<Nenner>/<Folge>``) wird
je Zeile ein zu ALKIS kompatibles Flurstückskennzeichen ``Flurstueck_csv`` je Zeile ein zu ALKIS kompatibles Flurstückskennzeichen ``Flurstueck_csv``
gebildet. gebildet.
@@ -19,10 +19,12 @@ Automatische Kaskade je nach Projektzustand:
:mod:`verfahrensgebiet_edit_service`) ist vorhanden → schneller :mod:`verfahrensgebiet_edit_service`) ist vorhanden → schneller
Mengenvergleich ohne WFS-Zugriff. Mengenvergleich ohne WFS-Zugriff.
- **try2**: Ein Verfahrensgebiet-Layer ist vorhanden, aber ohne verwertbare - **try2**: Ein Verfahrensgebiet-Layer ist vorhanden, aber ohne verwertbare
Kennzeichen → je CSV-Zeile eine WFS-Einzelabfrage gegen ``AX_Flurstueck``. Kennzeichen → die Pipeline "Verfahrensgebiet aus ALKIS laden"
(:func:`verfahrensgebiet_alkis_komplett`) wird silent (ohne
Nutzerrückfragen, aber inkl. Detailprüfung) durchlaufen, um einen
vollständigen, aktuellen Bestand zu erzeugen – danach wie try1.
- **try3**: Kein Verfahrensgebiet-Layer im Projekt → Verfahrensnummer aus - **try3**: Kein Verfahrensgebiet-Layer im Projekt → Verfahrensnummer aus
``VEF_VKZF`` als Projektvariable setzen, Verfahrensgebiet analog zur ``VEF_VKZF`` als Projektvariable setzen, danach wie try2.
bestehenden ALKIS-Pipeline laden (ohne Detailprüfung), danach wie try2.
""" """
from __future__ import annotations from __future__ import annotations
@@ -32,28 +34,29 @@ from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from sn_basis.functions import QgsVectorLayer, QgsProject, QgsFeature, QgsField from sn_basis.functions import QgsVectorLayer, QgsProject, QgsFeature, QgsField
from sn_basis.functions import QMessageBox
from sn_basis.functions.qt_wrapper import QFileDialog, QVariant from sn_basis.functions.qt_wrapper import QFileDialog, QVariant
from sn_basis.functions.variable_wrapper import set_variable from sn_basis.functions.variable_wrapper import set_variable
from sn_basis.functions.laenderkennung import get_laenderkennung from sn_basis.functions.laenderkennung import get_laenderkennung
from sn_basis.functions.dialog_wrapper import create_progress_dialog
from sn_basis.functions.ly_style_wrapper import apply_style
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import verfahrensgebiet_alkis from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
from sn_verfahrensgebiet.functions.flurstueck_alkis import ALKIS_SF_URL, FLST_TYPENAME, FLST_LAYER_NAME verfahrensgebiet_alkis_komplett,
)
from sn_verfahrensgebiet.functions.flurstueck_alkis import FLST_LAYER_NAME
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import ( from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
VERF_LAYER_NAME, VERF_LAYER_NAME,
get_kennzeichen_menge, get_kennzeichen_menge,
) )
CSV_SPALTE_FLURSTUECK = "FLURSTUECK" CSV_SPALTE_FLURSTUECK = "FLURSTUECK"
CSV_SPALTE_VEF_VKZF = "FLA_VEF_VKZF" CSV_SPALTE_VEF_VKZF = "VEF_VKZF"
CSV_SPALTE_BETEILIGUNG = "FLA_Beteiligung" CSV_SPALTE_BETEILIGUNG = "FLA_Beteiligung"
FELD_FLURSTUECK_CSV = "Flurstueck_csv" FELD_FLURSTUECK_CSV = "Flurstueck_csv"
FELD_FLURSTUECKSKENNZEICHEN_WFS = "flurstueckskennzeichen" FELD_FLURSTUECKSKENNZEICHEN_WFS = "flurstueckskennzeichen"
ERGEBNIS_LAYER_VERGLEICH = "Beteiligung – Vergleich" ERGEBNIS_LAYER_VERGLEICH = "Beteiligung – Vergleich"
ERGEBNIS_LAYER_ERFOLGREICH = "Beteiligung – gefunden"
ERGEBNIS_LAYER_NICHT_GEFUNDEN = "Beteiligung – nicht gefunden"
WERT_BEIDE = "beide" WERT_BEIDE = "beide"
WERT_NUR_VERFAHRENSGEBIET = "nur_verfahrensgebiet" WERT_NUR_VERFAHRENSGEBIET = "nur_verfahrensgebiet"
@@ -127,6 +130,38 @@ def lade_beteiligung_csv(parent_widget) -> Optional[Tuple[List[dict], List[str]]
# Flurstueck_csv-Bildung # Flurstueck_csv-Bildung
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
#: Alte CSV-Exporte führen Buchstaben-Nenner (ALKIS: a, b, c, ...) teils als
#: numerischen Platzhalter 9001, 9002, ... statt als Buchstabe. 9001 → "a",
#: 9002 → "b" usw. (Basiswert, auf den 1..26 für a..z addiert wird).
_NENNER_BUCHSTABEN_BASIS = 9000
def _baue_nenner_kennzeichen(nenner: str) -> str:
"""Bildet den 4-stelligen Nenner-Teil des Flurstückskennzeichens.
Normalfall: numerischer Nenner, 4-stellig mit führenden Nullen
aufgefüllt; Nenner 0/leer → Platzhalter ``"____"``.
Format-Inkonsistenz alter CSV-Exporte: ein Buchstaben-Nenner (z.B. "a")
wird dort als ``9001`` (a), ``9002`` (b), usw. geführt. Wird ein solcher
Platzhalterwert erkannt, wird stattdessen der Buchstabe selbst
(4-stellig mit führenden Nullen aufgefüllt, z.B. ``"000a"``) verwendet,
damit das Kennzeichen mit dem ALKIS-Bestand übereinstimmt.
"""
nenner = (nenner or "").strip()
nenner_ohne_null = nenner.lstrip("0")
if not nenner_ohne_null:
return "____"
if nenner_ohne_null.isdigit():
zahl = int(nenner_ohne_null)
if _NENNER_BUCHSTABEN_BASIS < zahl <= _NENNER_BUCHSTABEN_BASIS + 26:
buchstabe = chr(ord("a") + (zahl - _NENNER_BUCHSTABEN_BASIS - 1))
return buchstabe.zfill(4)
return nenner.zfill(4)
def baue_flurstueck_csv(flurstueck_wert: str, laenderkennung: str) -> Optional[str]: def baue_flurstueck_csv(flurstueck_wert: str, laenderkennung: str) -> Optional[str]:
"""Bildet aus dem CSV-Attribut ``FLURSTUECK`` ein ALKIS-Flurstückskennzeichen. """Bildet aus dem CSV-Attribut ``FLURSTUECK`` ein ALKIS-Flurstückskennzeichen.
@@ -154,11 +189,10 @@ def baue_flurstueck_csv(flurstueck_wert: str, laenderkennung: str) -> Optional[s
gemarkung_kz = gemarkung.zfill(4) gemarkung_kz = gemarkung.zfill(4)
flur_kz = "___" flur_kz = "___"
zaehler_kz = zaehler.zfill(5) zaehler_kz = zaehler.zfill(5)
nenner_kz = _baue_nenner_kennzeichen(nenner)
folge_kz = folge.zfill(2)
nenner_ohne_null = nenner.lstrip("0") return f"{laenderkennung}{gemarkung_kz}{flur_kz}{zaehler_kz}{nenner_kz}{folge_kz}"
nenner_kz = "____" if not nenner_ohne_null else nenner.zfill(4)
return f"{laenderkennung}{gemarkung_kz}{flur_kz}{zaehler_kz}{nenner_kz}{folge}"
def baue_flurstueck_csv_liste(zeilen: List[dict], laenderkennung: str) -> List[str]: def baue_flurstueck_csv_liste(zeilen: List[dict], laenderkennung: str) -> List[str]:
@@ -231,14 +265,25 @@ def _flurstueckskennzeichen_geometrien(flst_layer) -> Dict[str, object]:
# try1: schneller Listenabgleich # try1: schneller Listenabgleich
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _try1_listenabgleich(verf_layer, flurstueck_csv_liste: List[str]) -> Tuple[object, Dict[str, int]]: def _try1_listenabgleich(
tab_widget, verf_layer, flurstueck_csv_liste: List[str]
) -> Tuple[object, Dict[str, int]]:
progress = create_progress_dialog(
3, "Beteiligung prüfen", "Verfahrensgebiet-Kennzeichen einlesen …"
)
progress.set_value(0)
verf_menge = get_kennzeichen_menge(verf_layer) verf_menge = get_kennzeichen_menge(verf_layer)
csv_menge = set(flurstueck_csv_liste) csv_menge = set(flurstueck_csv_liste)
progress.set_value(1)
progress.set_label("CSV-Liste abgleichen …")
beide = sorted(verf_menge & csv_menge) beide = sorted(verf_menge & csv_menge)
nur_verfahrensgebiet = sorted(verf_menge - csv_menge) nur_verfahrensgebiet = sorted(verf_menge - csv_menge)
nur_aglb = sorted(csv_menge - verf_menge) nur_aglb = sorted(csv_menge - verf_menge)
progress.set_value(2)
progress.set_label("Ergebnislayer erstellen …")
flst_layer = _hole_flurstuecke_layer() flst_layer = _hole_flurstuecke_layer()
geom_map = _flurstueckskennzeichen_geometrien(flst_layer) geom_map = _flurstueckskennzeichen_geometrien(flst_layer)
if flst_layer is not None and flst_layer.isValid(): if flst_layer is not None and flst_layer.isValid():
@@ -274,6 +319,9 @@ def _try1_listenabgleich(verf_layer, flurstueck_csv_liste: List[str]) -> Tuple[o
pr.addFeatures(neue_features) pr.addFeatures(neue_features)
layer.updateExtents() layer.updateExtents()
progress.set_value(3)
progress.close()
statistik = { statistik = {
WERT_BEIDE: len(beide), WERT_BEIDE: len(beide),
WERT_NUR_VERFAHRENSGEBIET: len(nur_verfahrensgebiet), WERT_NUR_VERFAHRENSGEBIET: len(nur_verfahrensgebiet),
@@ -283,87 +331,68 @@ def _try1_listenabgleich(verf_layer, flurstueck_csv_liste: List[str]) -> Tuple[o
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# try2: WFS-Einzelabgleich # try2/try3: ALKIS-Pipeline (silent) + Listenabgleich wie try1
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _try2_wfs_einzelabgleich( def _lade_via_alkis_pipeline_und_vergleiche(
flurstueck_csv_liste: List[str], tab_widget, flurstueck_csv_liste: List[str]
) -> Tuple[Optional[object], object, Dict[str, int]]: ) -> BeteiligungErgebnis:
erfolgreich: List[tuple] = [] """Lädt das Verfahrensgebiet silent per ALKIS-Pipeline (inkl. Detailprüfung)
nicht_gefunden: List[str] = [] und gleicht den Bestand danach wie in try1 gegen die CSV-Liste ab.
schema_fields = None
crs = "EPSG:25833"
dienst_erreichbar = False
for kz in flurstueck_csv_liste: Raises
uri = ( ------
f"url='{ALKIS_SF_URL}' typename='{FLST_TYPENAME}' srsname='EPSG:25833' " BeteiligungPruefungFehler
f"sql=SELECT * FROM AX_Flurstueck " Wenn die ALKIS-Pipeline kein Verfahrensgebiet liefert (Pipeline hat
f"WHERE {FELD_FLURSTUECKSKENNZEICHEN_WFS} = '{kz}'" bereits eine erklärende Messagebox gezeigt).
"""
enriched_layer, flst_layer, bror_layer, status = verfahrensgebiet_alkis_komplett(
tab_widget, silent=True
) )
wfs = QgsVectorLayer(uri, f"_TmpBet{kz}", "WFS") if enriched_layer is None:
if not wfs.isValid():
continue
dienst_erreichbar = True
feats = list(wfs.getFeatures())
if not feats:
nicht_gefunden.append(kz)
continue
if schema_fields is None:
schema_fields = wfs.fields()
crs = wfs.crs().authid()
erfolgreich.append((wfs.fields(), feats[0]))
if not dienst_erreichbar:
raise BeteiligungPruefungFehler( raise BeteiligungPruefungFehler(
f"Dienst nicht erreichbar – ALKIS-WFS ({ALKIS_SF_URL}) konnte nicht " "Verfahrensgebiet konnte über die ALKIS-Pipeline nicht geladen werden."
f"abgefragt werden. Bitte Netzwerkverbindung prüfen."
) )
erfolg_layer = None project = QgsProject.instance()
if schema_fields is not None:
erfolg_layer = QgsVectorLayer(f"Polygon?crs={crs}", ERGEBNIS_LAYER_ERFOLGREICH, "memory")
pr = erfolg_layer.dataProvider()
pr.addAttributes(list(schema_fields))
erfolg_layer.updateFields()
mem_fields = erfolg_layer.fields()
neue_features = [] if flst_layer is not None and flst_layer.isValid():
for wfs_fields, feat in erfolgreich: if not project.mapLayersByName(flst_layer.name()):
new_f = QgsFeature(mem_fields) project.addMapLayer(flst_layer)
new_f.setGeometry(feat.geometry()) apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
for i in range(wfs_fields.count()):
fname = wfs_fields.field(i).name()
idx = mem_fields.indexFromName(fname)
if idx >= 0:
try:
new_f.setAttribute(idx, feat[fname])
except Exception:
pass
neue_features.append(new_f)
pr.addFeatures(neue_features)
erfolg_layer.updateExtents()
ng_layer = QgsVectorLayer( if bror_layer is not None and bror_layer.isValid():
f"None?field={FELD_FLURSTUECK_CSV}:string", ERGEBNIS_LAYER_NICHT_GEFUNDEN, "memory" if not project.mapLayer(bror_layer.id()):
project.addMapLayer(bror_layer)
apply_style(bror_layer, "BauRaumOderBodenordnungsrecht.qml")
if not project.mapLayer(enriched_layer.id()):
project.addMapLayer(enriched_layer)
apply_style(enriched_layer, "verfahrensgebiet.qml")
vergleich_layer, statistik = _try1_listenabgleich(
tab_widget, enriched_layer, flurstueck_csv_liste
) )
pr = ng_layer.dataProvider()
ng_fields = ng_layer.fields()
neue_ng = []
for kz in nicht_gefunden:
f = QgsFeature(ng_fields)
f.setAttribute(0, kz)
neue_ng.append(f)
pr.addFeatures(neue_ng)
statistik = {"erfolgreich": len(erfolgreich), "nicht_gefunden": len(nicht_gefunden)} layers: List[Tuple[object, Optional[str]]] = [
return erfolg_layer, ng_layer, statistik (enriched_layer, None),
(vergleich_layer, "beteiligung_vergleich.qml"),
]
if flst_layer is not None:
layers.append((flst_layer, None))
if bror_layer is not None:
layers.append((bror_layer, None))
text = (
f"Übereinstimmend: {statistik[WERT_BEIDE]}\n"
f"Nur im Verfahrensgebiet: {statistik[WERT_NUR_VERFAHRENSGEBIET]}\n"
f"Nur in der CSV-Liste (AGLB): {statistik[WERT_NUR_AGLB]}"
)
return BeteiligungErgebnis("try2", layers, statistik, text)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# try3: Bootstrap (Verfahrensnummer aus CSV, VG laden) + try2 # try3: Bootstrap (Verfahrensnummer aus CSV) + wie try2
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _lese_vef_vkzf(zeilen: List[dict], spaltennamen: List[str]) -> str: def _lese_vef_vkzf(zeilen: List[dict], spaltennamen: List[str]) -> str:
@@ -386,28 +415,10 @@ def _try3_bootstrap_und_try2(
vef_vkzf = _lese_vef_vkzf(zeilen, spaltennamen) vef_vkzf = _lese_vef_vkzf(zeilen, spaltennamen)
set_variable("verfahrensnummer", vef_vkzf, scope="project") set_variable("verfahrensnummer", vef_vkzf, scope="project")
# Identische BROR-Ladung wie die bestehende ALKIS-Pipeline, ohne die ergebnis = _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste)
# nachgelagerte Detailprüfung/Flächenvergleich aus verfahrensgebiet_alkis_komplett. ergebnis.modus = "try3"
neuer_vg_layer, status, _feats = verfahrensgebiet_alkis(tab_widget, skip_existing_check=True) ergebnis.statistik_text = f"Verfahrensnummer aus CSV: {vef_vkzf}\n" + ergebnis.statistik_text
if neuer_vg_layer is None: return ergebnis
# verfahrensgebiet_alkis() hat bereits eine erklärende Messagebox gezeigt.
raise BeteiligungPruefungFehler(
f"Verfahrensgebiet konnte für Verfahrensnummer '{vef_vkzf}' nicht geladen werden."
)
erfolg_layer, ng_layer, statistik = _try2_wfs_einzelabgleich(flurstueck_csv_liste)
layers: List[Tuple[object, Optional[str]]] = [(neuer_vg_layer, "verfahrensgebiet.qml")]
if erfolg_layer is not None:
layers.append((erfolg_layer, "beteiligung_erfolgreich.qml"))
layers.append((ng_layer, None))
text = (
f"Verfahrensnummer aus CSV: {vef_vkzf}\n"
f"Erfolgreich geladen: {statistik['erfolgreich']}\n"
f"Nicht gefunden: {statistik['nicht_gefunden']}"
)
return BeteiligungErgebnis("try3", layers, statistik, text)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -446,7 +457,7 @@ def beteiligung_pruefen(tab_widget) -> Optional[BeteiligungErgebnis]:
verf_layer = _hole_verfahrensgebiet_layer(tab_widget) verf_layer = _hole_verfahrensgebiet_layer(tab_widget)
if verf_layer is not None and _hat_verwendbare_kennzeichen(verf_layer): if verf_layer is not None and _hat_verwendbare_kennzeichen(verf_layer):
layer, statistik = _try1_listenabgleich(verf_layer, flurstueck_csv_liste) layer, statistik = _try1_listenabgleich(tab_widget, verf_layer, flurstueck_csv_liste)
text = ( text = (
f"Übereinstimmend: {statistik[WERT_BEIDE]}\n" f"Übereinstimmend: {statistik[WERT_BEIDE]}\n"
f"Nur im Verfahrensgebiet: {statistik[WERT_NUR_VERFAHRENSGEBIET]}\n" f"Nur im Verfahrensgebiet: {statistik[WERT_NUR_VERFAHRENSGEBIET]}\n"
@@ -455,15 +466,6 @@ def beteiligung_pruefen(tab_widget) -> Optional[BeteiligungErgebnis]:
return BeteiligungErgebnis("try1", [(layer, "beteiligung_vergleich.qml")], statistik, text) return BeteiligungErgebnis("try1", [(layer, "beteiligung_vergleich.qml")], statistik, text)
if verf_layer is not None: if verf_layer is not None:
erfolg_layer, ng_layer, statistik = _try2_wfs_einzelabgleich(flurstueck_csv_liste) return _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste)
layers: List[Tuple[object, Optional[str]]] = []
if erfolg_layer is not None:
layers.append((erfolg_layer, "beteiligung_erfolgreich.qml"))
layers.append((ng_layer, None))
text = (
f"Erfolgreich geladen: {statistik['erfolgreich']}\n"
f"Nicht gefunden: {statistik['nicht_gefunden']}"
)
return BeteiligungErgebnis("try2", layers, statistik, text)
return _try3_bootstrap_und_try2(tab_widget, zeilen, spaltennamen, flurstueck_csv_liste) return _try3_bootstrap_und_try2(tab_widget, zeilen, spaltennamen, flurstueck_csv_liste)
+13 -1
View File
@@ -72,11 +72,17 @@ def _check_existing_layer(tab_widget):
return existing_layers[0], LoadStatus.KEEP return existing_layers[0], LoadStatus.KEEP
def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False): def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False, silent: bool = False):
"""Lädt Verfahrensgebiet aus ALKIS-WFS basierend auf Verfahrensnummer. """Lädt Verfahrensgebiet aus ALKIS-WFS basierend auf Verfahrensnummer.
Args: Args:
tab_widget: Parent-Widget für Dialoge und Meldungen tab_widget: Parent-Widget für Dialoge und Meldungen
skip_existing_check: Überspringt die Nachfrage bei bereits vorhandenem
Layer (immer neu laden).
silent: Automatisierter Modus ohne Nutzerinteraktion. Impliziert
``skip_existing_check=True`` und wählt bei mehreren
BROR-Kombinationen automatisch die erste (sortierte) statt den
Nutzer per Dialog entscheiden zu lassen.
Returns: Returns:
Tuple[Optional[QgsVectorLayer], LoadStatus, List[QgsFeature]]: Tuple[Optional[QgsVectorLayer], LoadStatus, List[QgsFeature]]:
@@ -92,6 +98,8 @@ def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False):
"In den Projekteigenschaften ist noch keine Verfahrensnummer eingetragen!") "In den Projekteigenschaften ist noch keine Verfahrensnummer eingetragen!")
return None, LoadStatus.NONE, [] return None, LoadStatus.NONE, []
skip_existing_check = skip_existing_check or silent
# Vorhandenen Layer prüfen (überspringbar für die Shape-Pipeline) # Vorhandenen Layer prüfen (überspringbar für die Shape-Pipeline)
status = LoadStatus.FIRST status = LoadStatus.FIRST
if not skip_existing_check: if not skip_existing_check:
@@ -136,6 +144,10 @@ def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False):
if len(kombinationen) == 1: if len(kombinationen) == 1:
selected_key = next(iter(kombinationen)) selected_key = next(iter(kombinationen))
selected_feats = kombinationen[selected_key] selected_feats = kombinationen[selected_key]
elif silent:
# Automatisierter Modus: erste (sortierte) Kombination ohne Nachfrage
selected_key = sorted(kombinationen.keys())[0]
selected_feats = kombinationen[selected_key]
else: else:
# Mehrere Kombinationen → Nutzerauswahl # Mehrere Kombinationen → Nutzerauswahl
key_list = sorted(kombinationen.keys()) key_list = sorted(kombinationen.keys())
+18 -2
View File
@@ -254,6 +254,7 @@ def _vergleiche_flaechen(
def verfahrensgebiet_alkis_komplett( def verfahrensgebiet_alkis_komplett(
tab_widget, tab_widget,
silent: bool = False,
) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]: ) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]:
"""Vollständiger VG-Lade-Workflow: ALKIS-VG → Gemarkungen → """Vollständiger VG-Lade-Workflow: ALKIS-VG → Gemarkungen →
Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung. Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung.
@@ -276,6 +277,15 @@ def verfahrensgebiet_alkis_komplett(
---------- ----------
tab_widget : tab_widget :
Eltern-Widget für alle Dialoge. Eltern-Widget für alle Dialoge.
silent :
Automatisierter Modus ohne Nutzerentscheidungen: bestehender
VG-Layer wird ohne Nachfrage neu geladen, mehrdeutige
BROR-Kombinationen werden automatisch aufgelöst, bei
Flächenabweichung wird immer die Detailprüfung gestartet (nie „als
VG laden“, nie abbrechen), und die beiden rein informativen
Abschluss-Meldungen (Flächenvergleich-OK, Detailprüfung
abgeschlossen) werden unterdrückt. Die Fortschrittsdialoge bleiben
sichtbar.
Returns Returns
------- -------
@@ -305,7 +315,7 @@ def verfahrensgebiet_alkis_komplett(
if progress.is_canceled(): if progress.is_canceled():
return None, None, None, LoadStatus.NONE return None, None, None, LoadStatus.NONE
alkis_vg_layer, vg_status, _ = verfahrensgebiet_alkis(tab_widget) alkis_vg_layer, vg_status, _ = verfahrensgebiet_alkis(tab_widget, silent=silent)
progress.raise_to_front() progress.raise_to_front()
if vg_status == LoadStatus.NONE or not alkis_vg_layer: if vg_status == LoadStatus.NONE or not alkis_vg_layer:
@@ -426,6 +436,7 @@ def verfahrensgebiet_alkis_komplett(
if stimmt_ueberein: if stimmt_ueberein:
progress.set_value(7) progress.set_value(7)
progress.close() progress.close()
if not silent:
show_info_dialog( show_info_dialog(
"Flächenvergleich", "Flächenvergleich",
"Verfahrensgebiet mit erwarteter Geometrie erzeugt", "Verfahrensgebiet mit erwarteter Geometrie erzeugt",
@@ -435,10 +446,14 @@ def verfahrensgebiet_alkis_komplett(
return enriched_layer, flst_layer, alkis_vg_layer, vg_status return enriched_layer, flst_layer, alkis_vg_layer, vg_status
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Flächenabweichung → 3-Optionen-Dialog # Flächenabweichung → 3-Optionen-Dialog (im silent-Modus: immer
# Detailprüfung, ohne Nachfrage)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
progress.close() progress.close()
diff = abs(bror_flaeche - vg_flaeche) diff = abs(bror_flaeche - vg_flaeche)
if silent:
entscheidung = "detailpruefung"
else:
entscheidung = ask_detailpruefung_oder_vg_laden( entscheidung = ask_detailpruefung_oder_vg_laden(
tab_widget, tab_widget,
"Abweichung erkannt", "Abweichung erkannt",
@@ -550,6 +565,7 @@ def verfahrensgebiet_alkis_komplett(
if ergebnis_layer and ergebnis_layer.isValid(): if ergebnis_layer and ergebnis_layer.isValid():
project.addMapLayer(ergebnis_layer) project.addMapLayer(ergebnis_layer)
if not silent:
show_info_dialog( show_info_dialog(
"Detailprüfung abgeschlossen", "Detailprüfung abgeschlossen",
f"Knickpunkte geprüft: {n_knick}\n" f"Knickpunkte geprüft: {n_knick}\n"
+110 -1
View File
@@ -27,7 +27,7 @@ import sys
import tempfile import tempfile
import types import types
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch, MagicMock
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Minimales QGIS-Mock-Setup # Minimales QGIS-Mock-Setup
@@ -49,9 +49,12 @@ from sn_verfahrensgebiet.functions.beteiligung_pruefung import (
baue_flurstueck_csv_liste, baue_flurstueck_csv_liste,
lade_beteiligung_csv, lade_beteiligung_csv,
BeteiligungPruefungFehler, BeteiligungPruefungFehler,
BeteiligungErgebnis,
FELD_FLURSTUECK_CSV, FELD_FLURSTUECK_CSV,
CSV_SPALTE_FLURSTUECK, CSV_SPALTE_FLURSTUECK,
CSV_SPALTE_VEF_VKZF, CSV_SPALTE_VEF_VKZF,
_lade_via_alkis_pipeline_und_vergleiche,
_try3_bootstrap_und_try2,
) )
_MOD = "sn_verfahrensgebiet.functions.beteiligung_pruefung" _MOD = "sn_verfahrensgebiet.functions.beteiligung_pruefung"
@@ -83,6 +86,30 @@ class TestBaueFlurstueckCsv(unittest.TestCase):
self.assertIsNone(baue_flurstueck_csv("", "14")) self.assertIsNone(baue_flurstueck_csv("", "14"))
self.assertIsNone(baue_flurstueck_csv(None, "14")) self.assertIsNone(baue_flurstueck_csv(None, "14"))
def test_nenner_9001_wird_zu_buchstabe_a(self):
ergebnis = baue_flurstueck_csv("1234/123/9001/00", "14")
self.assertEqual(ergebnis, "141234___00123000a00")
def test_nenner_9002_wird_zu_buchstabe_b(self):
ergebnis = baue_flurstueck_csv("1234/123/9002/00", "14")
self.assertEqual(ergebnis, "141234___00123000b00")
def test_nenner_9026_wird_zu_buchstabe_z(self):
ergebnis = baue_flurstueck_csv("1234/123/9026/00", "14")
self.assertEqual(ergebnis, "141234___00123000z00")
def test_nenner_ausserhalb_buchstaben_bereich_bleibt_numerisch(self):
ergebnis = baue_flurstueck_csv("1234/123/9027/00", "14")
self.assertEqual(ergebnis, "141234___00123902700")
def test_nenner_9001_mit_fuehrenden_nullen_wird_erkannt(self):
ergebnis = baue_flurstueck_csv("1234/123/09001/00", "14")
self.assertEqual(ergebnis, "141234___00123000a00")
def test_einstellige_folge_wird_auf_2_stellen_aufgefuellt(self):
ergebnis = baue_flurstueck_csv("1234/123/45/5", "14")
self.assertEqual(ergebnis, "141234___00123004505")
class TestBaueFlurstueckCsvListe(unittest.TestCase): class TestBaueFlurstueckCsvListe(unittest.TestCase):
def test_nur_gueltige_zeilen_in_liste(self): def test_nur_gueltige_zeilen_in_liste(self):
@@ -98,6 +125,88 @@ class TestBaueFlurstueckCsvListe(unittest.TestCase):
self.assertEqual(zeilen[2][FELD_FLURSTUECK_CSV], "145678___00001____01") self.assertEqual(zeilen[2][FELD_FLURSTUECK_CSV], "145678___00001____01")
class TestLadeViaAlkisPipelineUndVergleiche(unittest.TestCase):
@patch(f"{_MOD}._try1_listenabgleich")
@patch(f"{_MOD}.apply_style")
@patch(f"{_MOD}.QgsProject")
@patch(f"{_MOD}.verfahrensgebiet_alkis_komplett")
def test_pipeline_fehler_wirft_beteiligung_pruefung_fehler(
self, mock_pipeline, mock_project_cls, mock_apply_style, mock_try1
):
mock_pipeline.return_value = (None, None, None, None)
with self.assertRaises(BeteiligungPruefungFehler):
_lade_via_alkis_pipeline_und_vergleiche(object(), ["145678___00001____01"])
mock_try1.assert_not_called()
@patch(f"{_MOD}._try1_listenabgleich")
@patch(f"{_MOD}.apply_style")
@patch(f"{_MOD}.QgsProject")
@patch(f"{_MOD}.verfahrensgebiet_alkis_komplett")
def test_ruft_pipeline_silent_auf_und_delegiert_an_try1(
self, mock_pipeline, mock_project_cls, mock_apply_style, mock_try1
):
tab_widget = object()
enriched = MagicMock(name="enriched")
enriched.isValid.return_value = True
enriched.id.return_value = "vg1"
flst = MagicMock(name="flst")
flst.isValid.return_value = True
flst.name.return_value = "Flurstücke"
bror = MagicMock(name="bror")
bror.isValid.return_value = True
bror.id.return_value = "bror1"
mock_pipeline.return_value = (enriched, flst, bror, "FIRST")
mock_project = MagicMock()
mock_project.mapLayersByName.return_value = []
mock_project.mapLayer.return_value = None
mock_project_cls.instance.return_value = mock_project
vergleich_layer = MagicMock(name="vergleich")
statistik = {"beide": 1, "nur_verfahrensgebiet": 2, "nur_aglb": 3}
mock_try1.return_value = (vergleich_layer, statistik)
flurstueck_csv_liste = ["145678___00001____01"]
ergebnis = _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste)
mock_pipeline.assert_called_once_with(tab_widget, silent=True)
mock_try1.assert_called_once_with(tab_widget, enriched, flurstueck_csv_liste)
self.assertIsInstance(ergebnis, BeteiligungErgebnis)
self.assertEqual(ergebnis.modus, "try2")
self.assertEqual(ergebnis.statistik, statistik)
self.assertIn("Übereinstimmend: 1", ergebnis.statistik_text)
layers = [lyr for lyr, _ in ergebnis.layers]
self.assertIn(enriched, layers)
self.assertIn(vergleich_layer, layers)
self.assertIn(flst, layers)
self.assertIn(bror, layers)
class TestTry3BootstrapUndTry2(unittest.TestCase):
@patch(f"{_MOD}._lade_via_alkis_pipeline_und_vergleiche")
@patch(f"{_MOD}.set_variable")
def test_setzt_verfahrensnummer_und_ergaenzt_statistik_text(
self, mock_set_variable, mock_lade
):
basis_ergebnis = BeteiligungErgebnis(
modus="try2",
layers=[],
statistik={"beide": 0, "nur_verfahrensgebiet": 0, "nur_aglb": 0},
statistik_text="Übereinstimmend: 0\n...",
)
mock_lade.return_value = basis_ergebnis
zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}]
spaltennamen = [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK]
ergebnis = _try3_bootstrap_und_try2(object(), zeilen, spaltennamen, ["kz1"])
mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project")
self.assertEqual(ergebnis.modus, "try3")
self.assertTrue(ergebnis.statistik_text.startswith("Verfahrensnummer aus CSV: 12345"))
class TestLadeBeteiligungCsv(unittest.TestCase): class TestLadeBeteiligungCsv(unittest.TestCase):
def setUp(self): def setUp(self):
self._tmp_files = [] self._tmp_files = []