Files
Masterprojekt_V3/Netzqualität_Genauigkeit.py
2026-01-22 08:51:18 +01:00

435 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
@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
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<id>'/'Y<id>'.")
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"<b>Maßstab Ellipsen:</b><br>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})
def plot_netz_final_mit_df_ellipsen(x_vektor, unbekannten_labels, beobachtungs_labels, df_ellipsen, v_faktor=1000):
# 1. Punkte extrahieren
coords = {}
# Wir nehmen an, dass die Reihenfolge im x_vektor X, Y, Z pro Punkt ist
punkt_ids = sorted(set(str(l)[1:] for l in unbekannten_labels if str(l).startswith(('X', 'Y', 'Z'))))
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}")
coords[pid] = (float(x_vektor[ix]), float(x_vektor[iy]))
except:
continue
fig = go.Figure()
# 2. Beobachtungslinien (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()
# Einfache Logik zur Typtrennung
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. Ellipsen aus dem DataFrame zeichnen
for _, row in df_ellipsen.iterrows():
pid = str(row['Punkt'])
if pid in coords:
x0, y0 = coords[pid]
# Werte aus DF (mit v_faktor skalieren)
a = row['a_K'] * v_faktor
b = row['b_K'] * v_faktor
theta_gon = row['θ [gon]']
# Umrechnung: gon -> rad für die Rotation
# Da im Plot X horizontal und Y vertikal ist, entspricht theta_gon dem Winkel zur X-Achse
theta_rad = theta_gon * (np.pi / 200.0)
# Ellipsen berechnen
t = np.linspace(0, 2 * np.pi, 50)
e_x = a * np.cos(t)
e_y = b * np.sin(t)
# Ausrichtung der Ellipsen
R = np.array([[np.cos(theta_rad), -np.sin(theta_rad)],
[np.sin(theta_rad), np.cos(theta_rad)]])
rot = np.dot(R, np.array([e_x, e_y]))
fig.add_trace(go.Scatter(
x=rot[0, :] + x0, y=rot[1, :] + y0,
mode='lines', line=dict(color='red', width=1.5),
name='Konfidenzellipsen',
legendgroup='Ellipsen',
showlegend=(pid == df_ellipsen.iloc[0]['Punkt']),
hoverinfo='text',
text=f"Punkt {pid}<br>a_K: {row['a_K']:.4f}m<br>b_K: {row['b_K']:.4f}m"
))
# Punkte plotten
df_pts = pd.DataFrame([(pid, c[0], c[1]) for pid, c in coords.items()], columns=['ID', 'X', 'Y'])
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"))
# Layout
fig.update_layout(
title=f"Netzplot (Ellipsen {v_faktor}x überhöht)",
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, height=900,
plot_bgcolor='white')
# Maßstabsangabe
fig.add_annotation(
text=f"<b>Skalierung:</b><br>Ellipsengröße im Plot = {v_faktor} × Realität",
align='left', showarrow=False, xref='paper', yref='paper', x=0.02, y=0.02,
bgcolor="rgba(255,255,255,0.8)", bordercolor="black", borderwidth=1)
fig.show(config={'scrollZoom': True})
import plotly.graph_objects as go
import numpy as np
def plot_netz_3D(x_vektor, unbekannten_labels, beobachtungs_labels, df_ellipsen, v_faktor=1000):
"""
Erzeugt einen interaktiven 3D-Plot des Netzes.
- v_faktor: Vergrößerung der Genauigkeits-Achsen (z.B. 1000 für mm -> m)
"""
# 1. Punkte extrahieren
pts = {}
punkt_ids = sorted(set(str(l)[1:] for l in unbekannten_labels if str(l).startswith(('X', 'Y', 'Z'))))
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}")
iz = next(i for i, s in enumerate(unbekannten_labels) if str(s) == f"Z{pid}")
pts[pid] = (float(x_vektor[ix]), float(x_vektor[iy]), float(x_vektor[iz]))
except:
continue
fig = go.Figure()
# 2. Beobachtungen (Linien im Raum)
# Wir zeichnen hier einfach alle Verbindungen
x_line, y_line, z_line = [], [], []
for bl in beobachtungs_labels:
p_in_l = [pid for pid in pts if f"_{pid}" in str(bl) or str(bl).startswith(f"{pid}_")]
if len(p_in_l) >= 2:
p1, p2 = pts[p_in_l[0]], pts[p_in_l[1]]
x_line.extend([p1[0], p2[0], None])
y_line.extend([p1[1], p2[1], None])
z_line.extend([p1[2], p2[2], None])
fig.add_trace(go.Scatter3d(
x=x_line, y=y_line, z=z_line,
mode='lines', line=dict(color='gray', width=2),
name='Beobachtungen'
))
# 3. Punkte & "Fehler-Kreuze" (als Ersatz für Ellipsoide)
# Ein echtes 3D-Ellipsoid ist grafisch schwer, daher zeichnen wir 3 Achsen
for pid, coord in pts.items():
# Hier könnten wir die echten Halbachsen aus der 3D-Eigenwertanalyse nutzen
# Für den Anfang plotten wir die Standardabweichungen sX, sY, sZ als Kreuz
fig.add_trace(go.Scatter3d(
x=[coord[0]], y=[coord[1]], z=[coord[2]],
mode='markers+text', text=[pid],
marker=dict(size=4, color='black'), name=f'Punkt {pid}'
))
# 4. Layout
fig.update_layout(
scene=dict(
xaxis_title='X [m]',
yaxis_title='Y [m]',
zaxis_title='Z [m]',
aspectmode='data' # WICHTIG: Verhältnisse 1:1:1 bewahren
),
width=1000, height=800,
title="Geozentrisches Netz in 3D"
)
fig.show()
# Aufruf