321 lines
11 KiB
Python
321 lines
11 KiB
Python
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}<br>E={E:.4f} m<br>N={N:.4f} m<br>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"<b>Maßstab Ellipsen:</b><br>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}) |