Files
Plugin_SN_Verfahrensgebiet/functions/verfahrensgebiet_alkis_komplett.py
T

310 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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