"""sn_verfahrensgebiet/functions/verfahrensgebiet_alkis_komplett.py – Vollständiger VG-Lade-Workflow. Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets aus ALKIS mit anschließender Flurstücks-Verschneidung: 1. Verfahrensgebiet aus ``AX_BauRaumOderBodenordnungsrecht`` per VKZ laden. 2. BBox des VG ableiten. 3. Gemarkungsschlüssel per BBox aus ``AX_Gemarkung`` vorschlagen. 4. Flurstücke per Dialog (mit Vorschlag) laden. 5. Flurstücke räumlich auf das VG einschränken (``selectByLocation``). 6. Aus der Auswahl den enriched Verfahrensgebiet-Memory-Layer erzeugen. 7. ALKIS-VG-Hilfs-Layer entfernen, enriched Layer zurückgeben. Der enriched Layer enthält die Felder ``flurstueckskennzeichen`` und ``gemarkung_ax_gemarkung_schluessel_gemarkungsnummer`` des :mod:`~sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service`-Schemas und ist damit vollständig kompatibel mit ``ergaenze_verfahrensgebiet`` und ``entferne_aus_verfahrensgebiet``. **Abbruchverhalten:** Jeder Schritt, der :attr:`LoadStatus.NONE` zurückgibt oder kein Ergebnis liefert, bricht den Workflow ab. ALKIS-Hilfs-Layer werden dabei gesäubert, damit das Projekt nicht in einem inkonsistenten Zustand verbleibt. """ from __future__ import annotations from typing import Optional, Tuple from sn_basis.functions import QgsProject from sn_basis.functions import QMessageBox from sn_basis.functions.message_wrapper import info, warning from sn_basis.functions.variable_wrapper import get_variable from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import ( verfahrensgebiet_alkis, LoadStatus, ) from sn_verfahrensgebiet.functions.gemarkung_alkis import ( gemarkung_schluessel_und_namen_von_vg_geometrie, ) from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import ( erstelle_verfahrensgebiet_aus_auswahl, FELD_KENNZEICHEN, VERF_LAYER_NAME, ) # Feld für das Verfahrenskennzeichen (VKZ) im enriched Layer FELD_VKZ = "verfahrensnummer" # --------------------------------------------------------------------------- # Interne Hilfsfunktionen # --------------------------------------------------------------------------- def _entferne_hilfs_layer(project, layer_name: str) -> None: """Entfernt alle Layer mit dem angegebenen Namen aus dem Projekt. Wird verwendet, um den temporären ALKIS-VG-Layer nach der Verschneidung aus dem Projekt zu bereinigen. Parameters ---------- project : Aktuelles QGIS-Projekt. layer_name : Anzeigename des zu entfernenden Layers. """ for lyr in project.mapLayersByName(layer_name): project.removeMapLayer(lyr.id()) def _select_by_location(flst_layer, vg_layer) -> int: """Selektiert Flurstücke, die das Verfahrensgebiet überlappen oder darin liegen. Verwendet ``processing.run("native:selectbylocation")`` mit den Prädikaten ``overlaps`` (5) und ``within`` (6). Flurstücke, die das VG nur berühren (gemeinsame Grenzlinie oder -punkt, aber kein gemeinsames Inneres), werden damit ausgeschlossen. Parameters ---------- flst_layer : Flurstücks-WFS-Layer (Quelle der Selektion). vg_layer : Verfahrensgebiet-Memory-Layer (Schnittmaske). Returns ------- int Anzahl der selektierten Features. """ try: import processing # type: ignore[import] processing.run( "native:selectbylocation", { "INPUT": flst_layer, "PREDICATE": [6], # 5 = overlaps, 6 = within; schließt reines Berühren aus "INTERSECT": vg_layer, "METHOD": 0, # 0 = neue Selektion }, ) return flst_layer.selectedFeatureCount() except Exception: # Fallback: keine Selektion (z. B. im Unit-Test-Modus) return 0 def _fuege_vkz_feld_hinzu(layer, vkz: str) -> None: """Fügt das Feld ``verfahrensnummer`` zum enriched Layer hinzu und befüllt es mit dem Verfahrenskennzeichen, sofern dieses nicht leer ist. Das Feld wird nur hinzugefügt, wenn ``vkz`` nicht leer ist, damit der Layer ohne Projektvariable weiterhin funktioniert. Parameters ---------- layer : Enriched Verfahrensgebiet-Memory-Layer (in-place geändert). vkz : Verfahrenskennzeichen aus der Projektvariable ``verfahrensnummer``. """ if not vkz: return try: from sn_basis.functions import QgsField from sn_basis.functions.qt_wrapper import QVariant pr = layer.dataProvider() pr.addAttributes([QgsField(FELD_VKZ, QVariant.String)]) layer.updateFields() idx = layer.fields().indexFromName(FELD_VKZ) if idx < 0: return changes = {} for feat in layer.getFeatures(): changes[feat.id()] = {idx: vkz} pr.changeAttributeValues(changes) layer.updateFields() except Exception: pass # --------------------------------------------------------------------------- # Öffentliche API # --------------------------------------------------------------------------- def verfahrensgebiet_alkis_komplett( tab_widget, ) -> Tuple[Optional[object], Optional[object], LoadStatus]: """Vollständiger VG-Lade-Workflow: ALKIS-VG → BBox → Gemarkungen → Flurstücke → Verschneidung → enriched Memory-Layer. Schrittfolge: 1. ``verfahrensgebiet_alkis(tab_widget)`` – Verfahrensgebiet temporär laden 2. ``vg_layer.extent()`` – BBox ableiten 3. ``gemarkung_schluessel_und_namen_by_bbox(extent)`` – Schlüssel vorschlagen 4. ``flurstueck_alkis(tab_widget, vorgabe_schluessel, vorgabe_namen, bbox)`` – Flurstücke laden 5. ``_select_by_location(flst_layer, vg_layer)`` – räumliche Selektion 6. ``erstelle_verfahrensgebiet_aus_auswahl(flst_layer)`` – enriched Layer erzeugen 7. Ggf. VKZ-Feld setzen, ALKIS-VG-Hilfs-Layer entfernen Parameters ---------- tab_widget : Eltern-Widget für alle Dialoge (weitergereicht an Unteraufrufe). Returns ------- Tuple[Optional[enriched_layer], Optional[flst_layer], LoadStatus] - ``enriched_layer``: Memory-Layer „Verfahrensgebiet" mit ``flurstueckskennzeichen``- und ``gemarkung``-Feldern, oder ``None`` bei Abbruch/Fehler. - ``flst_layer``: Geladener WFS-Flurstück-Layer (bleibt im Projekt), oder ``None``. - ``LoadStatus``: Status des VG-Lade-Vorgangs (``FIRST``, ``RELOAD`` oder ``NONE`` bei Abbruch). Notes ----- Der temporäre ALKIS-VG-Layer (aus ``AX_BauRaumOderBodenordnungsrecht``) wird nach der Verschneidung aus dem Projekt entfernt. Im Projekt verbleiben nur der enriched Memory-Layer und der Flurstücks-WFS-Layer. """ project = QgsProject.instance() alkis_vg_layer = None try: # ------------------------------------------------------------------ # Schritt 1: VG aus ALKIS laden (Zwischenobjekt) # ------------------------------------------------------------------ info("VG laden", "Schritt 1/6: Verfahrensgebiet aus ALKIS laden …", duration=3) alkis_vg_layer, vg_status, vg_features = verfahrensgebiet_alkis(tab_widget) if vg_status == LoadStatus.NONE or not alkis_vg_layer: return None, None, LoadStatus.NONE # Temporär ins Projekt hinzufügen, damit selectByLocation ihn findet if vg_status in (LoadStatus.FIRST, LoadStatus.RELOAD): project.addMapLayer(alkis_vg_layer) # ------------------------------------------------------------------ # Schritt 2 (entfernt): BBox-Ableitung war hier – WFS unterstützt kein # BBox-Filter, daher wird der Extent nicht mehr benötigt. # ------------------------------------------------------------------ # ------------------------------------------------------------------ # Schritt 3: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln # ------------------------------------------------------------------ info("VG laden", "Schritt 2/6: Gemarkungsschlüssel ermitteln \u2026", duration=3) # VG-Geometrie aus den Roh-WFS-Features bilden (Union, damit # alle Teilflächen berücksichtigt werden) from sn_basis.functions import QgsGeometry as _QgsGeometry if vg_features: vg_geom = _QgsGeometry.unaryUnion([f.geometry() for f in vg_features]) else: # Bei KEEP-Status keine WFS-Features vorhanden; Fallback auf Layergeometrie vg_geom = next(alkis_vg_layer.getFeatures()).geometry() schluessel_namen = gemarkung_schluessel_und_namen_von_vg_geometrie(vg_geom) vorgabe_schluessel = [nr for nr, _ in schluessel_namen] vorgabe_namen = [nm for _, nm in schluessel_namen if nm] if not vorgabe_schluessel: warning( "VG laden", "Keine Gemarkungen in der BBox gefunden. Bitte Schlüssel manuell eingeben.", duration=5, ) # ------------------------------------------------------------------ # Schritt 4: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt) # ------------------------------------------------------------------ info("VG laden", "Schritt 3/6: Flurstücke aus ALKIS laden …", duration=3) flst_layer, flst_status = flurstueck_alkis( tab_widget, vorgabe_schluessel=vorgabe_schluessel, vorgabe_namen=vorgabe_namen if vorgabe_namen else None, # bbox wird nicht übergeben – WFS unterstützt keinen BBox-Filter ohne_dialog=True, # kein Nutzer-Dialog in der Pipeline ) if flst_status == LoadStatus.NONE or not flst_layer: return None, None, LoadStatus.NONE # ------------------------------------------------------------------ # Schritt 5: Räumliche Selektion # ------------------------------------------------------------------ info("VG laden", "Schritt 4/6: Flurstücke räumlich selektieren …", duration=3) anzahl = _select_by_location(flst_layer, alkis_vg_layer) if anzahl == 0: QMessageBox.warning( tab_widget, "Keine Flurstücke im VG", "Es wurden keine Flurstücke gefunden, die das Verfahrensgebiet schneiden.\n" "Bitte Gemarkungsschlüssel und Ausdehnung prüfen.", ) return None, flst_layer, LoadStatus.NONE # ------------------------------------------------------------------ # Schritt 6: Enriched Memory-Layer aus Auswahl erzeugen # ------------------------------------------------------------------ info("VG laden", "Schritt 5/6: Verfahrensgebiet-Layer erzeugen …", duration=3) enriched_layer, erfolgreich, fehlgeschlagen = erstelle_verfahrensgebiet_aus_auswahl( flst_layer ) if not enriched_layer or not enriched_layer.isValid(): QMessageBox.critical( tab_widget, "Fehler", "Verfahrensgebiet-Layer konnte nicht erstellt werden.", ) return None, flst_layer, LoadStatus.NONE # ------------------------------------------------------------------ # Schritt 7: VKZ-Feld setzen (optional, wenn Projektvariable vorhanden) # ------------------------------------------------------------------ vkz = get_variable("verfahrensnummer") or "" _fuege_vkz_feld_hinzu(enriched_layer, vkz) info( "VG laden", f"Schritt 6/6: Fertig. {erfolgreich} Flurstücke verarbeitet, " f"{fehlgeschlagen} fehlgeschlagen.", duration=5, ) return enriched_layer, flst_layer, vg_status finally: # Hilfs-Layer (ALKIS-VG) stets aus dem Projekt entfernen if alkis_vg_layer is not None: _entferne_hilfs_layer(project, VERF_LAYER_NAME) # Auch nach Layername des ALKIS-Temp-Layers bereinigen _entferne_hilfs_layer(project, "TempVerfahrensgebiet") try: project.removeMapLayer(alkis_vg_layer.id()) except Exception: pass