Files
Masterprojekt/dashboard.py
2026-02-11 12:08:46 +01:00

1485 lines
49 KiB
Python

import builtins
import traceback
import dash_bootstrap_components as dbc
import numpy as np
import plotly.graph_objects as go
from dash import Dash, Input, Output, State, dcc, html, no_update
from dash.exceptions import PreventUpdate
from numpy import pi
import ausgaben as aus
import winkelumrechnungen as wu
from ES.gha1_ES import gha1_ES
from ES.gha2_ES import gha2_ES
from GHA_triaxial.gha1_ana import gha1_ana
from GHA_triaxial.gha1_approx import gha1_approx
from GHA_triaxial.gha1_num import gha1_num
from GHA_triaxial.gha2_approx import gha2_approx
from GHA_triaxial.gha2_num import gha2_num
from GHA_triaxial.utils import alpha_ell2para, alpha_para2ell
from ellipsoid_triaxial import EllipsoidTriaxial
# Prints von importierten Funktionen unterdücken
def _no_print(*args, **kwargs):
pass
builtins.print = _no_print
# Bootstrap (CSS) einbindung
app = Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP])
# App-Name im Tab
app.title = "Geodätische Hauptaufgaben"
# Erzeugen der Eingabefelder
def inputfeld(left_text, input_id, right_text="", width=200, mini=None, maxi=None):
return html.Div(
children=[
html.Span(f"{left_text} =", style={"minWidth": 36, "textAlign": "right", "marginRight": 5}),
dcc.Input(
id=input_id,
type="number",
placeholder=f"{left_text}...[{right_text}]",
style={"width": width, "display": "block"},
persistence=True,
persistence_type="memory",
),
html.Span(right_text, style={"marginLeft": 5}) if right_text else html.Span()
],
style={"display": "flex", "alignItems": "center", "marginBottom": "10px"},
)
# Positionierung der Input-Felder
def input_grid(*children, cols=2):
return html.Div(
list(children),
style={
"display": "grid",
"gridTemplateColumns": f"repeat({cols}, minmax(0, 1fr))",
"columnGap": "1.2vw",
"rowGap": "0.6vh",
},
)
# Erzeugen der Checklisten inkl. Eingabefelder
def method_row(label, cb_id, input_id=None, value="", info="", input_id2=None, value2="", info2="",):
base_row_style = {
"display": "flex",
"alignItems": "center",
"gap": "4px",
"marginLeft": "10px",
"marginBottom": "6px",
}
checkbox = dcc.Checklist(
id=cb_id,
options=[{"label": "", "value": "on"}],
value=[],
style={"margin": "0"},
persistence=True,
persistence_type="memory",
)
label_span = html.Span(
label,
style={"marginLeft": "4px", "minWidth": "130px"},
)
if not info and not info2 and not input_id and not input_id2:
return html.Div([checkbox, label_span], style=base_row_style)
def _input_box(_id, _value, width_px):
return dcc.Input(
id=_id,
type="number",
value=_value,
style={"width": f"{width_px}px", "marginLeft": "2px"},
disabled=True,
persistence=True,
persistence_type="memory",
)
use_two_inputs = bool(input_id2)
if use_two_inputs:
w = 45
input_box_1 = _input_box(input_id, value, w)
input_box_2 = _input_box(input_id2, value2, w)
else:
w = 96
input_box_1 = _input_box(input_id, value, w)
input_box_2 = None
info_parts = [s for s in (info, info2) if s]
info_text = html.Span(
" | ".join(info_parts),
style={
"marginLeft": "6px",
"fontSize": "11px",
"color": "#6c757d",
"lineHeight": "1.1",
"whiteSpace": "nowrap",
},
) if info_parts else None
children = [checkbox, label_span, input_box_1]
if input_box_2 is not None:
children.append(input_box_2)
if info_text is not None:
children.append(info_text)
return html.Div(children, style=base_row_style)
def method_failed(method_label: str, exc: Exception):
return html.Div([
html.Strong(f"{method_label}: "),
html.Span("konnte nicht berechnet werden. ", style={"color": "red"}),
# html.Span(f"({type(exc).__name__}: {exc})", style={"color": "#b02a37"}),
html.Details([
html.Summary("Details"),
html.Pre(traceback.format_exc(), style={
"whiteSpace": "pre-wrap",
"fontSize": "11px",
"color": "#6c757d",
"marginTop": "6px"
})
], style={"marginTop": "6px"})
])
def axes_valid(ax, ay, b) -> bool:
if ax is None or ay is None or b is None:
return False
try:
ax = float(ax); ay = float(ay); b = float(b)
except (TypeError, ValueError):
return False
if ax <= 0 or ay <= 0 or b <= 0:
return False
return ax >= ay >= b
def ellipsoid_figure(ell: EllipsoidTriaxial, title="Dreiachsiges Ellipsoid"):
fig = go.Figure()
# Darstellung
rx, ry, rz = 1.01*ell.ax, 1.01*ell.ay, 1.01*ell.b
fig.update_layout(
title=title,
scene=dict(
xaxis=dict(
range=[-rx, rx],
# title="X [m]",
title="",
showgrid=False,
zeroline=False,
showbackground=False,
showticklabels=False
),
yaxis=dict(
range=[-ry, ry],
# title="Y [m]",
title="",
showgrid=False,
zeroline=False,
showbackground=False,
showticklabels=False
),
zaxis=dict(
range=[-rz, rz],
# title="Z [m]",
title="",
showgrid=False,
zeroline=False,
showbackground=False,
showticklabels=False
),
aspectmode="data",
),
margin=dict(l=0, r=0, t=0, b=0),
scene_camera=dict(eye=dict(x=1.05, y=1.05, z=0.85)),
)
# Ellipsoid
u = np.linspace(-pi/2, pi/2, 80)
v = np.linspace(-pi, pi, 160)
U, V = np.meshgrid(u, v)
X, Y, Z = ell.para2cart(U, V)
fig.add_trace(go.Surface(
x=X, y=Y, z=Z, showscale=False, opacity=0.7,
surfacecolor=np.zeros_like(X),
colorscale=[[0, "rgb(200,220,255)"], [1, "rgb(200,220,255)"]],
name="Ellipsoid"
))
return fig
def figure_constant_lines(fig, ell: EllipsoidTriaxial, coordsystem: str = "para"):
if coordsystem == "para":
constants_u = wu.deg2rad(np.arange(-90, 91, 15))
all_v = wu.deg2rad(np.arange(-180, 180, 1))
for u in constants_u:
xm, ym, zm = ell.para2cart(u, all_v)
fig.add_trace(go.Scatter3d(
x=xm, y=ym, z=zm, mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
all_u = wu.deg2rad(np.arange(-90, 91, 1))
constants_v = wu.deg2rad(np.arange(-180, 180, 15))
for v in constants_v:
x, y, z = ell.para2cart(all_u, v)
fig.add_trace(go.Scatter3d(
x=x, y=y, z=z, mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
elif coordsystem == "ell":
constants_beta = wu.deg2rad(np.arange(-90, 91, 15))
all_lamb = wu.deg2rad(np.arange(-180, 180, 1))
for beta in constants_beta:
xyz = ell.ell2cart(beta, all_lamb)
fig.add_trace(go.Scatter3d(
x=xyz[:, 0], y=xyz[:, 1], z=xyz[:, 2], mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
all_beta = wu.deg2rad(np.arange(-90, 91, 1))
all_beta[0] += 1e-8
all_beta[-1] -= 1e-8
constants_lamb = wu.deg2rad(np.arange(-180, 180, 15))
for lamb in constants_lamb:
if lamb != 0 and abs(lamb) != pi:
xyz = ell.ell2cart(all_beta, lamb)
fig.add_trace(go.Scatter3d(
x=xyz[:, 0], y=xyz[:, 1], z=xyz[:, 2], mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
else:
x, y, z = ell.para2cart(wu.deg2rad(np.arange(-90, 91, 1)), lamb)
fig.add_trace(go.Scatter3d(
x=x, y=y, z=z, mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
elif coordsystem == "geod":
constants_phi = wu.deg2rad(np.arange(-90, 91, 15))
all_lamb = wu.deg2rad(np.arange(-180, 180, 1))
for phi in constants_phi:
x, y, z = ell.geod2cart(phi, all_lamb, 0)
fig.add_trace(go.Scatter3d(
x=x, y=y, z=z, mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
all_phi = wu.deg2rad(np.arange(-90, 91, 1))
constants_lamb = wu.deg2rad(np.arange(-180, 180, 15))
for lamb in constants_lamb:
x, y, z = ell.geod2cart(all_phi, lamb, 0)
fig.add_trace(go.Scatter3d(
x=x, y=y, z=z, mode="lines",
line=dict(width=1, color="black"),
showlegend=False
))
return fig
def figure_points(fig, points):
"""
:param fig: plotly.graph_objects.Figure
:param points: Punktliste [(name, (x,y,z), color)]
:return: plotly.graph_objects.Figure
"""
for name, (px, py, pz), color in points:
fig.add_trace(go.Scatter3d(
x=[px], y=[py], z=[pz],
mode="markers+text",
marker=dict(size=6, color=color),
text=[name], textposition="top center",
name=name, showlegend=False
))
return fig
def figure_lines(fig, line, name, color):
"""
:param fig: plotly.graph_objects.Figure
:param line: Punktliste [[x1,y1,z1], [x2,y2,z2]]
:param name: Name
:param color: Farbe
:return: plotly.graph_objects.Figure
"""
points = np.array(line, dtype=float)
fig.add_trace(go.Scatter3d(
x=points[:, 0], y=points[:, 1], z=points[:, 2],
mode="lines",
line=dict(width=4, color=color),
name=name, showlegend=False
))
return fig
# HTML der beiden Tabs
# Tab 1
pane_gha1 = html.Div(
[
html.Div(
[
html.Div(
[
inputfeld("β₀", "input-GHA1-beta0", "°"),
inputfeld("λ₀", "input-GHA1-lamb0", "°"),
],
style={"flex": "1"},
),
html.Div(
[
inputfeld("s", "input-GHA1-s", "m"),
inputfeld("α₀", "input-GHA1-a", "°"),
],
style={"flex": "1"},
),
],
style={
"display": "flex",
"gap": "1%",
"alignItems": "flex-start",
"flexWrap": "wrap",
},
),
method_row("Analytisch", "cb-ana-1", "input-ana-1", "70", info="Ordnung", input_id2="input-ana-1-2", value2="4", info2="max. Unterteilung"),
method_row("Numerisch", "cb-num-1", "input-num-n-1", "2000", info="Anzahl Schritte"),
method_row("Stochastisch (ES)", "cb-stoch-1", "input-stoch-n-1", "1000", info="Länge Streckensegment [m]"),
method_row("Approximiert", "cb-approx-1", "input-approx-ds-1", "1000", info="Länge Streckensegment [m]"),
html.Div(
[
html.Button(
"Berechnen",
id="button-calc-gha1",
n_clicks=0,
className="btn btn-secondary btn-lg shadow",
style={"marginRight": "10px", "marginLeft": "10px", "marginTop": "30px"},
),
],
style={"marginBottom": "20px"},
),
html.Div(id="tabs-GHA1-out", style={"marginBottom": "10px"}),
html.Div(id="gha1-header"),
dcc.Loading(html.Div(id="output-gha1-ana")),
dcc.Loading(html.Div(id="output-gha1-num")),
dcc.Loading(html.Div(id="output-gha1-stoch")),
dcc.Loading(html.Div(id="output-gha1-approx")),
dcc.Store(id="store-gha1-ana"),
dcc.Store(id="store-gha1-num"),
dcc.Store(id="store-gha1-stoch"),
dcc.Store(id="store-gha1-approx"),
],
id="pane-gha1",
style={"display": "block"},
)
# Tab2
pane_gha2 = html.Div(
[
html.Div(
[
html.Div(
[
inputfeld("β₀", "input-GHA2-beta0", "°"),
inputfeld("λ₀", "input-GHA2-lamb0", "°"),
],
style={"flex": "1"},
),
html.Div(
[
inputfeld("β₁", "input-GHA2-beta1", "°"),
inputfeld("λ₁", "input-GHA2-lamb1", "°"),
],
style={"flex": "1"},
),
],
style={
"display": "flex",
"gap": "1%",
"alignItems": "flex-start",
"flexWrap": "wrap",
},
),
method_row("Numerisch", "cb-num-2", "input-num-n-2", "2000", info="Anzahl Schritte"),
method_row("Stochastisch", "cb-stoch-2", "input-stoch-n-2", "1000", info="Länge Streckensegment [m]"),
method_row("Approximiert", "cb-approx-2", "input-approx-ds-2", "1000", info="Länge Streckensegment [m]"),
html.Div(
[
html.Button(
"Berechnen",
id="button-calc-gha2",
n_clicks=0,
className="btn btn-secondary btn-lg shadow",
style={"marginRight": "10px", "marginLeft": "10px", "marginTop": "30px"},
),
],
style={"marginBottom": "20px"},
),
html.Div(id="tabs-GHA2-out", style={"marginBottom": "10px"}),
html.Div(id="gha2-header"),
dcc.Loading(html.Div(id="output-gha2-num")),
dcc.Loading(html.Div(id="output-gha2-stoch")),
dcc.Loading(html.Div(id="output-gha2-approx")),
dcc.Store(id="store-gha2-num"),
dcc.Store(id="store-gha2-stoch"),
dcc.Store(id="store-gha2-approx"),
],
id="pane-gha2",
style={"display": "none"},
)
# HTML-Gerüst
app.layout = html.Div(
style={"fontFamily": "Arial", "padding": "10px", "width": "95%", "margin": "0 auto"},
children=[
html.H2("Geodätische Hauptaufgaben für dreiachsige Ellipsoide"),
# html.H2("für dreiachsige Ellipsoide"),
html.Div(
style={
"display": "flex",
"alignItems": "flex-start",
"gap": "24px",
"flexWrap": "nowrap"
},
children=[
# Linker Bereich
html.Div(
style={"flex": "1 1 420px", "maxWidth": "640px"},
children=[
html.Label("Ellipsoid wählen:"),
dcc.Dropdown(
id="dropdown-ellipsoid",
options=[
{"label": "BursaFialova1993", "value": "BursaFialova1993"},
{"label": "BursaSima1980", "value": "BursaSima1980"},
{"label": "BursaSima1980round", "value": "BursaSima1980round"},
{"label": "Eitschberger1978", "value": "Eitschberger1978"},
{"label": "Bursa1972", "value": "Bursa1972"},
{"label": "Bursa1970", "value": "Bursa1970"},
{"label": "BesselBiaxial", "value": "BesselBiaxial"},
{"label": "KarneyTest2024", "value": "KarneyTest2024"},
{"label": "Fiction", "value": "Fiction"},
],
value="",
style={"width": "300px", "marginBottom": "16px"},
),
html.P("Halbachsen:", style={"marginBottom": "0.8vh"}),
html.Div(
[
inputfeld("aₓ", "input-ax", "m", mini=0, width="clamp(80px, 7vw, 200px)"),
inputfeld("aᵧ", "input-ay", "m", mini=0, width="clamp(80px, 7vw, 200px)"),
inputfeld("b", "input-b", "m", mini=0, width="clamp(80px, 7vw, 200px)"),
],
style={
"display": "grid",
"gridTemplateColumns": "repeat(auto-fit, minmax(200px, 1fr))",
"columnGap": "0.2vw",
"rowGap": "0.6vh",
"alignItems": "center",
},
),
# html.Br(),
dcc.Tabs(
id="tabs-GHA",
value="tab-GHA1",
style={"margin": "20px 0 15px", "width": "100%"},
children=[
dcc.Tab(label="Erste Hauptaufgabe", value="tab-GHA1"),
dcc.Tab(label="Zweite Hauptaufgabe", value="tab-GHA2"),
],
),
html.Div([pane_gha1, pane_gha2], id="tabs-GHA-out", style={"marginBottom": "10px"}),
],
),
# Rechter Bereich
html.Div(
style={
"flex": "1 1 750px",
"minWidth": "520px",
"position": "sticky",
"top": "0",
"marginTop": "0px",
},
children=[
html.Label("Koordinatenart wählen:", style={"marginLeft": "5%"},),
dcc.Dropdown(
id="dropdown-coors-type",
options=[
{"label": "Ellipsoidisch", "value": "ell"},
{"label": "Parametrisch", "value": "para"},
{"label": "Geodätisch", "value": "geod"},
],
value="ell",
clearable=False,
style={"width": "200px", "marginLeft": "5%"},
),
dcc.Graph(
id="ellipsoid-plot",
style={"height": "80vh", "width": "100%"},
config={"responsive": True}
)
],
),
],
),
dcc.Store(id="calc-token-gha1", data=0),
dcc.Store(id="calc-token-gha2", data=0),
# html.P("© 2026", style={"fontSize": "10px", "color": "gray", "textAlign": "center", "marginTop": "16px"}),
],
)
# Funktion zur Wahl der Halbachsen
@app.callback(
Output("input-ax", "value"),
Output("input-ay", "value"),
Output("input-b", "value"),
Input("dropdown-ellipsoid", "value"),
)
def fill_inputs_from_dropdown(selected_ell):
if not selected_ell:
return no_update, no_update, no_update
ell = EllipsoidTriaxial.init_name(selected_ell)
return ell.ax, ell.ay, ell.b
# Funktion zur Generierung der Tab-Inhalte
@app.callback(
Output("pane-gha1", "style"),
Output("pane-gha2", "style"),
Input("tabs-GHA", "value"),
)
def switch_tabs(tab):
show1 = {"display": "block"} if tab == "tab-GHA1" else {"display": "none"}
show2 = {"display": "block"} if tab == "tab-GHA2" else {"display": "none"}
return show1, show2
# Funktionen zum Aktivieren der Eingabefelder innerhalb der Checklisten
@app.callback(
Output("input-ana-1", "disabled"),
Input("cb-ana-1", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-ana-1-2", "disabled"),
Input("cb-ana-1", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-num-n-1", "disabled"),
Input("cb-num-1", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-stoch-n-1", "disabled"),
Input("cb-stoch-1", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-approx-ds-1", "disabled"),
Input("cb-approx-1", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-num-n-2", "disabled"),
Input("cb-num-2", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-stoch-n-2", "disabled"),
Input("cb-stoch-2", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
@app.callback(
Output("input-approx-ds-2", "disabled"),
Input("cb-approx-2", "value"),
)
def toggle_ds(v):
return "on" not in (v or [])
# Abfrage ob Berechnungsverfahren gewählt
@app.callback(
Output("calc-token-gha1", "data"),
Output("tabs-GHA1-out", "children"),
Output("gha1-header", "children", allow_duplicate=True),
Output("output-gha1-ana", "children", allow_duplicate=True),
Output("output-gha1-num", "children", allow_duplicate=True),
Output("output-gha1-stoch", "children", allow_duplicate=True),
Output("output-gha1-approx", "children", allow_duplicate=True),
Output("store-gha1-ana", "data", allow_duplicate=True),
Output("store-gha1-num", "data", allow_duplicate=True),
Output("store-gha1-stoch", "data", allow_duplicate=True),
Output("store-gha1-approx", "data", allow_duplicate=True),
Input("button-calc-gha1", "n_clicks"),
State("calc-token-gha1", "data"),
State("cb-ana-1", "value"),
State("cb-num-1", "value"),
State("cb-stoch-1", "value"),
State("cb-approx-1", "value"),
State("input-GHA1-beta0", "value"),
State("input-GHA1-lamb0", "value"),
State("input-GHA1-s", "value"),
State("input-GHA1-a", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def request_calc_gha1(n_clicks, token,
cb_ana, cb_num, cb_stoch, cb_approx,
beta0, lamb0, s, a0,
ax, ay, b):
if not n_clicks:
raise PreventUpdate
if ax is None or ay is None or b is None:
msg = html.Span("Bitte Ellipsoid wählen bzw. Halbachsen eingeben.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
try:
ax_f, ay_f, b_f = float(ax), float(ay), float(b)
except (TypeError, ValueError):
msg = html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
if ax_f <= 0 or ay_f <= 0 or b_f <= 0:
msg = html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
if not (ax_f >= ay_f >= b_f):
msg = html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
# --- Validierung Verfahren ---
any_on = any("on" in (v or []) for v in (cb_ana, cb_num, cb_stoch, cb_approx))
if not any_on:
msg = html.Span("Bitte Berechnungsverfahren wählen.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
missing = []
if beta0 is None: missing.append("β₀")
if lamb0 is None: missing.append("λ₀")
if s is None: missing.append("s")
if a0 is None: missing.append("α₀")
if missing:
msg = html.Span("Bitte " + ", ".join(missing) + " eingeben.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
try:
beta0_f = float(beta0)
lamb0_f = float(lamb0)
s_f = float(s)
a0_f = float(a0)
except (TypeError, ValueError):
msg = html.Span("Bitte gültige Zahlen für β₀, λ₀, s und α₀ eingeben.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
if not (-90 <= beta0_f <= 90):
msg = html.Span("β₀ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
if not (-180 <= lamb0_f <= 180):
msg = html.Span("λ₀ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
if s_f <= 0:
msg = html.Span("s muss > 0 sein.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
if not (-180 <= a0_f <= 360):
msg = html.Span("α₀ muss im Bereich [-180°, 360°] liegen.", style={"color": "red"})
return (no_update, msg, no_update, no_update, no_update, no_update, no_update,
no_update, no_update, no_update, no_update)
new_token = (token or 0) + 1
return (
new_token,
"",
"",
"", "", "", "",
None, None, None, None
)
@app.callback(
Output("calc-token-gha2", "data"),
Output("tabs-GHA2-out", "children"),
Output("gha2-header", "children", allow_duplicate=True),
Output("output-gha2-num", "children", allow_duplicate=True),
Output("output-gha2-stoch", "children", allow_duplicate=True),
Output("output-gha2-approx", "children", allow_duplicate=True),
Output("store-gha2-num", "data", allow_duplicate=True),
Output("store-gha2-stoch", "data", allow_duplicate=True),
Output("store-gha2-approx", "data", allow_duplicate=True),
Input("button-calc-gha2", "n_clicks"),
State("calc-token-gha2", "data"),
State("cb-num-2", "value"),
State("cb-stoch-2", "value"),
State("cb-approx-2", "value"),
State("input-GHA2-beta0", "value"),
State("input-GHA2-lamb0", "value"),
State("input-GHA2-beta1", "value"),
State("input-GHA2-lamb1", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def request_calc_gha2(n_clicks, token,
cb_num, cb_stoch, cb_approx,
beta0, lamb0, beta1, lamb1,
ax, ay, b):
if not n_clicks:
raise PreventUpdate
if ax is None or ay is None or b is None:
msg = html.Span("Bitte Ellipsoid wählen bzw. Halbachsen eingeben.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
try:
ax_f, ay_f, b_f = float(ax), float(ay), float(b)
except (TypeError, ValueError):
msg = html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
if ax_f <= 0 or ay_f <= 0 or b_f <= 0:
msg = html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
if not (ax_f >= ay_f >= b_f):
msg = html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
any_on = any("on" in (v or []) for v in (cb_num, cb_stoch, cb_approx))
if not any_on:
msg = html.Span("Bitte Berechnungsverfahren wählen.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
missing = []
if beta0 is None: missing.append("β₀")
if lamb0 is None: missing.append("λ₀")
if beta1 is None: missing.append("β₁")
if lamb1 is None: missing.append("λ₁")
if missing:
msg = html.Span("Bitte " + ", ".join(missing) + " eingeben.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
try:
beta0_f = float(beta0); lamb0_f = float(lamb0)
beta1_f = float(beta1); lamb1_f = float(lamb1)
except (TypeError, ValueError):
msg = html.Span("Bitte gültige Zahlen für β₀, λ₀, β₁ und λ₁ eingeben.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
if not (-90 <= beta0_f <= 90):
msg = html.Span("β₀ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
if not (-180 <= lamb0_f <= 180):
msg = html.Span("λ₀ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
if not (-90 <= beta1_f <= 90):
msg = html.Span("β₁ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
if not (-180 <= lamb1_f <= 180):
msg = html.Span("λ₁ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"})
return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update
new_token = (token or 0) + 1
return (
new_token,
"",
"",
"", "", "",
None, None, None
)
# -- GHA 1 ---
@app.callback(
Output("output-gha1-ana", "children"),
Output("store-gha1-ana", "data"),
Input("calc-token-gha1", "data"),
State("cb-ana-1", "value"),
State("input-ana-1", "value"),
State("input-ana-1-2", "value"),
State("input-GHA1-beta0", "value"),
State("input-GHA1-lamb0", "value"),
State("input-GHA1-s", "value"),
State("input-GHA1-a", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha1_ana(n1, cb_ana, max_M, maxPartCircum, beta0, lamb0, s, a0, ax, ay, b):
if not n1:
return no_update, no_update
if "on" not in (cb_ana or []):
return "", None
max_M = int(max_M) if max_M else 70
maxPartCircum = int(maxPartCircum) if maxPartCircum else 4
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta_rad = wu.deg2rad(float(beta0))
lamb_rad = wu.deg2rad(float(lamb0))
alpha_rad = wu.deg2rad(float(a0))
_, _, alpha_rad_para = alpha_ell2para(ell, beta_rad, lamb_rad, alpha_rad)
s_val = float(s)
P0 = ell.ell2cart(beta_rad, lamb_rad)
P1_ana, alpha2_para = gha1_ana(ell, P0, alpha_rad_para, s_val, max_M, maxPartCircum)
u1, v1 = ell.cart2para(P1_ana)
alpha1 = alpha_para2ell(ell, u1, v1, alpha2_para)
beta2_ana, lamb2_ana = ell.cart2ell(P1_ana)
out = html.Div([
html.Strong("Analytisch: "),
html.Br(),
html.Span(f"kartesisch: x₁={P1_ana[0]:.4f} m, y₁={P1_ana[1]:.4f} m, z₁={P1_ana[2]:.4f} m"),
html.Br(),
html.Span(f"ellipsoidisch: {aus.gms('β₁', beta2_ana, 4)}, {aus.gms('λ₁', lamb2_ana, 4)}"),
html.Br(),
html.Span(f"{aus.gms('α₁', alpha1[-1], 4)}"),
])
store = {
"points": [("P0", P0, "black"), ("P1 (ana)", P1_ana, "red")],
"polyline": None,
"name": "Analytisch",
"color": "red"
}
return out, store
except Exception as e:
return method_failed("Analytisch", e), None
@app.callback(
Output("output-gha1-num", "children"),
Output("store-gha1-num", "data"),
Input("calc-token-gha1", "data"),
State("cb-num-1", "value"),
State("input-num-n-1", "value"),
State("input-GHA1-beta0", "value"),
State("input-GHA1-lamb0", "value"),
State("input-GHA1-s", "value"),
State("input-GHA1-a", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha1_num(n1, cb_num, n_in, beta0, lamb0, s, a0, ax, ay, b):
if not n1:
return no_update, no_update
if "on" not in (cb_num or []):
return "", None
n_in = int(n_in) if n_in else 2000
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta_rad = wu.deg2rad(float(beta0))
lamb_rad = wu.deg2rad(float(lamb0))
alpha_rad = wu.deg2rad(float(a0))
s_val = float(s)
P0 = ell.ell2cart(beta_rad, lamb_rad)
P1_num, alpha1, werte = gha1_num(ell, P0, alpha_rad, s_val, n_in, all_points=True)
beta2_num, lamb2_num = ell.cart2ell(P1_num)
out = html.Div([
html.Strong("Numerisch: "),
html.Br(),
html.Span(f"kartesisch: x₁={P1_num[0]:.4f} m, y₁={P1_num[1]:.4f} m, z₁={P1_num[2]:.4f} m"),
html.Br(),
html.Span(f"ellipsoidisch: {aus.gms('β₁', beta2_num, 4)}, {aus.gms('λ₁', lamb2_num, 4)}"),
html.Br(),
html.Span(f"{aus.gms('α₁', alpha1, 4)}"),
])
polyline = [[x1, y1, z1] for x1, _, y1, _, z1, _ in werte]
store = {
"points": [("P0", P0, "black"), ("P1 (num)", P1_num, "#ff8c00")],
"polyline": polyline,
"name": "Numerisch",
"color": "#ff8c00"
}
return out, store
except Exception as e:
return method_failed("Numerisch", e), None
@app.callback(
Output("output-gha1-stoch", "children"),
Output("store-gha1-stoch", "data"),
Input("calc-token-gha1", "data"),
State("cb-stoch-1", "value"),
State("input-stoch-n-1", "value"),
State("input-GHA1-beta0", "value"),
State("input-GHA1-lamb0", "value"),
State("input-GHA1-s", "value"),
State("input-GHA1-a", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha1_stoch(n1, cb_stoch, n_in, beta0, lamb0, s, a0, ax, ay, b):
if not n1:
return no_update, no_update
if "on" not in (cb_stoch or []):
return "", None
n_in = float(n_in) if n_in else 1000
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta_rad = wu.deg2rad(float(beta0))
lamb_rad = wu.deg2rad(float(lamb0))
alpha_rad = wu.deg2rad(float(a0))
s_val = float(s)
P1_stoch, alpha, points = gha1_ES(ell, beta0=beta_rad, omega0=lamb_rad, alpha0=alpha_rad, s_total=s_val, maxSegLen=n_in, all_points=True)
P0 = ell.ell2cart(beta_rad, lamb_rad)
beta1_stoch, lamb1_stoch = ell.cart2ell(P1_stoch)
out = html.Div([
html.Strong("Stochastisch: "),
html.Br(),
html.Span(f"kartesisch: x₁={P1_stoch[0]:.4f} m, y₁={P1_stoch[1]:.4f} m, z₁={P1_stoch[2]:.4f} m"),
html.Br(),
html.Span(f"ellipsoidisch: {aus.gms('β₁', beta1_stoch, 4)}, {aus.gms('λ₁', lamb1_stoch, 4)}"),
html.Br(),
html.Span(f"{aus.gms('α₁', alpha, 4)}"),
])
store = {
"points": [("P0", P0, "black"), ("P1 (ES)", P1_stoch, "#1fa342")],
"polyline": points,
"name": "Stochastisch (ES)",
"color": "#1fa342"
}
return out, store
except Exception as e:
return method_failed("Stochastisch (ES)", e), None
@app.callback(
Output("output-gha1-approx", "children"),
Output("store-gha1-approx", "data"),
Input("calc-token-gha1", "data"),
State("cb-approx-1", "value"),
State("input-approx-ds-1", "value"),
State("input-GHA1-beta0", "value"),
State("input-GHA1-lamb0", "value"),
State("input-GHA1-s", "value"),
State("input-GHA1-a", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha1_approx(n1, cb_approx, ds_in, beta0, lamb0, s, a0, ax, ay, b):
if not n1:
return no_update, no_update
if not n1 or "on" not in (cb_approx or []):
return "", None
ds_in = float(ds_in) if ds_in else 1000
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta_rad = wu.deg2rad(float(beta0))
lamb_rad = wu.deg2rad(float(lamb0))
alpha_rad = wu.deg2rad(float(a0))
s_val = float(s)
P0 = ell.ell2cart(beta_rad, lamb_rad)
P1_app, alpha1_app, points, alphas = gha1_approx(ell, P0, alpha_rad, s_val, ds=ds_in, all_points=True)
beta1_app, lamb1_app = ell.cart2ell(P1_app)
out = html.Div([
html.Strong("Approximiert: "),
html.Br(),
html.Span(f"kartesisch: x₁={P1_app[0]:.4f} m, y₁={P1_app[1]:.4f} m, z₁={P1_app[2]:.4f} m"),
html.Br(),
html.Span(f"ellipsoidisch: {aus.gms('β₁', beta1_app, 4)}, {aus.gms('λ₁', lamb1_app, 4)}"),
html.Br(),
html.Span(f"{aus.gms('α₁', alpha1_app, 4)}"),
])
store = {
"points": [("P0", P0, "black"), ("P1 (approx)", P1_app, "#00c2fc")],
"polyline": points,
"name": "Approximiert",
"color": "#00c2fc"
}
return out, store
except Exception as e:
return method_failed("Approximiert", e), None
# --- GHA 2 ---
@app.callback(
Output("output-gha2-num", "children"),
Output("store-gha2-num", "data"),
Input("calc-token-gha2", "data"),
State("cb-num-2", "value"),
State("input-num-n-2", "value"),
State("input-GHA2-beta0", "value"),
State("input-GHA2-lamb0", "value"),
State("input-GHA2-beta1", "value"),
State("input-GHA2-lamb1", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha2_num(n2, cb_num, n_in, beta0, lamb0, beta1, lamb1, ax, ay, b):
if not n2:
return no_update, no_update
if "on" not in (cb_num or []):
return "", None
n_in = int(n_in) if n_in else 2000
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta0_rad = wu.deg2rad(float(beta0))
lamb0_rad = wu.deg2rad(float(lamb0))
beta1_rad = wu.deg2rad(float(beta1))
lamb1_rad = wu.deg2rad(float(lamb1))
P0 = ell.ell2cart(beta0_rad, lamb0_rad)
P1 = ell.ell2cart(beta1_rad, lamb1_rad)
a0_num, a1_num, s_num, beta_arr, lamb_arr = gha2_num(ell, beta0_rad, lamb0_rad, beta1_rad, lamb1_rad, all_points=True, n=n_in)
polyline = []
for b_rad, l_rad in zip(beta_arr, lamb_arr):
x, y, z = ell.ell2cart(b_rad, l_rad)
polyline.append([float(x), float(y), float(z)])
out = html.Div([
html.Strong("Numerisch: "),
html.Br(),
html.Span(f"{aus.gms('α₀', a0_num, 4)}, {aus.gms('α₁', a1_num, 4)}, s = {s_num:.4f} m"),
])
store = {
"points": [("P0", P0, "black"), ("P1", P1, "black")],
"polyline": polyline,
"name": "Numerisch",
"color": "#ff8c00",
}
return out, store
except Exception as e:
return method_failed("Numerisch", e), None
@app.callback(
Output("output-gha2-stoch", "children"),
Output("store-gha2-stoch", "data"),
Input("calc-token-gha2", "data"),
State("cb-stoch-2", "value"),
State("input-stoch-n-2", "value"),
State("input-GHA2-beta0", "value"),
State("input-GHA2-lamb0", "value"),
State("input-GHA2-beta1", "value"),
State("input-GHA2-lamb1", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha2_stoch(n2, cb_stoch, n_in, beta0, lamb0, beta1, lamb1, ax, ay, b):
if not n2:
return no_update, no_update
if "on" not in (cb_stoch or []):
return "", None
n_in = float(n_in) if n_in else 1000
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta0_rad = wu.deg2rad(float(beta0))
lamb0_rad = wu.deg2rad(float(lamb0))
beta1_rad = wu.deg2rad(float(beta1))
lamb1_rad = wu.deg2rad(float(lamb1))
P0 = ell.ell2cart(beta0_rad, lamb0_rad)
P1 = ell.ell2cart(beta1_rad, lamb1_rad)
a0_stoch, a1_stoch, s_stoch, points = gha2_ES(ell, P0, P1, maxSegLen=n_in, all_points=True)
out = html.Div([
html.Strong("Stochastisch (ES): "),
html.Br(),
html.Span(f"{aus.gms('α₀', a0_stoch, 4)}, {aus.gms('α₁', a1_stoch, 4)}, s = {s_stoch:.4f} m"),
])
store = {
"points": [("P0", P0, "black"), ("P1", P1, "black")],
"polyline": points,
"name": "Stochastisch (ES)",
"color": "#1fa342",
}
return out, store
except Exception as e:
return method_failed("Stochastisch (ES)", e), None
@app.callback(
Output("output-gha2-approx", "children"),
Output("store-gha2-approx", "data"),
Input("calc-token-gha2", "data"),
State("cb-approx-2", "value"),
State("input-approx-ds-2", "value"),
State("input-GHA2-beta0", "value"),
State("input-GHA2-lamb0", "value"),
State("input-GHA2-beta1", "value"),
State("input-GHA2-lamb1", "value"),
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def compute_gha2_approx(n2, cb_approx, ds_in, beta0, lamb0, beta1, lamb1, ax, ay, b):
if not n2:
return no_update, no_update
if "on" not in (cb_approx or []):
return "", None
ds_in = float(ds_in) if ds_in else 1000
try:
ell = EllipsoidTriaxial(ax, ay, b)
beta0_rad = wu.deg2rad(float(beta0))
lamb0_rad = wu.deg2rad(float(lamb0))
beta1_rad = wu.deg2rad(float(beta1))
lamb1_rad = wu.deg2rad(float(lamb1))
P0 = ell.ell2cart(beta0_rad, lamb0_rad)
P1 = ell.ell2cart(beta1_rad, lamb1_rad)
a0_app, a1_app, s_app, points = gha2_approx(ell, P0, P1, ds=ds_in, all_points=True)
out = html.Div([
html.Strong("Approximiert: "),
html.Br(),
html.Span(f"{aus.gms('α₀', a0_app, 4)}, {aus.gms('α₁', a1_app, 4)}, s = {s_app:.4f} m"),
])
store = {
"points": [("P0", P0, "black"), ("P1", P1, "black")],
"polyline": points,
"name": "Approximiert",
"color": "#00c2fc",
}
return out, store
except Exception as e:
return method_failed("Approximiert", e), None
# --- Plot ---
@app.callback(
Output("ellipsoid-plot", "figure"),
Input("input-ax", "value"),
Input("input-ay", "value"),
Input("input-b", "value"),
Input("dropdown-coors-type", "value"),
Input("tabs-GHA", "value"),
Input("calc-token-gha1", "data"),
Input("calc-token-gha2", "data"),
Input("store-gha1-ana", "data"),
Input("store-gha1-num", "data"),
Input("store-gha1-stoch", "data"),
Input("store-gha1-approx", "data"),
Input("store-gha2-num", "data"),
Input("store-gha2-stoch", "data"),
Input("store-gha2-approx", "data"),
)
def render_all(ax, ay, b, coords_type, tab, t1, t2,
s1a, s1n, s1s, s1p, s2n, s2s, s2p):
if None in (ax, ay, b):
return go.Figure()
if not axes_valid(ax, ay, b):
return go.Figure()
ell = EllipsoidTriaxial(ax, ay, b)
fig = ellipsoid_figure(ell, title="")
fig = figure_constant_lines(fig, ell, coords_type)
if tab == "tab-GHA1":
stores = (s1a, s1n, s1s, s1p)
else:
stores = (s2n, s2s, s2p)
def add_method(store, fallback_name):
nonlocal fig
if not store:
return
name = store.get("name") or fallback_name
color = store.get("color", "#ff8c00")
group = name # legendgroup-ID
pts = store.get("points") or []
line = store.get("polyline")
if line:
arr = np.asarray(line, dtype=float)
fig.add_trace(go.Scatter3d(
x=arr[:, 0], y=arr[:, 1], z=arr[:, 2],
mode="lines",
line=dict(width=4, color=color),
name=name,
legendgroup=group,
showlegend=True,
))
else:
fig.add_trace(go.Scatter3d(
x=[None], y=[None], z=[None],
mode="markers",
marker=dict(size=8, color=color),
name=name,
legendgroup=group,
showlegend=True,
hoverinfo="skip",
))
for pname, (px, py, pz), pcolor in pts:
fig.add_trace(go.Scatter3d(
x=[px], y=[py], z=[pz],
mode="markers+text",
marker=dict(size=6, color=pcolor),
text=[pname],
textposition="top center",
name=pname,
showlegend=False,
legendgroup=group,
))
for i, st in enumerate(stores, start=1):
add_method(st, f"Methode {i}")
fig.update_layout(
showlegend=True,
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="left",
x=0.05,
groupclick="togglegroup",
itemclick="toggle",
itemdoubleclick="toggleothers",
),
)
return fig
# Funktion zum Leeren des Plots bei Änderung des Ellipsoids
@app.callback(
Output("store-gha1-ana", "data", allow_duplicate=True),
Output("store-gha1-num", "data", allow_duplicate=True),
Output("store-gha1-stoch", "data", allow_duplicate=True),
Output("store-gha1-approx", "data", allow_duplicate=True),
Output("store-gha2-num", "data", allow_duplicate=True),
Output("store-gha2-stoch", "data", allow_duplicate=True),
Output("store-gha2-approx", "data", allow_duplicate=True),
Input("input-ax", "value"),
Input("input-ay", "value"),
Input("input-b", "value"),
prevent_initial_call=True,
)
def clear_all_stores_on_ellipsoid_change(ax, ay, b):
if None in (ax, ay, b):
return (no_update,)*7
return None, None, None, None, None, None, None
# Funktionen zur Erzeugung der Überschriften
@app.callback(
Output("gha1-header", "children"),
Input("calc-token-gha1", "data"),
prevent_initial_call=True,
)
def set_gha1_header(_):
return html.H4("Erste Hauptaufgabe")
@app.callback(
Output("gha2-header", "children"),
Input("calc-token-gha2", "data"),
prevent_initial_call=True,
)
def set_gha2_header(_):
return html.H4("Zweite Hauptaufgabe")
# Funktion zur Überprüfung der Halbachsen
@app.callback(
Output("axes-error", "children"),
Output("button-calc-gha1", "disabled"),
Output("button-calc-gha2", "disabled"),
Input("input-ax", "value"),
Input("input-ay", "value"),
Input("input-b", "value"),
)
def validate_axes(ax, ay, b):
if ax is None or ay is None or b is None:
return "", True, True
try:
ax = float(ax)
ay = float(ay)
b = float(b)
except (TypeError, ValueError):
return html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"}), True, True
if ax <= 0 or ay <= 0 or b <= 0:
return html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"}), True, True
if not (ax >= ay >= b):
return html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"}), True, True
return "", False, False
# Leeren des Dropdowns
@app.callback(
Output("dropdown-ellipsoid", "value"),
Input("input-ax", "value"),
Input("input-ay", "value"),
Input("input-b", "value"),
State("dropdown-ellipsoid", "value"),
prevent_initial_call=True,
)
def clear_dropdown_if_axes_changed(ax, ay, b, selected_ell):
if not selected_ell:
return no_update
if ax is None or ay is None or b is None:
return ""
try:
ax = float(ax); ay = float(ay); b = float(b)
ell = EllipsoidTriaxial.init_name(selected_ell)
same = (
np.isclose(ax, float(ell.ax), rtol=0, atol=1e-9) and
np.isclose(ay, float(ell.ay), rtol=0, atol=1e-9) and
np.isclose(b, float(ell.b), rtol=0, atol=1e-9)
)
except Exception:
return ""
return no_update if same else ""
if __name__ == "__main__":
# Automatisiertes Öffnen der Seite im Browser
HOST = "127.0.0.1"
PORT = 8050
# Timer(1.0, webbrowser.open_new_tab(f"http://{HOST}:{PORT}/")).start
app.run(host=HOST, port=PORT, debug=False)