2026-04-22 14:53:45 +02:00
|
|
|
|
"""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,
|
2026-04-23 13:04:35 +02:00
|
|
|
|
"PREDICATE": [6], # 5 = overlaps, 6 = within; schließt reines Berühren aus
|
2026-04-22 14:53:45 +02:00
|
|
|
|
"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
|