shape Verfahrensgebiet ergänzt
This commit is contained in:
@@ -72,7 +72,7 @@ def _check_existing_layer(tab_widget):
|
||||
return existing_layers[0], LoadStatus.KEEP
|
||||
|
||||
|
||||
def verfahrensgebiet_alkis(tab_widget):
|
||||
def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False):
|
||||
"""Lädt Verfahrensgebiet aus ALKIS-WFS basierend auf Verfahrensnummer.
|
||||
|
||||
Args:
|
||||
@@ -92,10 +92,12 @@ def verfahrensgebiet_alkis(tab_widget):
|
||||
"In den Projekteigenschaften ist noch keine Verfahrensnummer eingetragen!")
|
||||
return None, LoadStatus.NONE, []
|
||||
|
||||
# Vorhandenen Layer prüfen
|
||||
existing, status = _check_existing_layer(tab_widget)
|
||||
if status == LoadStatus.KEEP:
|
||||
return existing, status, []
|
||||
# Vorhandenen Layer prüfen (überspringbar für die Shape-Pipeline)
|
||||
status = LoadStatus.FIRST
|
||||
if not skip_existing_check:
|
||||
existing, status = _check_existing_layer(tab_widget)
|
||||
if status == LoadStatus.KEEP:
|
||||
return existing, status, []
|
||||
|
||||
# WFS mit Filter laden – nur erste 5 Stellen der VKZ für den Vergleich verwenden,
|
||||
# da die Daten im Dienst ggf. abweichende Suffixe führen.
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
"""sn_verfahrensgebiet/functions/verfahrensgebiet_shape_komplett.py – VG-Lade-Workflow aus Shape (ZIP).
|
||||
|
||||
Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets
|
||||
aus einem ZIP-Archiv mit Shapefile-Set:
|
||||
|
||||
1. Dateidialog öffnen (.zip-Filter); Nutzerabbruch beendet den Vorgang.
|
||||
2. ZIP neben der ZIP-Datei entpacken und auf ein gültiges Shapefile-Set prüfen.
|
||||
3. Shapefile als QGIS-Vektorlayer laden und validieren (Standard-QGIS-Routinen).
|
||||
4. Alle Features per ``QgsGeometry.unaryUnion`` zu einem Single-Feature-
|
||||
Memory-Layer „Verfahrensgebiet" verschmelzen.
|
||||
5. Gemarkungsschlüssel per Geometrie aus ``AX_Gemarkung`` vorschlagen.
|
||||
6. Flurstücke per Dialog (mit Vorschlag) laden.
|
||||
7. Flurstücke räumlich auf das Shape-VG einschränken (``selectByLocation``).
|
||||
8. Temporären enriched Layer aus Auswahl für den Flächenvergleich erzeugen
|
||||
(wird **nicht** ins Projekt eingefügt und **nicht** zurückgegeben).
|
||||
9. Flächenvergleich zwischen Shape-VG und temporärem enriched Layer.
|
||||
|
||||
- **Übereinstimmung** (≤ Toleranz): modale OK-Meldung, Ablauf beendet.
|
||||
- **Abweichung**: 2-Optionen-Dialog (Detailprüfung / Abbrechen).
|
||||
|
||||
10. Bei Wahl „Detailprüfung" (knickpunktbasiert):
|
||||
|
||||
- Grenzpunkte (AX_PunktortTA) per BBox laden.
|
||||
- Knickpunkte des Shape-VG mit PunktortTA vergleichen (grün/rot/orange).
|
||||
- Korrigiertes Polygon aus identischen + gefundenen PunktortTA ableiten.
|
||||
- Ergebnislayer (Punkte), Verbindungslinien und Polygon ins Projekt einfügen.
|
||||
- Abschlussmeldung mit Zählung.
|
||||
|
||||
Das Shape-VG (``Verfahrensgebiet``) ist das finale Rückgabe-Layer. Ein
|
||||
``enriched Layer aus Flurstücken`` wird nur temporär für den Flächenvergleich
|
||||
erzeugt, aber weder ins Projekt eingefügt noch zurückgegeben.
|
||||
|
||||
**Rückgabe:**
|
||||
``Tuple[shape_vg_layer, flst_layer, None, LoadStatus]``
|
||||
|
||||
**Abbruchverhalten:** Jeder Schritt, der :attr:`LoadStatus.NONE` zurückgibt
|
||||
oder kein Ergebnis liefert, bricht den Workflow ab.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from sn_basis.functions import QgsProject, QgsVectorLayer, QgsFeature
|
||||
from sn_basis.functions import QMessageBox
|
||||
from sn_basis.functions.message_wrapper import warning
|
||||
from sn_basis.functions.variable_wrapper import get_variable
|
||||
from sn_basis.functions.qt_wrapper import QFileDialog
|
||||
from sn_basis.functions.qgiscore_wrapper import QgsGeometry
|
||||
from sn_basis.functions.dialog_wrapper import (
|
||||
create_progress_dialog,
|
||||
show_info_dialog,
|
||||
ask_detailpruefung_oder_abbrechen,
|
||||
)
|
||||
|
||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus, verfahrensgebiet_alkis
|
||||
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,
|
||||
VERF_LAYER_NAME,
|
||||
)
|
||||
from sn_verfahrensgebiet.functions.grenzpunkt_alkis import lade_punktort_ta
|
||||
from sn_verfahrensgebiet.functions.knickpunkt_pruefung import (
|
||||
knickpunkt_pruefung,
|
||||
_extrahiere_knickpunkte,
|
||||
erstelle_korrigiertes_polygon,
|
||||
)
|
||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
||||
_select_by_location,
|
||||
_vergleiche_flaechen,
|
||||
_lese_flaechen_toleranz,
|
||||
_entferne_hilfs_layer,
|
||||
MINDEST_FLAECHEN_TOLERANZ_M2,
|
||||
BROR_LAYER_NAME,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Interne Hilfsfunktionen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _finde_shp(verzeichnis: str) -> Optional[str]:
|
||||
"""Sucht rekursiv nach der ersten ``.shp``-Datei im Verzeichnis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
verzeichnis :
|
||||
Startverzeichnis für die Suche.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[str]
|
||||
Absoluter Pfad zur ersten gefundenen ``.shp``-Datei, oder ``None``.
|
||||
"""
|
||||
for root, _dirs, files in os.walk(verzeichnis):
|
||||
for fname in files:
|
||||
if fname.lower().endswith(".shp"):
|
||||
return os.path.join(root, fname)
|
||||
return None
|
||||
|
||||
|
||||
def _erstelle_memory_layer_aus_shp(shp_layer) -> Optional[object]:
|
||||
"""Vereint alle Features des Shapefile-Layers zu einem Single-Feature-
|
||||
Memory-Layer „Verfahrensgebiet" via ``QgsGeometry.unaryUnion``.
|
||||
|
||||
Alle Felder des Shapefiles werden in den Memory-Layer übernommen.
|
||||
Die Attributwerte des ersten Features mit gültiger Geometrie werden
|
||||
auf das Union-Feature gesetzt.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shp_layer :
|
||||
Geladener ``QgsVectorLayer`` aus dem Shapefile.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[QgsVectorLayer]
|
||||
Single-Feature-Memory-Layer mit Union-Geometrie und Shapefile-Attributen
|
||||
oder ``None`` bei Fehler.
|
||||
"""
|
||||
geom_list = []
|
||||
first_feat = None
|
||||
|
||||
for feat in shp_layer.getFeatures():
|
||||
try:
|
||||
geom = feat.geometry()
|
||||
if geom and not geom.isEmpty():
|
||||
geom_list.append(geom)
|
||||
if first_feat is None:
|
||||
first_feat = feat
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not geom_list:
|
||||
return None
|
||||
|
||||
union_geom = QgsGeometry.unaryUnion(geom_list)
|
||||
if not union_geom or union_geom.isEmpty():
|
||||
return None
|
||||
|
||||
crs_authid = shp_layer.crs().authid()
|
||||
mem_layer = QgsVectorLayer(
|
||||
f"MultiPolygon?crs={crs_authid}", VERF_LAYER_NAME, "memory"
|
||||
)
|
||||
pr = mem_layer.dataProvider()
|
||||
|
||||
# Felder des Shapefiles übernehmen
|
||||
try:
|
||||
pr.addAttributes(list(shp_layer.fields()))
|
||||
mem_layer.updateFields()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
feat_out = QgsFeature(mem_layer.fields())
|
||||
feat_out.setGeometry(union_geom)
|
||||
|
||||
# Attributwerte vom ersten Feature übernehmen
|
||||
if first_feat is not None:
|
||||
try:
|
||||
feat_out.setAttributes(first_feat.attributes())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pr.addFeatures([feat_out])
|
||||
mem_layer.updateExtents()
|
||||
|
||||
if not mem_layer.isValid() or mem_layer.featureCount() == 0:
|
||||
return None
|
||||
|
||||
return mem_layer
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Öffentliche Lade-Hilfsfunktion
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def lade_vg_aus_zip(parent_widget) -> Optional[object]:
|
||||
"""Öffnet Dateidialog, entpackt ZIP und gibt Union-Memory-Layer zurück.
|
||||
|
||||
Schrittfolge:
|
||||
|
||||
1. ``QFileDialog.getOpenFileName`` mit Filter ``ZIP-Dateien (*.zip)``.
|
||||
2. ZIP neben der Quelldatei in ein Unterverzeichnis entpacken.
|
||||
3. Erste ``.shp``-Datei im Verzeichnis finden.
|
||||
4. Als ``QgsVectorLayer`` laden und ``isValid()`` prüfen.
|
||||
5. Alle Features per ``QgsGeometry.unaryUnion`` verschmelzen.
|
||||
6. Single-Feature-Memory-Layer „Verfahrensgebiet" zurückgeben.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parent_widget :
|
||||
Eltern-Widget für Dateidialog und Fehlermeldungen.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[QgsVectorLayer]
|
||||
Valider Memory-Layer „Verfahrensgebiet" oder ``None`` bei Abbruch/Fehler.
|
||||
"""
|
||||
pfad, _ = QFileDialog.getOpenFileName(
|
||||
parent_widget,
|
||||
"ZIP-Datei mit Shapefile-Set auswählen",
|
||||
"",
|
||||
"ZIP-Dateien (*.zip)",
|
||||
)
|
||||
if not pfad:
|
||||
return None # Nutzerabbruch
|
||||
|
||||
zip_dir = os.path.dirname(pfad)
|
||||
zip_base = os.path.splitext(os.path.basename(pfad))[0]
|
||||
extract_dir = os.path.join(zip_dir, zip_base)
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(pfad, "r") as zf:
|
||||
zf.extractall(extract_dir)
|
||||
except Exception:
|
||||
QMessageBox.warning(
|
||||
parent_widget,
|
||||
"Fehler",
|
||||
"Die ZIP-Datei konnte nicht entpackt werden.\n"
|
||||
"Bitte eine gültige ZIP-Datei auswählen.",
|
||||
)
|
||||
return None
|
||||
|
||||
shp_path = _finde_shp(extract_dir)
|
||||
if not shp_path:
|
||||
QMessageBox.warning(
|
||||
parent_widget,
|
||||
"Fehler",
|
||||
"gültiges Shape-Dateiset angeben",
|
||||
)
|
||||
return None
|
||||
|
||||
raw_layer = QgsVectorLayer(shp_path, "tmp_shape_vg", "ogr")
|
||||
if not raw_layer or not raw_layer.isValid():
|
||||
QMessageBox.warning(
|
||||
parent_widget,
|
||||
"Fehler",
|
||||
"gültiges Shape-Dateiset angeben",
|
||||
)
|
||||
return None
|
||||
|
||||
mem_layer = _erstelle_memory_layer_aus_shp(raw_layer)
|
||||
if mem_layer is None:
|
||||
QMessageBox.warning(
|
||||
parent_widget,
|
||||
"Fehler",
|
||||
"gültiges Shape-Dateiset angeben",
|
||||
)
|
||||
return None
|
||||
|
||||
return mem_layer
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Öffentliche API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def verfahrensgebiet_shape_komplett(
|
||||
tab_widget,
|
||||
) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]:
|
||||
"""Vollständiger VG-Lade-Workflow aus Shape (ZIP): ZIP → BROR → Gemarkungen →
|
||||
Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung.
|
||||
|
||||
Das aus der ZIP geladene Shape-VG ist das finale Verfahrensgebiet-Layer.
|
||||
Ist die Projektvariable ``verfahrensnummer`` gesetzt, wird das zugehörige
|
||||
BROR-Objekt (``AX_BauRaumOderBodenordnungsrecht``) aus ALKIS geladen und
|
||||
als Vergleichsobjekt im Flächenvergleich verwendet. Andernfalls wird ein
|
||||
temporärer enriched Layer aus Flurstücken für den Vergleich herangezogen.
|
||||
|
||||
Schrittfolge:
|
||||
|
||||
1. ``lade_vg_aus_zip(tab_widget)`` – ZIP öffnen, Shapefile validieren,
|
||||
Union-Memory-Layer „Verfahrensgebiet" erzeugen.
|
||||
2. ``verfahrensgebiet_alkis(tab_widget, skip_existing_check=True)`` –
|
||||
BROR-Objekt als Memory-Layer (sofern VKZ gesetzt).
|
||||
Der BROR wird **vor** dem Shape-VG ins Projekt eingefügt, um den
|
||||
Namenskonflikt mit ``_check_existing_layer`` zu vermeiden.
|
||||
3. Shape-VG ins Projekt einfügen; BROR umbenennen zu
|
||||
``BauRaumOderBodenordnungsrecht (ALKIS)`` und zum Projekt hinzufügen.
|
||||
4. ``gemarkung_schluessel_und_namen_von_vg_geometrie(geom)`` – Schlüssel.
|
||||
5. ``flurstueck_alkis(...)`` – Flurstücke laden.
|
||||
6. ``_select_by_location(...)`` – räumliche Selektion.
|
||||
7. Flächenvergleich:
|
||||
|
||||
- Mit BROR: ``_vergleiche_flaechen(bror_layer, shape_vg_layer, ...)``.
|
||||
- Ohne BROR: temp. enriched Layer erzeugen und mit Shape-VG vergleichen.
|
||||
|
||||
8. Ggf. Detailprüfung (knickpunktbasiert) auf BROR bzw. Shape-VG.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tab_widget :
|
||||
Eltern-Widget für alle Dialoge.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[Optional[shape_vg_layer], Optional[flst_layer], Optional[bror_layer], LoadStatus]
|
||||
- ``shape_vg_layer``: Memory-Layer „Verfahrensgebiet" (aus Shape).
|
||||
- ``flst_layer``: Flurstücke-Layer (bleibt im Projekt).
|
||||
- ``bror_layer``: BROR-Layer ``BauRaumOderBodenordnungsrecht (ALKIS)``
|
||||
(wenn geladen, bleibt im Projekt); ``None`` wenn VKZ nicht gesetzt.
|
||||
- :class:`LoadStatus`: ``FIRST`` oder ``NONE``.
|
||||
"""
|
||||
project = QgsProject.instance()
|
||||
shape_vg_layer = None
|
||||
bror_layer = None
|
||||
shape_was_added_by_us = False
|
||||
bror_was_added_by_us = False
|
||||
abgeschlossen = False
|
||||
|
||||
progress = create_progress_dialog(
|
||||
7, "Verfahrensgebiet aus Shape laden", "Initialisierung …"
|
||||
)
|
||||
|
||||
try:
|
||||
# ------------------------------------------------------------------
|
||||
# Schritt 1: Shape-VG aus ZIP laden (noch NICHT ins Projekt einfügen)
|
||||
# ------------------------------------------------------------------
|
||||
progress.set_label("Schritt 1/6: Verfahrensgebiet aus ZIP laden …")
|
||||
progress.set_value(0)
|
||||
if progress.is_canceled():
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
shape_vg_layer = lade_vg_aus_zip(tab_widget)
|
||||
progress.raise_to_front()
|
||||
|
||||
if not shape_vg_layer:
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
vg_status = LoadStatus.FIRST
|
||||
progress.set_value(1)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schritt 2: BROR aus ALKIS laden (vor shape_vg, um Namenskonflikt
|
||||
# in _check_existing_layer zu vermeiden)
|
||||
# ------------------------------------------------------------------
|
||||
progress.set_label("Schritt 2/6: BROR aus ALKIS laden …")
|
||||
if progress.is_canceled():
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
vkz = get_variable("verfahrensnummer") or ""
|
||||
if str(vkz).strip():
|
||||
bror_result, bror_status, _ = verfahrensgebiet_alkis(
|
||||
tab_widget, skip_existing_check=True
|
||||
)
|
||||
progress.raise_to_front()
|
||||
if bror_status != LoadStatus.NONE and bror_result:
|
||||
bror_result.setName(BROR_LAYER_NAME)
|
||||
project.addMapLayer(bror_result)
|
||||
bror_layer = bror_result
|
||||
bror_was_added_by_us = True
|
||||
|
||||
progress.set_value(2)
|
||||
|
||||
# Shape-VG erst jetzt ins Projekt einfügen (nach BROR-Ladung)
|
||||
project.addMapLayer(shape_vg_layer)
|
||||
shape_was_added_by_us = True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schritt 3: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
||||
# ------------------------------------------------------------------
|
||||
progress.set_label("Schritt 3/6: Gemarkungsschlüssel ermitteln …")
|
||||
if progress.is_canceled():
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
shape_feats = list(shape_vg_layer.getFeatures())
|
||||
vg_geom = shape_feats[0].geometry() if shape_feats else None
|
||||
|
||||
schluessel_namen = (
|
||||
gemarkung_schluessel_und_namen_von_vg_geometrie(vg_geom)
|
||||
if vg_geom else []
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
progress.set_value(3)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schritt 4: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt)
|
||||
# ------------------------------------------------------------------
|
||||
progress.set_label("Schritt 4/6: Flurstücke aus ALKIS laden …")
|
||||
if progress.is_canceled():
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
flst_layer, flst_status = flurstueck_alkis(
|
||||
tab_widget,
|
||||
vorgabe_schluessel=vorgabe_schluessel,
|
||||
vorgabe_namen=vorgabe_namen if vorgabe_namen else None,
|
||||
ohne_dialog=True,
|
||||
)
|
||||
|
||||
if flst_status == LoadStatus.NONE or not flst_layer:
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
progress.set_value(4)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schritt 5: Räumliche Selektion
|
||||
# ------------------------------------------------------------------
|
||||
progress.set_label("Schritt 5/6: Flurstücke räumlich selektieren …")
|
||||
if progress.is_canceled():
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
anzahl = _select_by_location(flst_layer, shape_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, None, LoadStatus.NONE
|
||||
|
||||
progress.set_value(5)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Schritt 6: Flächenvergleich als Gate
|
||||
# ------------------------------------------------------------------
|
||||
progress.set_label("Schritt 6/6: Flächenvergleich …")
|
||||
if progress.is_canceled():
|
||||
return None, None, None, LoadStatus.NONE
|
||||
|
||||
toleranz = _lese_flaechen_toleranz()
|
||||
|
||||
if bror_layer and bror_layer.isValid():
|
||||
# BROR verfügbar → als Referenz-Objekt verwenden
|
||||
ref_flaeche, shape_flaeche, stimmt_ueberein = _vergleiche_flaechen(
|
||||
bror_layer, shape_vg_layer, toleranz
|
||||
)
|
||||
dialog_ref_label = "BauRaumOderBodenordnungsrecht"
|
||||
dialog_vg_label = "Shape-Verfahrensgebiet "
|
||||
pruef_layer = bror_layer
|
||||
else:
|
||||
# Kein BROR → temp. enriched Layer aus Flurstücken erzeugen
|
||||
temp_enriched, _, _ = erstelle_verfahrensgebiet_aus_auswahl(flst_layer)
|
||||
ref_flaeche, shape_flaeche, stimmt_ueberein = _vergleiche_flaechen(
|
||||
shape_vg_layer, temp_enriched, toleranz
|
||||
)
|
||||
dialog_ref_label = "Shape-Verfahrensgebiet "
|
||||
dialog_vg_label = "Verfahrensgebiet (Flurstücke)"
|
||||
pruef_layer = shape_vg_layer
|
||||
|
||||
progress.set_value(6)
|
||||
|
||||
if stimmt_ueberein:
|
||||
progress.set_value(7)
|
||||
progress.close()
|
||||
show_info_dialog(
|
||||
"Flächenvergleich",
|
||||
"Verfahrensgebiet mit erwarteter Geometrie erzeugt",
|
||||
parent=tab_widget,
|
||||
)
|
||||
abgeschlossen = True
|
||||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Flächenabweichung → 2-Optionen-Dialog
|
||||
# ------------------------------------------------------------------
|
||||
progress.close()
|
||||
diff = abs(ref_flaeche - shape_flaeche)
|
||||
entscheidung = ask_detailpruefung_oder_abbrechen(
|
||||
tab_widget,
|
||||
"Abweichung erkannt",
|
||||
f"Abweichung erkannt.\n\n"
|
||||
f"{dialog_ref_label}: {ref_flaeche:.2f} m²\n"
|
||||
f"{dialog_vg_label}: {shape_flaeche:.2f} m²\n"
|
||||
f"Differenz: {diff:.2f} m²\n\n"
|
||||
f"Wie möchten Sie fortfahren?",
|
||||
)
|
||||
|
||||
if entscheidung == "abbrechen":
|
||||
abgeschlossen = True
|
||||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Option: Detailprüfung (knickpunktbasiert) auf pruef_layer
|
||||
# (= BROR wenn vorhanden, sonst Shape-VG)
|
||||
# ------------------------------------------------------------------
|
||||
knickpunkte = _extrahiere_knickpunkte(pruef_layer)
|
||||
n_knick = len(knickpunkte)
|
||||
gesamt_schritte = n_knick + 5
|
||||
|
||||
detail_progress = create_progress_dialog(
|
||||
gesamt_schritte,
|
||||
"Detailprüfung (Knickpunkte)",
|
||||
"Schritt 1: Grenzpunkte laden …",
|
||||
)
|
||||
detail_progress.set_value(0)
|
||||
|
||||
punktort_layer = lade_punktort_ta(
|
||||
vorgabe_schluessel, vg_geom, progress=detail_progress
|
||||
)
|
||||
|
||||
if detail_progress.is_canceled():
|
||||
detail_progress.close()
|
||||
abgeschlossen = True
|
||||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||||
|
||||
detail_progress.set_value(1)
|
||||
|
||||
if punktort_layer is None:
|
||||
detail_progress.close()
|
||||
abgeschlossen = True
|
||||
if not detail_progress.is_canceled():
|
||||
QMessageBox.critical(
|
||||
tab_widget,
|
||||
"WFS-Fehler",
|
||||
"Die Grenzpunktdaten (PunktortTA) konnten nicht geladen werden.\n\n"
|
||||
"Mögliche Ursache: Netzwerk- oder Proxy-Fehler.\n"
|
||||
"Bitte prüfen Sie die QGIS-Proxy-Einstellungen "
|
||||
"(Einstellungen -> Optionen -> Netzwerk) und den Log-Eintrag "
|
||||
"unter Ansicht -> Bedienfelder -> Protokollnachrichten "
|
||||
"(Kategorie 'sn_verfahrensgebiet').",
|
||||
)
|
||||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||||
|
||||
ergebnis_layer, identisch, kein_pta, fehlender_knick = knickpunkt_pruefung(
|
||||
pruef_layer,
|
||||
punktort_layer,
|
||||
flst_layer,
|
||||
progress=detail_progress,
|
||||
knickpunkte=knickpunkte,
|
||||
)
|
||||
|
||||
if detail_progress.is_canceled():
|
||||
detail_progress.close()
|
||||
abgeschlossen = True
|
||||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||||
|
||||
detail_progress.set_value(gesamt_schritte)
|
||||
detail_progress.close()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Korrigiertes Polygon aus identischen + fehlenden Knickpunkten
|
||||
# (nur für Visualisierung – shape_vg_layer bleibt das finale VG)
|
||||
# ------------------------------------------------------------------
|
||||
if ergebnis_layer and ergebnis_layer.isValid():
|
||||
linien_layer_korr, polygon_layer_korr = erstelle_korrigiertes_polygon(
|
||||
pruef_layer, ergebnis_layer, pruef_layer.crs().authid()
|
||||
)
|
||||
if polygon_layer_korr is not None and polygon_layer_korr.isValid():
|
||||
if linien_layer_korr is not None and linien_layer_korr.isValid():
|
||||
project.addMapLayer(linien_layer_korr)
|
||||
project.addMapLayer(polygon_layer_korr)
|
||||
|
||||
if ergebnis_layer and ergebnis_layer.isValid():
|
||||
project.addMapLayer(ergebnis_layer)
|
||||
|
||||
show_info_dialog(
|
||||
"Detailprüfung abgeschlossen",
|
||||
f"Knickpunkte geprüft: {n_knick}\n"
|
||||
f" Identisch (grün): {identisch}\n"
|
||||
f" Kein PunktortTA (rot): {kein_pta}\n"
|
||||
f" Fehlende Knickpunkte (orange): {fehlender_knick}",
|
||||
parent=tab_widget,
|
||||
)
|
||||
|
||||
abgeschlossen = True
|
||||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||||
|
||||
finally:
|
||||
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
||||
# Shape-VG und BROR bei Fehler/Abbruch entfernen
|
||||
if not abgeschlossen:
|
||||
if shape_was_added_by_us and shape_vg_layer is not None:
|
||||
try:
|
||||
project.removeMapLayer(shape_vg_layer.id())
|
||||
except Exception:
|
||||
pass
|
||||
if bror_was_added_by_us and bror_layer is not None:
|
||||
try:
|
||||
project.removeMapLayer(bror_layer.id())
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,13 @@
|
||||
[general]
|
||||
version=26.7.3-testing
|
||||
name=LNO Sachsen | Verfahrensgebiet
|
||||
description=Plugin zum Erzeugen eines Objektes "Verfahrensgebiet"
|
||||
author=Daniel Helbig, Michael Otto
|
||||
homepage=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet
|
||||
tracker=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet/issues
|
||||
repository=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Verfahrensgebiet/src/branch/testing/
|
||||
QGISMaximumVersion=4.99
|
||||
qgisMinimumVersion=3.40
|
||||
experimental=true
|
||||
deprecated=false
|
||||
supportsQt6=true
|
||||
@@ -0,0 +1,932 @@
|
||||
"""sn_verfahrensgebiet/tests/test_verfahrensgebiet_shape_komplett.py
|
||||
|
||||
Unit-Tests für :mod:`sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett`.
|
||||
|
||||
Getestete Szenarien:
|
||||
|
||||
lade_vg_aus_zip:
|
||||
- Dateidialog abgebrochen (leerer Pfad) → None
|
||||
- ZIP-Entpacken schlägt fehl (Exception) → None + QMessageBox.warning
|
||||
- Kein .shp in ZIP → None + Warning "gültiges Shape-Dateiset angeben"
|
||||
- QgsVectorLayer nicht valid → None + Warning
|
||||
- Leere Geometrien im Shapefile → None + Warning
|
||||
- Erfolgspfad → Memory-Layer mit Union-Geometrie, Name "Verfahrensgebiet"
|
||||
|
||||
verfahrensgebiet_shape_komplett:
|
||||
- lade_vg_aus_zip None (Abbruch) → (None, None, None, NONE)
|
||||
- flurstueck_alkis NONE → (None, None, None, NONE)
|
||||
- Keine Flurstücke selektiert → (None, flst, None, NONE) + QMessageBox.warning
|
||||
- erstelle_verfahrensgebiet_aus_auswahl ungültig → Workflow noch immer abgeschlossen
|
||||
(temp_enriched-Fehler: stimmt_ueberein=True wegen 0-Fläche)
|
||||
- Flächenübereinstimmung → show_info_dialog, return (shape_vg, flst, None, FIRST)
|
||||
- Flächenabweichung + abbrechen → return (shape_vg, flst, None, FIRST)
|
||||
- Flächenabweichung + detailpruefung → lade_punktort_ta + knickpunkt_pruefung aufgerufen
|
||||
- Cleanup: TempVerfahrensgebiet stets aus Projekt entfernt
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Minimales QGIS-Mock-Setup
|
||||
# ---------------------------------------------------------------------------
|
||||
if "qgis" not in sys.modules:
|
||||
qgis_mod = types.ModuleType("qgis")
|
||||
qgis_core = types.ModuleType("qgis.core")
|
||||
qgis_utils = types.ModuleType("qgis.utils")
|
||||
qgis_utils.iface = types.SimpleNamespace()
|
||||
qgis_utils.plugins = {}
|
||||
qgis_mod.utils = qgis_utils
|
||||
qgis_mod.core = qgis_core
|
||||
sys.modules["qgis"] = qgis_mod
|
||||
sys.modules["qgis.core"] = qgis_core
|
||||
sys.modules["qgis.utils"] = qgis_utils
|
||||
|
||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus
|
||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import (
|
||||
verfahrensgebiet_shape_komplett,
|
||||
lade_vg_aus_zip,
|
||||
_finde_shp,
|
||||
_erstelle_memory_layer_aus_shp,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hilfs-Dummies
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _make_layer(valid=True, name="Layer", lid="L1", feature_count=1, area=1000.0):
|
||||
layer = MagicMock()
|
||||
layer.isValid.return_value = valid
|
||||
layer.id.return_value = lid
|
||||
layer.name.return_value = name
|
||||
layer.featureCount.return_value = feature_count
|
||||
layer.selectedFeatureCount.return_value = feature_count
|
||||
|
||||
mock_geom = MagicMock()
|
||||
mock_geom.isEmpty.return_value = False
|
||||
mock_geom.area.return_value = area
|
||||
mock_feat = MagicMock()
|
||||
mock_feat.geometry.return_value = mock_geom
|
||||
layer.getFeatures.return_value = [mock_feat]
|
||||
|
||||
mock_crs = MagicMock()
|
||||
mock_crs.authid.return_value = "EPSG:25833"
|
||||
layer.crs.return_value = mock_crs
|
||||
return layer
|
||||
|
||||
|
||||
class _DummyProject:
|
||||
def __init__(self):
|
||||
self.added = []
|
||||
self.removed = []
|
||||
self._layers_by_name: dict = {}
|
||||
|
||||
def addMapLayer(self, layer):
|
||||
self.added.append(layer)
|
||||
|
||||
def removeMapLayer(self, lid):
|
||||
self.removed.append(lid)
|
||||
|
||||
def mapLayersByName(self, name):
|
||||
return self._layers_by_name.get(name, [])
|
||||
|
||||
def mapLayer(self, lid):
|
||||
return None
|
||||
|
||||
def instance(self):
|
||||
return self
|
||||
|
||||
|
||||
def _make_progress_mock(canceled=False):
|
||||
prog = MagicMock()
|
||||
prog.is_canceled.return_value = canceled
|
||||
return prog
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Patch-Konstanten
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MOD = "sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett"
|
||||
_PATCH_LADE_ZIP = f"{_MOD}.lade_vg_aus_zip"
|
||||
_PATCH_GEM = f"{_MOD}.gemarkung_schluessel_und_namen_von_vg_geometrie"
|
||||
_PATCH_FLST = f"{_MOD}.flurstueck_alkis"
|
||||
_PATCH_SELECT = f"{_MOD}._select_by_location"
|
||||
_PATCH_ERSTELLE = f"{_MOD}.erstelle_verfahrensgebiet_aus_auswahl"
|
||||
_PATCH_PROJECT = f"{_MOD}.QgsProject"
|
||||
_PATCH_MSGBOX = f"{_MOD}.QMessageBox"
|
||||
_PATCH_WARNING = f"{_MOD}.warning"
|
||||
_PATCH_PROGRESS = f"{_MOD}.create_progress_dialog"
|
||||
_PATCH_VERGLEICHE = f"{_MOD}._vergleiche_flaechen"
|
||||
_PATCH_SHOW_INFO = f"{_MOD}.show_info_dialog"
|
||||
_PATCH_ASK_DIALOG = f"{_MOD}.ask_detailpruefung_oder_abbrechen"
|
||||
_PATCH_GP_ALKIS = f"{_MOD}.lade_punktort_ta"
|
||||
_PATCH_KNICK = f"{_MOD}.knickpunkt_pruefung"
|
||||
_PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte"
|
||||
_PATCH_LESE_TOL = f"{_MOD}._lese_flaechen_toleranz"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: _finde_shp
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFindShp(unittest.TestCase):
|
||||
|
||||
def test_findet_shp_in_root(self):
|
||||
with patch("os.walk") as mock_walk:
|
||||
mock_walk.return_value = [
|
||||
("/extract", [], ["shape.shp", "shape.dbf"]),
|
||||
]
|
||||
result = _finde_shp("/extract")
|
||||
self.assertEqual(result, os.path.join("/extract", "shape.shp"))
|
||||
|
||||
def test_findet_shp_in_unterordner(self):
|
||||
with patch("os.walk") as mock_walk:
|
||||
mock_walk.return_value = [
|
||||
("/extract", ["sub"], []),
|
||||
("/extract/sub", [], ["daten.shp"]),
|
||||
]
|
||||
result = _finde_shp("/extract")
|
||||
self.assertEqual(result, os.path.join("/extract/sub", "daten.shp"))
|
||||
|
||||
def test_kein_shp_gibt_none_zurueck(self):
|
||||
with patch("os.walk") as mock_walk:
|
||||
mock_walk.return_value = [
|
||||
("/extract", [], ["shape.dbf", "shape.prj"]),
|
||||
]
|
||||
result = _finde_shp("/extract")
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_case_insensitive_erkennung(self):
|
||||
with patch("os.walk") as mock_walk:
|
||||
mock_walk.return_value = [
|
||||
("/extract", [], ["DATEN.SHP"]),
|
||||
]
|
||||
result = _finde_shp("/extract")
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: _erstelle_memory_layer_aus_shp
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestErstelleMemoryLayerAusShp(unittest.TestCase):
|
||||
|
||||
def test_leere_geometrien_geben_none(self):
|
||||
shp_layer = MagicMock()
|
||||
geom = MagicMock()
|
||||
geom.isEmpty.return_value = True
|
||||
feat = MagicMock()
|
||||
feat.geometry.return_value = geom
|
||||
shp_layer.getFeatures.return_value = [feat]
|
||||
shp_layer.crs.return_value.authid.return_value = "EPSG:25833"
|
||||
|
||||
result = _erstelle_memory_layer_aus_shp(shp_layer)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_keine_features_gibt_none(self):
|
||||
shp_layer = MagicMock()
|
||||
shp_layer.getFeatures.return_value = []
|
||||
shp_layer.crs.return_value.authid.return_value = "EPSG:25833"
|
||||
|
||||
result = _erstelle_memory_layer_aus_shp(shp_layer)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch(f"{_MOD}.QgsGeometry")
|
||||
@patch(f"{_MOD}.QgsVectorLayer")
|
||||
@patch(f"{_MOD}.QgsFeature")
|
||||
def test_attribute_von_erstem_feature_werden_gesetzt(
|
||||
self, mock_feat_cls, mock_vl_cls, mock_geom_cls
|
||||
):
|
||||
"""Attributwerte des ersten Features werden auf das Union-Feature übertragen."""
|
||||
# Geometrie-Mock
|
||||
mock_union = MagicMock()
|
||||
mock_union.isEmpty.return_value = False
|
||||
mock_geom_cls.unaryUnion.return_value = mock_union
|
||||
|
||||
# Memory-Layer-Mock
|
||||
mock_mem = MagicMock()
|
||||
mock_mem.isValid.return_value = True
|
||||
mock_mem.featureCount.return_value = 1
|
||||
mock_vl_cls.return_value = mock_mem
|
||||
mock_pr = MagicMock()
|
||||
mock_mem.dataProvider.return_value = mock_pr
|
||||
|
||||
# Feature-Mock (Output)
|
||||
mock_feat_out = MagicMock()
|
||||
mock_feat_cls.return_value = mock_feat_out
|
||||
|
||||
# Shapefile-Layer-Mock
|
||||
shp_layer = MagicMock()
|
||||
shp_layer.crs.return_value.authid.return_value = "EPSG:25833"
|
||||
shp_layer.fields.return_value = [MagicMock(), MagicMock()] # list → iterable
|
||||
|
||||
mock_geom = MagicMock()
|
||||
mock_geom.isEmpty.return_value = False
|
||||
src_feat = MagicMock()
|
||||
src_feat.geometry.return_value = mock_geom
|
||||
src_feat.attributes.return_value = ["wert_a", 42]
|
||||
shp_layer.getFeatures.return_value = [src_feat]
|
||||
|
||||
result = _erstelle_memory_layer_aus_shp(shp_layer)
|
||||
|
||||
# Felder sollen übernommen werden
|
||||
mock_pr.addAttributes.assert_called_once()
|
||||
# Attributwerte sollen vom ersten Feature gesetzt werden
|
||||
mock_feat_out.setAttributes.assert_called_once_with(["wert_a", 42])
|
||||
self.assertIs(result, mock_mem)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: lade_vg_aus_zip
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestLadeVgAusZip(unittest.TestCase):
|
||||
|
||||
def _patch(self, **kwargs):
|
||||
"""Hilfsmethode: kombinierter Patch-Kontext für lade_vg_aus_zip."""
|
||||
return kwargs
|
||||
|
||||
@patch(f"{_MOD}.QFileDialog")
|
||||
def test_dialog_abgebrochen_gibt_none_zurueck(self, mock_fd):
|
||||
mock_fd.getOpenFileName.return_value = ("", "")
|
||||
result = lade_vg_aus_zip(None)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch(f"{_MOD}.QMessageBox")
|
||||
@patch(f"{_MOD}.zipfile")
|
||||
@patch(f"{_MOD}.QFileDialog")
|
||||
def test_zip_fehler_gibt_none_und_warning(self, mock_fd, mock_zf, mock_mb):
|
||||
mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "")
|
||||
mock_zf.ZipFile.side_effect = Exception("bad zip")
|
||||
|
||||
with patch("os.path.dirname", return_value="/tmp"), \
|
||||
patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \
|
||||
patch("os.path.basename", return_value="vg.zip"), \
|
||||
patch("os.path.join", return_value="/tmp/vg"):
|
||||
result = lade_vg_aus_zip(None)
|
||||
|
||||
self.assertIsNone(result)
|
||||
mock_mb.warning.assert_called_once()
|
||||
|
||||
@patch(f"{_MOD}.QMessageBox")
|
||||
@patch(f"{_MOD}._finde_shp", return_value=None)
|
||||
@patch(f"{_MOD}.zipfile")
|
||||
@patch(f"{_MOD}.QFileDialog")
|
||||
def test_kein_shp_in_zip_gibt_none_und_warning(self, mock_fd, mock_zf, mock_find, mock_mb):
|
||||
mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "")
|
||||
mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock())
|
||||
mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
with patch("os.path.dirname", return_value="/tmp"), \
|
||||
patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \
|
||||
patch("os.path.basename", return_value="vg.zip"), \
|
||||
patch("os.path.join", return_value="/tmp/vg"):
|
||||
result = lade_vg_aus_zip(None)
|
||||
|
||||
self.assertIsNone(result)
|
||||
mock_mb.warning.assert_called_once()
|
||||
args = mock_mb.warning.call_args[0]
|
||||
self.assertIn("gültiges Shape-Dateiset angeben", args)
|
||||
|
||||
@patch(f"{_MOD}.QMessageBox")
|
||||
@patch(f"{_MOD}.QgsVectorLayer")
|
||||
@patch(f"{_MOD}._finde_shp", return_value="/tmp/vg/shape.shp")
|
||||
@patch(f"{_MOD}.zipfile")
|
||||
@patch(f"{_MOD}.QFileDialog")
|
||||
def test_invalider_qgslayer_gibt_none_und_warning(
|
||||
self, mock_fd, mock_zf, mock_find, mock_vl, mock_mb
|
||||
):
|
||||
mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "")
|
||||
mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock())
|
||||
mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
invalid_layer = MagicMock()
|
||||
invalid_layer.isValid.return_value = False
|
||||
mock_vl.return_value = invalid_layer
|
||||
|
||||
with patch("os.path.dirname", return_value="/tmp"), \
|
||||
patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \
|
||||
patch("os.path.basename", return_value="vg.zip"), \
|
||||
patch("os.path.join", return_value="/tmp/vg"):
|
||||
result = lade_vg_aus_zip(None)
|
||||
|
||||
self.assertIsNone(result)
|
||||
mock_mb.warning.assert_called_once()
|
||||
args = mock_mb.warning.call_args[0]
|
||||
self.assertIn("gültiges Shape-Dateiset angeben", args)
|
||||
|
||||
@patch(f"{_MOD}.QMessageBox")
|
||||
@patch(f"{_MOD}._erstelle_memory_layer_aus_shp", return_value=None)
|
||||
@patch(f"{_MOD}.QgsVectorLayer")
|
||||
@patch(f"{_MOD}._finde_shp", return_value="/tmp/vg/shape.shp")
|
||||
@patch(f"{_MOD}.zipfile")
|
||||
@patch(f"{_MOD}.QFileDialog")
|
||||
def test_leere_geometrien_geben_none_und_warning(
|
||||
self, mock_fd, mock_zf, mock_find, mock_vl, mock_mem, mock_mb
|
||||
):
|
||||
mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "")
|
||||
mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock())
|
||||
mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False)
|
||||
valid_layer = MagicMock()
|
||||
valid_layer.isValid.return_value = True
|
||||
mock_vl.return_value = valid_layer
|
||||
|
||||
with patch("os.path.dirname", return_value="/tmp"), \
|
||||
patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \
|
||||
patch("os.path.basename", return_value="vg.zip"), \
|
||||
patch("os.path.join", return_value="/tmp/vg"):
|
||||
result = lade_vg_aus_zip(None)
|
||||
|
||||
self.assertIsNone(result)
|
||||
mock_mb.warning.assert_called_once()
|
||||
|
||||
@patch(f"{_MOD}._erstelle_memory_layer_aus_shp")
|
||||
@patch(f"{_MOD}.QgsVectorLayer")
|
||||
@patch(f"{_MOD}._finde_shp", return_value="/tmp/vg/shape.shp")
|
||||
@patch(f"{_MOD}.zipfile")
|
||||
@patch(f"{_MOD}.QFileDialog")
|
||||
def test_erfolg_gibt_memory_layer_zurueck(
|
||||
self, mock_fd, mock_zf, mock_find, mock_vl, mock_mem
|
||||
):
|
||||
mock_fd.getOpenFileName.return_value = ("/tmp/vg.zip", "")
|
||||
mock_zf.ZipFile.return_value.__enter__ = MagicMock(return_value=MagicMock())
|
||||
mock_zf.ZipFile.return_value.__exit__ = MagicMock(return_value=False)
|
||||
valid_layer = MagicMock()
|
||||
valid_layer.isValid.return_value = True
|
||||
mock_vl.return_value = valid_layer
|
||||
|
||||
mem_layer = _make_layer(name="Verfahrensgebiet", lid="MEM1")
|
||||
mock_mem.return_value = mem_layer
|
||||
|
||||
with patch("os.path.dirname", return_value="/tmp"), \
|
||||
patch("os.path.splitext", return_value=("/tmp/vg", ".zip")), \
|
||||
patch("os.path.basename", return_value="vg.zip"), \
|
||||
patch("os.path.join", return_value="/tmp/vg"):
|
||||
result = lade_vg_aus_zip(None)
|
||||
|
||||
self.assertIs(result, mem_layer)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: verfahrensgebiet_shape_komplett (Hauptworkflow)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestVerfahrensgebietShapeKomplett(unittest.TestCase):
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Abbruchpfade
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_lade_zip_none_aborts(self, mock_lade, mock_proj, mock_prog):
|
||||
mock_proj.instance.return_value = _DummyProject()
|
||||
mock_lade.return_value = None
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertIsNone(enriched)
|
||||
self.assertIsNone(flst)
|
||||
self.assertIsNone(bror)
|
||||
self.assertEqual(status, LoadStatus.NONE)
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_shape_vg_sofort_ins_projekt_nach_laden(self, mock_lade, mock_gem, mock_proj, mock_prog):
|
||||
"""Shape-VG wird unmittelbar nach dem Laden ins Projekt eingefügt."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)):
|
||||
verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
# Shape-VG muss ins Projekt eingefügt worden sein
|
||||
self.assertIn(shape_vg, proj.added)
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_shape_vg_wird_bei_abbruch_aus_projekt_entfernt(
|
||||
self, mock_lade, mock_gem, mock_proj, mock_prog
|
||||
):
|
||||
"""Shape-VG wird bei Fehler/Abbruch aus dem Projekt entfernt."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)):
|
||||
_, _, _, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertEqual(status, LoadStatus.NONE)
|
||||
# Layer-ID muss in removed sein
|
||||
self.assertIn("S1", proj.removed)
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_shape_vg_bleibt_im_projekt_bei_erfolg(self, mock_lade, mock_gem, mock_proj, mock_prog):
|
||||
"""Bei erfolgreichem Abschluss wird shape_vg NICHT aus dem Projekt entfernt."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
with patch(_PATCH_FLST, return_value=(flst_layer, LoadStatus.FIRST)), \
|
||||
patch(_PATCH_SELECT, return_value=5), \
|
||||
patch(_PATCH_ERSTELLE, return_value=(_make_layer(lid="T1"), 5, 0)), \
|
||||
patch(_PATCH_VERGLEICHE, return_value=(1000.0, 1000.0, True)), \
|
||||
patch(_PATCH_SHOW_INFO):
|
||||
verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
# Layer-ID darf NICHT in removed sein
|
||||
self.assertNotIn("S1", proj.removed)
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_flst_none_aborts(self, mock_lade, mock_gem, mock_proj, mock_prog):
|
||||
mock_proj.instance.return_value = _DummyProject()
|
||||
mock_lade.return_value = _make_layer(name="Verfahrensgebiet")
|
||||
mock_gem.return_value = [("1234", "Muster")]
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)):
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertIsNone(enriched)
|
||||
self.assertIsNone(flst)
|
||||
self.assertEqual(status, LoadStatus.NONE)
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_MSGBOX)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_keine_flurstuecke_selektiert_gibt_warning(
|
||||
self, mock_lade, mock_flst, mock_gem, mock_proj, mock_select, mock_mb, mock_prog
|
||||
):
|
||||
mock_proj.instance.return_value = _DummyProject()
|
||||
mock_lade.return_value = _make_layer(name="Verfahrensgebiet")
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 0
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertIsNone(enriched)
|
||||
self.assertIs(flst, flst_layer)
|
||||
self.assertIsNone(bror)
|
||||
self.assertEqual(status, LoadStatus.NONE)
|
||||
mock_mb.warning.assert_called_once()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Flächenübereinstimmung
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_ERSTELLE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_flaechen_uebereinstimmung_gibt_info_und_kein_dialog(
|
||||
self, mock_lade, mock_flst, mock_gem, mock_proj,
|
||||
mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info
|
||||
):
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = [("1234", "Muster")]
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1")
|
||||
mock_erstelle.return_value = (temp_enriched, 5, 0)
|
||||
mock_vergleiche.return_value = (1000.0, 1000.0, True)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertIs(enriched, shape_vg)
|
||||
self.assertIs(flst, flst_layer)
|
||||
self.assertIsNone(bror)
|
||||
self.assertEqual(status, LoadStatus.FIRST)
|
||||
mock_info.assert_called_once()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Flächenabweichung + Abbrechen
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_ASK_DIALOG)
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_ERSTELLE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_abweichung_abbrechen_gibt_shape_vg_zurueck(
|
||||
self, mock_lade, mock_flst, mock_gem, mock_proj,
|
||||
mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info, mock_ask
|
||||
):
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1")
|
||||
mock_erstelle.return_value = (temp_enriched, 5, 0)
|
||||
mock_vergleiche.return_value = (1000.0, 800.0, False)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
mock_ask.return_value = "abbrechen"
|
||||
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertIs(enriched, shape_vg)
|
||||
self.assertIs(flst, flst_layer)
|
||||
self.assertIsNone(bror)
|
||||
self.assertEqual(status, LoadStatus.FIRST)
|
||||
mock_info.assert_not_called()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Detailprüfung
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_KNICK)
|
||||
@patch(_PATCH_GP_ALKIS)
|
||||
@patch(_PATCH_EXTRAHIERE)
|
||||
@patch(_PATCH_ASK_DIALOG)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_ERSTELLE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_abweichung_detailpruefung_ruft_knickpunkt_pruefung_auf(
|
||||
self, mock_lade, mock_flst, mock_gem, mock_proj,
|
||||
mock_select, mock_erstelle, mock_vergleiche, mock_prog,
|
||||
mock_ask, mock_extrahiere, mock_gp, mock_knick, mock_info
|
||||
):
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = [("1234", "Muster")]
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1")
|
||||
mock_erstelle.return_value = (temp_enriched, 5, 0)
|
||||
mock_vergleiche.return_value = (1000.0, 800.0, False)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
mock_ask.return_value = "detailpruefung"
|
||||
|
||||
knickpunkte_mock = [MagicMock() for _ in range(3)]
|
||||
mock_extrahiere.return_value = knickpunkte_mock
|
||||
|
||||
pta_layer = _make_layer(name="PunktortTA", lid="P1")
|
||||
mock_gp.return_value = pta_layer
|
||||
|
||||
ergebnis = _make_layer(name="Knickpunkte", lid="K1")
|
||||
mock_knick.return_value = (ergebnis, 2, 1, 0)
|
||||
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
mock_gp.assert_called_once()
|
||||
mock_knick.assert_called_once()
|
||||
self.assertIs(enriched, shape_vg)
|
||||
self.assertEqual(status, LoadStatus.FIRST)
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_KNICK)
|
||||
@patch(_PATCH_GP_ALKIS, return_value=None)
|
||||
@patch(_PATCH_EXTRAHIERE)
|
||||
@patch(_PATCH_ASK_DIALOG)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_ERSTELLE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_punktort_ta_none_gibt_shape_vg_zurueck_und_zeigt_fehler(
|
||||
self, mock_lade, mock_flst, mock_gem, mock_proj,
|
||||
mock_select, mock_erstelle, mock_vergleiche, mock_prog,
|
||||
mock_ask, mock_extrahiere, mock_gp, mock_knick, mock_info
|
||||
):
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
temp_enriched = _make_layer(name="Verfahrensgebiet", lid="T1")
|
||||
mock_erstelle.return_value = (temp_enriched, 5, 0)
|
||||
mock_vergleiche.return_value = (1000.0, 800.0, False)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
mock_ask.return_value = "detailpruefung"
|
||||
mock_extrahiere.return_value = [MagicMock()]
|
||||
|
||||
with patch(f"{_MOD}.QMessageBox") as mock_mb:
|
||||
enriched, flst, bror, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
mock_knick.assert_not_called()
|
||||
self.assertIs(enriched, shape_vg)
|
||||
mock_mb.critical.assert_called_once()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Cleanup: TempVerfahrensgebiet
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_temp_layer_wird_stets_entfernt(self, mock_lade, mock_proj, mock_gem, mock_prog):
|
||||
proj = _DummyProject()
|
||||
temp_layer = _make_layer(name="TempVerfahrensgebiet", lid="TEMP")
|
||||
proj._layers_by_name["TempVerfahrensgebiet"] = [temp_layer]
|
||||
mock_proj.instance.return_value = proj
|
||||
mock_lade.return_value = None # → sofortiger Abbruch nach Schritt 1
|
||||
mock_gem.return_value = []
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertIn("TEMP", proj.removed)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Rückgabe-Konsistenz
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_ERSTELLE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_bror_layer_ist_stets_none(
|
||||
self, mock_lade, mock_flst, mock_gem, mock_proj,
|
||||
mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info
|
||||
):
|
||||
"""Dritter Rückgabewert ist im Shape-Workflow immer None (kein BROR)."""
|
||||
mock_proj.instance.return_value = _DummyProject()
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
temp_enriched = _make_layer(lid="T1")
|
||||
mock_erstelle.return_value = (temp_enriched, 5, 0)
|
||||
mock_vergleiche.return_value = (1000.0, 1000.0, True)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
_, _, bror, _ = verfahrensgebiet_shape_komplett(None)
|
||||
self.assertIsNone(bror)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# BROR-Ladelogik
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(f"{_MOD}.verfahrensgebiet_alkis")
|
||||
@patch(f"{_MOD}.get_variable", return_value="VF-2026-001")
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_bror_geladen_und_zurueckgegeben_wenn_vkz_gesetzt(
|
||||
self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst,
|
||||
mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog, mock_info
|
||||
):
|
||||
"""VKZ gesetzt → verfahrensgebiet_alkis aufgerufen, bror_layer zurückgegeben."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
bror = _make_layer(name="Verfahrensgebiet", lid="BROR")
|
||||
mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, [])
|
||||
mock_vergleiche.return_value = (1000.0, 1000.0, True)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
enriched, flst, bror_ret, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
mock_vg_alkis.assert_called_once()
|
||||
self.assertIs(bror_ret, bror)
|
||||
self.assertIs(enriched, shape_vg)
|
||||
self.assertEqual(status, LoadStatus.FIRST)
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(f"{_MOD}.verfahrensgebiet_alkis")
|
||||
@patch(f"{_MOD}.get_variable", return_value="VF-2026-001")
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_bror_wird_vor_shape_vg_ins_projekt_eingefuegt(
|
||||
self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst,
|
||||
mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog, mock_info
|
||||
):
|
||||
"""BROR wird VOR shape_vg ins Projekt eingefügt (kein Namenskonflikt)."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
bror = _make_layer(name="Verfahrensgebiet", lid="BROR")
|
||||
mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, [])
|
||||
mock_vergleiche.return_value = (1000.0, 1000.0, True)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
# BROR muss in added sein
|
||||
self.assertIn(bror, proj.added)
|
||||
# BROR muss vor shape_vg hinzugefügt werden
|
||||
bror_idx = proj.added.index(bror)
|
||||
shape_idx = proj.added.index(shape_vg)
|
||||
self.assertLess(bror_idx, shape_idx, "BROR muss vor shape_vg eingefügt werden")
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(f"{_MOD}.verfahrensgebiet_alkis")
|
||||
@patch(f"{_MOD}.get_variable", return_value="VF-2026-001")
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_bror_wird_bei_abbruch_aus_projekt_entfernt(
|
||||
self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst,
|
||||
mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog
|
||||
):
|
||||
"""BROR und shape_vg werden bei Fehler aus dem Projekt entfernt."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
bror = _make_layer(name="Verfahrensgebiet", lid="BROR")
|
||||
mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, [])
|
||||
mock_flst.return_value = (None, LoadStatus.NONE) # → Abbruch
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
_, _, _, status = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
self.assertEqual(status, LoadStatus.NONE)
|
||||
self.assertIn("S1", proj.removed)
|
||||
self.assertIn("BROR", proj.removed)
|
||||
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(f"{_MOD}.verfahrensgebiet_alkis")
|
||||
@patch(f"{_MOD}.get_variable", return_value="")
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_verfahrensgebiet_alkis_nicht_aufgerufen_wenn_vkz_leer(
|
||||
self, mock_lade, mock_get_var, mock_vg_alkis, mock_gem, mock_proj, mock_prog
|
||||
):
|
||||
"""Leere VKZ → verfahrensgebiet_alkis wird NICHT aufgerufen."""
|
||||
mock_proj.instance.return_value = _DummyProject()
|
||||
mock_lade.return_value = _make_layer(name="Verfahrensgebiet")
|
||||
mock_gem.return_value = []
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)):
|
||||
verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
mock_vg_alkis.assert_not_called()
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(f"{_MOD}.verfahrensgebiet_alkis")
|
||||
@patch(f"{_MOD}.get_variable", return_value="VF-2026-001")
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_bror_als_erstes_argument_beim_flaechenvergleich(
|
||||
self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst,
|
||||
mock_gem, mock_proj, mock_select, mock_vergleiche, mock_prog, mock_info
|
||||
):
|
||||
"""Mit BROR: _vergleiche_flaechen wird (bror, shape_vg, ...) aufgerufen."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
bror = _make_layer(name="Verfahrensgebiet", lid="BROR")
|
||||
mock_vg_alkis.return_value = (bror, LoadStatus.FIRST, [])
|
||||
mock_vergleiche.return_value = (1000.0, 1000.0, True)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
# Erster Arg = bror, zweiter Arg = shape_vg
|
||||
call_args = mock_vergleiche.call_args[0]
|
||||
self.assertIs(call_args[0], bror)
|
||||
self.assertIs(call_args[1], shape_vg)
|
||||
|
||||
@patch(_PATCH_SHOW_INFO)
|
||||
@patch(_PATCH_PROGRESS)
|
||||
@patch(_PATCH_VERGLEICHE)
|
||||
@patch(_PATCH_ERSTELLE)
|
||||
@patch(_PATCH_SELECT)
|
||||
@patch(_PATCH_PROJECT)
|
||||
@patch(_PATCH_GEM)
|
||||
@patch(_PATCH_FLST)
|
||||
@patch(f"{_MOD}.verfahrensgebiet_alkis", return_value=(None, LoadStatus.NONE, []))
|
||||
@patch(f"{_MOD}.get_variable", return_value="VF-2026-001")
|
||||
@patch(_PATCH_LADE_ZIP)
|
||||
def test_fallback_auf_temp_enriched_wenn_bror_ladeversagen(
|
||||
self, mock_lade, mock_get_var, mock_vg_alkis, mock_flst,
|
||||
mock_gem, mock_proj, mock_select, mock_erstelle, mock_vergleiche, mock_prog, mock_info
|
||||
):
|
||||
"""BROR-Ladeversagen → Fallback: _vergleiche_flaechen mit shape_vg vs temp_enriched."""
|
||||
proj = _DummyProject()
|
||||
mock_proj.instance.return_value = proj
|
||||
shape_vg = _make_layer(name="Verfahrensgebiet", lid="S1")
|
||||
mock_lade.return_value = shape_vg
|
||||
mock_gem.return_value = []
|
||||
flst_layer = _make_layer(name="Flurstücke", lid="F1")
|
||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||
mock_select.return_value = 5
|
||||
temp_enriched = _make_layer(lid="T1")
|
||||
mock_erstelle.return_value = (temp_enriched, 5, 0)
|
||||
mock_vergleiche.return_value = (1000.0, 1000.0, True)
|
||||
mock_prog.return_value = _make_progress_mock()
|
||||
|
||||
_, _, bror_ret, _ = verfahrensgebiet_shape_komplett(None)
|
||||
|
||||
# kein BROR zurückgegeben
|
||||
self.assertIsNone(bror_ret)
|
||||
# Fallback: erste Arg = shape_vg
|
||||
call_args = mock_vergleiche.call_args[0]
|
||||
self.assertIs(call_args[0], shape_vg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -566,5 +566,262 @@ class TestWorkingTab(unittest.TestCase):
|
||||
self.assertTrue(tab.btn_gem_alkis.isEnabled()) # finally
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# handle_verf_shape
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestHandleVerfShape(unittest.TestCase):
|
||||
|
||||
def _make_tab(self):
|
||||
tab = WorkingTab.__new__(WorkingTab)
|
||||
tab.btn_verf_alkis = _DummyButton()
|
||||
tab.btn_verf_shape = _DummyButton()
|
||||
tab.btn_flurst_alkis = _DummyButton()
|
||||
tab.btn_gem_alkis = _DummyButton()
|
||||
tab.btn_grenz_alkis = _DummyButton()
|
||||
tab.btn_verf_aus_auswahl = _DummyButton()
|
||||
tab.btn_verf_aus_liste = _DummyButton()
|
||||
tab.verf_layer_combo = _DummyCombo()
|
||||
tab.haken_verf = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
|
||||
tab.haken_flurst = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
|
||||
tab.haken_gem = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
|
||||
tab.haken_grenz = types.SimpleNamespace(setText=lambda _t: None, setStyleSheet=lambda _s: None)
|
||||
tab.setze_haken = lambda *_args, **_kwargs: None
|
||||
tab.pruefmanager = types.SimpleNamespace(verarbeite=lambda ergebnis: ergebnis)
|
||||
return tab
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
@patch(
|
||||
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett",
|
||||
return_value=(None, None, None, LoadStatus.NONE),
|
||||
)
|
||||
def test_status_none_clears_layer_variable(self, _mock_shape, mock_set_id):
|
||||
"""Status NONE → Layer-Variable wird auf '' gesetzt."""
|
||||
tab = self._make_tab()
|
||||
|
||||
with patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123"), \
|
||||
patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no"):
|
||||
tab.handle_verf_shape()
|
||||
|
||||
mock_set_id.assert_called_once_with("")
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
@patch(
|
||||
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett",
|
||||
return_value=(None, None, None, LoadStatus.NONE),
|
||||
)
|
||||
def test_button_always_re_enabled_on_failure(self, _mock_shape, _mock_set_id):
|
||||
"""Button muss nach Status NONE wieder freigegeben sein (finally)."""
|
||||
tab = self._make_tab()
|
||||
|
||||
with patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123"), \
|
||||
patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no"):
|
||||
tab.handle_verf_shape()
|
||||
|
||||
self.assertTrue(tab.btn_verf_shape.isEnabled())
|
||||
self.assertEqual(tab.btn_verf_shape.text(), "Aus Shape laden")
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no", return_value=False)
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett")
|
||||
def test_verfahrensnummer_nicht_gesetzt_nein_bricht_ab(
|
||||
self, mock_shape, mock_get_var, mock_ask
|
||||
):
|
||||
"""Verfahrensnummer leer + Nutzer wählt 'Nein' → Vorgang abgebrochen."""
|
||||
tab = self._make_tab()
|
||||
|
||||
tab.handle_verf_shape()
|
||||
|
||||
mock_ask.assert_called_once()
|
||||
mock_shape.assert_not_called()
|
||||
# Button darf nicht busy geblieben sein
|
||||
self.assertTrue(tab.btn_verf_shape.isEnabled())
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no", return_value=True)
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="")
|
||||
@patch(
|
||||
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett",
|
||||
return_value=(None, None, None, LoadStatus.NONE),
|
||||
)
|
||||
def test_verfahrensnummer_nicht_gesetzt_ja_fuehrt_fort(
|
||||
self, mock_shape, mock_get_var, mock_ask, mock_set_id
|
||||
):
|
||||
"""Verfahrensnummer leer + Nutzer wählt 'Ja' → Pipeline wird aufgerufen."""
|
||||
tab = self._make_tab()
|
||||
|
||||
tab.handle_verf_shape()
|
||||
|
||||
mock_ask.assert_called_once()
|
||||
mock_shape.assert_called_once_with(tab)
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.ask_yes_no")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-2026-001")
|
||||
@patch(
|
||||
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett",
|
||||
return_value=(None, None, None, LoadStatus.NONE),
|
||||
)
|
||||
def test_verfahrensnummer_gesetzt_kein_rueckfrage_dialog(
|
||||
self, mock_shape, mock_get_var, mock_ask, mock_set_id
|
||||
):
|
||||
"""Verfahrensnummer gesetzt → kein ask_yes_no-Dialog."""
|
||||
tab = self._make_tab()
|
||||
|
||||
tab.handle_verf_shape()
|
||||
|
||||
mock_ask.assert_not_called()
|
||||
mock_shape.assert_called_once_with(tab)
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
def test_success_path_applies_style_and_sets_haken(
|
||||
self,
|
||||
mock_set_layer_id,
|
||||
mock_iface,
|
||||
mock_shape_komplett,
|
||||
mock_project_instance,
|
||||
mock_apply_style,
|
||||
mock_success,
|
||||
mock_get_var,
|
||||
):
|
||||
"""Erfolg → verfahrensgebiet.qml angewendet, haken_verf gesetzt, success aufgerufen."""
|
||||
tab = self._make_tab()
|
||||
haken_calls = []
|
||||
tab.setze_haken = lambda lbl, v: haken_calls.append((lbl, v))
|
||||
|
||||
enriched = _DummyLayer(valid=True, layer_id="shape_vg")
|
||||
flst = _DummyLayer(valid=True, layer_id="flst_ok")
|
||||
mock_shape_komplett.return_value = (enriched, flst, None, LoadStatus.FIRST)
|
||||
proj = _DummyProject()
|
||||
proj.mapLayersByName = lambda _n: []
|
||||
proj.mapLayer = lambda _lid: None
|
||||
mock_project_instance.return_value = proj
|
||||
mock_iface.mapCanvas.return_value = _DummyCanvas()
|
||||
|
||||
tab.handle_verf_shape()
|
||||
|
||||
# Style auf enriched layer angewendet
|
||||
apply_calls = [str(c) for c in mock_apply_style.call_args_list]
|
||||
self.assertTrue(
|
||||
any("verfahrensgebiet.qml" in c for c in apply_calls),
|
||||
"verfahrensgebiet.qml muss angewendet werden",
|
||||
)
|
||||
# Flurstücke-Style angewendet
|
||||
self.assertTrue(
|
||||
any("GIS_Flst_Beschriftung_ALKIS_NAS.qml" in c for c in apply_calls),
|
||||
"Flurstücke-Style muss angewendet werden",
|
||||
)
|
||||
# Haken gesetzt
|
||||
self.assertTrue(any(v for _, v in haken_calls), "Mindestens ein Haken muss gesetzt werden")
|
||||
# Layer-ID gespeichert
|
||||
mock_set_layer_id.assert_called_with("shape_vg")
|
||||
# Success-Meldung
|
||||
mock_success.assert_called_once()
|
||||
# Button wieder freigegeben
|
||||
self.assertTrue(tab.btn_verf_shape.isEnabled())
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.success")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
def test_bror_layer_erhaelt_qml_stil(
|
||||
self,
|
||||
mock_set_layer_id,
|
||||
mock_iface,
|
||||
mock_shape_komplett,
|
||||
mock_project_instance,
|
||||
mock_apply_style,
|
||||
mock_success,
|
||||
mock_get_var,
|
||||
):
|
||||
"""Wenn bror_layer zurückgegeben → BauRaumOderBodenordnungsrecht.qml angewendet."""
|
||||
tab = self._make_tab()
|
||||
tab.setze_haken = lambda *a, **kw: None
|
||||
|
||||
enriched = _DummyLayer(valid=True, layer_id="shape_vg")
|
||||
bror = _DummyLayer(valid=True, layer_id="bror_id")
|
||||
mock_shape_komplett.return_value = (enriched, None, bror, LoadStatus.FIRST)
|
||||
|
||||
proj = _DummyProject()
|
||||
proj.mapLayersByName = lambda _n: []
|
||||
proj.mapLayer = lambda lid: enriched if lid == "shape_vg" else None
|
||||
mock_project_instance.return_value = proj
|
||||
mock_iface.mapCanvas.return_value = _DummyCanvas()
|
||||
|
||||
tab.handle_verf_shape()
|
||||
|
||||
apply_calls = [str(c) for c in mock_apply_style.call_args_list]
|
||||
self.assertTrue(
|
||||
any("BauRaumOderBodenordnungsrecht.qml" in c for c in apply_calls),
|
||||
"BauRaumOderBodenordnungsrecht.qml muss auf bror_layer angewendet werden",
|
||||
)
|
||||
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.apply_style")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.QgsProject.instance")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.iface")
|
||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||
def test_enriched_layer_already_in_project_not_added_again(
|
||||
self,
|
||||
mock_set_layer_id,
|
||||
mock_iface,
|
||||
mock_shape_komplett,
|
||||
mock_project_instance,
|
||||
mock_apply_style,
|
||||
mock_get_var,
|
||||
):
|
||||
"""Layer bereits im Projekt → kein erneutes addMapLayer für VG-Layer."""
|
||||
tab = self._make_tab()
|
||||
tab.setze_haken = lambda *a, **kw: None
|
||||
|
||||
enriched = _DummyLayer(valid=True, layer_id="shape_vg")
|
||||
mock_shape_komplett.return_value = (enriched, None, None, LoadStatus.FIRST)
|
||||
|
||||
proj = _DummyProject()
|
||||
proj.mapLayersByName = lambda _n: []
|
||||
proj.mapLayer = lambda lid: enriched if lid == "shape_vg" else None
|
||||
mock_project_instance.return_value = proj
|
||||
mock_iface.mapCanvas.return_value = _DummyCanvas()
|
||||
|
||||
tab.handle_verf_shape()
|
||||
|
||||
# Enriched-Layer darf NICHT erneut via addMapLayer hinzugefügt worden sein
|
||||
self.assertNotIn(enriched, proj.added)
|
||||
|
||||
def test_busy_state_set_during_execution(self):
|
||||
"""Button wird während der Ausführung auf busy gesetzt."""
|
||||
tab = self._make_tab()
|
||||
busy_states = []
|
||||
|
||||
original_set_busy = WorkingTab._set_busy
|
||||
|
||||
def capture_busy(self_ref, button, busy, original_text="Aus Shape laden"):
|
||||
busy_states.append(busy)
|
||||
original_set_busy(self_ref, button, busy, original_text)
|
||||
|
||||
with patch.object(WorkingTab, "_set_busy", capture_busy), \
|
||||
patch(
|
||||
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_shape_komplett",
|
||||
return_value=(None, None, None, LoadStatus.NONE),
|
||||
), \
|
||||
patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id"), \
|
||||
patch("sn_verfahrensgebiet.ui.tabs.working_tab.get_variable", return_value="VF-123"):
|
||||
tab.handle_verf_shape()
|
||||
|
||||
self.assertIn(True, busy_states, "Button muss mindestens einmal auf busy gesetzt werden")
|
||||
self.assertIn(False, busy_states, "Button muss am Ende wieder freigegeben werden")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
+60
-2
@@ -8,7 +8,8 @@ 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
|
||||
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
|
||||
@@ -21,6 +22,7 @@ from sn_verfahrensgebiet.functions.flurstueck_alkis import (
|
||||
FLST_LAYER_NAME,
|
||||
)
|
||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import verfahrensgebiet_alkis_komplett
|
||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett
|
||||
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,
|
||||
@@ -167,7 +169,7 @@ class WorkingTab(QWidget):
|
||||
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_verf_shape.clicked.connect(self.handle_verf_shape)
|
||||
# self.btn_flurst_shape.clicked.connect(self.handle_flurst_shape)
|
||||
|
||||
# Beim Init: aktuelle Verfahrensgebiet-Layer-ID laden und im Dropdown anzeigen
|
||||
@@ -220,6 +222,62 @@ class WorkingTab(QWidget):
|
||||
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 _set_busy(self, button: QPushButton, busy: bool, original_text: str = "Aus ALKIS laden") -> None:
|
||||
"""Setzt den Busy-State eines Buttons.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user