Umringsprüfung-Segmentbasiert
This commit is contained in:
@@ -0,0 +1,311 @@
|
|||||||
|
"""sn_verfahrensgebiet/functions/umringspruefung.py – Umringsprüfung (Variante 1).
|
||||||
|
|
||||||
|
Vergleicht den Umring (Boundary) des BauRaumOderBodenordnungsrecht-Polygons
|
||||||
|
mit den Grenzen der angrenzenden Flurstücke und hebt Abweichungen hervor.
|
||||||
|
|
||||||
|
**Algorithmus (Variante 1 – linienbasierter Umringvergleich):**
|
||||||
|
|
||||||
|
1. BROR-Polygon → Umringslinie via :func:`sn_basis.functions.qgiscore_wrapper.polygon_zu_linie`
|
||||||
|
(QGIS-3/4-kompatibel).
|
||||||
|
2. Flurstücke, die den BROR-Umring berühren oder schneiden, werden gefiltert.
|
||||||
|
3. Gefilterte Flurstücke → Umringslinien.
|
||||||
|
4. Union aller Flurstücksumringe.
|
||||||
|
5. ``BROR-Umring.difference(Flurstücksumrings-Union)`` → Abweichungen (rot).
|
||||||
|
6. ``BROR-Umring.difference(rote_Segmente)`` → Übereinstimmungen (grün).
|
||||||
|
7. Ergebnislayer mit Kategoriesymbologie (grün/rot) und Legendenbeschriftung.
|
||||||
|
|
||||||
|
**Rückgabe:** ``(result_layer, conflict_count)``
|
||||||
|
|
||||||
|
- ``result_layer``: Temporärer Linienlayer mit Feld ``typ``
|
||||||
|
(``"identisch"`` = grün, ``"Abweichung"`` = rot).
|
||||||
|
- ``conflict_count``: Anzahl der roten Konfliktsegmente (Parts).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
from sn_basis.functions.qgiscore_wrapper import (
|
||||||
|
QgsVectorLayer,
|
||||||
|
QgsFeature,
|
||||||
|
QgsField,
|
||||||
|
QgsGeometry,
|
||||||
|
polygon_zu_linie,
|
||||||
|
)
|
||||||
|
from sn_basis.functions.qt_wrapper import QVariant
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Konstanten
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#: Geometrische Toleranz für den Linienvergleich (EPSG:25833 = Meter).
|
||||||
|
#: Der BROR-Umring wird gegen die um diesen Betrag gepufferten Flurstücksumringe
|
||||||
|
#: verglichen, um numerische Rundungsfehler aus ALKIS-Koordinaten abzufangen.
|
||||||
|
LINIE_TOLERANZ_M: float = 0.0001
|
||||||
|
|
||||||
|
ERGEBNIS_LAYER_NAME = "Umringsprüfung"
|
||||||
|
FELD_TYP = "typ"
|
||||||
|
WERT_IDENTISCH = "identisch"
|
||||||
|
WERT_ABWEICHUNG = "Abweichung"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Interne Hilfsfunktionen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _zerlege_in_teile(geom: object) -> List[object]:
|
||||||
|
"""Zerlegt eine (Multi-)LineString-Geometrie in einzelne Teile.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
geom :
|
||||||
|
``QgsGeometry`` vom Typ LineString oder MultiLineString.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[QgsGeometry]
|
||||||
|
Liste einzelner Geometrieteile; leer wenn ``geom`` None oder leer ist.
|
||||||
|
"""
|
||||||
|
if geom is None:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
if getattr(geom, "isEmpty", lambda: True)():
|
||||||
|
return []
|
||||||
|
# asGeometryCollection() gibt eigene Kopien zurück – kein Ownership-Problem.
|
||||||
|
# QgsGeometry(part) über geom.parts() ist in QGIS 4.x aus den Python-Bindings
|
||||||
|
# entfernt worden und führt zu Absturz beim Rendern.
|
||||||
|
parts = geom.asGeometryCollection()
|
||||||
|
if parts:
|
||||||
|
return [p for p in parts if p is not None and not p.isEmpty()]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Fallback: Geometrie nicht zerlegbar → als Ganzes zurückgeben
|
||||||
|
try:
|
||||||
|
if not geom.isEmpty():
|
||||||
|
return [geom]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _erstelle_ergebnis_layer(crs_authid: str) -> object:
|
||||||
|
"""Erzeugt den leeren Ergebnislayer mit Feld ``typ``.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
crs_authid :
|
||||||
|
CRS-Kennung des Quell-Layers (z. B. ``"EPSG:25833"``).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
QgsVectorLayer
|
||||||
|
Leerer Memory-Layer vom Typ LineString.
|
||||||
|
"""
|
||||||
|
layer = QgsVectorLayer(
|
||||||
|
f"LineString?crs={crs_authid}", ERGEBNIS_LAYER_NAME, "memory"
|
||||||
|
)
|
||||||
|
pr = layer.dataProvider()
|
||||||
|
pr.addAttributes([QgsField(FELD_TYP, QVariant.String)])
|
||||||
|
layer.updateFields()
|
||||||
|
return layer
|
||||||
|
|
||||||
|
|
||||||
|
def _setze_kategorien_symbologie(layer) -> None:
|
||||||
|
"""Wendet Kategoriesymbologie an: grün = identisch, rot = Abweichung.
|
||||||
|
|
||||||
|
Wird bei fehlendem QGIS-Kontext still ignoriert (Unit-Test-Robustheit).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from qgis.core import ( # type: ignore[import]
|
||||||
|
QgsCategorizedSymbolRenderer,
|
||||||
|
QgsRendererCategory,
|
||||||
|
QgsLineSymbol,
|
||||||
|
)
|
||||||
|
|
||||||
|
green_sym = QgsLineSymbol.createSimple(
|
||||||
|
{"color": "0,180,0,255", "line_width": "0.5"}
|
||||||
|
)
|
||||||
|
red_sym = QgsLineSymbol.createSimple(
|
||||||
|
{"color": "220,0,0,255", "line_width": "0.8"}
|
||||||
|
)
|
||||||
|
categories = [
|
||||||
|
QgsRendererCategory(
|
||||||
|
WERT_IDENTISCH,
|
||||||
|
green_sym,
|
||||||
|
"identisch (übereinstimmend)",
|
||||||
|
),
|
||||||
|
QgsRendererCategory(
|
||||||
|
WERT_ABWEICHUNG,
|
||||||
|
red_sym,
|
||||||
|
"Abweichung (nicht in Flurstücken)",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
renderer = QgsCategorizedSymbolRenderer(FELD_TYP, categories)
|
||||||
|
layer.setRenderer(renderer)
|
||||||
|
except Exception:
|
||||||
|
pass # Kein QGIS-Kontext (Tests) → Standarddarstellung behalten
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Öffentliche API
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def umringspruefung(
|
||||||
|
bror_layer,
|
||||||
|
flst_layer,
|
||||||
|
progress=None,
|
||||||
|
linie_toleranz_m: float = LINIE_TOLERANZ_M,
|
||||||
|
) -> Tuple[Optional[object], int]:
|
||||||
|
"""Variante 1: Linienbasierter Umringvergleich.
|
||||||
|
|
||||||
|
Ermittelt, welche Teile des BROR-Umrings mit den Flurstücksgrenzen
|
||||||
|
übereinstimmen (grün) und welche keine Entsprechung haben (rot).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bror_layer :
|
||||||
|
BauRaumOderBodenordnungsrecht-Memory-Layer (Polygon).
|
||||||
|
flst_layer :
|
||||||
|
Flurstücke-Layer (Polygon, WFS oder Memory).
|
||||||
|
progress :
|
||||||
|
Optionaler ``ProgressDialog`` aus
|
||||||
|
:mod:`sn_basis.functions.dialog_wrapper` für Fortschrittsanzeige
|
||||||
|
und Abbrucherkennung.
|
||||||
|
linie_toleranz_m : float
|
||||||
|
Geometrische Puffertoleranz für den Linienvergleich in
|
||||||
|
CRS-Einheiten (Standardwert :data:`LINIE_TOLERANZ_M` = 0.001 m).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[Optional[QgsVectorLayer], int]
|
||||||
|
``(result_layer, conflict_count)``
|
||||||
|
|
||||||
|
- ``result_layer``: Linienlayer mit grünen/roten Segmenten,
|
||||||
|
oder ``None`` bei Abbruch oder fehlgeschlagener Verarbeitung.
|
||||||
|
- ``conflict_count``: Anzahl der roten Konfliktsegmente (BROR-Teile
|
||||||
|
ohne Entsprechung in den Flurstücksgrenzen).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _canceled() -> bool:
|
||||||
|
return progress is not None and progress.is_canceled()
|
||||||
|
|
||||||
|
def _schritt(label: str, wert: int) -> None:
|
||||||
|
if progress is not None:
|
||||||
|
progress.set_label(label)
|
||||||
|
progress.set_value(wert)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 1. BROR-Geometrie und Umringslinie ermitteln
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
_schritt("Umringsprüfung: BROR-Umring ermitteln …", 0)
|
||||||
|
|
||||||
|
bror_feats = [
|
||||||
|
f for f in bror_layer.getFeatures()
|
||||||
|
if f.geometry() and not f.geometry().isEmpty()
|
||||||
|
]
|
||||||
|
if not bror_feats:
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
bror_union = QgsGeometry.unaryUnion([f.geometry() for f in bror_feats])
|
||||||
|
if bror_union is None or bror_union.isEmpty():
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
bror_boundary = polygon_zu_linie(bror_union)
|
||||||
|
if bror_boundary is None or bror_boundary.isEmpty():
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
crs_authid = bror_layer.crs().authid()
|
||||||
|
|
||||||
|
if _canceled():
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2. Relevante Flurstücke: nur jene, deren Geometrie den BROR-Umring
|
||||||
|
# berührt oder schneidet
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
_schritt("Umringsprüfung: Relevante Flurstücke filtern …", 1)
|
||||||
|
|
||||||
|
relevante_boundaries: List[object] = []
|
||||||
|
for feat in flst_layer.getFeatures():
|
||||||
|
if _canceled():
|
||||||
|
return None, 0
|
||||||
|
geom = feat.geometry()
|
||||||
|
if geom is None or geom.isEmpty():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if geom.intersects(bror_boundary):
|
||||||
|
boundary = polygon_zu_linie(geom)
|
||||||
|
if boundary is not None and not boundary.isEmpty():
|
||||||
|
relevante_boundaries.append(boundary)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if relevante_boundaries:
|
||||||
|
flst_boundary_union = QgsGeometry.unaryUnion(relevante_boundaries)
|
||||||
|
else:
|
||||||
|
flst_boundary_union = None
|
||||||
|
|
||||||
|
if _canceled():
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 3. Segmentvergleich: Differenz und Übereinstimmung berechnen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
_schritt("Umringsprüfung: Segmentvergleich …", 2)
|
||||||
|
|
||||||
|
ergebnis_layer = _erstelle_ergebnis_layer(crs_authid)
|
||||||
|
pr = ergebnis_layer.dataProvider()
|
||||||
|
feats_to_add: List[object] = []
|
||||||
|
conflict_count = 0
|
||||||
|
|
||||||
|
if flst_boundary_union is None or flst_boundary_union.isEmpty():
|
||||||
|
# Kein Flurstück berührt den BROR-Umring → alles ist Abweichung
|
||||||
|
for part in _zerlege_in_teile(bror_boundary):
|
||||||
|
feat = QgsFeature()
|
||||||
|
feat.setGeometry(part)
|
||||||
|
feat.setAttributes([WERT_ABWEICHUNG])
|
||||||
|
feats_to_add.append(feat)
|
||||||
|
conflict_count += 1
|
||||||
|
else:
|
||||||
|
# Flurstücksumringe leicht puffern für Toleranz-Matching
|
||||||
|
if linie_toleranz_m > 0:
|
||||||
|
flst_fuer_diff = flst_boundary_union.buffer(linie_toleranz_m, 4)
|
||||||
|
else:
|
||||||
|
flst_fuer_diff = flst_boundary_union
|
||||||
|
|
||||||
|
# Rote Segmente: BROR-Teile ohne Entsprechung in Flurstücksgrenzen
|
||||||
|
try:
|
||||||
|
red_geom = bror_boundary.difference(flst_fuer_diff)
|
||||||
|
except Exception:
|
||||||
|
red_geom = None
|
||||||
|
|
||||||
|
# Grüne Segmente: BROR-Teile, die nicht rot sind
|
||||||
|
try:
|
||||||
|
if red_geom and not red_geom.isEmpty():
|
||||||
|
green_geom = bror_boundary.difference(red_geom)
|
||||||
|
else:
|
||||||
|
green_geom = bror_boundary
|
||||||
|
except Exception:
|
||||||
|
green_geom = None
|
||||||
|
|
||||||
|
for part in _zerlege_in_teile(green_geom):
|
||||||
|
feat = QgsFeature()
|
||||||
|
feat.setGeometry(part)
|
||||||
|
feat.setAttributes([WERT_IDENTISCH])
|
||||||
|
feats_to_add.append(feat)
|
||||||
|
|
||||||
|
for part in _zerlege_in_teile(red_geom):
|
||||||
|
feat = QgsFeature()
|
||||||
|
feat.setGeometry(part)
|
||||||
|
feat.setAttributes([WERT_ABWEICHUNG])
|
||||||
|
feats_to_add.append(feat)
|
||||||
|
conflict_count += 1
|
||||||
|
|
||||||
|
if _canceled():
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
pr.addFeatures(feats_to_add)
|
||||||
|
ergebnis_layer.updateExtents()
|
||||||
|
_setze_kategorien_symbologie(ergebnis_layer)
|
||||||
|
|
||||||
|
_schritt("Umringsprüfung abgeschlossen.", 3)
|
||||||
|
return ergebnis_layer, conflict_count
|
||||||
@@ -1,26 +1,34 @@
|
|||||||
"""sn_verfahrensgebiet/functions/verfahrensgebiet_alkis_komplett.py – Vollständiger VG-Lade-Workflow.
|
"""sn_verfahrensgebiet/functions/verfahrensgebiet_alkis_komplett.py – Vollständiger VG-Lade-Workflow.
|
||||||
|
|
||||||
Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets
|
Orchestriert den vollständigen Arbeitsablauf zum Laden eines Verfahrensgebiets
|
||||||
aus ALKIS mit anschließender Flurstücks-Verschneidung:
|
aus ALKIS mit anschließender Flurstücks-Verschneidung, Flächenvergleich und
|
||||||
|
optionaler Umringsprüfung:
|
||||||
|
|
||||||
1. Verfahrensgebiet aus ``AX_BauRaumOderBodenordnungsrecht`` per VKZ laden.
|
1. Verfahrensgebiet aus ``AX_BauRaumOderBodenordnungsrecht`` per VKZ laden.
|
||||||
2. BBox des VG ableiten.
|
2. Gemarkungsschlüssel per Geometrie aus ``AX_Gemarkung`` vorschlagen.
|
||||||
3. Gemarkungsschlüssel per BBox aus ``AX_Gemarkung`` vorschlagen.
|
3. Flurstücke per Dialog (mit Vorschlag) laden.
|
||||||
4. Flurstücke per Dialog (mit Vorschlag) laden.
|
4. Flurstücke räumlich auf das VG einschränken (``selectByLocation``).
|
||||||
5. Flurstücke räumlich auf das VG einschränken (``selectByLocation``).
|
5. Aus der Auswahl den enriched Verfahrensgebiet-Memory-Layer erzeugen.
|
||||||
6. Aus der Auswahl den enriched Verfahrensgebiet-Memory-Layer erzeugen.
|
6. Flächenvergleich zwischen BROR-Objekt und abgeleitetem Verfahrensgebiet.
|
||||||
7. ALKIS-VG-Hilfs-Layer entfernen, enriched Layer zurückgeben.
|
|
||||||
|
|
||||||
Der enriched Layer enthält die Felder ``flurstueckskennzeichen`` und
|
- **Übereinstimmung** (≤ Toleranz): modale OK-Meldung, Ablauf beendet.
|
||||||
``gemarkung_ax_gemarkung_schluessel_gemarkungsnummer`` des
|
- **Abweichung**: Ja/Nein-Abfrage für Detailprüfung.
|
||||||
:mod:`~sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service`-Schemas
|
|
||||||
und ist damit vollständig kompatibel mit ``ergaenze_verfahrensgebiet`` und
|
7. Optionale Umringsprüfung (Variante 1):
|
||||||
``entferne_aus_verfahrensgebiet``.
|
|
||||||
|
- BROR-Umring gegen Flurstücksgrenzen vergleichen.
|
||||||
|
- Ergebnislayer (grün/rot) ins Projekt einfügen.
|
||||||
|
- Abschlussmeldung mit Konfliktanzahl.
|
||||||
|
|
||||||
|
Das BROR-Objekt (``AX_BauRaumOderBodenordnungsrecht``) bleibt bei Erfolg als
|
||||||
|
temporärer Layer ``BauRaumOrdnungsrecht (ALKIS)`` im Projekt erhalten.
|
||||||
|
|
||||||
|
**Rückgabe:**
|
||||||
|
``Tuple[enriched_layer, flst_layer, bror_layer, LoadStatus]``
|
||||||
|
|
||||||
**Abbruchverhalten:** Jeder Schritt, der :attr:`LoadStatus.NONE` zurückgibt
|
**Abbruchverhalten:** Jeder Schritt, der :attr:`LoadStatus.NONE` zurückgibt
|
||||||
oder kein Ergebnis liefert, bricht den Workflow ab. ALKIS-Hilfs-Layer werden
|
oder kein Ergebnis liefert, bricht den Workflow ab. Der BROR-Layer wird nur
|
||||||
dabei gesäubert, damit das Projekt nicht in einem inkonsistenten Zustand
|
bei Fehler/Abbruch entfernt (wenn er in diesem Lauf hinzugefügt wurde).
|
||||||
verbleibt.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -29,8 +37,13 @@ from typing import Optional, Tuple
|
|||||||
|
|
||||||
from sn_basis.functions import QgsProject
|
from sn_basis.functions import QgsProject
|
||||||
from sn_basis.functions import QMessageBox
|
from sn_basis.functions import QMessageBox
|
||||||
from sn_basis.functions.message_wrapper import info, warning
|
from sn_basis.functions.message_wrapper import warning
|
||||||
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 (
|
||||||
|
create_progress_dialog,
|
||||||
|
show_info_dialog,
|
||||||
|
ask_yes_no,
|
||||||
|
)
|
||||||
|
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import (
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import (
|
||||||
verfahrensgebiet_alkis,
|
verfahrensgebiet_alkis,
|
||||||
@@ -42,12 +55,17 @@ from sn_verfahrensgebiet.functions.gemarkung_alkis import (
|
|||||||
from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis
|
from sn_verfahrensgebiet.functions.flurstueck_alkis import flurstueck_alkis
|
||||||
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
|
from sn_verfahrensgebiet.functions.verfahrensgebiet_edit_service import (
|
||||||
erstelle_verfahrensgebiet_aus_auswahl,
|
erstelle_verfahrensgebiet_aus_auswahl,
|
||||||
FELD_KENNZEICHEN,
|
|
||||||
VERF_LAYER_NAME,
|
VERF_LAYER_NAME,
|
||||||
)
|
)
|
||||||
|
from sn_verfahrensgebiet.functions.umringspruefung import umringspruefung
|
||||||
|
|
||||||
# Feld für das Verfahrenskennzeichen (VKZ) im enriched Layer
|
# Feldname für das Verfahrenskennzeichen im enriched Layer
|
||||||
FELD_VKZ = "verfahrensnummer"
|
FELD_VKZ = "verfahrensnummer"
|
||||||
|
#: Anzeigename des BROR-Layers im Projekt
|
||||||
|
BROR_LAYER_NAME = "BauRaumOrdnungsrecht (ALKIS)"
|
||||||
|
#: Projektvariable für die Flächentoleranz (in m²); Default 0
|
||||||
|
FLAECHEN_TOLERANZ_VARIABLE = "flaechen_toleranz_m2"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -57,7 +75,7 @@ FELD_VKZ = "verfahrensnummer"
|
|||||||
def _entferne_hilfs_layer(project, layer_name: str) -> None:
|
def _entferne_hilfs_layer(project, layer_name: str) -> None:
|
||||||
"""Entfernt alle Layer mit dem angegebenen Namen aus dem Projekt.
|
"""Entfernt alle Layer mit dem angegebenen Namen aus dem Projekt.
|
||||||
|
|
||||||
Wird verwendet, um den temporären ALKIS-VG-Layer nach der Verschneidung
|
Wird verwendet, um temporäre WFS-Hilfs-Layer nach der Verschneidung
|
||||||
aus dem Projekt zu bereinigen.
|
aus dem Projekt zu bereinigen.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -72,24 +90,22 @@ def _entferne_hilfs_layer(project, layer_name: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _select_by_location(flst_layer, vg_layer) -> int:
|
def _select_by_location(flst_layer, vg_layer) -> int:
|
||||||
"""Selektiert Flurstücke, die das Verfahrensgebiet überlappen oder darin liegen.
|
"""Selektiert Flurstücke, die innerhalb des Verfahrensgebiets liegen.
|
||||||
|
|
||||||
Verwendet ``processing.run("native:selectbylocation")`` mit den Prädikaten
|
Verwendet ``processing.run("native:selectbylocation")`` mit Prädikat 6
|
||||||
``overlaps`` (5) und ``within`` (6). Flurstücke, die das VG nur berühren
|
(within). Flurstücke, die das VG nur berühren, werden ausgeschlossen.
|
||||||
(gemeinsame Grenzlinie oder -punkt, aber kein gemeinsames Inneres), werden
|
|
||||||
damit ausgeschlossen.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
flst_layer :
|
flst_layer :
|
||||||
Flurstücks-WFS-Layer (Quelle der Selektion).
|
Flurstücks-Layer (Quelle der Selektion).
|
||||||
vg_layer :
|
vg_layer :
|
||||||
Verfahrensgebiet-Memory-Layer (Schnittmaske).
|
Verfahrensgebiet-Layer (Schnittmaske).
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
int
|
int
|
||||||
Anzahl der selektierten Features.
|
Anzahl der selektierten Features; 0 bei Fehler oder leerem Ergebnis.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import processing # type: ignore[import]
|
import processing # type: ignore[import]
|
||||||
@@ -98,15 +114,14 @@ def _select_by_location(flst_layer, vg_layer) -> int:
|
|||||||
"native:selectbylocation",
|
"native:selectbylocation",
|
||||||
{
|
{
|
||||||
"INPUT": flst_layer,
|
"INPUT": flst_layer,
|
||||||
"PREDICATE": [6], # 5 = overlaps, 6 = within; schließt reines Berühren aus
|
"PREDICATE": [6], # 6 = within; schließt reines Berühren aus
|
||||||
"INTERSECT": vg_layer,
|
"INTERSECT": vg_layer,
|
||||||
"METHOD": 0, # 0 = neue Selektion
|
"METHOD": 0, # 0 = neue Selektion
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return flst_layer.selectedFeatureCount()
|
return flst_layer.selectedFeatureCount()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# Fallback: keine Selektion (z. B. im Unit-Test-Modus)
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -114,9 +129,6 @@ def _fuege_vkz_feld_hinzu(layer, vkz: str) -> None:
|
|||||||
"""Fügt das Feld ``verfahrensnummer`` zum enriched Layer hinzu und befüllt
|
"""Fügt das Feld ``verfahrensnummer`` zum enriched Layer hinzu und befüllt
|
||||||
es mit dem Verfahrenskennzeichen, sofern dieses nicht leer ist.
|
es mit dem Verfahrenskennzeichen, sofern dieses nicht leer ist.
|
||||||
|
|
||||||
Das Feld wird nur hinzugefügt, wenn ``vkz`` nicht leer ist, damit der
|
|
||||||
Layer ohne Projektvariable weiterhin funktioniert.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
layer :
|
layer :
|
||||||
@@ -149,83 +161,161 @@ def _fuege_vkz_feld_hinzu(layer, vkz: str) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _lese_flaechen_toleranz() -> float:
|
||||||
|
"""Liest die Flächentoleranz aus der Projektvariable (Default: 0.0 m²).
|
||||||
|
|
||||||
|
Die Projektvariable :data:`FLAECHEN_TOLERANZ_VARIABLE` kann in den
|
||||||
|
Projekteigenschaften gesetzt werden. Fehlt sie oder ist sie ungültig,
|
||||||
|
wird 0.0 zurückgegeben (exakter Vergleich).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
Toleranz in m² (bei EPSG:25833 identisch mit den Flächen-Einheiten).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
raw = get_variable(FLAECHEN_TOLERANZ_VARIABLE)
|
||||||
|
if raw is not None and str(raw).strip():
|
||||||
|
return float(str(raw).strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def _flaeche_des_layers(layer) -> float:
|
||||||
|
"""Summiert die Flächen aller Features eines Layers (in CRS-Einheiten²).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
layer :
|
||||||
|
Beliebiger Polygon-Layer.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
Summe aller Feature-Flächen; 0.0 wenn keine Features vorhanden.
|
||||||
|
"""
|
||||||
|
total = 0.0
|
||||||
|
for feat in layer.getFeatures():
|
||||||
|
try:
|
||||||
|
geom = feat.geometry()
|
||||||
|
if geom and not geom.isEmpty():
|
||||||
|
total += geom.area()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def _vergleiche_flaechen(
|
||||||
|
bror_layer, vg_layer, toleranz_m2: float = 0.0
|
||||||
|
) -> Tuple[float, float, bool]:
|
||||||
|
"""Vergleicht Flächen von BROR- und Verfahrensgebiet-Layer.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bror_layer :
|
||||||
|
BauRaumOrdnungsrecht-Layer.
|
||||||
|
vg_layer :
|
||||||
|
Enriched Verfahrensgebiet-Layer.
|
||||||
|
toleranz_m2 :
|
||||||
|
Maximale zulässige Flächendifferenz in m².
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[float, float, bool]
|
||||||
|
``(bror_flaeche, vg_flaeche, stimmt_ueberein)``
|
||||||
|
"""
|
||||||
|
bror_flaeche = _flaeche_des_layers(bror_layer)
|
||||||
|
vg_flaeche = _flaeche_des_layers(vg_layer)
|
||||||
|
stimmt_ueberein = abs(bror_flaeche - vg_flaeche) <= toleranz_m2
|
||||||
|
return bror_flaeche, vg_flaeche, stimmt_ueberein
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Öffentliche API
|
# Öffentliche API
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def verfahrensgebiet_alkis_komplett(
|
def verfahrensgebiet_alkis_komplett(
|
||||||
tab_widget,
|
tab_widget,
|
||||||
) -> Tuple[Optional[object], Optional[object], LoadStatus]:
|
) -> Tuple[Optional[object], Optional[object], Optional[object], LoadStatus]:
|
||||||
"""Vollständiger VG-Lade-Workflow: ALKIS-VG → BBox → Gemarkungen →
|
"""Vollständiger VG-Lade-Workflow: ALKIS-VG → Gemarkungen →
|
||||||
Flurstücke → Verschneidung → enriched Memory-Layer.
|
Flurstücke → Verschneidung → Flächenvergleich → opt. Umringsprüfung.
|
||||||
|
|
||||||
Schrittfolge:
|
Schrittfolge:
|
||||||
|
|
||||||
1. ``verfahrensgebiet_alkis(tab_widget)`` – Verfahrensgebiet temporär laden
|
1. ``verfahrensgebiet_alkis(tab_widget)`` – BROR-Objekt als Memory-Layer.
|
||||||
2. ``vg_layer.extent()`` – BBox ableiten
|
2. ``gemarkung_schluessel_und_namen_von_vg_geometrie(geom)`` – Schlüssel.
|
||||||
3. ``gemarkung_schluessel_und_namen_by_bbox(extent)`` – Schlüssel vorschlagen
|
3. ``flurstueck_alkis(...)`` – Flurstücke laden.
|
||||||
4. ``flurstueck_alkis(tab_widget, vorgabe_schluessel, vorgabe_namen, bbox)`` – Flurstücke laden
|
4. ``_select_by_location(...)`` – räumliche Selektion.
|
||||||
5. ``_select_by_location(flst_layer, vg_layer)`` – räumliche Selektion
|
5. ``erstelle_verfahrensgebiet_aus_auswahl(...)`` – enriched Layer.
|
||||||
6. ``erstelle_verfahrensgebiet_aus_auswahl(flst_layer)`` – enriched Layer erzeugen
|
6. ``_vergleiche_flaechen(...)`` – Flächenvergleich als Gate.
|
||||||
7. Ggf. VKZ-Feld setzen, ALKIS-VG-Hilfs-Layer entfernen
|
7. Ggf. ``umringspruefung(...)`` nach Nutzerbestätigung.
|
||||||
|
|
||||||
|
Der BROR-Layer (``BauRaumOrdnungsrecht (ALKIS)``) bleibt bei Erfolg
|
||||||
|
im Projekt. Bei Abbruch/Fehler wird er entfernt, falls er in diesem
|
||||||
|
Lauf hinzugefügt wurde.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
tab_widget :
|
tab_widget :
|
||||||
Eltern-Widget für alle Dialoge (weitergereicht an Unteraufrufe).
|
Eltern-Widget für alle Dialoge.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Tuple[Optional[enriched_layer], Optional[flst_layer], LoadStatus]
|
Tuple[Optional[enriched_layer], Optional[flst_layer], Optional[bror_layer], LoadStatus]
|
||||||
- ``enriched_layer``: Memory-Layer „Verfahrensgebiet" mit
|
- ``enriched_layer``: Memory-Layer „Verfahrensgebiet".
|
||||||
``flurstueckskennzeichen``- und ``gemarkung``-Feldern, oder ``None``
|
- ``flst_layer``: Flurstücke-Layer (bleibt im Projekt).
|
||||||
bei Abbruch/Fehler.
|
- ``bror_layer``: BROR-Layer ``BauRaumOrdnungsrecht (ALKIS)``
|
||||||
- ``flst_layer``: Geladener WFS-Flurstück-Layer (bleibt im Projekt),
|
(bleibt bei Erfolg im Projekt).
|
||||||
oder ``None``.
|
- :class:`LoadStatus`: ``FIRST``, ``RELOAD`` oder ``NONE``.
|
||||||
- ``LoadStatus``: Status des VG-Lade-Vorgangs (``FIRST``, ``RELOAD``
|
|
||||||
oder ``NONE`` bei Abbruch).
|
|
||||||
|
|
||||||
Notes
|
|
||||||
-----
|
|
||||||
Der temporäre ALKIS-VG-Layer (aus ``AX_BauRaumOderBodenordnungsrecht``)
|
|
||||||
wird nach der Verschneidung aus dem Projekt entfernt. Im Projekt verbleiben
|
|
||||||
nur der enriched Memory-Layer und der Flurstücks-WFS-Layer.
|
|
||||||
"""
|
"""
|
||||||
project = QgsProject.instance()
|
project = QgsProject.instance()
|
||||||
alkis_vg_layer = None
|
alkis_vg_layer = None
|
||||||
|
bror_was_added_by_us = False
|
||||||
|
abgeschlossen = False
|
||||||
|
|
||||||
|
# Fortschrittsdialog für den Basisworkflow (7 Schritte)
|
||||||
|
progress = create_progress_dialog(
|
||||||
|
7, "Verfahrensgebiet laden", "Initialisierung …"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 1: VG aus ALKIS laden (Zwischenobjekt)
|
# Schritt 1: VG aus ALKIS laden (BROR-Objekt → Memory-Layer)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
info("VG laden", "Schritt 1/6: Verfahrensgebiet aus ALKIS laden …", duration=3)
|
progress.set_label("Schritt 1/6: Verfahrensgebiet aus ALKIS laden …")
|
||||||
alkis_vg_layer, vg_status, vg_features = verfahrensgebiet_alkis(tab_widget)
|
progress.set_value(0)
|
||||||
|
if progress.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
alkis_vg_layer, vg_status, _ = verfahrensgebiet_alkis(tab_widget)
|
||||||
|
|
||||||
if vg_status == LoadStatus.NONE or not alkis_vg_layer:
|
if vg_status == LoadStatus.NONE or not alkis_vg_layer:
|
||||||
return None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
# BROR-Layer umbenennen, um Namenskonflikt mit enriched VG-Layer zu vermeiden
|
||||||
|
alkis_vg_layer.setName(BROR_LAYER_NAME)
|
||||||
|
|
||||||
# Temporär ins Projekt hinzufügen, damit selectByLocation ihn findet
|
|
||||||
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
|
||||||
|
|
||||||
|
progress.set_value(1)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 2 (entfernt): BBox-Ableitung war hier – WFS unterstützt kein
|
# Schritt 2: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
||||||
# BBox-Filter, daher wird der Extent nicht mehr benötigt.
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
progress.set_label("Schritt 2/6: Gemarkungsschlüssel ermitteln …")
|
||||||
|
if progress.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
bror_feats = list(alkis_vg_layer.getFeatures())
|
||||||
# Schritt 3: Gemarkungsschlüssel per Geometrie-Spatial-Join ermitteln
|
vg_geom = bror_feats[0].geometry() if bror_feats else None
|
||||||
# ------------------------------------------------------------------
|
|
||||||
info("VG laden", "Schritt 2/6: Gemarkungsschlüssel ermitteln \u2026", duration=3)
|
schluessel_namen = (
|
||||||
# VG-Geometrie aus den Roh-WFS-Features bilden (Union, damit
|
gemarkung_schluessel_und_namen_von_vg_geometrie(vg_geom)
|
||||||
# alle Teilflächen berücksichtigt werden)
|
if vg_geom else []
|
||||||
from sn_basis.functions import QgsGeometry as _QgsGeometry
|
)
|
||||||
if vg_features:
|
|
||||||
vg_geom = _QgsGeometry.unaryUnion([f.geometry() for f in vg_features])
|
|
||||||
else:
|
|
||||||
# Bei KEEP-Status keine WFS-Features vorhanden; Fallback auf Layergeometrie
|
|
||||||
vg_geom = next(alkis_vg_layer.getFeatures()).geometry()
|
|
||||||
schluessel_namen = gemarkung_schluessel_und_namen_von_vg_geometrie(vg_geom)
|
|
||||||
vorgabe_schluessel = [nr for nr, _ in schluessel_namen]
|
vorgabe_schluessel = [nr for nr, _ in schluessel_namen]
|
||||||
vorgabe_namen = [nm for _, nm in schluessel_namen if nm]
|
vorgabe_namen = [nm for _, nm in schluessel_namen if nm]
|
||||||
|
|
||||||
@@ -236,25 +326,34 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
duration=5,
|
duration=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
progress.set_value(2)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 4: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt)
|
# Schritt 3: Flurstücke laden (mit Nutzer-Dialog, Vorschlag befüllt)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
info("VG laden", "Schritt 3/6: Flurstücke aus ALKIS laden …", duration=3)
|
progress.set_label("Schritt 3/6: Flurstücke aus ALKIS laden …")
|
||||||
|
if progress.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
flst_layer, flst_status = flurstueck_alkis(
|
flst_layer, flst_status = flurstueck_alkis(
|
||||||
tab_widget,
|
tab_widget,
|
||||||
vorgabe_schluessel=vorgabe_schluessel,
|
vorgabe_schluessel=vorgabe_schluessel,
|
||||||
vorgabe_namen=vorgabe_namen if vorgabe_namen else None,
|
vorgabe_namen=vorgabe_namen if vorgabe_namen else None,
|
||||||
# bbox wird nicht übergeben – WFS unterstützt keinen BBox-Filter
|
ohne_dialog=True,
|
||||||
ohne_dialog=True, # kein Nutzer-Dialog in der Pipeline
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if flst_status == LoadStatus.NONE or not flst_layer:
|
if flst_status == LoadStatus.NONE or not flst_layer:
|
||||||
return None, None, LoadStatus.NONE
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
progress.set_value(3)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 5: Räumliche Selektion
|
# Schritt 4: Räumliche Selektion
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
info("VG laden", "Schritt 4/6: Flurstücke räumlich selektieren …", duration=3)
|
progress.set_label("Schritt 4/6: Flurstücke räumlich selektieren …")
|
||||||
|
if progress.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
anzahl = _select_by_location(flst_layer, alkis_vg_layer)
|
anzahl = _select_by_location(flst_layer, alkis_vg_layer)
|
||||||
|
|
||||||
if anzahl == 0:
|
if anzahl == 0:
|
||||||
@@ -264,12 +363,17 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
"Es wurden keine Flurstücke gefunden, die das Verfahrensgebiet schneiden.\n"
|
"Es wurden keine Flurstücke gefunden, die das Verfahrensgebiet schneiden.\n"
|
||||||
"Bitte Gemarkungsschlüssel und Ausdehnung prüfen.",
|
"Bitte Gemarkungsschlüssel und Ausdehnung prüfen.",
|
||||||
)
|
)
|
||||||
return None, flst_layer, LoadStatus.NONE
|
return None, flst_layer, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
progress.set_value(4)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Schritt 6: Enriched Memory-Layer aus Auswahl erzeugen
|
# Schritt 5: Enriched Memory-Layer aus Auswahl erzeugen
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
info("VG laden", "Schritt 5/6: Verfahrensgebiet-Layer erzeugen …", duration=3)
|
progress.set_label("Schritt 5/6: Verfahrensgebiet-Layer erzeugen …")
|
||||||
|
if progress.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
enriched_layer, erfolgreich, fehlgeschlagen = erstelle_verfahrensgebiet_aus_auswahl(
|
enriched_layer, erfolgreich, fehlgeschlagen = erstelle_verfahrensgebiet_aus_auswahl(
|
||||||
flst_layer
|
flst_layer
|
||||||
)
|
)
|
||||||
@@ -280,30 +384,97 @@ def verfahrensgebiet_alkis_komplett(
|
|||||||
"Fehler",
|
"Fehler",
|
||||||
"Verfahrensgebiet-Layer konnte nicht erstellt werden.",
|
"Verfahrensgebiet-Layer konnte nicht erstellt werden.",
|
||||||
)
|
)
|
||||||
return None, flst_layer, LoadStatus.NONE
|
return None, flst_layer, None, LoadStatus.NONE
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Schritt 7: VKZ-Feld setzen (optional, wenn Projektvariable vorhanden)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
vkz = get_variable("verfahrensnummer") or ""
|
vkz = get_variable("verfahrensnummer") or ""
|
||||||
_fuege_vkz_feld_hinzu(enriched_layer, vkz)
|
_fuege_vkz_feld_hinzu(enriched_layer, vkz)
|
||||||
|
|
||||||
info(
|
progress.set_value(5)
|
||||||
"VG laden",
|
|
||||||
f"Schritt 6/6: Fertig. {erfolgreich} Flurstücke verarbeitet, "
|
# ------------------------------------------------------------------
|
||||||
f"{fehlgeschlagen} fehlgeschlagen.",
|
# Schritt 6: Flächenvergleich als Gate
|
||||||
duration=5,
|
# ------------------------------------------------------------------
|
||||||
|
progress.set_label("Schritt 6/6: Flächenvergleich …")
|
||||||
|
if progress.is_canceled():
|
||||||
|
return None, None, None, LoadStatus.NONE
|
||||||
|
|
||||||
|
toleranz = _lese_flaechen_toleranz()
|
||||||
|
bror_flaeche, vg_flaeche, stimmt_ueberein = _vergleiche_flaechen(
|
||||||
|
alkis_vg_layer, enriched_layer, toleranz
|
||||||
)
|
)
|
||||||
|
|
||||||
return enriched_layer, flst_layer, vg_status
|
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 enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Flächenabweichung → Nutzerabfrage für Detailprüfung
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
progress.close()
|
||||||
|
diff = abs(bror_flaeche - vg_flaeche)
|
||||||
|
starten = ask_yes_no(
|
||||||
|
"Abweichung erkannt",
|
||||||
|
f"Abweichung zwischen BauRaumOrdnungsrecht-Objekt und abgeleitetem "
|
||||||
|
f"Verfahrensgebiet erkannt. Detailprüfung starten?\n\n"
|
||||||
|
f"BauRaumOrdnungsrecht: {bror_flaeche:.2f} m²\n"
|
||||||
|
f"Verfahrensgebiet: {vg_flaeche:.2f} m²\n"
|
||||||
|
f"Differenz: {diff:.2f} m²",
|
||||||
|
default=True,
|
||||||
|
parent=tab_widget,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not starten:
|
||||||
|
abgeschlossen = True
|
||||||
|
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Umringsprüfung (Variante 1: linienbasierter Umringvergleich)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
umring_progress = create_progress_dialog(
|
||||||
|
3, "Detailprüfung (Umringe)", "Starte Umringsprüfung …"
|
||||||
|
)
|
||||||
|
|
||||||
|
ergebnis_layer, konflikt_anzahl = umringspruefung(
|
||||||
|
alkis_vg_layer, flst_layer, progress=umring_progress
|
||||||
|
)
|
||||||
|
|
||||||
|
if umring_progress.is_canceled():
|
||||||
|
umring_progress.close()
|
||||||
|
abgeschlossen = True
|
||||||
|
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
||||||
|
|
||||||
|
umring_progress.set_value(3)
|
||||||
|
umring_progress.close()
|
||||||
|
|
||||||
|
if ergebnis_layer and ergebnis_layer.isValid():
|
||||||
|
project.addMapLayer(ergebnis_layer)
|
||||||
|
|
||||||
|
show_info_dialog(
|
||||||
|
"Detailprüfung",
|
||||||
|
f"Detailprüfung abgeschlossen. "
|
||||||
|
f"Es wurden {konflikt_anzahl} Konfliktstellen identifiziert.",
|
||||||
|
parent=tab_widget,
|
||||||
|
)
|
||||||
|
|
||||||
|
abgeschlossen = True
|
||||||
|
return enriched_layer, flst_layer, alkis_vg_layer, vg_status
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Hilfs-Layer (ALKIS-VG) stets aus dem Projekt entfernen
|
# WFS-Temp-Layer stets aus dem Projekt entfernen
|
||||||
if alkis_vg_layer is not None:
|
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
||||||
_entferne_hilfs_layer(project, VERF_LAYER_NAME)
|
# BROR-Layer bei Fehler/Abbruch entfernen (nur wenn in diesem Lauf hinzugefügt)
|
||||||
# Auch nach Layername des ALKIS-Temp-Layers bereinigen
|
if not abgeschlossen and bror_was_added_by_us and alkis_vg_layer is not None:
|
||||||
_entferne_hilfs_layer(project, "TempVerfahrensgebiet")
|
|
||||||
try:
|
try:
|
||||||
project.removeMapLayer(alkis_vg_layer.id())
|
project.removeMapLayer(alkis_vg_layer.id())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,39 @@ Unit-Tests für :mod:`sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_kompl
|
|||||||
|
|
||||||
Getestete Szenarien:
|
Getestete Szenarien:
|
||||||
|
|
||||||
Erfolgsfall:
|
Erfolgsfall (Flächenübereinstimmung):
|
||||||
- Vollständiger Workflow → enriched Layer + flst_layer + LoadStatus.FIRST
|
- Vollständiger Workflow → enriched Layer + flst_layer + bror_layer + LoadStatus.FIRST
|
||||||
- VKZ aus Projektvariable wird als Feld gesetzt (wenn vorhanden)
|
- VKZ aus Projektvariable wird als Feld gesetzt (wenn vorhanden)
|
||||||
- Kein VKZ in Projektvariable → Feld wird nicht gesetzt (kein Fehler)
|
- Kein VKZ in Projektvariable → Feld wird nicht gesetzt (kein Fehler)
|
||||||
|
|
||||||
Abbruchpfade:
|
Abbruchpfade:
|
||||||
- verfahrensgebiet_alkis gibt LoadStatus.NONE → (None, None, NONE)
|
- verfahrensgebiet_alkis gibt LoadStatus.NONE → (None, None, None, NONE)
|
||||||
- flurstueck_alkis gibt LoadStatus.NONE → (None, None, NONE)
|
- flurstueck_alkis gibt LoadStatus.NONE → (None, None, None, NONE)
|
||||||
- Keine Flurstücke selektiert → (None, flst_layer, NONE) + Warnung
|
- Keine Flurstücke selektiert → (None, flst_layer, None, NONE) + Warnung
|
||||||
- erstelle_verfahrensgebiet_aus_auswahl liefert ungültigen Layer → (None, flst_layer, NONE)
|
- erstelle_verfahrensgebiet_aus_auswahl liefert ungültigen Layer → (None, flst_layer, None, NONE)
|
||||||
- Cleanup: ALKIS-VG-Hilfs-Layer wird in allen Fällen aus Projekt entfernt
|
- Progress-Abbruch nach Schritt 1 → sofortiger sauberer Rückgabestatus
|
||||||
|
|
||||||
|
Flächenvergleich:
|
||||||
|
- Übereinstimmung → show_info_dialog, keine Umringsprüfung
|
||||||
|
- Abweichung + Nein → kein Umringsprüfungs-Aufruf
|
||||||
|
- Abweichung + Ja → umringspruefung aufgerufen, Ergebnislayer ins Projekt
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
- BROR-Layer wird bei Erfolg NICHT entfernt
|
||||||
|
- BROR-Layer wird bei Fehler entfernt (wenn in diesem Lauf hinzugefügt)
|
||||||
|
- TempVerfahrensgebiet wird stets entfernt
|
||||||
|
|
||||||
Hilfsfunktionen:
|
Hilfsfunktionen:
|
||||||
- _select_by_location: processing-Fallback liefert 0 → keine Exception
|
- _select_by_location: processing-Fallback liefert 0 → keine Exception
|
||||||
- _fuege_vkz_feld_hinzu: leeres VKZ wird ignoriert
|
- _fuege_vkz_feld_hinzu: leeres VKZ wird ignoriert
|
||||||
|
- _vergleiche_flaechen: korrekte Toleranzlogik
|
||||||
|
- _lese_flaechen_toleranz: Default 0.0, parst Projektvariable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, MagicMock, call
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Minimales QGIS-Mock-Setup
|
# Minimales QGIS-Mock-Setup
|
||||||
@@ -46,6 +58,8 @@ from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import (
|
|||||||
verfahrensgebiet_alkis_komplett,
|
verfahrensgebiet_alkis_komplett,
|
||||||
_select_by_location,
|
_select_by_location,
|
||||||
_fuege_vkz_feld_hinzu,
|
_fuege_vkz_feld_hinzu,
|
||||||
|
_vergleiche_flaechen,
|
||||||
|
_lese_flaechen_toleranz,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,7 +74,8 @@ class _DummyRect:
|
|||||||
def yMaximum(self): return 5658000.0
|
def yMaximum(self): return 5658000.0
|
||||||
|
|
||||||
|
|
||||||
def _make_layer(valid=True, name="Layer", lid="L1", feature_count=1):
|
def _make_layer(valid=True, name="Layer", lid="L1", feature_count=1, area=1000.0):
|
||||||
|
"""Erzeugt einen konfigurierten Mock-Layer mit getFeatures-Support."""
|
||||||
layer = MagicMock()
|
layer = MagicMock()
|
||||||
layer.isValid.return_value = valid
|
layer.isValid.return_value = valid
|
||||||
layer.id.return_value = lid
|
layer.id.return_value = lid
|
||||||
@@ -68,6 +83,18 @@ def _make_layer(valid=True, name="Layer", lid="L1", feature_count=1):
|
|||||||
layer.extent.return_value = _DummyRect()
|
layer.extent.return_value = _DummyRect()
|
||||||
layer.featureCount.return_value = feature_count
|
layer.featureCount.return_value = feature_count
|
||||||
layer.selectedFeatureCount.return_value = feature_count
|
layer.selectedFeatureCount.return_value = feature_count
|
||||||
|
|
||||||
|
mock_geom = MagicMock()
|
||||||
|
mock_geom.isEmpty.return_value = False
|
||||||
|
mock_geom.area.return_value = area
|
||||||
|
mock_geom.boundary.return_value = MagicMock(isEmpty=lambda: True)
|
||||||
|
mock_feat = MagicMock()
|
||||||
|
mock_feat.geometry.return_value = mock_geom
|
||||||
|
layer.getFeatures.return_value = [mock_feat]
|
||||||
|
|
||||||
|
mock_crs = MagicMock()
|
||||||
|
mock_crs.authid.return_value = "EPSG:25833"
|
||||||
|
layer.crs.return_value = mock_crs
|
||||||
return layer
|
return layer
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +117,34 @@ class _DummyProject:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def _make_progress_mock(canceled=False):
|
||||||
|
prog = MagicMock()
|
||||||
|
prog.is_canceled.return_value = canceled
|
||||||
|
return prog
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Patch-Konstanten
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_MOD = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett"
|
||||||
|
_PATCH_VG_ALKIS = f"{_MOD}.verfahrensgebiet_alkis"
|
||||||
|
_PATCH_GEM = f"{_MOD}.gemarkung_schluessel_und_namen_von_vg_geometrie"
|
||||||
|
_PATCH_FLST = f"{_MOD}.flurstueck_alkis"
|
||||||
|
_PATCH_SELECT = f"{_MOD}._select_by_location"
|
||||||
|
_PATCH_ERSTELLE = f"{_MOD}.erstelle_verfahrensgebiet_aus_auswahl"
|
||||||
|
_PATCH_VKZ = f"{_MOD}._fuege_vkz_feld_hinzu"
|
||||||
|
_PATCH_PROJECT = f"{_MOD}.QgsProject"
|
||||||
|
_PATCH_MSGBOX = f"{_MOD}.QMessageBox"
|
||||||
|
_PATCH_WARNING = f"{_MOD}.warning"
|
||||||
|
_PATCH_GET_VAR = f"{_MOD}.get_variable"
|
||||||
|
_PATCH_PROGRESS = f"{_MOD}.create_progress_dialog"
|
||||||
|
_PATCH_VERGLEICHE = f"{_MOD}._vergleiche_flaechen"
|
||||||
|
_PATCH_SHOW_INFO = f"{_MOD}.show_info_dialog"
|
||||||
|
_PATCH_ASK_YES_NO = f"{_MOD}.ask_yes_no"
|
||||||
|
_PATCH_UMRING = f"{_MOD}.umringspruefung"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tests: _select_by_location
|
# Tests: _select_by_location
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -97,35 +152,26 @@ class _DummyProject:
|
|||||||
class TestSelectByLocation(unittest.TestCase):
|
class TestSelectByLocation(unittest.TestCase):
|
||||||
|
|
||||||
def test_processing_not_available_returns_zero(self):
|
def test_processing_not_available_returns_zero(self):
|
||||||
"""Wenn processing nicht importierbar ist → 0 ohne Exception."""
|
flst, vg = MagicMock(), MagicMock()
|
||||||
flst = MagicMock()
|
|
||||||
vg = MagicMock()
|
|
||||||
with patch.dict("sys.modules", {"processing": None}):
|
with patch.dict("sys.modules", {"processing": None}):
|
||||||
result = _select_by_location(flst, vg)
|
result = _select_by_location(flst, vg)
|
||||||
self.assertEqual(result, 0)
|
self.assertEqual(result, 0)
|
||||||
|
|
||||||
def test_processing_success_returns_count(self):
|
def test_processing_success_returns_count(self):
|
||||||
"""processing.run erfolgreich → selectedFeatureCount des Layers."""
|
|
||||||
flst = MagicMock()
|
flst = MagicMock()
|
||||||
flst.selectedFeatureCount.return_value = 7
|
flst.selectedFeatureCount.return_value = 7
|
||||||
vg = MagicMock()
|
vg = MagicMock()
|
||||||
|
mock_proc = MagicMock()
|
||||||
mock_processing = MagicMock()
|
with patch.dict("sys.modules", {"processing": mock_proc}):
|
||||||
with patch.dict("sys.modules", {"processing": mock_processing}):
|
|
||||||
result = _select_by_location(flst, vg)
|
result = _select_by_location(flst, vg)
|
||||||
|
|
||||||
self.assertEqual(result, 7)
|
self.assertEqual(result, 7)
|
||||||
|
|
||||||
def test_processing_exception_returns_zero(self):
|
def test_processing_exception_returns_zero(self):
|
||||||
"""Exception in processing.run → 0 ohne Propagation."""
|
flst, vg = MagicMock(), MagicMock()
|
||||||
flst = MagicMock()
|
mock_proc = MagicMock()
|
||||||
vg = MagicMock()
|
mock_proc.run.side_effect = RuntimeError("Kein QGIS")
|
||||||
|
with patch.dict("sys.modules", {"processing": mock_proc}):
|
||||||
mock_processing = MagicMock()
|
|
||||||
mock_processing.run.side_effect = RuntimeError("Kein QGIS")
|
|
||||||
with patch.dict("sys.modules", {"processing": mock_processing}):
|
|
||||||
result = _select_by_location(flst, vg)
|
result = _select_by_location(flst, vg)
|
||||||
|
|
||||||
self.assertEqual(result, 0)
|
self.assertEqual(result, 0)
|
||||||
|
|
||||||
|
|
||||||
@@ -136,225 +182,427 @@ class TestSelectByLocation(unittest.TestCase):
|
|||||||
class TestFuegeVkzFeldHinzu(unittest.TestCase):
|
class TestFuegeVkzFeldHinzu(unittest.TestCase):
|
||||||
|
|
||||||
def test_empty_vkz_does_nothing(self):
|
def test_empty_vkz_does_nothing(self):
|
||||||
"""Leeres VKZ → kein Zugriff auf Layer."""
|
|
||||||
layer = MagicMock()
|
layer = MagicMock()
|
||||||
_fuege_vkz_feld_hinzu(layer, "")
|
_fuege_vkz_feld_hinzu(layer, "")
|
||||||
layer.dataProvider.assert_not_called()
|
layer.dataProvider.assert_not_called()
|
||||||
|
|
||||||
def test_none_vkz_does_nothing(self):
|
def test_none_vkz_does_nothing(self):
|
||||||
"""None-VKZ → kein Zugriff auf Layer."""
|
|
||||||
layer = MagicMock()
|
layer = MagicMock()
|
||||||
_fuege_vkz_feld_hinzu(layer, None)
|
_fuege_vkz_feld_hinzu(layer, None)
|
||||||
layer.dataProvider.assert_not_called()
|
layer.dataProvider.assert_not_called()
|
||||||
|
|
||||||
def test_vkz_field_added(self):
|
def test_vkz_field_added(self):
|
||||||
"""Nicht-leeres VKZ → Provider.addAttributes wird aufgerufen."""
|
|
||||||
pr = MagicMock()
|
pr = MagicMock()
|
||||||
pr.addAttributes.return_value = True
|
pr.addAttributes.return_value = True
|
||||||
pr.changeAttributeValues.return_value = True
|
pr.changeAttributeValues.return_value = True
|
||||||
|
|
||||||
layer = MagicMock()
|
layer = MagicMock()
|
||||||
layer.dataProvider.return_value = pr
|
layer.dataProvider.return_value = pr
|
||||||
layer.fields.return_value.indexFromName.return_value = 2
|
layer.fields.return_value.indexFromName.return_value = 2
|
||||||
layer.getFeatures.return_value = []
|
layer.getFeatures.return_value = []
|
||||||
|
|
||||||
_fuege_vkz_feld_hinzu(layer, "VF-2024-001")
|
_fuege_vkz_feld_hinzu(layer, "VF-2024-001")
|
||||||
|
|
||||||
pr.addAttributes.assert_called_once()
|
pr.addAttributes.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tests: _vergleiche_flaechen / _lese_flaechen_toleranz
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestFlaechenvergleich(unittest.TestCase):
|
||||||
|
|
||||||
|
def _layer(self, area: float):
|
||||||
|
layer = MagicMock()
|
||||||
|
geom = MagicMock()
|
||||||
|
geom.isEmpty.return_value = False
|
||||||
|
geom.area.return_value = area
|
||||||
|
feat = MagicMock()
|
||||||
|
feat.geometry.return_value = geom
|
||||||
|
layer.getFeatures.return_value = [feat]
|
||||||
|
return layer
|
||||||
|
|
||||||
|
def test_gleiche_flaeche_stimmt_ueberein(self):
|
||||||
|
self.assertTrue(_vergleiche_flaechen(self._layer(1234.5), self._layer(1234.5), 0.0)[2])
|
||||||
|
|
||||||
|
def test_verschiedene_flaeche_stimmt_nicht_ueberein(self):
|
||||||
|
self.assertFalse(_vergleiche_flaechen(self._layer(1234.5), self._layer(1200.0), 0.0)[2])
|
||||||
|
|
||||||
|
def test_differenz_innerhalb_toleranz(self):
|
||||||
|
self.assertTrue(_vergleiche_flaechen(self._layer(1234.5), self._layer(1234.8), 1.0)[2])
|
||||||
|
|
||||||
|
def test_differenz_ausserhalb_toleranz(self):
|
||||||
|
self.assertFalse(_vergleiche_flaechen(self._layer(1234.5), self._layer(1236.0), 1.0)[2])
|
||||||
|
|
||||||
|
def test_flaechen_werte_korrekt(self):
|
||||||
|
bror_f, vg_f, _ = _vergleiche_flaechen(self._layer(800.0), self._layer(750.0))
|
||||||
|
self.assertAlmostEqual(bror_f, 800.0)
|
||||||
|
self.assertAlmostEqual(vg_f, 750.0)
|
||||||
|
|
||||||
|
@patch(f"{_MOD}.get_variable", return_value=None)
|
||||||
|
def test_lese_toleranz_default_ist_null(self, _):
|
||||||
|
self.assertEqual(_lese_flaechen_toleranz(), 0.0)
|
||||||
|
|
||||||
|
@patch(f"{_MOD}.get_variable", return_value="2.5")
|
||||||
|
def test_lese_toleranz_parst_string(self, _):
|
||||||
|
self.assertAlmostEqual(_lese_flaechen_toleranz(), 2.5)
|
||||||
|
|
||||||
|
@patch(f"{_MOD}.get_variable", return_value="ungueltig")
|
||||||
|
def test_lese_toleranz_ungueltig_fallback_null(self, _):
|
||||||
|
self.assertEqual(_lese_flaechen_toleranz(), 0.0)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tests: verfahrensgebiet_alkis_komplett (Hauptworkflow)
|
# Tests: verfahrensgebiet_alkis_komplett (Hauptworkflow)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_PATCH_VG_ALKIS = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.verfahrensgebiet_alkis"
|
|
||||||
_PATCH_GEM_BY_BBOX = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.gemarkung_schluessel_und_namen_by_bbox"
|
|
||||||
_PATCH_FLST = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.flurstueck_alkis"
|
|
||||||
_PATCH_SELECT = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett._select_by_location"
|
|
||||||
_PATCH_ERSTELLE = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.erstelle_verfahrensgebiet_aus_auswahl"
|
|
||||||
_PATCH_VKZ = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett._fuege_vkz_feld_hinzu"
|
|
||||||
_PATCH_PROJECT = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.QgsProject"
|
|
||||||
_PATCH_MSGBOX = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.QMessageBox"
|
|
||||||
_PATCH_INFO = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.info"
|
|
||||||
_PATCH_WARNING = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.warning"
|
|
||||||
_PATCH_GET_VAR = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.get_variable"
|
|
||||||
|
|
||||||
|
|
||||||
class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
|
||||||
|
|
||||||
def _make_project(self):
|
# ------------------------------------------------------------------ #
|
||||||
proj = _DummyProject()
|
# Abbruchpfade
|
||||||
mock = MagicMock()
|
# ------------------------------------------------------------------ #
|
||||||
mock.instance.return_value = proj
|
|
||||||
# Leere mapLayersByName für Cleanup
|
|
||||||
proj._layers_by_name = {"Verfahrensgebiet": [], "TempVerfahrensgebiet": []}
|
|
||||||
return mock, proj
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
@patch(_PATCH_PROGRESS)
|
||||||
# 1. VG-ALKIS liefert NONE → sofortiger Abbruch #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
@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):
|
def test_vg_alkis_none_aborts(self, 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, [])
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
enriched, flst, status = verfahrensgebiet_alkis_komplett(None)
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
self.assertIsNone(enriched)
|
self.assertIsNone(enriched)
|
||||||
self.assertIsNone(flst)
|
self.assertIsNone(flst)
|
||||||
|
self.assertIsNone(bror)
|
||||||
self.assertEqual(status, LoadStatus.NONE)
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
@patch(_PATCH_PROGRESS)
|
||||||
# 2. flurstueck_alkis liefert NONE → Abbruch nach Gemarkungsabfrage #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
@patch(_PATCH_FLST)
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_GEM_BY_BBOX)
|
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
def test_flst_none_aborts(self, mock_vg, mock_gem, mock_flst, mock_proj):
|
def test_flst_none_aborts(self, 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="Verfahrensgebiet")
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST)
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
mock_gem.return_value = [("1234", "Muster")]
|
mock_gem.return_value = [("1234", "Muster")]
|
||||||
mock_flst.return_value = (None, LoadStatus.NONE)
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
enriched, flst, status = verfahrensgebiet_alkis_komplett(None)
|
with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)):
|
||||||
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
self.assertIsNone(enriched)
|
self.assertIsNone(enriched)
|
||||||
self.assertIsNone(flst)
|
self.assertIsNone(flst)
|
||||||
self.assertEqual(status, LoadStatus.NONE)
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
@patch(_PATCH_PROGRESS)
|
||||||
# 3. Keine Selektion → Warnung + (None, flst_layer, NONE) #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
@patch(_PATCH_MSGBOX)
|
@patch(_PATCH_MSGBOX)
|
||||||
@patch(_PATCH_SELECT)
|
@patch(_PATCH_SELECT)
|
||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_FLST)
|
@patch(_PATCH_FLST)
|
||||||
@patch(_PATCH_GEM_BY_BBOX)
|
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
def test_no_selection_returns_none_enriched(
|
def test_no_selection_returns_none_enriched(
|
||||||
self, mock_vg, mock_gem, mock_flst, mock_proj, mock_select, mock_msgbox
|
self, 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="Verfahrensgebiet")
|
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, [])
|
||||||
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 # keine Selektion
|
mock_select.return_value = 0
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
enriched, flst, status = verfahrensgebiet_alkis_komplett(None)
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
self.assertIsNone(enriched)
|
self.assertIsNone(enriched)
|
||||||
self.assertIs(flst, flst_layer)
|
self.assertIs(flst, flst_layer)
|
||||||
|
self.assertIsNone(bror)
|
||||||
self.assertEqual(status, LoadStatus.NONE)
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
mock_msgbox.warning.assert_called_once()
|
mock_msgbox.warning.assert_called_once()
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
@patch(_PATCH_PROGRESS)
|
||||||
# 4. Vollständiger Erfolgsfall #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
@patch(_PATCH_VKZ)
|
|
||||||
@patch(_PATCH_GET_VAR)
|
|
||||||
@patch(_PATCH_ERSTELLE)
|
|
||||||
@patch(_PATCH_SELECT)
|
|
||||||
@patch(_PATCH_PROJECT)
|
|
||||||
@patch(_PATCH_FLST)
|
|
||||||
@patch(_PATCH_GEM_BY_BBOX)
|
|
||||||
@patch(_PATCH_VG_ALKIS)
|
|
||||||
def test_success_full_workflow(
|
|
||||||
self,
|
|
||||||
mock_vg, mock_gem, mock_flst, mock_proj,
|
|
||||||
mock_select, mock_erstelle, mock_get_var, mock_vkz_fn,
|
|
||||||
):
|
|
||||||
"""Vollständiger Workflow → enriched Layer + flst Layer + FIRST."""
|
|
||||||
proj_instance = _DummyProject()
|
|
||||||
mock_proj.instance.return_value = proj_instance
|
|
||||||
|
|
||||||
vg_layer = _make_layer(name="Verfahrensgebiet")
|
|
||||||
flst_layer = _make_layer(name="Flurstücke", lid="FLST1")
|
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", lid="VG_ENRICHED", valid=True)
|
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST)
|
|
||||||
mock_gem.return_value = [("1234", "Muster")]
|
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
|
||||||
mock_select.return_value = 3
|
|
||||||
mock_erstelle.return_value = (enriched, 3, 0)
|
|
||||||
mock_get_var.return_value = "VF-2024-001"
|
|
||||||
|
|
||||||
result_enriched, result_flst, status = verfahrensgebiet_alkis_komplett(None)
|
|
||||||
|
|
||||||
self.assertIs(result_enriched, enriched)
|
|
||||||
self.assertIs(result_flst, flst_layer)
|
|
||||||
self.assertEqual(status, LoadStatus.FIRST)
|
|
||||||
mock_vkz_fn.assert_called_once_with(enriched, "VF-2024-001")
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
# 5. Kein VKZ → _fuege_vkz_feld_hinzu mit leerem String #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
@patch(_PATCH_VKZ)
|
|
||||||
@patch(_PATCH_GET_VAR)
|
|
||||||
@patch(_PATCH_ERSTELLE)
|
|
||||||
@patch(_PATCH_SELECT)
|
|
||||||
@patch(_PATCH_PROJECT)
|
|
||||||
@patch(_PATCH_FLST)
|
|
||||||
@patch(_PATCH_GEM_BY_BBOX)
|
|
||||||
@patch(_PATCH_VG_ALKIS)
|
|
||||||
def test_no_vkz_variable_passes_empty_string(
|
|
||||||
self,
|
|
||||||
mock_vg, mock_gem, mock_flst, mock_proj,
|
|
||||||
mock_select, mock_erstelle, mock_get_var, mock_vkz_fn,
|
|
||||||
):
|
|
||||||
proj_instance = _DummyProject()
|
|
||||||
mock_proj.instance.return_value = proj_instance
|
|
||||||
|
|
||||||
vg_layer = _make_layer(name="Verfahrensgebiet")
|
|
||||||
flst_layer = _make_layer(name="Flurstücke")
|
|
||||||
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
|
||||||
|
|
||||||
mock_vg.return_value = (vg_layer, LoadStatus.FIRST)
|
|
||||||
mock_gem.return_value = []
|
|
||||||
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
|
||||||
mock_select.return_value = 2
|
|
||||||
mock_erstelle.return_value = (enriched, 2, 0)
|
|
||||||
mock_get_var.return_value = "" # keine Projektvariable
|
|
||||||
|
|
||||||
verfahrensgebiet_alkis_komplett(None)
|
|
||||||
|
|
||||||
mock_vkz_fn.assert_called_once_with(enriched, "")
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
# 6. erstelle_verfahrensgebiet liefert ungültigen Layer #
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
@patch(_PATCH_MSGBOX)
|
@patch(_PATCH_MSGBOX)
|
||||||
@patch(_PATCH_ERSTELLE)
|
@patch(_PATCH_ERSTELLE)
|
||||||
@patch(_PATCH_SELECT)
|
@patch(_PATCH_SELECT)
|
||||||
@patch(_PATCH_PROJECT)
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
@patch(_PATCH_FLST)
|
@patch(_PATCH_FLST)
|
||||||
@patch(_PATCH_GEM_BY_BBOX)
|
|
||||||
@patch(_PATCH_VG_ALKIS)
|
@patch(_PATCH_VG_ALKIS)
|
||||||
def test_invalid_enriched_layer_returns_none(
|
def test_invalid_enriched_layer_returns_none(
|
||||||
self, mock_vg, mock_gem, mock_flst, mock_proj, mock_select, mock_erstelle, mock_msgbox
|
self, 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()
|
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, [])
|
||||||
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
|
||||||
mock_erstelle.return_value = (bad_enriched, 0, 5)
|
mock_erstelle.return_value = (bad_enriched, 0, 5)
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
enriched, flst, status = verfahrensgebiet_alkis_komplett(None)
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
self.assertIsNone(enriched)
|
self.assertIsNone(enriched)
|
||||||
self.assertIs(flst, flst_layer)
|
self.assertIs(flst, flst_layer)
|
||||||
|
self.assertIsNone(bror)
|
||||||
self.assertEqual(status, LoadStatus.NONE)
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
mock_msgbox.critical.assert_called_once()
|
mock_msgbox.critical.assert_called_once()
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_progress_cancel_after_step1_returns_none(self, mock_vg, mock_proj, mock_prog):
|
||||||
|
"""Abbruch nach Schritt 1 → sofortiger sauberer Rückgabestatus."""
|
||||||
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
|
||||||
|
prog = MagicMock()
|
||||||
|
prog.is_canceled.side_effect = [False, True] # 1. Aufruf False, 2. True
|
||||||
|
mock_prog.return_value = prog
|
||||||
|
|
||||||
|
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
self.assertIsNone(enriched)
|
||||||
|
self.assertEqual(status, LoadStatus.NONE)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Flächenvergleich – Übereinstimmung
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_SHOW_INFO)
|
||||||
|
@patch(_PATCH_VERGLEICHE)
|
||||||
|
@patch(_PATCH_VKZ)
|
||||||
|
@patch(_PATCH_GET_VAR)
|
||||||
|
@patch(_PATCH_ERSTELLE)
|
||||||
|
@patch(_PATCH_SELECT)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
|
@patch(_PATCH_FLST)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_success_flaechen_match_zeigt_info(
|
||||||
|
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select,
|
||||||
|
mock_erstelle, mock_get_var, mock_vkz, mock_vergl, mock_info, mock_prog,
|
||||||
|
):
|
||||||
|
"""Flächenübereinstimmung → show_info_dialog mit erwartetem Text."""
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
flst_layer = _make_layer(name="Flurstücke", lid="FLST1")
|
||||||
|
enriched = _make_layer(name="Verfahrensgebiet", lid="VG_ENRICHED", valid=True)
|
||||||
|
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
mock_gem.return_value = [("1234", "Muster")]
|
||||||
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
|
mock_select.return_value = 3
|
||||||
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
|
mock_get_var.return_value = "VF-2024-001"
|
||||||
|
mock_vergl.return_value = (1234.5, 1234.5, True)
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
result_enriched, result_flst, result_bror, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
self.assertIs(result_enriched, enriched)
|
||||||
|
self.assertIs(result_flst, flst_layer)
|
||||||
|
self.assertIs(result_bror, vg_layer)
|
||||||
|
self.assertEqual(status, LoadStatus.FIRST)
|
||||||
|
mock_vkz.assert_called_once_with(enriched, "VF-2024-001")
|
||||||
|
mock_info.assert_called_once()
|
||||||
|
self.assertIn("erwarteter Geometrie", mock_info.call_args[0][1])
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_SHOW_INFO)
|
||||||
|
@patch(_PATCH_VERGLEICHE)
|
||||||
|
@patch(_PATCH_VKZ)
|
||||||
|
@patch(_PATCH_GET_VAR)
|
||||||
|
@patch(_PATCH_ERSTELLE)
|
||||||
|
@patch(_PATCH_SELECT)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
|
@patch(_PATCH_FLST)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_no_vkz_variable_passes_empty_string(
|
||||||
|
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select,
|
||||||
|
mock_erstelle, mock_get_var, mock_vkz, mock_vergl, mock_info, mock_prog,
|
||||||
|
):
|
||||||
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
mock_gem.return_value = []
|
||||||
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
|
mock_select.return_value = 2
|
||||||
|
mock_erstelle.return_value = (enriched, 2, 0)
|
||||||
|
mock_get_var.return_value = ""
|
||||||
|
mock_vergl.return_value = (1000.0, 1000.0, True)
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
mock_vkz.assert_called_once_with(enriched, "")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Flächenvergleich – Abweichung + Nein
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_UMRING)
|
||||||
|
@patch(_PATCH_ASK_YES_NO)
|
||||||
|
@patch(_PATCH_VERGLEICHE)
|
||||||
|
@patch(_PATCH_VKZ)
|
||||||
|
@patch(_PATCH_GET_VAR)
|
||||||
|
@patch(_PATCH_ERSTELLE)
|
||||||
|
@patch(_PATCH_SELECT)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
|
@patch(_PATCH_FLST)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_flaechen_abweichung_nein_kein_umring(
|
||||||
|
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select,
|
||||||
|
mock_erstelle, mock_get_var, mock_vkz, mock_vergl, mock_ask, mock_umring, mock_prog,
|
||||||
|
):
|
||||||
|
"""Abweichung + Nein → umringspruefung wird NICHT aufgerufen."""
|
||||||
|
mock_proj.instance.return_value = _DummyProject()
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
mock_gem.return_value = []
|
||||||
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
|
mock_select.return_value = 3
|
||||||
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
|
mock_get_var.return_value = ""
|
||||||
|
mock_vergl.return_value = (1000.0, 800.0, False)
|
||||||
|
mock_ask.return_value = False
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
result_enriched, _, _, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
mock_umring.assert_not_called()
|
||||||
|
self.assertIs(result_enriched, enriched)
|
||||||
|
self.assertEqual(status, LoadStatus.FIRST)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Flächenvergleich – Abweichung + Ja → Umringsprüfung
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_SHOW_INFO)
|
||||||
|
@patch(_PATCH_UMRING)
|
||||||
|
@patch(_PATCH_ASK_YES_NO)
|
||||||
|
@patch(_PATCH_VERGLEICHE)
|
||||||
|
@patch(_PATCH_VKZ)
|
||||||
|
@patch(_PATCH_GET_VAR)
|
||||||
|
@patch(_PATCH_ERSTELLE)
|
||||||
|
@patch(_PATCH_SELECT)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
|
@patch(_PATCH_FLST)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_flaechen_abweichung_ja_startet_umringspruefung(
|
||||||
|
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select,
|
||||||
|
mock_erstelle, mock_get_var, mock_vkz, mock_vergl, mock_ask,
|
||||||
|
mock_umring, mock_info, mock_prog,
|
||||||
|
):
|
||||||
|
"""Abweichung + Ja → umringspruefung aufgerufen, Ergebnislayer ins Projekt,
|
||||||
|
Abschlussmeldung mit Konfliktanzahl."""
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
|
||||||
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
ergebnis_layer = _make_layer(name="Umringsprüfung", lid="UMRING_L")
|
||||||
|
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
mock_gem.return_value = []
|
||||||
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
|
mock_select.return_value = 3
|
||||||
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
|
mock_get_var.return_value = ""
|
||||||
|
mock_vergl.return_value = (1000.0, 800.0, False)
|
||||||
|
mock_ask.return_value = True
|
||||||
|
mock_umring.return_value = (ergebnis_layer, 4)
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
result_enriched, _, _, status = verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
mock_umring.assert_called_once()
|
||||||
|
self.assertIn(ergebnis_layer, proj_inst.added)
|
||||||
|
mock_info.assert_called_once()
|
||||||
|
self.assertIn("4", mock_info.call_args[0][1])
|
||||||
|
self.assertIn("Konfliktstellen", mock_info.call_args[0][1])
|
||||||
|
self.assertEqual(status, LoadStatus.FIRST)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
# Cleanup-Verhalten
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_SHOW_INFO)
|
||||||
|
@patch(_PATCH_VERGLEICHE)
|
||||||
|
@patch(_PATCH_VKZ)
|
||||||
|
@patch(_PATCH_GET_VAR)
|
||||||
|
@patch(_PATCH_ERSTELLE)
|
||||||
|
@patch(_PATCH_SELECT)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
|
@patch(_PATCH_FLST)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_bror_layer_bleibt_bei_erfolg_im_projekt(
|
||||||
|
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select,
|
||||||
|
mock_erstelle, mock_get_var, mock_vkz, mock_vergl, mock_info, mock_prog,
|
||||||
|
):
|
||||||
|
"""Bei Erfolg darf der BROR-Layer NICHT entfernt werden."""
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)", lid="BROR_ID")
|
||||||
|
flst_layer = _make_layer(name="Flurstücke")
|
||||||
|
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
|
||||||
|
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
mock_gem.return_value = []
|
||||||
|
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
|
||||||
|
mock_select.return_value = 3
|
||||||
|
mock_erstelle.return_value = (enriched, 3, 0)
|
||||||
|
mock_get_var.return_value = ""
|
||||||
|
mock_vergl.return_value = (1000.0, 1000.0, True)
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
self.assertNotIn("BROR_ID", proj_inst.removed)
|
||||||
|
|
||||||
|
@patch(_PATCH_PROGRESS)
|
||||||
|
@patch(_PATCH_PROJECT)
|
||||||
|
@patch(_PATCH_GEM)
|
||||||
|
@patch(_PATCH_VG_ALKIS)
|
||||||
|
def test_bror_layer_wird_bei_fehler_entfernt(
|
||||||
|
self, mock_vg, mock_gem, mock_proj, mock_prog
|
||||||
|
):
|
||||||
|
"""Bei Fehler (flst_alkis NONE) wird der BROR-Layer aus dem Projekt entfernt."""
|
||||||
|
proj_inst = _DummyProject()
|
||||||
|
mock_proj.instance.return_value = proj_inst
|
||||||
|
|
||||||
|
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)", lid="BROR_ERR")
|
||||||
|
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
|
||||||
|
mock_gem.return_value = []
|
||||||
|
mock_prog.return_value = _make_progress_mock()
|
||||||
|
|
||||||
|
with patch(_PATCH_FLST, return_value=(None, LoadStatus.NONE)):
|
||||||
|
verfahrensgebiet_alkis_komplett(None)
|
||||||
|
|
||||||
|
self.assertIn("BROR_ERR", proj_inst.removed)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ from sn_verfahrensgebiet.ui.tabs.working_tab import WorkingTab, LoadStatus
|
|||||||
|
|
||||||
|
|
||||||
class _DummyLayer:
|
class _DummyLayer:
|
||||||
def __init__(self, valid=True, layer_id="L1"):
|
def __init__(self, valid=True, layer_id="L1", layer_name="DummyLayer"):
|
||||||
self._valid = valid
|
self._valid = valid
|
||||||
self._id = layer_id
|
self._id = layer_id
|
||||||
|
self._name = layer_name
|
||||||
|
|
||||||
def isValid(self):
|
def isValid(self):
|
||||||
return self._valid
|
return self._valid
|
||||||
@@ -28,6 +29,9 @@ class _DummyLayer:
|
|||||||
def id(self):
|
def id(self):
|
||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
def extent(self):
|
def extent(self):
|
||||||
return object()
|
return object()
|
||||||
|
|
||||||
@@ -140,11 +144,11 @@ class TestWorkingTab(unittest.TestCase):
|
|||||||
mock_apply_style,
|
mock_apply_style,
|
||||||
mock_success,
|
mock_success,
|
||||||
):
|
):
|
||||||
"""verfahrensgebiet_alkis_komplett → 3-Tupel (enriched, flst, FIRST)."""
|
"""verfahrensgebiet_alkis_komplett → 4-Tupel (enriched, flst, bror, FIRST)."""
|
||||||
tab = self._make_tab()
|
tab = self._make_tab()
|
||||||
enriched = _DummyLayer(valid=True, layer_id="enriched_ok")
|
enriched = _DummyLayer(valid=True, layer_id="enriched_ok")
|
||||||
flst = _DummyLayer(valid=True, layer_id="flst_ok")
|
flst = _DummyLayer(valid=True, layer_id="flst_ok")
|
||||||
mock_alkis_komplett.return_value = (enriched, flst, LoadStatus.FIRST)
|
mock_alkis_komplett.return_value = (enriched, flst, None, LoadStatus.FIRST)
|
||||||
proj = _DummyProject()
|
proj = _DummyProject()
|
||||||
proj.mapLayersByName = lambda _n: [] # kein bestehender Flst-Layer
|
proj.mapLayersByName = lambda _n: [] # kein bestehender Flst-Layer
|
||||||
mock_project_instance.return_value = proj
|
mock_project_instance.return_value = proj
|
||||||
@@ -159,7 +163,7 @@ class TestWorkingTab(unittest.TestCase):
|
|||||||
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
@patch("sn_verfahrensgebiet.ui.tabs.working_tab.set_verfahrensgebiet_layer_id")
|
||||||
@patch(
|
@patch(
|
||||||
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis_komplett",
|
"sn_verfahrensgebiet.ui.tabs.working_tab.verfahrensgebiet_alkis_komplett",
|
||||||
return_value=(None, None, LoadStatus.NONE),
|
return_value=(None, None, None, LoadStatus.NONE),
|
||||||
)
|
)
|
||||||
def test_handle_verf_alkis_failure_clears_layer_var(self, _mock_alkis, mock_set_layer_id):
|
def test_handle_verf_alkis_failure_clears_layer_var(self, _mock_alkis, mock_set_layer_id):
|
||||||
"""Status NONE → Layer-Variable wird auf '' gesetzt."""
|
"""Status NONE → Layer-Variable wird auf '' gesetzt."""
|
||||||
|
|||||||
+43
-4
@@ -138,15 +138,16 @@ class WorkingTab(QWidget):
|
|||||||
self._restore_verf_layer_selection()
|
self._restore_verf_layer_selection()
|
||||||
|
|
||||||
def handle_verf_alkis(self) -> None:
|
def handle_verf_alkis(self) -> None:
|
||||||
"""Startet den vollständigen VG-Lade-Workflow (ALKIS → Gemarkungen → Flurstücke → Verschneidung).
|
"""Startet den vollständigen VG-Lade-Workflow (ALKIS → Gemarkungen → Flurstücke →
|
||||||
|
Verschneidung → Flächenvergleich → opt. Umringsprüfung).
|
||||||
|
|
||||||
Delegiert an :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.verfahrensgebiet_alkis_komplett`.
|
Delegiert an :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett.verfahrensgebiet_alkis_komplett`.
|
||||||
Nach Erfolg werden enriched VG-Layer und Flurstücks-Layer ins Projekt eingefügt,
|
Nach Erfolg werden enriched VG-Layer, Flurstücks-Layer und BROR-Layer
|
||||||
Styles angewendet und die UI-Elemente aktualisiert.
|
ins Projekt eingefügt, Styles angewendet und die UI-Elemente aktualisiert.
|
||||||
"""
|
"""
|
||||||
self._set_busy(self.btn_verf_alkis, True, "Aus ALKIS laden")
|
self._set_busy(self.btn_verf_alkis, True, "Aus ALKIS laden")
|
||||||
try:
|
try:
|
||||||
enriched_layer, flst_layer, status = verfahrensgebiet_alkis_komplett(self)
|
enriched_layer, flst_layer, bror_layer, status = verfahrensgebiet_alkis_komplett(self)
|
||||||
|
|
||||||
if status == LoadStatus.NONE or not enriched_layer:
|
if status == LoadStatus.NONE or not enriched_layer:
|
||||||
set_verfahrensgebiet_layer_id("")
|
set_verfahrensgebiet_layer_id("")
|
||||||
@@ -162,6 +163,10 @@ 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: einfache Darstellung setzen (roter Umring, transparent)
|
||||||
|
if bror_layer and bror_layer.isValid():
|
||||||
|
_setze_bror_stil(bror_layer)
|
||||||
|
|
||||||
# Enriched VG-Layer ins Projekt
|
# Enriched VG-Layer ins Projekt
|
||||||
project.addMapLayer(enriched_layer)
|
project.addMapLayer(enriched_layer)
|
||||||
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
apply_style(enriched_layer, "verfahrensgebiet.qml")
|
||||||
@@ -430,3 +435,37 @@ class WorkingTab(QWidget):
|
|||||||
# self._set_busy(self.btn_grenz_alkis, False, "Grenzpunkte aus ALKIS laden")
|
# self._set_busy(self.btn_grenz_alkis, False, "Grenzpunkte aus ALKIS laden")
|
||||||
from sn_basis.functions.message_wrapper import warning as _warn
|
from sn_basis.functions.message_wrapper import warning as _warn
|
||||||
_warn("Grenzpunkte", "Grenzpunkt-Pipeline ist noch nicht implementiert.", duration=5)
|
_warn("Grenzpunkte", "Grenzpunkt-Pipeline ist noch nicht implementiert.", duration=5)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Modul-Hilfsfunktionen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _setze_bror_stil(layer) -> None:
|
||||||
|
"""Setzt eine einfache Darstellung für den BauRaumOrdnungsrecht-Layer.
|
||||||
|
|
||||||
|
Roter Umring, halbtransparente Füllung; kein QGIS-Kontext → stilles Ignorieren.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
layer :
|
||||||
|
BauRaumOrdnungsrecht-Memory-Layer (Polygon).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from qgis.core import ( # type: ignore[import]
|
||||||
|
QgsSimpleFillSymbolLayer,
|
||||||
|
QgsFillSymbol,
|
||||||
|
QgsSingleSymbolRenderer,
|
||||||
|
)
|
||||||
|
from qgis.PyQt.QtGui import QColor # type: ignore[import]
|
||||||
|
from qgis.PyQt.QtCore import Qt # type: ignore[import]
|
||||||
|
|
||||||
|
fill = QgsSimpleFillSymbolLayer()
|
||||||
|
fill.setColor(QColor(220, 50, 50, 40)) # halbtransparent rot
|
||||||
|
fill.setStrokeColor(QColor(220, 50, 50))
|
||||||
|
fill.setStrokeWidth(0.8)
|
||||||
|
symbol = QgsFillSymbol([fill])
|
||||||
|
layer.setRenderer(QgsSingleSymbolRenderer(symbol))
|
||||||
|
layer.triggerRepaint()
|
||||||
|
except Exception:
|
||||||
|
pass # Kein QGIS-Kontext (Tests, Mock-Modus) → Standarddarstellung
|
||||||
|
|||||||
Reference in New Issue
Block a user