Hong-Phuc Bui
2026-10-16 02147e7d251077ddecc6c5d36bf0a52f5a460d0e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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], normalize = false) => {
    const n = cross(toXYZ(lon1, lat1), toXYZ(lon2, lat2));
    if (!normalize) return n;
 
    const len = Math.hypot(...n);
    if (len < 1e-12) return null; // points are parallel or antipodal: no unique normal
    const m = n.map(c => c / len);
    m.unshift(0)
    return m;
}
 
 
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 view = board.create('view3d',
    [[-10, -10], [19, 19],
        [bound, bound, bound]],
    {
        axesPosition: 'none',
        xPlaneRear: {visible: true},
        yPlaneRear: {visible: true},
        zPlaneRear: {visible: true},
        //
        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 TANGENT_COORDINATE = [-30, 60]
const tangentPoint = view.create('point3d', [toXYZ(...TANGENT_COORDINATE)], {
    id:'T', fixed: true, highlight: true, showInfobox: false,
});
tangentPoint.element2D.label.setText( () => "T = " + toGeographicLabel( ...toLonLat(tangentPoint.X(),tangentPoint.Y(), tangentPoint.Z()  ) ) );
 
const tangentPlan = view.create('circle3d',
    [tangentPoint, () => [0, tangentPoint.X(), tangentPoint.Y(), tangentPoint.Z()], RADIUS],
    {fillColor:"red", fillOpacity:0.25, fixed: true}
);
 
const TARGET_COORDINATE = [120, 0];
const targetPoint = view.create('point3d', [toXYZ(...TARGET_COORDINATE)], {
    id: 't', name:toGeographicLabel(...TARGET_COORDINATE), fixed: true, highlight: true, showInfobox: false,
});
 
 
const routeNormalVector = normalVector(TANGENT_COORDINATE, TARGET_COORDINATE);
const route = view.create('circle3d', [origin, routeNormalVector, RADIUS] );
 
const meridianNormalVector = normalVector(TANGENT_COORDINATE, NORTH_POLE);
const meridianPlan = view.create('circle3d', [origin, meridianNormalVector, RADIUS]);
 
const projectionLine = view.create('line3d', [targetPoint, toXYZ(...TANGENT_COORDINATE),  [0, Infinity] ]);
const ot = view.create('line3d', [origin, tangentPoint])