import numpy as np import plotly.graph_objects as go from scipy.stats import f as f_dist import pandas as pd 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) def helmert_punktfehler(Qxx, s0_apost, unbekannten_liste, dim=3): diagQ = np.diag(Qxx) daten = [] n_punkte = len(unbekannten_liste) // 3 for i in range(n_punkte): sym_x = str(unbekannten_liste[3 * i]) # z.B. "X10009" punkt = sym_x[1:] # -> "10009" qx = diagQ[3 * i] qy = diagQ[3 * i + 1] qz = diagQ[3 * i + 2] sx = s0_apost * np.sqrt(qx) sy = s0_apost * np.sqrt(qy) sz = s0_apost * np.sqrt(qz) if dim == 2: sP = s0_apost * np.sqrt(qx + qy) else: sP = s0_apost * np.sqrt(qx + qy + qz) daten.append([ punkt, float(sx), float(sy), float(sz), float(sP) ]) helmert_punktfehler = pd.DataFrame(daten, columns=["Punkt", "σx", "σy", "σz", f"σP_{dim}D"]) return helmert_punktfehler def standardellipse(Qxx, s0_apost, unbekannten_liste, dim_labels=3): Qxx = np.asarray(Qxx, float) data = [] n_punkte = len(unbekannten_liste) // dim_labels for i in range(n_punkte): sym_x = str(unbekannten_liste[dim_labels * i]) # z.B. "X10009" punkt = sym_x[1:] # -> "10009" ix = dim_labels * i iy = dim_labels * i + 1 # 2x2-Kofaktorblock Qxx_ = Qxx[ix, ix] Qyy_ = Qxx[iy, iy] Qyx_ = Qxx[iy, ix] # Standardabweichungen der Koordinatenkomponenten sx = s0_apost * np.sqrt(Qxx_) sy = s0_apost * np.sqrt(Qyy_) sxy = (s0_apost ** 2) * Qyx_ # k und Eigenwerte (Q_dmax, Q_dmin) k = np.sqrt((Qxx_ - Qyy_) ** 2 + 4 * (Qyx_ ** 2)) Q_dmax = 0.5 * (Qxx_ + Qyy_ + k) Q_dmin = 0.5 * (Qxx_ + Qyy_ - k) # Halbachsen (Standardabweichungen entlang Hauptachsen) s_max = s0_apost * np.sqrt(Q_dmax) s_min = s0_apost * np.sqrt(Q_dmin) # Richtungswinkel theta (Hauptachse) in rad: theta_rad = 0.5 * np.arctan2(2 * Qyx_, (Qxx_ - Qyy_)) # in gon theta_gon = theta_rad * (200 / np.pi) if theta_gon < 0: theta_gon += 200.0 data.append([ punkt, float(sx), float(sy), float(sxy), float(s_max), float(s_min), float(theta_gon) ]) standardellipse = pd.DataFrame(data, columns=["Punkt", "σx", "σy", "σxy", "s_max", "s_min", "θ [gon]"]) return standardellipse def konfidenzellipse(Qxx, s0_apost, unbekannten_liste, R, alpha=0.05): Qxx = np.asarray(Qxx, float) data = [] n_punkte = len(unbekannten_liste) // 3 # X,Y,Z je Punkt angenommen k = float(np.sqrt(2.0 * f_dist.ppf(1.0 - alpha, 2, R))) for i in range(n_punkte): punkt = str(unbekannten_liste[3 * i])[1:] # "X10009" -> "10009" ix = 3 * i iy = 3 * i + 1 Qxx_ = Qxx[ix, ix] Qyy_ = Qxx[iy, iy] Qxy_ = Qxx[iy, ix] # = Qyx # k für Eigenwerte kk = np.sqrt((Qxx_ - Qyy_) ** 2 + 4 * (Qxy_ ** 2)) Q_dmax = 0.5 * (Qxx_ + Qyy_ + kk) Q_dmin = 0.5 * (Qxx_ + Qyy_ - kk) # Standard-Halbachsen (1-sigma) s_max = s0_apost * np.sqrt(Q_dmax) s_min = s0_apost * np.sqrt(Q_dmin) # Orientierung (Hauptachse) in gon theta_rad = 0.5 * np.arctan2(2 * Qxy_, (Qxx_ - Qyy_)) theta_gon = theta_rad * (200 / np.pi) if theta_gon < 0: theta_gon += 200.0 # Konfidenz-Halbachsen a_K = k * s_max b_K = k * s_min data.append([punkt, float(a_K), float(b_K), float(theta_gon)]) konfidenzellipsen = pd.DataFrame(data, columns=["Punkt", "a_K", "b_K", "θ [gon]"]) return konfidenzellipsen def plot_netz_komplett_final(x_vektor, unbekannten_labels, beobachtungs_labels, Qxx, sigma0_apost, k_faktor=2.447, v_faktor=1000): """ Optimierter Plot für Jupyter Notebook: - k_faktor: Statistischer Sicherheitsfaktor (2.447 entspricht 95% für 2D) - v_faktor: Optische Überhöhung der Ellipsen (z.B. 1000 = mm werden als m dargestellt) """ x_vektor = np.asarray(x_vektor, float).reshape(-1) Qxx = np.asarray(Qxx, float) # 1. Datenaufbereitung coords = {} punkt_ids = sorted(set(str(l)[1:] for l in unbekannten_labels if str(l).startswith(('X', 'Y', 'Z')))) pts_data = [] for pid in punkt_ids: try: ix = next(i for i, s in enumerate(unbekannten_labels) if str(s) == f"X{pid}") iy = next(i for i, s in enumerate(unbekannten_labels) if str(s) == f"Y{pid}") x, y = float(x_vektor[ix]), float(x_vektor[iy]) coords[pid] = (x, y) # Kovarianzmatrix extrahieren und mit s0^2 skalieren q_idx = [ix, iy] Q_sub = Qxx[np.ix_(q_idx, q_idx)] * (sigma0_apost ** 2) pts_data.append({'id': pid, 'x': x, 'y': y, 'Q': Q_sub}) except: continue if len(pts_data) == 0: raise ValueError( "Keine Netzpunkte extrahiert. Prüfe: x_vektor Form (u,) und Qxx Form (u,u) sowie Labels 'X'/'Y'.") fig = go.Figure() # 2. Beobachtungen (Gruppiert) beob_typen = { 'GNSS-Basislinien': {'pattern': 'gnss', 'color': 'rgba(255, 100, 0, 0.4)'}, 'Nivellement': {'pattern': 'niv', 'color': 'rgba(0, 200, 100, 0.4)'}, 'Tachymeter': {'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() if (info['pattern'] in bl_str and info['pattern'] != '') or ( info['pattern'] == '' and 'gnss' not in bl_str and 'niv' not in bl_str): pts = [pid for pid in coords if f"_{pid}" in str(bl) or str(bl).startswith(f"{pid}_")] if len(pts) >= 2: x_l.extend([coords[pts[0]][0], coords[pts[1]][0], None]) y_l.extend([coords[pts[0]][1], coords[pts[1]][1], None]) 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))) # 3. Konfidenzellipsen mit v_faktor for pt in pts_data: vals, vecs = np.linalg.eigh(pt['Q']) order = vals.argsort()[::-1] vals, vecs = vals[order], vecs[:, order] theta = np.degrees(np.arctan2(vecs[1, 0], vecs[0, 0])) # Skalierung: k_faktor (Statistik) * v_faktor (Optik) a = k_faktor * np.sqrt(vals[0]) * v_faktor b = k_faktor * np.sqrt(vals[1]) * v_faktor t = np.linspace(0, 2 * np.pi, 40) e_x = a * np.cos(t) e_y = b * np.sin(t) R = np.array([[np.cos(np.radians(theta)), -np.sin(np.radians(theta))], [np.sin(np.radians(theta)), np.cos(np.radians(theta))]]) rot = np.dot(R, np.array([e_x, e_y])) fig.add_trace(go.Scatter( x=rot[0, :] + pt['x'], y=rot[1, :] + pt['y'], mode='lines', line=dict(color='red', width=1.5), name=f"Ellipsen (Vergrößert {v_faktor}x)", legendgroup="Ellipsen", showlegend=(pt == pts_data[0]), # Nur einmal in der Legende zeigen hoverinfo='skip' )) # 4. Punkte df_pts = pd.DataFrame(pts_data) fig.add_trace(go.Scatter( x=df_pts['x'], y=df_pts['y'], mode='markers+text', text=df_pts['id'], textposition="top center", marker=dict(size=8, color='black'), name="Netzpunkte" )) # 5. Layout & Notebook-Größe fig.update_layout( title=f"Netzausgleichung: Ellipsen {v_faktor}-fach vergrößert (k={k_faktor})", xaxis=dict(title="X [m]", tickformat="f", separatethousands=True, scaleanchor="y", scaleratio=1, showgrid=True, gridcolor='lightgrey'), yaxis=dict(title="Y [m]", tickformat="f", separatethousands=True, showgrid=True, gridcolor='lightgrey'), width=1100, # Breite angepasst height=900, # Höhe deutlich vergrößert für Jupiter Notebook plot_bgcolor='white', legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01, bgcolor="rgba(255,255,255,0.8)") ) # Info-Annotation als Ersatz für einen physischen Maßstabstab fig.add_annotation( text=f"Maßstab Ellipsen:
Dargestellte Größe = Wahre Ellipse × {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})