704 lines
26 KiB
Python
704 lines
26 KiB
Python
"""sn_verfahrensgebiet/functions/verfahrensgebiet_shape_komplett.py – VG-Lade-Workflow aus Shape (ZIP).
|
||
|
||
Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets
|
||
aus einem ZIP-Archiv mit Shapefile-Set:
|
||
|
||
1. Dateidialog öffnen (.zip-Filter); Nutzerabbruch beendet den Vorgang.
|
||
2. ZIP neben der ZIP-Datei entpacken und auf ein gültiges Shapefile-Set prüfen.
|
||
3. Shapefile als QGIS-Vektorlayer laden und validieren (Standard-QGIS-Routinen).
|
||
4. Alle Features per ``QgsGeometry.unaryUnion`` zu einem Single-Feature-
|
||
Memory-Layer „Verfahrensgebiet" verschmelzen.
|
||
5. Gemarkungsschlüssel per Geometrie aus ``AX_Gemarkung`` vorschlagen.
|
||
6. Flurstücke per Dialog (mit Vorschlag) laden.
|
||
7. Flurstücke räumlich auf das Shape-VG einschränken (``selectByLocation``).
|
||
8. Temporären enriched Layer aus Auswahl für den Flächenvergleich erzeugen
|
||
(wird **nicht** ins Projekt eingefügt und **nicht** zurückgegeben).
|
||
9. Flächenvergleich zwischen Shape-VG und temporärem enriched Layer.
|
||
|
||
- **Übereinstimmung** (≤ Toleranz): modale OK-Meldung, Ablauf beendet.
|
||
- **Abweichung**: 2-Optionen-Dialog (Detailprüfung / Abbrechen).
|
||
|
||
10. Bei Wahl „Detailprüfung" (knickpunktbasiert):
|
||
|
||
- Grenzpunkte (AX_PunktortTA) per BBox laden.
|
||
- Knickpunkte des Shape-VG mit PunktortTA vergleichen (grün/rot/orange).
|
||
- Korrigiertes Polygon aus identischen + gefundenen PunktortTA ableiten.
|
||
- Ergebnislayer (Punkte), Verbindungslinien und Polygon ins Projekt einfügen.
|
||
- Abschlussmeldung mit Zählung.
|
||
|
||
Das Shape-VG (``Verfahrensgebiet``) ist das finale Rückgabe-Layer. Ein
|
||
``enriched Layer aus Flurstücken`` wird nur temporär für den Flächenvergleich
|
||
erzeugt, aber weder ins Projekt eingefügt noch zurückgegeben.
|
||
|
||
**Rückgabe:**
|
||
``Tuple[shape_vg_layer, flst_layer, None, LoadStatus]``
|
||
|
||
**Abbruchverhalten:** Jeder Schritt, der :attr:`LoadStatus.NONE` zurückgibt
|
||
oder kein Ergebnis liefert, bricht den Workflow ab.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import zipfile
|
||
from typing import Optional, Tuple
|
||
|
||
from sn_basis.functions import QgsProject, QgsVectorLayer, QgsFeature, QgsField
|
||
from sn_basis.functions import QMessageBox
|
||
from sn_basis.functions.message_wrapper import warning
|
||
from sn_basis.functions.variable_wrapper import get_variable
|
||
from sn_basis.functions.qt_wrapper import QFileDialog, QVariant
|
||
from sn_basis.functions.qgiscore_wrapper import QgsGeometry
|
||
from sn_basis.functions.dialog_wrapper import (
|
||
create_progress_dialog,
|
||
show_info_dialog,
|
||
ask_detailpruefung_oder_abbrechen,
|
||
)
|
||
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import LoadStatus, verfahrensgebiet_alkis
|
||
from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
||
gemarkung_schluessel_und_namen_von_vg_geometrie,
|
||
)
|
||
from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
|
||
erstelle_verfahrensgebiet_aus_auswahl,
|
||
VERF_LAYER_NAME,
|
||
)
|
||
from sn_verfahrensgebiet.functions.grenzpunkt_alkis import lade_punktort_ta
|
||
from sn_verfahrensgebiet.functions.knickpunkt_pruefung import (
|
||
knickpunkt_pruefung,
|
||
_extrahiere_knickpunkte,
|
||
erstelle_korrigiertes_polygon,
|
||
)
|
||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
||
_select_by_location,
|
||
_vergleiche_flaechen,
|
||
_lese_flaechen_toleranz,
|
||
_entferne_hilfs_layer,
|
||
MINDEST_FLAECHEN_TOLERANZ_M2,
|
||
BROR_LAYER_NAME,
|
||
)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Interne Hilfsfunktionen
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _wende_vergleich_stil_an(layer) -> None:
|
||
"""Wendet einen kategorisierten Renderer auf den Vergleich-Layer an.
|
||
|
||
Farben:
|
||
- ``identisch`` (Schnittmenge): Gr\u00fcn
|
||
- ``nur_bror`` (nur im BROR): Gelb
|
||
- ``nur_vg`` (nur im Verfahrensgebiet): Blau
|
||
|
||
Schl\u00e4gt der Import der QGIS-Rendering-Klassen fehl (z.\u00a0B. im
|
||
Test-Modus), wird der Fehler still ignoriert.
|
||
"""
|
||
try:
|
||
from qgis.core import ( # type: ignore[import]
|
||
QgsCategorizedSymbolRenderer,
|
||
QgsRendererCategory,
|
||
QgsFillSymbol,
|
||
)
|
||
|
||
_FARBEN = [
|
||
("identisch", "#00aa00", 0.5), # gr\u00fcn
|
||
("nur_bror", "#ffcc00", 0.5), # gelb
|
||
("nur_vg", "#3366ff", 0.5), # blau
|
||
]
|
||
|
||
kategorien = []
|
||
for wert, farbe, opazitaet in _FARBEN:
|
||
symbol = QgsFillSymbol.createSimple({
|
||
"color": farbe,
|
||
"outline_color": "#555555",
|
||
"outline_width": "0.26",
|
||
})
|
||
symbol.setOpacity(opazitaet)
|
||
kategorien.append(QgsRendererCategory(wert, symbol, wert))
|
||
|
||
renderer = QgsCategorizedSymbolRenderer("typ", kategorien)
|
||
layer.setRenderer(renderer)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
def _erstelle_vergleich_layer(vg_layer, bror_layer) -> Optional[object]:
|
||
"""Erzeugt einen Vergleich-Layer aus geometrischen Differenzoperationen.
|
||
|
||
F\u00fcr jeden Fl\u00e4chentyp wird ein Feature angelegt:
|
||
|
||
- ``identisch``: Schnittmenge aus VG und BROR (gr\u00fcn).
|
||
- ``nur_bror``: Fl\u00e4che, die nur im BROR-Objekt liegt (gelb).
|
||
- ``nur_vg``: Fl\u00e4che, die nur im Verfahrensgebiet liegt (blau).
|
||
|
||
Parameters
|
||
----------
|
||
vg_layer :
|
||
Verfahrensgebiet-Layer (Shape).
|
||
bror_layer :
|
||
BauRaumOderBodenordnungsrecht-Layer (ALKIS).
|
||
|
||
Returns
|
||
-------
|
||
Optional[QgsVectorLayer]
|
||
Memory-Layer ``Vergleich`` mit Stil oder ``None`` bei Fehler.
|
||
"""
|
||
try:
|
||
vg_geom = None
|
||
for feat in vg_layer.getFeatures():
|
||
g = feat.geometry()
|
||
if g and not g.isEmpty():
|
||
vg_geom = g
|
||
break
|
||
|
||
bror_geom = None
|
||
for feat in bror_layer.getFeatures():
|
||
g = feat.geometry()
|
||
if g and not g.isEmpty():
|
||
bror_geom = g
|
||
break
|
||
|
||
if not vg_geom or not bror_geom:
|
||
return None
|
||
|
||
geom_identisch = vg_geom.intersection(bror_geom)
|
||
geom_nur_bror = bror_geom.difference(vg_geom)
|
||
geom_nur_vg = vg_geom.difference(bror_geom)
|
||
|
||
crs = vg_layer.crs().authid()
|
||
mem_layer = QgsVectorLayer(
|
||
f"MultiPolygon?crs={crs}", "Vergleich", "memory"
|
||
)
|
||
pr = mem_layer.dataProvider()
|
||
pr.addAttributes([QgsField("typ", QVariant.String)])
|
||
mem_layer.updateFields()
|
||
|
||
feats = []
|
||
for geom, typ in [
|
||
(geom_identisch, "identisch"),
|
||
(geom_nur_bror, "nur_bror"),
|
||
(geom_nur_vg, "nur_vg"),
|
||
]:
|
||
if geom and not geom.isEmpty():
|
||
f = QgsFeature()
|
||
f.setGeometry(geom)
|
||
f.setAttributes([typ])
|
||
feats.append(f)
|
||
|
||
pr.addFeatures(feats)
|
||
mem_layer.updateExtents()
|
||
_wende_vergleich_stil_an(mem_layer)
|
||
|
||
return mem_layer if mem_layer.isValid() else None
|
||
except Exception:
|
||
return None
|
||
|
||
def _finde_shp(verzeichnis: str) -> Optional[str]:
|
||
"""Sucht rekursiv nach der ersten ``.shp``-Datei im Verzeichnis.
|
||
|
||
Parameters
|
||
----------
|
||
verzeichnis :
|
||
Startverzeichnis für die Suche.
|
||
|
||
Returns
|
||
-------
|
||
Optional[str]
|
||
Absoluter Pfad zur ersten gefundenen ``.shp``-Datei, oder ``None``.
|
||
"""
|
||
for root, _dirs, files in os.walk(verzeichnis):
|
||
for fname in files:
|
||
if fname.lower().endswith(".shp"):
|
||
return os.path.join(root, fname)
|
||
return None
|
||
|
||
|
||
def _erstelle_memory_layer_aus_shp(shp_layer) -> Optional[object]:
|
||
"""Vereint alle Features des Shapefile-Layers zu einem Single-Feature-
|
||
Memory-Layer „Verfahrensgebiet" via ``QgsGeometry.unaryUnion``.
|
||
|
||
Alle Felder des Shapefiles werden in den Memory-Layer übernommen.
|
||
Die Attributwerte des ersten Features mit gültiger Geometrie werden
|
||
auf das Union-Feature gesetzt.
|
||
|
||
Parameters
|
||
----------
|
||
shp_layer :
|
||
Geladener ``QgsVectorLayer`` aus dem Shapefile.
|
||
|
||
Returns
|
||
-------
|
||
Optional[QgsVectorLayer]
|
||
Single-Feature-Memory-Layer mit Union-Geometrie und Shapefile-Attributen
|
||
oder ``None`` bei Fehler.
|
||
"""
|
||
geom_list = []
|
||
first_feat = None
|
||
|
||
for feat in shp_layer.getFeatures():
|
||
try:
|
||
geom = feat.geometry()
|
||
if geom and not geom.isEmpty():
|
||
geom_list.append(geom)
|
||
if first_feat is None:
|
||
first_feat = feat
|
||
except Exception:
|
||
pass
|
||
|
||
if not geom_list:
|
||
return None
|
||
|
||
union_geom = QgsGeometry.unaryUnion(geom_list)
|
||
if not union_geom or union_geom.isEmpty():
|
||
return None
|
||
|
||
crs_authid = shp_layer.crs().authid()
|
||
mem_layer = QgsVectorLayer(
|
||
f"MultiPolygon?crs={crs_authid}", VERF_LAYER_NAME, "memory"
|
||
)
|
||
pr = mem_layer.dataProvider()
|
||
|
||
# Felder des Shapefiles übernehmen
|
||
try:
|
||
pr.addAttributes(list(shp_layer.fields()))
|
||
mem_layer.updateFields()
|
||
except Exception:
|
||
pass
|
||
|
||
feat_out = QgsFeature(mem_layer.fields())
|
||
feat_out.setGeometry(union_geom)
|
||
|
||
# Attributwerte vom ersten Feature übernehmen
|
||
if first_feat is not None:
|
||
try:
|
||
feat_out.setAttributes(first_feat.attributes())
|
||
except Exception:
|
||
pass
|
||
|
||
pr.addFeatures([feat_out])
|
||
mem_layer.updateExtents()
|
||
|
||
if not mem_layer.isValid() or mem_layer.featureCount() == 0:
|
||
return None
|
||
|
||
return mem_layer
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Öffentliche Lade-Hilfsfunktion
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def lade_vg_aus_zip(parent_widget) -> Optional[object]:
|
||
"""Öffnet Dateidialog, entpackt ZIP und gibt Union-Memory-Layer zurück.
|
||
|
||
Schrittfolge:
|
||
|
||
1. ``QFileDialog.getOpenFileName`` mit Filter ``ZIP-Dateien (*.zip)``.
|
||
2. ZIP neben der Quelldatei in ein Unterverzeichnis entpacken.
|
||
3. Erste ``.shp``-Datei im Verzeichnis finden.
|
||
4. Als ``QgsVectorLayer`` laden und ``isValid()`` prüfen.
|
||
5. Alle Features per ``QgsGeometry.unaryUnion`` verschmelzen.
|
||
6. Single-Feature-Memory-Layer „Verfahrensgebiet" zurückgeben.
|
||
|
||
Parameters
|
||
----------
|
||
parent_widget :
|
||
Eltern-Widget für Dateidialog und Fehlermeldungen.
|
||
|
||
Returns
|
||
-------
|
||
Optional[QgsVectorLayer]
|
||
Valider Memory-Layer „Verfahrensgebiet" oder ``None`` bei Abbruch/Fehler.
|
||
"""
|
||
pfad, _ = QFileDialog.getOpenFileName(
|
||
parent_widget,
|
||
"ZIP-Datei mit Shapefile-Set auswählen",
|
||
"",
|
||
"ZIP-Dateien (*.zip)",
|
||
)
|
||
if not pfad:
|
||
return None # Nutzerabbruch
|
||
|
||
zip_dir = os.path.dirname(pfad)
|
||
zip_base = os.path.splitext(os.path.basename(pfad))[0]
|
||
extract_dir = os.path.join(zip_dir, zip_base)
|
||
|
||
try:
|
||
with zipfile.ZipFile(pfad, "r") as zf:
|
||
zf.extractall(extract_dir)
|
||
except Exception:
|
||
QMessageBox.warning(
|
||
parent_widget,
|
||
"Fehler",
|
||
"Die ZIP-Datei konnte nicht entpackt werden.\n"
|
||
"Bitte eine gültige ZIP-Datei auswählen.",
|
||
)
|
||
return None
|
||
|
||
shp_path = _finde_shp(extract_dir)
|
||
if not shp_path:
|
||
QMessageBox.warning(
|
||
parent_widget,
|
||
"Fehler",
|
||
"gültiges Shape-Dateiset angeben",
|
||
)
|
||
return None
|
||
|
||
raw_layer = QgsVectorLayer(shp_path, "tmp_shape_vg", "ogr")
|
||
if not raw_layer or not raw_layer.isValid():
|
||
QMessageBox.warning(
|
||
parent_widget,
|
||
"Fehler",
|
||
"gültiges Shape-Dateiset angeben",
|
||
)
|
||
return None
|
||
|
||
mem_layer = _erstelle_memory_layer_aus_shp(raw_layer)
|
||
if mem_layer is None:
|
||
QMessageBox.warning(
|
||
parent_widget,
|
||
"Fehler",
|
||
"gültiges Shape-Dateiset angeben",
|
||
)
|
||
return None
|
||
|
||
return mem_layer
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Öffentliche API
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def verfahrensgebiet_shape_komplett(
|
||
tab_widget,
|
||
) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]:
|
||
"""Vollständiger VG-Lade-Workflow aus Shape (ZIP): ZIP → BROR → Gemarkungen →
|
||
Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung.
|
||
|
||
Das aus der ZIP geladene Shape-VG ist das finale Verfahrensgebiet-Layer.
|
||
Ist die Projektvariable ``verfahrensnummer`` gesetzt, wird das zugehörige
|
||
BROR-Objekt (``AX_BauRaumOderBodenordnungsrecht``) aus ALKIS geladen und
|
||
als Vergleichsobjekt im Flächenvergleich verwendet. Andernfalls wird ein
|
||
temporärer enriched Layer aus Flurstücken für den Vergleich herangezogen.
|
||
|
||
Schrittfolge:
|
||
|
||
1. ``lade_vg_aus_zip(tab_widget)`` – ZIP öffnen, Shapefile validieren,
|
||
Union-Memory-Layer „Verfahrensgebiet" erzeugen.
|
||
2. ``verfahrensgebiet_alkis(tab_widget, skip_existing_check=True)`` –
|
||
BROR-Objekt als Memory-Layer (sofern VKZ gesetzt).
|
||
Der BROR wird **vor** dem Shape-VG ins Projekt eingefügt, um den
|
||
Namenskonflikt mit ``_check_existing_layer`` zu vermeiden.
|
||
3. Shape-VG ins Projekt einfügen; BROR umbenennen zu
|
||
``BauRaumOderBodenordnungsrecht (ALKIS)`` und zum Projekt hinzufügen.
|
||
4. ``gemarkung_schluessel_und_namen_von_vg_geometrie(geom)`` – Schlüssel.
|
||
5. ``flurstueck_alkis(...)`` – Flurstücke laden.
|
||
6. ``_select_by_location(...)`` – räumliche Selektion.
|
||
7. Flächenvergleich:
|
||
|
||
- Mit BROR: ``_vergleiche_flaechen(bror_layer, shape_vg_layer, ...)``.
|
||
- Ohne BROR: temp. enriched Layer erzeugen und mit Shape-VG vergleichen.
|
||
|
||
8. Ggf. Detailprüfung (knickpunktbasiert) auf BROR bzw. Shape-VG.
|
||
|
||
Parameters
|
||
----------
|
||
tab_widget :
|
||
Eltern-Widget für alle Dialoge.
|
||
|
||
Returns
|
||
-------
|
||
Tuple[Optional[shape_vg_layer], Optional[flst_layer], Optional[bror_layer], LoadStatus]
|
||
- ``shape_vg_layer``: Memory-Layer „Verfahrensgebiet" (aus Shape).
|
||
- ``flst_layer``: Flurstücke-Layer (bleibt im Projekt).
|
||
- ``bror_layer``: BROR-Layer ``BauRaumOderBodenordnungsrecht (ALKIS)``
|
||
(wenn geladen, bleibt im Projekt); ``None`` wenn VKZ nicht gesetzt.
|
||
- :class:`LoadStatus`: ``FIRST`` oder ``NONE``.
|
||
"""
|
||
project = QgsProject.instance()
|
||
shape_vg_layer = None
|
||
bror_layer = None
|
||
shape_was_added_by_us = False
|
||
bror_was_added_by_us = False
|
||
abgeschlossen = False
|
||
|
||
progress = create_progress_dialog(
|
||
7, "Verfahrensgebiet aus Shape laden", "Initialisierung …"
|
||
)
|
||
|
||
try:
|
||
# ------------------------------------------------------------------
|
||
# Schritt 1: Shape-VG aus ZIP laden (noch NICHT ins Projekt einfügen)
|
||
# ------------------------------------------------------------------
|
||
progress.set_label("Schritt 1/6: Verfahrensgebiet aus ZIP laden …")
|
||
progress.set_value(0)
|
||
if progress.is_canceled():
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
shape_vg_layer = lade_vg_aus_zip(tab_widget)
|
||
progress.raise_to_front()
|
||
|
||
if not shape_vg_layer:
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
vg_status = LoadStatus.FIRST
|
||
progress.set_value(1)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Schritt 2: BROR aus ALKIS laden (vor shape_vg, um Namenskonflikt
|
||
# in _check_existing_layer zu vermeiden)
|
||
# ------------------------------------------------------------------
|
||
progress.set_label("Schritt 2/6: BROR aus ALKIS laden …")
|
||
if progress.is_canceled():
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
vkz = get_variable("verfahrensnummer") or ""
|
||
if str(vkz).strip():
|
||
bror_result, bror_status, _ = verfahrensgebiet_alkis(
|
||
tab_widget, skip_existing_check=True
|
||
)
|
||
progress.raise_to_front()
|
||
if bror_status != LoadStatus.NONE and bror_result:
|
||
bror_result.setName(BROR_LAYER_NAME)
|
||
project.addMapLayer(bror_result)
|
||
bror_layer = bror_result
|
||
bror_was_added_by_us = True
|
||
|
||
progress.set_value(2)
|
||
|
||
# Shape-VG erst jetzt ins Projekt einfügen (nach BROR-Ladung)
|
||
project.addMapLayer(shape_vg_layer)
|
||
shape_was_added_by_us = True
|
||
|
||
# ------------------------------------------------------------------
|
||
# Schritt 3: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
||
# ------------------------------------------------------------------
|
||
progress.set_label("Schritt 3/6: Gemarkungsschlüssel ermitteln …")
|
||
if progress.is_canceled():
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
shape_feats = list(shape_vg_layer.getFeatures())
|
||
vg_geom = shape_feats[0].geometry() if shape_feats else None
|
||
|
||
schluessel_namen = (
|
||
gemarkung_schluessel_und_namen_von_vg_geometrie(vg_geom)
|
||
if vg_geom else []
|
||
)
|
||
vorgabe_schluessel = [nr for nr, _ in schluessel_namen]
|
||
vorgabe_namen = [nm for _, nm in schluessel_namen if nm]
|
||
|
||
if not vorgabe_schluessel:
|
||
warning(
|
||
"VG laden",
|
||
"Keine Gemarkungen in der BBox gefunden. Bitte Schlüssel manuell eingeben.",
|
||
duration=5,
|
||
)
|
||
|
||
progress.set_value(3)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Schritt 4: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt)
|
||
# ------------------------------------------------------------------
|
||
progress.set_label("Schritt 4/6: Flurstücke aus ALKIS laden …")
|
||
if progress.is_canceled():
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
flst_layer, flst_status = flurstueck_alkis(
|
||
tab_widget,
|
||
vorgabe_schluessel=vorgabe_schluessel,
|
||
vorgabe_namen=vorgabe_namen if vorgabe_namen else None,
|
||
ohne_dialog=True,
|
||
)
|
||
|
||
if flst_status == LoadStatus.NONE or not flst_layer:
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
progress.set_value(4)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Schritt 5: Räumliche Selektion
|
||
# ------------------------------------------------------------------
|
||
progress.set_label("Schritt 5/6: Flurstücke räumlich selektieren …")
|
||
if progress.is_canceled():
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
anzahl = _select_by_location(flst_layer, shape_vg_layer)
|
||
|
||
if anzahl == 0:
|
||
QMessageBox.warning(
|
||
tab_widget,
|
||
"Keine Flurstücke im VG",
|
||
"Es wurden keine Flurstücke gefunden, die das Verfahrensgebiet schneiden.\n"
|
||
"Bitte Gemarkungsschlüssel und Ausdehnung prüfen.",
|
||
)
|
||
return None, flst_layer, None, LoadStatus.NONE
|
||
|
||
progress.set_value(5)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Schritt 6: Flächenvergleich als Gate
|
||
# ------------------------------------------------------------------
|
||
progress.set_label("Schritt 6/6: Flächenvergleich …")
|
||
if progress.is_canceled():
|
||
return None, None, None, LoadStatus.NONE
|
||
|
||
toleranz = _lese_flaechen_toleranz()
|
||
|
||
if bror_layer and bror_layer.isValid():
|
||
# BROR verfügbar → als Referenz-Objekt verwenden
|
||
ref_flaeche, shape_flaeche, stimmt_ueberein = _vergleiche_flaechen(
|
||
bror_layer, shape_vg_layer, toleranz
|
||
)
|
||
dialog_ref_label = "BauRaumOderBodenordnungsrecht"
|
||
dialog_vg_label = "Shape-Verfahrensgebiet "
|
||
pruef_layer = bror_layer
|
||
|
||
# Vergleich-Layer (grün/gelb/blau) ins Projekt einfügen
|
||
vergleich_layer = _erstelle_vergleich_layer(shape_vg_layer, bror_layer)
|
||
if vergleich_layer:
|
||
project.addMapLayer(vergleich_layer)
|
||
else:
|
||
# Kein BROR → temp. enriched Layer aus Flurstücken erzeugen
|
||
temp_enriched, _, _ = erstelle_verfahrensgebiet_aus_auswahl(flst_layer)
|
||
ref_flaeche, shape_flaeche, stimmt_ueberein = _vergleiche_flaechen(
|
||
shape_vg_layer, temp_enriched, toleranz
|
||
)
|
||
dialog_ref_label = "Shape-Verfahrensgebiet "
|
||
dialog_vg_label = "Verfahrensgebiet (Flurstücke)"
|
||
pruef_layer = shape_vg_layer
|
||
|
||
progress.set_value(6)
|
||
|
||
if stimmt_ueberein:
|
||
progress.set_value(7)
|
||
progress.close()
|
||
show_info_dialog(
|
||
"Flächenvergleich",
|
||
"Verfahrensgebiet mit erwarteter Geometrie erzeugt",
|
||
parent=tab_widget,
|
||
)
|
||
abgeschlossen = True
|
||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||
|
||
# ------------------------------------------------------------------
|
||
# Flächenabweichung → 2-Optionen-Dialog
|
||
# ------------------------------------------------------------------
|
||
progress.close()
|
||
diff = abs(ref_flaeche - shape_flaeche)
|
||
entscheidung = ask_detailpruefung_oder_abbrechen(
|
||
tab_widget,
|
||
"Abweichung erkannt",
|
||
f"Abweichung erkannt.\n\n"
|
||
f"{dialog_ref_label}: {ref_flaeche:.2f} m²\n"
|
||
f"{dialog_vg_label}: {shape_flaeche:.2f} m²\n"
|
||
f"Differenz: {diff:.2f} m²\n\n"
|
||
f"Wie möchten Sie fortfahren?",
|
||
)
|
||
|
||
if entscheidung == "abbrechen":
|
||
abgeschlossen = True
|
||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||
|
||
# ------------------------------------------------------------------
|
||
# Option: Detailprüfung (knickpunktbasiert) auf pruef_layer
|
||
# (= BROR wenn vorhanden, sonst Shape-VG)
|
||
# ------------------------------------------------------------------
|
||
knickpunkte = _extrahiere_knickpunkte(pruef_layer)
|
||
n_knick = len(knickpunkte)
|
||
gesamt_schritte = n_knick + 5
|
||
|
||
detail_progress = create_progress_dialog(
|
||
gesamt_schritte,
|
||
"Detailprüfung (Knickpunkte)",
|
||
"Schritt 1: Grenzpunkte laden …",
|
||
)
|
||
detail_progress.set_value(0)
|
||
|
||
punktort_layer = lade_punktort_ta(
|
||
vorgabe_schluessel, vg_geom, progress=detail_progress
|
||
)
|
||
|
||
if detail_progress.is_canceled():
|
||
detail_progress.close()
|
||
abgeschlossen = True
|
||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||
|
||
detail_progress.set_value(1)
|
||
|
||
if punktort_layer is None:
|
||
detail_progress.close()
|
||
abgeschlossen = True
|
||
if not detail_progress.is_canceled():
|
||
QMessageBox.critical(
|
||
tab_widget,
|
||
"WFS-Fehler",
|
||
"Die Grenzpunktdaten (PunktortTA) konnten nicht geladen werden.\n\n"
|
||
"Mögliche Ursache: Netzwerk- oder Proxy-Fehler.\n"
|
||
"Bitte prüfen Sie die QGIS-Proxy-Einstellungen "
|
||
"(Einstellungen -> Optionen -> Netzwerk) und den Log-Eintrag "
|
||
"unter Ansicht -> Bedienfelder -> Protokollnachrichten "
|
||
"(Kategorie 'sn_verfahrensgebiet').",
|
||
)
|
||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||
|
||
ergebnis_layer, identisch, kein_pta, fehlender_knick = knickpunkt_pruefung(
|
||
pruef_layer,
|
||
punktort_layer,
|
||
flst_layer,
|
||
progress=detail_progress,
|
||
knickpunkte=knickpunkte,
|
||
)
|
||
|
||
if detail_progress.is_canceled():
|
||
detail_progress.close()
|
||
abgeschlossen = True
|
||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||
|
||
detail_progress.set_value(gesamt_schritte)
|
||
detail_progress.close()
|
||
|
||
# ------------------------------------------------------------------
|
||
# Korrigiertes Polygon aus identischen + fehlenden Knickpunkten
|
||
# (nur für Visualisierung – shape_vg_layer bleibt das finale VG)
|
||
# ------------------------------------------------------------------
|
||
if ergebnis_layer and ergebnis_layer.isValid():
|
||
linien_layer_korr, polygon_layer_korr = erstelle_korrigiertes_polygon(
|
||
pruef_layer, ergebnis_layer, pruef_layer.crs().authid()
|
||
)
|
||
if polygon_layer_korr is not None and polygon_layer_korr.isValid():
|
||
if linien_layer_korr is not None and linien_layer_korr.isValid():
|
||
project.addMapLayer(linien_layer_korr)
|
||
project.addMapLayer(polygon_layer_korr)
|
||
|
||
if ergebnis_layer and ergebnis_layer.isValid():
|
||
project.addMapLayer(ergebnis_layer)
|
||
|
||
show_info_dialog(
|
||
"Detailprüfung abgeschlossen",
|
||
f"Knickpunkte geprüft: {n_knick}\n"
|
||
f" Identisch (grün): {identisch}\n"
|
||
f" Kein PunktortTA (rot): {kein_pta}\n"
|
||
f" Fehlende Knickpunkte (orange): {fehlender_knick}",
|
||
parent=tab_widget,
|
||
)
|
||
|
||
abgeschlossen = True
|
||
return shape_vg_layer, flst_layer, bror_layer, vg_status
|
||
|
||
finally:
|
||
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
||
# Shape-VG und BROR bei Fehler/Abbruch entfernen
|
||
if not abgeschlossen:
|
||
if shape_was_added_by_us and shape_vg_layer is not None:
|
||
try:
|
||
project.removeMapLayer(shape_vg_layer.id())
|
||
except Exception:
|
||
pass
|
||
if bror_was_added_by_us and bror_layer is not None:
|
||
try:
|
||
project.removeMapLayer(bror_layer.id())
|
||
except Exception:
|
||
pass
|