Push 05.02.2026
This commit is contained in:
564
Netzqualitaet_Genauigkeit.py
Normal file
564
Netzqualitaet_Genauigkeit.py
Normal file
@@ -0,0 +1,564 @@
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from scipy.stats import f
|
||||
import pandas as pd
|
||||
from IPython.display import HTML
|
||||
from IPython.display import display, clear_output
|
||||
import Berechnungen
|
||||
import Einheitenumrechnung
|
||||
|
||||
|
||||
class Genauigkeitsmaße:
|
||||
"""Berechnung von Genauigkeitsmaße zur Bewertung der erreichten Netzqualität.
|
||||
|
||||
Die Klasse stellt Methoden zur Verfügung für:
|
||||
|
||||
- Berechnung der a-posteriori Standardabweichung der Gewichtseinheit s₀
|
||||
- Berechnung des Helmertschen Punktfehlers (2D/3D),
|
||||
- Berechnung der Standardellipse (Helmertschen Fehlerellipse),
|
||||
- Berechnung der Konfidenzellipse auf Basis eines Konfidenzniveaus (alpha) mit Skalierung über die F-Verteilung,
|
||||
- Berechnung von Konfidenzellipsen im lokalen ENU-System durch Transformation von Qxx → Qxx_ENU,
|
||||
inkl. Ausgabe/Export tabellarischer Ergebnisse.
|
||||
"""
|
||||
|
||||
|
||||
@staticmethod
|
||||
def berechne_s0apost(v: np.ndarray, P: np.ndarray, r: int) -> float:
|
||||
"""
|
||||
Berechnet die a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||||
|
||||
Die a-posteriori Standardabweichung dient als Qualitätsmaß für die
|
||||
Ausgleichung nach der Methode der kleinsten Quadrate. Dabei beschreibt
|
||||
r die Redundanz (Freiheitsgrade).
|
||||
|
||||
:param v: Residuenvektor der Beobachtungen.
|
||||
:type v: numpy.ndarray
|
||||
:param P: Gewichtsmatrix der Beobachtungen.
|
||||
:type P: numpy.ndarray
|
||||
:param r: Redundanz bzw. Anzahl der Freiheitsgrade der Ausgleichung.
|
||||
:type r: int
|
||||
:return: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||||
:rtype: float
|
||||
"""
|
||||
|
||||
vTPv_matrix = v.T @ P @ v
|
||||
vTPv = vTPv_matrix.item()
|
||||
s0apost = np.sqrt(vTPv / r)
|
||||
print(f"s0 a posteriori beträgt: {s0apost:.4f}")
|
||||
return s0apost
|
||||
|
||||
|
||||
@staticmethod
|
||||
def helmert_punktfehler(Qxx, s0_apost, unbekannten_liste):
|
||||
"""
|
||||
Berechnet den Helmertschen Punktfehler (2D/3D) anhand der Standardabweichungen der Koordinaten der Punkte.
|
||||
|
||||
Aus der Kofaktor-Matrix der Unbekannten Qxx werden die Kofaktoren punktweise ausgelesen. Durch Multiplikation
|
||||
mit der a-posteriori Standardabweichung der Gewichtseinheit s₀ werden die Standardabweichungen je Koordinate
|
||||
(σx, σy, σz) sowie der Helmertsche Punktfehler σP berechnet:
|
||||
|
||||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste (z.B. X1, Y1, Z1).
|
||||
Die Dimension (2D/3D) wird interaktiv per Eingabe abgefragt. Zusätzlich werden die
|
||||
Ergebnisse als Tabelle ausgegeben und in eine Excel-Datei exportiert.
|
||||
|
||||
:param Qxx: Kofaktor-Matrix der Unbekannten.
|
||||
:type Qxx: numpy.ndarray
|
||||
:param s0_apost: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||||
:type s0_apost: float
|
||||
:param unbekannten_liste: Liste der Unbekannten.
|
||||
:type unbekannten_liste: list
|
||||
:return: Tabelle mit Standardabweichungen und Helmertschem Punktfehler je Punkt.
|
||||
:rtype: pandas.DataFrame
|
||||
:raises ValueError: Wenn eine ungültige Dimension (nicht 2 oder 3) eingegeben wird.
|
||||
"""
|
||||
dim = int(input("Helmertscher Punktfehler (2 = 2D, 3 = 3D): "))
|
||||
diagQ = np.diag(Qxx)
|
||||
daten = []
|
||||
namen_str = [str(sym) for sym in unbekannten_liste]
|
||||
|
||||
punkt_ids = []
|
||||
for n in namen_str:
|
||||
if n.upper().startswith('X'):
|
||||
punkt_ids.append(n[1:])
|
||||
|
||||
for pid in punkt_ids:
|
||||
try:
|
||||
idx_x = next(i for i, n in enumerate(namen_str) if n.upper() == f"X{pid}".upper())
|
||||
idx_y = next(i for i, n in enumerate(namen_str) if n.upper() == f"Y{pid}".upper())
|
||||
|
||||
qx = diagQ[idx_x]
|
||||
qy = diagQ[idx_y]
|
||||
qz = 0.0
|
||||
|
||||
if dim == 3:
|
||||
try:
|
||||
idx_z = next(i for i, n in enumerate(namen_str) if n.upper() == f"Z{pid}".upper())
|
||||
qz = diagQ[idx_z]
|
||||
except StopIteration:
|
||||
qz = 0.0
|
||||
|
||||
sx = s0_apost * np.sqrt(qx)
|
||||
sy = s0_apost * np.sqrt(qy)
|
||||
sz = s0_apost * np.sqrt(qz) if dim == 3 else 0
|
||||
|
||||
sP = s0_apost * np.sqrt(qx + qy + qz)
|
||||
|
||||
daten.append([pid, float(sx), float(sy), float(sz), float(sP)])
|
||||
except:
|
||||
continue
|
||||
|
||||
helmert_punktfehler = pd.DataFrame(daten, columns=["Punkt", "σx", "σy", "σz", f"σP_{dim}D"])
|
||||
display(HTML(helmert_punktfehler.to_html(index=False)))
|
||||
helmert_punktfehler.to_excel(r"Zwischenergebnisse\Standardabweichungen_Helmertscher_Punktfehler.xlsx",index=False)
|
||||
return helmert_punktfehler
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def standardellipse(Qxx, s0_apost, unbekannten_liste):
|
||||
"""
|
||||
Berechnet die Standardellipse (Helmertsche Fehlerellipse) für die Punkte aus Qxx und s₀ a posteriori.
|
||||
|
||||
Für jeden Punkt werden aus der Kofaktor-Matrix der Unbekannten Qxx die
|
||||
Kofaktoren von X und Y ausgelesen (qxx, qyy, qyx).
|
||||
Daraus werden Standardabweichungen σx, σy sowie die Kovarianz σxy bestimmt und
|
||||
anschließend die Parameter der Standardellipse berechnet:
|
||||
|
||||
- Große und kleine Halbachse der Standardellipse,
|
||||
- Richtungswinkel θ der großen Halbachse in gon.
|
||||
|
||||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste (z.B. X1, Y1).
|
||||
Zusätzlich werden die Ergebnisse tabellarisch ausgegeben und in eine Excel-Datei expoertiert.
|
||||
|
||||
:param Qxx: Kofaktor-Matrix der Unbekannten.
|
||||
:type Qxx: numpy.ndarray
|
||||
:param s0_apost: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||||
:type s0_apost: float
|
||||
:param unbekannten_liste: Liste der Unbekannten.
|
||||
:type unbekannten_liste: list
|
||||
:return: Tabelle mit Standardabweichungen und Parametern der Standardellipse je Punkt.
|
||||
:rtype: pandas.DataFrame
|
||||
"""
|
||||
Qxx = np.asarray(Qxx, float)
|
||||
daten = []
|
||||
namen_str = [str(sym) for sym in unbekannten_liste]
|
||||
|
||||
punkt_ids = []
|
||||
for n in namen_str:
|
||||
if n.upper().startswith('X'):
|
||||
punkt_ids.append(n[1:])
|
||||
|
||||
for pid in punkt_ids:
|
||||
try:
|
||||
idx_x = next(i for i, n in enumerate(namen_str) if n.upper() == f"X{pid}".upper())
|
||||
idx_y = next(i for i, n in enumerate(namen_str) if n.upper() == f"Y{pid}".upper())
|
||||
|
||||
qxx = Qxx[idx_x, idx_x]
|
||||
qyy = Qxx[idx_y, idx_y]
|
||||
qyx = Qxx[idx_y, idx_x]
|
||||
|
||||
# Standardabweichungen
|
||||
sx = s0_apost * np.sqrt(qxx)
|
||||
sy = s0_apost * np.sqrt(qyy)
|
||||
sxy = (s0_apost ** 2) * qyx
|
||||
|
||||
k = np.sqrt((qxx - qyy) ** 2 + 4 * (qyx ** 2))
|
||||
|
||||
# Q_dmax/min = 0.5 * (Qyy + Qxx +/- k)
|
||||
q_dmax = 0.5 * (qyy + qxx + k)
|
||||
q_dmin = 0.5 * (qyy + qxx - k)
|
||||
|
||||
# Halbachsen
|
||||
s_max = s0_apost * np.sqrt(q_dmax)
|
||||
s_min = s0_apost * np.sqrt(q_dmin)
|
||||
|
||||
# Richtungswinkel theta in gon:
|
||||
zaehler = 2 * qyx
|
||||
nenner = qxx - qyy
|
||||
t_grund = 0.5 * np.arctan(abs(zaehler) / abs(nenner)) * (200 / np.pi)
|
||||
|
||||
# Quadrantenabfrage
|
||||
if nenner > 0 and qyx > 0: # Qxx - Qyy > 0 und Qyx > 0
|
||||
t_gon = t_grund # 0 - 50 gon
|
||||
elif nenner < 0 and qyx > 0: # Qxx - Qyy < 0 und Qyx > 0
|
||||
t_gon = 100 - t_grund # 50 - 100 gon
|
||||
elif nenner < 0 and qyx < 0: # Qxx - Qyy < 0 und Qyx < 0
|
||||
t_gon = 100 + t_grund # 100 - 150 gon
|
||||
elif nenner > 0 and qyx < 0: # Qxx - Qyy > 0 und Qyx < 0
|
||||
t_gon = 200 - t_grund # 150 - 200 gon
|
||||
else:
|
||||
t_gon = 0.0
|
||||
|
||||
daten.append([
|
||||
pid,
|
||||
float(sx), float(sy), float(sxy),
|
||||
float(s_max), float(s_min),
|
||||
float(t_gon)
|
||||
])
|
||||
except:
|
||||
continue
|
||||
standardellipse = pd.DataFrame(daten, columns=["Punkt", "σx [m]", "σy [m]", "σxy [m]", "Große Halbachse [m]", "Kleine Halbachse [m]", "θ [gon]"])
|
||||
standardellipse["σx [m]"] = standardellipse["σx [m]"].astype(float).round(4)
|
||||
standardellipse["σy [m]"] = standardellipse["σy [m]"].astype(float).round(4)
|
||||
standardellipse["Große Halbachse [m]"] = standardellipse["Große Halbachse [m]"].astype(float).round(4)
|
||||
standardellipse["Kleine Halbachse [m]"] = standardellipse["Kleine Halbachse [m]"].astype(float).round(4)
|
||||
standardellipse["θ [gon]"] = standardellipse["θ [gon]"].astype(float).round(3)
|
||||
display(HTML(standardellipse.to_html(index=False)))
|
||||
standardellipse.to_excel(r"Zwischenergebnisse\Standardellipse.xlsx", index=False)
|
||||
return standardellipse
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def konfidenzellipse(Qxx, s0_apost, unbekannten_liste, R, ausgabe_erfolgt):
|
||||
"""
|
||||
Berechnet die Konfidenzellipse für Punkte aus Qxx und einem Konfidenzniveau.
|
||||
|
||||
Auf Basis der Kovarianz-Matrix der Unbekannten Qxx und der a-posteriori
|
||||
Standardabweichung der Gewichtseinheit s₀ werden für jeden Punkt die Parameter
|
||||
der Konfidenzellipse berechnet. Das Konfidenzniveau wird mittels einer Eingabe
|
||||
über alpha gewählt (Standard: 0.05 für 95%).
|
||||
|
||||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste (z.B. X1, Y1).
|
||||
Optional wird die Tabelle ausgegeben und als Excel-Datei exportiert, abhängig von
|
||||
ausgabe_erfolgt.
|
||||
|
||||
:param Qxx: Kofaktor-Matrix der geschätzten Unbekannten.
|
||||
:type Qxx: numpy.ndarray
|
||||
:param s0_apost: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||||
:type s0_apost: float
|
||||
:param unbekannten_liste: Liste der Unbekannten.
|
||||
:type unbekannten_liste: list
|
||||
:param R: Redundanz (Freiheitsgrade) für die F-Verteilung.
|
||||
:type R: int
|
||||
:param ausgabe_erfolgt: Steuert, ob eine Ausgabe/Dateischreibung erfolgen soll (False = Ausgabe).
|
||||
:type ausgabe_erfolgt: bool
|
||||
:return: Tabelle der Konfidenzellipse je Punkt, verwendetes alpha.
|
||||
:rtype: tuple[pandas.DataFrame, float]
|
||||
:raises ValueError: Wenn alpha nicht in (0, 1) liegt oder nicht in float umgewandelt werden kann.
|
||||
"""
|
||||
alpha_input = input("Konfidenzniveau wählen (z.B. 0.05 für 95%, 0.01 für 99%) [Standard=0.05]: ")
|
||||
if alpha_input.strip() == "":
|
||||
alpha = 0.05
|
||||
else:
|
||||
alpha = float(alpha_input)
|
||||
print(f"→ Verwende alpha = {alpha} (Konfidenz = {(1 - alpha) * 100:.1f}%)")
|
||||
Qxx = np.asarray(Qxx, float)
|
||||
daten = []
|
||||
namen_str = [str(sym) for sym in unbekannten_liste]
|
||||
|
||||
punkt_ids = [n[1:] for n in namen_str if n.upper().startswith('X')]
|
||||
|
||||
# Faktor für Konfidenzellipse (F-Verteilung)
|
||||
kk = float(np.sqrt(2.0 * f.ppf(1.0 - alpha, 2, R)))
|
||||
|
||||
for pid in punkt_ids:
|
||||
try:
|
||||
idx_x = next(i for i, n in enumerate(namen_str) if n.upper() == f"X{pid}".upper())
|
||||
idx_y = next(i for i, n in enumerate(namen_str) if n.upper() == f"Y{pid}".upper())
|
||||
|
||||
qxx = Qxx[idx_x, idx_x]
|
||||
qyy = Qxx[idx_y, idx_y]
|
||||
qyx = Qxx[idx_y, idx_x]
|
||||
|
||||
# Standardabweichungen
|
||||
sx = s0_apost * np.sqrt(qxx)
|
||||
sy = s0_apost * np.sqrt(qyy)
|
||||
sxy = (s0_apost ** 2) * qyx
|
||||
|
||||
k = np.sqrt((qxx - qyy) ** 2 + 4 * (qyx ** 2))
|
||||
|
||||
# Q_dmax/min = 0.5 * (Qyy + Qxx +/- k)
|
||||
q_dmax = 0.5 * (qyy + qxx + k)
|
||||
q_dmin = 0.5 * (qyy + qxx - k)
|
||||
|
||||
# Halbachsen der Standardellipse
|
||||
s_max = s0_apost * np.sqrt(q_dmax)
|
||||
s_min = s0_apost * np.sqrt(q_dmin)
|
||||
|
||||
# Halbachsen der Konfidenzellipse
|
||||
A_K = kk * s_max
|
||||
B_K = kk * s_min
|
||||
|
||||
# Richtungswinkel theta in gon:
|
||||
zaehler = 2 * qyx
|
||||
nenner = qxx - qyy
|
||||
t_grund = 0.5 * np.arctan(abs(zaehler) / abs(nenner)) * (200 / np.pi)
|
||||
|
||||
# Quadrantenabfrage
|
||||
if nenner > 0 and qyx > 0:
|
||||
t_gon = t_grund # 0 - 50 gon
|
||||
elif nenner < 0 and qyx > 0:
|
||||
t_gon = 100 - t_grund # 50 - 100 gon
|
||||
elif nenner < 0 and qyx < 0:
|
||||
t_gon = 100 + t_grund # 100 - 150 gon
|
||||
elif nenner > 0 and qyx < 0:
|
||||
t_gon = 200 - t_grund # 150 - 200 gon
|
||||
else:
|
||||
t_gon = 0.0
|
||||
|
||||
daten.append([
|
||||
pid,
|
||||
float(sx), float(sy), float(sxy),
|
||||
float(A_K), float(B_K),
|
||||
float(t_gon)
|
||||
])
|
||||
|
||||
except:
|
||||
continue
|
||||
|
||||
konfidenzellipse = pd.DataFrame(daten, columns=["Punkt", "σx [m]", "σy [m]", "σxy [m]", "Große Halbachse [m]", "Kleine Halbachse [m]", "θ [gon]"])
|
||||
konfidenzellipse["Große Halbachse [m]"] = konfidenzellipse["Große Halbachse [m]"].round(4)
|
||||
konfidenzellipse["Kleine Halbachse [m]"] = konfidenzellipse["Kleine Halbachse [m]"].round(4)
|
||||
konfidenzellipse["θ [gon]"] = konfidenzellipse["θ [gon]"].round(3)
|
||||
if ausgabe_erfolgt == False:
|
||||
display(HTML(konfidenzellipse.to_html(index=False)))
|
||||
konfidenzellipse.to_excel(r"Zwischenergebnisse\Konfidenzellipse.xlsx", index=False)
|
||||
return konfidenzellipse, alpha
|
||||
|
||||
|
||||
@staticmethod
|
||||
def konfidenzellipsen_enu(a, b, ausgabe_parameterschaetzung, liste_unbekannte, ausgleichungsergebnis, s0apost, r_gesamt):
|
||||
"""
|
||||
Berechnet Konfidenzellipsen im lokalen ENU-System aus einer ins ENU-System transformierten Qxx-Matrix.
|
||||
|
||||
Die Funktion transformiert zunächst die Kofaktor-Matrix der Unbekannten Qxx
|
||||
in ein East-North-Up-System (ENU) bezogen auf den Schwerpunkt der verwendeten
|
||||
Punkte (B0, L0). Anschließend wird auf Basis der transformierten Matrix die
|
||||
Konfidenzellipse über die Funktion "konfidenzellipse" bestimmt.
|
||||
Zum Schluss werden Spaltennamen an die ENU-Notation angepasst, Werte gerundet,
|
||||
tabellarisch ausgegeben und als Excel-Datei exportiert.
|
||||
|
||||
:param a: Große Halbachse a des Referenzellipsoids (z.B. WGS84/GRS80) in Metern.
|
||||
:type a: float
|
||||
:param b: Große Halbachse b des Referenzellipsoids (z.B. WGS84/GRS80) in Metern.
|
||||
:type b: float
|
||||
:param ausgabe_parameterschaetzung: Dictonary der Ergebnisse der Parameterschätzung, muss "Q_xx" enthalten.
|
||||
:type ausgabe_parameterschaetzung: dict
|
||||
:param liste_unbekannte: Liste der Unbekannten.
|
||||
:type liste_unbekannte: list
|
||||
:param ausgleichungsergebnis: Dictionary der geschätzten Punktkoordinaten (XYZ) zur ENU-Referenzbildung.
|
||||
:type ausgleichungsergebnis: dict
|
||||
:param s0apost: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||||
:type s0apost: float
|
||||
:param r_gesamt: Redundanz (Freiheitsgrade) für die Konfidenzberechnung.
|
||||
:type r_gesamt: int
|
||||
:return: Tabelle der Konfidenzellipse im ENU-System, Rotationsmatrix R0 der ENU-Transformation.
|
||||
:rtype: tuple[pandas.DataFrame, numpy.ndarray]
|
||||
:raises KeyError: Wenn ``ausgabe_parameterschaetzung`` keinen Eintrag ``"Q_xx"`` enthält.
|
||||
"""
|
||||
|
||||
berechnungen = Berechnungen.Berechnungen(a, b)
|
||||
|
||||
# 1) Qxx ins ENU-System transformieren
|
||||
Qxx_enu, (B0, L0), R0 = Berechnungen.ENU.transform_Qxx_zu_QxxENU(
|
||||
Qxx=ausgabe_parameterschaetzung["Q_xx"],
|
||||
unbekannten_liste= liste_unbekannte,
|
||||
berechnungen=berechnungen,
|
||||
dict_xyz= ausgleichungsergebnis,
|
||||
)
|
||||
|
||||
print(
|
||||
f"ENU-Referenz (Schwerpunkt): B0={Einheitenumrechnung.rad_to_gon_Decimal(B0):.8f} rad, L0={Einheitenumrechnung.rad_to_gon_Decimal(L0):.8f} rad")
|
||||
|
||||
# 2) Konfidenzellipse im ENU-System
|
||||
Konfidenzellipse_ENU, alpha = Genauigkeitsmaße.konfidenzellipse(
|
||||
Qxx_enu,
|
||||
s0apost,
|
||||
liste_unbekannte,
|
||||
r_gesamt,
|
||||
ausgabe_erfolgt = True
|
||||
)
|
||||
|
||||
# 3) Spaltennamen anpassen
|
||||
Konfidenzellipse_ENU = Konfidenzellipse_ENU.rename(columns={
|
||||
"σx [m]": "σE [m]",
|
||||
"σy [m]": "σN [m]",
|
||||
"σxy [m]": "σEN [m]",
|
||||
"θ [gon]": "θ_EN [gon]"
|
||||
})
|
||||
|
||||
# 4) Runden und Anzeigen
|
||||
Konfidenzellipse_ENU["σE [m]"] = Konfidenzellipse_ENU["σE [m]"].round(4)
|
||||
Konfidenzellipse_ENU["σN [m]"] = Konfidenzellipse_ENU["σN [m]"].round(4)
|
||||
Konfidenzellipse_ENU["Große Halbachse [m]"] = Konfidenzellipse_ENU["Große Halbachse [m]"].round(4)
|
||||
Konfidenzellipse_ENU["Kleine Halbachse [m]"] = Konfidenzellipse_ENU["Kleine Halbachse [m]"].round(4)
|
||||
Konfidenzellipse_ENU["θ_EN [gon]"] = Konfidenzellipse_ENU["θ_EN [gon]"].round(4)
|
||||
|
||||
display(HTML(Konfidenzellipse_ENU.to_html(index=False)))
|
||||
|
||||
# 5) Export
|
||||
Konfidenzellipse_ENU.to_excel(r"Zwischenergebnisse\Konfidenzellipse_ENU.xlsx", index=False)
|
||||
return Konfidenzellipse_ENU, R0
|
||||
|
||||
|
||||
|
||||
class Plot:
|
||||
"""Visualisierung geodätischer Netze und Genauigkeitsmaße.
|
||||
|
||||
Die Klasse stellt Methoden zur Verfügung für:
|
||||
|
||||
- grafische Darstellung von geodätischen Netzen im lokalen ENU-System,
|
||||
- Visualisierung von Beobachtungen als Verbindungslinien,
|
||||
- Darstellung von Konfidenzellipsen,
|
||||
- interaktive Netzdarstellung mit Plotly inklusive Hover-Informationen,
|
||||
- Skalierung und Layout-Anpassung zur anschaulichen Präsentation von
|
||||
Lagegenauigkeiten.
|
||||
|
||||
Die Klasse dient ausschließlich der Ergebnisvisualisierung und nimmt keine
|
||||
numerischen Berechnungen vor.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def netzplot_ellipsen(
|
||||
Koord_ENU,
|
||||
unbekannten_labels,
|
||||
beobachtungs_labels,
|
||||
df_konf_ellipsen_enu,
|
||||
skalierung=1000,
|
||||
n_ellipse_pts=60,
|
||||
titel="Netzplot im ENU-System mit Konfidenzellipsen"
|
||||
):
|
||||
"""
|
||||
Erstellt einen Netzplot im ENU-System inklusive Konfidenzellipsen, Netzpunkten und Beobachtungslinien.
|
||||
|
||||
Die Funktion visualisiert das geodätische Netz im East-North-Up-System (ENU)
|
||||
mit Plotly. Dabei werden:
|
||||
|
||||
- Beobachtungen als Verbindungslinien zwischen Punkten dargestellt, deren Ansicht aus- und eingeschaltet werden kann,
|
||||
- Konfidenzellipsen je Punkt (Halbachsen und Richtungswinkel),
|
||||
- Netzpunkte mit Punkt-ID und Koordinaten im Hover-Text angezeigt.
|
||||
|
||||
Die Ellipsen werden zur besseren Sichtbarkeit mit einem Faktor "skalierung" vergrößert. Dieser kann angepasst werden.
|
||||
Der Richtungswinkel wird in gon erwartet und intern nach Radiant umgerechnet.
|
||||
|
||||
:param Koord_ENU: Dictionary der Punktkoordinaten im ENU-System.
|
||||
:type Koord_ENU: dict
|
||||
:param unbekannten_labels: Liste der Unbekannten zur Ableitung der Punkt-IDs (z.B. X1, Y1, Z1).
|
||||
:type unbekannten_labels: list
|
||||
:param beobachtungs_labels: Liste der Beobachtungen zur Ableitung von Verbindungslinien.
|
||||
:type beobachtungs_labels: list
|
||||
:param df_konf_ellipsen_enu: DataFrame mit Konfidenzellipsenparametern je Punkt.
|
||||
:type df_konf_ellipsen_enu: pandas.DataFrame
|
||||
:param skalierung: Faktor zur visuellen Vergrößerung der Ellipsen im Plot.
|
||||
:type skalierung: float
|
||||
:param n_ellipse_pts: Anzahl der Stützpunkte zur Approximation der Ellipse.
|
||||
:type n_ellipse_pts: int
|
||||
:param titel: Titel des Plots.
|
||||
:type titel: str
|
||||
:return: None
|
||||
:rtype: None
|
||||
:raises ValueError: Wenn weder "θ_EN [gon]" noch "θ [gon]" im DataFrame vorhanden ist.
|
||||
"""
|
||||
|
||||
names = [str(s).strip() for s in unbekannten_labels]
|
||||
|
||||
if "θ_EN [gon]" in df_konf_ellipsen_enu.columns:
|
||||
theta_col = "θ_EN [gon]"
|
||||
elif "θ [gon]" in df_konf_ellipsen_enu.columns:
|
||||
theta_col = "θ [gon]"
|
||||
else:
|
||||
raise ValueError("Spalte 'θ_EN [gon]' oder 'θ [gon]' fehlt im DataFrame.")
|
||||
|
||||
punkt_ids = sorted({nm[1:] for nm in names if nm and nm[0].upper() in ("X", "Y", "Z")})
|
||||
|
||||
fig = go.Figure()
|
||||
|
||||
# 1) Darstellungen der Beobachtungen
|
||||
beob_typen = {
|
||||
'GNSS-Basislinien': {'pattern': 'gnss', 'color': 'rgba(255, 100, 0, 0.4)'},
|
||||
'Tachymeter-Beob': {'pattern': '', 'color': 'rgba(100, 100, 100, 0.3)'}
|
||||
}
|
||||
|
||||
for typ, info in beob_typen.items():
|
||||
x_l, y_l = [], []
|
||||
for bl in beobachtungs_labels:
|
||||
bl_str = str(bl).lower()
|
||||
is_typ = ((info['pattern'] in bl_str and info['pattern'] != '') or
|
||||
(info['pattern'] == '' and 'gnss' not in bl_str and 'niv' not in bl_str))
|
||||
if not is_typ:
|
||||
continue
|
||||
|
||||
bl_raw = str(bl)
|
||||
pts = []
|
||||
for pid in punkt_ids:
|
||||
if (f"_{pid}" in bl_raw) or bl_raw.startswith(f"{pid}_"):
|
||||
if pid in Koord_ENU:
|
||||
pts.append(pid)
|
||||
|
||||
if len(pts) >= 2:
|
||||
p1, p2 = pts[0], pts[1]
|
||||
x_l.extend([Koord_ENU[p1][0], Koord_ENU[p2][0], None]) # E
|
||||
y_l.extend([Koord_ENU[p1][1], Koord_ENU[p2][1], None]) # N
|
||||
|
||||
if x_l:
|
||||
fig.add_trace(go.Scatter(x=x_l, y=y_l, mode='lines', name=typ,
|
||||
line=dict(color=info['color'], width=1)))
|
||||
|
||||
# 2) Darstellung der Konfidenzellipsen
|
||||
t = np.linspace(0, 2 * np.pi, n_ellipse_pts)
|
||||
first = True
|
||||
for _, row in df_konf_ellipsen_enu.iterrows():
|
||||
pid = str(row["Punkt"])
|
||||
if pid not in Koord_ENU:
|
||||
continue
|
||||
|
||||
a = float(row["Große Halbachse [m]"]) * skalierung
|
||||
b = float(row["Kleine Halbachse [m]"]) * skalierung
|
||||
theta = float(row[theta_col]) * np.pi / 200.0 # gon->rad
|
||||
|
||||
ex = a * np.cos(t)
|
||||
ey = b * np.sin(t)
|
||||
|
||||
c, s = np.cos(theta), np.sin(theta)
|
||||
xr = c * ex - s * ey
|
||||
yr = s * ex + c * ey
|
||||
|
||||
E0, N0, _ = Koord_ENU[pid]
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=E0 + xr, y=N0 + yr,
|
||||
mode="lines",
|
||||
line=dict(color="red", width=1.5),
|
||||
name=f"Ellipsen (×{skalierung})",
|
||||
legendgroup="Ellipsen",
|
||||
showlegend=first,
|
||||
hoverinfo="skip"
|
||||
))
|
||||
first = False
|
||||
|
||||
# 3) Darstellung der Punkte
|
||||
xs, ys, texts, hovers = [], [], [], []
|
||||
for pid in punkt_ids:
|
||||
if pid not in Koord_ENU:
|
||||
continue
|
||||
E, N, U = Koord_ENU[pid]
|
||||
xs.append(E);
|
||||
ys.append(N);
|
||||
texts.append(pid)
|
||||
hovers.append(f"Punkt {pid}<br>E={E:.4f} m<br>N={N:.4f} m<br>U={U:.4f} m")
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=xs, y=ys, mode="markers+text",
|
||||
text=texts, textposition="top center",
|
||||
marker=dict(size=8, color="black"),
|
||||
name="Netzpunkte",
|
||||
hovertext=hovers, hoverinfo="text"
|
||||
))
|
||||
|
||||
fig.update_layout(
|
||||
title=f"{titel} (Ellipsen ×{skalierung})",
|
||||
xaxis=dict(title="E [m]", scaleanchor="y", scaleratio=1, showgrid=True, gridcolor="lightgrey"),
|
||||
yaxis=dict(title="N [m]", showgrid=True, gridcolor="lightgrey"),
|
||||
width=1100, height=900,
|
||||
template="plotly_white",
|
||||
plot_bgcolor="white"
|
||||
)
|
||||
|
||||
fig.add_annotation(
|
||||
text=f"<b>Maßstab Ellipsen:</b><br>Dargestellte Größe = Konfidenzellipse × {skalierung}",
|
||||
align='left', showarrow=False, xref='paper', yref='paper', x=0.02, y=0.05,
|
||||
bgcolor="white", bordercolor="black", borderwidth=1
|
||||
)
|
||||
|
||||
fig.show(config={'scrollZoom': True})
|
||||
Reference in New Issue
Block a user