"Fachdaten laden" lädt Pufferlayer und erzeugt Verfahrens-DB (oder temp)

This commit is contained in:
2026-03-11 20:56:53 +01:00
parent 1de7526db8
commit 3f553efd21
2 changed files with 374 additions and 92 deletions

View File

@@ -1,8 +1,10 @@
"""
sn_plan41/ui/tab_a_logic.py Fachlogik für Tab A (Daten)
"""
from __future__ import annotations
from sn_basis.functions.sys_wrapper import get_plugin_root, join_path
from typing import Any, Dict, List, Optional
from collections.abc import Mapping as _Mapping
@@ -12,6 +14,9 @@ from sn_basis.functions.qgiscore_wrapper import (
QgsVectorFileWriter,
QgsVectorLayer,
QgsProject,
QgsGeometry,
QgsFeature,
QgsField,
)
@@ -22,13 +27,17 @@ from sn_basis.functions.variable_wrapper import (
from sn_basis.functions.sys_wrapper import file_exists
from sn_basis.functions.ly_existence_wrapper import layer_exists
from sn_basis.functions.ly_metadata_wrapper import get_layer_type
from sn_basis.functions.qgisui_wrapper import QgsFileWidget
from sn_basis.functions.qt_wrapper import QVariant
# Prüfer-Typen
from sn_basis.modules.Pruefmanager import Pruefmanager
from sn_basis.modules.linkpruefer import Linkpruefer
from sn_basis.modules.stilpruefer import Stilpruefer
from sn_basis.modules.Dateipruefer import Dateipruefer
from sn_basis.modules.layerpruefer import Layerpruefer
from sn_basis.modules.Datenschreiber import Datenschreiber
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
from sn_basis.modules.DataGrabber import DataGrabber, SourceType, SourceDict
@@ -46,6 +55,8 @@ class TabALogic:
self.link_pruefer = link_pruefer
self.stil_pruefer = stil_pruefer
self.data_grabber: Optional[DataGrabber] = None
def _log(self, msg: str) -> None:
print(f"[TabALogic] {msg}")
# -------------------------------
# Verfahrens-Datenbank (Pfad-Management)
@@ -137,26 +148,10 @@ class TabALogic:
# Verfahrensgebiet-Layer
# -------------------------------
def save_verfahrensgebiet_layer(self, layer) -> None:
if layer is None:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
if not hasattr(layer, "id") or not callable(layer.id):
set_variable("verfahrensgebiet_layer", "", scope="project")
return
try:
layer_id = layer.id()
except Exception:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
if not layer_id:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
def save_verfahrensgebiet_layer(self, layer: QgsVectorLayer) -> None:
"""Speichert die Verfahrensgebiet-Layer-ID, unter Annahme, dass der Layer prevalidiert ist."""
layer_id = layer.id() if layer is not None else ""
set_variable("verfahrensgebiet_layer", layer_id or "", scope="project")
def load_verfahrensgebiet_layer_id(self) -> Optional[str]:
value = get_variable("verfahrensgebiet_layer", scope="project")
@@ -170,73 +165,48 @@ class TabALogic:
return layer_type == "vector"
# === PIPELINE ===
def _on_run_pipeline(self,source: str) -> None:
"""DEBUG: Pipeline mit maximaler Ausgabe."""
print("\n" + "="*60)
print("🚀 _on_run_pipeline GESTARTET")
print("="*60)
# 🔥 DEBUG QT STATUS
from sn_basis.functions import qt_wrapper
qt_wrapper.debug_qt_status() # ← Zeigt Version an
# 1. Services prüfen
print(f"pruefmanager: {self.pruefmanager is not None}")
print(f"data_grabber: {self.data_grabber is not None}")
print(f"logic: {hasattr(self, 'logic')}")
if not self.pruefmanager:
print("❌ FEHLER: self.pruefmanager fehlt!")
return
if not self.data_grabber:
print("❌ FEHLER: self.data_grabber fehlt!")
def _on_run_pipeline(
self,
source: str,
linkliste: str | None,
raumfilter: str,
) -> None:
"""Pipeline starten; Eingaben gelten als vorvalidiert (Dateiprüfer + Pruefmanager)."""
if not self.pruefmanager or not self.data_grabber:
return
print("✅ Services OK")
# 2. FileWidget
#source = self.file_widget.filePath()
print(f"📁 Eingabe: '{source}' (len={len(source or '')})")
# 3. Dateipruefer
print("🔍 Dateipruefer starte...")
try:
pruefer = Dateipruefer(
ergebnis1 = Dateipruefer(
source,
basis_pfad="",
leereingabe_erlaubt=False,
standarddatei=None,
temporaer_erlaubt=True, # ✅ Explizit True
verfahrens_db_modus=True # ✅ Keyword-only
)
ergebnis1 = pruefer.pruefe()
print(f" → ok={ergebnis1.ok}, aktion='{ergebnis1.aktion}', kontext={ergebnis1.kontext}")
except Exception as e:
print(f"💥 Dateipruefer FEHLER: {e}")
import traceback
traceback.print_exc()
return
temporaer_erlaubt=True,
verfahrens_db_modus=True,
).pruefe()
# 4. Pruefmanager
print("🤖 Pruefmanager starte...")
try:
ergebnis2 = self.pruefmanager.verarbeite(ergebnis1)
print(f" → ok={ergebnis2.ok}, aktion='{ergebnis2.aktion}', kontext={ergebnis2.kontext}")
except Exception as e:
print(f"💥 Pruefmanager FEHLER: {e}")
import traceback
traceback.print_exc()
if not ergebnis2.ok:
return
# 5. Entscheidung
weiter = ergebnis2.ok
print(f"➡️ Weiter? {weiter} (aktion='{ergebnis2.aktion}')")
final_pfad = str(ergebnis2.kontext or source)
self.set_verfahrens_db(final_pfad)
self.data_grabber.run(final_pfad)
linkliste_final = self._resolve_linkliste(linkliste)
if not linkliste_final:
return
raumfilter_layer = self._resolve_raumfilter(raumfilter, final_pfad)
if raumfilter in ("Verfahrensgebiet", "Pufferlayer") and raumfilter_layer is None:
return
pipeline_context = {
"source": final_pfad,
"linkliste": linkliste_final,
"raumfilter": raumfilter_layer,
}
if weiter:
final_pfad = ergebnis2.kontext if ergebnis2.kontext else source
print(f"🚀 DataGrabber mit: '{final_pfad}'")
try:
self.data_grabber.run(final_pfad)
print("✅ DataGrabber aufgerufen!")
@@ -248,3 +218,299 @@ class TabALogic:
print("⏹️ Pipeline gestoppt (erwartet bei leerem Pfad)")
print("="*60 + "\n")
linkliste_final=self._resolve_linkliste(linkliste)
if linkliste_final is None:
print("⏹️ Pipeline abgebrochen (Linkliste)")
return
raumfilter_layer = self._resolve_raumfilter(raumfilter, final_pfad)
if raumfilter == "Verfahrensgebiet" and raumfilter_layer is None:
print("⏹️ Pipeline abgebrochen: kein Verfahrensgebiet gesetzt")
return
if raumfilter == "Pufferlayer" and raumfilter_layer is None:
print("⏹️ Pipeline abgebrochen: Pufferlayer konnte nicht erzeugt werden")
return
pipeline_context = {
"source": final_pfad,
"linkliste": linkliste_final,
"raumfilter": raumfilter_layer,
}
def _resolve_linkliste(self, linkliste: str | None) -> str | None:
"""
Prüft und normalisiert den Linklisten-Pfad.
Rückgabe:
- gültiger Pfad zur Linkliste (str)
- None → Pipeline abbrechen
"""
# --------------------------------------------------
# Standard-Linkliste (plattformneutral)
# --------------------------------------------------
plugin_root = get_plugin_root()
standard_linkliste = join_path(plugin_root, "assets", "Linkliste.xlsx")
# --------------------------------------------------
# 🔹 LEERE EINGABE → AUTOMATISCH STANDARDDATEI
# --------------------------------------------------
if not linkliste:
linkliste_final = str(standard_linkliste)
self.set_linkliste(linkliste_final)
return linkliste_final
# --------------------------------------------------
# Dateiprüfung nur bei expliziter Eingabe
# --------------------------------------------------
pruefer = Dateipruefer(
pfad=linkliste,
leereingabe_erlaubt=True,
standarddatei=str(standard_linkliste),
)
ergebnis = pruefer.pruefe()
# --------------------------------------------------
# Entscheidung über Pruefmanager
# --------------------------------------------------
ergebnis = self.pruefmanager.verarbeite(ergebnis)
if not ergebnis.ok:
# Nutzer hat abgebrochen oder Fehler nicht bestätigt
return None
# --------------------------------------------------
# Erfolgsfall → geprüften Pfad übernehmen
# --------------------------------------------------
linkliste_final = str(ergebnis.kontext)
# Optional: Projektvariable aktualisieren
self.set_linkliste(linkliste_final)
return linkliste_final
def _resolve_raumfilter(self, raumfilter: str, source: str) -> QgsVectorLayer | None:
self._log(f"Raumfilter-Auswahl: '{raumfilter}'")
self._log(f"Source: '{source}'")
if raumfilter == "Verfahrensgebiet":
layer = self._get_verfahrensgebiet_layer()
self._log(
"Verfahrensgebiet gefunden"
if layer else
"❌ Kein Verfahrensgebiet im Projekt"
)
return layer
if raumfilter == "Pufferlayer":
self._log("Pufferlayer-Modus aktiv")
return self._handle_pufferlayer(source)
self._log("Kein Raumfilter gewählt")
return None
def _get_verfahrensgebiet_layer(self) -> QgsVectorLayer | None:
layer_id = self.load_verfahrensgebiet_layer_id()
self._log(f"Verfahrensgebiet-Layer-ID: {layer_id}")
if not layer_id:
self._log("❌ Keine Layer-ID gespeichert")
return None
layer = QgsProject.instance().mapLayer(layer_id)
if not layer:
self._log("❌ Layer-ID existiert nicht im Projekt")
return None
if not self.is_valid_verfahrensgebiet_layer(layer):
self._log("❌ Layer ist kein gültiger Vektorlayer")
return None
self._log(f"Verfahrensgebiet-Layer OK: '{layer.name()}'")
return layer
def _handle_pufferlayer(self, source: str) -> QgsVectorLayer | None:
self._log("Prüfe vorhandenen Pufferlayer im Projekt")
existing = self._load_existing_pufferlayer()
if existing:
self._log("✔ Pufferlayer bereits im Projekt vorhanden")
return existing
self._log("Kein Pufferlayer im Projekt")
if source:
self._log("Prüfe Pufferlayer im Source")
exists = self._pufferlayer_exists_in_source(source)
self._log(f"Pufferlayer im Source vorhanden: {exists}")
if exists:
return self._load_existing_pufferlayer() or self._create_pufferlayer()
self._log("Erzeuge neuen Pufferlayer")
return self._create_pufferlayer()
def _load_existing_pufferlayer(self) -> QgsVectorLayer | None:
"""
Liefert einen vorhandenen Pufferlayer aus dem Projekt.
"""
layers = QgsProject.instance().mapLayersByName("Pufferlayer")
return layers[0] if layers else None
def _create_pufferlayer(self) -> QgsVectorLayer | None:
self._log("Starte Pufferlayer-Erstellung")
basis_layer = self._get_verfahrensgebiet_layer()
if not basis_layer:
self._log("❌ Kein Verfahrensgebiet → kein Puffer möglich")
return None
source = self.load_verfahrens_db()
self._log(f"Basislayer: '{basis_layer.name()}'")
layer = self.Pufferlayer_erstellen(
basis_layer=basis_layer,
distance=1000,
name="Pufferlayer",
source=source,
)
self._log(
"✔ Pufferlayer erfolgreich erzeugt"
if layer else
"❌ Pufferlayer-Erstellung fehlgeschlagen"
)
return layer
from sn_basis.functions.qgiscore_wrapper import QgsVectorLayer
def _pufferlayer_exists_in_source(self, source: str) -> bool:
"""
Prüft, ob im Source (z.B. GPKG) ein Layer namens 'Pufferlayer' existiert.
"""
if not source:
return False
uri = f"{source}|layername=Pufferlayer"
layer = QgsVectorLayer(uri, "Pufferlayer", "ogr")
return layer.isValid()
def Pufferlayer_erstellen(
self,
basis_layer: QgsVectorLayer,
distance: float,
name: str,
source: str | None = None,
) -> QgsVectorLayer | None:
"""
Erzeugt einen rechteckigen Pufferlayer (BBOX + Abstand)
um das Verfahrensgebiet.
- Ohne Source → temporärer Memory-Layer
- Mit Source → Schreiben über Datenschreiber
Parameters
----------
basis_layer : QgsVectorLayer
Verfahrensgebiet-Layer.
distance : float
Pufferabstand in Metern.
name : str
Name des Ziel-Layers.
source : str | None
Zielquelle (z.B. Verfahrens-DB) oder None für temporär.
Returns
-------
QgsVectorLayer | None
Neuer Pufferlayer oder None bei Fehler.
"""
if not basis_layer or not basis_layer.isValid():
self._log("❌ Basislayer ungültig kein Puffer möglich")
return None
# --------------------------------------------------
# 1. Rechteck-Geometrie (Extent + Puffer)
# --------------------------------------------------
extent = basis_layer.extent().buffered(distance)
bbox_geom = QgsGeometry.fromRect(extent)
# --------------------------------------------------
# 2. CRS übernehmen
# --------------------------------------------------
crs_auth = basis_layer.crs().authid()
uri = f"Polygon?crs={crs_auth}"
mem_layer = QgsVectorLayer(uri, name, "memory")
provider = mem_layer.dataProvider()
provider.addAttributes([
QgsField("id", QVariant.Int)
])
mem_layer.updateFields()
# --------------------------------------------------
# 4. Feature erzeugen
# --------------------------------------------------
feat = QgsFeature(mem_layer.fields())
feat.setGeometry(bbox_geom)
feat["id"] = 1
provider.addFeature(feat)
mem_layer.updateExtents()
# --------------------------------------------------
# 5. Temporärer Modus → direkt ins Projekt
# --------------------------------------------------
if not source:
QgsProject.instance().addMapLayer(mem_layer)
self._log("✔ Temporärer rechteckiger Pufferlayer erzeugt")
return mem_layer
# --------------------------------------------------
# 6. Persistenter Modus → Datenschreiber
# --------------------------------------------------
writer = Datenschreiber(
pruefmanager=self.pruefmanager,
gpkg_path=source,
)
daten_dict = {
"daten": {
name: {
"layer": mem_layer
}
}
}
results = writer.schreibe_Daten(
daten_dict=daten_dict,
processed_results=[],
speicherort=source,
)
if not results:
self._log("❌ Schreiben des Pufferlayers fehlgeschlagen")
return None
writer.lade_Layer(results)
layers = QgsProject.instance().mapLayersByName(name)
if layers:
self._log("✔ Persistenter rechteckiger Pufferlayer geladen")
return layers[0]
return None

View File

@@ -178,6 +178,7 @@ class TabA(QWidget):
main_layout.addWidget(QLabel("Raumfilter"))
self._raumfilter_combo = QComboBox(self)
self._raumfilter_combo.setToolTip("Wählt die räumliche Bezugsfläche für die Datenextraktion.")
self._raumfilter_combo.addItems(RAUMFILTER_OPTIONS)
self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed)
main_layout.addWidget(self._raumfilter_combo)
@@ -249,11 +250,20 @@ class TabA(QWidget):
set_variable("tab_a_linkliste", path, scope="project")
def _on_layer_changed(self, layer) -> None:
"""Persistieret Layer-Auswahl."""
"""Persistiert Layer-Auswahl und registriert Verfahrensgebiet."""
self._pufferlayer = layer
if layer:
if not layer:
return
# UI-State speichern
set_variable("tab_a_layer_id", layer.id(), scope="project")
# 🔹 NEU: Verfahrensgebiet explizit registrieren
if self.logic:
self.logic.save_verfahrensgebiet_layer(layer)
def _on_raumfilter_changed(self, value: str) -> None:
"""Persistieret Raumfilter-Auswahl."""
set_variable(RAUMFILTER_VAR, value, scope="project")
@@ -284,4 +294,10 @@ class TabA(QWidget):
def _on_load_fachdaten(self) -> None:
"""Kompatibilitäts-Handler → neue Pipeline."""
source=self.file_widget.filePath()
self.logic._on_run_pipeline(source)
raumfilter=self._raumfilter_combo.currentText()
linkliste=self.linkliste_widget.filePath()
if self.logic and self.layer_combo:
layer = self.layer_combo.currentLayer()
if layer and layer.name() == "Verfahrensgebiet":
self.logic.save_verfahrensgebiet_layer(layer)
self.logic._on_run_pipeline(source, linkliste,raumfilter)