Überarbeitung für Pufferlayer-Fachdaten laden und gpkg-speichern/laden
This commit is contained in:
@@ -10,6 +10,8 @@ YES: Optional[Any] = None
|
|||||||
NO: Optional[Any] = None
|
NO: Optional[Any] = None
|
||||||
CANCEL: Optional[Any] = None
|
CANCEL: Optional[Any] = None
|
||||||
ICON_QUESTION: Optional[Any] = None
|
ICON_QUESTION: Optional[Any] = None
|
||||||
|
QVariant: Type[Any] = object
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Qt-Klassen (werden dynamisch gesetzt)
|
# Qt-Klassen (werden dynamisch gesetzt)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ ausschließlich über den ``Pruefmanager`` im aufrufenden Kontext (UI / Pipeline
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Tuple, Literal
|
from typing import Any, Dict, List, Mapping, Optional, Tuple, Literal
|
||||||
|
|
||||||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||||
@@ -66,10 +67,6 @@ class DataGrabber:
|
|||||||
"""Setzt die aktuell zu untersuchende Rohquelle."""
|
"""Setzt die aktuell zu untersuchende Rohquelle."""
|
||||||
self._source = source
|
self._source = source
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
SourceType = str # "excel" | "datenbank" | "dienst" | "unbekannt"
|
SourceType = str # "excel" | "datenbank" | "dienst" | "unbekannt"
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +120,7 @@ class DataGrabber:
|
|||||||
die vom Aufrufer über den ``Pruefmanager`` verarbeitet werden.
|
die vom Aufrufer über den ``Pruefmanager`` verarbeitet werden.
|
||||||
"""
|
"""
|
||||||
self.set_source(source)
|
self.set_source(source)
|
||||||
source_type = self.analyze_source_type(source)
|
source_type, source_result = self.analyze_source_type(source)
|
||||||
|
|
||||||
source_dict: SourceDict = {}
|
source_dict: SourceDict = {}
|
||||||
partial_results: List[pruef_ergebnis] = []
|
partial_results: List[pruef_ergebnis] = []
|
||||||
@@ -135,14 +132,7 @@ class DataGrabber:
|
|||||||
elif source_type == "service":
|
elif source_type == "service":
|
||||||
source_dict, partial_results = self._process_service_source(source)
|
source_dict, partial_results = self._process_service_source(source)
|
||||||
else:
|
else:
|
||||||
partial_results.append(
|
partial_results.append(source_result)
|
||||||
pruef_ergebnis(
|
|
||||||
ok=False,
|
|
||||||
meldung="Quelle konnte nicht klassifiziert werden",
|
|
||||||
aktion="kein_dateipfad",
|
|
||||||
kontext={"source": source},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
summary = self._aggregate_results(source, source_dict, partial_results)
|
summary = self._aggregate_results(source, source_dict, partial_results)
|
||||||
return source_dict, summary
|
return source_dict, summary
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class Datenschreiber:
|
|||||||
|
|
||||||
def __init__(self, pruefmanager: Any, gpkg_path: Optional[str] = None) -> None:
|
def __init__(self, pruefmanager: Any, gpkg_path: Optional[str] = None) -> None:
|
||||||
self.pruefmanager = pruefmanager
|
self.pruefmanager = pruefmanager
|
||||||
self.gpkg_path = gpkg_path
|
self.gpkg_path = str(gpkg_path) if gpkg_path else None
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Schreibe Daten
|
# Schreibe Daten
|
||||||
@@ -65,192 +65,82 @@ class Datenschreiber:
|
|||||||
speicherort: str,
|
speicherort: str,
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Schreibt die abgerufenen Daten in die Zieldatenbank/Dateien.
|
Schreibt die übergebenen Layer in die Ziel-GPKG.
|
||||||
|
|
||||||
Ablauf
|
Erwartung:
|
||||||
------
|
- daten_dict["daten"] enthält Einträge der Form:
|
||||||
Für jede Zeile (ident) in ``daten_dict["daten"]``:
|
ident -> {"layer": QgsVectorLayer}
|
||||||
1. Bestimme Ziel-Layername (z. B. Thema oder ident).
|
- self.gpkg_path ist ein str
|
||||||
2. Prüfe, ob ein Layer mit diesem Namen bereits existiert (Wrapper).
|
|
||||||
3. Falls vorhanden, frage den Benutzer (Überschreiben / Anhängen / Abbrechen)
|
|
||||||
über die zentrale Pruefmanager-Methode `ask_overwrite_append_cancel`.
|
|
||||||
4. Führe die gewählte Operation aus oder schreibe den Layer, wenn er noch nicht existiert.
|
|
||||||
5. Schreibe ggf. den Stil in die GPKG und setze ihn als Vorgabe.
|
|
||||||
6. Sammle und gib eine Liste der angelegten/geänderten Layer zurück.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
List[Dict[str, Any]]
|
|
||||||
Liste von Dicts mit Informationen zu jedem angelegten/geänderten Layer.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not speicherort:
|
if not speicherort:
|
||||||
raise ValueError("Ein gültiger Speicherort (speicherort) muss übergeben werden.")
|
raise ValueError("Ein gültiger Speicherort (speicherort) muss übergeben werden.")
|
||||||
|
|
||||||
# Setze gpkg_path falls noch nicht vorhanden
|
# gpkg_path einmalig setzen / normalisieren
|
||||||
if not self.gpkg_path:
|
if not self.gpkg_path:
|
||||||
self.gpkg_path = speicherort
|
self.gpkg_path = str(speicherort)
|
||||||
|
|
||||||
results: List[Dict[str, Any]] = []
|
results: List[Dict[str, Any]] = []
|
||||||
daten_map: Dict[str, List[Any]] = daten_dict.get("daten", {})
|
daten_map: Dict[str, Any] = daten_dict.get("daten", {})
|
||||||
|
|
||||||
# Iteriere über alle Einträge
|
for ident, entry in daten_map.items():
|
||||||
for ident, features in daten_map.items():
|
layer = None
|
||||||
# Thema/Name ableiten (falls vorhanden in processed_results oder ident)
|
|
||||||
|
# -----------------------------
|
||||||
|
# Layer extrahieren
|
||||||
|
# -----------------------------
|
||||||
|
if isinstance(entry, dict) and "layer" in entry:
|
||||||
|
layer = entry["layer"]
|
||||||
|
|
||||||
|
if layer is None or not hasattr(layer, "isValid") or not layer.isValid():
|
||||||
|
pe_err = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
meldung=f"Ungültiger Layer für {ident}",
|
||||||
|
aktion="save_exception",
|
||||||
|
kontext={"ident": ident},
|
||||||
|
)
|
||||||
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Layername bestimmen
|
||||||
|
# -----------------------------
|
||||||
thema = None
|
thema = None
|
||||||
for pe in processed_results:
|
for pe in processed_results:
|
||||||
try:
|
try:
|
||||||
kontext = getattr(pe, "kontext", None) or {}
|
kontext = getattr(pe, "kontext", None) or {}
|
||||||
if kontext and kontext.get("ident") == ident:
|
if kontext.get("ident") == ident:
|
||||||
thema = kontext.get("thema")
|
thema = kontext.get("thema")
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
if not thema:
|
|
||||||
thema = str(ident)
|
|
||||||
|
|
||||||
layer_name = thema
|
layer_name = thema or str(ident)
|
||||||
|
# Layer in GPKG schreiben
|
||||||
# Prüfe, ob Layer bereits existiert in der Ziel-GPKG
|
err_msg = self._write_layer_to_gpkg(layer_name=layer_name, layer=layer)
|
||||||
layer_exists = False
|
if err_msg is not None:
|
||||||
try:
|
|
||||||
layer_exists_fn = getattr(qgiscore, "layer_exists_in_gpkg", None)
|
|
||||||
if callable(layer_exists_fn):
|
|
||||||
layer_exists = layer_exists_fn(self.gpkg_path, layer_name)
|
|
||||||
else:
|
|
||||||
# Fallback: QGIS-Fallback-Check via QgsVectorLayer
|
|
||||||
if getattr(qgiscore, "QgsVectorLayer", None) is not None and qgiscore.QGIS_AVAILABLE:
|
|
||||||
uri = f"{self.gpkg_path}|layername={layer_name}"
|
|
||||||
layer = qgiscore.QgsVectorLayer(uri, layer_name, "ogr")
|
|
||||||
layer_exists = bool(layer and getattr(layer, "isValid", lambda: False)())
|
|
||||||
except Exception:
|
|
||||||
layer_exists = False
|
|
||||||
|
|
||||||
operation = "created"
|
|
||||||
|
|
||||||
if layer_exists:
|
|
||||||
# Zentrale Nutzerabfrage über Pruefmanager
|
|
||||||
# Erwartet Rückgabe: "overwrite" | "append" | "cancel"
|
|
||||||
try:
|
|
||||||
user_choice = self.pruefmanager.ask_overwrite_append_cancel(layer_name)
|
|
||||||
except Exception:
|
|
||||||
# Fallback: overwrite, falls Pruefmanager nicht verfügbar
|
|
||||||
user_choice = "overwrite"
|
|
||||||
|
|
||||||
if user_choice == "cancel":
|
|
||||||
operation = "skipped"
|
|
||||||
results.append({
|
|
||||||
"ident": ident,
|
|
||||||
"thema": thema,
|
|
||||||
"operation": operation,
|
|
||||||
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
|
||||||
"feature_count": 0,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
|
|
||||||
if user_choice == "overwrite":
|
|
||||||
write_err = self._write_layer_to_gpkg(layer_name, features, mode="overwrite")
|
|
||||||
if write_err:
|
|
||||||
pe_err = pruef_ergebnis(
|
pe_err = pruef_ergebnis(
|
||||||
ok=False,
|
ok=False,
|
||||||
meldung=f"Fehler beim Überschreiben von {layer_name}: {write_err}",
|
meldung=f"Fehler beim Schreiben des Layers {layer_name}: {err_msg}",
|
||||||
aktion="save_exception",
|
aktion="save_exception",
|
||||||
kontext={"ident": ident, "thema": thema, "error": write_err},
|
kontext={"ident": ident, "layer_name": layer_name},
|
||||||
)
|
)
|
||||||
self.pruefmanager.verarbeite(pe_err)
|
self.pruefmanager.verarbeite(pe_err)
|
||||||
operation = "skipped"
|
|
||||||
results.append({
|
|
||||||
"ident": ident,
|
|
||||||
"thema": thema,
|
|
||||||
"operation": operation,
|
|
||||||
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
|
||||||
"feature_count": 0,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
operation = "overwritten"
|
|
||||||
|
|
||||||
elif user_choice == "append":
|
|
||||||
write_err = self._write_layer_to_gpkg(layer_name, features, mode="append")
|
|
||||||
if write_err:
|
|
||||||
pe_err = pruef_ergebnis(
|
|
||||||
ok=False,
|
|
||||||
meldung=f"Fehler beim Anhängen an {layer_name}: {write_err}",
|
|
||||||
aktion="save_exception",
|
|
||||||
kontext={"ident": ident, "thema": thema, "error": write_err},
|
|
||||||
)
|
|
||||||
self.pruefmanager.verarbeite(pe_err)
|
|
||||||
operation = "skipped"
|
|
||||||
results.append({
|
|
||||||
"ident": ident,
|
|
||||||
"thema": thema,
|
|
||||||
"operation": operation,
|
|
||||||
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
|
||||||
"feature_count": 0,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
operation = "appended"
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Layer existiert nicht -> neu anlegen
|
|
||||||
write_err = self._write_layer_to_gpkg(layer_name, features, mode="create")
|
|
||||||
if write_err:
|
|
||||||
pe_err = pruef_ergebnis(
|
|
||||||
ok=False,
|
|
||||||
meldung=f"Fehler beim Erstellen von {layer_name}: {write_err}",
|
|
||||||
aktion="save_exception",
|
|
||||||
kontext={"ident": ident, "thema": thema, "error": write_err},
|
|
||||||
)
|
|
||||||
self.pruefmanager.verarbeite(pe_err)
|
|
||||||
operation = "skipped"
|
|
||||||
results.append({
|
|
||||||
"ident": ident,
|
|
||||||
"thema": thema,
|
|
||||||
"operation": operation,
|
|
||||||
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
|
||||||
"feature_count": 0,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
operation = "created"
|
|
||||||
|
|
||||||
# Stilbehandlung (falls in processed_results referenziert)
|
|
||||||
style_written = False
|
|
||||||
style_path = None
|
|
||||||
for pe in processed_results:
|
|
||||||
try:
|
|
||||||
kontext = getattr(pe, "kontext", None) or {}
|
|
||||||
if kontext and kontext.get("ident") == ident:
|
|
||||||
style_path = kontext.get("stildatei") or kontext.get("Stildatei")
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if style_path:
|
# Erfolgsfall: Info für lade_Layer sammeln
|
||||||
if not os.path.isabs(style_path):
|
layer_path = f"{self.gpkg_path}|layername={layer_name}"
|
||||||
base_dir = os.path.dirname(__file__)
|
|
||||||
style_path = os.path.join(base_dir, style_path)
|
|
||||||
write_style_fn = getattr(qgiscore, "write_style_to_gpkg", None)
|
|
||||||
if callable(write_style_fn):
|
|
||||||
try:
|
|
||||||
write_style_fn(self.gpkg_path, style_path, layer_name)
|
|
||||||
style_written = True
|
|
||||||
except Exception:
|
|
||||||
style_written = False
|
|
||||||
|
|
||||||
feature_count = len(features) if isinstance(features, list) else 0
|
|
||||||
|
|
||||||
results.append({
|
results.append({
|
||||||
|
"layer_path": layer_path,
|
||||||
|
"thema": layer_name,
|
||||||
"ident": ident,
|
"ident": ident,
|
||||||
"thema": thema,
|
|
||||||
"operation": operation,
|
|
||||||
"layer_path": f"{self.gpkg_path}|layername={layer_name}",
|
|
||||||
"feature_count": feature_count,
|
|
||||||
"style_written": style_written,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Lade Layer ins Projekt
|
# Lade Layer ins Projekt
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
@@ -374,62 +264,54 @@ class Datenschreiber:
|
|||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Hilfsfunktionen intern
|
# Hilfsfunktionen intern
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
def _write_layer_to_gpkg(self, layer_name: str, features: List[Any], mode: str = "create") -> Optional[str]:
|
def _write_layer_to_gpkg(
|
||||||
|
self,
|
||||||
|
layer_name: str,
|
||||||
|
layer: Any,
|
||||||
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Interne Hilfsfunktion zum Schreiben eines Layers in das GPKG.
|
Schreibt einen QgsVectorLayer in die Ziel-GPKG.
|
||||||
|
|
||||||
Erwartete qgiscore-Funktion:
|
Voraussetzungen:
|
||||||
qgiscore.write_features_to_gpkg(gpkg_path, layer_name, features, mode)
|
- self.gpkg_path ist ein str
|
||||||
|
- layer ist ein gültiger QgsVectorLayer
|
||||||
"""
|
"""
|
||||||
write_fn = getattr(qgiscore, "write_features_to_gpkg", None)
|
|
||||||
if callable(write_fn):
|
if layer is None or not hasattr(layer, "isValid") or not layer.isValid():
|
||||||
|
return "Ungültiger Layer zum Schreiben übergeben"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
write_fn(self.gpkg_path, layer_name, features, mode)
|
|
||||||
return None
|
|
||||||
except Exception as exc:
|
|
||||||
return str(exc)
|
|
||||||
|
|
||||||
# Fallback: Verwende QgsVectorFileWriter, falls QGIS verfügbar
|
|
||||||
if getattr(qgiscore, "QGIS_AVAILABLE", False) and getattr(qgiscore, "QgsVectorFileWriter", None) is not None:
|
|
||||||
try:
|
|
||||||
# Minimaler Fallback: erwarte, dass 'features' eine Liste von QgsFeature ist
|
|
||||||
if not features:
|
|
||||||
# Erstelle leeren Layer-Eintrag (GPKG erlaubt leere Layer)
|
|
||||||
# Hier vereinfachen wir: writeAsVectorFormatV3 benötigt ein Layer-Objekt.
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Versuche, ein Memory-Layer aus dem ersten Feature zu ermitteln
|
|
||||||
first = features[0]
|
|
||||||
mem_layer = None
|
|
||||||
if hasattr(first, "fields") and hasattr(first, "geometry"):
|
|
||||||
# Wenn Features QgsFeature sind, versuchen wir, das zugehörige Layer zu nutzen
|
|
||||||
try:
|
|
||||||
mem_layer = first.layer() if hasattr(first, "layer") else None
|
|
||||||
except Exception:
|
|
||||||
mem_layer = None
|
|
||||||
|
|
||||||
if mem_layer is None:
|
|
||||||
return "Keine Feld-/Geometrie-Informationen zum Schreiben vorhanden"
|
|
||||||
|
|
||||||
opts = qgiscore.QgsVectorFileWriter.SaveVectorOptions()
|
opts = qgiscore.QgsVectorFileWriter.SaveVectorOptions()
|
||||||
opts.driverName = "GPKG"
|
opts.driverName = "GPKG"
|
||||||
opts.layerName = layer_name
|
opts.layerName = layer_name
|
||||||
opts.fileEncoding = "UTF-8"
|
opts.fileEncoding = "UTF-8"
|
||||||
if mode == "overwrite":
|
|
||||||
|
# Datei existiert → Layer überschreiben
|
||||||
|
# Datei existiert nicht → neue GPKG anlegen
|
||||||
|
if not os.path.exists(self.gpkg_path):
|
||||||
opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteFile
|
opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteFile
|
||||||
else:
|
else:
|
||||||
opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteLayer
|
opts.actionOnExistingFile = qgiscore.QgsVectorFileWriter.CreateOrOverwriteLayer
|
||||||
|
|
||||||
err = qgiscore.QgsVectorFileWriter.writeAsVectorFormatV3(
|
err = qgiscore.QgsVectorFileWriter.writeAsVectorFormatV3(
|
||||||
mem_layer,
|
layer,
|
||||||
self.gpkg_path,
|
self.gpkg_path,
|
||||||
qgiscore.QgsProject.instance().transformContext(),
|
qgiscore.QgsProject.instance().transformContext(),
|
||||||
opts
|
opts,
|
||||||
)
|
)
|
||||||
if err != qgiscore.QgsVectorFileWriter.NoError:
|
|
||||||
return f"Fehler beim Schreiben (Code {err})"
|
# QGIS ≥3 liefert ein Tupel: (error_code, error_message, new_filename, new_layer_name)
|
||||||
|
if isinstance(err, tuple):
|
||||||
|
error_code = err[0]
|
||||||
|
error_msg = err[1] if len(err) > 1 else ""
|
||||||
|
else:
|
||||||
|
error_code = err
|
||||||
|
error_msg = ""
|
||||||
|
|
||||||
|
if error_code != qgiscore.QgsVectorFileWriter.NoError:
|
||||||
|
return f"Fehler beim Schreiben (Code {error_code}, msg='{error_msg}')"
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return str(exc)
|
return str(exc)
|
||||||
|
|
||||||
return "Keine Schreib-Funktion verfügbar (Wrapper nicht implementiert)"
|
|
||||||
|
|||||||
@@ -216,3 +216,26 @@ class Pruefmanager:
|
|||||||
)
|
)
|
||||||
print("🔥 verarbeite() ENDE mit ok=False")
|
print("🔥 verarbeite() ENDE mit ok=False")
|
||||||
return ergebnis
|
return ergebnis
|
||||||
|
|
||||||
|
def _ask_use_or_replace_pufferlayer(self) -> str:
|
||||||
|
"""
|
||||||
|
Fragt den Nutzer, ob ein vorhandener Pufferlayer verwendet
|
||||||
|
oder ersetzt werden soll.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
"verwenden", "ersetzen" oder "abbrechen"
|
||||||
|
"""
|
||||||
|
ergebnis = pruef_ergebnis(
|
||||||
|
ok=False,
|
||||||
|
aktion="layer_existiert",
|
||||||
|
meldung="Ein Pufferlayer ist bereits vorhanden.",
|
||||||
|
)
|
||||||
|
|
||||||
|
ergebnis = self.pruefmanager.verarbeite(ergebnis)
|
||||||
|
|
||||||
|
if not ergebnis.ok:
|
||||||
|
return "abbrechen"
|
||||||
|
|
||||||
|
return "verwenden" if ergebnis.aktion == "ok" else "ersetzen"
|
||||||
|
|||||||
Reference in New Issue
Block a user