Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a906d3ac7 | ||
|
|
39f8209b9d | ||
|
|
3c0e6e5a19 | ||
|
|
c93776d19e | ||
|
|
70c14783c5 | ||
|
|
b4f2222b50 | ||
|
|
8447a2615c | ||
|
|
4e7f796840 | ||
|
|
63a70737f9 | ||
|
|
d5db43bc57 | ||
|
|
7b48d29d56 | ||
|
|
5816b2af22 | ||
|
|
148d4dfa15 |
+11
-1
@@ -1,5 +1,15 @@
|
|||||||
|
- Fortschrittsdialog beim Laden aus ALKIS schließt jetzt zuverlässig bei jedem Abbruch (Bugfix)
|
||||||
|
- Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt
|
||||||
|
- Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte)
|
||||||
|
- Neu: Laden aller/mehrerer Objekte eines ganzen Landkreises als Fallback, wenn keine Verfahrensnummer gesetzt ist (Gruppierung je Verfahren, optional "Nur laden" ohne weiteren Ablauf)
|
||||||
|
- Neu: Ist weder Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt, fragt ein Dialog den Landkreis ab (inkl. Option "Alle Landkreise"), statt direkt abzubrechen
|
||||||
|
- Landkreisweiter Objekt-Auswahldialog überarbeitet: Tabelle mit sortierbaren Spalten "Nummer"/"Name" (Standard: aufsteigend nach Nummer), Suchfeld, "Alle auswählen" jetzt korrekt (vorheriger Qt-Bug bei programmatischer Mehrfachauswahl behoben)
|
||||||
|
- Bugfix: BROR-Layer bekommt seinen ALKIS-Style jetzt direkt beim Laden (auch bei "Nur Objekte laden") statt später fälschlich den Verfahrensgebiet-Style zu erhalten – **Detailprüfung/Verifikation im echten QGIS-Test steht noch aus**
|
||||||
|
- Bugfix: Erfolgsmeldung "Layer wurde ... geladen (inkl. Flurstücksverschneidung)" erscheint nicht mehr fälschlich bei "Nur Objekte laden" (da dort keine Verschneidung stattfindet)
|
||||||
|
- Verfahrensgebiet-spezifische ALKIS-Dialoge aus sn_basis nach sn_verfahrensgebiet verschoben (functions/verfahrensgebiet_dialoge.py)
|
||||||
|
---
|
||||||
Version 26.8.1-unstable:
|
Version 26.8.1-unstable:
|
||||||
-Vergleich einer CSV aus dem BI-Server mit den Beteiligten Flurstücken lt. ALKIS
|
- Neu: "Beteiligung prüfen" – Abgleich einer BI-Server-CSV-Exportliste mit dem ALKIS-Flurstücksbestand des Verfahrensgebiets (try1: schneller Listenabgleich bei vorhandenem Kennzeichen-Bestand; try2/try3: Aufforderung, das Verfahrensgebiet zunächst über "Aus ALKIS laden" zu laden, statt automatischem Hintergrund-Laden)
|
||||||
---
|
---
|
||||||
Version 26.7.1-unstable:
|
Version 26.7.1-unstable:
|
||||||
- Verfahrensgebiet als shape laden, inkl. Abgleich mit BROBOR-Objekt
|
- Verfahrensgebiet als shape laden, inkl. Abgleich mit BROBOR-Objekt
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Ablauf: Aus ALKIS laden
|
||||||
|
|
||||||
|
Betrachteter Teilablauf: nur bis zum Erzeugen des Layers
|
||||||
|
"BauRaumOderBodenordnungsrecht (ALKIS)". Die anschließenden Schritte des
|
||||||
|
vollständigen Workflows (Gemarkungen, Flurstücke, Flächenvergleich,
|
||||||
|
Detailprüfung) sind hier bewusst nicht dargestellt.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start(["Aus ALKIS laden"])
|
||||||
|
B0{"Verfahrensnummer oder Landkreis in Projekteigenschaften gesetzt?"}
|
||||||
|
LKAUSW["Landkreis auswählen (Dialog)"]
|
||||||
|
Abbruch(["Abbruch"])
|
||||||
|
MODUS{"Verfahrensnummer gesetzt?"}
|
||||||
|
EXIST1{"Layer 'Verfahrensgebiet' bereits vorhanden?"}
|
||||||
|
EXIST2{"Layer 'Verfahrensgebiet' bereits vorhanden?"}
|
||||||
|
Keep[("Verfahrensgebiet (bestehender Layer)")]
|
||||||
|
WFS1["WFS-Suche per Verfahrensnummer"]
|
||||||
|
WFS2["WFS-Suche per Landkreis"]
|
||||||
|
C1{"Mehrere Objekt-Kombinationen gefunden?"}
|
||||||
|
AUSW1["Objektauswahl"]
|
||||||
|
AUSW2["Objektauswahl"]
|
||||||
|
BUILD["Layer erzeugen"]
|
||||||
|
Ende[("BauRaumOderBodenordnungsrecht (ALKIS)")]
|
||||||
|
|
||||||
|
Start --> B0
|
||||||
|
B0 -- Nein --> LKAUSW
|
||||||
|
LKAUSW -- Abbruch --> Abbruch
|
||||||
|
LKAUSW -- OK --> MODUS
|
||||||
|
B0 -- Ja --> MODUS
|
||||||
|
|
||||||
|
MODUS -- Ja --> EXIST1
|
||||||
|
MODUS -- Nein --> EXIST2
|
||||||
|
|
||||||
|
EXIST1 -- "Ja, behalten" --> Keep
|
||||||
|
EXIST2 -- "Ja, behalten" --> Keep
|
||||||
|
|
||||||
|
EXIST1 -- "Nein / neu laden" --> WFS1
|
||||||
|
EXIST2 -- "Nein / neu laden" --> WFS2
|
||||||
|
|
||||||
|
WFS1 --> C1
|
||||||
|
C1 -- Ja --> AUSW1
|
||||||
|
C1 -- Nein --> BUILD
|
||||||
|
AUSW1 --> BUILD
|
||||||
|
|
||||||
|
WFS2 --> AUSW2
|
||||||
|
AUSW2 --> BUILD
|
||||||
|
|
||||||
|
BUILD --> Ende
|
||||||
|
```
|
||||||
|
|
||||||
|
Hinweise zu den Entscheidungsknoten:
|
||||||
|
|
||||||
|
- **B0**: Es werden die Projektvariablen `verfahrensnummer` und
|
||||||
|
`landkreis_kennzahl` gemeinsam geprüft. Nur wenn **beide** fehlen, wird der
|
||||||
|
Landkreis per Dialog abgefragt.
|
||||||
|
- **MODUS**: Ist eine Verfahrensnummer gesetzt, wird über die Verfahrensnummer
|
||||||
|
gesucht (auch wenn zusätzlich ein Landkreis gesetzt ist); andernfalls über
|
||||||
|
den Landkreis.
|
||||||
|
- **EXIST1/EXIST2**: Existiert bereits ein Layer "Verfahrensgebiet" und lehnt
|
||||||
|
der Nutzer das Neuladen ab, wird direkt dieser vorhandene Layer verwendet –
|
||||||
|
ohne erneute WFS-Suche oder Objektauswahl.
|
||||||
|
- **C1 (nur im Verfahrensnummer-Zweig)**: Die Objektauswahl erscheint nur,
|
||||||
|
wenn der WFS mehrere unterschiedliche (adv_name, bezeichnung)-Kombinationen
|
||||||
|
liefert. Bei genau einem Treffer wird der Layer ohne Nutzerinteraktion
|
||||||
|
erzeugt.
|
||||||
|
- **Landkreis-Zweig**: Hier erscheint die Objektauswahl immer (inkl.
|
||||||
|
"Alle auswählen"-Option), da hier potenziell deutlich mehr Treffer als bei
|
||||||
|
der Verfahrensnummer-Suche auftreten.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Ablauf: Aus Shape laden
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start(["Aus Shape laden"])
|
||||||
|
A0{"Datei öffnen?"}
|
||||||
|
A2["entpacken"]
|
||||||
|
A3["Inhalt prüfen"]
|
||||||
|
|
||||||
|
Ende1[("Layer:</br>Neuordnungsgebiet (NoBIS)")]
|
||||||
|
Ende2[("Layer:</br>Neuvermessungsgebiet (NoBIS)")]
|
||||||
|
|
||||||
|
Start --> A0
|
||||||
|
A0 -- Shape --> A3
|
||||||
|
A0 -- ZIP --> A2
|
||||||
|
A2 --> A3
|
||||||
|
A3 -- Neuordnungsgebiet --> Ende1
|
||||||
|
A3 -- Neuvermessungsgebiet --> Ende2
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Technischer Ablauf: Aus Shape laden (bis zum Stop)
|
||||||
|
|
||||||
|
Ergänzung zu [aus_shape_laden.md](aus_shape_laden.md), die den fachlichen
|
||||||
|
Ablauf in Kurzform beschreibt. Dieses Dokument zeigt den tatsächlichen
|
||||||
|
technischen Ablauf in `lade_vg_aus_zip()` und dem anschließenden Stop in
|
||||||
|
`verfahrensgebiet_shape_komplett()` (Datei
|
||||||
|
`functions/verfahrensgebiet_shape_komplett.py`).
|
||||||
|
|
||||||
|
Betrachteter Teilablauf: nur bis zum Stop nach dem Einfügen der Layer ins
|
||||||
|
Projekt. Die anschließenden Schritte (Gemarkung, Flurstücke,
|
||||||
|
Flächenvergleich, Detailprüfung) sind hier nicht dargestellt.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start(["Aus Shape laden"])
|
||||||
|
DLG["Dateidialog (Mehrfachauswahl):<br/>.zip und/oder .shp"]
|
||||||
|
ABBRUCH1(["Abbruch (keine Auswahl)"])
|
||||||
|
|
||||||
|
SAMMEL["Für jede ausgewählte Datei:"]
|
||||||
|
ISZIP{"Endet auf .shp?"}
|
||||||
|
DIREKT["Direkt als Shapefile-Eintrag übernehmen"]
|
||||||
|
ENTPACK["In temporäres Verzeichnis entpacken"]
|
||||||
|
ENTPACKFEHLER{"Entpacken erfolgreich?"}
|
||||||
|
FEHLER_ZIP["Fehler notieren:<br/>'ZIP konnte nicht entpackt werden'"]
|
||||||
|
FINDEALL["Alle .shp-Dateien im<br/>entpackten Verzeichnis suchen"]
|
||||||
|
KEINSHP{"Mind. eine .shp gefunden?"}
|
||||||
|
FEHLER_KEIN_SHP["Fehler notieren:<br/>'kein gültiges Shape-Dateiset'"]
|
||||||
|
ALS_EINTRAG["Jede gefundene .shp als<br/>eigenen Eintrag übernehmen"]
|
||||||
|
|
||||||
|
PROJEDEN["Für jeden Shapefile-Eintrag:"]
|
||||||
|
LOAD["Als QgsVectorLayer laden"]
|
||||||
|
VALID{"isValid()?"}
|
||||||
|
FEHLER_INVALID["Fehler notieren:<br/>'kein gültiges Shape-Dateiset'"]
|
||||||
|
FBSCHL{"Feld 'FBSchl' vorhanden<br/>und Wert bekannt?"}
|
||||||
|
FEHLER_FBSCHL["Fehler notieren:<br/>Feld fehlt / unbekannter Wert"]
|
||||||
|
TYP{"FBSchl-Wert"}
|
||||||
|
ALS_NEUORDNUNG["Geometrien zu<br/>'Neuordnungsgebiet' sammeln"]
|
||||||
|
ALS_NEUVERMESSUNG["Geometrien zu<br/>'Neuvermessungsgebiet' sammeln"]
|
||||||
|
|
||||||
|
NACHSCHLEIFE["Nach Verarbeitung aller Einträge"]
|
||||||
|
FEHLERVORHANDEN{"Fehler gesammelt?"}
|
||||||
|
SAMMELMELDUNG["Sammelmeldung anzeigen:<br/>alle fehlerhaften Dateien"]
|
||||||
|
NICHTSVERWERTBAR{"Mind. ein Typ mit<br/>gültigen Geometrien?"}
|
||||||
|
ABBRUCH2(["Abbruch (None)"])
|
||||||
|
|
||||||
|
MERGE["Je Gebietstyp:<br/>unaryUnion aller gesammelten Geometrien<br/>→ ein Memory-Layer je Typ"]
|
||||||
|
BENENNEN["Layer benennen:<br/>'Neuordnungsgebiet (NoBIS)' /<br/>'Neuvermessungsgebiet (NoBIS)'"]
|
||||||
|
RETURN["Liste der Layer zurückgeben<br/>(1 oder 2 Layer)"]
|
||||||
|
|
||||||
|
EINFUEGEN["Alle Layer ins Projekt einfügen"]
|
||||||
|
STOP{{"STOP:<br/>'Layer ... wurde(n) geladen. Weiter?'"}}
|
||||||
|
WEITER(["weiter mit Gemarkung/Flurstücke/…"])
|
||||||
|
ABBRUCH3(["Abbruch, Layer bleiben im Projekt"])
|
||||||
|
|
||||||
|
Start --> DLG
|
||||||
|
DLG -- keine Auswahl --> ABBRUCH1
|
||||||
|
DLG -- Auswahl --> SAMMEL
|
||||||
|
SAMMEL --> ISZIP
|
||||||
|
ISZIP -- Ja --> DIREKT
|
||||||
|
ISZIP -- Nein --> ENTPACK
|
||||||
|
ENTPACK --> ENTPACKFEHLER
|
||||||
|
ENTPACKFEHLER -- Nein --> FEHLER_ZIP
|
||||||
|
ENTPACKFEHLER -- Ja --> FINDEALL
|
||||||
|
FINDEALL --> KEINSHP
|
||||||
|
KEINSHP -- Nein --> FEHLER_KEIN_SHP
|
||||||
|
KEINSHP -- Ja --> ALS_EINTRAG
|
||||||
|
|
||||||
|
DIREKT --> PROJEDEN
|
||||||
|
ALS_EINTRAG --> PROJEDEN
|
||||||
|
FEHLER_ZIP -.->|nächste Datei| SAMMEL
|
||||||
|
FEHLER_KEIN_SHP -.->|nächste Datei| SAMMEL
|
||||||
|
|
||||||
|
PROJEDEN --> LOAD
|
||||||
|
LOAD --> VALID
|
||||||
|
VALID -- Nein --> FEHLER_INVALID
|
||||||
|
VALID -- Ja --> FBSCHL
|
||||||
|
FBSCHL -- Nein --> FEHLER_FBSCHL
|
||||||
|
FBSCHL -- Ja --> TYP
|
||||||
|
TYP -- "901/10110F" --> ALS_NEUORDNUNG
|
||||||
|
TYP -- "901/10120F" --> ALS_NEUVERMESSUNG
|
||||||
|
FEHLER_INVALID -.->|nächstes Shapefile| PROJEDEN
|
||||||
|
FEHLER_FBSCHL -.->|nächstes Shapefile| PROJEDEN
|
||||||
|
ALS_NEUORDNUNG -.->|nächstes Shapefile| PROJEDEN
|
||||||
|
ALS_NEUVERMESSUNG -.->|nächstes Shapefile| PROJEDEN
|
||||||
|
|
||||||
|
PROJEDEN --> NACHSCHLEIFE
|
||||||
|
NACHSCHLEIFE --> FEHLERVORHANDEN
|
||||||
|
FEHLERVORHANDEN -- Ja --> SAMMELMELDUNG
|
||||||
|
FEHLERVORHANDEN -- Nein --> NICHTSVERWERTBAR
|
||||||
|
SAMMELMELDUNG --> NICHTSVERWERTBAR
|
||||||
|
NICHTSVERWERTBAR -- Nein --> ABBRUCH2
|
||||||
|
NICHTSVERWERTBAR -- Ja --> MERGE
|
||||||
|
MERGE --> BENENNEN
|
||||||
|
BENENNEN --> RETURN
|
||||||
|
|
||||||
|
RETURN --> EINFUEGEN
|
||||||
|
EINFUEGEN --> STOP
|
||||||
|
STOP -- Ja --> WEITER
|
||||||
|
STOP -- Nein --> ABBRUCH3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hinweise
|
||||||
|
|
||||||
|
- **Mehrfachauswahl**: Es können mehrere Dateien gleichzeitig gewählt werden,
|
||||||
|
auch gemischt `.zip` und `.shp`.
|
||||||
|
- **ZIP mit mehreren Shapefile-Sets**: Enthält ein ZIP mehrere `.shp`-Dateien,
|
||||||
|
wird jede einzeln geprüft und verarbeitet – wie ein eigener Eintrag in der
|
||||||
|
Auswahl.
|
||||||
|
- **Fehler pro Datei, nicht pro Vorgang**: Schlägt die Prüfung einer
|
||||||
|
einzelnen Datei/eines einzelnen Shapefiles fehl (ZIP nicht entpackbar,
|
||||||
|
kein Shapefile enthalten, ungültiger Layer, Feld `FBSchl` fehlt oder hat
|
||||||
|
einen unbekannten Wert), wird nur dieser Eintrag übersprungen. Alle
|
||||||
|
anderen werden weiterverarbeitet. Am Ende erscheint eine **gesammelte**
|
||||||
|
Fehlermeldung mit allen fehlerhaften Einträgen – Abbruch (`None`) nur,
|
||||||
|
wenn *kein* Eintrag verwertbare Geometrien geliefert hat.
|
||||||
|
- **Zusammenführen gleicher Gebietstypen**: Alle Geometrien mit demselben
|
||||||
|
`FBSchl`-Typ (über alle Dateien/Shapefiles hinweg) werden per
|
||||||
|
`QgsGeometry.unaryUnion` zu **einem** Memory-Layer verschmolzen – nicht
|
||||||
|
ein Layer pro Datei.
|
||||||
|
- **Temporäre Verzeichnisse**: Jedes entpackte ZIP landet in einem eigenen
|
||||||
|
`tempfile.mkdtemp()`-Verzeichnis, das nach der Verarbeitung (auch im
|
||||||
|
Fehlerfall) wieder gelöscht wird.
|
||||||
|
- **Stop-Punkt**: Nach dem Einfügen aller erzeugten Layer ins Projekt fragt
|
||||||
|
ein Ja/Nein-Dialog, ob mit dem restlichen Workflow (Gemarkung,
|
||||||
|
Flurstücke, Flächenvergleich, Detailprüfung) fortgefahren werden soll.
|
||||||
|
Bei "Nein" bleiben die geladenen Layer im Projekt erhalten, der weitere
|
||||||
|
Ablauf wird aber nicht gestartet.
|
||||||
@@ -12,19 +12,23 @@ ab. Aus ``FLURSTUECK`` (Format ``<Gemarkung>/<Zähler>/<Nenner>/<Folge>``) wird
|
|||||||
je Zeile ein zu ALKIS kompatibles Flurstückskennzeichen ``Flurstueck_csv``
|
je Zeile ein zu ALKIS kompatibles Flurstückskennzeichen ``Flurstueck_csv``
|
||||||
gebildet.
|
gebildet.
|
||||||
|
|
||||||
Automatische Kaskade je nach Projektzustand:
|
Kaskade je nach Projektzustand:
|
||||||
|
|
||||||
- **try1**: Ein Layer ``Verfahrensgebiet`` mit befülltem Attribut
|
- **try1**: Ein Layer ``Verfahrensgebiet`` mit befülltem Attribut
|
||||||
``flurstueckskennzeichen`` (Single-Feature-Modell, siehe
|
``flurstueckskennzeichen`` (Single-Feature-Modell, siehe
|
||||||
:mod:`verfahrensgebiet_edit_service`) ist vorhanden → schneller
|
:mod:`verfahrensgebiet_edit_service`) ist vorhanden → schneller
|
||||||
Mengenvergleich ohne WFS-Zugriff.
|
Mengenvergleich ohne WFS-Zugriff.
|
||||||
- **try2**: Ein Verfahrensgebiet-Layer ist vorhanden, aber ohne verwertbare
|
- **try2**: Ein Verfahrensgebiet-Layer ist vorhanden, aber ohne verwertbare
|
||||||
Kennzeichen → die Pipeline "Verfahrensgebiet aus ALKIS laden"
|
Kennzeichen → statt einer automatischen (nutzerlosen) ALKIS-Ladepipeline
|
||||||
(:func:`verfahrensgebiet_alkis_komplett`) wird silent (ohne
|
wird der Nutzer per Meldung aufgefordert, das Verfahrensgebiet zunächst
|
||||||
Nutzerrückfragen, aber inkl. Detailprüfung) durchlaufen, um einen
|
regulär über "Aus ALKIS laden" neu zu laden und die Beteiligungsprüfung
|
||||||
vollständigen, aktuellen Bestand zu erzeugen – danach wie try1.
|
danach erneut zu starten (landet dann automatisch bei try1). Ein
|
||||||
|
automatischer Durchlauf hat sich in der Praxis nicht als zuverlässig genug
|
||||||
|
erwiesen (mehrdeutige BROR-Treffer, Flächenabweichungen etc. lassen sich
|
||||||
|
nur im interaktiven Ablauf sinnvoll auflösen).
|
||||||
- **try3**: Kein Verfahrensgebiet-Layer im Projekt → Verfahrensnummer aus
|
- **try3**: Kein Verfahrensgebiet-Layer im Projekt → Verfahrensnummer aus
|
||||||
``VEF_VKZF`` als Projektvariable setzen, danach wie try2.
|
``VEF_VKZF`` wird als Projektvariable übernommen, danach wie try2 (Meldung
|
||||||
|
statt automatischem Laden).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -38,11 +42,7 @@ from sn_basis.functions.qt_wrapper import QFileDialog, QVariant
|
|||||||
from sn_basis.functions.variable_wrapper import set_variable
|
from sn_basis.functions.variable_wrapper import set_variable
|
||||||
from sn_basis.functions.laenderkennung import get_laenderkennung
|
from sn_basis.functions.laenderkennung import get_laenderkennung
|
||||||
from sn_basis.functions.dialog_wrapper import create_progress_dialog
|
from sn_basis.functions.dialog_wrapper import create_progress_dialog
|
||||||
from sn_basis.functions.ly_style_wrapper import apply_style
|
|
||||||
|
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
|
||||||
verfahrensgebiet_alkis_komplett,
|
|
||||||
)
|
|
||||||
from sn_verfahrensgebiet.functions.flurstueck_alkis import FLST_LAYER_NAME
|
from sn_verfahrensgebiet.functions.flurstueck_alkis import FLST_LAYER_NAME
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
|
||||||
VERF_LAYER_NAME,
|
VERF_LAYER_NAME,
|
||||||
@@ -283,9 +283,6 @@ def _try1_listenabgleich(
|
|||||||
3, "Beteiligung prüfen", "Verfahrensgebiet-Kennzeichen einlesen …"
|
3, "Beteiligung prüfen", "Verfahrensgebiet-Kennzeichen einlesen …"
|
||||||
)
|
)
|
||||||
progress.set_value(0)
|
progress.set_value(0)
|
||||||
# Ohne eine dazwischenliegende Nutzerinteraktion (z.B. nach der silent
|
|
||||||
# durchlaufenen ALKIS-Pipeline in try2/try3) holt sich kein anderer
|
|
||||||
# Dialog den Fokus zurück – Dialog daher explizit in den Vordergrund holen.
|
|
||||||
progress.raise_to_front()
|
progress.raise_to_front()
|
||||||
|
|
||||||
verf_menge = get_kennzeichen_menge(verf_layer)
|
verf_menge = get_kennzeichen_menge(verf_layer)
|
||||||
@@ -346,68 +343,38 @@ def _try1_listenabgleich(
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# try2/try3: ALKIS-Pipeline (silent) + Listenabgleich wie try1
|
# try2/try3: Aufforderung statt automatischer ALKIS-Pipeline
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Ein automatischer (nutzerloser) Durchlauf der kompletten Ladepipeline
|
||||||
|
# (mehrdeutige BROR-Treffer, Flächenabweichungen, Detailprüfung) hat sich in
|
||||||
|
# der Praxis nicht als zuverlässig genug erwiesen – deshalb hier bewusst
|
||||||
|
# KEIN silent-Aufruf von verfahrensgebiet_alkis_komplett(), sondern eine
|
||||||
|
# Aufforderung an den Nutzer, den regulären interaktiven Ablauf
|
||||||
|
# ("Aus ALKIS laden") selbst zu starten. Ein erneuter Klick auf
|
||||||
|
# "Beteiligung prüfen" landet danach automatisch bei try1, sobald der Layer
|
||||||
|
# verwertbare Kennzeichen hat.
|
||||||
|
|
||||||
def _lade_via_alkis_pipeline_und_vergleiche(
|
def _fordere_manuelles_laden_an(vef_vkzf: Optional[str] = None) -> None:
|
||||||
tab_widget, flurstueck_csv_liste: List[str]
|
"""Bricht try2/try3 mit einer Aufforderung an den Nutzer ab (statt eines
|
||||||
) -> BeteiligungErgebnis:
|
automatischen Ladevorgangs)."""
|
||||||
"""Lädt das Verfahrensgebiet silent per ALKIS-Pipeline (inkl. Detailprüfung)
|
hinweis = (
|
||||||
und gleicht den Bestand danach wie in try1 gegen die CSV-Liste ab.
|
"Bitte laden Sie das Verfahrensgebiet jetzt über 'Aus ALKIS laden' "
|
||||||
|
"und starten Sie die Beteiligungsprüfung danach erneut."
|
||||||
Raises
|
|
||||||
------
|
|
||||||
BeteiligungPruefungFehler
|
|
||||||
Wenn die ALKIS-Pipeline kein Verfahrensgebiet liefert (Pipeline hat
|
|
||||||
bereits eine erklärende Messagebox gezeigt).
|
|
||||||
"""
|
|
||||||
enriched_layer, flst_layer, bror_layer, status = verfahrensgebiet_alkis_komplett(
|
|
||||||
tab_widget, silent=True
|
|
||||||
)
|
)
|
||||||
if enriched_layer is None:
|
if vef_vkzf:
|
||||||
raise BeteiligungPruefungFehler(
|
raise BeteiligungPruefungFehler(
|
||||||
"Verfahrensgebiet konnte über die ALKIS-Pipeline nicht geladen werden."
|
f"Es ist noch kein Verfahrensgebiet geladen. Die Verfahrensnummer "
|
||||||
|
f"{vef_vkzf} wurde aus der CSV übernommen. {hinweis}"
|
||||||
)
|
)
|
||||||
|
raise BeteiligungPruefungFehler(
|
||||||
project = QgsProject.instance()
|
f"Der vorhandene Verfahrensgebiet-Layer enthält keine verwertbaren "
|
||||||
|
f"Flurstückskennzeichen. {hinweis}"
|
||||||
if flst_layer is not None and flst_layer.isValid():
|
|
||||||
if not project.mapLayersByName(flst_layer.name()):
|
|
||||||
project.addMapLayer(flst_layer)
|
|
||||||
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
|
||||||
|
|
||||||
if bror_layer is not None and bror_layer.isValid():
|
|
||||||
if not project.mapLayer(bror_layer.id()):
|
|
||||||
project.addMapLayer(bror_layer)
|
|
||||||
apply_style(bror_layer, "BauRaumOderBodenordnungsrecht.qml")
|
|
||||||
|
|
||||||
if not project.mapLayer(enriched_layer.id()):
|
|
||||||
project.addMapLayer(enriched_layer)
|
|
||||||
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
|
||||||
|
|
||||||
vergleich_layer, statistik = _try1_listenabgleich(
|
|
||||||
tab_widget, enriched_layer, flurstueck_csv_liste
|
|
||||||
)
|
)
|
||||||
|
|
||||||
layers: List[Tuple[object, Optional[str]]] = [
|
|
||||||
(enriched_layer, None),
|
|
||||||
(vergleich_layer, "beteiligung_vergleich.qml"),
|
|
||||||
]
|
|
||||||
if flst_layer is not None:
|
|
||||||
layers.append((flst_layer, None))
|
|
||||||
if bror_layer is not None:
|
|
||||||
layers.append((bror_layer, None))
|
|
||||||
|
|
||||||
text = (
|
|
||||||
f"Übereinstimmend: {statistik[WERT_BEIDE]}\n"
|
|
||||||
f"Nur im Verfahrensgebiet: {statistik[WERT_NUR_VERFAHRENSGEBIET]}\n"
|
|
||||||
f"Nur in der CSV-Liste (AGLB): {statistik[WERT_NUR_AGLB]}"
|
|
||||||
)
|
|
||||||
return BeteiligungErgebnis("try2", layers, statistik, text)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# try3: Bootstrap (Verfahrensnummer aus CSV) + wie try2
|
# try3: Bootstrap (Verfahrensnummer aus CSV) + Aufforderung wie try2
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _lese_vef_vkzf(zeilen: List[dict], spaltennamen: List[str]) -> str:
|
def _lese_vef_vkzf(zeilen: List[dict], spaltennamen: List[str]) -> str:
|
||||||
@@ -424,16 +391,10 @@ def _lese_vef_vkzf(zeilen: List[dict], spaltennamen: List[str]) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _try3_bootstrap_und_try2(
|
def _try3_bootstrap(tab_widget, zeilen: List[dict], spaltennamen: List[str]) -> None:
|
||||||
tab_widget, zeilen: List[dict], spaltennamen: List[str], flurstueck_csv_liste: List[str]
|
|
||||||
) -> BeteiligungErgebnis:
|
|
||||||
vef_vkzf = _lese_vef_vkzf(zeilen, spaltennamen)
|
vef_vkzf = _lese_vef_vkzf(zeilen, spaltennamen)
|
||||||
set_variable("verfahrensnummer", vef_vkzf, scope="project")
|
set_variable("verfahrensnummer", vef_vkzf, scope="project")
|
||||||
|
_fordere_manuelles_laden_an(vef_vkzf)
|
||||||
ergebnis = _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste)
|
|
||||||
ergebnis.modus = "try3"
|
|
||||||
ergebnis.statistik_text = f"Verfahrensnummer aus CSV: {vef_vkzf}\n" + ergebnis.statistik_text
|
|
||||||
return ergebnis
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -453,8 +414,11 @@ def beteiligung_pruefen(tab_widget) -> Optional[BeteiligungErgebnis]:
|
|||||||
------
|
------
|
||||||
BeteiligungPruefungFehler
|
BeteiligungPruefungFehler
|
||||||
Bei einem der dokumentierten Fehlerfälle (CSV-Format, fehlende
|
Bei einem der dokumentierten Fehlerfälle (CSV-Format, fehlende
|
||||||
Spalten, WFS-Dienst nicht erreichbar). Der Aufrufer zeigt daraufhin
|
Spalten) oder wenn kein Verfahrensgebiet mit verwertbaren
|
||||||
eine Messagebox an.
|
Kennzeichen vorliegt (try2/try3 – der Nutzer wird dann aufgefordert,
|
||||||
|
das Verfahrensgebiet zunächst über "Aus ALKIS laden" zu laden und
|
||||||
|
die Beteiligungsprüfung danach erneut zu starten). Der Aufrufer
|
||||||
|
zeigt daraufhin eine Messagebox an.
|
||||||
"""
|
"""
|
||||||
geladen = lade_beteiligung_csv(tab_widget)
|
geladen = lade_beteiligung_csv(tab_widget)
|
||||||
if geladen is None:
|
if geladen is None:
|
||||||
@@ -481,6 +445,8 @@ def beteiligung_pruefen(tab_widget) -> Optional[BeteiligungErgebnis]:
|
|||||||
return BeteiligungErgebnis("try1", [(layer, "beteiligung_vergleich.qml")], statistik, text)
|
return BeteiligungErgebnis("try1", [(layer, "beteiligung_vergleich.qml")], statistik, text)
|
||||||
|
|
||||||
if verf_layer is not None:
|
if verf_layer is not None:
|
||||||
return _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste)
|
_fordere_manuelles_laden_an()
|
||||||
|
return None # unreachable – _fordere_manuelles_laden_an() wirft immer
|
||||||
|
|
||||||
return _try3_bootstrap_und_try2(tab_widget, zeilen, spaltennamen, flurstueck_csv_liste)
|
_try3_bootstrap(tab_widget, zeilen, spaltennamen)
|
||||||
|
return None # unreachable – _try3_bootstrap() wirft immer
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from sn_basis.functions import (
|
|||||||
from sn_basis.functions import QMessageBox
|
from sn_basis.functions import QMessageBox
|
||||||
from sn_basis.functions.qt_wrapper import QVariant
|
from sn_basis.functions.qt_wrapper import QVariant
|
||||||
from sn_basis.functions.variable_wrapper import get_variable
|
from sn_basis.functions.variable_wrapper import get_variable
|
||||||
from sn_basis.functions.dialog_wrapper import ask_bror_kombination
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import ask_bror_kombinationen, ask_landkreis_objekte
|
||||||
|
from sn_basis.functions.landkreise import ALLE_LANDKREISE
|
||||||
|
|
||||||
alkis_sf_url = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
|
alkis_sf_url = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs"
|
||||||
typename = "adv:AX_BauRaumOderBodenordnungsrecht"
|
typename = "adv:AX_BauRaumOderBodenordnungsrecht"
|
||||||
@@ -28,7 +29,7 @@ BROR_ATTRIBUTE_FELDER = [
|
|||||||
|
|
||||||
class LoadStatus(Enum):
|
class LoadStatus(Enum):
|
||||||
"""Status eines Layer-Ladevorgangs.
|
"""Status eines Layer-Ladevorgangs.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
NONE: Nichts geladen
|
NONE: Nichts geladen
|
||||||
FIRST: Erstmalig geladen
|
FIRST: Erstmalig geladen
|
||||||
@@ -44,10 +45,10 @@ class LoadStatus(Enum):
|
|||||||
|
|
||||||
def _check_existing_layer(tab_widget):
|
def _check_existing_layer(tab_widget):
|
||||||
"""Prüft, ob ein Verfahrensgebiet-Layer bereits existiert.
|
"""Prüft, ob ein Verfahrensgebiet-Layer bereits existiert.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tab_widget: Parent-Widget für Dialoge
|
tab_widget: Parent-Widget für Dialoge
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[Optional[QgsVectorLayer], LoadStatus]: Layer und Status
|
Tuple[Optional[QgsVectorLayer], LoadStatus]: Layer und Status
|
||||||
"""
|
"""
|
||||||
@@ -72,40 +73,36 @@ def _check_existing_layer(tab_widget):
|
|||||||
return existing_layers[0], LoadStatus.KEEP
|
return existing_layers[0], LoadStatus.KEEP
|
||||||
|
|
||||||
|
|
||||||
def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False, silent: bool = False):
|
def _suche_verfahrensgebiet_features(tab_widget, skip_existing_check: bool = False):
|
||||||
"""Lädt Verfahrensgebiet aus ALKIS-WFS basierend auf Verfahrensnummer.
|
"""Phase 1 ("Suchen"): vorhandenen Layer prüfen, WFS abfragen, nach
|
||||||
|
(adv_name, bezeichnung) gruppieren.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tab_widget: Parent-Widget für Dialoge und Meldungen
|
tab_widget: Parent-Widget für Dialoge und Meldungen
|
||||||
skip_existing_check: Überspringt die Nachfrage bei bereits vorhandenem
|
skip_existing_check: Überspringt die Nachfrage bei bereits vorhandenem
|
||||||
Layer (immer neu laden).
|
Layer (immer neu laden).
|
||||||
silent: Automatisierter Modus ohne Nutzerinteraktion. Impliziert
|
|
||||||
``skip_existing_check=True`` und wählt bei mehreren
|
|
||||||
BROR-Kombinationen automatisch die erste (sortierte) statt den
|
|
||||||
Nutzer per Dialog entscheiden zu lassen.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[Optional[QgsVectorLayer], LoadStatus, List[QgsFeature]]:
|
Tuple[Optional[QgsVectorLayer], LoadStatus, dict, Optional[str]]:
|
||||||
Geladener Memory-Layer, Lade-Status und Liste der Roh-WFS-Features
|
``(fertig_layer, status, kombinationen, crs)``.
|
||||||
(für geometriebasierte Weiterverarbeitung, z.B. Gemarkungsschlüssel-
|
Bei ``KEEP`` oder Fehler/Abbruch ist ``fertig_layer`` bereits das
|
||||||
Ermittlung via Spatial-Join). Bei Abbruch oder Fehler: ``(None,
|
Endergebnis (ggf. ``None``) und ``kombinationen`` leer – Phase 2
|
||||||
LoadStatus.NONE, [])``. Bei ``LoadStatus.KEEP`` werden keine
|
entfällt dann. Bei Erfolg mit mindestens einer Kombination ist
|
||||||
WFS-Features neu geladen; die Feature-Liste ist in diesem Fall leer.
|
``fertig_layer`` ``None`` und ``kombinationen``/``crs`` sind für
|
||||||
|
Phase 2 (:func:`_baue_layer_aus_kombinationen`) befüllt.
|
||||||
"""
|
"""
|
||||||
verfahrensnummer = get_variable("verfahrensnummer")
|
verfahrensnummer = get_variable("verfahrensnummer")
|
||||||
if not verfahrensnummer:
|
if not verfahrensnummer:
|
||||||
QMessageBox.critical(tab_widget, "Fehler",
|
QMessageBox.critical(tab_widget, "Fehler",
|
||||||
"In den Projekteigenschaften ist noch keine Verfahrensnummer eingetragen!")
|
"In den Projekteigenschaften ist noch keine Verfahrensnummer eingetragen!")
|
||||||
return None, LoadStatus.NONE, []
|
return None, LoadStatus.NONE, {}, None
|
||||||
|
|
||||||
skip_existing_check = skip_existing_check or silent
|
|
||||||
|
|
||||||
# Vorhandenen Layer prüfen (überspringbar für die Shape-Pipeline)
|
# Vorhandenen Layer prüfen (überspringbar für die Shape-Pipeline)
|
||||||
status = LoadStatus.FIRST
|
status = LoadStatus.FIRST
|
||||||
if not skip_existing_check:
|
if not skip_existing_check:
|
||||||
existing, status = _check_existing_layer(tab_widget)
|
existing, status = _check_existing_layer(tab_widget)
|
||||||
if status == LoadStatus.KEEP:
|
if status == LoadStatus.KEEP:
|
||||||
return existing, status, []
|
return existing, status, {}, None
|
||||||
|
|
||||||
# WFS mit Filter laden – nur erste 5 Stellen der VKZ für den Vergleich verwenden,
|
# 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.
|
# da die Daten im Dienst ggf. abweichende Suffixe führen.
|
||||||
@@ -119,13 +116,13 @@ def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False, silent
|
|||||||
|
|
||||||
if not temp_layer.isValid():
|
if not temp_layer.isValid():
|
||||||
QMessageBox.critical(tab_widget, "Fehler", "Layer konnte nicht geladen werden.")
|
QMessageBox.critical(tab_widget, "Fehler", "Layer konnte nicht geladen werden.")
|
||||||
return None, LoadStatus.NONE, []
|
return None, LoadStatus.NONE, {}, None
|
||||||
|
|
||||||
features = list(temp_layer.getFeatures())
|
features = list(temp_layer.getFeatures())
|
||||||
if not features:
|
if not features:
|
||||||
QMessageBox.critical(tab_widget, "Fehler",
|
QMessageBox.critical(tab_widget, "Fehler",
|
||||||
f"Verfahrenskennzeichen {verfahrensnummer} nicht im WFS gefunden.")
|
f"Verfahrenskennzeichen {verfahrensnummer} nicht im WFS gefunden.")
|
||||||
return None, LoadStatus.NONE, []
|
return None, LoadStatus.NONE, {}, None
|
||||||
|
|
||||||
# Objekte nach (adv_name, bezeichnung) gruppieren
|
# Objekte nach (adv_name, bezeichnung) gruppieren
|
||||||
kombinationen: dict[tuple[str, str], list] = {}
|
kombinationen: dict[tuple[str, str], list] = {}
|
||||||
@@ -141,20 +138,32 @@ def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False, silent
|
|||||||
kombinationen[key] = []
|
kombinationen[key] = []
|
||||||
kombinationen[key].append(feat)
|
kombinationen[key].append(feat)
|
||||||
|
|
||||||
|
return None, status, kombinationen, temp_layer.crs().authid()
|
||||||
|
|
||||||
|
|
||||||
|
def _baue_layer_aus_kombinationen(tab_widget, kombinationen: dict, crs: str, status: "LoadStatus"):
|
||||||
|
"""Phase 2 ("Auswählen + Laden"): bei mehreren Kombinationen Auswahldialog
|
||||||
|
zeigen, Geometrien der gewählten Kombination(en) verschmelzen, Memory-Layer bauen.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab_widget: Parent-Widget für Dialoge und Meldungen
|
||||||
|
kombinationen: Ergebnis aus :func:`_suche_verfahrensgebiet_features`
|
||||||
|
crs: CRS-Authid aus :func:`_suche_verfahrensgebiet_features`
|
||||||
|
status: Lade-Status aus Phase 1 (FIRST/RELOAD), wird durchgereicht
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[Optional[QgsVectorLayer], LoadStatus, List[QgsFeature]]
|
||||||
|
"""
|
||||||
if len(kombinationen) == 1:
|
if len(kombinationen) == 1:
|
||||||
selected_key = next(iter(kombinationen))
|
selected_feats = next(iter(kombinationen.values()))
|
||||||
selected_feats = kombinationen[selected_key]
|
|
||||||
elif silent:
|
|
||||||
# Automatisierter Modus: erste (sortierte) Kombination ohne Nachfrage
|
|
||||||
selected_key = sorted(kombinationen.keys())[0]
|
|
||||||
selected_feats = kombinationen[selected_key]
|
|
||||||
else:
|
else:
|
||||||
# Mehrere Kombinationen → Nutzerauswahl
|
# Mehrere Kombinationen → Nutzerauswahl, ein oder mehrere Objekte
|
||||||
|
# können gemeinsam ausgewählt und geladen (verschmolzen) werden.
|
||||||
key_list = sorted(kombinationen.keys())
|
key_list = sorted(kombinationen.keys())
|
||||||
selected_key = ask_bror_kombination(tab_widget, key_list)
|
selected_keys = ask_bror_kombinationen(tab_widget, key_list)
|
||||||
if selected_key is None:
|
if not selected_keys:
|
||||||
return None, LoadStatus.NONE, []
|
return None, LoadStatus.NONE, []
|
||||||
selected_feats = kombinationen[selected_key]
|
selected_feats = [feat for key in selected_keys for feat in kombinationen[key]]
|
||||||
|
|
||||||
union_geom = QgsGeometry.unaryUnion([f.geometry() for f in selected_feats])
|
union_geom = QgsGeometry.unaryUnion([f.geometry() for f in selected_feats])
|
||||||
if union_geom is None or union_geom.isEmpty():
|
if union_geom is None or union_geom.isEmpty():
|
||||||
@@ -162,7 +171,6 @@ def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False, silent
|
|||||||
return None, LoadStatus.NONE, []
|
return None, LoadStatus.NONE, []
|
||||||
|
|
||||||
# Memory-Layer erzeugen
|
# Memory-Layer erzeugen
|
||||||
crs = temp_layer.crs().authid()
|
|
||||||
memory_layer = QgsVectorLayer(f"Polygon?crs={crs}", "Verfahrensgebiet", "memory")
|
memory_layer = QgsVectorLayer(f"Polygon?crs={crs}", "Verfahrensgebiet", "memory")
|
||||||
pr = memory_layer.dataProvider()
|
pr = memory_layer.dataProvider()
|
||||||
pr.addAttributes([QgsField(attr, QVariant.String) for attr in BROR_ATTRIBUTE_FELDER])
|
pr.addAttributes([QgsField(attr, QVariant.String) for attr in BROR_ATTRIBUTE_FELDER])
|
||||||
@@ -192,3 +200,158 @@ def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False, silent
|
|||||||
|
|
||||||
# Status: FIRST oder RELOAD; selected_feats für Gemarkungsermittlung weitergeben
|
# Status: FIRST oder RELOAD; selected_feats für Gemarkungsermittlung weitergeben
|
||||||
return memory_layer, status, selected_feats
|
return memory_layer, status, selected_feats
|
||||||
|
|
||||||
|
|
||||||
|
def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_existing_check: bool = False):
|
||||||
|
"""Fallback-Suche über einen ganzen Landkreis (statt einer Verfahrensnummer).
|
||||||
|
|
||||||
|
Wie :func:`_suche_verfahrensgebiet_features`, aber mit WFS-Filter über die
|
||||||
|
Landkreiskennzahl (``bezeichnung LIKE '{landkreis_kennzahl}%'``) statt
|
||||||
|
über den 5-stelligen VKZ-Präfix. Gruppierung der Rohfeatures identisch:
|
||||||
|
nach exaktem (adv_name, bezeichnung).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[Optional[QgsVectorLayer], LoadStatus, dict, Optional[str]]:
|
||||||
|
Gleiche Bedeutung wie bei :func:`_suche_verfahrensgebiet_features`.
|
||||||
|
"""
|
||||||
|
status = LoadStatus.FIRST
|
||||||
|
if not skip_existing_check:
|
||||||
|
existing, status = _check_existing_layer(tab_widget)
|
||||||
|
if status == LoadStatus.KEEP:
|
||||||
|
return existing, status, {}, None
|
||||||
|
|
||||||
|
# "Alle Landkreise" (Sentinel) -> kein Kennzahl-Präfix, LIKE '%' matcht alles.
|
||||||
|
praefix = "" if landkreis_kennzahl == ALLE_LANDKREISE else landkreis_kennzahl
|
||||||
|
landkreis_anzeige = "allen Landkreisen" if landkreis_kennzahl == ALLE_LANDKREISE else f"Landkreis {landkreis_kennzahl}"
|
||||||
|
|
||||||
|
wfs_uri_find = (
|
||||||
|
f"url='{alkis_sf_url}' typename='{typename}' srsname='EPSG:25833' "
|
||||||
|
f"sql=SELECT * FROM AX_BauRaumOderBodenordnungsrecht "
|
||||||
|
f"WHERE bezeichnung LIKE '{praefix}%'"
|
||||||
|
)
|
||||||
|
temp_layer = QgsVectorLayer(wfs_uri_find, "TempVerfahrensgebiet", "WFS")
|
||||||
|
|
||||||
|
if not temp_layer.isValid():
|
||||||
|
QMessageBox.critical(tab_widget, "Fehler", "Layer konnte nicht geladen werden.")
|
||||||
|
return None, LoadStatus.NONE, {}, None
|
||||||
|
|
||||||
|
features = list(temp_layer.getFeatures())
|
||||||
|
if not features:
|
||||||
|
QMessageBox.critical(tab_widget, "Fehler",
|
||||||
|
f"Für {landkreis_anzeige} wurden keine Objekte im WFS gefunden.")
|
||||||
|
return None, LoadStatus.NONE, {}, None
|
||||||
|
|
||||||
|
kombinationen: dict[tuple[str, str], list] = {}
|
||||||
|
for feat in features:
|
||||||
|
try:
|
||||||
|
adv_name_val = str(feat.attribute("adv_name") or "")
|
||||||
|
bezeichnung_val = str(feat.attribute("bezeichnung") or "")
|
||||||
|
except Exception:
|
||||||
|
adv_name_val = ""
|
||||||
|
bezeichnung_val = ""
|
||||||
|
key = (adv_name_val, bezeichnung_val)
|
||||||
|
if key not in kombinationen:
|
||||||
|
kombinationen[key] = []
|
||||||
|
kombinationen[key].append(feat)
|
||||||
|
|
||||||
|
return None, status, kombinationen, temp_layer.crs().authid()
|
||||||
|
|
||||||
|
|
||||||
|
def _baue_layer_aus_landkreis_auswahl(tab_widget, kombinationen: dict, crs: str, status: "LoadStatus"):
|
||||||
|
"""Auswahl + Bau für den Landkreis-Fallback.
|
||||||
|
|
||||||
|
Zeigt :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge.ask_landkreis_objekte`
|
||||||
|
(Mehrfachauswahl inkl. "Alle auswählen"). Die ausgewählten Kombinationen
|
||||||
|
werden anhand der ersten 5 Zeichen von ``bezeichnung`` (= die eigentliche
|
||||||
|
VKZ) gruppiert; je Gruppe wird EIN Feature erzeugt (Geometrien der
|
||||||
|
Gruppe verschmolzen). Damit entsteht bei Auswahl mehrerer/aller Objekte
|
||||||
|
eines Landkreises ein Layer mit mehreren Features – einem je
|
||||||
|
tatsächlichem Verfahren, nicht ein Gesamt-Blob.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab_widget: Parent-Widget für Dialoge und Meldungen
|
||||||
|
kombinationen: Ergebnis aus :func:`_suche_landkreis_kombinationen`
|
||||||
|
crs: CRS-Authid aus :func:`_suche_landkreis_kombinationen`
|
||||||
|
status: Lade-Status aus Phase 1 (FIRST/RELOAD), wird durchgereicht
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[Optional[QgsVectorLayer], LoadStatus]: ``(layer, status)``.
|
||||||
|
"""
|
||||||
|
key_list = sorted(kombinationen.keys())
|
||||||
|
selected_keys = ask_landkreis_objekte(tab_widget, key_list)
|
||||||
|
if not selected_keys:
|
||||||
|
return None, LoadStatus.NONE
|
||||||
|
|
||||||
|
# Nach VKZ (erste 5 Zeichen der bezeichnung) gruppieren
|
||||||
|
vkz_gruppen: dict[str, list] = {}
|
||||||
|
for key in selected_keys:
|
||||||
|
_, bezeichnung = key
|
||||||
|
vkz = bezeichnung[:5]
|
||||||
|
vkz_gruppen.setdefault(vkz, []).extend(kombinationen[key])
|
||||||
|
|
||||||
|
memory_layer = QgsVectorLayer(f"Polygon?crs={crs}", "Verfahrensgebiet", "memory")
|
||||||
|
pr = memory_layer.dataProvider()
|
||||||
|
pr.addAttributes([QgsField(attr, QVariant.String) for attr in BROR_ATTRIBUTE_FELDER])
|
||||||
|
memory_layer.updateFields()
|
||||||
|
|
||||||
|
neue_features = []
|
||||||
|
for vkz, feats in sorted(vkz_gruppen.items()):
|
||||||
|
union_geom = QgsGeometry.unaryUnion([f.geometry() for f in feats])
|
||||||
|
if union_geom is None or union_geom.isEmpty():
|
||||||
|
continue
|
||||||
|
|
||||||
|
first_feat = feats[0]
|
||||||
|
attrs = []
|
||||||
|
for attr in BROR_ATTRIBUTE_FELDER:
|
||||||
|
try:
|
||||||
|
val = first_feat.attribute(attr)
|
||||||
|
if val is None:
|
||||||
|
attrs.append("")
|
||||||
|
elif type(val).__name__ == "QDate":
|
||||||
|
attrs.append(val.toString("dd.MM.yyyy"))
|
||||||
|
else:
|
||||||
|
attrs.append(str(val))
|
||||||
|
except Exception:
|
||||||
|
attrs.append("")
|
||||||
|
|
||||||
|
feat_out = QgsFeature()
|
||||||
|
feat_out.setGeometry(union_geom)
|
||||||
|
feat_out.setAttributes(attrs)
|
||||||
|
neue_features.append(feat_out)
|
||||||
|
|
||||||
|
if not neue_features:
|
||||||
|
QMessageBox.critical(tab_widget, "Fehler", "Keine Geometrien zum Verschmelzen gefunden.")
|
||||||
|
return None, LoadStatus.NONE
|
||||||
|
|
||||||
|
pr.addFeatures(neue_features)
|
||||||
|
memory_layer.updateExtents()
|
||||||
|
|
||||||
|
return memory_layer, status
|
||||||
|
|
||||||
|
|
||||||
|
def verfahrensgebiet_alkis(tab_widget, skip_existing_check: bool = False):
|
||||||
|
"""Lädt Verfahrensgebiet aus ALKIS-WFS basierend auf Verfahrensnummer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab_widget: Parent-Widget für Dialoge und Meldungen
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[Optional[QgsVectorLayer], LoadStatus, List[QgsFeature]]:
|
||||||
|
Geladener Memory-Layer, Lade-Status und Liste der Roh-WFS-Features
|
||||||
|
(für geometriebasierte Weiterverarbeitung, z.B. Gemarkungsschlüssel-
|
||||||
|
Ermittlung via Spatial-Join). Bei Abbruch oder Fehler: ``(None,
|
||||||
|
LoadStatus.NONE, [])``. Bei ``LoadStatus.KEEP`` werden keine
|
||||||
|
WFS-Features neu geladen; die Feature-Liste ist in diesem Fall leer.
|
||||||
|
|
||||||
|
Ruft intern nacheinander :func:`_suche_verfahrensgebiet_features` (Phase 1)
|
||||||
|
und ggf. :func:`_baue_layer_aus_kombinationen` (Phase 2) auf – aufgeteilt,
|
||||||
|
damit ``verfahrensgebiet_alkis_komplett()`` dazwischen einen eigenen
|
||||||
|
Fortschrittsschritt anzeigen kann. Verhalten/Signatur dieser Funktion
|
||||||
|
selbst ist unverändert.
|
||||||
|
"""
|
||||||
|
layer, status, kombinationen, crs = _suche_verfahrensgebiet_features(
|
||||||
|
tab_widget, skip_existing_check
|
||||||
|
)
|
||||||
|
if layer is not None or not kombinationen:
|
||||||
|
return layer, status, []
|
||||||
|
return _baue_layer_aus_kombinationen(tab_widget, kombinationen, crs, status)
|
||||||
|
|||||||
@@ -51,10 +51,17 @@ from sn_basis.functions.dialog_wrapper import (
|
|||||||
create_progress_dialog,
|
create_progress_dialog,
|
||||||
show_info_dialog,
|
show_info_dialog,
|
||||||
ask_detailpruefung_oder_vg_laden,
|
ask_detailpruefung_oder_vg_laden,
|
||||||
|
ask_yes_no,
|
||||||
)
|
)
|
||||||
|
from sn_basis.functions.landkreise import LANDKREISE, ALLE_LANDKREISE
|
||||||
|
from sn_basis.functions.ly_style_wrapper import apply_style
|
||||||
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import ask_landkreis_auswahl
|
||||||
|
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import (
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import (
|
||||||
verfahrensgebiet_alkis,
|
_suche_verfahrensgebiet_features,
|
||||||
|
_baue_layer_aus_kombinationen,
|
||||||
|
_suche_landkreis_kombinationen,
|
||||||
|
_baue_layer_aus_landkreis_auswahl,
|
||||||
LoadStatus,
|
LoadStatus,
|
||||||
)
|
)
|
||||||
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
||||||
@@ -76,6 +83,8 @@ from sn_verfahrensgebiet.functions.knickpunkt_pruefung import (
|
|||||||
FELD_VKZ = "verfahrensnummer"
|
FELD_VKZ = "verfahrensnummer"
|
||||||
#: Anzeigename des BROR-Layers im Projekt
|
#: Anzeigename des BROR-Layers im Projekt
|
||||||
BROR_LAYER_NAME = "BauRaumOderBodenordnungsrecht (ALKIS)"
|
BROR_LAYER_NAME = "BauRaumOderBodenordnungsrecht (ALKIS)"
|
||||||
|
#: QML-Stildatei für den BROR-Layer (sn_verfahrensgebiet/styles/)
|
||||||
|
BROR_STYLE_QML = "BauRaumOderBodenordnungsrecht.qml"
|
||||||
#: Projektvariable für die Flächentoleranz (in m²); Default 0
|
#: Projektvariable für die Flächentoleranz (in m²); Default 0
|
||||||
FLAECHEN_TOLERANZ_VARIABLE = "flaechen_toleranz_m2"
|
FLAECHEN_TOLERANZ_VARIABLE = "flaechen_toleranz_m2"
|
||||||
#: Minimale implizite Flächentoleranz – Abweichungen unter diesem Wert
|
#: Minimale implizite Flächentoleranz – Abweichungen unter diesem Wert
|
||||||
@@ -254,20 +263,33 @@ def _vergleiche_flaechen(
|
|||||||
|
|
||||||
def verfahrensgebiet_alkis_komplett(
|
def verfahrensgebiet_alkis_komplett(
|
||||||
tab_widget,
|
tab_widget,
|
||||||
silent: bool = False,
|
|
||||||
) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]:
|
) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]:
|
||||||
"""Vollständiger VG-Lade-Workflow: ALKIS-VG → Gemarkungen →
|
"""Vollständiger VG-Lade-Workflow: ALKIS-VG → Gemarkungen →
|
||||||
Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung.
|
Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung.
|
||||||
|
|
||||||
Schrittfolge:
|
Zwei Fortschrittsdialoge nacheinander:
|
||||||
|
|
||||||
1. ``verfahrensgebiet_alkis(tab_widget)`` – BROR-Objekt als Memory-Layer.
|
- **Dialog 1** ("Verfahrensgebiet(e) in ALKIS ermitteln/suchen"): läuft
|
||||||
2. ``gemarkung_schluessel_und_namen_von_vg_geometrie(geom)`` – Schlüssel.
|
während ``_suche_verfahrensgebiet_features(tab_widget)`` (ALKIS-WFS
|
||||||
3. ``flurstueck_alkis(...)`` – Flurstücke laden.
|
durchsuchen) und wird danach immer geschlossen – auch wenn anschließend
|
||||||
4. ``_select_by_location(...)`` – räumliche Selektion.
|
der BROR-Auswahldialog erscheint (kein Fortschrittsdialog läuft
|
||||||
5. ``erstelle_verfahrensgebiet_aus_auswahl(...)`` – enriched Layer.
|
währenddessen parallel zum Auswahldialog).
|
||||||
6. ``_vergleiche_flaechen(...)`` – Flächenvergleich als Gate.
|
- **STOP** (Zwischenabfrage "Weiter?"): erscheint nach dem Laden/Übernehmen
|
||||||
7. Ggf. ``umringspruefung(...)`` nach Nutzerbestätigung.
|
des BROR-Objekts, bevor Dialog 2 startet. Bei "Nein" bricht der Ablauf
|
||||||
|
ab, das BROR-Objekt bleibt aber im Projekt (ersetzt die frühere
|
||||||
|
"Nur Objekte laden"-Option des Landkreis-Auswahldialogs).
|
||||||
|
- **Dialog 2** (eigentlicher Workflow, 6 Schritte): startet erst, nachdem
|
||||||
|
das Verfahrensgebiet feststeht und die Stop-Abfrage mit "Ja" bestätigt
|
||||||
|
wurde.
|
||||||
|
|
||||||
|
Schrittfolge in Dialog 2:
|
||||||
|
|
||||||
|
1. ``gemarkung_schluessel_und_namen_von_vg_geometrie(geom)`` – Schlüssel.
|
||||||
|
2. ``flurstueck_alkis(...)`` – Flurstücke laden.
|
||||||
|
3. ``_select_by_location(...)`` – räumliche Selektion.
|
||||||
|
4. ``erstelle_verfahrensgebiet_aus_auswahl(...)`` – enriched Layer.
|
||||||
|
5. ``_vergleiche_flaechen(...)`` – Flächenvergleich als Gate.
|
||||||
|
6. Ggf. ``umringspruefung(...)`` nach Nutzerbestätigung.
|
||||||
|
|
||||||
Der BROR-Layer (``BauRaumOderBodenordnungsrecht (ALKIS)``) bleibt bei Erfolg
|
Der BROR-Layer (``BauRaumOderBodenordnungsrecht (ALKIS)``) bleibt bei Erfolg
|
||||||
im Projekt. Bei Abbruch/Fehler wird er entfernt, falls er in diesem
|
im Projekt. Bei Abbruch/Fehler wird er entfernt, falls er in diesem
|
||||||
@@ -277,15 +299,6 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
----------
|
----------
|
||||||
tab_widget :
|
tab_widget :
|
||||||
Eltern-Widget für alle Dialoge.
|
Eltern-Widget für alle Dialoge.
|
||||||
silent :
|
|
||||||
Automatisierter Modus ohne Nutzerentscheidungen: bestehender
|
|
||||||
VG-Layer wird ohne Nachfrage neu geladen, mehrdeutige
|
|
||||||
BROR-Kombinationen werden automatisch aufgelöst, bei
|
|
||||||
Flächenabweichung wird immer die Detailprüfung gestartet (nie „als
|
|
||||||
VG laden“, nie abbrechen), und die beiden rein informativen
|
|
||||||
Abschluss-Meldungen (Flächenvergleich-OK, Detailprüfung
|
|
||||||
abgeschlossen) werden unterdrückt. Die Fortschrittsdialoge bleiben
|
|
||||||
sichtbar.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -300,26 +313,75 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
alkis_vg_layer = None
|
alkis_vg_layer = None
|
||||||
bror_was_added_by_us = False
|
bror_was_added_by_us = False
|
||||||
abgeschlossen = False
|
abgeschlossen = False
|
||||||
|
progress = None
|
||||||
|
|
||||||
# Fortschrittsdialog für den Basisworkflow (7 Schritte)
|
verfahrensnummer = get_variable("verfahrensnummer")
|
||||||
progress = create_progress_dialog(
|
landkreis_kennzahl = get_variable("landkreis_kennzahl")
|
||||||
7, "Verfahrensgebiet laden", "Initialisierung …"
|
|
||||||
)
|
if not verfahrensnummer and not landkreis_kennzahl:
|
||||||
|
# Keine Projekteigenschaften vorhanden – Landkreis per Dialog abfragen,
|
||||||
|
# statt direkt abzubrechen.
|
||||||
|
landkreis_kennzahl = ask_landkreis_auswahl(tab_widget, LANDKREISE)
|
||||||
|
if not landkreis_kennzahl:
|
||||||
|
# Nutzer hat den Landkreis-Dialog abgebrochen – stiller Abbruch,
|
||||||
|
# keine Fehlermeldung (bewusster Abbruch, kein Fehlerzustand).
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
landkreis_modus = not verfahrensnummer
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Dialog 1: Verfahrensgebiet(e) in ALKIS ermitteln/suchen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
if landkreis_modus:
|
||||||
|
if landkreis_kennzahl == ALLE_LANDKREISE:
|
||||||
|
such_label = "Objekte in allen Landkreisen ermitteln/suchen …"
|
||||||
|
else:
|
||||||
|
such_label = f"Objekte in Landkreis {landkreis_kennzahl} ermitteln/suchen …"
|
||||||
|
else:
|
||||||
|
such_label = "Verfahrensgebiet(e) in ALKIS ermitteln/suchen …"
|
||||||
|
|
||||||
|
progress_suche = create_progress_dialog(1, "Verfahrensgebiet laden", such_label)
|
||||||
|
try:
|
||||||
|
progress_suche.set_value(0)
|
||||||
|
if progress_suche.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
if landkreis_modus:
|
||||||
|
vorab_layer, vg_status, kombinationen, crs = _suche_landkreis_kombinationen(
|
||||||
|
tab_widget, landkreis_kennzahl
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
vorab_layer, vg_status, kombinationen, crs = _suche_verfahrensgebiet_features(tab_widget)
|
||||||
|
finally:
|
||||||
|
# Dialog 1 schließt hier immer – egal ob eine Auswahl folgt oder nicht.
|
||||||
|
progress_suche.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# ------------------------------------------------------------------
|
if vorab_layer is not None or not kombinationen:
|
||||||
# Schritt 1: VG aus ALKIS laden (BROR-Objekt → Memory-Layer)
|
# KEEP oder Fehler/Abbruch bereits in Phase 1 entschieden
|
||||||
# ------------------------------------------------------------------
|
if vg_status == LoadStatus.NONE or not vorab_layer:
|
||||||
progress.set_label("Schritt 1/6: Verfahrensgebiet aus ALKIS laden …")
|
return None, None, None, LoadStatus.NONE
|
||||||
progress.set_value(0)
|
alkis_vg_layer = vorab_layer
|
||||||
if progress.is_canceled():
|
elif landkreis_modus:
|
||||||
return None, None, None, LoadStatus.NONE
|
# ------------------------------------------------------------------
|
||||||
|
# Objekt(e) des Landkreises auswählen – kein eigener Fortschritts-
|
||||||
alkis_vg_layer, vg_status, _ = verfahrensgebiet_alkis(tab_widget, silent=silent)
|
# dialog währenddessen, nur der Auswahldialog selbst.
|
||||||
progress.raise_to_front()
|
# ------------------------------------------------------------------
|
||||||
|
alkis_vg_layer, vg_status = _baue_layer_aus_landkreis_auswahl(
|
||||||
if vg_status == LoadStatus.NONE or not alkis_vg_layer:
|
tab_widget, kombinationen, crs, vg_status
|
||||||
return None, None, None, LoadStatus.NONE
|
)
|
||||||
|
if vg_status == LoadStatus.NONE or not alkis_vg_layer:
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
else:
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Verfahrensgebiet auswählen (bei mehreren Treffern) – kein eigener
|
||||||
|
# Fortschrittsdialog währenddessen, nur der Auswahldialog selbst.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
alkis_vg_layer, vg_status, _ = _baue_layer_aus_kombinationen(
|
||||||
|
tab_widget, kombinationen, crs, vg_status
|
||||||
|
)
|
||||||
|
if vg_status == LoadStatus.NONE or not alkis_vg_layer:
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
# BROR-Layer umbenennen, um Namenskonflikt mit enriched VG-Layer zu vermeiden
|
# BROR-Layer umbenennen, um Namenskonflikt mit enriched VG-Layer zu vermeiden
|
||||||
alkis_vg_layer.setName(BROR_LAYER_NAME)
|
alkis_vg_layer.setName(BROR_LAYER_NAME)
|
||||||
@@ -327,13 +389,38 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
if vg_status in (LoadStatus.FIRST, LoadStatus.RELOAD):
|
if vg_status in (LoadStatus.FIRST, LoadStatus.RELOAD):
|
||||||
project.addMapLayer(alkis_vg_layer)
|
project.addMapLayer(alkis_vg_layer)
|
||||||
bror_was_added_by_us = True
|
bror_was_added_by_us = True
|
||||||
|
# Style direkt nach dem Laden anwenden – unabhängig vom weiteren
|
||||||
progress.set_value(1)
|
# Ablauf, damit auch bei "Nein" auf die Stop-Abfrage der Style
|
||||||
|
# gesetzt ist.
|
||||||
|
apply_style(alkis_vg_layer, BROR_STYLE_QML)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 2: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
# STOP: Zwischenabfrage nach dem Laden des BROR-Objekts, bevor mit
|
||||||
|
# Gemarkung/Flurstücke/Flächenvergleich fortgefahren wird (ersetzt
|
||||||
|
# die frühere "Nur Objekte laden"-Option).
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 2/6: Gemarkungsschlüssel ermitteln …")
|
if not ask_yes_no(
|
||||||
|
"Verfahrensgebiet laden",
|
||||||
|
f"Layer '{alkis_vg_layer.name()}' wurde geladen. Weiter?",
|
||||||
|
default=True,
|
||||||
|
parent=tab_widget,
|
||||||
|
):
|
||||||
|
abgeschlossen = True
|
||||||
|
return alkis_vg_layer, None, None, vg_status
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Dialog 2: eigentlicher Fortschrittsdialog für die restlichen Schritte
|
||||||
|
# (startet erst jetzt, nach erfolgreicher Auswahl/Übernahme des VG)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
progress = create_progress_dialog(
|
||||||
|
6, "Verfahrensgebiet laden", "Initialisierung …"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Schritt 1: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
progress.set_label("Schritt 1/6: Gemarkungen ermitteln und laden …")
|
||||||
|
progress.set_value(0)
|
||||||
if progress.is_canceled():
|
if progress.is_canceled():
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
@@ -354,12 +441,12 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
duration=5,
|
duration=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
progress.set_value(2)
|
progress.set_value(1)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 3: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt)
|
# Schritt 2: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 3/6: Flurstücke aus ALKIS laden …")
|
progress.set_label("Schritt 2/6: Flurstücke aus ALKIS laden …")
|
||||||
if progress.is_canceled():
|
if progress.is_canceled():
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
@@ -373,12 +460,12 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
if flst_status == LoadStatus.NONE or not flst_layer:
|
if flst_status == LoadStatus.NONE or not flst_layer:
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
progress.set_value(3)
|
progress.set_value(2)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 4: Räumliche Selektion
|
# Schritt 3: Räumliche Selektion
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 4/6: Flurstücke räumlich selektieren …")
|
progress.set_label("Schritt 3/6: Flurstücke räumlich selektieren …")
|
||||||
if progress.is_canceled():
|
if progress.is_canceled():
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
@@ -393,12 +480,12 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
)
|
)
|
||||||
return None, flst_layer, None, LoadStatus.NONE
|
return None, flst_layer, None, LoadStatus.NONE
|
||||||
|
|
||||||
progress.set_value(4)
|
progress.set_value(3)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 5: Enriched Memory-Layer aus Auswahl erzeugen
|
# Schritt 4: Enriched Memory-Layer aus Auswahl erzeugen
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 5/6: Verfahrensgebiet-Layer erzeugen …")
|
progress.set_label("Schritt 4/6: Verfahrensgebiet-Layer erzeugen …")
|
||||||
if progress.is_canceled():
|
if progress.is_canceled():
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
@@ -417,12 +504,12 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
vkz = get_variable("verfahrensnummer") or ""
|
vkz = get_variable("verfahrensnummer") or ""
|
||||||
_fuege_vkz_feld_hinzu(enriched_layer, vkz)
|
_fuege_vkz_feld_hinzu(enriched_layer, vkz)
|
||||||
|
|
||||||
progress.set_value(5)
|
progress.set_value(4)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 6: Flächenvergleich als Gate
|
# Schritt 5: Flächenvergleich als Gate
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 6/6: Flächenvergleich …")
|
progress.set_label("Schritt 5/6: Flächenvergleich …")
|
||||||
if progress.is_canceled():
|
if progress.is_canceled():
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
@@ -431,39 +518,32 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
alkis_vg_layer, enriched_layer, toleranz
|
alkis_vg_layer, enriched_layer, toleranz
|
||||||
)
|
)
|
||||||
|
|
||||||
progress.set_value(6)
|
progress.set_value(5)
|
||||||
|
|
||||||
if stimmt_ueberein:
|
if stimmt_ueberein:
|
||||||
progress.set_value(7)
|
progress.set_value(6)
|
||||||
progress.close()
|
show_info_dialog(
|
||||||
if not silent:
|
"Flächenvergleich",
|
||||||
show_info_dialog(
|
"Verfahrensgebiet mit erwarteter Geometrie erzeugt",
|
||||||
"Flächenvergleich",
|
parent=tab_widget,
|
||||||
"Verfahrensgebiet mit erwarteter Geometrie erzeugt",
|
)
|
||||||
parent=tab_widget,
|
|
||||||
)
|
|
||||||
abgeschlossen = True
|
abgeschlossen = True
|
||||||
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Flächenabweichung → 3-Optionen-Dialog (im silent-Modus: immer
|
# Flächenabweichung → 3-Optionen-Dialog
|
||||||
# Detailprüfung, ohne Nachfrage)
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.close()
|
|
||||||
diff = abs(bror_flaeche - vg_flaeche)
|
diff = abs(bror_flaeche - vg_flaeche)
|
||||||
if silent:
|
entscheidung = ask_detailpruefung_oder_vg_laden(
|
||||||
entscheidung = "detailpruefung"
|
tab_widget,
|
||||||
else:
|
"Abweichung erkannt",
|
||||||
entscheidung = ask_detailpruefung_oder_vg_laden(
|
f"Abweichung zwischen BauRaumOderBodenordnungsrecht-Objekt und abgeleitetem "
|
||||||
tab_widget,
|
f"Verfahrensgebiet erkannt.\n\n"
|
||||||
"Abweichung erkannt",
|
f"As: {bror_flaeche:.2f} m²\n"
|
||||||
f"Abweichung zwischen BauRaumOderBodenordnungsrecht-Objekt und abgeleitetem "
|
f"Verfahrensgebiet: {vg_flaeche:.2f} m²\n"
|
||||||
f"Verfahrensgebiet erkannt.\n\n"
|
f"Differenz: {diff:.2f} m²\n\n"
|
||||||
f"As: {bror_flaeche:.2f} m²\n"
|
f"Wie möchten Sie fortfahren?",
|
||||||
f"Verfahrensgebiet: {vg_flaeche:.2f} m²\n"
|
)
|
||||||
f"Differenz: {diff:.2f} m²\n\n"
|
|
||||||
f"Wie möchten Sie fortfahren?",
|
|
||||||
)
|
|
||||||
|
|
||||||
if entscheidung == "abbrechen":
|
if entscheidung == "abbrechen":
|
||||||
abgeschlossen = True
|
abgeschlossen = True
|
||||||
@@ -496,10 +576,6 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
"Schritt 1: Grenzpunkte laden …",
|
"Schritt 1: Grenzpunkte laden …",
|
||||||
)
|
)
|
||||||
detail_progress.set_value(0)
|
detail_progress.set_value(0)
|
||||||
# Ohne die (im silent-Modus übersprungene) Nachfrage vor diesem Punkt
|
|
||||||
# holt sich kein anderer Dialog mehr den Fokus zurück – Dialog daher
|
|
||||||
# explizit in den Vordergrund holen, sonst bleibt er unsichtbar.
|
|
||||||
detail_progress.raise_to_front()
|
|
||||||
|
|
||||||
# Schritt 1: PunktortTA gemarkungsweise laden (Hits + 5000er-Batches)
|
# Schritt 1: PunktortTA gemarkungsweise laden (Hits + 5000er-Batches)
|
||||||
punktort_layer = lade_punktort_ta(
|
punktort_layer = lade_punktort_ta(
|
||||||
@@ -569,20 +645,22 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
if ergebnis_layer and ergebnis_layer.isValid():
|
if ergebnis_layer and ergebnis_layer.isValid():
|
||||||
project.addMapLayer(ergebnis_layer)
|
project.addMapLayer(ergebnis_layer)
|
||||||
|
|
||||||
if not silent:
|
show_info_dialog(
|
||||||
show_info_dialog(
|
"Detailprüfung abgeschlossen",
|
||||||
"Detailprüfung abgeschlossen",
|
f"Knickpunkte geprüft: {n_knick}\n"
|
||||||
f"Knickpunkte geprüft: {n_knick}\n"
|
f" Identisch (grün): {identisch}\n"
|
||||||
f" Identisch (grün): {identisch}\n"
|
f" Kein PunktortTA (rot): {kein_pta}\n"
|
||||||
f" Kein PunktortTA (rot): {kein_pta}\n"
|
f" Fehlende Knickpunkte (orange): {fehlender_knick}",
|
||||||
f" Fehlende Knickpunkte (orange): {fehlender_knick}",
|
parent=tab_widget,
|
||||||
parent=tab_widget,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
abgeschlossen = True
|
abgeschlossen = True
|
||||||
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
# Dialog 2 (falls erstellt) bei jedem Ausgang (Erfolg, Abbruch, Fehler) schließen
|
||||||
|
if progress is not None:
|
||||||
|
progress.close()
|
||||||
# WFS-Temp-Layer stets aus dem Projekt entfernen
|
# WFS-Temp-Layer stets aus dem Projekt entfernen
|
||||||
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
||||||
# BROR-Layer bei Fehler/Abbruch entfernen (nur wenn in diesem Lauf hinzugefügt)
|
# BROR-Layer bei Fehler/Abbruch entfernen (nur wenn in diesem Lauf hinzugefügt)
|
||||||
|
|||||||
@@ -0,0 +1,296 @@
|
|||||||
|
"""sn_verfahrensgebiet/functions/verfahrensgebiet_dialoge.py – Dialoge zum
|
||||||
|
Laden eines Verfahrensgebiets aus ALKIS (Qt5/6/Mock-kompatibel).
|
||||||
|
"""
|
||||||
|
from typing import Any, Optional
|
||||||
|
from sn_basis.functions.qt_wrapper import (
|
||||||
|
QT_VERSION, exec_dialog, QInputDialog, QLineEdit,
|
||||||
|
QDialog, QDialogButtonBox, QListWidget, QListWidgetItem, QAbstractItemView,
|
||||||
|
QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange,
|
||||||
|
QIcon, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox,
|
||||||
|
DialogAccepted, ExtendedSelection, SelectRows, NoEditTriggers, AscendingOrder,
|
||||||
|
)
|
||||||
|
from sn_basis.functions.landkreise import ALLE_LANDKREISE
|
||||||
|
import os
|
||||||
|
|
||||||
|
# .../python/plugins (drei Ebenen über dieser Datei)
|
||||||
|
_PLUGINS_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
_WAPPEN_ICON = os.path.join(_PLUGINS_DIR, "sn_basis", "favicon.ico")
|
||||||
|
|
||||||
|
|
||||||
|
def ask_bror_kombinationen(
|
||||||
|
parent: Any,
|
||||||
|
kombinationen: list[tuple[str, str]],
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
"""Zeigt Dialog zur Auswahl einer oder mehrerer BROR-Kombinationen
|
||||||
|
(adv_name + bezeichnung).
|
||||||
|
|
||||||
|
Wird aufgerufen, wenn der WFS mehrere verschiedene Kombinationen aus
|
||||||
|
``adv_name`` und ``bezeichnung`` zurückliefert. Es können ein oder
|
||||||
|
mehrere Objekte gemeinsam ausgewählt und geladen (verschmolzen) werden.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
kombinationen :
|
||||||
|
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[tuple[str, str]]
|
||||||
|
Die gewählten ``(adv_name, bezeichnung)``-Tupel. Leere Liste bei
|
||||||
|
Abbruch oder wenn nichts ausgewählt wurde.
|
||||||
|
"""
|
||||||
|
if not kombinationen:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print(f"Mock-Modus: ask_bror_kombinationen → {kombinationen[:1]}")
|
||||||
|
return kombinationen[:1]
|
||||||
|
|
||||||
|
dialog = QDialog(parent)
|
||||||
|
dialog.setWindowTitle("Verfahrensgebiet aus ALKIS laden")
|
||||||
|
if os.path.exists(_WAPPEN_ICON):
|
||||||
|
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
|
hinweis = QLabel(
|
||||||
|
f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n"
|
||||||
|
"Bitte wählen Sie eines oder mehrere Objekte aus:"
|
||||||
|
)
|
||||||
|
hinweis.setWordWrap(True)
|
||||||
|
layout.addWidget(hinweis)
|
||||||
|
|
||||||
|
list_widget = QListWidget()
|
||||||
|
list_widget.setSelectionMode(ExtendedSelection)
|
||||||
|
for adv, bez in kombinationen:
|
||||||
|
item = QListWidgetItem(f"{adv} | {bez}")
|
||||||
|
list_widget.addItem(item)
|
||||||
|
list_widget.item(0).setSelected(True)
|
||||||
|
layout.addWidget(list_widget, 1) # Stretch-Faktor: wächst mit Fenstergröße
|
||||||
|
|
||||||
|
mehrfachauswahl_hinweis = QLabel("<small>Mehrfachauswahl mit Strg + Umschalt</small>")
|
||||||
|
layout.addWidget(mehrfachauswahl_hinweis)
|
||||||
|
|
||||||
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||||
|
buttons.accepted.connect(dialog.accept)
|
||||||
|
buttons.rejected.connect(dialog.reject)
|
||||||
|
layout.addWidget(buttons)
|
||||||
|
|
||||||
|
dialog.adjustSize()
|
||||||
|
breite = dialog.sizeHint().width()
|
||||||
|
|
||||||
|
# Liste auf max. 5 sichtbare Zeilen begrenzen, Rest per Scrollbar
|
||||||
|
sichtbare_zeilen = min(len(kombinationen), 5)
|
||||||
|
zeilenhoehe = list_widget.sizeHintForRow(0) if list_widget.count() else 20
|
||||||
|
# + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen
|
||||||
|
# Einträgen), damit diese nicht die letzte Zeile abschneidet.
|
||||||
|
scrollbar_puffer = list_widget.horizontalScrollBar().sizeHint().height()
|
||||||
|
list_hoehe = zeilenhoehe * sichtbare_zeilen + 2 * list_widget.frameWidth() + 4 + scrollbar_puffer
|
||||||
|
list_widget.setMinimumHeight(list_hoehe)
|
||||||
|
|
||||||
|
dialog.resize(breite, dialog.sizeHint().height())
|
||||||
|
|
||||||
|
if exec_dialog(dialog) != DialogAccepted:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ausgewaehlte_indizes = sorted(list_widget.row(item) for item in list_widget.selectedItems())
|
||||||
|
return [kombinationen[i] for i in ausgewaehlte_indizes]
|
||||||
|
|
||||||
|
|
||||||
|
def ask_landkreis_objekte(
|
||||||
|
parent: Any,
|
||||||
|
kombinationen: list[tuple[str, str]],
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
"""Zeigt Dialog zur Auswahl eines, mehrerer oder aller BROR-Objekte eines
|
||||||
|
Landkreises (Fallback, wenn keine Verfahrensnummer gesetzt ist).
|
||||||
|
|
||||||
|
Wie :func:`ask_bror_kombinationen`, zusätzlich mit einer "Alle
|
||||||
|
auswählen"-Checkbox (da hier potenziell deutlich mehr Treffer als bei der
|
||||||
|
VKZ-Suche auftreten). Ob nach dem Laden der weitere Workflow
|
||||||
|
(Gemarkung/Flurstücke/Flächenvergleich) fortgesetzt wird, klärt eine
|
||||||
|
separate Stop-Abfrage nach dem Laden – nicht mehr dieser Dialog.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
kombinationen :
|
||||||
|
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list[tuple[str, str]]
|
||||||
|
Die gewählten ``(adv_name, bezeichnung)``-Tupel. Leere Liste bei
|
||||||
|
Abbruch oder wenn nichts ausgewählt wurde.
|
||||||
|
"""
|
||||||
|
if not kombinationen:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print(f"Mock-Modus: ask_landkreis_objekte → {kombinationen[:1]}")
|
||||||
|
return kombinationen[:1]
|
||||||
|
|
||||||
|
dialog = QDialog(parent)
|
||||||
|
dialog.setWindowTitle("Verfahrensgebiete aus Landkreis laden")
|
||||||
|
if os.path.exists(_WAPPEN_ICON):
|
||||||
|
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
|
||||||
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
|
hinweis = QLabel(
|
||||||
|
f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n"
|
||||||
|
"Bitte wählen Sie eines oder mehrere Objekte aus:"
|
||||||
|
)
|
||||||
|
hinweis.setWordWrap(True)
|
||||||
|
layout.addWidget(hinweis)
|
||||||
|
|
||||||
|
suche_zeile = QHBoxLayout()
|
||||||
|
suche_zeile.addWidget(QLabel("Suche:"))
|
||||||
|
suche_feld = QLineEdit()
|
||||||
|
suche_feld.setPlaceholderText("Nummer oder Name …")
|
||||||
|
suche_zeile.addWidget(suche_feld)
|
||||||
|
layout.addLayout(suche_zeile)
|
||||||
|
|
||||||
|
alle_auswaehlen = QCheckBox("Alle auswählen")
|
||||||
|
layout.addWidget(alle_auswaehlen)
|
||||||
|
|
||||||
|
# Standardsortierung nach Bezeichnung; per Klick auf Spaltenkopf änderbar
|
||||||
|
# (setSortingEnabled aktiviert die eingebaute Tabellensortierung).
|
||||||
|
sortierte_kombinationen = sorted(kombinationen, key=lambda kb: kb[1])
|
||||||
|
|
||||||
|
table = QTableWidget(len(sortierte_kombinationen), 2)
|
||||||
|
table.setHorizontalHeaderLabels(["Nummer", "Name"])
|
||||||
|
table.setSelectionMode(ExtendedSelection)
|
||||||
|
table.setSelectionBehavior(SelectRows)
|
||||||
|
table.setEditTriggers(NoEditTriggers)
|
||||||
|
table.verticalHeader().setVisible(False)
|
||||||
|
table.verticalHeader().setDefaultSectionSize(22) # flachere Zeilen
|
||||||
|
table.horizontalHeader().setStretchLastSection(True)
|
||||||
|
table.setSortingEnabled(False)
|
||||||
|
for row, (adv, bez) in enumerate(sortierte_kombinationen):
|
||||||
|
table.setItem(row, 0, QTableWidgetItem(bez))
|
||||||
|
table.setItem(row, 1, QTableWidgetItem(adv))
|
||||||
|
table.setSortingEnabled(True)
|
||||||
|
table.sortByColumn(0, AscendingOrder) # Standard: aufsteigend nach Nummer
|
||||||
|
table.resizeColumnToContents(0)
|
||||||
|
table.setColumnWidth(0, max(110, table.columnWidth(0)))
|
||||||
|
table.selectRow(0)
|
||||||
|
layout.addWidget(table, 1) # Stretch-Faktor: wächst mit Fenstergröße
|
||||||
|
|
||||||
|
def _suche_geaendert(text):
|
||||||
|
text_lower = text.strip().lower()
|
||||||
|
for row in range(table.rowCount()):
|
||||||
|
bez_text = table.item(row, 0).text().lower()
|
||||||
|
adv_text = table.item(row, 1).text().lower()
|
||||||
|
treffer = not text_lower or text_lower in bez_text or text_lower in adv_text
|
||||||
|
table.setRowHidden(row, not treffer)
|
||||||
|
|
||||||
|
suche_feld.textChanged.connect(_suche_geaendert)
|
||||||
|
|
||||||
|
def _alle_auswaehlen_umschalten(checked):
|
||||||
|
table.clearSelection()
|
||||||
|
if checked:
|
||||||
|
letzte_spalte = table.columnCount() - 1
|
||||||
|
for row in range(table.rowCount()):
|
||||||
|
if not table.isRowHidden(row):
|
||||||
|
# setRangeSelected (statt selectRow) fügt der Selektion
|
||||||
|
# hinzu, ohne vorherige Zeilen wieder abzuwählen.
|
||||||
|
table.setRangeSelected(
|
||||||
|
QTableWidgetSelectionRange(row, 0, row, letzte_spalte), True
|
||||||
|
)
|
||||||
|
|
||||||
|
alle_auswaehlen.stateChanged.connect(_alle_auswaehlen_umschalten)
|
||||||
|
|
||||||
|
mehrfachauswahl_hinweis = QLabel("<small>Mehrfachauswahl mit Strg + Umschalt</small>")
|
||||||
|
layout.addWidget(mehrfachauswahl_hinweis)
|
||||||
|
|
||||||
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||||
|
buttons.accepted.connect(dialog.accept)
|
||||||
|
buttons.rejected.connect(dialog.reject)
|
||||||
|
layout.addWidget(buttons)
|
||||||
|
|
||||||
|
dialog.adjustSize()
|
||||||
|
# Bezeichnungs-Spalte + großzügige Breite für den ADV-Namen (lange Namen
|
||||||
|
# sollen möglichst ohne Abschneiden sichtbar sein) + Rahmen/Scrollbar-Puffer.
|
||||||
|
breite = max(dialog.sizeHint().width(), table.columnWidth(0) + 480)
|
||||||
|
|
||||||
|
# Tabelle auf max. 15 sichtbare Zeilen begrenzen, Rest per Scrollbar – hier
|
||||||
|
# potenziell deutlich mehr Treffer als bei der VKZ-Suche, daher großzügiger
|
||||||
|
# bemessen als bei ask_bror_kombinationen (dort 5).
|
||||||
|
sichtbare_zeilen = min(len(sortierte_kombinationen), 15)
|
||||||
|
zeilenhoehe = table.verticalHeader().defaultSectionSize() if table.rowCount() else 20
|
||||||
|
kopfzeile_hoehe = table.horizontalHeader().height()
|
||||||
|
# + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen
|
||||||
|
# Einträgen), damit diese nicht die letzte Zeile abschneidet.
|
||||||
|
scrollbar_puffer = table.horizontalScrollBar().sizeHint().height()
|
||||||
|
table_hoehe = (
|
||||||
|
zeilenhoehe * sichtbare_zeilen + kopfzeile_hoehe
|
||||||
|
+ 2 * table.frameWidth() + 4 + scrollbar_puffer
|
||||||
|
)
|
||||||
|
table.setMinimumHeight(table_hoehe)
|
||||||
|
|
||||||
|
dialog.resize(breite, dialog.sizeHint().height())
|
||||||
|
|
||||||
|
if exec_dialog(dialog) != DialogAccepted:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ausgewaehlte_zeilen = sorted(set(item.row() for item in table.selectedItems()))
|
||||||
|
ausgewaehlt = [(table.item(row, 1).text(), table.item(row, 0).text()) for row in ausgewaehlte_zeilen]
|
||||||
|
return ausgewaehlt
|
||||||
|
|
||||||
|
|
||||||
|
def ask_landkreis_auswahl(
|
||||||
|
parent: Any,
|
||||||
|
landkreise: list[tuple[str, str]],
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Fragt per Dropdown-Dialog einen Landkreis ab (Fallback, wenn weder
|
||||||
|
Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt ist).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent :
|
||||||
|
Eltern-Widget oder None.
|
||||||
|
landkreise :
|
||||||
|
Liste von ``(kennzahl, name)``-Tupeln, z.B. ``LANDKREISE`` aus
|
||||||
|
:mod:`sn_basis.functions.landkreise`.
|
||||||
|
|
||||||
|
Die Auswahlliste beginnt mit einem leeren Platzhalter (der Nutzer muss
|
||||||
|
aktiv einen Eintrag wählen) und enthält zusätzlich "Alle Landkreise"
|
||||||
|
(Sentinel :data:`~sn_basis.functions.landkreise.ALLE_LANDKREISE`).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Optional[str]
|
||||||
|
Gewählte Landkreiskennzahl, :data:`ALLE_LANDKREISE` bei Auswahl aller
|
||||||
|
Landkreise, oder ``None`` bei Abbruch, leerem Platzhalter oder leerer
|
||||||
|
Landkreisliste.
|
||||||
|
"""
|
||||||
|
if not landkreise:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if QT_VERSION == 0: # Mock-Modus
|
||||||
|
print("Mock-Modus: ask_landkreis_auswahl → None (keine aktive Auswahl)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
ALLE_LABEL = "Alle Landkreise"
|
||||||
|
namen = [""] + [ALLE_LABEL] + [name for _, name in landkreise]
|
||||||
|
|
||||||
|
dialog = QInputDialog(parent)
|
||||||
|
dialog.setWindowTitle("Landkreis auswählen")
|
||||||
|
if os.path.exists(_WAPPEN_ICON):
|
||||||
|
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
|
||||||
|
dialog.setLabelText("Landkreis:")
|
||||||
|
dialog.setComboBoxItems(namen)
|
||||||
|
dialog.setComboBoxEditable(False)
|
||||||
|
|
||||||
|
if exec_dialog(dialog) != DialogAccepted:
|
||||||
|
return None
|
||||||
|
|
||||||
|
name = dialog.textValue()
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
if name == ALLE_LABEL:
|
||||||
|
return ALLE_LANDKREISE
|
||||||
|
|
||||||
|
name_zu_kennzahl = {n: k for k, n in landkreise}
|
||||||
|
return name_zu_kennzahl.get(str(name))
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets
|
Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets
|
||||||
aus einem ZIP-Archiv mit Shapefile-Set:
|
aus einem ZIP-Archiv mit Shapefile-Set:
|
||||||
|
|
||||||
1. Dateidialog öffnen (.zip-Filter); Nutzerabbruch beendet den Vorgang.
|
1. Dateidialog öffnen (.zip/.shp-Filter, Mehrfachauswahl); Nutzerabbruch
|
||||||
2. ZIP neben der ZIP-Datei entpacken und auf ein gültiges Shapefile-Set prüfen.
|
beendet den Vorgang.
|
||||||
|
2. ZIP in ein temporäres Verzeichnis entpacken (danach wieder gelöscht) und
|
||||||
|
auf ein gültiges Shapefile-Set prüfen.
|
||||||
3. Shapefile als QGIS-Vektorlayer laden und validieren (Standard-QGIS-Routinen).
|
3. Shapefile als QGIS-Vektorlayer laden und validieren (Standard-QGIS-Routinen).
|
||||||
4. Alle Features per ``QgsGeometry.unaryUnion`` zu einem Single-Feature-
|
4. Alle Features per ``QgsGeometry.unaryUnion`` zu einem Single-Feature-
|
||||||
Memory-Layer „Verfahrensgebiet" verschmelzen.
|
Memory-Layer „Verfahrensgebiet" verschmelzen.
|
||||||
@@ -40,6 +42,9 @@ oder kein Ergebnis liefert, bricht den Workflow ab.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
@@ -53,6 +58,7 @@ from sn_basis.functions.dialog_wrapper import (
|
|||||||
create_progress_dialog,
|
create_progress_dialog,
|
||||||
show_info_dialog,
|
show_info_dialog,
|
||||||
ask_detailpruefung_oder_abbrechen,
|
ask_detailpruefung_oder_abbrechen,
|
||||||
|
ask_yes_no,
|
||||||
)
|
)
|
||||||
|
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus, verfahrensgebiet_alkis
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus, verfahrensgebiet_alkis
|
||||||
@@ -79,11 +85,129 @@ from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
|||||||
BROR_LAYER_NAME,
|
BROR_LAYER_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#: Feldname im Shapefile, über den der Gebietstyp bestimmt wird.
|
||||||
|
FBSCHL_FELD = "FBSchl"
|
||||||
|
#: FBSchl-Wert für ein Neuordnungsgebiet.
|
||||||
|
FBSCHL_NEUORDNUNGSGEBIET = "901/10110F"
|
||||||
|
#: FBSchl-Wert für ein Neuvermessungsgebiet.
|
||||||
|
FBSCHL_NEUVERMESSUNGSGEBIET = "901/10120F"
|
||||||
|
|
||||||
|
#: Anzeigename des Layers für ein Neuordnungsgebiet (NoBIS).
|
||||||
|
NEUORDNUNGSGEBIET_LAYER_NAME = "Neuordnungsgebiet (NoBIS)"
|
||||||
|
#: Anzeigename des Layers für ein Neuvermessungsgebiet (NoBIS).
|
||||||
|
NEUVERMESSUNGSGEBIET_LAYER_NAME = "Neuvermessungsgebiet (NoBIS)"
|
||||||
|
#: Zuordnung Gebietstyp → Layername.
|
||||||
|
_GEBIETSTYP_LAYER_NAMEN = {
|
||||||
|
"Neuordnungsgebiet": NEUORDNUNGSGEBIET_LAYER_NAME,
|
||||||
|
"Neuvermessungsgebiet": NEUVERMESSUNGSGEBIET_LAYER_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Interne Hilfsfunktionen
|
# Interne Hilfsfunktionen
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _normalisiere_feldname(name: str) -> str:
|
||||||
|
"""Normalisiert einen Feldnamen für den toleranten Vergleich.
|
||||||
|
|
||||||
|
Entfernt alle Zeichen außer Buchstaben/Ziffern (z.B. Punkte, Leerzeichen,
|
||||||
|
Unterstriche) und wandelt in Kleinbuchstaben um. Hintergrund: manche
|
||||||
|
Shape-Exporte liefern den Feldnamen ``FBSchl`` mit einem angehängten
|
||||||
|
Punkt (``FBSchl.``) oder abweichender Groß-/Kleinschreibung, wodurch ein
|
||||||
|
exakter Namensvergleich das Feld fälschlich als fehlend meldet (#44).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name :
|
||||||
|
Roher Feldname.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
Normalisierter Feldname (nur Kleinbuchstaben/Ziffern).
|
||||||
|
"""
|
||||||
|
return re.sub(r"[^a-zA-Z0-9]", "", name).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def _finde_fbschl_index(raw_layer) -> int:
|
||||||
|
"""Sucht den Feldindex von ``FBSchl`` tolerant gegenüber Abweichungen im
|
||||||
|
Namen (Groß-/Kleinschreibung, angehängte Sonderzeichen wie ein Punkt).
|
||||||
|
|
||||||
|
Versucht zuerst den exakten Namen; schlägt das fehl, wird über alle
|
||||||
|
Felder mit normalisiertem Namensvergleich gesucht (siehe
|
||||||
|
:func:`_normalisiere_feldname`).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
raw_layer :
|
||||||
|
Dateibasierter Shapefile-Layer.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
Feldindex, oder ``-1`` wenn kein passendes Feld gefunden wurde.
|
||||||
|
"""
|
||||||
|
idx = raw_layer.fields().indexFromName(FBSCHL_FELD)
|
||||||
|
if idx >= 0:
|
||||||
|
return idx
|
||||||
|
|
||||||
|
ziel = _normalisiere_feldname(FBSCHL_FELD)
|
||||||
|
for feld_idx, feld in enumerate(raw_layer.fields()):
|
||||||
|
if _normalisiere_feldname(feld.name()) == ziel:
|
||||||
|
return feld_idx
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def _ermittle_gebietstyp_aus_fbschl(raw_layer) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""Prüft das Feld ``FBSchl`` und ermittelt den Gebietstyp.
|
||||||
|
|
||||||
|
Zeigt selbst keine Fehlermeldung an, sondern gibt einen Fehlertext
|
||||||
|
zurück, damit der Aufrufer Fehler mehrerer Dateien sammeln und am Ende
|
||||||
|
gemeinsam anzeigen kann.
|
||||||
|
|
||||||
|
Die Feldsuche ist tolerant gegenüber Abweichungen im Namen (z.B. ein
|
||||||
|
angehängter Punkt ``FBSchl.`` in manchen Shape-Exporten, siehe #44) –
|
||||||
|
siehe :func:`_finde_fbschl_index`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
raw_layer :
|
||||||
|
Dateibasierter Shapefile-Layer (vor dem Verschmelzen zum Memory-Layer).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[Optional[str], Optional[str]]
|
||||||
|
``(gebietstyp, fehlertext)``. Bei Erfolg ist ``gebietstyp``
|
||||||
|
``"Neuordnungsgebiet"`` oder ``"Neuvermessungsgebiet"`` und
|
||||||
|
``fehlertext`` ``None``. Bei Fehler ist ``gebietstyp`` ``None`` und
|
||||||
|
``fehlertext`` beschreibt die Ursache (fehlendes Feld oder
|
||||||
|
unbekannter Wert).
|
||||||
|
"""
|
||||||
|
idx = _finde_fbschl_index(raw_layer)
|
||||||
|
if idx < 0:
|
||||||
|
return None, f"Feld '{FBSCHL_FELD}' fehlt (Shape entspricht nicht dem erwarteten Inhalt)."
|
||||||
|
|
||||||
|
wert = None
|
||||||
|
for feat in raw_layer.getFeatures():
|
||||||
|
try:
|
||||||
|
roh = feat.attribute(idx)
|
||||||
|
except Exception:
|
||||||
|
roh = None
|
||||||
|
if roh not in (None, ""):
|
||||||
|
wert = str(roh).strip()
|
||||||
|
break
|
||||||
|
|
||||||
|
if wert == FBSCHL_NEUORDNUNGSGEBIET:
|
||||||
|
return "Neuordnungsgebiet", None
|
||||||
|
if wert == FBSCHL_NEUVERMESSUNGSGEBIET:
|
||||||
|
return "Neuvermessungsgebiet", None
|
||||||
|
|
||||||
|
return None, (
|
||||||
|
f"Unbekannter Wert im Feld '{FBSCHL_FELD}': {wert!r} "
|
||||||
|
f"(erwartet: '{FBSCHL_NEUORDNUNGSGEBIET}' oder "
|
||||||
|
f"'{FBSCHL_NEUVERMESSUNGSGEBIET}')."
|
||||||
|
)
|
||||||
|
|
||||||
def _wende_vergleich_stil_an(layer) -> None:
|
def _wende_vergleich_stil_an(layer) -> None:
|
||||||
"""Wendet einen kategorisierten Renderer auf den Vergleich-Layer an.
|
"""Wendet einen kategorisierten Renderer auf den Vergleich-Layer an.
|
||||||
|
|
||||||
@@ -215,6 +339,31 @@ def _finde_shp(verzeichnis: str) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _finde_alle_shp(verzeichnis: str) -> list:
|
||||||
|
"""Sucht rekursiv nach allen ``.shp``-Dateien im Verzeichnis.
|
||||||
|
|
||||||
|
Ein ZIP-Archiv kann mehrere Shapefile-Sets enthalten (z.B. je eines für
|
||||||
|
Neuordnungs- und Neuvermessungsgebiet) – im Unterschied zu
|
||||||
|
:func:`_finde_shp` werden hier alle gefunden, nicht nur die erste.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
verzeichnis :
|
||||||
|
Startverzeichnis für die Suche.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list
|
||||||
|
Sortierte Liste der absoluten Pfade aller gefundenen ``.shp``-Dateien.
|
||||||
|
"""
|
||||||
|
treffer = []
|
||||||
|
for root, _dirs, files in os.walk(verzeichnis):
|
||||||
|
for fname in files:
|
||||||
|
if fname.lower().endswith(".shp"):
|
||||||
|
treffer.append(os.path.join(root, fname))
|
||||||
|
return sorted(treffer)
|
||||||
|
|
||||||
|
|
||||||
def _erstelle_memory_layer_aus_shp(shp_layer) -> Optional[object]:
|
def _erstelle_memory_layer_aus_shp(shp_layer) -> Optional[object]:
|
||||||
"""Vereint alle Features des Shapefile-Layers zu einem Single-Feature-
|
"""Vereint alle Features des Shapefile-Layers zu einem Single-Feature-
|
||||||
Memory-Layer „Verfahrensgebiet" via ``QgsGeometry.unaryUnion``.
|
Memory-Layer „Verfahrensgebiet" via ``QgsGeometry.unaryUnion``.
|
||||||
@@ -290,17 +439,38 @@ def _erstelle_memory_layer_aus_shp(shp_layer) -> Optional[object]:
|
|||||||
# Öffentliche Lade-Hilfsfunktion
|
# Öffentliche Lade-Hilfsfunktion
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def lade_vg_aus_zip(parent_widget) -> Optional[object]:
|
def lade_vg_aus_zip(parent_widget) -> Optional[list]:
|
||||||
"""Öffnet Dateidialog, entpackt ZIP und gibt Union-Memory-Layer zurück.
|
"""Öffnet Dateidialog (Mehrfachauswahl), entpackt ZIPs (bzw. nutzt direkt
|
||||||
|
gewählte ``.shp``-Dateien) und liefert je Gebietstyp einen Union-Memory-Layer.
|
||||||
|
|
||||||
Schrittfolge:
|
Schrittfolge:
|
||||||
|
|
||||||
1. ``QFileDialog.getOpenFileName`` mit Filter ``ZIP-Dateien (*.zip)``.
|
1. ``QFileDialog.getOpenFileNames`` mit Filter für ``.zip`` und ``.shp``
|
||||||
2. ZIP neben der Quelldatei in ein Unterverzeichnis entpacken.
|
(Mehrfachauswahl möglich).
|
||||||
3. Erste ``.shp``-Datei im Verzeichnis finden.
|
2. Für jede ausgewählte Datei: bei ``.zip`` in ein temporäres Verzeichnis
|
||||||
4. Als ``QgsVectorLayer`` laden und ``isValid()`` prüfen.
|
entpacken (danach wieder gelöscht) und darin **alle** enthaltenen
|
||||||
5. Alle Features per ``QgsGeometry.unaryUnion`` verschmelzen.
|
``.shp``-Dateien finden (ein ZIP kann mehrere Shapefile-Sets
|
||||||
6. Single-Feature-Memory-Layer „Verfahrensgebiet" zurückgeben.
|
bündeln, z.B. je eines für Neuordnungs- und Neuvermessungsgebiet);
|
||||||
|
bei ``.shp`` die Datei direkt verwenden.
|
||||||
|
3. Jede gefundene Shapefile-Datei einzeln als ``QgsVectorLayer`` laden
|
||||||
|
und ``isValid()`` prüfen.
|
||||||
|
4. Je Shapefile-Datei das Feld ``FBSchl`` prüfen und den Gebietstyp
|
||||||
|
ermitteln (Neuordnungs- bzw. Neuvermessungsgebiet).
|
||||||
|
5. Die Features aller Dateien **desselben Gebietstyps** werden gemeinsam
|
||||||
|
per ``QgsGeometry.unaryUnion`` zu einem Single-Feature-Memory-Layer
|
||||||
|
verschmolzen – d.h. mehrere Neuordnungsgebiet-Dateien ergeben einen
|
||||||
|
Layer, mehrere Neuvermessungsgebiet-Dateien einen zweiten.
|
||||||
|
|
||||||
|
Fehlerverhalten: Schlägt die Prüfung eines einzelnen Shapefiles fehl
|
||||||
|
(ZIP nicht entpackbar, kein Shapefile enthalten, ungültiger Layer, Feld
|
||||||
|
``FBSchl`` fehlt oder hat einen unbekannten Wert), wird **nur dieses
|
||||||
|
Shapefile** übersprungen und sein Fehler gesammelt – das gilt auch,
|
||||||
|
wenn mehrere Shapefiles in derselben ZIP-Datei liegen: eine fehlerhafte
|
||||||
|
Datei darin bricht nicht die Verarbeitung der übrigen ab. Alle
|
||||||
|
verwertbaren Dateien werden gemäß Vorgabe eingelesen. Am Ende erscheint
|
||||||
|
eine gesammelte Fehlermeldung mit allen fehlerhaften Dateien. Erst wenn
|
||||||
|
**keine** Datei verwertbare Geometrien liefert, wird ``None``
|
||||||
|
zurückgegeben.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -309,62 +479,173 @@ def lade_vg_aus_zip(parent_widget) -> Optional[object]:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Optional[QgsVectorLayer]
|
Optional[list]
|
||||||
Valider Memory-Layer „Verfahrensgebiet" oder ``None`` bei Abbruch/Fehler.
|
Liste der erzeugten Memory-Layer (ein Layer je in der Auswahl
|
||||||
|
vorkommendem, erfolgreich geprüftem Gebietstyp, max. 2), oder
|
||||||
|
``None`` wenn keine Datei verwertet werden konnte oder der Dialog
|
||||||
|
abgebrochen wurde.
|
||||||
"""
|
"""
|
||||||
pfad, _ = QFileDialog.getOpenFileName(
|
pfade, _ = QFileDialog.getOpenFileNames(
|
||||||
parent_widget,
|
parent_widget,
|
||||||
"ZIP-Datei mit Shapefile-Set auswählen",
|
"Shapefile-Set(s) (ZIP oder .shp) auswählen",
|
||||||
"",
|
"",
|
||||||
"ZIP-Dateien (*.zip)",
|
"Shapefile-Set (*.zip *.shp);;ZIP-Dateien (*.zip);;Shapefile (*.shp)",
|
||||||
)
|
)
|
||||||
if not pfad:
|
if not pfade:
|
||||||
return None # Nutzerabbruch
|
return None # Nutzerabbruch
|
||||||
|
|
||||||
zip_dir = os.path.dirname(pfad)
|
# Je Gebietstyp gesammelte Geometrien/Felder/Attribute über alle
|
||||||
zip_base = os.path.splitext(os.path.basename(pfad))[0]
|
# ausgewählten Dateien hinweg (Reihenfolge der ersten Sichtung bleibt
|
||||||
extract_dir = os.path.join(zip_dir, zip_base)
|
# über dict-Einfügereihenfolge erhalten).
|
||||||
|
geometrien_je_typ: dict = {}
|
||||||
|
felder_je_typ: dict = {}
|
||||||
|
attribute_je_typ: dict = {}
|
||||||
|
crs_je_typ: dict = {}
|
||||||
|
temp_dirs: list = []
|
||||||
|
fehler_liste: list = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(pfad, "r") as zf:
|
# (shp_path, anzeige_name) für jede tatsächlich zu prüfende
|
||||||
zf.extractall(extract_dir)
|
# Shapefile-Datei sammeln – ein ZIP kann mehrere Shapefile-Sets
|
||||||
except Exception:
|
# enthalten, jedes wird einzeln behandelt.
|
||||||
QMessageBox.warning(
|
shp_eintraege: list = []
|
||||||
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)
|
for pfad in pfade:
|
||||||
if not shp_path:
|
dateiname = os.path.basename(pfad)
|
||||||
QMessageBox.warning(
|
|
||||||
parent_widget,
|
|
||||||
"Fehler",
|
|
||||||
"gültiges Shape-Dateiset angeben",
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
raw_layer = QgsVectorLayer(shp_path, "tmp_shape_vg", "ogr")
|
if pfad.lower().endswith(".shp"):
|
||||||
if not raw_layer or not raw_layer.isValid():
|
shp_eintraege.append((pfad, dateiname))
|
||||||
QMessageBox.warning(
|
continue
|
||||||
parent_widget,
|
|
||||||
"Fehler",
|
|
||||||
"gültiges Shape-Dateiset angeben",
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
mem_layer = _erstelle_memory_layer_aus_shp(raw_layer)
|
extract_dir = tempfile.mkdtemp(prefix="sn_vg_shape_")
|
||||||
if mem_layer is None:
|
temp_dirs.append(extract_dir)
|
||||||
QMessageBox.warning(
|
|
||||||
parent_widget,
|
|
||||||
"Fehler",
|
|
||||||
"gültiges Shape-Dateiset angeben",
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return mem_layer
|
try:
|
||||||
|
with zipfile.ZipFile(pfad, "r") as zf:
|
||||||
|
zf.extractall(extract_dir)
|
||||||
|
except Exception:
|
||||||
|
fehler_liste.append(
|
||||||
|
f"{dateiname}: ZIP-Datei konnte nicht entpackt werden."
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
shp_pfade_im_zip = _finde_alle_shp(extract_dir)
|
||||||
|
if not shp_pfade_im_zip:
|
||||||
|
fehler_liste.append(
|
||||||
|
f"{dateiname}: kein gültiges Shape-Dateiset enthalten."
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for shp_path in shp_pfade_im_zip:
|
||||||
|
anzeige_name = (
|
||||||
|
f"{dateiname} ({os.path.basename(shp_path)})"
|
||||||
|
if len(shp_pfade_im_zip) > 1
|
||||||
|
else dateiname
|
||||||
|
)
|
||||||
|
shp_eintraege.append((shp_path, anzeige_name))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Jede gefundene Shapefile-Datei einzeln prüfen und einlesen.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
for shp_path, anzeige_name in shp_eintraege:
|
||||||
|
raw_layer = QgsVectorLayer(shp_path, "tmp_shape_vg", "ogr")
|
||||||
|
if not raw_layer or not raw_layer.isValid():
|
||||||
|
fehler_liste.append(
|
||||||
|
f"{anzeige_name}: kein gültiges Shape-Dateiset."
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
gebietstyp, fehlertext = _ermittle_gebietstyp_aus_fbschl(raw_layer)
|
||||||
|
if gebietstyp is None:
|
||||||
|
fehler_liste.append(f"{anzeige_name}: {fehlertext}")
|
||||||
|
del raw_layer
|
||||||
|
continue
|
||||||
|
|
||||||
|
geom_list = []
|
||||||
|
first_feat = None
|
||||||
|
for feat in raw_layer.getFeatures():
|
||||||
|
try:
|
||||||
|
geom = feat.geometry()
|
||||||
|
except Exception:
|
||||||
|
geom = None
|
||||||
|
if geom and not geom.isEmpty():
|
||||||
|
geom_list.append(geom)
|
||||||
|
if first_feat is None:
|
||||||
|
first_feat = feat
|
||||||
|
|
||||||
|
if geom_list:
|
||||||
|
geometrien_je_typ.setdefault(gebietstyp, []).extend(geom_list)
|
||||||
|
if gebietstyp not in felder_je_typ:
|
||||||
|
felder_je_typ[gebietstyp] = list(raw_layer.fields())
|
||||||
|
attribute_je_typ[gebietstyp] = (
|
||||||
|
first_feat.attributes() if first_feat is not None else []
|
||||||
|
)
|
||||||
|
crs_je_typ[gebietstyp] = raw_layer.crs().authid()
|
||||||
|
else:
|
||||||
|
fehler_liste.append(f"{anzeige_name}: keine gültigen Geometrien enthalten.")
|
||||||
|
|
||||||
|
# Referenz auf den dateibasierten Layer freigeben, bevor das
|
||||||
|
# temporäre Verzeichnis gelöscht wird (Windows hält sonst ggf.
|
||||||
|
# noch einen Dateihandle auf das Shapefile).
|
||||||
|
del raw_layer
|
||||||
|
|
||||||
|
if fehler_liste:
|
||||||
|
meldung = (
|
||||||
|
"Folgende Dateien konnten nicht eingelesen werden:\n\n"
|
||||||
|
+ "\n".join(fehler_liste)
|
||||||
|
)
|
||||||
|
if geometrien_je_typ:
|
||||||
|
meldung += "\n\nDie übrigen Dateien wurden gemäß Vorgabe eingelesen."
|
||||||
|
QMessageBox.warning(parent_widget, "Fehler in ausgewählten Dateien", meldung)
|
||||||
|
|
||||||
|
if not geometrien_je_typ:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ergebnis_layer = []
|
||||||
|
for typ, geometrien in geometrien_je_typ.items():
|
||||||
|
union_geom = QgsGeometry.unaryUnion(geometrien)
|
||||||
|
if not union_geom or union_geom.isEmpty():
|
||||||
|
continue
|
||||||
|
|
||||||
|
mem_layer = QgsVectorLayer(
|
||||||
|
f"MultiPolygon?crs={crs_je_typ[typ]}",
|
||||||
|
_GEBIETSTYP_LAYER_NAMEN[typ],
|
||||||
|
"memory",
|
||||||
|
)
|
||||||
|
pr = mem_layer.dataProvider()
|
||||||
|
try:
|
||||||
|
pr.addAttributes(felder_je_typ[typ])
|
||||||
|
mem_layer.updateFields()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
feat_out = QgsFeature(mem_layer.fields())
|
||||||
|
feat_out.setGeometry(union_geom)
|
||||||
|
try:
|
||||||
|
feat_out.setAttributes(attribute_je_typ[typ])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
pr.addFeatures([feat_out])
|
||||||
|
mem_layer.updateExtents()
|
||||||
|
|
||||||
|
if mem_layer.isValid() and mem_layer.featureCount() > 0:
|
||||||
|
mem_layer.setCustomProperty("fb_gebietstyp", typ)
|
||||||
|
ergebnis_layer.append(mem_layer)
|
||||||
|
|
||||||
|
if not ergebnis_layer:
|
||||||
|
QMessageBox.warning(
|
||||||
|
parent_widget,
|
||||||
|
"Fehler",
|
||||||
|
"gültiges Shape-Dateiset angeben",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return ergebnis_layer
|
||||||
|
|
||||||
|
finally:
|
||||||
|
for d in temp_dirs:
|
||||||
|
shutil.rmtree(d, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -419,38 +700,66 @@ def verfahrensgebiet_shape_komplett(
|
|||||||
"""
|
"""
|
||||||
project = QgsProject.instance()
|
project = QgsProject.instance()
|
||||||
shape_vg_layer = None
|
shape_vg_layer = None
|
||||||
|
zusatz_shape_layer: list = []
|
||||||
bror_layer = None
|
bror_layer = None
|
||||||
shape_was_added_by_us = False
|
shape_was_added_by_us = False
|
||||||
bror_was_added_by_us = False
|
bror_was_added_by_us = False
|
||||||
abgeschlossen = False
|
abgeschlossen = False
|
||||||
|
|
||||||
progress = create_progress_dialog(
|
progress = None
|
||||||
7, "Verfahrensgebiet aus Shape laden", "Initialisierung …"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 1: Shape-VG aus ZIP laden (noch NICHT ins Projekt einfügen)
|
# Schritt 1: Shape-VG aus ZIP laden (noch NICHT ins Projekt einfügen)
|
||||||
|
# Läuft VOR dem Fortschrittsdialog, damit dieser nicht schon während
|
||||||
|
# des Dateidialogs sichtbar ist.
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 1/6: Verfahrensgebiet aus ZIP laden …")
|
shape_vg_layers = lade_vg_aus_zip(tab_widget)
|
||||||
progress.set_value(0)
|
|
||||||
if progress.is_canceled():
|
if not shape_vg_layers:
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
shape_vg_layer = lade_vg_aus_zip(tab_widget)
|
# Je Gebietstyp ein Layer (max. 2). Der erste ist das für den
|
||||||
progress.raise_to_front()
|
# weiteren Ablauf maßgebliche Shape-VG; etwaige weitere Layer
|
||||||
|
# (zweiter Gebietstyp) werden vorerst nur mit ins Projekt geladen –
|
||||||
|
# ihre Einbindung in den weiteren Workflow folgt in einem späteren
|
||||||
|
# Schritt.
|
||||||
|
shape_vg_layer = shape_vg_layers[0]
|
||||||
|
zusatz_shape_layer = shape_vg_layers[1:]
|
||||||
|
|
||||||
if not shape_vg_layer:
|
# TEMPORÄRER STOPP (wieder entfernen): Pause nach "Verfahrensgebiet
|
||||||
|
# aus ZIP laden" zur Zwischenkontrolle – Layer dafür schon jetzt
|
||||||
|
# sichtbar ins Projekt einfügen (normalerweise erst nach Schritt 2).
|
||||||
|
project.addMapLayer(shape_vg_layer)
|
||||||
|
shape_was_added_by_us = True
|
||||||
|
for lyr in zusatz_shape_layer:
|
||||||
|
project.addMapLayer(lyr)
|
||||||
|
|
||||||
|
geladene_namen = ", ".join(lyr.name() for lyr in shape_vg_layers)
|
||||||
|
if not ask_yes_no(
|
||||||
|
"Verfahrensgebiet aus ZIP laden",
|
||||||
|
f"Layer '{geladene_namen}' wurde(n) geladen. Weiter?",
|
||||||
|
default=True,
|
||||||
|
parent=tab_widget,
|
||||||
|
):
|
||||||
|
# abgeschlossen=True, damit der finally-Block den zur Kontrolle
|
||||||
|
# eingefügten Layer NICHT wieder entfernt (kein Fehlerfall).
|
||||||
|
abgeschlossen = True
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
vg_status = LoadStatus.FIRST
|
vg_status = LoadStatus.FIRST
|
||||||
|
|
||||||
|
# Fortschrittsdialog erst jetzt erzeugen (nach dem Stop) – er darf
|
||||||
|
# nicht schon während Dateidialog/Stop-Abfrage sichtbar sein.
|
||||||
|
progress = create_progress_dialog(
|
||||||
|
7, "Verfahrensgebiet aus Shape laden", "Schritt 2/6: BROR aus ALKIS laden …"
|
||||||
|
)
|
||||||
progress.set_value(1)
|
progress.set_value(1)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 2: BROR aus ALKIS laden (vor shape_vg, um Namenskonflikt
|
# Schritt 2: BROR aus ALKIS laden (vor shape_vg, um Namenskonflikt
|
||||||
# in _check_existing_layer zu vermeiden)
|
# in _check_existing_layer zu vermeiden)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
progress.set_label("Schritt 2/6: BROR aus ALKIS laden …")
|
|
||||||
if progress.is_canceled():
|
if progress.is_canceled():
|
||||||
return None, None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
@@ -468,9 +777,25 @@ def verfahrensgebiet_shape_komplett(
|
|||||||
|
|
||||||
progress.set_value(2)
|
progress.set_value(2)
|
||||||
|
|
||||||
# Shape-VG erst jetzt ins Projekt einfügen (nach BROR-Ladung)
|
# Shape-VG erst jetzt ins Projekt einfügen (nach BROR-Ladung) – außer
|
||||||
project.addMapLayer(shape_vg_layer)
|
# der TEMPORÄRE STOPP oben hat ihn schon eingefügt.
|
||||||
shape_was_added_by_us = True
|
if not project.mapLayer(shape_vg_layer.id()):
|
||||||
|
project.addMapLayer(shape_vg_layer)
|
||||||
|
shape_was_added_by_us = True
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Zwischenabfrage: Nutzer kann vor dem Gemarkungsschlüssel-Schritt
|
||||||
|
# abbrechen (Shape-VG und ggf. BROR bleiben im Projekt erhalten).
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
progress.raise_to_front()
|
||||||
|
if not ask_yes_no(
|
||||||
|
"Verfahrensgebiet aus Shape laden",
|
||||||
|
"Weiter mit Gemarkungsschlüssel ermitteln?",
|
||||||
|
default=True,
|
||||||
|
parent=tab_widget,
|
||||||
|
):
|
||||||
|
abgeschlossen = True
|
||||||
|
return shape_vg_layer, None, bror_layer, vg_status
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 3: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
# Schritt 3: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
||||||
@@ -691,6 +1016,11 @@ def verfahrensgebiet_shape_komplett(
|
|||||||
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
||||||
# Shape-VG und BROR bei Fehler/Abbruch entfernen
|
# Shape-VG und BROR bei Fehler/Abbruch entfernen
|
||||||
if not abgeschlossen:
|
if not abgeschlossen:
|
||||||
|
for lyr in zusatz_shape_layer:
|
||||||
|
try:
|
||||||
|
project.removeMapLayer(lyr.id())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if shape_was_added_by_us and shape_vg_layer is not None:
|
if shape_was_added_by_us and shape_vg_layer is not None:
|
||||||
try:
|
try:
|
||||||
project.removeMapLayer(shape_vg_layer.id())
|
project.removeMapLayer(shape_vg_layer.id())
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ lade_beteiligung_csv:
|
|||||||
- Spalte FLURSTUECK fehlt → BeteiligungPruefungFehler
|
- Spalte FLURSTUECK fehlt → BeteiligungPruefungFehler
|
||||||
- Leere Datei → BeteiligungPruefungFehler
|
- Leere Datei → BeteiligungPruefungFehler
|
||||||
- Erfolgsfall → (zeilen, spaltennamen)
|
- Erfolgsfall → (zeilen, spaltennamen)
|
||||||
|
|
||||||
|
try2/try3 (kein automatisches Laden mehr, siehe Modul-Docstring):
|
||||||
|
- Verfahrensgebiet-Layer ohne Kennzeichen vorhanden → Aufforderung statt Auto-Laden
|
||||||
|
- Kein Verfahrensgebiet-Layer vorhanden → Verfahrensnummer aus CSV wird gesetzt,
|
||||||
|
dann Aufforderung statt Auto-Laden
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
@@ -48,13 +53,13 @@ from sn_verfahrensgebiet.functions.beteiligung_pruefung import (
|
|||||||
baue_flurstueck_csv,
|
baue_flurstueck_csv,
|
||||||
baue_flurstueck_csv_liste,
|
baue_flurstueck_csv_liste,
|
||||||
lade_beteiligung_csv,
|
lade_beteiligung_csv,
|
||||||
|
beteiligung_pruefen,
|
||||||
BeteiligungPruefungFehler,
|
BeteiligungPruefungFehler,
|
||||||
BeteiligungErgebnis,
|
|
||||||
FELD_FLURSTUECK_CSV,
|
FELD_FLURSTUECK_CSV,
|
||||||
CSV_SPALTE_FLURSTUECK,
|
CSV_SPALTE_FLURSTUECK,
|
||||||
CSV_SPALTE_VEF_VKZF,
|
CSV_SPALTE_VEF_VKZF,
|
||||||
_lade_via_alkis_pipeline_und_vergleiche,
|
_fordere_manuelles_laden_an,
|
||||||
_try3_bootstrap_und_try2,
|
_try3_bootstrap,
|
||||||
_hole_verfahrensgebiet_layer,
|
_hole_verfahrensgebiet_layer,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -170,86 +175,73 @@ class TestHoleVerfahrensgebietLayer(unittest.TestCase):
|
|||||||
self.assertIsNone(_hole_verfahrensgebiet_layer(tab_widget))
|
self.assertIsNone(_hole_verfahrensgebiet_layer(tab_widget))
|
||||||
|
|
||||||
|
|
||||||
class TestLadeViaAlkisPipelineUndVergleiche(unittest.TestCase):
|
class TestFordereManuellesLadenAn(unittest.TestCase):
|
||||||
@patch(f"{_MOD}._try1_listenabgleich")
|
"""try2/try3 laden nicht mehr automatisch, sondern fordern den Nutzer auf,
|
||||||
@patch(f"{_MOD}.apply_style")
|
den regulären interaktiven Ablauf ('Aus ALKIS laden') selbst zu starten."""
|
||||||
@patch(f"{_MOD}.QgsProject")
|
|
||||||
@patch(f"{_MOD}.verfahrensgebiet_alkis_komplett")
|
|
||||||
def test_pipeline_fehler_wirft_beteiligung_pruefung_fehler(
|
|
||||||
self, mock_pipeline, mock_project_cls, mock_apply_style, mock_try1
|
|
||||||
):
|
|
||||||
mock_pipeline.return_value = (None, None, None, None)
|
|
||||||
with self.assertRaises(BeteiligungPruefungFehler):
|
|
||||||
_lade_via_alkis_pipeline_und_vergleiche(object(), ["145678___00001____01"])
|
|
||||||
mock_try1.assert_not_called()
|
|
||||||
|
|
||||||
@patch(f"{_MOD}._try1_listenabgleich")
|
def test_ohne_vef_vkzf_nennt_fehlende_kennzeichen(self):
|
||||||
@patch(f"{_MOD}.apply_style")
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||||||
@patch(f"{_MOD}.QgsProject")
|
_fordere_manuelles_laden_an()
|
||||||
@patch(f"{_MOD}.verfahrensgebiet_alkis_komplett")
|
self.assertIn("keine verwertbaren", str(ctx.exception))
|
||||||
def test_ruft_pipeline_silent_auf_und_delegiert_an_try1(
|
self.assertIn("Aus ALKIS laden", str(ctx.exception))
|
||||||
self, mock_pipeline, mock_project_cls, mock_apply_style, mock_try1
|
|
||||||
):
|
|
||||||
tab_widget = object()
|
|
||||||
enriched = MagicMock(name="enriched")
|
|
||||||
enriched.isValid.return_value = True
|
|
||||||
enriched.id.return_value = "vg1"
|
|
||||||
flst = MagicMock(name="flst")
|
|
||||||
flst.isValid.return_value = True
|
|
||||||
flst.name.return_value = "Flurstücke"
|
|
||||||
bror = MagicMock(name="bror")
|
|
||||||
bror.isValid.return_value = True
|
|
||||||
bror.id.return_value = "bror1"
|
|
||||||
mock_pipeline.return_value = (enriched, flst, bror, "FIRST")
|
|
||||||
|
|
||||||
mock_project = MagicMock()
|
def test_mit_vef_vkzf_nennt_verfahrensnummer(self):
|
||||||
mock_project.mapLayersByName.return_value = []
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||||||
mock_project.mapLayer.return_value = None
|
_fordere_manuelles_laden_an("12345")
|
||||||
mock_project_cls.instance.return_value = mock_project
|
self.assertIn("12345", str(ctx.exception))
|
||||||
|
self.assertIn("Aus ALKIS laden", str(ctx.exception))
|
||||||
vergleich_layer = MagicMock(name="vergleich")
|
|
||||||
statistik = {"beide": 1, "nur_verfahrensgebiet": 2, "nur_aglb": 3}
|
|
||||||
mock_try1.return_value = (vergleich_layer, statistik)
|
|
||||||
|
|
||||||
flurstueck_csv_liste = ["145678___00001____01"]
|
|
||||||
ergebnis = _lade_via_alkis_pipeline_und_vergleiche(tab_widget, flurstueck_csv_liste)
|
|
||||||
|
|
||||||
mock_pipeline.assert_called_once_with(tab_widget, silent=True)
|
|
||||||
mock_try1.assert_called_once_with(tab_widget, enriched, flurstueck_csv_liste)
|
|
||||||
|
|
||||||
self.assertIsInstance(ergebnis, BeteiligungErgebnis)
|
|
||||||
self.assertEqual(ergebnis.modus, "try2")
|
|
||||||
self.assertEqual(ergebnis.statistik, statistik)
|
|
||||||
self.assertIn("Übereinstimmend: 1", ergebnis.statistik_text)
|
|
||||||
layers = [lyr for lyr, _ in ergebnis.layers]
|
|
||||||
self.assertIn(enriched, layers)
|
|
||||||
self.assertIn(vergleich_layer, layers)
|
|
||||||
self.assertIn(flst, layers)
|
|
||||||
self.assertIn(bror, layers)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTry3BootstrapUndTry2(unittest.TestCase):
|
class TestTry3Bootstrap(unittest.TestCase):
|
||||||
@patch(f"{_MOD}._lade_via_alkis_pipeline_und_vergleiche")
|
|
||||||
@patch(f"{_MOD}.set_variable")
|
@patch(f"{_MOD}.set_variable")
|
||||||
def test_setzt_verfahrensnummer_und_ergaenzt_statistik_text(
|
def test_setzt_verfahrensnummer_und_fordert_manuelles_laden_an(self, mock_set_variable):
|
||||||
self, mock_set_variable, mock_lade
|
|
||||||
):
|
|
||||||
basis_ergebnis = BeteiligungErgebnis(
|
|
||||||
modus="try2",
|
|
||||||
layers=[],
|
|
||||||
statistik={"beide": 0, "nur_verfahrensgebiet": 0, "nur_aglb": 0},
|
|
||||||
statistik_text="Übereinstimmend: 0\n...",
|
|
||||||
)
|
|
||||||
mock_lade.return_value = basis_ergebnis
|
|
||||||
|
|
||||||
zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}]
|
zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}]
|
||||||
spaltennamen = [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK]
|
spaltennamen = [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK]
|
||||||
|
|
||||||
ergebnis = _try3_bootstrap_und_try2(object(), zeilen, spaltennamen, ["kz1"])
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||||||
|
_try3_bootstrap(object(), zeilen, spaltennamen)
|
||||||
|
|
||||||
mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project")
|
mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project")
|
||||||
self.assertEqual(ergebnis.modus, "try3")
|
self.assertIn("12345", str(ctx.exception))
|
||||||
self.assertTrue(ergebnis.statistik_text.startswith("Verfahrensnummer aus CSV: 12345"))
|
|
||||||
|
|
||||||
|
class TestBeteiligungPruefenDispatch(unittest.TestCase):
|
||||||
|
"""Dispatch-Logik in beteiligung_pruefen(): try1 unverändert, try2/try3
|
||||||
|
fordern jetzt auf statt automatisch zu laden."""
|
||||||
|
|
||||||
|
@patch(f"{_MOD}._hat_verwendbare_kennzeichen", return_value=False)
|
||||||
|
@patch(f"{_MOD}._hole_verfahrensgebiet_layer")
|
||||||
|
@patch(f"{_MOD}.baue_flurstueck_csv_liste")
|
||||||
|
@patch(f"{_MOD}.get_laenderkennung", return_value="14")
|
||||||
|
@patch(f"{_MOD}.lade_beteiligung_csv")
|
||||||
|
def test_layer_ohne_kennzeichen_fordert_manuelles_laden_an(
|
||||||
|
self, mock_lade_csv, mock_laenderkennung, mock_liste, mock_hole_layer, mock_hat_kz
|
||||||
|
):
|
||||||
|
mock_lade_csv.return_value = ([{}], ["FLURSTUECK"])
|
||||||
|
mock_liste.return_value = ["145678___00001____01"]
|
||||||
|
mock_hole_layer.return_value = MagicMock(name="verf_layer_ohne_kz")
|
||||||
|
|
||||||
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||||||
|
beteiligung_pruefen(object())
|
||||||
|
self.assertIn("keine verwertbaren", str(ctx.exception))
|
||||||
|
|
||||||
|
@patch(f"{_MOD}.set_variable")
|
||||||
|
@patch(f"{_MOD}._hole_verfahrensgebiet_layer", return_value=None)
|
||||||
|
@patch(f"{_MOD}.baue_flurstueck_csv_liste")
|
||||||
|
@patch(f"{_MOD}.get_laenderkennung", return_value="14")
|
||||||
|
@patch(f"{_MOD}.lade_beteiligung_csv")
|
||||||
|
def test_kein_layer_setzt_verfahrensnummer_und_fordert_laden_an(
|
||||||
|
self, mock_lade_csv, mock_laenderkennung, mock_liste, mock_hole_layer, mock_set_variable
|
||||||
|
):
|
||||||
|
zeilen = [{CSV_SPALTE_VEF_VKZF: "12345", CSV_SPALTE_FLURSTUECK: "1234/1/1/00"}]
|
||||||
|
mock_lade_csv.return_value = (zeilen, [CSV_SPALTE_VEF_VKZF, CSV_SPALTE_FLURSTUECK])
|
||||||
|
mock_liste.return_value = ["145678___00001____01"]
|
||||||
|
|
||||||
|
with self.assertRaises(BeteiligungPruefungFehler) as ctx:
|
||||||
|
beteiligung_pruefen(object())
|
||||||
|
|
||||||
|
mock_set_variable.assert_called_once_with("verfahrensnummer", "12345", scope="project")
|
||||||
|
self.assertIn("12345", str(ctx.exception))
|
||||||
|
|
||||||
|
|
||||||
class TestLadeBeteiligungCsv(unittest.TestCase):
|
class TestLadeBeteiligungCsv(unittest.TestCase):
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class TestVerfahrensgebietAlkis(unittest.TestCase):
|
|||||||
self.assertEqual(status, vg.LoadStatus.FIRST)
|
self.assertEqual(status, vg.LoadStatus.FIRST)
|
||||||
self.assertEqual(len(feats), 1)
|
self.assertEqual(len(feats), 1)
|
||||||
|
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.ask_bror_kombination")
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.ask_bror_kombinationen")
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsFeature")
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsFeature")
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsGeometry.unaryUnion", return_value=_DummyGeom(empty=False))
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsGeometry.unaryUnion", return_value=_DummyGeom(empty=False))
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
|
||||||
@@ -202,7 +202,7 @@ class TestVerfahrensgebietAlkis(unittest.TestCase):
|
|||||||
temp_layer = _DummyTempLayer(valid=True, features=[feat1, feat2])
|
temp_layer = _DummyTempLayer(valid=True, features=[feat1, feat2])
|
||||||
memory_layer = _DummyMemoryLayer()
|
memory_layer = _DummyMemoryLayer()
|
||||||
mock_qgsvectorlayer.side_effect = [temp_layer, memory_layer]
|
mock_qgsvectorlayer.side_effect = [temp_layer, memory_layer]
|
||||||
mock_dialog.return_value = ("ADV-A", "VKZ12/001")
|
mock_dialog.return_value = [("ADV-A", "VKZ12/001")]
|
||||||
|
|
||||||
mock_feature = mock_feature_cls.return_value
|
mock_feature = mock_feature_cls.return_value
|
||||||
mock_feature.setGeometry.return_value = None
|
mock_feature.setGeometry.return_value = None
|
||||||
@@ -216,7 +216,7 @@ class TestVerfahrensgebietAlkis(unittest.TestCase):
|
|||||||
# Nur feat1 gehört zur gewählten Kombination
|
# Nur feat1 gehört zur gewählten Kombination
|
||||||
self.assertEqual(feats, [feat1])
|
self.assertEqual(feats, [feat1])
|
||||||
|
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.ask_bror_kombination")
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.ask_bror_kombinationen")
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.get_variable", return_value="VKZ123")
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.get_variable", return_value="VKZ123")
|
||||||
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsVectorLayer")
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsVectorLayer")
|
||||||
@@ -229,7 +229,7 @@ class TestVerfahrensgebietAlkis(unittest.TestCase):
|
|||||||
feat2 = _DummyFeature(attrs={"adv_name": "ADV-B", "bezeichnung": "VKZ12/002"})
|
feat2 = _DummyFeature(attrs={"adv_name": "ADV-B", "bezeichnung": "VKZ12/002"})
|
||||||
temp_layer = _DummyTempLayer(valid=True, features=[feat1, feat2])
|
temp_layer = _DummyTempLayer(valid=True, features=[feat1, feat2])
|
||||||
mock_qgsvectorlayer.return_value = temp_layer
|
mock_qgsvectorlayer.return_value = temp_layer
|
||||||
mock_dialog.return_value = None # Nutzer bricht ab
|
mock_dialog.return_value = [] # Nutzer bricht ab
|
||||||
|
|
||||||
layer, status, feats = vg.verfahrensgebiet_alkis(tab_widget=None)
|
layer, status, feats = vg.verfahrensgebiet_alkis(tab_widget=None)
|
||||||
|
|
||||||
@@ -238,6 +238,35 @@ class TestVerfahrensgebietAlkis(unittest.TestCase):
|
|||||||
self.assertEqual(status, vg.LoadStatus.NONE)
|
self.assertEqual(status, vg.LoadStatus.NONE)
|
||||||
self.assertEqual(feats, [])
|
self.assertEqual(feats, [])
|
||||||
|
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.ask_bror_kombinationen")
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsFeature")
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsGeometry.unaryUnion", return_value=_DummyGeom(empty=False))
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis._check_existing_layer", return_value=(None, vg.LoadStatus.FIRST))
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.get_variable", return_value="VKZ123")
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QgsVectorLayer")
|
||||||
|
@patch("sn_verfahrensgebiet.functions.verfahrensgebiet_alkis.QMessageBox", new=_DummyMessageBox)
|
||||||
|
def test_verfahrensgebiet_alkis_multi_kombination_mehrfachauswahl(
|
||||||
|
self, mock_qgsvectorlayer, _mock_get_var, _mock_check, _mock_union, mock_feature_cls, mock_dialog
|
||||||
|
):
|
||||||
|
"""Zwei verschiedene Kombinationen → beide ausgewählt → beide Feature-Gruppen genutzt."""
|
||||||
|
feat1 = _DummyFeature(attrs={"adv_name": "ADV-A", "bezeichnung": "VKZ12/001"})
|
||||||
|
feat2 = _DummyFeature(attrs={"adv_name": "ADV-B", "bezeichnung": "VKZ12/002"})
|
||||||
|
temp_layer = _DummyTempLayer(valid=True, features=[feat1, feat2])
|
||||||
|
memory_layer = _DummyMemoryLayer()
|
||||||
|
mock_qgsvectorlayer.side_effect = [temp_layer, memory_layer]
|
||||||
|
mock_dialog.return_value = [("ADV-A", "VKZ12/001"), ("ADV-B", "VKZ12/002")]
|
||||||
|
|
||||||
|
mock_feature = mock_feature_cls.return_value
|
||||||
|
mock_feature.setGeometry.return_value = None
|
||||||
|
mock_feature.setAttributes.return_value = None
|
||||||
|
|
||||||
|
layer, status, feats = vg.verfahrensgebiet_alkis(tab_widget=None)
|
||||||
|
|
||||||
|
mock_dialog.assert_called_once()
|
||||||
|
self.assertIs(layer, memory_layer)
|
||||||
|
self.assertEqual(status, vg.LoadStatus.FIRST)
|
||||||
|
self.assertEqual(feats, [feat1, feat2])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ def _make_progress_mock(canceled=False):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_MOD = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett"
|
_MOD = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett"
|
||||||
_PATCH_VG_ALKIS = f"{_MOD}.verfahrensgebiet_alkis"
|
_PATCH_VG_ALKIS = f"{_MOD}._suche_verfahrensgebiet_features"
|
||||||
_PATCH_GEM = f"{_MOD}.gemarkung_schluessel_und_namen_von_vg_geometrie"
|
_PATCH_GEM = f"{_MOD}.gemarkung_schluessel_und_namen_von_vg_geometrie"
|
||||||
_PATCH_FLST = f"{_MOD}.flurstueck_alkis"
|
_PATCH_FLST = f"{_MOD}.flurstueck_alkis"
|
||||||
_PATCH_SELECT = f"{_MOD}._select_by_location"
|
_PATCH_SELECT = f"{_MOD}._select_by_location"
|
||||||
@@ -146,6 +146,9 @@ _PATCH_ASK_DIALOG = f"{_MOD}.ask_detailpruefung_oder_vg_laden"
|
|||||||
_PATCH_GP_ALKIS = f"{_MOD}.lade_punktort_ta"
|
_PATCH_GP_ALKIS = f"{_MOD}.lade_punktort_ta"
|
||||||
_PATCH_KNICK = f"{_MOD}.knickpunkt_pruefung"
|
_PATCH_KNICK = f"{_MOD}.knickpunkt_pruefung"
|
||||||
_PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte"
|
_PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte"
|
||||||
|
_PATCH_LANDKREIS_SUCHE = f"{_MOD}._suche_landkreis_kombinationen"
|
||||||
|
_PATCH_LANDKREIS_BAU = f"{_MOD}._baue_layer_aus_landkreis_auswahl"
|
||||||
|
_PATCH_ASK_LANDKREIS = f"{_MOD}.ask_landkreis_auswahl"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -265,9 +268,10 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
@patch(_PATCH_PROGRESS)
|
@patch(_PATCH_PROGRESS)
|
||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
def test_vg_alkis_none_aborts(self, mock_vg, mock_proj, mock_prog):
|
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
|
||||||
|
def test_vg_alkis_none_aborts(self, mock_get_var, mock_vg, mock_proj, mock_prog):
|
||||||
mock_proj.instance.return_value = _DummyProject()
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
mock_vg.return_value = (None, LoadStatus.NONE, [])
|
mock_vg.return_value = (None, LoadStatus.NONE, {}, None)
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
@@ -281,10 +285,11 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
@patch(_PATCH_GEM)
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
def test_flst_none_aborts(self, mock_vg, mock_gem, mock_proj, mock_prog):
|
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
|
||||||
|
def test_flst_none_aborts(self, mock_get_var, mock_vg, mock_gem, mock_proj, mock_prog):
|
||||||
mock_proj.instance.return_value = _DummyProject()
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = [("1234", "Muster")]
|
mock_gem.return_value = [("1234", "Muster")]
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
@@ -302,13 +307,14 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
@patch(_PATCH_GEM)
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_FLST)
|
@patch(_PATCH_FLST)
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
|
||||||
def test_no_selection_returns_none_enriched(
|
def test_no_selection_returns_none_enriched(
|
||||||
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_msgbox, mock_prog
|
self, mock_get_var, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_msgbox, mock_prog
|
||||||
):
|
):
|
||||||
mock_proj.instance.return_value = _DummyProject()
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
flst_layer = _make_layer(name="Flurstücke")
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 0
|
mock_select.return_value = 0
|
||||||
@@ -330,15 +336,16 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
@patch(_PATCH_GEM)
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_FLST)
|
@patch(_PATCH_FLST)
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
|
||||||
def test_invalid_enriched_layer_returns_none(
|
def test_invalid_enriched_layer_returns_none(
|
||||||
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_msgbox, mock_prog
|
self, mock_get_var, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_msgbox, mock_prog
|
||||||
):
|
):
|
||||||
mock_proj.instance.return_value = _DummyProject()
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
flst_layer = _make_layer(name="Flurstücke")
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
bad_enriched = _make_layer(valid=False)
|
bad_enriched = _make_layer(valid=False)
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 5
|
mock_select.return_value = 5
|
||||||
@@ -356,11 +363,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
@patch(_PATCH_PROGRESS)
|
@patch(_PATCH_PROGRESS)
|
||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
def test_progress_cancel_after_step1_returns_none(self, mock_vg, mock_proj, mock_prog):
|
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
|
||||||
|
def test_progress_cancel_after_step1_returns_none(self, mock_get_var, mock_vg, mock_proj, mock_prog):
|
||||||
"""Abbruch nach Schritt 1 → sofortiger sauberer Rückgabestatus."""
|
"""Abbruch nach Schritt 1 → sofortiger sauberer Rückgabestatus."""
|
||||||
mock_proj.instance.return_value = _DummyProject()
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
|
|
||||||
prog = MagicMock()
|
prog = MagicMock()
|
||||||
prog.is_canceled.side_effect = [False, True] # 1. Aufruf False, 2. True
|
prog.is_canceled.side_effect = [False, True] # 1. Aufruf False, 2. True
|
||||||
@@ -398,7 +406,7 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
flst_layer = _make_layer(name="Flurstücke", lid="FLST1")
|
flst_layer = _make_layer(name="Flurstücke", lid="FLST1")
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", lid="VG_ENRICHED", valid=True)
|
enriched = _make_layer(name="Verfahrensgebiet", lid="VG_ENRICHED", valid=True)
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = [("1234", "Muster")]
|
mock_gem.return_value = [("1234", "Muster")]
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 3
|
mock_select.return_value = 3
|
||||||
@@ -437,18 +445,18 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
flst_layer = _make_layer(name="Flurstücke")
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 2
|
mock_select.return_value = 2
|
||||||
mock_erstelle.return_value = (enriched, 2, 0)
|
mock_erstelle.return_value = (enriched, 2, 0)
|
||||||
mock_get_var.return_value = ""
|
mock_get_var.return_value = "VF-2024-001"
|
||||||
mock_vergl.return_value = (1000.0, 1000.0, True)
|
mock_vergl.return_value = (1000.0, 1000.0, True)
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
verfahrensgebiet_alkis_komplett(None)
|
verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
mock_vkz.assert_called_once_with(enriched, "")
|
mock_vkz.assert_called_once_with(enriched, "VF-2024-001")
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Flächenvergleich – Abweichung + Abbrechen
|
# Flächenvergleich – Abweichung + Abbrechen
|
||||||
@@ -476,12 +484,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
flst_layer = _make_layer(name="Flurstücke")
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 3
|
mock_select.return_value = 3
|
||||||
mock_erstelle.return_value = (enriched, 3, 0)
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
mock_get_var.return_value = ""
|
mock_get_var.return_value = "VF-2024-001"
|
||||||
mock_vergl.return_value = (1000.0, 800.0, False)
|
mock_vergl.return_value = (1000.0, 800.0, False)
|
||||||
mock_ask.return_value = "abbrechen"
|
mock_ask.return_value = "abbrechen"
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
@@ -521,12 +529,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
flst_layer = _make_layer(name="Flurstücke")
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 3
|
mock_select.return_value = 3
|
||||||
mock_erstelle.return_value = (enriched, 3, 0)
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
mock_get_var.return_value = ""
|
mock_get_var.return_value = "VF-2024-001"
|
||||||
mock_vergl.return_value = (1000.0, 800.0, False)
|
mock_vergl.return_value = (1000.0, 800.0, False)
|
||||||
mock_ask.return_value = "als_vg_laden"
|
mock_ask.return_value = "als_vg_laden"
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
@@ -577,12 +585,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
punktort_layer = _make_layer(name="PunktortTA", lid="PTA_L")
|
punktort_layer = _make_layer(name="PunktortTA", lid="PTA_L")
|
||||||
ergebnis_layer = _make_layer(name="Knickpunktprüfung", lid="KNICK_L")
|
ergebnis_layer = _make_layer(name="Knickpunktprüfung", lid="KNICK_L")
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 3
|
mock_select.return_value = 3
|
||||||
mock_erstelle.return_value = (enriched, 3, 0)
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
mock_get_var.return_value = ""
|
mock_get_var.return_value = "VF-2024-001"
|
||||||
mock_vergl.return_value = (1000.0, 800.0, False)
|
mock_vergl.return_value = (1000.0, 800.0, False)
|
||||||
mock_ask.return_value = "detailpruefung"
|
mock_ask.return_value = "detailpruefung"
|
||||||
mock_extrahiere.return_value = [MagicMock(), MagicMock(), MagicMock()] # 3 Knickpunkte
|
mock_extrahiere.return_value = [MagicMock(), MagicMock(), MagicMock()] # 3 Knickpunkte
|
||||||
@@ -630,12 +638,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
flst_layer = _make_layer(name="Flurstücke")
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
mock_select.return_value = 3
|
mock_select.return_value = 3
|
||||||
mock_erstelle.return_value = (enriched, 3, 0)
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
mock_get_var.return_value = ""
|
mock_get_var.return_value = "VF-2024-001"
|
||||||
mock_vergl.return_value = (1000.0, 1000.0, True)
|
mock_vergl.return_value = (1000.0, 1000.0, True)
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
@@ -647,15 +655,16 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
@patch(_PATCH_GEM)
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
|
||||||
def test_bror_layer_wird_bei_fehler_entfernt(
|
def test_bror_layer_wird_bei_fehler_entfernt(
|
||||||
self, mock_vg, mock_gem, mock_proj, mock_prog
|
self, mock_get_var, mock_vg, mock_gem, mock_proj, mock_prog
|
||||||
):
|
):
|
||||||
"""Bei Fehler (flst_alkis NONE) wird der BROR-Layer aus dem Projekt entfernt."""
|
"""Bei Fehler (flst_alkis NONE) wird der BROR-Layer aus dem Projekt entfernt."""
|
||||||
proj_inst = _DummyProject()
|
proj_inst = _DummyProject()
|
||||||
mock_proj.instance.return_value = proj_inst
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
|
||||||
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)", lid="BROR_ERR")
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)", lid="BROR_ERR")
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
|
||||||
mock_gem.return_value = []
|
mock_gem.return_value = []
|
||||||
mock_prog.return_value = _make_progress_mock()
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
@@ -664,6 +673,110 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertIn("BROR_ERR", proj_inst.removed)
|
self.assertIn("BROR_ERR", proj_inst.removed)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Landkreis-Fallback (keine Verfahrensnummer)
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_MSGBOX)
|
||||||
|
@patch(_PATCH_ASK_LANDKREIS, return_value=None)
|
||||||
|
@patch(_PATCH_GET_VAR, return_value="")
|
||||||
|
def test_kein_vkz_kein_landkreis_dialog_abgebrochen(
|
||||||
|
self, mock_get_var, mock_ask_landkreis, mock_msgbox, mock_prog
|
||||||
|
):
|
||||||
|
"""Weder Verfahrensnummer noch Landkreis gesetzt, Nutzer bricht den
|
||||||
|
Landkreis-Auswahldialog ab → stiller Abbruch, kein Layer, KEINE
|
||||||
|
Fehlermeldung (bewusster Abbruch, kein Fehlerzustand)."""
|
||||||
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
self.assertIsNone(enriched)
|
||||||
|
self.assertIsNone(flst)
|
||||||
|
self.assertIsNone(bror)
|
||||||
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
|
mock_msgbox.critical.assert_not_called()
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_LANDKREIS_BAU)
|
||||||
|
@patch(_PATCH_LANDKREIS_SUCHE)
|
||||||
|
@patch(_PATCH_ASK_LANDKREIS, return_value="22")
|
||||||
|
@patch(_PATCH_GET_VAR, return_value="")
|
||||||
|
def test_kein_vkz_kein_landkreis_dialog_liefert_landkreis(
|
||||||
|
self, mock_get_var, mock_ask_landkreis, mock_such, mock_bau, mock_proj, mock_prog
|
||||||
|
):
|
||||||
|
"""Weder Verfahrensnummer noch Landkreis gesetzt, Nutzer wählt einen
|
||||||
|
Landkreis im Dialog → Landkreis-Suchpfad wird mit dieser Kennzahl
|
||||||
|
verwendet (wie im Fall 'Landkreis aus Projekteigenschaften')."""
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
mock_such.return_value = (None, LoadStatus.FIRST, {("A", "22015"): [MagicMock()]}, "EPSG:25833")
|
||||||
|
mock_bau.return_value = (vg_layer, LoadStatus.FIRST, True) # nur_laden=True
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
mock_ask_landkreis.assert_called_once()
|
||||||
|
mock_such.assert_called_once_with(None, "22")
|
||||||
|
mock_bau.assert_called_once()
|
||||||
|
self.assertIs(enriched, vg_layer)
|
||||||
|
self.assertEqual(status, LoadStatus.FIRST)
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_LANDKREIS_BAU)
|
||||||
|
@patch(_PATCH_LANDKREIS_SUCHE)
|
||||||
|
def test_landkreis_modus_wird_bei_fehlender_vkz_verwendet(
|
||||||
|
self, mock_such, mock_bau, mock_proj, mock_prog
|
||||||
|
):
|
||||||
|
"""Keine Verfahrensnummer, aber Landkreis gesetzt → Landkreis-Suchpfad
|
||||||
|
statt VKZ-Pfad wird verwendet."""
|
||||||
|
|
||||||
|
def _get_var_side_effect(key, scope="project"):
|
||||||
|
return {"verfahrensnummer": "", "landkreis_kennzahl": "22"}.get(key, "")
|
||||||
|
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
mock_such.return_value = (None, LoadStatus.FIRST, {("A", "22015"): [MagicMock()]}, "EPSG:25833")
|
||||||
|
mock_bau.return_value = (vg_layer, LoadStatus.FIRST, True) # nur_laden=True
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
with patch(_PATCH_GET_VAR, side_effect=_get_var_side_effect):
|
||||||
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
mock_such.assert_called_once()
|
||||||
|
mock_bau.assert_called_once()
|
||||||
|
self.assertIs(enriched, vg_layer)
|
||||||
|
self.assertIsNone(flst)
|
||||||
|
self.assertIsNone(bror)
|
||||||
|
self.assertEqual(status, LoadStatus.FIRST)
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_LANDKREIS_BAU)
|
||||||
|
@patch(_PATCH_LANDKREIS_SUCHE)
|
||||||
|
def test_landkreis_modus_nur_laden_ueberspringt_dialog2(
|
||||||
|
self, mock_such, mock_bau, mock_proj, mock_prog
|
||||||
|
):
|
||||||
|
"""nur_laden=True → Funktion kehrt sofort zurück, Dialog 2 (Gemarkung
|
||||||
|
etc.) wird nicht gestartet."""
|
||||||
|
|
||||||
|
def _get_var_side_effect(key, scope="project"):
|
||||||
|
return {"verfahrensnummer": "", "landkreis_kennzahl": "22"}.get(key, "")
|
||||||
|
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
mock_such.return_value = (None, LoadStatus.FIRST, {("A", "22015"): [MagicMock()]}, "EPSG:25833")
|
||||||
|
mock_bau.return_value = (vg_layer, LoadStatus.FIRST, True)
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
with patch(_PATCH_GET_VAR, side_effect=_get_var_side_effect), \
|
||||||
|
patch(_PATCH_GEM) as mock_gem:
|
||||||
|
verfahrensgebiet_alkis_komplett(None)
|
||||||
|
mock_gem.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
+36
-27
@@ -21,7 +21,10 @@ from sn_verfahrensgebiet.functions.flurstueck_alkis import (
|
|||||||
ALKIS_SF_URL,
|
ALKIS_SF_URL,
|
||||||
FLST_LAYER_NAME,
|
FLST_LAYER_NAME,
|
||||||
)
|
)
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import verfahrensgebiet_alkis_komplett
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
||||||
|
verfahrensgebiet_alkis_komplett,
|
||||||
|
BROR_LAYER_NAME,
|
||||||
|
)
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett
|
||||||
from sn_verfahrensgebiet.functions.beteiligung_pruefung import (
|
from sn_verfahrensgebiet.functions.beteiligung_pruefung import (
|
||||||
beteiligung_pruefen,
|
beteiligung_pruefen,
|
||||||
@@ -215,23 +218,27 @@ class WorkingTab(QWidget):
|
|||||||
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml")
|
||||||
self.setze_haken(self.haken_flurst, True)
|
self.setze_haken(self.haken_flurst, True)
|
||||||
|
|
||||||
# BROR-Layer: QML-Stil anwenden
|
# BROR-Layer bekommt seinen Style bereits direkt beim Laden in
|
||||||
if bror_layer and bror_layer.isValid():
|
# verfahrensgebiet_alkis_komplett() (auch im "Nur laden"-Fall).
|
||||||
apply_style(bror_layer, "BauRaumOderBodenordnungsrecht.qml")
|
|
||||||
|
|
||||||
# Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden")
|
# Bei "Nur Objekte laden" (kein weiterer Ablauf) ist enriched_layer
|
||||||
if not project.mapLayer(enriched_layer.id()):
|
# tatsächlich nur der rohe BROR-Layer (erkennbar am Namen) – hier
|
||||||
project.addMapLayer(enriched_layer)
|
# fand keine Flurstücksverschneidung statt, es gibt also keinen
|
||||||
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
# echten VG-Layer: VG-Style/-Kombobox/-Haken/-Meldung überspringen.
|
||||||
iface.mapCanvas().setExtent(enriched_layer.extent())
|
if enriched_layer.name() != BROR_LAYER_NAME:
|
||||||
iface.mapCanvas().refresh()
|
# Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden")
|
||||||
self.setze_haken(self.haken_verf, True)
|
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.verf_layer_combo.setLayer(enriched_layer)
|
||||||
self._on_verf_layer_changed(enriched_layer)
|
self._on_verf_layer_changed(enriched_layer)
|
||||||
|
|
||||||
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen"
|
||||||
success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5)
|
success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")
|
self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")
|
||||||
@@ -244,15 +251,15 @@ class WorkingTab(QWidget):
|
|||||||
Styles angewendet und die UI-Elemente aktualisiert.
|
Styles angewendet und die UI-Elemente aktualisiert.
|
||||||
"""
|
"""
|
||||||
# Vorprüfung: Verfahrensnummer muss gesetzt sein
|
# Vorprüfung: Verfahrensnummer muss gesetzt sein
|
||||||
vkz = get_variable("verfahrensnummer") or ""
|
# vkz = get_variable("verfahrensnummer") or ""
|
||||||
if not str(vkz).strip():
|
# if not str(vkz).strip():
|
||||||
if not ask_yes_no(
|
# if not ask_yes_no(
|
||||||
"Verfahrensnummer nicht gesetzt",
|
# "Verfahrensnummer nicht gesetzt",
|
||||||
"Verfahrensnummer nicht gesetzt. Trotzdem fortfahren?",
|
# "Verfahrensnummer nicht gesetzt. Trotzdem fortfahren?",
|
||||||
default=False,
|
# default=False,
|
||||||
parent=self,
|
# parent=self,
|
||||||
):
|
# ):
|
||||||
return
|
# return
|
||||||
|
|
||||||
self._set_busy(self.btn_verf_shape, True, "Aus Shape laden")
|
self._set_busy(self.btn_verf_shape, True, "Aus Shape laden")
|
||||||
try:
|
try:
|
||||||
@@ -296,9 +303,11 @@ class WorkingTab(QWidget):
|
|||||||
"""Gleicht eine BI-Server-CSV-Beteiligungsliste mit dem Verfahrensgebiet ab.
|
"""Gleicht eine BI-Server-CSV-Beteiligungsliste mit dem Verfahrensgebiet ab.
|
||||||
|
|
||||||
Delegiert an :func:`~sn_verfahrensgebiet.functions.beteiligung_pruefung.beteiligung_pruefen`.
|
Delegiert an :func:`~sn_verfahrensgebiet.functions.beteiligung_pruefung.beteiligung_pruefen`.
|
||||||
Je nach Projektzustand (try1/try2/try3) werden ein oder mehrere
|
Ist noch kein (verwertbares) Verfahrensgebiet geladen, fordert die
|
||||||
Ergebnis-Layer ins Projekt eingefügt, gestylt und eine Statistik als
|
Funktion den Nutzer per Meldung auf, es zunächst über "Aus ALKIS
|
||||||
Messagebox angezeigt.
|
laden" zu laden – es gibt kein automatisches Laden im Hintergrund.
|
||||||
|
Sonst werden ein oder mehrere Ergebnis-Layer ins Projekt eingefügt,
|
||||||
|
gestylt und eine Statistik als Messagebox angezeigt.
|
||||||
"""
|
"""
|
||||||
self._set_busy(self.btn_beteiligung_pruefen, True, "Beteiligung prüfen")
|
self._set_busy(self.btn_beteiligung_pruefen, True, "Beteiligung prüfen")
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user