686 lines
31 KiB
Python
686 lines
31 KiB
Python
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
|
||
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 σ̂₀
|
||
- Berechnung des Helmertschen Punktfehlers (2D/3D),
|
||
- Berechnung der Standardellipsoide,
|
||
- Berechnung der Konfidenzellipsoide auf Basis eines Konfidenzniveaus (alpha) mit Skalierung über die F-Verteilung,
|
||
- Berechnung der Konfidenzellipsen auf Basis eines Konfidenzniveaus (alpha) mit Skalierung über die F-Verteilung,
|
||
- Berechnung von Konfidenzellipsen im lokalen ENU-System durch Transformation von Qxx zu Qxx_ENU,
|
||
inkl. Ausgabe/Export tabellarischer Ergebnisse.
|
||
"""
|
||
|
||
|
||
@staticmethod
|
||
def berechne_sigma0apost(v: np.ndarray, P: np.ndarray, r: int, print_ausgabe = True) -> float:
|
||
"""
|
||
Berechnet die a-posteriori Standardabweichung der Gewichtseinheit σ̂₀.
|
||
|
||
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: np.ndarray
|
||
:param P: Gewichtsmatrix der Beobachtungen.
|
||
:type P: np.ndarray
|
||
:param r: Redundanz bzw. Anzahl der Freiheitsgrade der Ausgleichung.
|
||
:type r: int
|
||
:return: a-posteriori Standardabweichung der Gewichtseinheit σ̂₀.
|
||
:rtype: float
|
||
"""
|
||
|
||
vTPv_matrix = v.T @ P @ v
|
||
vTPv = vTPv_matrix.item()
|
||
sigma0apost = np.sqrt(vTPv / r)
|
||
if print_ausgabe:
|
||
print(f"σ̂₀ a posteriori beträgt: {sigma0apost:.4f}")
|
||
return sigma0apost
|
||
|
||
|
||
@staticmethod
|
||
def helmert_punktfehler(Qxx: np.ndarray, sigma0_apost: float, unbekannten_liste: list) -> pd.DataFrame:
|
||
"""
|
||
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 σ̂₀ werden die Standardabweichungen je Koordinate
|
||
(σx, σy, σz) sowie der Helmert'sche Punktfehler σP berechnet:
|
||
|
||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste.
|
||
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: np.ndarray
|
||
:param sigma0_apost: a-posteriori Standardabweichung der Gewichtseinheit σ̂₀.
|
||
:type sigma0_apost: float
|
||
:param unbekannten_liste: Liste der Unbekannten.
|
||
:type unbekannten_liste: list
|
||
:return: Tabelle mit Standardabweichungen und Helmertschem Punktfehler je Punkt.
|
||
:rtype: pandas.DataFrame
|
||
"""
|
||
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 = sigma0_apost * np.sqrt(qx)
|
||
sy = sigma0_apost * np.sqrt(qy)
|
||
sz = sigma0_apost * np.sqrt(qz) if dim == 3 else 0
|
||
|
||
sP = sigma0_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 [m]", "σ̂y [m]", "σ̂z [m]", f"σ̂P_{dim}D [m]"])
|
||
mittel_sP = helmert_punktfehler[f"σ̂P_{dim}D [m]"].mean()
|
||
print(f"Mittlerer Helmert-Punktfehler über alle Punkte: {mittel_sP:.6f} m")
|
||
display(HTML(helmert_punktfehler.to_html(index=False)))
|
||
helmert_punktfehler.to_excel(r"Netzqualitaet\Standardabweichungen_Helmertscher_Punktfehler.xlsx",index=False)
|
||
return helmert_punktfehler, mittel_sP
|
||
|
||
|
||
|
||
@staticmethod
|
||
def standardellipsoid(Qxx: np.ndarray, sigma0_apost: float, unbekannten_liste: list) -> pd.DataFrame:
|
||
"""
|
||
Berechnet das Standardellipsoid aus Qxx und σ̂₀ a posteriori.
|
||
|
||
Für jeden Punkt wird aus der Kofaktor-Matrix der Unbekannten Qxx die 3×3-Submatrix
|
||
der Koordinaten (X, Y, Z) gebildet. Daraus werden zunächst die Standardabweichungen
|
||
σx, σy, σz bestimmt. Anschließend werden über eine Eigenwertzerlegung der Submatrix
|
||
die Halbachsen des (punktbezogenen) Fehlerellipsoids berechnet:
|
||
|
||
- Halbachse a aus dem größten Eigenwert,
|
||
- Halbachse b aus dem mittleren Eigenwert,
|
||
- Halbachse c aus dem kleinsten Eigenwert.
|
||
|
||
Die Orientierung des Ellipsoids im Raum wird über die zugehörigen Eigenvektoren bestimmt.
|
||
Aus der Modalmatrix M, bestehend aus den sortierten Eigenvektoren, werden die drei Eulerwinkel α, β und γ
|
||
berechnet.
|
||
|
||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste.
|
||
Die Ergebnisse werden tabellarisch ausgegeben und in eine Excel-Datei exportiert.
|
||
|
||
:param Qxx: Kofaktor-Matrix der Unbekannten.
|
||
:type Qxx: np.ndarray
|
||
:param sigma0_apost: A-posteriori Standardabweichung der Gewichtseinheit σ̂₀.
|
||
:type sigma0_apost: float
|
||
:param unbekannten_liste: Liste der Unbekannten.
|
||
:type unbekannten_liste: list
|
||
:return: Tabelle mit Standardabweichungen, Ellipsoid-Halbachsen, Eulerwinkel.
|
||
: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())
|
||
idx_z = next(i for i, n in enumerate(namen_str) if n.upper() == f"Z{pid}".upper())
|
||
|
||
indices = [idx_x, idx_y, idx_z]
|
||
Q_sub = Qxx[np.ix_(indices, indices)]
|
||
|
||
# Standardabweichungen
|
||
sx = sigma0_apost * np.sqrt(Q_sub[0, 0])
|
||
sy = sigma0_apost * np.sqrt(Q_sub[1, 1])
|
||
sz = sigma0_apost * np.sqrt(Q_sub[2, 2])
|
||
|
||
# Eigenwertzerlegung
|
||
eigenwerte, eigenvektoren = np.linalg.eigh(Q_sub)
|
||
reihenfolge = np.argsort(eigenwerte)[::-1]
|
||
eigenwerte = eigenwerte[reihenfolge]
|
||
eigenwerte = np.clip(eigenwerte, 0.0, None)
|
||
M = eigenvektoren[:, reihenfolge]
|
||
if np.linalg.det(M) < 0:
|
||
M[:, 2] *= -1
|
||
|
||
# Halbachsen des Standardellipsoids
|
||
s_a = sigma0_apost * np.sqrt(eigenwerte[0]) # Große Halbachse a
|
||
s_b = sigma0_apost * np.sqrt(eigenwerte[1]) # Mittlere Halbachse b
|
||
s_c = sigma0_apost * np.sqrt(eigenwerte[2]) # Kleine Halbachse c
|
||
|
||
# Eulerwinkel aus M
|
||
beta = np.arcsin(-M[2, 0])
|
||
alpha = np.arctan2(M[2, 1], M[2, 2])
|
||
gamma = np.arctan2(M[1, 0], M[0, 0])
|
||
alpha_gon = (alpha * 200 / np.pi)% 400.0
|
||
beta_gon = (beta * 200 / np.pi)% 400.0
|
||
gamma_gon = (gamma * 200 / np.pi)% 400.0
|
||
|
||
daten.append([
|
||
pid,
|
||
float(sx), float(sy), float(sz),
|
||
float(s_a), float(s_b), float(s_c),
|
||
float(alpha_gon), float(beta_gon), float(gamma_gon)
|
||
])
|
||
except:
|
||
continue
|
||
standardellipsoid = pd.DataFrame(daten, columns=["Punkt", "σ̂x [m]", "σ̂y [m]", "σ̂z [m]", "Halbachse a [m]", "Halbachse b [m]", "Halbachse c [m]", "α [gon]", "β [gon]", "γ [gon]"])
|
||
standardellipsoid["σ̂x [m]"] = standardellipsoid["σ̂x [m]"].astype(float).round(6)
|
||
standardellipsoid["σ̂y [m]"] = standardellipsoid["σ̂y [m]"].astype(float).round(6)
|
||
standardellipsoid["σ̂z [m]"] = standardellipsoid["σ̂z [m]"].astype(float).round(6)
|
||
standardellipsoid["Halbachse a [m]"] = standardellipsoid["Halbachse a [m]"].astype(float).round(6)
|
||
standardellipsoid["Halbachse b [m]"] = standardellipsoid["Halbachse b [m]"].astype(float).round(6)
|
||
standardellipsoid["Halbachse c [m]"] = standardellipsoid["Halbachse c [m]"].astype(float).round(6)
|
||
standardellipsoid["α [gon]"] = standardellipsoid["α [gon]"].astype(float).round(3)
|
||
standardellipsoid["β [gon]"] = standardellipsoid["β [gon]"].astype(float).round(3)
|
||
standardellipsoid["γ [gon]"] = standardellipsoid["γ [gon]"].astype(float).round(3)
|
||
display(HTML(standardellipsoid.to_html(index=False)))
|
||
standardellipsoid.to_excel(r"Netzqualitaet\Standardellipsoid.xlsx", index=False)
|
||
return standardellipsoid
|
||
|
||
|
||
|
||
@staticmethod
|
||
def konfidenzellipsoid(Qxx: np.ndarray, sigma0_apost: float, unbekannten_liste: list, R: int) -> tuple[pd.DataFrame, float]:
|
||
"""
|
||
Berechnet das Konfidenzellipsoid aus Qxx, σ̂₀ a posteriori und einem Konfidenzniveau.
|
||
|
||
Auf Basis der Kovarianz-Matrix der Unbekannten Qxx und der a posteriori
|
||
Standardabweichung der Gewichtseinheit σ̂₀ werden für jeden Punkt die Parameter
|
||
des Konfidenzellipsoids berechnet. Das Konfidenzniveau wird mittels einer Eingabe
|
||
über alpha festgelegt.
|
||
|
||
Für jeden Punkt wird die 3×3-Submatrix der Koordinaten aus Qxx gebildet.
|
||
Über eine Eigenwertzerlegung dieser Submatrix werden die drei Hauptachsen des
|
||
Ellipsoids bestimmt. Die Halbachsen des Konfidenzellipsoids ergeben sich aus:
|
||
|
||
- Aₖ: große Halbachse des Konfidenzellipsoids,
|
||
- Bₖ: mittlere Halbachse des Konfidenzellipsoids,
|
||
- Cₖ: kleine Halbachse des Konfidenzellipsoids,
|
||
|
||
unter Verwendung eines Faktors aus der F-Verteilung in Abhängigkeit vom Konfidenzniveau und den Freiheitsgraden.
|
||
Die Orientierung des Ellipsoids im Raum wird über die zugehörigen Eigenvektoren bestimmt.
|
||
Aus der Modalmatrix M, bestehend aus den sortierten Eigenvektoren, werden die drei Eulerwinkel α, β und γ
|
||
berechnet.
|
||
|
||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste.
|
||
Optional wird die Tabelle ausgegeben und als Excel-Datei exportiert.
|
||
|
||
:param Qxx: Kofaktor-Matrix der geschätzten Unbekannten.
|
||
:type Qxx: np.ndarray
|
||
:param sigma0_apost: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||
:type sigma0_apost: float
|
||
:param unbekannten_liste: Liste der Unbekannten.
|
||
:type unbekannten_liste: list
|
||
:param R: Redundanz (Freiheitsgrade) für die F-Verteilung.
|
||
:type R: int
|
||
:return: Tabelle der Konfidenzellipse je Punkt, verwendetes alpha.
|
||
:rtype: tuple[pandas.DataFrame, float]
|
||
"""
|
||
alpha_input = input("Irrtumswahrscheinlichkeit α wählen (z.B. 0.05, 0.01) [Standard=0.05]: ")
|
||
if alpha_input.strip() == "":
|
||
alpha_konf = 0.05
|
||
else:
|
||
alpha_konf = float(alpha_input)
|
||
print(f"→ Verwende alpha = {alpha_konf} (Konfidenz = {(1 - alpha_konf) * 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ürs Konfidenzellipoid (F-Verteilung)
|
||
k = float(np.sqrt(3.0 * f.ppf(1.0 - alpha_konf, 3, 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())
|
||
idx_z = next(i for i, n in enumerate(namen_str) if n.upper() == f"Z{pid}".upper())
|
||
|
||
indices = [idx_x, idx_y, idx_z]
|
||
Q_sub = Qxx[np.ix_(indices, indices)]
|
||
|
||
# Standardabweichungen
|
||
sx = sigma0_apost * np.sqrt(Q_sub[0, 0])
|
||
sy = sigma0_apost * np.sqrt(Q_sub[1, 1])
|
||
sz = sigma0_apost * np.sqrt(Q_sub[2, 2])
|
||
|
||
# Eigenwertzerlegung
|
||
eigenwerte, eigenvektoren = np.linalg.eigh(Q_sub)
|
||
order = np.argsort(eigenwerte)[::-1]
|
||
eigenwerte = eigenwerte[order]
|
||
eigenwerte = np.clip(eigenwerte, 0.0, None)
|
||
M = eigenvektoren[:, order]
|
||
if np.linalg.det(M) < 0:
|
||
M[:, 2] *= -1
|
||
|
||
# Halbachsen des Konfidenzellipoid
|
||
A_K = k * sigma0_apost * np.sqrt(eigenwerte[0])
|
||
B_K = k * sigma0_apost * np.sqrt(eigenwerte[1])
|
||
C_K = k * sigma0_apost * np.sqrt(eigenwerte[2])
|
||
|
||
# Eulerwinkel aus M
|
||
beta = np.arcsin(-M[2, 0])
|
||
alpha_ = np.arctan2(M[2, 1], M[2, 2])
|
||
gamma = np.arctan2(M[1, 0], M[0, 0])
|
||
|
||
alpha_gon = (alpha_ * 200.0 / np.pi) % 400.0
|
||
beta_gon = (beta * 200.0 / np.pi) % 400.0
|
||
gamma_gon = (gamma * 200.0 / np.pi) % 400.0
|
||
|
||
daten.append([
|
||
pid,
|
||
float(sx), float(sy), float(sz),
|
||
float(A_K), float(B_K), float(C_K),
|
||
float(alpha_gon), float(beta_gon), float(gamma_gon)
|
||
])
|
||
|
||
except:
|
||
continue
|
||
|
||
konfidenzellipoid = pd.DataFrame(daten, columns=["Punkt", "σ̂x [m]", "σ̂y [m]", "σ̂z [m]", "Halbachse a_k [m]", "Halbachse b_k [m]", "Halbachse c_k [m]", "α [gon]", "β [gon]", "γ [gon]"])
|
||
konfidenzellipoid["σ̂x [m]"] = konfidenzellipoid["σ̂x [m]"].astype(float).round(6)
|
||
konfidenzellipoid["σ̂y [m]"] = konfidenzellipoid["σ̂y [m]"].astype(float).round(6)
|
||
konfidenzellipoid["σ̂z [m]"] = konfidenzellipoid["σ̂z [m]"].astype(float).round(6)
|
||
konfidenzellipoid["Halbachse a_k [m]"] = konfidenzellipoid["Halbachse a_k [m]"].astype(float).round(6)
|
||
konfidenzellipoid["Halbachse b_k [m]"] = konfidenzellipoid["Halbachse b_k [m]"].astype(float).round(6)
|
||
konfidenzellipoid["Halbachse c_k [m]"] = konfidenzellipoid["Halbachse c_k [m]"].astype(float).round(6)
|
||
konfidenzellipoid["α [gon]"] = konfidenzellipoid["α [gon]"].astype(float).round(3)
|
||
konfidenzellipoid["β [gon]"] = konfidenzellipoid["β [gon]"].astype(float).round(3)
|
||
konfidenzellipoid["γ [gon]"] = konfidenzellipoid["γ [gon]"].astype(float).round(3)
|
||
display(HTML(konfidenzellipoid.to_html(index=False)))
|
||
konfidenzellipoid.to_excel(r"Netzqualitaet\Konfidenzellipoid.xlsx", index=False)
|
||
return konfidenzellipoid, alpha_konf
|
||
|
||
|
||
|
||
@staticmethod
|
||
def konfidenzellipsen(Qxx: np.ndarray, sigma0_apost: float, unbekannten_liste: list, r_gesamt: int) -> tuple[pd.DataFrame, float]:
|
||
"""
|
||
Berechnet Konfidenzellipsen für Punkte aus der Kofaktor-Matrix Qxx.
|
||
|
||
Für jeden Punkt werden aus der 2×2-Submatrix der Koordinaten die Parameter der Konfidenzellipse bestimmt.
|
||
Grundlage ist die a posteriori Standardabweichung der Gewichtseinheit sowie ein Konfidenzniveau, das über
|
||
die Irrtumswahrscheinlichkeit α festgelegt wird.
|
||
|
||
Die Halbachsen der Konfidenzellipse werden aus den Eigenwerten der Kovarianzmatrix berechnet.
|
||
Der Skalierungsfaktor ergibt sich aus der F-Verteilung.Der Richtungswinkel θ wird aus dem
|
||
Eigenvektor der großen Halbachse bestimmt und in gon ausgegeben.
|
||
|
||
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste.
|
||
|
||
:param Qxx: Kofaktor-Matrix der geschätzten Unbekannten.
|
||
:type Qxx: numpy.ndarray
|
||
:param sigma0_apost: a posteriori Standardabweichung der Gewichtseinheit.
|
||
:type sigma0_apost: float
|
||
:param unbekannten_liste: Liste der Unbekannten.
|
||
:type unbekannten_liste: list
|
||
:param r_gesamt: Redundanz (Freiheitsgrade) des Netzes.
|
||
:type r_gesamt: int
|
||
:return: Tabelle der Konfidenzellipsen je Punkt.
|
||
:rtype: tuple[pd.DataFrame, float]
|
||
"""
|
||
|
||
# Irrtumswahrscheinlichkeit alpha
|
||
alpha_input = input("Irrtumswahrscheinlichkeit α wählen (z.B. 0.05, 0.01) [Standard=0.05]: ")
|
||
if alpha_input.strip() == "":
|
||
alpha_konf2d = 0.05
|
||
else:
|
||
alpha_konf2d = float(alpha_input)
|
||
print(f"→ Verwende alpha = {alpha_konf2d} (Konfidenz = {(1 - alpha_konf2d) * 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 die Konfidenzellipe (F-Verteilung)
|
||
k2 = float(np.sqrt(2.0 * f.ppf(1.0 - alpha_konf2d, 2, r_gesamt)))
|
||
|
||
for pid in punkt_ids:
|
||
try:
|
||
idx_1 = next(i for i, n in enumerate(namen_str) if n.upper() == f"X{pid}".upper())
|
||
idx_2 = next(i for i, n in enumerate(namen_str) if n.upper() == f"Y{pid}".upper())
|
||
|
||
index = [idx_1, idx_2]
|
||
Q_sub = Qxx[np.ix_(index, index)]
|
||
|
||
# Standardabweichungen
|
||
sigma1 = sigma0_apost * np.sqrt(Q_sub[0, 0])
|
||
sigma2 = sigma0_apost * np.sqrt(Q_sub[1, 1])
|
||
|
||
# Eigenwertzerlegung 2D
|
||
eigenwerte, eigenvektoren = np.linalg.eigh(Q_sub)
|
||
reihenfolge = np.argsort(eigenwerte)[::-1]
|
||
eigenwerte = eigenwerte[reihenfolge]
|
||
eigenwerte = np.clip(eigenwerte, 0.0, None)
|
||
eigenvektoren = eigenvektoren[:, reihenfolge]
|
||
|
||
# Halbachsen der Konfidenzellipse
|
||
a = k2 * sigma0_apost * np.sqrt(eigenwerte[0])
|
||
b = k2 * sigma0_apost * np.sqrt(eigenwerte[1])
|
||
|
||
# Winkel aus Eigenvektor der großen Halbachse
|
||
v = eigenvektoren[:, 0]
|
||
t_gon = (np.arctan2(v[1], v[0]) * (200.0 / np.pi)) % 400.0
|
||
|
||
daten.append([pid, float(sigma1), float(sigma2), float(a), float(b), float(t_gon)])
|
||
|
||
except StopIteration:
|
||
continue
|
||
|
||
Konfidenzellipse = pd.DataFrame(daten, columns=[
|
||
"Punkt", "σ̂x [m]", "σ̂y [m]",
|
||
"Halbachse a_k [m]", "Halbachse b_k [m]", "θ [gon]"
|
||
])
|
||
return Konfidenzellipse, alpha_konf2d
|
||
|
||
|
||
|
||
@staticmethod
|
||
def konfidenzellipsen_enu(a: float, b: float, ausgabe_parameterschaetzung: dict, liste_unbekannte: list, ausgleichungsergebnis: dict, sigma0apost: float, r_gesamt: int) -> tuple[pd.DataFrame, np.ndarray, float]:
|
||
"""
|
||
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 "konfidenzellipsen" 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 sigma0apost: a-posteriori Standardabweichung der Gewichtseinheit s₀.
|
||
:type sigma0apost: 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, float]
|
||
"""
|
||
|
||
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.Einheitenumrechnung.rad_to_gon_Decimal(B0):.8f} rad, L0={Einheitenumrechnung.Einheitenumrechnung.rad_to_gon_Decimal(L0):.8f} rad")
|
||
|
||
# 2) Konfidenzellipsen im ENU-System
|
||
Konfidenzellipse_ENU, alpha_konf2d = Genauigkeitsmaße.konfidenzellipsen(
|
||
Qxx_enu,
|
||
sigma0apost,
|
||
liste_unbekannte,
|
||
r_gesamt
|
||
)
|
||
|
||
# 3) Spaltennamen anpassen
|
||
Konfidenzellipse_ENU = Konfidenzellipse_ENU.rename(columns={
|
||
"σ̂x [m]": "σ̂E [m]",
|
||
"σ̂y [m]": "σ̂N [m]",
|
||
"Halbachse a_k [m]": "Halbachse a_k_EN [m]",
|
||
"Halbachse b_k [m]": "Halbachse b_k_EN [m]",
|
||
"θ [gon]": "θ_EN [gon]"
|
||
})
|
||
|
||
# 4) Runden und Anzeigen
|
||
Konfidenzellipse_ENU["σ̂E [m]"] = Konfidenzellipse_ENU["σ̂E [m]"].round(6)
|
||
Konfidenzellipse_ENU["σ̂N [m]"] = Konfidenzellipse_ENU["σ̂N [m]"].round(6)
|
||
Konfidenzellipse_ENU["Halbachse a_k_EN [m]"] = Konfidenzellipse_ENU["Halbachse a_k_EN [m]"].round(6)
|
||
Konfidenzellipse_ENU["Halbachse b_k_EN [m]"] = Konfidenzellipse_ENU["Halbachse b_k_EN [m]"].round(6)
|
||
Konfidenzellipse_ENU["θ_EN [gon]"] = Konfidenzellipse_ENU["θ_EN [gon]"].round(3)
|
||
|
||
display(HTML(Konfidenzellipse_ENU.to_html(index=False)))
|
||
|
||
# 5) Export
|
||
Konfidenzellipse_ENU.to_excel(r"Netzqualitaet\Konfidenzellipse_ENU.xlsx", index=False)
|
||
return Konfidenzellipse_ENU, R0, alpha_konf2d
|
||
|
||
|
||
|
||
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: dict[str, tuple[float, float, float]], unbekannten_labels: list, beobachtungs_labels: list, df_konf_ellipsen_enu: pd.DataFrame, skalierung: float, n_ellipse_pts: int = 60, titel: str = "Netzplot im ENU-System mit Konfidenzellipsen") -> go.Figure:
|
||
|
||
"""
|
||
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,
|
||
- Konfidenzellipsen je Punkt,
|
||
- Netzpunkte mit Punkt-ID und Koordinaten im Hover-Text angezeigt.
|
||
|
||
Die Ellipsen werden zur besseren Sichtbarkeit mit dem Faktor "skalierung" vergrößert.
|
||
Zusätzlich wird eine PNG-Grafik über "plot.write_image(...)" exportiert.
|
||
|
||
:param Koord_ENU: Dictionary der Punktkoordinaten im ENU-System.
|
||
:type Koord_ENU: dict
|
||
:param unbekannten_labels: Liste der Unbekannten zur Ableitung der Punkt-IDs.
|
||
: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: Plotly-Figure-Objekt mit Netz, Beobachtungen und Ellipsen.
|
||
:rtype: plotly.graph_objects.Figure
|
||
"""
|
||
|
||
namen = [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 namen if nm and nm[0].upper() in ("X", "Y", "Z")})
|
||
|
||
plot = 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_linie, y_linie = [], []
|
||
for beob in beobachtungs_labels:
|
||
bl_str = str(beob).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
|
||
|
||
beob_text = str(beob)
|
||
punkte = []
|
||
for pid in punkt_ids:
|
||
if (f"_{pid}" in beob_text) or beob_text.startswith(f"{pid}_"):
|
||
if pid in Koord_ENU:
|
||
punkte.append(pid)
|
||
|
||
if len(punkte) >= 2:
|
||
punkt1, punkt2 = punkte[0], punkte[1]
|
||
x_linie.extend([Koord_ENU[punkt1][0], Koord_ENU[punkt2][0], None]) # E
|
||
y_linie.extend([Koord_ENU[punkt1][1], Koord_ENU[punkt2][1], None]) # N
|
||
|
||
if x_linie:
|
||
plot.add_trace(go.Scatter(x=x_linie, y=y_linie, 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["Halbachse a_k_EN [m]"]) * skalierung
|
||
b = float(row["Halbachse b_k_EN [m]"]) * skalierung
|
||
theta = float(row["θ_EN [gon]"]) * np.pi / 200.0
|
||
|
||
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]
|
||
|
||
plot.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")
|
||
|
||
plot.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"
|
||
))
|
||
|
||
plot.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"
|
||
)
|
||
|
||
plot.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
|
||
)
|
||
plot.write_image(r"Netzqualitaet\netzplot_ellipsen_volle_ausdehnung.png")
|
||
return plot
|
||
|
||
|
||
def plot_speichere_aktuelle_ansicht(plot: go.Figure, dateiname: str = r"Netzqualitaet\netzplot_ellipsen_zoom_ansicht.png") -> None:
|
||
"""
|
||
Speichert die aktuell dargestellte Plot-Ansicht als Bilddatei.
|
||
|
||
Die Funktion übernimmt ein vorhandenes Plotly-Figure-Objekt und exportiert die momentan im Layout
|
||
eingestellte Ansicht (inklusive Zoom- oder Achsenbereich) als PNG-Datei. Dazu wird das aktuelle
|
||
Layout übernommen und in ein neues Figure-Objekt kopiert, welches anschließend mit
|
||
"write_image" gespeichert wird.
|
||
|
||
Zusätzlich werden die aktuellen Achsenbereiche (E/N) in der Konsole ausgegeben, um den gespeicherten
|
||
Ausschnitt zu dokumentieren.
|
||
|
||
:param plot: Plotly-Figure-Objekt mit dem darzustellenden Netzplot.
|
||
:type plot: plotly.graph_objects.Figure
|
||
:param dateiname: Dateipfad für die Ausgabedatei (Standard: PNG im Ordner Netzqualitaet).
|
||
:type dateiname: str
|
||
:return: None
|
||
:rtype: None
|
||
"""
|
||
|
||
aktuelles_layout = plot.layout
|
||
export_plot = go.Figure(plot.data, layout=aktuelles_layout)
|
||
export_plot.write_image(dateiname, scale=2)
|
||
print(f"✅ Aktuelle Ansicht wurde erfolgreich gespeichert: {dateiname}")
|
||
x_bereich = aktuelles_layout.xaxis.range
|
||
y_bereich = aktuelles_layout.yaxis.range
|
||
if x_bereich and y_bereich:
|
||
print(
|
||
f"Ausschnitt: E[{x_bereich[0]:.2f} bis {x_bereich[1]:.2f}], N[{y_bereich[0]:.2f} bis {y_bereich[1]:.2f}]")
|
||
else:
|
||
print("Hinweis: Es wurde die Standardansicht (kein Zoom) gespeichert.") |