const RADIUS = 6;
|
const BOARD_SIZE = 14;
|
|
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)}°`;
|
}
|
|
|
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',
|
[[-8, -8], [15, 15],
|
[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), [-Infinity, Infinity] ]);
|