Files
Plugin_SN_Verfahrensgebiet/functions/umringstatistik_grenzpunkte.py
T

374 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""sn_verfahrensgebiet/functions/umringstatistik_grenzpunkte.py – Umringstatistik Grenzpunkte.
Extrahiert aus einem vorhandenen ``Grenzpunkte (ALKIS)``-Layer alle Punkte,
die auf dem Umring des Verfahrensgebiets liegen, und klassifiziert sie in
drei Kategorien:
1. **FGP** (grün): ``gp_festgestellterGrenzpunkt == "true"``
2. **<3cm** (gelb): ``gp_festgestellterGrenzpunkt != "true"``
UND ``qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe == 2100``
3. **>3cm** (rot): ``gp_festgestellterGrenzpunkt != "true"``
UND ``qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe > 2100``
4. **unbekannt** (grau): keiner der obigen Fälle
Der Umring-Test verwendet einen Abstandsschwellwert von :data:`UMRING_TOLERANZ_M`.
**Rückgabe:** Memory-Layer ``Umringstatistik Grenzpunkte`` oder ``None`` bei Fehler.
"""
from __future__ import annotations
from typing import Optional
from sn_basis.functions import QgsProject
from sn_basis.functions.qt_wrapper import QMessageBox
from sn_basis.functions.dialog_wrapper import create_progress_dialog
from sn_basis.functions.message_wrapper import warning
from sn_basis.functions.qgiscore_wrapper import (
QgsVectorLayer,
QgsFeature,
QgsField,
QgsGeometry,
polygon_zu_linie,
)
from sn_basis.functions.qt_wrapper import QVariant
# ---------------------------------------------------------------------------
# Konstanten
# ---------------------------------------------------------------------------
#: Anzeigename des Ergebnis-Layers.
ERGEBNIS_LAYER_NAME = "Umringstatistik Grenzpunkte"
#: Feldname für die Klassifikationskategorie im Ergebnis-Layer.
FELD_KATEGORIE = "kategorie"
#: Feldname für ``gp_festgestellterGrenzpunkt`` im Grenzpunkte(ALKIS)-Layer.
FELD_FESTGESTELLT = "gp_festgestellterGrenzpunkt"
#: Feldname für das Genauigkeitsattribut im Grenzpunkte(ALKIS)-Layer.
FELD_GENAUIGKEIT = "qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe"
#: Feldname für die GML-ID im Grenzpunkte(ALKIS)-Layer.
FELD_GML_ID = "gml_id"
#: Kategoriewert: festgestellter Grenzpunkt.
KATEGORIE_FGP = "FGP"
#: Kategoriewert: Lagegenauigkeit ≤ 3 cm (Gütestufe 2100).
KATEGORIE_KLEIN = "<3cm"
#: Kategoriewert: Lagegenauigkeit > 3 cm (schlechtere Gütestufe).
KATEGORIE_GROSS = ">3cm"
#: Kategoriewert: Kategorie konnte nicht bestimmt werden.
KATEGORIE_UNBEKANNT = "unbekannt"
#: Maximaler Abstand (m) eines Punktes vom Umring, um als „auf dem Umring" zu gelten.
UMRING_TOLERANZ_M: float = 0.1
#: Grenzwert für ``qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe`` (3-cm-Schwelle).
GENAUIGKEIT_GRENZWERT: int = 2100
# ---------------------------------------------------------------------------
# Interne Hilfsfunktionen
# ---------------------------------------------------------------------------
def _setze_kategorien_symbologie(layer) -> None:
"""Wendet Kategoriesymbologie an: grün / gelb / rot / grau.
Wird bei fehlendem QGIS-Kontext still ignoriert.
"""
try:
from qgis.core import ( # type: ignore[import]
QgsCategorizedSymbolRenderer,
QgsRendererCategory,
QgsMarkerSymbol,
)
def _marker(color: str, size: str = "2.5") -> object:
return QgsMarkerSymbol.createSimple(
{"color": color, "size": size, "outline_style": "no"}
)
categories = [
QgsRendererCategory(
KATEGORIE_FGP,
_marker("0,180,0,255"),
f"{KATEGORIE_FGP} – festgestellter Grenzpunkt",
),
QgsRendererCategory(
KATEGORIE_KLEIN,
_marker("255,220,0,255"),
f"{KATEGORIE_KLEIN} – Gütestufe {GENAUIGKEIT_GRENZWERT}",
),
QgsRendererCategory(
KATEGORIE_GROSS,
_marker("220,0,0,255"),
f"{KATEGORIE_GROSS} – Gütestufe > {GENAUIGKEIT_GRENZWERT}",
),
QgsRendererCategory(
KATEGORIE_UNBEKANNT,
_marker("128,128,128,255"),
f"{KATEGORIE_UNBEKANNT} – Kategorie nicht bestimmbar",
),
]
renderer = QgsCategorizedSymbolRenderer(FELD_KATEGORIE, categories)
layer.setRenderer(renderer)
except Exception:
pass # Kein QGIS-Kontext (Tests) → Standarddarstellung behalten
def _klassifiziere(feat) -> str:
"""Bestimmt die Kategorie eines Grenzpunkts anhand seiner Attribute.
Parameters
----------
feat :
Feature aus dem Grenzpunkte(ALKIS)-Layer.
Returns
-------
str
Einer der Kategorienwerte :data:`KATEGORIE_FGP`, :data:`KATEGORIE_KLEIN`,
:data:`KATEGORIE_GROSS` oder :data:`KATEGORIE_UNBEKANNT`.
"""
# festgestellterGrenzpunkt
try:
festgestellt_raw = feat[FELD_FESTGESTELLT]
festgestellt = str(festgestellt_raw).lower().strip() == "true" if festgestellt_raw else False
except Exception:
festgestellt = False
if festgestellt:
return KATEGORIE_FGP
# Genauigkeitsstufe auslesen
try:
gen_raw = feat[FELD_GENAUIGKEIT]
genauigkeit = int(str(gen_raw).strip()) if gen_raw else None
except (ValueError, TypeError, Exception):
genauigkeit = None
if genauigkeit is None:
return KATEGORIE_UNBEKANNT
if genauigkeit == GENAUIGKEIT_GRENZWERT:
return KATEGORIE_KLEIN
if genauigkeit > GENAUIGKEIT_GRENZWERT:
return KATEGORIE_GROSS
# Genauigkeit < 2100 (besser als 3 cm, aber nicht festgestellt)
return KATEGORIE_UNBEKANNT
# ---------------------------------------------------------------------------
# Öffentliche API
# ---------------------------------------------------------------------------
def umringstatistik_grenzpunkte(
tab_widget,
grenzpunkte_layer,
vg_layer,
) -> Optional[object]:
"""Klassifiziert Grenzpunkte auf dem VG-Umring nach Lagegenauigkeit.
Ablauf:
1. VG-Geometrie aus ``vg_layer`` berechnen → Umringslinie.
2. Alle Features aus ``grenzpunkte_layer`` auf Abstand ≤ :data:`UMRING_TOLERANZ_M`
zum Umring prüfen.
3. Treffer klassifizieren (FGP / <3cm / >3cm / unbekannt).
4. Ergebnis-Memory-Layer aufbauen und Kategoriesymbologie setzen.
5. Layer ins Projekt einfügen, Zusammenfassungs-MessageBox anzeigen.
Parameters
----------
tab_widget :
Eltern-Widget für Dialoge.
grenzpunkte_layer :
Geladener ``Grenzpunkte (ALKIS)``-Layer mit Attributen ``gp_festgestellterGrenzpunkt``
und ``qualitaetsangaben_ax_dqpunktort_genauigkeitsstufe``.
vg_layer :
Verfahrensgebiet-Layer (Polygon, EPSG:25833).
Returns
-------
QgsVectorLayer | None
Ergebnis-Memory-Layer oder ``None`` bei Fehler/Abbruch.
"""
project = QgsProject.instance()
prog = create_progress_dialog(5, "Umringstatistik Grenzpunkte", "Schritt 1/5: VG-Umring berechnen …")
abgeschlossen = False
try:
prog.set_value(0)
if prog.is_canceled():
return None
# ------------------------------------------------------------------
# Schritt 1: VG-Umring berechnen
# ------------------------------------------------------------------
feats_vg = [f for f in vg_layer.getFeatures() if f.hasGeometry() and not f.geometry().isEmpty()]
if not feats_vg:
prog.close()
warning("Umringstatistik", "Verfahrensgebiet-Layer enthält keine Geometrien.", duration=6)
return None
vg_geom = QgsGeometry.unaryUnion([f.geometry() for f in feats_vg])
if vg_geom is None or vg_geom.isEmpty():
prog.close()
warning("Umringstatistik", "Verfahrensgebiet-Geometrie konnte nicht vereint werden.", duration=6)
return None
umring_geom = polygon_zu_linie(vg_geom)
if umring_geom is None or umring_geom.isEmpty():
prog.close()
warning("Umringstatistik", "Umringslinie konnte aus der VG-Geometrie nicht extrahiert werden.", duration=6)
return None
prog.set_value(1)
if prog.is_canceled():
return None
# ------------------------------------------------------------------
# Schritt 2: Grenzpunkte auf dem Umring filtern
# ------------------------------------------------------------------
prog.set_label("Schritt 2/5: Grenzpunkte auf Umring prüfen …")
n_gesamt = grenzpunkte_layer.featureCount()
treffer: list = []
for i, feat in enumerate(grenzpunkte_layer.getFeatures()):
if i % 200 == 0:
prog.set_label(
f"Schritt 2/5: Grenzpunkte prüfen … ({i}/{n_gesamt})"
)
if prog.is_canceled():
return None
if not feat.hasGeometry() or feat.geometry().isEmpty():
continue
try:
dist = feat.geometry().distance(umring_geom)
except Exception:
continue
if dist <= UMRING_TOLERANZ_M:
treffer.append(feat)
prog.set_value(2)
if prog.is_canceled():
return None
if not treffer:
prog.close()
QMessageBox.information(
tab_widget,
"Umringstatistik Grenzpunkte",
"Es wurden keine Grenzpunkte auf dem Umring des Verfahrensgebiets gefunden.\n\n"
f"Geprüft: {n_gesamt} Grenzpunkte\n"
f"Toleranz: {UMRING_TOLERANZ_M} m",
)
return None
# ------------------------------------------------------------------
# Schritt 3: Klassifizieren
# ------------------------------------------------------------------
prog.set_label(f"Schritt 3/5: {len(treffer)} Treffer klassifizieren …")
n_fgp = 0
n_klein = 0
n_gross = 0
n_unbekannt = 0
klassifiziert: list = [] # [(feat, kategorie), ...]
for feat in treffer:
kat = _klassifiziere(feat)
klassifiziert.append((feat, kat))
if kat == KATEGORIE_FGP:
n_fgp += 1
elif kat == KATEGORIE_KLEIN:
n_klein += 1
elif kat == KATEGORIE_GROSS:
n_gross += 1
else:
n_unbekannt += 1
prog.set_value(3)
if prog.is_canceled():
return None
# ------------------------------------------------------------------
# Schritt 4: Ergebnis-Layer aufbauen
# ------------------------------------------------------------------
prog.set_label("Schritt 4/5: Ergebnis-Layer aufbauen …")
crs_authid = vg_layer.crs().authid() or "EPSG:25833"
result_layer = QgsVectorLayer(
f"Point?crs={crs_authid}", ERGEBNIS_LAYER_NAME, "memory"
)
pr = result_layer.dataProvider()
felder = [
QgsField(FELD_KATEGORIE, QVariant.String),
QgsField(FELD_GML_ID, QVariant.String),
QgsField(FELD_FESTGESTELLT, QVariant.String),
QgsField(FELD_GENAUIGKEIT, QVariant.String),
]
pr.addAttributes(felder)
result_layer.updateFields()
neue_features = []
for feat, kat in klassifiziert:
new_f = QgsFeature(result_layer.fields())
new_f.setGeometry(feat.geometry())
new_f[FELD_KATEGORIE] = kat
for src_feld in (FELD_GML_ID, FELD_FESTGESTELLT, FELD_GENAUIGKEIT):
try:
new_f[src_feld] = feat[src_feld]
except Exception:
pass
neue_features.append(new_f)
pr.addFeatures(neue_features)
result_layer.updateExtents()
_setze_kategorien_symbologie(result_layer)
prog.set_value(4)
if prog.is_canceled():
return None
# ------------------------------------------------------------------
# Schritt 5: Layer ins Projekt + Ergebnis-MessageBox
# ------------------------------------------------------------------
prog.set_label("Schritt 5/5: Layer ins Projekt einfügen …")
# Bestehenden Ergebnis-Layer ggf. ersetzen
vorhandene = project.mapLayersByName(ERGEBNIS_LAYER_NAME)
for lyr in vorhandene:
project.removeMapLayer(lyr.id())
project.addMapLayer(result_layer)
prog.set_value(5)
abgeschlossen = True
prog.close()
n_umring = len(treffer)
QMessageBox.information(
tab_widget,
"Umringstatistik Grenzpunkte",
f"Grenzpunkte auf dem Umring: {n_umring}\n"
f"\n"
f" FGP (festgestellt): {n_fgp}\n"
f" < 3 cm (Gütestufe {GENAUIGKEIT_GRENZWERT}): {n_klein}\n"
f" > 3 cm (schlechtere Güte): {n_gross}\n"
f" Unbekannt: {n_unbekannt}\n"
f"\n"
f"(Geprüft: {n_gesamt} Grenzpunkte, Toleranz: {UMRING_TOLERANZ_M} m)",
)
return result_layer
finally:
if not abgeschlossen:
prog.close()