from dash import Dash, dash, html, dcc, Input, Output, State, no_update, ctx
import plotly.graph_objects as go
import numpy as np
import dash_bootstrap_components as dbc
import builtins
from dash.exceptions import PreventUpdate
import traceback
import webbrowser
from threading import Timer
from ellipsoide import EllipsoidTriaxial
import winkelumrechnungen as wu
import ausgaben as aus
from GHA_triaxial.utils import alpha_ell2para, alpha_para2ell
from GHA_triaxial.gha1_ana import gha1_ana
from GHA_triaxial.gha1_num import gha1_num
from GHA_triaxial.gha1_ES import gha1_ES
from GHA_triaxial.gha1_approx import gha1_approx
from GHA_triaxial.gha2_num import gha2_num
from GHA_triaxial.gha2_ES import gha2_ES
from GHA_triaxial.gha2_approx import gha2_approx
# 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, min=None, max=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}]",
min=min,
max=max,
style={"width": width, "display": "block"},
persistence=True,
persistence_type="memory", # oder "session"/"local"
),
html.Span(right_text, style={"marginLeft": 5}) if right_text else html.Span()
],
style={"display": "flex", "alignItems": "center", "marginBottom": "10px"},
)
# 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": "12px",
"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": "12px",
"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(-np.pi/2, np.pi/2, 80)
v = np.linspace(-np.pi, np.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) != np.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(
[
inputfeld("β₀", "input-GHA1-beta0", "°"),
inputfeld("λ₀", "input-GHA1-lamb0", "°"),
inputfeld("s", "input-GHA1-s", "m"),
inputfeld("α₀", "input-GHA1-a", "°"),
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(
[
inputfeld("β₀", "input-GHA2-beta0", "°"),
inputfeld("λ₀", "input-GHA2-lamb0", "°"),
inputfeld("β₁", "input-GHA2-beta1", "°"),
inputfeld("λ₁", "input-GHA2-lamb1", "°"),
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.H1("Geodätische Hauptaufgaben"),
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": "10px"}),
inputfeld("aₓ", "input-ax", "m"),
inputfeld("aᵧ", "input-ay", "m"),
inputfeld("b", "input-b", "m"),
html.Div(id="axes-error", style={"marginLeft": "10px", "marginTop": "5px", "marginBottom": "10px"}),
#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": "-50px",
},
children=[
html.Label("Koordinatenart wählen:", style={"marginLeft": "80px"},),
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": "80px"},
),
dcc.Graph(
id="ellipsoid-plot",
style={"height": "85vh", "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("tabs-GHA1-out", "children"),
Input("button-calc-gha1", "n_clicks"),
State("cb-ana-1", "value"),
State("cb-num-1", "value"),
State("cb-stoch-1", "value"),
State("cb-approx-1", "value"),
# Eingaben GHA1
State("input-GHA1-beta0", "value"),
State("input-GHA1-lamb0", "value"),
State("input-GHA1-s", "value"),
State("input-GHA1-a", "value"),
# Halbachsen
State("input-ax", "value"),
State("input-ay", "value"),
State("input-b", "value"),
prevent_initial_call=True,
)
def gha1_method_hint(n, a, nu, st, ap, beta0, lamb0, s, a0, ax, ay, b):
# Ellipsoid
if ax is None or ay is None or b is None:
return html.Span("Bitte Ellipsoid wählen bzw. Halbachsen eingeben.", style={"color": "red"})
# Halbachsen
try:
ax_f, ay_f, b_f = float(ax), float(ay), float(b)
except (TypeError, ValueError):
return html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"})
if ax_f <= 0 or ay_f <= 0 or b_f <= 0:
return html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"})
if not (ax_f >= ay_f >= b_f):
return html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"})
# GHA1-Eingabefelder prüfen
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:
return html.Span(
"Bitte " + ", ".join(missing) + " eingeben.",
style={"color": "red"},
)
# Berechnungsverfahren
any_on = any("on" in (v or []) for v in (a, nu, st, ap))
if not any_on:
return html.Span("Bitte Berechnungsverfahren wählen.", style={"color": "red"})
# Eingaben Wertebereiche
try:
beta0_f = float(beta0)
lamb0_f = float(lamb0)
s_f = float(s)
a0_f = float(a0)
except (TypeError, ValueError):
return html.Span("Bitte gültige Zahlen für β₀, λ₀, s und α₀ eingeben.", style={"color": "red"})
if not (-90 <= beta0_f <= 90):
return html.Span("β₀ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"})
if not (-180 <= lamb0_f <= 180):
return html.Span("λ₀ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"})
if s_f <= 0:
return html.Span("s muss > 0 sein.", style={"color": "red"})
if not (-180 <= lamb0_f <= 360):
return html.Span("α₀ muss im Bereich [-180°, 360°] liegen.", style={"color": "red"})
return ""
# -- 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)
alpha2 = 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(),
])
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
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)
try:
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(),
])
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 = int(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)}"),
])
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 = int(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)}"),
])
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 = int(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 = int(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("Stochastisch (ES)", 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.06,
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 separaten Darstellung der Tabs
@app.callback(
Output("calc-token-gha1", "data"),
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"),
prevent_initial_call=True,
)
def start_calc_gha1(n, token):
if not n:
raise PreventUpdate
token = (token or 0) + 1
return token, "", "", "", "", "", None, None, None, None
@app.callback(
Output("calc-token-gha2", "data"),
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"),
prevent_initial_call=True,
)
def start_calc_gha2(n, token):
if not n:
raise PreventUpdate
token = (token or 0) + 1
return token, "", "", "", "", 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)