Portiert aus origin/unstable (Feature "Beteiligung prüfen": Abgleich einer BI-Server-CSV-Exportliste mit dem ALKIS-Flurstücksbestand des Verfahrensgebiets), bewusst OHNE den dortigen silent-Modus für verfahrensgebiet_alkis()/verfahrensgebiet_alkis_komplett(): - try1 (Verfahrensgebiet-Layer mit Kennzeichen vorhanden) bleibt unverändert – schneller Mengenvergleich ohne WFS-Zugriff. - try2 (Layer vorhanden, aber ohne verwertbare Kennzeichen) und try3 (kein Layer vorhanden) laden nicht mehr automatisch/silent über die komplette ALKIS-Pipeline, sondern fordern den Nutzer per Meldung auf, das Verfahrensgebiet zunächst regulär über "Aus ALKIS laden" zu laden und die Beteiligungsprüfung danach erneut zu starten (landet dann automatisch bei try1). Grund: ein automatischer Durchlauf ohne Nutzerinteraktion (mehrdeutige BROR-Treffer, Flächenabweichungen) hat sich in der Praxis nicht als zuverlässig genug erwiesen. - verfahrensgebiet_alkis.py/verfahrensgebiet_alkis_komplett.py bleiben dadurch unangetastet – kein silent-Parameter, kein Konflikt mit dem heutigen Zwei-Dialog-Split und dem Landkreis-Fallback. - Neuer Button "Beteiligung prüfen" in working_tab.py, delegiert an beteiligung_pruefen(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
873 lines
38 KiB
Python
873 lines
38 KiB
Python
"""sn_verfahrensgebiet/ui/tabs/working_tab.py – Bearbeitungs-Tab.
|
||
Verwaltet die Verfahrensgebiet-Abgrenzung und ALKIS-Integration.
|
||
"""
|
||
|
||
from sn_basis.functions.qt_wrapper import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QMessageBox
|
||
from sn_basis.functions.qt_wrapper import Qt
|
||
from sn_basis.functions import Qgis, QgsProject #QgsMessageLog
|
||
from sn_basis.functions.qgisui_wrapper import QgsMapLayerComboBox
|
||
from sn_basis.functions.qgiscore_wrapper import QgsMapLayerProxyModel
|
||
from sn_basis.functions.verfahrensgebiet_manager import get_verfahrensgebiet_layer_id, set_verfahrensgebiet_layer_id
|
||
from sn_basis.functions.dialog_wrapper import show_info_dialog, create_progress_dialog, ask_yes_no
|
||
from sn_basis.functions.variable_wrapper import get_variable
|
||
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||
from qgis.utils import iface
|
||
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
|
||
from sn_verfahrensgebiet.functions.flurstueck_alkis import (
|
||
flurstueck_alkis,
|
||
lade_flurstuecke_per_gemarkung,
|
||
ALKIS_SF_URL,
|
||
FLST_LAYER_NAME,
|
||
)
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
||
verfahrensgebiet_alkis_komplett,
|
||
BROR_LAYER_NAME,
|
||
)
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett
|
||
from sn_verfahrensgebiet.functions.beteiligung_pruefung import (
|
||
beteiligung_pruefen,
|
||
BeteiligungPruefungFehler,
|
||
)
|
||
from sn_verfahrensgebiet.functions.grenzpunkt_alkis import grenzpunkte_gemarkungsweise, JOINED_LAYER_NAME
|
||
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
||
gemarkung_schluessel_und_namen_von_vg_geometrie,
|
||
lade_gemarkungen_layer,
|
||
ALKIS_VEREINF_URL,
|
||
)
|
||
from sn_verfahrensgebiet.functions.umringstatistik_grenzpunkte import umringstatistik_grenzpunkte
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
|
||
pruefe_flurstueck_layer,
|
||
pruefe_auswahl,
|
||
erstelle_verfahrensgebiet_aus_auswahl,
|
||
ergaenze_verfahrensgebiet,
|
||
entferne_aus_verfahrensgebiet,
|
||
ersetze_verfahrensgebiet,
|
||
)
|
||
from sn_basis.functions.message_wrapper import success, info, warning, error
|
||
from sn_basis.functions.ly_style_wrapper import apply_style
|
||
|
||
class WorkingTab(QWidget):
|
||
"""Tab für die Verfahrensgebiet-Bearbeitung und Verwaltung.
|
||
|
||
Attributes:
|
||
tab_title: Anzeigetitel des Tabs ("Bearbeitung")
|
||
"""
|
||
|
||
tab_title = "Bearbeitung"
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
|
||
layout = QVBoxLayout()
|
||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||
|
||
# Abschnitt 1: Verfahrensgebiet laden
|
||
row_verf_header = QHBoxLayout()
|
||
lbl_verf = QLabel("<b>Verfahrensgebiet</b>")
|
||
self.haken_verf = self._haken_label()
|
||
row_verf_header.addWidget(lbl_verf)
|
||
row_verf_header.addWidget(self.haken_verf)
|
||
row_verf_header.addStretch()
|
||
layout.addLayout(row_verf_header)
|
||
|
||
row_verf_buttons = QHBoxLayout()
|
||
self.btn_verf_alkis = QPushButton("Aus ALKIS laden")
|
||
self.btn_verf_shape = QPushButton("Aus Shape laden")
|
||
row_verf_buttons.addWidget(self.btn_verf_alkis)
|
||
row_verf_buttons.addWidget(self.btn_verf_shape)
|
||
layout.addLayout(row_verf_buttons)
|
||
|
||
row_beteiligung_buttons = QHBoxLayout()
|
||
self.btn_beteiligung_pruefen = QPushButton("Beteiligung prüfen")
|
||
self.btn_beteiligung_pruefen.setToolTip(
|
||
"CSV-Export aus dem BI-Server verwenden. benötigte Spalten: "
|
||
"FLA_Verfahren-VEF_VKZF, FLA-Flurstuecke-Flurstueck, FLA_Beteiligung"
|
||
)
|
||
row_beteiligung_buttons.addWidget(self.btn_beteiligung_pruefen)
|
||
layout.addLayout(row_beteiligung_buttons)
|
||
|
||
# Abschnitt 1b: Verfahrensgebiet-Layer auswählen
|
||
row_layer_label = QHBoxLayout()
|
||
lbl_layer = QLabel("Oder: Verfahrensgebiet-Layer auswählen")
|
||
lbl_layer.setStyleSheet("font-style: italic; color: gray; margin-top: 8px;")
|
||
row_layer_label.addWidget(lbl_layer)
|
||
row_layer_label.addStretch()
|
||
layout.addLayout(row_layer_label)
|
||
|
||
self.verf_layer_combo = QgsMapLayerComboBox(self)
|
||
self.verf_layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)
|
||
self.verf_layer_combo.setToolTip("Wähle den Verfahrensgebiet-Layer, oder lasse es leer für Automatik")
|
||
self.verf_layer_combo.layerChanged.connect(self._on_verf_layer_changed)
|
||
layout.addWidget(self.verf_layer_combo)
|
||
|
||
# Abschnitt 2: Gemarkungen
|
||
row_gem_header = QHBoxLayout()
|
||
lbl_gem = QLabel("<b>Gemarkungen</b>")
|
||
self.haken_gem = self._haken_label()
|
||
row_gem_header.addWidget(lbl_gem)
|
||
row_gem_header.addWidget(self.haken_gem)
|
||
row_gem_header.addStretch()
|
||
layout.addLayout(row_gem_header)
|
||
|
||
row_gem_buttons = QHBoxLayout()
|
||
self.btn_gem_alkis = QPushButton("Aus ALKIS laden")
|
||
row_gem_buttons.addWidget(self.btn_gem_alkis)
|
||
layout.addLayout(row_gem_buttons)
|
||
|
||
# Abschnitt 2b: Flurstücke
|
||
row_flurst_header = QHBoxLayout()
|
||
lbl_flurst = QLabel("<b>Flurstücke</b>")
|
||
self.haken_flurst = self._haken_label()
|
||
row_flurst_header.addWidget(lbl_flurst)
|
||
row_flurst_header.addWidget(self.haken_flurst)
|
||
row_flurst_header.addStretch()
|
||
layout.addLayout(row_flurst_header)
|
||
|
||
row_flurst_buttons = QHBoxLayout()
|
||
self.btn_flurst_alkis = QPushButton("Aus ALKIS laden")
|
||
self.btn_flurst_shape = QPushButton("Aus Shape laden")
|
||
row_flurst_buttons.addWidget(self.btn_flurst_alkis)
|
||
row_flurst_buttons.addWidget(self.btn_flurst_shape)
|
||
layout.addLayout(row_flurst_buttons)
|
||
|
||
row_flurst_gem_buttons = QHBoxLayout()
|
||
self.btn_flurst_aus_gemarkungen = QPushButton("aus gewählten Gemarkungen laden")
|
||
row_flurst_gem_buttons.addWidget(self.btn_flurst_aus_gemarkungen)
|
||
layout.addLayout(row_flurst_gem_buttons)
|
||
|
||
# Abschnitt 2c: Grenzpunkte
|
||
row_grenz_header = QHBoxLayout()
|
||
lbl_grenz = QLabel("<b>Grenzpunkte</b>")
|
||
self.haken_grenz = self._haken_label()
|
||
row_grenz_header.addWidget(lbl_grenz)
|
||
row_grenz_header.addWidget(self.haken_grenz)
|
||
row_grenz_header.addStretch()
|
||
layout.addLayout(row_grenz_header)
|
||
|
||
row_grenz_buttons = QHBoxLayout()
|
||
self.btn_grenz_alkis = QPushButton("Grenzpunkte aus ALKIS laden")
|
||
row_grenz_buttons.addWidget(self.btn_grenz_alkis)
|
||
layout.addLayout(row_grenz_buttons)
|
||
|
||
row_umring_buttons = QHBoxLayout()
|
||
self.btn_umring_statistik = QPushButton("Umringstatistik Grenzpunkte")
|
||
row_umring_buttons.addWidget(self.btn_umring_statistik)
|
||
layout.addLayout(row_umring_buttons)
|
||
|
||
# Abschnitt 3: Verfahrensgebiet erstellen
|
||
row_verf_erstellen_header = QHBoxLayout()
|
||
lbl_verf_erstellen = QLabel("<b>Verfahrensgebiet erstellen</b>")
|
||
row_verf_erstellen_header.addWidget(lbl_verf_erstellen)
|
||
row_verf_erstellen_header.addStretch()
|
||
layout.addLayout(row_verf_erstellen_header)
|
||
|
||
row_verf_erstellen_buttons = QHBoxLayout()
|
||
self.btn_verf_aus_auswahl = QPushButton("aus Auswahl")
|
||
self.btn_verf_aus_liste = QPushButton("Aus Liste")
|
||
row_verf_erstellen_buttons.addWidget(self.btn_verf_aus_auswahl)
|
||
row_verf_erstellen_buttons.addWidget(self.btn_verf_aus_liste)
|
||
layout.addLayout(row_verf_erstellen_buttons)
|
||
|
||
layout.addStretch()
|
||
self.setLayout(layout)
|
||
|
||
# Pruefmanager für interaktive Entscheidungen (Ergänzen/Entfernen/Ersetzen)
|
||
self.pruefmanager = Pruefmanager(ui_modus="qgis", parent=self)
|
||
|
||
# Signale verbinden
|
||
self.btn_verf_alkis.clicked.connect(self.handle_verf_alkis)
|
||
self.btn_flurst_alkis.clicked.connect(self.handle_flurst_alkis)
|
||
self.btn_flurst_aus_gemarkungen.clicked.connect(self.handle_flurst_aus_gemarkungen)
|
||
self.btn_gem_alkis.clicked.connect(self.handle_gem_alkis)
|
||
self.btn_grenz_alkis.clicked.connect(self.handle_grenz_alkis)
|
||
self.btn_umring_statistik.clicked.connect(self.handle_umringstatistik)
|
||
self.btn_verf_aus_auswahl.clicked.connect(self.handle_verf_aus_auswahl)
|
||
self.btn_verf_aus_liste.clicked.connect(self.handle_verf_aus_liste)
|
||
self.btn_verf_shape.clicked.connect(self.handle_verf_shape)
|
||
# self.btn_flurst_shape.clicked.connect(self.handle_flurst_shape)
|
||
self.btn_beteiligung_pruefen.clicked.connect(self.handle_beteiligung_pruefen)
|
||
|
||
# Beim Init: aktuelle Verfahrensgebiet-Layer-ID laden und im Dropdown anzeigen
|
||
self._restore_verf_layer_selection()
|
||
|
||
def handle_verf_alkis(self) -> None:
|
||
"""Startet den vollständigen VG-Lade-Workflow (ALKIS → Gemarkungen → Flurstücke →
|
||
Verschneidung → Flächenvergleich → opt. Umringsprüfung).
|
||
|
||
Delegiert an :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.verfahrensgebiet_alkis_komplett`.
|
||
Nach Erfolg werden enriched VG-Layer, Flurstücks-Layer und BROR-Layer
|
||
ins Projekt eingefügt, Styles angewendet und die UI-Elemente aktualisiert.
|
||
"""
|
||
self._set_busy(self.btn_verf_alkis, True, "Aus ALKIS laden")
|
||
try:
|
||
enriched_layer, flst_layer, bror_layer, status = verfahrensgebiet_alkis_komplett(self)
|
||
|
||
if status == LoadStatus.NONE or not enriched_layer:
|
||
set_verfahrensgebiet_layer_id("")
|
||
return
|
||
|
||
project = QgsProject.instance()
|
||
|
||
# Flurstücks-Layer ins Projekt (sofern neu geladen)
|
||
if flst_layer and flst_layer.isValid():
|
||
existing_flst = project.mapLayersByName(flst_layer.name())
|
||
if not existing_flst:
|
||
project.addMapLayer(flst_layer)
|
||
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
||
self.setze_haken(self.haken_flurst, True)
|
||
|
||
# BROR-Layer bekommt seinen Style bereits direkt beim Laden in
|
||
# verfahrensgebiet_alkis_komplett() (auch im "Nur laden"-Fall).
|
||
|
||
# Bei "Nur Objekte laden" (kein weiterer Ablauf) ist enriched_layer
|
||
# tatsächlich nur der rohe BROR-Layer (erkennbar am Namen) – hier
|
||
# fand keine Flurstücksverschneidung statt, es gibt also keinen
|
||
# echten VG-Layer: VG-Style/-Kombobox/-Haken/-Meldung überspringen.
|
||
if enriched_layer.name() != BROR_LAYER_NAME:
|
||
# Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden")
|
||
if not project.mapLayer(enriched_layer.id()):
|
||
project.addMapLayer(enriched_layer)
|
||
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
||
iface.mapCanvas().setExtent(enriched_layer.extent())
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_verf, True)
|
||
|
||
self.verf_layer_combo.setLayer(enriched_layer)
|
||
self._on_verf_layer_changed(enriched_layer)
|
||
|
||
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
||
success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")
|
||
|
||
def handle_verf_shape(self) -> None:
|
||
"""Lädt ein Verfahrensgebiet aus einem ZIP-Archiv mit Shapefile-Set.
|
||
|
||
Delegiert an :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett.verfahrensgebiet_shape_komplett`.
|
||
Nach Erfolg werden Shape-VG-Layer und Flurstücks-Layer ins Projekt eingefügt,
|
||
Styles angewendet und die UI-Elemente aktualisiert.
|
||
"""
|
||
# Vorprüfung: Verfahrensnummer muss gesetzt sein
|
||
vkz = get_variable("verfahrensnummer") or ""
|
||
if not str(vkz).strip():
|
||
if not ask_yes_no(
|
||
"Verfahrensnummer nicht gesetzt",
|
||
"Verfahrensnummer nicht gesetzt. Trotzdem fortfahren?",
|
||
default=False,
|
||
parent=self,
|
||
):
|
||
return
|
||
|
||
self._set_busy(self.btn_verf_shape, True, "Aus Shape laden")
|
||
try:
|
||
enriched_layer, flst_layer, bror_layer, status = verfahrensgebiet_shape_komplett(self)
|
||
|
||
if status == LoadStatus.NONE or not enriched_layer:
|
||
set_verfahrensgebiet_layer_id("")
|
||
return
|
||
|
||
project = QgsProject.instance()
|
||
|
||
# Flurstücks-Layer ins Projekt (sofern neu geladen)
|
||
if flst_layer and flst_layer.isValid():
|
||
existing_flst = project.mapLayersByName(flst_layer.name())
|
||
if not existing_flst:
|
||
project.addMapLayer(flst_layer)
|
||
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
||
self.setze_haken(self.haken_flurst, True)
|
||
|
||
# BROR-Layer: QML-Stil anwenden (sofern geladen)
|
||
if bror_layer and bror_layer.isValid():
|
||
apply_style(bror_layer, "BauRaumOderBodenordnungsrecht.qml")
|
||
|
||
# Shape-VG-Layer ins Projekt
|
||
if not project.mapLayer(enriched_layer.id()):
|
||
project.addMapLayer(enriched_layer)
|
||
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
||
iface.mapCanvas().setExtent(enriched_layer.extent())
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_verf, True)
|
||
|
||
self.verf_layer_combo.setLayer(enriched_layer)
|
||
self._on_verf_layer_changed(enriched_layer)
|
||
|
||
success("Verfahrensgebiet", "Layer aus Shape geladen.", duration=5)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_verf_shape, False, "Aus Shape laden")
|
||
|
||
def handle_beteiligung_pruefen(self) -> None:
|
||
"""Gleicht eine BI-Server-CSV-Beteiligungsliste mit dem Verfahrensgebiet ab.
|
||
|
||
Delegiert an :func:`~sn_verfahrensgebiet.functions.beteiligung_pruefung.beteiligung_pruefen`.
|
||
Ist noch kein (verwertbares) Verfahrensgebiet geladen, fordert die
|
||
Funktion den Nutzer per Meldung auf, es zunächst über "Aus ALKIS
|
||
laden" zu laden – es gibt kein automatisches Laden im Hintergrund.
|
||
Sonst werden ein oder mehrere Ergebnis-Layer ins Projekt eingefügt,
|
||
gestylt und eine Statistik als Messagebox angezeigt.
|
||
"""
|
||
self._set_busy(self.btn_beteiligung_pruefen, True, "Beteiligung prüfen")
|
||
try:
|
||
try:
|
||
ergebnis = beteiligung_pruefen(self)
|
||
except BeteiligungPruefungFehler as exc:
|
||
QMessageBox.critical(self, "Beteiligung prüfen", str(exc))
|
||
return
|
||
|
||
if ergebnis is None:
|
||
return # Nutzerabbruch des Dateidialogs
|
||
|
||
project = QgsProject.instance()
|
||
for layer, style_name in ergebnis.layers:
|
||
if layer is None or not layer.isValid():
|
||
continue
|
||
if not project.mapLayer(layer.id()):
|
||
project.addMapLayer(layer)
|
||
if style_name:
|
||
apply_style(layer, style_name)
|
||
|
||
iface.mapCanvas().refresh()
|
||
QMessageBox.information(self, "Beteiligung prüfen", ergebnis.statistik_text)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_beteiligung_pruefen, False, "Beteiligung prüfen")
|
||
|
||
def _set_busy(self, button: QPushButton, busy: bool, original_text: str = "Aus ALKIS laden") -> None:
|
||
"""Setzt den Busy-State eines Buttons.
|
||
|
||
Parameters
|
||
----------
|
||
button :
|
||
Der zu sperrende/entsperrende Button.
|
||
busy : bool
|
||
True zum Sperren (Text → "Lade ..."), False zum Freigeben.
|
||
original_text : str
|
||
Text, der nach dem Freigeben wiederhergestellt wird.
|
||
Jeder Button gibt seinen eigenen Originaltext explizit an.
|
||
"""
|
||
button.setEnabled(not busy)
|
||
button.setText("Lade ..." if busy else original_text)
|
||
|
||
def _haken_label(self) -> QLabel:
|
||
label = QLabel()
|
||
label.setFixedSize(20, 20)
|
||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||
label.setText("") # Start ohne Haken
|
||
return label
|
||
|
||
def setze_haken(self, label: QLabel, aktiv: bool):
|
||
"""Zeigt oder entfernt den grünen Haken (Unicode), ohne Theme-Icons."""
|
||
if aktiv:
|
||
label.setText("✓")
|
||
label.setStyleSheet("color: #2ea043; font-weight: bold;")
|
||
else:
|
||
label.setText("")
|
||
label.setStyleSheet("")
|
||
|
||
def _on_verf_layer_changed(self, layer) -> None:
|
||
"""Callback: Nutzer hat einen anderen Layer im Dropdown gewählt."""
|
||
if layer and layer.isValid():
|
||
# Speichere die Layer-ID in der zentralen Variable
|
||
set_verfahrensgebiet_layer_id(layer.id())
|
||
else:
|
||
# Layer is None oder invalid; leere die Variable
|
||
set_verfahrensgebiet_layer_id("")
|
||
|
||
def _restore_verf_layer_selection(self) -> None:
|
||
"""Beim Tab-Init: Lade die zuletzt gespeicherte Verfahrensgebiet-Layer-ID."""
|
||
layer_id = get_verfahrensgebiet_layer_id()
|
||
if layer_id:
|
||
try:
|
||
project = QgsProject.instance()
|
||
layer = project.mapLayer(layer_id)
|
||
if layer and layer.isValid():
|
||
self.verf_layer_combo.setLayer(layer)
|
||
except Exception:
|
||
pass
|
||
|
||
# ------------------------------------------------------------------
|
||
# Flurstücke-Handler
|
||
# ------------------------------------------------------------------
|
||
|
||
def handle_flurst_alkis(self) -> None:
|
||
"""Lädt Flurstücke aus ALKIS-WFS anhand von Gemarkungsschlüsseln."""
|
||
self._set_busy(self.btn_flurst_alkis, True, "Aus ALKIS laden")
|
||
try:
|
||
layer, status = flurstueck_alkis(self)
|
||
if not layer or not layer.isValid():
|
||
return
|
||
|
||
if status in (LoadStatus.FIRST, LoadStatus.RELOAD):
|
||
QgsProject.instance().addMapLayer(layer)
|
||
# iface.mapCanvas().setExtent(layer.extent()) # Kartenausschnitt bleibt unverändert
|
||
apply_style(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_flurst, True)
|
||
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
||
success("Flurstücke", f"Layer wurde {msg}.", duration=5)
|
||
|
||
elif status == LoadStatus.KEEP:
|
||
self.setze_haken(self.haken_flurst, True)
|
||
info("Flurstücke", "Vorhandener Layer wurde behalten.", duration=4)
|
||
|
||
elif status == LoadStatus.APPEND:
|
||
self.setze_haken(self.haken_flurst, True)
|
||
iface.mapCanvas().refresh()
|
||
success("Flurstücke", "Features wurden an den vorhandenen Layer angehängt.", duration=5)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_flurst_alkis, False, "Aus ALKIS laden")
|
||
|
||
def handle_flurst_aus_gemarkungen(self) -> None:
|
||
"""Lädt Flurstücke aus ALKIS anhand der Auswahl im Layer 'Gemarkungen'.
|
||
|
||
- Kein Layer 'Gemarkungen': Layer wird geladen, Nutzer aufgefordert Auswahl zu treffen.
|
||
- Keine Auswahl: Warnmeldung.
|
||
- Auswahl vorhanden: Gemarkungsschlüssel extrahieren und Flurstücke direkt laden.
|
||
"""
|
||
self._set_busy(self.btn_flurst_aus_gemarkungen, True, "aus gewählten Gemarkungen laden")
|
||
try:
|
||
project = QgsProject.instance()
|
||
|
||
# Fall A: Layer 'Gemarkungen' nicht vorhanden → laden
|
||
gem_layers = project.mapLayersByName("Gemarkungen")
|
||
if not gem_layers:
|
||
gem_layer = lade_gemarkungen_layer()
|
||
if not gem_layer:
|
||
error(
|
||
"Gemarkungen",
|
||
f"Gemarkungslayer konnte nicht geladen werden – "
|
||
f"Dienst nicht erreichbar ({ALKIS_VEREINF_URL}). "
|
||
f"Bitte Netzwerkverbindung prüfen.",
|
||
duration=0,
|
||
)
|
||
return
|
||
project.addMapLayer(gem_layer)
|
||
apply_style(gem_layer, "gemarkungen_beschriftet.qml")
|
||
self.setze_haken(self.haken_gem, True)
|
||
QMessageBox.information(
|
||
self,
|
||
"Gemarkungen",
|
||
"Gemarkungslayer wurde geladen – bitte Gemarkungen auswählen.",
|
||
)
|
||
return
|
||
|
||
gemarkungen_layer = gem_layers[0]
|
||
|
||
# Fall B: Keine Auswahl
|
||
if not gemarkungen_layer.selectedFeatureCount():
|
||
QMessageBox.warning(
|
||
self,
|
||
"Gemarkungen",
|
||
"Bitte mindestens eine Gemarkung im Layer Gemarkungen auswählen.",
|
||
)
|
||
return
|
||
|
||
# Fall C: Auswahl vorhanden → Schlüssel extrahieren
|
||
vorgabe_schluessel = []
|
||
vorgabe_namen = []
|
||
for feat in gemarkungen_layer.selectedFeatures():
|
||
try:
|
||
schluessel = str(feat["schluessel"]).strip()
|
||
nummer = schluessel[-4:] if len(schluessel) >= 4 else schluessel
|
||
except Exception:
|
||
nummer = ""
|
||
try:
|
||
name = str(feat["ave_name"]).strip()
|
||
except Exception:
|
||
name = ""
|
||
if nummer:
|
||
vorgabe_schluessel.append(nummer)
|
||
vorgabe_namen.append(name)
|
||
|
||
if not vorgabe_schluessel:
|
||
error(
|
||
"Flurstücke",
|
||
"Aus der Gemarkungsauswahl konnten keine gültigen Gemarkungsschlüssel extrahiert werden.",
|
||
duration=8,
|
||
)
|
||
return
|
||
|
||
# Prüfen ob bereits ein Flurstücke-Layer vorhanden → 3-Knopf-Dialog
|
||
existing_flst = project.mapLayersByName(FLST_LAYER_NAME)
|
||
if existing_flst:
|
||
msg_box = QMessageBox(self)
|
||
msg_box.setWindowTitle("Layer bereits vorhanden")
|
||
msg_box.setText(
|
||
f"Der Layer '{FLST_LAYER_NAME}' existiert bereits.\nWas soll geschehen?"
|
||
)
|
||
btn_ueberschreiben = msg_box.addButton("Überschreiben", QMessageBox.ButtonRole.DestructiveRole)
|
||
btn_anhaengen = msg_box.addButton("Anhängen", QMessageBox.ButtonRole.AcceptRole)
|
||
btn_abbrechen = msg_box.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole)
|
||
msg_box.setDefaultButton(btn_abbrechen)
|
||
msg_box.exec()
|
||
clicked = msg_box.clickedButton()
|
||
if clicked is btn_abbrechen:
|
||
return
|
||
if clicked is btn_anhaengen:
|
||
temp_layer = lade_flurstuecke_per_gemarkung(vorgabe_schluessel)
|
||
if not temp_layer or not temp_layer.isValid() or temp_layer.featureCount() == 0:
|
||
error(
|
||
"Flurstücke",
|
||
f"Flurstücke konnten nicht geladen werden – WFS-Dienst ({ALKIS_SF_URL}) "
|
||
f"nicht erreichbar oder keine Features für die gewählten Gemarkungen.",
|
||
duration=0,
|
||
)
|
||
return
|
||
existing_flst[0].dataProvider().addFeatures(list(temp_layer.getFeatures()))
|
||
existing_flst[0].updateExtents()
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_flurst, True)
|
||
success("Flurstücke", "Features wurden an den vorhandenen Layer angehängt.", duration=5)
|
||
return
|
||
# else: Überschreiben → fall-through zum flurstueck_alkis(ohne_dialog=True)-Aufruf
|
||
|
||
# Flurstücke direkt laden (kein Dialog)
|
||
layer, status = flurstueck_alkis(
|
||
self,
|
||
vorgabe_schluessel=vorgabe_schluessel,
|
||
vorgabe_namen=vorgabe_namen,
|
||
ohne_dialog=True,
|
||
)
|
||
|
||
if not layer or not layer.isValid():
|
||
error(
|
||
"Flurstücke",
|
||
f"Flurstücke konnten nicht geladen werden – WFS-Dienst ({ALKIS_SF_URL}) "
|
||
f"nicht erreichbar oder keine Features für die gewählten Gemarkungen.",
|
||
duration=0,
|
||
)
|
||
return
|
||
|
||
if status in (LoadStatus.FIRST, LoadStatus.RELOAD):
|
||
project.addMapLayer(layer)
|
||
apply_style(layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_flurst, True)
|
||
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
||
success("Flurstücke", f"Layer wurde {msg}.", duration=5)
|
||
|
||
elif status == LoadStatus.KEEP:
|
||
self.setze_haken(self.haken_flurst, True)
|
||
info("Flurstücke", "Vorhandener Layer wurde behalten.", duration=4)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_flurst_aus_gemarkungen, False, "aus gewählten Gemarkungen laden")
|
||
|
||
# ------------------------------------------------------------------
|
||
# Gemarkungen-Handler
|
||
# ------------------------------------------------------------------
|
||
|
||
def handle_gem_alkis(self) -> None:
|
||
"""Lädt den KatasterBezirk-Layer aus dem vereinfachten ALKIS-WFS
|
||
als Layer „Gemarkungen" ins Projekt.
|
||
|
||
Ist ein Layer namens „Gemarkungen" bereits vorhanden, wird er behalten
|
||
und eine Info-Meldung angezeigt (kein doppeltes Laden).
|
||
"""
|
||
self._set_busy(self.btn_gem_alkis, True, "Aus ALKIS laden")
|
||
try:
|
||
existing = QgsProject.instance().mapLayersByName("Gemarkungen")
|
||
if existing:
|
||
self.setze_haken(self.haken_gem, True)
|
||
info("Gemarkungen", "Layer 'Gemarkungen' ist bereits im Projekt vorhanden.", duration=4)
|
||
return
|
||
|
||
layer = lade_gemarkungen_layer()
|
||
if not layer:
|
||
warning(
|
||
"Gemarkungen",
|
||
"Layer konnte nicht geladen werden – Dienst nicht erreichbar oder ungültig.",
|
||
duration=6,
|
||
)
|
||
return
|
||
|
||
QgsProject.instance().addMapLayer(layer)
|
||
apply_style(layer, "gemarkungen_beschriftet.qml")
|
||
self.setze_haken(self.haken_gem, True)
|
||
success("Gemarkungen", "Layer 'Gemarkungen' wurde geladen.", duration=4)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_gem_alkis, False, "Aus ALKIS laden")
|
||
|
||
# ------------------------------------------------------------------
|
||
# Verfahrensgebiet-erstellen-Handler
|
||
# ------------------------------------------------------------------
|
||
|
||
def handle_verf_aus_auswahl(self) -> None:
|
||
"""Erstellt oder aktualisiert den Verfahrensgebiet-Layer aus der Flurstück-Auswahl."""
|
||
self._set_busy(self.btn_verf_aus_auswahl, True, "aus Auswahl")
|
||
try:
|
||
# 1. Aktiven Layer ermitteln
|
||
active_layer = iface.activeLayer()
|
||
if not active_layer:
|
||
warning(
|
||
"Verfahrensgebiet erstellen",
|
||
"Kein aktiver Layer. Bitte den Flurstück-Layer aktivieren.",
|
||
duration=None,
|
||
)
|
||
return
|
||
|
||
# 2. Vorbedingungen via Layerpruefer
|
||
ergebnis = pruefe_flurstueck_layer(active_layer)
|
||
if not ergebnis.ok:
|
||
warning("Verfahrensgebiet erstellen", ergebnis.meldung, duration=None)
|
||
return
|
||
|
||
# 3. Auswahl prüfen
|
||
ergebnis_auswahl = pruefe_auswahl(active_layer)
|
||
if not ergebnis_auswahl.ok:
|
||
warning("Verfahrensgebiet erstellen", ergebnis_auswahl.meldung, duration=None)
|
||
return
|
||
|
||
# 4. Bestehenden Verfahrensgebiet-Layer prüfen
|
||
existing_layers = QgsProject.instance().mapLayersByName("Verfahrensgebiet")
|
||
|
||
if not existing_layers:
|
||
# Erstlauf: neuen Memory-Layer erzeugen
|
||
layer, erfolgreich, fehlgeschlagen = erstelle_verfahrensgebiet_aus_auswahl(
|
||
active_layer
|
||
)
|
||
if not layer or not layer.isValid():
|
||
error("Verfahrensgebiet erstellen", "Layer konnte nicht erstellt werden.")
|
||
return
|
||
QgsProject.instance().addMapLayer(layer)
|
||
apply_style(layer, "verfahrensgebiet.qml")
|
||
iface.mapCanvas().refresh()
|
||
self.verf_layer_combo.setLayer(layer)
|
||
self._on_verf_layer_changed(layer)
|
||
self.setze_haken(self.haken_verf, True)
|
||
QMessageBox.information(
|
||
self,
|
||
"Verfahrensgebiet erstellt",
|
||
f"Verfahrensgebiet aus Auswahl erstellt.\n"
|
||
f"Erfolgreich: {erfolgreich}\n"
|
||
f"Fehlgeschlagen: {fehlgeschlagen}",
|
||
)
|
||
return
|
||
|
||
# 5. Bestehender Layer: Nutzerentscheidung via Pruefmanager
|
||
entscheidung_ergebnis = self.pruefmanager.verarbeite(
|
||
pruef_ergebnis(
|
||
ok=False,
|
||
aktion="verfahrensgebiet_layer_existiert",
|
||
meldung=(
|
||
"Ein Verfahrensgebiet-Layer existiert bereits.\n"
|
||
"Was soll mit der Auswahl geschehen?"
|
||
),
|
||
kontext=existing_layers[0],
|
||
)
|
||
)
|
||
|
||
aktion = entscheidung_ergebnis.aktion
|
||
verf_layer = existing_layers[0]
|
||
|
||
if aktion == "verfahrensgebiet_abbrechen" or not entscheidung_ergebnis.ok:
|
||
return
|
||
|
||
elif aktion == "verfahrensgebiet_ergaenzen":
|
||
_, hinzugefuegt, bereits_vorhanden, fehlgeschlagen = ergaenze_verfahrensgebiet(
|
||
verf_layer, active_layer
|
||
)
|
||
apply_style(verf_layer, "verfahrensgebiet.qml")
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_verf, True)
|
||
QMessageBox.information(
|
||
self,
|
||
"Flurstücke ergänzt",
|
||
f"Hinzugefügt: {hinzugefuegt}\n"
|
||
f"Bereits vorhanden (übersprungen): {bereits_vorhanden}\n"
|
||
f"Fehlgeschlagen: {fehlgeschlagen}",
|
||
)
|
||
|
||
elif aktion == "verfahrensgebiet_entfernen":
|
||
_, entfernt, nicht_enthalten, fehlgeschlagen = entferne_aus_verfahrensgebiet(
|
||
verf_layer, active_layer
|
||
)
|
||
apply_style(verf_layer, "verfahrensgebiet.qml")
|
||
iface.mapCanvas().refresh()
|
||
QMessageBox.information(
|
||
self,
|
||
"Flurstücke entfernt",
|
||
f"Entfernt: {entfernt}\n"
|
||
f"Nicht im Verfahrensgebiet (übersprungen): {nicht_enthalten}\n"
|
||
f"Fehlgeschlagen: {fehlgeschlagen}",
|
||
)
|
||
|
||
elif aktion == "verfahrensgebiet_ersetzen":
|
||
for lyr in existing_layers:
|
||
QgsProject.instance().removeMapLayer(lyr.id())
|
||
layer, erfolgreich, fehlgeschlagen = ersetze_verfahrensgebiet(active_layer)
|
||
if not layer or not layer.isValid():
|
||
error("Verfahrensgebiet erstellen", "Layer konnte nicht erstellt werden.")
|
||
return
|
||
QgsProject.instance().addMapLayer(layer)
|
||
apply_style(layer, "verfahrensgebiet.qml")
|
||
iface.mapCanvas().refresh()
|
||
self.verf_layer_combo.setLayer(layer)
|
||
self._on_verf_layer_changed(layer)
|
||
self.setze_haken(self.haken_verf, True)
|
||
QMessageBox.information(
|
||
self,
|
||
"Verfahrensgebiet ersetzt",
|
||
f"Verfahrensgebiet neu erstellt.\n"
|
||
f"Erfolgreich: {erfolgreich}\n"
|
||
f"Fehlgeschlagen: {fehlgeschlagen}",
|
||
)
|
||
|
||
finally:
|
||
self._set_busy(self.btn_verf_aus_auswahl, False, "aus Auswahl")
|
||
|
||
def handle_verf_aus_liste(self) -> None:
|
||
"""Platzhalter – Verfahrensgebiet aus Liste erstellen (noch nicht implementiert)."""
|
||
show_info_dialog(
|
||
"Noch nicht implementiert",
|
||
"Die Funktion 'Aus Liste' steht in einer zukünftigen Version zur Verfügung.",
|
||
parent=self,
|
||
)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Grenzpunkt-Handler
|
||
# ------------------------------------------------------------------
|
||
|
||
def handle_grenz_alkis(self) -> None:
|
||
"""Lädt Grenzpunkte gemarkungsweise aus ALKIS.
|
||
|
||
Verwendet denselben Gemarkungsfilter wie "Verfahrensgebiet aus ALKIS laden":
|
||
Die Gemarkungsschlüssel werden per Spatial-Join aus der Geometrie des
|
||
aktuell gewählten Verfahrensgebiet-Layers abgeleitet.
|
||
"""
|
||
# ------------------------------------------------------------------ #
|
||
# Schritt 0: Fortschrittsdialog sofort anzeigen #
|
||
# ------------------------------------------------------------------ #
|
||
# 4 Vorbereitungsschritte (Geometrie, Gemarkungen, Hits, Laden) –
|
||
# die genaue Schrittzahl wird in grenzpunkte_gemarkungsweise verfeinert.
|
||
prog = create_progress_dialog(
|
||
4, "Grenzpunkte aus ALKIS laden", "Verfahrensgebiet prüfen …"
|
||
)
|
||
prog.set_value(0)
|
||
|
||
# Verfahrensgebiet-Layer aus dem Dropdown holen
|
||
vg_layer = self.verf_layer_combo.currentLayer()
|
||
if not vg_layer or not vg_layer.isValid():
|
||
prog.close()
|
||
warning(
|
||
"Grenzpunkte",
|
||
"Kein Verfahrensgebiet-Layer gewählt. Bitte zuerst ein Verfahrensgebiet laden.",
|
||
duration=6,
|
||
)
|
||
return
|
||
|
||
# Geometrie des ersten Features als Suchgeometrie
|
||
feats = list(vg_layer.getFeatures())
|
||
if not feats:
|
||
prog.close()
|
||
warning("Grenzpunkte", "Verfahrensgebiet-Layer enthält keine Features.", duration=5)
|
||
return
|
||
|
||
# Geometrie über alle Features vereinen
|
||
from qgis.core import QgsGeometry # type: ignore[import]
|
||
vg_geom = QgsGeometry.unaryUnion([f.geometry() for f in feats if f.hasGeometry()])
|
||
|
||
if not vg_geom or vg_geom.isEmpty():
|
||
prog.close()
|
||
warning("Grenzpunkte", "Verfahrensgebiet-Layer hat keine Geometrie.", duration=5)
|
||
return
|
||
|
||
prog.set_label("Gemarkungsschlüssel per WFS ermitteln …")
|
||
prog.set_value(1)
|
||
if prog.is_canceled():
|
||
return
|
||
|
||
# Gemarkungsschlüssel per Spatial-Join ermitteln (identisch zu VG-Workflow)
|
||
schluessel_namen = gemarkung_schluessel_und_namen_von_vg_geometrie(vg_geom)
|
||
schluessel_liste = [nr for nr, _ in schluessel_namen]
|
||
|
||
if not schluessel_liste:
|
||
prog.close()
|
||
warning(
|
||
"Grenzpunkte",
|
||
"Keine Gemarkungen für das Verfahrensgebiet gefunden. "
|
||
"Bitte Netzwerkverbindung prüfen.",
|
||
duration=6,
|
||
)
|
||
return
|
||
|
||
prog.set_label(f"{len(schluessel_liste)} Gemarkung(en) gefunden – Grenzpunkte laden …")
|
||
prog.set_value(2)
|
||
if prog.is_canceled():
|
||
return
|
||
|
||
self._set_busy(self.btn_grenz_alkis, True, "Grenzpunkte aus ALKIS laden")
|
||
prog.close() # Vorbereitungs-Dialog schließen; grenzpunkte_gemarkungsweise öffnet eigenen
|
||
try:
|
||
joined_layer = grenzpunkte_gemarkungsweise(self, schluessel_liste, vg_geom=vg_geom)
|
||
|
||
if joined_layer and joined_layer.isValid():
|
||
iface.mapCanvas().refresh()
|
||
self.setze_haken(self.haken_grenz, True)
|
||
QMessageBox.information(
|
||
self,
|
||
"Grenzpunkte geladen",
|
||
f"Grenzpunkte erfolgreich geladen ({joined_layer.featureCount()} Objekte).",
|
||
)
|
||
elif joined_layer is None:
|
||
error(
|
||
"Grenzpunkte",
|
||
"Laden fehlgeschlagen – WFS- oder Netzwerkfehler. "
|
||
"Details unter Ansicht -> Bedienfelder -> Protokollnachrichten "
|
||
"(Kategorie 'sn_verfahrensgebiet'). "
|
||
"Bitte prüfen Sie ggf. die Proxy-Einstellungen.",
|
||
duration=0,
|
||
)
|
||
finally:
|
||
self._set_busy(self.btn_grenz_alkis, False, "Grenzpunkte aus ALKIS laden")
|
||
|
||
def handle_umringstatistik(self) -> None:
|
||
"""Klassifiziert Grenzpunkte auf dem VG-Umring nach Lagegenauigkeit.
|
||
|
||
Sucht den vorhandenen ``Grenzpunkte (ALKIS)``-Layer im Projekt und
|
||
kombiniert ihn mit dem gewählten Verfahrensgebiet-Layer, um die
|
||
Umringstatistik zu berechnen.
|
||
"""
|
||
# 1. Grenzpunkte-Layer aus dem Projekt holen
|
||
project = QgsProject.instance()
|
||
gp_layers = project.mapLayersByName(JOINED_LAYER_NAME)
|
||
if not gp_layers:
|
||
warning(
|
||
"Umringstatistik",
|
||
f"Kein Layer '{JOINED_LAYER_NAME}' im Projekt gefunden. "
|
||
"Bitte zuerst 'Grenzpunkte aus ALKIS laden' ausführen.",
|
||
duration=8,
|
||
)
|
||
return
|
||
grenzpunkte_layer = gp_layers[0]
|
||
|
||
# 2. Verfahrensgebiet-Layer aus Dropdown
|
||
vg_layer = self.verf_layer_combo.currentLayer()
|
||
if not vg_layer or not vg_layer.isValid():
|
||
warning(
|
||
"Umringstatistik",
|
||
"Kein Verfahrensgebiet-Layer gewählt. Bitte zuerst ein Verfahrensgebiet laden.",
|
||
duration=6,
|
||
)
|
||
return
|
||
|
||
self._set_busy(self.btn_umring_statistik, True, "Umringstatistik Grenzpunkte")
|
||
try:
|
||
result_layer = umringstatistik_grenzpunkte(self, grenzpunkte_layer, vg_layer)
|
||
if result_layer and result_layer.isValid():
|
||
iface.mapCanvas().refresh()
|
||
finally:
|
||
self._set_busy(self.btn_umring_statistik, False, "Umringstatistik Grenzpunkte")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Modul-Hilfsfunktionen
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|