From 94811ea59ca1b8f07284444736b6cc2eb4b35617 Mon Sep 17 00:00:00 2001
From: Hong-Phuc Bui <hong-phuc.bui@htwsaar.de>
Date: Sun, 18 Oct 2026 14:48:11 +0200
Subject: [PATCH] Interactive UI

---
 02-0-kurswinkel-benutzer-interactive/tangente.js          |   34 ++++++++++++++--
 02-0-kurswinkel-benutzer-interactive/sphere-calculator.js |   27 +++++++++++++
 02-0-kurswinkel-benutzer-interactive/index.html           |    2 
 3 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/02-0-kurswinkel-benutzer-interactive/index.html b/02-0-kurswinkel-benutzer-interactive/index.html
index 8d0a2f5..55bf94a 100644
--- a/02-0-kurswinkel-benutzer-interactive/index.html
+++ b/02-0-kurswinkel-benutzer-interactive/index.html
@@ -23,6 +23,6 @@
         </div>
     </div>
 </div>
-<script src="tangente.js"></script>
+<script type="module" src="tangente.js"></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/02-0-kurswinkel-benutzer-interactive/sphere-calculator.js b/02-0-kurswinkel-benutzer-interactive/sphere-calculator.js
new file mode 100644
index 0000000..dca48ff
--- /dev/null
+++ b/02-0-kurswinkel-benutzer-interactive/sphere-calculator.js
@@ -0,0 +1,27 @@
+export const toRadians = (deg) => deg * Math.PI / 180;
+export const toDegrees = (rad) => rad * 180 / Math.PI;
+
+export const azimuth = (longitude1, latitude1, longitude2, latitude2) =>{
+    const [lon1, lat1, lon2, lat2] = [longitude1, latitude1, longitude2, latitude2].map(toRadians);
+    const _deltaLon = Math.abs(lon2 - lon1);
+    const PI = Math.PI;
+    const deltaLon = (_deltaLon) <= PI ? _deltaLon : 2*PI - _deltaLon;
+    const q = (Math.tan(PI/4 + lat2/2))/(Math.tan(PI/4 + lat1/2));
+    const ln = 1/Math.log(q);
+    const alpha = Math.atan( deltaLon*ln ) * 180/PI; // degree!
+    const eastCourse = _deltaLon <= PI;
+    const direction = ( lon2>=lon1 &&  eastCourse) || (lon2<lon1 && !eastCourse);
+    if(lat2 > lat1 && direction) {
+        return alpha;
+    } else if(lat2 < lat1 && direction) {
+        return 180 - alpha;
+    } else if(lat2 < lat1 && !direction) {
+        return 180 + alpha;
+    } else if(lat2 > lat1 && !direction) {
+        return 360 - alpha;
+    }
+    if(lat2 === lat1) {
+        return direction ? 90 : 270;
+    }
+}
+
diff --git a/02-0-kurswinkel-benutzer-interactive/tangente.js b/02-0-kurswinkel-benutzer-interactive/tangente.js
index 1978e4e..9d68024 100644
--- a/02-0-kurswinkel-benutzer-interactive/tangente.js
+++ b/02-0-kurswinkel-benutzer-interactive/tangente.js
@@ -1,3 +1,5 @@
+import {azimuth} from "./sphere-calculator.js";
+
 const toXYZ = (longitude, latitude) => {
     const lon = (longitude * Math.PI) / 180;
     const lat = (latitude * Math.PI) / 180;
@@ -40,6 +42,11 @@
     return `${ lon.toFixed(1) }°|${ lat.toFixed(1)}°`;
 }
 
+const solveProjectionPoint = (startX, startY, startZ, targetX, targetY, targetZ) => {
+    const t = targetX*targetX + targetY*targetY + targetZ*targetZ;
+    const k = -(startX*targetX + startY*targetY + startZ*targetZ - t) / t;
+    return [targetX + k * startX, targetY + k*startY, targetZ + k*startZ]
+}
 
 // board commands
 const RADIUS = 6;
@@ -70,6 +77,14 @@
     {name: "Ziel LAT.", precision: 2}
 );
 
+const displayAzimuth = () => {
+    const a = azimuth(startLongitude.Value(), startLatitude.Value(),
+        targetLongitude.Value(), targetLatitude.Value()
+    );
+    return `Kurswinkel: ${a.toFixed(2)}°`;
+}
+const azimuthText = board.create('text', [8,6, displayAzimuth], {});
+
 
 const view = board.create('view3d',
     [[-10, -10], [19, 19],
@@ -86,7 +101,9 @@
     }
 );
 
-const origin = view.create('point3d', [0,0,0], {name:"O", fixed: true});
+const origin = view.create('point3d', [0,0,0],
+    {name:"O", fixed: true, highlight: false, showInfobox: false,}
+);
 const earth = view.create('sphere3d', [origin, RADIUS]);
 
 const NORTH_POLE = [0, 90];
@@ -118,11 +135,17 @@
 
 const targetPoint = view.create('point3d',
     () => [...toXYZ(targetLongitude.Value(), targetLatitude.Value())],
-    {id: 'T', fixed: true, highlight: true, showInfobox: false,
-});
+    {id: 'T', fixed: true, highlight: true, showInfobox: false,}
+);
 targetPoint.element2D.label.setText( () => "Ziel: " +
     toGeographicLabel( ...toLonLat(targetPoint.X(), targetPoint.Y(), targetPoint.Z()  ) ) );
-
+const projectionPoint = view.create('point3d',
+    () => solveProjectionPoint(
+        startPoint.X(), startPoint.Y(), startPoint.Z(),
+        targetPoint.X(), targetPoint.Y(), targetPoint.Z()
+    ),
+    {id: 'P', name:"Prj. Ziel", fixed: true, highlight: true, showInfobox: false, strokeColor:"black", fillColor: "gray"}
+);
 
 const routeNormalVector = () => {
     return normalVector(
@@ -139,7 +162,8 @@
 
 const projectionDirectionVector = () => [startPoint.X(), startPoint.Y(), startPoint.Z()];
 const projectionLine = view.create('line3d',
-    [targetPoint, projectionDirectionVector,  [0, Infinity] ]
+    [targetPoint, projectionDirectionVector,  [0, Infinity]],
+    {strokeColor: "gray", dash: 2, strokeWidth: 1}
 );
 
 

--
Gitblit v1.10.0