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})