259 lines
9.1 KiB
Python
259 lines
9.1 KiB
Python
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<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}) |