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 ellipsoid_figure(ell: EllipsoidTriaxial, title="Dreiachsiges Ellipsoid"): fig = go.Figure() # Darstellung rx, ry, rz = 1.05*ell.ax, 1.05*ell.ay, 1.05*ell.b fig.update_layout( title=title, scene=dict( xaxis=dict(range=[-rx, rx], title="X [m]"), yaxis=dict(range=[-ry, ry], title="Y [m]"), zaxis=dict(range=[-rz, rz], title="Z [m]"), aspectmode="data" ), margin=dict(l=0, r=0, t=10, b=0), ) # 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.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.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.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() try: ax = float(ax); ay = float(ay); b = float(b) except (TypeError, ValueError): return go.Figure() if ax <= 0 or ay <= 0 or b <= 0: return go.Figure() if not (ax >= ay >= b): return go.Figure() ell = EllipsoidTriaxial(ax, ay, b) fig = ellipsoid_figure(ell, title="") fig = figure_constant_lines(fig, ell, coords_type) legend_added = set() def add_legend_for_store(store): nonlocal fig if not store: return name = store.get("name") color = store.get("color") if not name or not color: return key = (name, color) if key in legend_added: return legend_added.add(key) has_line = bool(store.get("polyline")) if has_line: fig.add_trace(go.Scatter3d( x=[None], y=[None], z=[None], mode="lines", line=dict(width=6, color=color), name=name, showlegend=True, hoverinfo="skip", )) else: fig.add_trace(go.Scatter3d( x=[None], y=[None], z=[None], mode="markers", marker=dict(size=8, color=color), name=name, showlegend=True, hoverinfo="skip", )) def add_from_store(store): nonlocal fig if not store: return pts = store.get("points") or [] if pts: fig = figure_points(fig, pts) line = store.get("polyline") if line: fig = figure_lines( fig, line, store.get("name", ""), store.get("color", "#ff8c00"), ) if tab == "tab-GHA1": stores = (s1a, s1n, s1s, s1p) else: stores = (s2n, s2s, s2p) for st in stores: add_legend_for_store(st) add_from_store(st) fig.update_layout( showlegend=True, legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0.06, ), ) 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)