From 25064146c744dc9adb74f32c4b8cb2aba23f6666 Mon Sep 17 00:00:00 2001
From: Hong-Phuc Bui <hong-phuc.bui@htwsaar.de>
Date: Sun, 18 Oct 2026 01:30:40 +0200
Subject: [PATCH] Interactive UI

---
 02-0-kurswinkel-benutzer-interactive/.gitignore  |    0 
 02-0-kurswinkel-benutzer-interactive/deploy.sh   |   23 +++++
 02-0-kurswinkel-benutzer-interactive/tangente.js |  151 +++++++++++++++++++++++++++++++++++++
 02-0-kurswinkel-benutzer-interactive/index.html  |   28 +++++++
 02-0-kurswinkel-benutzer-interactive/common.css  |   27 ++++++
 02-0-kurswinkel-benutzer-interactive/README.md   |   11 ++
 6 files changed, 240 insertions(+), 0 deletions(-)

diff --git a/02-0-kurswinkel-benutzer-interactive/.gitignore b/02-0-kurswinkel-benutzer-interactive/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/.gitignore
diff --git a/02-0-kurswinkel-benutzer-interactive/README.md b/02-0-kurswinkel-benutzer-interactive/README.md
new file mode 100644
index 0000000..247f38d
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/README.md
@@ -0,0 +1,11 @@
+Lernziel:
+
+* HTTP-Server
+* Syntax von URLs (=Bestandteil eines URL)
+* Relative/absolute Pfade
+
+Task:
+
+* Identifizieren Sie alle URL in der Datei `index.html`
+* Identifizieren Sie die Bestandteile von einer URL. 
+  Welcher Bestandteil einer URL haben wir in eine ULR in `index.html` verwendet?
\ No newline at end of file
diff --git a/02-0-kurswinkel-benutzer-interactive/common.css b/02-0-kurswinkel-benutzer-interactive/common.css
new file mode 100644
index 0000000..877a2f5
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/common.css
@@ -0,0 +1,27 @@
+html {
+    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
+}
+
+h1 {
+    font-size: large;
+}
+
+.container {
+    display: flex;
+    gap: 20px;
+    border: 0;
+    margin: 0;
+    padding: 0;
+}
+
+.column {
+    flex: 1;
+}
+
+@media (max-width: 600px) {
+    .container {
+        flex-direction: column;
+    }
+}
+
+
diff --git a/02-0-kurswinkel-benutzer-interactive/deploy.sh b/02-0-kurswinkel-benutzer-interactive/deploy.sh
new file mode 100755
index 0000000..4236c51
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/deploy.sh
@@ -0,0 +1,23 @@
+sync_git() {
+    SRC=$1
+    DEST=$2
+    echo $SRC
+    echo $DEST
+    mkdir -p $DEST
+    ls $DEST
+    rsync -aHAXxv --numeric-ids --delete --progress --exclude='.git' \
+        --exclude='.gitignore' \
+        --exclude='*/target' \
+        --exclude='.idea' \
+        --exclude='deploy.sh' \
+        --exclude='.venv' \
+        --exclude='site' \
+        $SRC $DEST
+
+    git -C $DEST add .
+    git -C $DEST commit -a -m "Commit Datum: $(date --iso-8601=seconds)"
+    git -C $DEST push origin main
+
+}
+
+sync_git ./ ../../codeberg.page/kurswinkel/
\ No newline at end of file
diff --git a/02-0-kurswinkel-benutzer-interactive/index.html b/02-0-kurswinkel-benutzer-interactive/index.html
new file mode 100644
index 0000000..8d0a2f5
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/index.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Index</title>
+    <script src="https://cdn.jsdelivr.net/npm/jsxgraph/distrib/jsxgraphcore.js"></script>
+    <link rel="stylesheet" type="text/css"
+          href="https://cdn.jsdelivr.net/npm/jsxgraph/distrib/jsxgraph.css">
+    <link rel="stylesheet" type="text/css"
+          href="./common.css"/>
+</head>
+<body>
+<h1>Fluglinien mit Kurswinkel</h1>
+<div class="container">
+    <div class="column">
+        <div id="box" class="jxgbox" style="width:800px; aspect-ratio: 1/1;"></div>
+    </div>
+    <div class="column">
+        <div class="explain">
+            <p>Drehe die Kugel so, dass der Startpunkt sich mit dem Punkt O deckt.
+                (el = 1.05, az = 2.09)</p>
+            <p>Der Winkel Nordpol-Startpunkt-Ziel ist der gesuchte Kurswinkel in der wahren Gestalt.</p>
+        </div>
+    </div>
+</div>
+<script src="tangente.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/02-0-kurswinkel-benutzer-interactive/tangente.js b/02-0-kurswinkel-benutzer-interactive/tangente.js
new file mode 100644
index 0000000..1978e4e
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/tangente.js
@@ -0,0 +1,151 @@
+const toXYZ = (longitude, latitude) => {
+    const lon = (longitude * Math.PI) / 180;
+    const lat = (latitude * Math.PI) / 180;
+
+    const x = RADIUS * Math.cos(lat) * Math.cos(lon);
+    const y = RADIUS * Math.cos(lat) * Math.sin(lon);
+    const z = RADIUS * Math.sin(lat);
+
+    return [x, y, z];
+};
+
+const toHomogeneousXYZ = (longitude, latitude) => {
+    const xyz = toXYZ(longitude, latitude);
+    xyz.unshift(0);
+    return xyz;
+}
+
+const toLonLat = (x, y, z) => {
+    const R = Math.hypot(x, y, z);
+    const longitude = (Math.atan2(y, x) * 180) / Math.PI;   // [-180, 180]
+    const latitude  = (Math.asin(Math.max(-1, Math.min(1, z / R))) * 180) / Math.PI; // [-90, 90]
+    return [longitude, latitude];
+};
+
+
+const cross = (a, b) => [
+    a[1] * b[2] - a[2] * b[1],
+    a[2] * b[0] - a[0] * b[2],
+    a[0] * b[1] - a[1] * b[0],
+];
+
+const normalVector = ([lon1, lat1], [lon2, lat2]) => {
+    const n = cross(toXYZ(lon1, lat1), toXYZ(lon2, lat2));
+    n.unshift(0);
+    return n;
+}
+
+
+const toGeographicLabel = (lon, lat) => {
+    return `${ lon.toFixed(1) }°|${ lat.toFixed(1)}°`;
+}
+
+
+// board commands
+const RADIUS = 6;
+const BOARD_SIZE = 14;
+
+const board = JXG.JSXGraph.initBoard(
+    'box', {
+        boundingbox: [-BOARD_SIZE, BOARD_SIZE, BOARD_SIZE, -BOARD_SIZE],
+        //axis: true
+    });
+const bound = [-10, 10];
+
+const startLongitude = board.create("slider",
+    [[-2, 10], [9, 10], [-180, -30, 180]],
+    {name: "Start LON.", precision: 2}
+);
+const startLatitude = board.create("slider",
+    [[-2, 9], [9, 9], [-90, 60, 90]],
+    {name: "Start LAT.", precision: 2}
+);
+
+const targetLongitude = board.create("slider",
+    [[-2, -10], [9, -10], [-180, 120, 180]],
+    {name: "Ziel LON.", precision: 2}
+);
+const targetLatitude = board.create("slider",
+    [[-2, -11], [9, -11], [-90, 0, 90]],
+    {name: "Ziel LAT.", precision: 2}
+);
+
+
+const view = board.create('view3d',
+    [[-10, -10], [19, 19],
+        [bound, bound, bound]],
+    {
+        axesPosition: 'none',
+        xPlaneRear: {visible: false},
+        yPlaneRear: {visible: false},
+        zPlaneRear: {visible: false},
+        //
+        az:   { slider: { visible: true } },
+        el:   { slider: { visible: true } },
+        bank: { slider: { visible: true } },
+    }
+);
+
+const origin = view.create('point3d', [0,0,0], {name:"O", fixed: true});
+const earth = view.create('sphere3d', [origin, RADIUS]);
+
+const NORTH_POLE = [0, 90];
+const north_pole = toHomogeneousXYZ(...NORTH_POLE);
+const equator = view.create('circle3d', [origin, north_pole, RADIUS], {
+    strokeColor:"red", fixed: true
+});
+const northPoint = view.create('point3d', [toXYZ(...NORTH_POLE)], {
+    name:'N', fixed: true, highlight: true, showInfobox: false,
+});
+
+
+const prime_meridian_normal = toHomogeneousXYZ(90, 0)
+const prime_meridian = view.create('circle3d', [origin, prime_meridian_normal, RADIUS], {
+    strokeColor: "blue",
+    fixed: true
+});
+
+const startPoint = view.create('point3d',
+    () => [...toXYZ(startLongitude.Value(), startLatitude.Value())],
+    {id:'S', fixed: true, highlight: true, showInfobox: false,}
+);
+startPoint.element2D.label.setText( () => "Start: " +
+    toGeographicLabel( ...toLonLat(startPoint.X(),startPoint.Y(), startPoint.Z()  ) ) );
+const tangentPlan = view.create('circle3d',
+    [startPoint, () => [0, startPoint.X(), startPoint.Y(), startPoint.Z()], RADIUS],
+    {fillColor:"red", fillOpacity:0.25, fixed: true}
+);
+
+const targetPoint = view.create('point3d',
+    () => [...toXYZ(targetLongitude.Value(), targetLatitude.Value())],
+    {id: 'T', fixed: true, highlight: true, showInfobox: false,
+});
+targetPoint.element2D.label.setText( () => "Ziel: " +
+    toGeographicLabel( ...toLonLat(targetPoint.X(), targetPoint.Y(), targetPoint.Z()  ) ) );
+
+
+const routeNormalVector = () => {
+    return normalVector(
+        [startLongitude.Value(), startLatitude.Value()],
+        [targetLongitude.Value(), targetLatitude.Value()]
+    );
+};
+const route = view.create('circle3d', [origin, routeNormalVector, RADIUS] );
+
+
+const meridianNormalVector = () => normalVector([startLongitude.Value(), startLatitude.Value()], NORTH_POLE);
+const meridianPlan = view.create('circle3d', [origin, meridianNormalVector, RADIUS]);
+const ot = view.create('line3d', [origin, startPoint])
+
+const projectionDirectionVector = () => [startPoint.X(), startPoint.Y(), startPoint.Z()];
+const projectionLine = view.create('line3d',
+    [targetPoint, projectionDirectionVector,  [0, Infinity] ]
+);
+
+
+
+
+
+
+
+

--
Gitblit v1.10.0