anpassung ellipsoid statt ellipse, anpassung lokaltest wegen falschem beta

This commit is contained in:
2026-02-07 04:02:31 +01:00
parent 4e89330cab
commit b40d61a9b1
4 changed files with 39680 additions and 282 deletions

File diff suppressed because one or more lines are too long

View File

@@ -230,8 +230,8 @@ class Export:
<li><a href="#vks">4. Varianzkomponentenschätzung</a></li> <li><a href="#vks">4. Varianzkomponentenschätzung</a></li>
<li><a href="#aeussere_zuver">5. Äussere Zuverlässigkeit</a></li> <li><a href="#aeussere_zuver">5. Äussere Zuverlässigkeit</a></li>
<li><a href="#Helmertscher_Punktfehler">6. Standardabweichungen und Helmert'scher Punktfehler</a></li> <li><a href="#Helmertscher_Punktfehler">6. Standardabweichungen und Helmert'scher Punktfehler</a></li>
<li><a href="#standardellipsen">7. Standardellipsen</a></li> <li><a href="#Standardellipsoid">7. Standardellipsoid</a></li>
<li><a href="#konfidenzellipsen">8. Konfidenzellipsen</a></li> <li><a href="#Konfidenzellipoid">8. Konfidenzellipoid</a></li>
<li><a href="#Konfidenzellipse_ENU">9. Konfidenzellipsen im ENU-System</a></li> <li><a href="#Konfidenzellipse_ENU">9. Konfidenzellipsen im ENU-System</a></li>
<li><a href="#netzplot_gesamt">10. Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz</a></li> <li><a href="#netzplot_gesamt">10. Netzplot im ENU-System mit Konfidenzellipsen - Gesamtes Netz</a></li>
<li><a href="#netzplot_zoom">11. Netzplot im ENU-System mit Konfidenzellipsen - Ausschnitt</a></li> <li><a href="#netzplot_zoom">11. Netzplot im ENU-System mit Konfidenzellipsen - Ausschnitt</a></li>
@@ -258,11 +258,11 @@ class Export:
<h3 id="Helmertscher_Punktfehler">6. Standardabweichungen und Helmert'scher Punktfehler</h3> <h3 id="Helmertscher_Punktfehler">6. Standardabweichungen und Helmert'scher Punktfehler</h3>
{ergebnisse['df_punktfehler'].to_html(index=False)} {ergebnisse['df_punktfehler'].to_html(index=False)}
<h3 id="standardellipsen">7. Standardellipsen</h3> <h3 id="Standardellipsoid">7. Standardellipsoid</h3>
{ergebnisse['df_ellipsen'].to_html(index=False)} {ergebnisse['df_standardellipsoid'].to_html(index=False)}
<h3 id="konfidenzellipsen">8. Konfidenzellipsen</h3> <h3 id="Konfidenzellipoid">8. Konfidenzellipoid</h3>
{ergebnisse['df_konfidenzellipsen'].to_html(index=False)} {ergebnisse['df_konfidenzellipsoid'].to_html(index=False)}
<h3 id="Konfidenzellipse_ENU">9. Konfidenzellipsen im ENU-System</h3> <h3 id="Konfidenzellipse_ENU">9. Konfidenzellipsen im ENU-System</h3>
{ergebnisse['df_konfidenzellipsen_enu'].to_html(index=False)} {ergebnisse['df_konfidenzellipsen_enu'].to_html(index=False)}

View File

@@ -56,9 +56,9 @@ class Genauigkeitsmaße:
Aus der Kofaktor-Matrix der Unbekannten Qxx werden die Kofaktoren punktweise ausgelesen. Durch Multiplikation Aus der Kofaktor-Matrix der Unbekannten Qxx werden die Kofaktoren punktweise ausgelesen. Durch Multiplikation
mit der a-posteriori Standardabweichung der Gewichtseinheit s₀ werden die Standardabweichungen je Koordinate mit der a-posteriori Standardabweichung der Gewichtseinheit s₀ werden die Standardabweichungen je Koordinate
(σx, σy, σz) sowie der Helmertsche Punktfehler σP berechnet: (σx, σy, σz) sowie der Helmert'sche Punktfehler σP berechnet:
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste (z.B. X1, Y1, Z1). Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste.
Die Dimension (2D/3D) wird interaktiv per Eingabe abgefragt. Zusätzlich werden die Die Dimension (2D/3D) wird interaktiv per Eingabe abgefragt. Zusätzlich werden die
Ergebnisse als Tabelle ausgegeben und in eine Excel-Datei exportiert. Ergebnisse als Tabelle ausgegeben und in eine Excel-Datei exportiert.
@@ -116,30 +116,35 @@ class Genauigkeitsmaße:
@staticmethod @staticmethod
def standardellipse(Qxx, s0_apost, unbekannten_liste): def standardellipsoid(Qxx, s0_apost, unbekannten_liste):
""" """
Berechnet die Standardellipse (Helmertsche Fehlerellipse) für die Punkte aus Qxx und s₀ a posteriori. Berechnet Achsenlängen des Fehlerellipsoids (a, b, c) sowie den 2D-Richtungswinkel θ aus Qxx und s₀ a posteriori.
Für jeden Punkt werden aus der Kofaktor-Matrix der Unbekannten Qxx die Für jeden Punkt wird aus der Kofaktor-Matrix der Unbekannten Qxx die 3×3-Submatrix
Kofaktoren von X und Y ausgelesen (qxx, qyy, qyx). der Koordinaten (X, Y, Z) gebildet. Daraus werden zunächst die Standardabweichungen
Daraus werden Standardabweichungen σx, σy sowie die Kovarianz σxy bestimmt und σx, σy, σz bestimmt. Anschließend werden über eine Eigenwertzerlegung der Submatrix
anschließend die Parameter der Standardellipse berechnet: die Halbachsen des (punktbezogenen) Fehlerellipsoids berechnet:
- Große und kleine Halbachse der Standardellipse, - Halbachse a aus dem größten Eigenwert,
- Richtungswinkel θ der großen Halbachse in gon. - Halbachse b aus dem mittleren Eigenwert,
- Halbachse c aus dem kleinsten Eigenwert.
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste (z.B. X1, Y1). Zusätzlich wird zur zweidimensionalen Darstellung ein Richtungswinkel θ in der X/Y-Ebene
Zusätzlich werden die Ergebnisse tabellarisch ausgegeben und in eine Excel-Datei expoertiert. aus den Kovarianzanteilen (qxx, qyy, qyx) berechnet (Winkel in gon).
Die Punktzuordnung erfolgt über die Symbolnamen der Unbekanntenliste.
Die Ergebnisse werden tabellarisch ausgegeben und in eine Excel-Datei exportiert.
:param Qxx: Kofaktor-Matrix der Unbekannten. :param Qxx: Kofaktor-Matrix der Unbekannten.
:type Qxx: numpy.ndarray :type Qxx: numpy.ndarray
:param s0_apost: a-posteriori Standardabweichung der Gewichtseinheit s₀. :param s0_apost: A-posteriori Standardabweichung der Gewichtseinheit s₀.
:type s0_apost: float :type s0_apost: float
:param unbekannten_liste: Liste der Unbekannten. :param unbekannten_liste: Liste der Unbekannten.
:type unbekannten_liste: list :type unbekannten_liste: list
:return: Tabelle mit Standardabweichungen und Parametern der Standardellipse je Punkt. :return: Tabelle mit Standardabweichungen (σx, σy, σz), Ellipsoid-Halbachsen (a, b, c) und Richtungswinkel θ je Punkt.
:rtype: pandas.DataFrame :rtype: pandas.DataFrame
""" """
Qxx = np.asarray(Qxx, float) Qxx = np.asarray(Qxx, float)
daten = [] daten = []
namen_str = [str(sym) for sym in unbekannten_liste] namen_str = [str(sym) for sym in unbekannten_liste]
@@ -153,65 +158,55 @@ class Genauigkeitsmaße:
try: try:
idx_x = next(i for i, n in enumerate(namen_str) if n.upper() == f"X{pid}".upper()) 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()) idx_y = next(i for i, n in enumerate(namen_str) if n.upper() == f"Y{pid}".upper())
idx_z = next(i for i, n in enumerate(namen_str) if n.upper() == f"Z{pid}".upper())
qxx = Qxx[idx_x, idx_x] indices = [idx_x, idx_y, idx_z]
qyy = Qxx[idx_y, idx_y] Q_sub = Qxx[np.ix_(indices, indices)]
qyx = Qxx[idx_y, idx_x]
# Standardabweichungen # Standardabweichungen
sx = s0_apost * np.sqrt(qxx) sx = s0_apost * np.sqrt(Q_sub[0, 0])
sy = s0_apost * np.sqrt(qyy) sy = s0_apost * np.sqrt(Q_sub[1, 1])
sxy = (s0_apost ** 2) * qyx sz = s0_apost * np.sqrt(Q_sub[2, 2])
k = np.sqrt((qxx - qyy) ** 2 + 4 * (qyx ** 2)) # Eigenwertzerlegung
eigenwerte, eigenvektoren = np.linalg.eigh(Q_sub)
# Q_dmax/min = 0.5 * (Qyy + Qxx +/- k) eigenwerte = np.sort(eigenwerte)[::-1]
q_dmax = 0.5 * (qyy + qxx + k)
q_dmin = 0.5 * (qyy + qxx - k)
# Halbachsen # Halbachsen
s_max = s0_apost * np.sqrt(q_dmax) s_a = s0_apost * np.sqrt(eigenwerte[0]) # Große Halbachse a
s_min = s0_apost * np.sqrt(q_dmin) s_b = s0_apost * np.sqrt(eigenwerte[1]) # Mittlere Halbachse b
s_c = s0_apost * np.sqrt(eigenwerte[2]) # Kleine Halbachse c
# Richtungswinkel theta in gon: # Richtungswinkel theta in gon:
zaehler = 2 * qyx qyx = Q_sub[1, 0]
nenner = qxx - qyy qxx = Q_sub[0, 0]
t_grund = 0.5 * np.arctan(abs(zaehler) / abs(nenner)) * (200 / np.pi) qyy = Q_sub[1, 1]
t_gon = 0.5 * np.arctan2(2 * qyx, qxx - qyy) * (200 / np.pi)
# Quadrantenabfrage if t_gon < 0:
if nenner > 0 and qyx > 0: # Qxx - Qyy > 0 und Qyx > 0 t_gon += 200
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([ daten.append([
pid, pid, float(sx), float(sy), float(sz),
float(sx), float(sy), float(sxy), float(s_a), float(s_b), float(s_c), float(t_gon)
float(s_max), float(s_min),
float(t_gon)
]) ])
except: except:
continue continue
standardellipse = pd.DataFrame(daten, columns=["Punkt", "σx [m]", "σy [m]", "σxy [m]", "Große Halbachse [m]", "Kleine Halbachse [m]", "θ [gon]"]) standardellipsoid = pd.DataFrame(daten, columns=["Punkt", "σx [m]", "σy [m]", "σz [m]", "Halbachse a [m]", "Halbachse b [m]", "Halbachse c [m]", "θ [gon]"])
standardellipse["σx [m]"] = standardellipse["σx [m]"].astype(float).round(4) standardellipsoid["σx [m]"] = standardellipsoid["σx [m]"].astype(float).round(4)
standardellipse["σy [m]"] = standardellipse["σy [m]"].astype(float).round(4) standardellipsoid["σy [m]"] = standardellipsoid["σy [m]"].astype(float).round(4)
standardellipse["Große Halbachse [m]"] = standardellipse["Große Halbachse [m]"].astype(float).round(4) standardellipsoid["σz [m]"] = standardellipsoid["σz [m]"].astype(float).round(4)
standardellipse["Kleine Halbachse [m]"] = standardellipse["Kleine Halbachse [m]"].astype(float).round(4) standardellipsoid["Halbachse a [m]"] = standardellipsoid["Halbachse a [m]"].astype(float).round(4)
standardellipse["θ [gon]"] = standardellipse["θ [gon]"].astype(float).round(3) standardellipsoid["Halbachse b [m]"] = standardellipsoid["Halbachse b [m]"].astype(float).round(4)
display(HTML(standardellipse.to_html(index=False))) standardellipsoid["Halbachse c [m]"] = standardellipsoid["Halbachse c [m]"].astype(float).round(4)
standardellipse.to_excel(r"Netzqualitaet\Standardellipse.xlsx", index=False) standardellipsoid["θ [gon]"] = standardellipsoid["θ [gon]"].astype(float).round(3)
return standardellipse display(HTML(standardellipsoid.to_html(index=False)))
standardellipsoid.to_excel(r"Netzqualitaet\Standardellipsoid.xlsx", index=False)
return standardellipsoid
@staticmethod @staticmethod
def konfidenzellipse(Qxx, s0_apost, unbekannten_liste, R, ausgabe_erfolgt): def konfidenzellipsoid(Qxx, s0_apost, unbekannten_liste, R, ausgabe_erfolgt):
""" """
Berechnet die Konfidenzellipse für Punkte aus Qxx und einem Konfidenzniveau. Berechnet die Konfidenzellipse für Punkte aus Qxx und einem Konfidenzniveau.
@@ -238,7 +233,7 @@ class Genauigkeitsmaße:
:rtype: tuple[pandas.DataFrame, float] :rtype: tuple[pandas.DataFrame, float]
:raises ValueError: Wenn alpha nicht in (0, 1) liegt oder nicht in float umgewandelt werden kann. :raises ValueError: Wenn alpha nicht in (0, 1) liegt oder nicht in float umgewandelt werden kann.
""" """
alpha_input = input("Konfidenzniveau wählen (z.B. 0.05 für 95%, 0.01 für 99%) [Standard=0.05]: ") alpha_input = input("Irrtumswahrscheinlichkeit α wählen (z.B. 0.05, 0.01) [Standard=0.05]: ")
if alpha_input.strip() == "": if alpha_input.strip() == "":
alpha = 0.05 alpha = 0.05
else: else:
@@ -250,72 +245,53 @@ class Genauigkeitsmaße:
punkt_ids = [n[1:] for n in namen_str if n.upper().startswith('X')] punkt_ids = [n[1:] for n in namen_str if n.upper().startswith('X')]
# Faktor für Konfidenzellipse (F-Verteilung) # Faktor fürs Konfidenzellipoid (F-Verteilung)
kk = float(np.sqrt(2.0 * f.ppf(1.0 - alpha, 2, R))) k = float(np.sqrt(3.0 * f.ppf(1.0 - alpha, 3, R)))
for pid in punkt_ids: for pid in punkt_ids:
try: try:
idx_x = next(i for i, n in enumerate(namen_str) if n.upper() == f"X{pid}".upper()) 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()) idx_y = next(i for i, n in enumerate(namen_str) if n.upper() == f"Y{pid}".upper())
idx_z = next(i for i, n in enumerate(namen_str) if n.upper() == f"Z{pid}".upper())
qxx = Qxx[idx_x, idx_x] indices = [idx_x, idx_y, idx_z]
qyy = Qxx[idx_y, idx_y] Q_sub = Qxx[np.ix_(indices, indices)]
qyx = Qxx[idx_y, idx_x]
# Standardabweichungen # Standardabweichungen
sx = s0_apost * np.sqrt(qxx) sx = s0_apost * np.sqrt(Q_sub[0, 0])
sy = s0_apost * np.sqrt(qyy) sy = s0_apost * np.sqrt(Q_sub[1, 1])
sxy = (s0_apost ** 2) * qyx sz = s0_apost * np.sqrt(Q_sub[2, 2])
k = np.sqrt((qxx - qyy) ** 2 + 4 * (qyx ** 2)) # Eigenwertzerlegung
eigenwerte, eigenvektoren = np.linalg.eigh(Q_sub)
eigenwerte = np.sort(eigenwerte)[::-1]
# Q_dmax/min = 0.5 * (Qyy + Qxx +/- k) # Halbachsen des Konfidenzellipoid
q_dmax = 0.5 * (qyy + qxx + k) A_K = k * s0_apost * np.sqrt(eigenwerte[0])
q_dmin = 0.5 * (qyy + qxx - k) B_K = k * s0_apost * np.sqrt(eigenwerte[1])
C_K = k * s0_apost * np.sqrt(eigenwerte[2])
# 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: # Richtungswinkel theta in gon:
zaehler = 2 * qyx qyx = Q_sub[1, 0]
nenner = qxx - qyy qxx = Q_sub[0, 0]
t_grund = 0.5 * np.arctan(abs(zaehler) / abs(nenner)) * (200 / np.pi) qyy = Q_sub[1, 1]
t_gon = 0.5 * np.arctan2(2 * qyx, qxx - qyy) * (200 / np.pi)
# Quadrantenabfrage if t_gon < 0:
if nenner > 0 and qyx > 0: t_gon += 200
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([ daten.append([
pid, pid, float(sx), float(sy), float(sz),
float(sx), float(sy), float(sxy), float(A_K), float(B_K), float(C_K), float(t_gon)
float(A_K), float(B_K),
float(t_gon)
]) ])
except: except:
continue continue
konfidenzellipse = pd.DataFrame(daten, columns=["Punkt", "σx [m]", "σy [m]", "σxy [m]", "Große Halbachse [m]", "Kleine Halbachse [m]", "θ [gon]"]) konfidenzellipoid = pd.DataFrame(daten, columns=["Punkt", "σx [m]", "σy [m]", "σz [m]", "Halbachse a_k [m]", "Halbachse b_k [m]", "Halbachse c_k [m]", "θ [gon]"])
konfidenzellipse["Große Halbachse [m]"] = konfidenzellipse["Große Halbachse [m]"].round(4)
konfidenzellipse["Kleine Halbachse [m]"] = konfidenzellipse["Kleine Halbachse [m]"].round(4)
konfidenzellipse["θ [gon]"] = konfidenzellipse["θ [gon]"].round(3)
if ausgabe_erfolgt == False: if ausgabe_erfolgt == False:
display(HTML(konfidenzellipse.to_html(index=False))) display(HTML(konfidenzellipoid.to_html(index=False)))
konfidenzellipse.to_excel(r"Netzqualitaet\Konfidenzellipse.xlsx", index=False) konfidenzellipoid.to_excel(r"Netzqualitaet\Konfidenzellipoid.xlsx", index=False)
return konfidenzellipse, alpha return konfidenzellipoid, alpha
@staticmethod @staticmethod
@@ -326,7 +302,7 @@ class Genauigkeitsmaße:
Die Funktion transformiert zunächst die Kofaktor-Matrix der Unbekannten Qxx Die Funktion transformiert zunächst die Kofaktor-Matrix der Unbekannten Qxx
in ein East-North-Up-System (ENU) bezogen auf den Schwerpunkt der verwendeten in ein East-North-Up-System (ENU) bezogen auf den Schwerpunkt der verwendeten
Punkte (B0, L0). Anschließend wird auf Basis der transformierten Matrix die Punkte (B0, L0). Anschließend wird auf Basis der transformierten Matrix die
Konfidenzellipse über die Funktion "konfidenzellipse" bestimmt. Konfidenzellipse über die Funktion "konfidenzellipsoid" bestimmt.
Zum Schluss werden Spaltennamen an die ENU-Notation angepasst, Werte gerundet, Zum Schluss werden Spaltennamen an die ENU-Notation angepasst, Werte gerundet,
tabellarisch ausgegeben und als Excel-Datei exportiert. tabellarisch ausgegeben und als Excel-Datei exportiert.
@@ -362,8 +338,8 @@ class Genauigkeitsmaße:
print( print(
f"ENU-Referenz (Schwerpunkt): B0={Einheitenumrechnung.Einheitenumrechnung.rad_to_gon_Decimal(B0):.8f} rad, L0={Einheitenumrechnung.Einheitenumrechnung.rad_to_gon_Decimal(L0):.8f} rad") f"ENU-Referenz (Schwerpunkt): B0={Einheitenumrechnung.Einheitenumrechnung.rad_to_gon_Decimal(B0):.8f} rad, L0={Einheitenumrechnung.Einheitenumrechnung.rad_to_gon_Decimal(L0):.8f} rad")
# 2) Konfidenzellipse im ENU-System # 2) Konfidenzellipoid im ENU-System
Konfidenzellipse_ENU, alpha = Genauigkeitsmaße.konfidenzellipse( Konfidenzellipse_ENU, alpha = Genauigkeitsmaße.konfidenzellipsoid(
Qxx_enu, Qxx_enu,
s0apost, s0apost,
liste_unbekannte, liste_unbekannte,
@@ -375,15 +351,17 @@ class Genauigkeitsmaße:
Konfidenzellipse_ENU = Konfidenzellipse_ENU.rename(columns={ Konfidenzellipse_ENU = Konfidenzellipse_ENU.rename(columns={
"σx [m]": "σE [m]", "σx [m]": "σE [m]",
"σy [m]": "σN [m]", "σy [m]": "σN [m]",
"σxy [m]": "σEN [m]", "σz [m]": "σU [m]",
"θ [gon]": "θ_EN [gon]" "θ [gon]": "θ_EN [gon]"
}) })
# 4) Runden und Anzeigen # 4) Runden und Anzeigen
Konfidenzellipse_ENU["σE [m]"] = Konfidenzellipse_ENU["σE [m]"].round(4) Konfidenzellipse_ENU["σE [m]"] = Konfidenzellipse_ENU["σE [m]"].round(6)
Konfidenzellipse_ENU["σN [m]"] = Konfidenzellipse_ENU["σN [m]"].round(4) Konfidenzellipse_ENU["σN [m]"] = Konfidenzellipse_ENU["σN [m]"].round(6)
Konfidenzellipse_ENU["Große Halbachse [m]"] = Konfidenzellipse_ENU["Große Halbachse [m]"].round(4) Konfidenzellipse_ENU["σU [m]"] = Konfidenzellipse_ENU["σU [m]"].round(6)
Konfidenzellipse_ENU["Kleine Halbachse [m]"] = Konfidenzellipse_ENU["Kleine Halbachse [m]"].round(4) Konfidenzellipse_ENU["Halbachse a_k [m]"] = Konfidenzellipse_ENU["Halbachse a_k [m]"].round(4)
Konfidenzellipse_ENU["Halbachse b_k [m]"] = Konfidenzellipse_ENU["Halbachse b_k [m]"].round(4)
Konfidenzellipse_ENU["Halbachse c_k [m]"] = Konfidenzellipse_ENU["Halbachse c_k [m]"].round(4)
Konfidenzellipse_ENU["θ_EN [gon]"] = Konfidenzellipse_ENU["θ_EN [gon]"].round(4) Konfidenzellipse_ENU["θ_EN [gon]"] = Konfidenzellipse_ENU["θ_EN [gon]"].round(4)
display(HTML(Konfidenzellipse_ENU.to_html(index=False))) display(HTML(Konfidenzellipse_ENU.to_html(index=False)))
@@ -416,26 +394,28 @@ class Plot:
unbekannten_labels, unbekannten_labels,
beobachtungs_labels, beobachtungs_labels,
df_konf_ellipsen_enu, df_konf_ellipsen_enu,
skalierung=1000, skalierung,
n_ellipse_pts=60, n_ellipse_pts=60,
titel="Netzplot im ENU-System mit Konfidenzellipsen" titel="Netzplot im ENU-System mit Konfidenzellipsen"
): ):
""" """
Erstellt einen Netzplot im ENU-System inklusive Konfidenzellipsen, Netzpunkten und Beobachtungslinien. Erstellt einen Netzplot im ENU-System inklusive Konfidenzellipsen, Netzpunkten und Beobachtungslinien.
Die Funktion visualisiert das geodätische Netz im East-North-Up-System (ENU) Die Funktion visualisiert das geodätische Netz im East-North-Up-System (ENU) mit Plotly.
mit Plotly. Dabei werden: Dabei werden:
- Beobachtungen als Verbindungslinien zwischen Punkten dargestellt, deren Ansicht aus- und eingeschaltet werden kann, - Beobachtungen als Verbindungslinien zwischen Punkten dargestellt (als separate Traces/Legenden-Einträge),
- Konfidenzellipsen je Punkt (Halbachsen und Richtungswinkel), - Konfidenzellipsen je Punkt (Halbachsen und Richtungswinkel),
- Netzpunkte mit Punkt-ID und Koordinaten im Hover-Text angezeigt. - Netzpunkte mit Punkt-ID und Koordinaten im Hover-Text angezeigt.
Die Ellipsen werden zur besseren Sichtbarkeit mit einem Faktor "skalierung" vergrößert. Dieser kann angepasst werden. Die Ellipsen werden zur besseren Sichtbarkeit mit dem Faktor "skalierung" vergrößert.
Der Richtungswinkel wird in gon erwartet und intern nach Radiant umgerechnet. Der Richtungswinkel wird in gon erwartet und intern nach Radiant umgerechnet.
Zusätzlich wird eine PNG-Grafik über "plot.write_image(...)" exportiert.
:param Koord_ENU: Dictionary der Punktkoordinaten im ENU-System. :param Koord_ENU: Dictionary der Punktkoordinaten im ENU-System.
:type Koord_ENU: dict :type Koord_ENU: dict
:param unbekannten_labels: Liste der Unbekannten zur Ableitung der Punkt-IDs (z.B. X1, Y1, Z1). :param unbekannten_labels: Liste der Unbekannten zur Ableitung der Punkt-IDs.
:type unbekannten_labels: list :type unbekannten_labels: list
:param beobachtungs_labels: Liste der Beobachtungen zur Ableitung von Verbindungslinien. :param beobachtungs_labels: Liste der Beobachtungen zur Ableitung von Verbindungslinien.
:type beobachtungs_labels: list :type beobachtungs_labels: list
@@ -447,12 +427,12 @@ class Plot:
:type n_ellipse_pts: int :type n_ellipse_pts: int
:param titel: Titel des Plots. :param titel: Titel des Plots.
:type titel: str :type titel: str
:return: None :return: Plotly-Figure-Objekt mit Netz, Beobachtungen und Ellipsen.
:rtype: None :rtype: plotly.graph_objects.Figure
:raises ValueError: Wenn weder "θ_EN [gon]" noch "θ [gon]" im DataFrame vorhanden ist. :raises ValueError: Wenn weder "θ_EN [gon]" noch "θ [gon]" im DataFrame vorhanden ist.
""" """
names = [str(s).strip() for s in unbekannten_labels] namen = [str(s).strip() for s in unbekannten_labels]
if "θ_EN [gon]" in df_konf_ellipsen_enu.columns: if "θ_EN [gon]" in df_konf_ellipsen_enu.columns:
theta_col = "θ_EN [gon]" theta_col = "θ_EN [gon]"
@@ -461,9 +441,9 @@ class Plot:
else: else:
raise ValueError("Spalte 'θ_EN [gon]' oder 'θ [gon]' fehlt im DataFrame.") 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")}) punkt_ids = sorted({nm[1:] for nm in namen if nm and nm[0].upper() in ("X", "Y", "Z")})
fig = go.Figure() plot = go.Figure()
# 1) Darstellungen der Beobachtungen # 1) Darstellungen der Beobachtungen
beob_typen = { beob_typen = {
@@ -472,28 +452,28 @@ class Plot:
} }
for typ, info in beob_typen.items(): for typ, info in beob_typen.items():
x_l, y_l = [], [] x_linie, y_linie = [], []
for bl in beobachtungs_labels: for beob in beobachtungs_labels:
bl_str = str(bl).lower() bl_str = str(beob).lower()
is_typ = ((info['pattern'] in bl_str and info['pattern'] != '') or 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)) (info['pattern'] == '' and 'gnss' not in bl_str and 'niv' not in bl_str))
if not is_typ: if not is_typ:
continue continue
bl_raw = str(bl) beob_text = str(beob)
pts = [] punkte = []
for pid in punkt_ids: for pid in punkt_ids:
if (f"_{pid}" in bl_raw) or bl_raw.startswith(f"{pid}_"): if (f"_{pid}" in beob_text) or beob_text.startswith(f"{pid}_"):
if pid in Koord_ENU: if pid in Koord_ENU:
pts.append(pid) punkte.append(pid)
if len(pts) >= 2: if len(punkte) >= 2:
p1, p2 = pts[0], pts[1] punkt1, punkt2 = punkte[0], punkte[1]
x_l.extend([Koord_ENU[p1][0], Koord_ENU[p2][0], None]) # E x_linie.extend([Koord_ENU[punkt1][0], Koord_ENU[punkt2][0], None]) # E
y_l.extend([Koord_ENU[p1][1], Koord_ENU[p2][1], None]) # N y_linie.extend([Koord_ENU[punkt1][1], Koord_ENU[punkt2][1], None]) # N
if x_l: if x_linie:
fig.add_trace(go.Scatter(x=x_l, y=y_l, mode='lines', name=typ, plot.add_trace(go.Scatter(x=x_linie, y=y_linie, mode='lines', name=typ,
line=dict(color=info['color'], width=1))) line=dict(color=info['color'], width=1)))
# 2) Darstellung der Konfidenzellipsen # 2) Darstellung der Konfidenzellipsen
@@ -504,8 +484,8 @@ class Plot:
if pid not in Koord_ENU: if pid not in Koord_ENU:
continue continue
a = float(row["Große Halbachse [m]"]) * skalierung a = float(row["Halbachse a_k [m]"]) * skalierung
b = float(row["Kleine Halbachse [m]"]) * skalierung b = float(row["Halbachse b_k [m]"]) * skalierung
theta = float(row[theta_col]) * np.pi / 200.0 # gon->rad theta = float(row[theta_col]) * np.pi / 200.0 # gon->rad
ex = a * np.cos(t) ex = a * np.cos(t)
@@ -517,7 +497,7 @@ class Plot:
E0, N0, _ = Koord_ENU[pid] E0, N0, _ = Koord_ENU[pid]
fig.add_trace(go.Scatter( plot.add_trace(go.Scatter(
x=E0 + xr, y=N0 + yr, x=E0 + xr, y=N0 + yr,
mode="lines", mode="lines",
line=dict(color="red", width=1.5), line=dict(color="red", width=1.5),
@@ -539,7 +519,7 @@ class Plot:
texts.append(pid) texts.append(pid)
hovers.append(f"Punkt {pid}<br>E={E:.4f} m<br>N={N:.4f} m<br>U={U:.4f} m") 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( plot.add_trace(go.Scatter(
x=xs, y=ys, mode="markers+text", x=xs, y=ys, mode="markers+text",
text=texts, textposition="top center", text=texts, textposition="top center",
marker=dict(size=8, color="black"), marker=dict(size=8, color="black"),
@@ -547,7 +527,7 @@ class Plot:
hovertext=hovers, hoverinfo="text" hovertext=hovers, hoverinfo="text"
)) ))
fig.update_layout( plot.update_layout(
title=f"{titel} (Ellipsen ×{skalierung})", title=f"{titel} (Ellipsen ×{skalierung})",
xaxis=dict(title="E [m]", scaleanchor="y", scaleratio=1, showgrid=True, gridcolor="lightgrey"), xaxis=dict(title="E [m]", scaleanchor="y", scaleratio=1, showgrid=True, gridcolor="lightgrey"),
yaxis=dict(title="N [m]", showgrid=True, gridcolor="lightgrey"), yaxis=dict(title="N [m]", showgrid=True, gridcolor="lightgrey"),
@@ -556,16 +536,35 @@ class Plot:
plot_bgcolor="white" plot_bgcolor="white"
) )
fig.add_annotation( plot.add_annotation(
text=f"<b>Maßstab Ellipsen:</b><br>Dargestellte Größe = Konfidenzellipse × {skalierung}", text=f"<b>Maßstab Ellipsen:</b><br>Dargestellte Größe = Konfidenzellipse × {skalierung}",
align='left', showarrow=False, xref='paper', yref='paper', x=0.02, y=0.05, align='left', showarrow=False, xref='paper', yref='paper', x=0.02, y=0.05,
bgcolor="white", bordercolor="black", borderwidth=1 bgcolor="white", bordercolor="black", borderwidth=1
) )
fig.write_image(r"Netzqualitaet\netzplot_ellipsen_volle_ausdehnung.png") plot.write_image(r"Netzqualitaet\netzplot_ellipsen_volle_ausdehnung.png")
return fig return plot
def plot_speichere_aktuelle_ansicht(plot, dateiname=r"Netzqualitaet\netzplot_ellipsen_zoom_ansicht.png"): def plot_speichere_aktuelle_ansicht(plot, dateiname=r"Netzqualitaet\netzplot_ellipsen_zoom_ansicht.png"):
"""
Speichert die aktuell dargestellte Plot-Ansicht als Bilddatei.
Die Funktion übernimmt ein vorhandenes Plotly-Figure-Objekt und exportiert die momentan im Layout
eingestellte Ansicht (inklusive Zoom- oder Achsenbereich) als PNG-Datei. Dazu wird das aktuelle
Layout übernommen und in ein neues Figure-Objekt kopiert, welches anschließend mit
"write_image" gespeichert wird.
Zusätzlich werden die aktuellen Achsenbereiche (E/N) in der Konsole ausgegeben, um den gespeicherten
Ausschnitt zu dokumentieren.
:param plot: Plotly-Figure-Objekt mit dem darzustellenden Netzplot.
:type plot: plotly.graph_objects.Figure
:param dateiname: Dateipfad für die Ausgabedatei (Standard: PNG im Ordner Netzqualitaet).
:type dateiname: str
:return: None
:rtype: None
:raises OSError: Wenn die Bilddatei nicht geschrieben werden kann (z.B. fehlender Pfad oder fehlendes Kaleido).
"""
aktuelles_layout = plot.layout aktuelles_layout = plot.layout
export_plot = go.Figure(plot.data, layout=aktuelles_layout) export_plot = go.Figure(plot.data, layout=aktuelles_layout)

View File

@@ -296,8 +296,8 @@ class Zuverlaessigkeit:
labels = [str(s) for s in liste_beob] labels = [str(s) for s in liste_beob]
# Benutzereingabe von β # Benutzereingabe von β
beta_input = input("Macht des Tests (1-β) wählen [Standard: 80 % -> 0.80]: ").strip() beta_input = input("β für Macht des Tests (1-β) wählen [Standard: 0.20]: ").strip()
beta = 0.80 if beta_input == "" else float(beta_input) beta = 0.20 if beta_input == "" else float(beta_input)
# Berechnungen für den Lokaltest # Berechnungen für den Lokaltest
Lokaltest = Zuverlaessigkeit.lokaltest_innere_Zuverlaessigkeit( Lokaltest = Zuverlaessigkeit.lokaltest_innere_Zuverlaessigkeit(