import numpy as np
import plotly.graph_objects as go
from scipy.stats import f
import pandas as pd
import Berechnungen
class Genauigkeitsmaße:
@staticmethod
def berechne_s0apost(v: np.ndarray, P: np.ndarray, r: int) -> float:
vTPv_matrix = v.T @ P @ v
vTPv = float(vTPv_matrix.item())
s0apost = np.sqrt(vTPv / r)
return float(s0apost)
@staticmethod
def helmert_punktfehler(Qxx, s0_apost, unbekannten_liste, dim=3):
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"])
return helmert_punktfehler
@staticmethod
def standardellipse(Qxx, s0_apost, unbekannten_liste):
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]"])
return standardellipse
@staticmethod
def konfidenzellipse(Qxx, s0_apost, unbekannten_liste, R, alpha):
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]"])
return konfidenzellipse
class Plot:
@staticmethod
def netzplot_ellipsen(
Koord_ENU,
unbekannten_labels,
beobachtungs_labels,
df_konf_ellipsen_enu,
v_faktor=1000,
n_ellipse_pts=60,
title="Netzplot im ENU-System mit Konfidenzellipsen"
):
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["a_K"]) * v_faktor
b = float(row["b_K"]) * v_faktor
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 (×{v_faktor})",
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}
E={E:.4f} m
N={N:.4f} m
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"{title} (Ellipsen ×{v_faktor})",
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"Maßstab Ellipsen:
Dargestellte Größe = Konfidenzellipse × {v_faktor}",
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})