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)