mirror of
https://github.com/cosinekitty/astronomy.git
synced 2026-03-23 17:03:37 -04:00
301 lines
12 KiB
JavaScript
301 lines
12 KiB
JavaScript
'use strict';
|
|
const fs = require('fs');
|
|
const Astronomy = require('../source/js/astronomy.min.js');
|
|
|
|
function Fail(message) {
|
|
console.log(`FATAL(mag_test.js): ${message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function CompareMatrices(caller, a, b, tolerance) {
|
|
for (let i=0; i<3; ++i) {
|
|
for (let j=0; j<3; ++j) {
|
|
const diff = Math.abs(a.rot[i][j] - b.rot[i][j]);
|
|
if (diff > tolerance) {
|
|
throw `ERROR(${caller}): matrix[${i}][${j}] = ${a.rot[i][j]}, expected ${b.rot[i][j]}, diff ${diff}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Rotation_MatrixInverse() {
|
|
const a = Astronomy.MakeRotation([
|
|
[1, 4, 7],
|
|
[2, 5, 8],
|
|
[3, 6, 9]
|
|
]);
|
|
|
|
const v = Astronomy.MakeRotation([
|
|
[1, 2, 3],
|
|
[4, 5, 6],
|
|
[7, 8, 9]
|
|
]);
|
|
|
|
const b = Astronomy.InverseRotation(a);
|
|
CompareMatrices('Rotation_MatrixInverse', b, v, 0);
|
|
}
|
|
|
|
function Rotation_MatrixMultiply() {
|
|
const a = Astronomy.MakeRotation([
|
|
[1, 4, 7],
|
|
[2, 5, 8],
|
|
[3, 6, 9]
|
|
]);
|
|
|
|
const b = Astronomy.MakeRotation([
|
|
[10, 13, 16],
|
|
[11, 14, 17],
|
|
[12, 15, 18]
|
|
]);
|
|
|
|
const v = Astronomy.MakeRotation([
|
|
[84, 201, 318],
|
|
[90, 216, 342],
|
|
[96, 231, 366]
|
|
]);
|
|
|
|
const c = Astronomy.CombineRotation(b, a);
|
|
CompareMatrices('Rotation_MatrixMultiply', c, v, 0);
|
|
}
|
|
|
|
function VectorDiff(a, b) {
|
|
const dx = a.x - b.x;
|
|
const dy = a.y - b.y;
|
|
const dz = a.z - b.z;
|
|
return Math.sqrt(dx*dx + dy*dy + dz*dz);
|
|
}
|
|
|
|
function Test_EQJ_ECL() {
|
|
const r = Astronomy.Rotation_EQJ_ECL();
|
|
|
|
/* Calculate heliocentric Earth position at a test time. */
|
|
const time = Astronomy.MakeTime(new Date('2019-12-08T19:39:15Z'));
|
|
const ev = Astronomy.HelioVector('Earth', time);
|
|
|
|
/* Use the existing Astronomy.Ecliptic() to calculate ecliptic vector and angles. */
|
|
const ecl = Astronomy.Ecliptic(ev.x, ev.y, ev.z);
|
|
console.log(`Test_EQJ_ECL ecl = (${ecl.ex}, ${ecl.ey}, ${ecl.ez})`);
|
|
|
|
/* Now compute the same vector via rotation matrix. */
|
|
const ee = Astronomy.RotateVector(r, ev);
|
|
const dx = ee.x - ecl.ex;
|
|
const dy = ee.y - ecl.ey;
|
|
const dz = ee.z - ecl.ez;
|
|
const diff = Math.sqrt(dx*dx + dy*dy + dz*dz);
|
|
console.log(`Test_EQJ_ECL ee = (${ee.x}, ${ee.y}, ${ee.z}); diff = ${diff}`);
|
|
if (diff > 1.0e-16)
|
|
throw 'Test_EQJ_ECL: EXCESSIVE VECTOR ERROR';
|
|
|
|
/* Reverse the test: go from ecliptic back to equatorial. */
|
|
const ir = Astronomy.Rotation_ECL_EQJ();
|
|
const et = Astronomy.RotateVector(ir, ee);
|
|
const idiff = VectorDiff(et, ev);
|
|
console.log(`Test_EQJ_ECL ev diff = ${idiff}`);
|
|
if (idiff > 1.0e-16)
|
|
throw 'Test_EQJ_ECL: EXCESSIVE REVERSE ROTATION ERROR';
|
|
}
|
|
|
|
function Test_EQJ_EQD(body) {
|
|
/* Verify conversion of equatorial J2000 to equatorial of-date, and back. */
|
|
/* Use established functions to calculate spherical coordinates for the body, in both EQJ and EQD. */
|
|
const time = Astronomy.MakeTime(new Date('2019-12-08T20:50:00Z'));
|
|
const observer = Astronomy.MakeObserver(+35, -85, 0);
|
|
const eq2000 = Astronomy.Equator(body, time, observer, false, true);
|
|
const eqdate = Astronomy.Equator(body, time, observer, true, true);
|
|
|
|
/* Convert EQJ spherical coordinates to vector. */
|
|
const v2000 = Astronomy.VectorFromEquator(eq2000, time);
|
|
|
|
/* Find rotation matrix. */
|
|
const r = Astronomy.Rotation_EQJ_EQD(time);
|
|
|
|
/* Rotate EQJ vector to EQD vector. */
|
|
const vdate = Astronomy.RotateVector(r, v2000);
|
|
|
|
/* Convert vector back to angular equatorial coordinates. */
|
|
let equcheck = Astronomy.EquatorFromVector(vdate);
|
|
|
|
/* Compare the result with the eqdate. */
|
|
const ra_diff = Math.abs(equcheck.ra - eqdate.ra);
|
|
const dec_diff = Math.abs(equcheck.dec - eqdate.dec);
|
|
const dist_diff = Math.abs(equcheck.dist - eqdate.dist);
|
|
console.log(`Test_EQJ_EQD: ${body} ra=${eqdate.ra}, dec=${eqdate.dec}, dist=${eqdate.dist}, ra_diff=${ra_diff}, dec_diff=${dec_diff}, dist_diff=${dist_diff}`);
|
|
if (ra_diff > 1.0e-14 || dec_diff > 1.0e-14 || dist_diff > 4.0e-15)
|
|
throw 'Test_EQJ_EQD: EXCESSIVE ERROR';
|
|
|
|
/* Perform the inverse conversion back to equatorial J2000 coordinates. */
|
|
const ir = Astronomy.Rotation_EQD_EQJ(time);
|
|
const t2000 = Astronomy.RotateVector(ir, vdate);
|
|
const diff = VectorDiff(t2000, v2000);
|
|
console.log(`Test_EQJ_EQD: ${body} inverse diff = ${diff}`);
|
|
if (diff > 3.0e-15)
|
|
throw 'Test_EQJ_EQD: EXCESSIVE INVERSE ERROR';
|
|
}
|
|
|
|
function Test_EQD_HOR(body) {
|
|
/* Use existing functions to calculate horizontal coordinates of the body for the time+observer. */
|
|
const time = Astronomy.MakeTime(new Date('1970-12-13T05:15:00Z'));
|
|
const observer = Astronomy.MakeObserver(-37, +45, 0);
|
|
const eqd = Astronomy.Equator(body, time, observer, true, true);
|
|
console.log(`Test_EQD_HOR ${body}: OFDATE ra=${eqd.ra}, dec=${eqd.dec}`);
|
|
const hor = Astronomy.Horizon(time, observer, eqd.ra, eqd.dec, 'normal');
|
|
|
|
/* Calculate the position of the body as an equatorial vector of date. */
|
|
const vec_eqd = Astronomy.VectorFromEquator(eqd, time);
|
|
|
|
/* Calculate rotation matrix to convert equatorial J2000 vector to horizontal vector. */
|
|
const rot = Astronomy.Rotation_EQD_HOR(time, observer);
|
|
|
|
/* Rotate the equator of date vector to a horizontal vector. */
|
|
const vec_hor = Astronomy.RotateVector(rot, vec_eqd);
|
|
|
|
/* Convert the horizontal vector to horizontal angular coordinates. */
|
|
const xsphere = Astronomy.HorizonFromVector(vec_hor, 'normal');
|
|
const diff_alt = Math.abs(xsphere.lat - hor.altitude);
|
|
const diff_az = Math.abs(xsphere.lon - hor.azimuth);
|
|
|
|
console.log(`Test_EQD_HOR ${body}: trusted alt=${hor.altitude}, az=${hor.azimuth}; test alt=${xsphere.lat}, az=${xsphere.lon}; diff_alt=${diff_alt}, diff_az=${diff_az}`);
|
|
if (diff_alt > 4.0e-14 || diff_az > 1.0e-13)
|
|
throw 'Test_EQD_HOR: EXCESSIVE HORIZONTAL ERROR.';
|
|
|
|
/* Confirm that we can convert back to horizontal vector. */
|
|
const check_hor = Astronomy.VectorFromHorizon(xsphere, time, 'normal');
|
|
let diff = VectorDiff(check_hor, vec_hor);
|
|
console.log(`Test_EQD_HOR ${body}: horizontal recovery: diff = ${diff}`);
|
|
if (diff > 1.0e-15)
|
|
throw 'Test_EQD_HOR: EXCESSIVE ERROR IN HORIZONTAL RECOVERY.';
|
|
|
|
/* Verify the inverse translation from horizontal vector to equatorial of-date vector. */
|
|
const irot = Astronomy.Rotation_HOR_EQD(time, observer);
|
|
const check_eqd = Astronomy.RotateVector(irot, vec_hor);
|
|
diff = VectorDiff(check_eqd, vec_eqd);
|
|
console.log(`Test_EQD_HOR ${body}: OFDATE inverse rotation diff = ${diff}`);
|
|
if (diff > 1.0e-15)
|
|
throw 'Test_EQD_HOR: EXCESSIVE OFDATE INVERSE HORIZONTAL ERROR.';
|
|
|
|
/* Exercise HOR to EQJ translation. */
|
|
const eqj = Astronomy.Equator(body, time, observer, false, true);
|
|
const vec_eqj = Astronomy.VectorFromEquator(eqj, time);
|
|
const yrot = Astronomy.Rotation_HOR_EQJ(time, observer);
|
|
const check_eqj = Astronomy.RotateVector(yrot, vec_hor);
|
|
diff = VectorDiff(check_eqj, vec_eqj);
|
|
console.log(`Test_EQD_HOR ${body}: J2000 inverse rotation diff = ${diff}`);
|
|
if (diff > 3.0e-15)
|
|
throw 'Test_EQD_HOR: EXCESSIVE J2000 INVERSE HORIZONTAL ERROR.';
|
|
|
|
/* Verify the inverse translation: EQJ to HOR. */
|
|
const zrot = Astronomy.Rotation_EQJ_HOR(time, observer);
|
|
const another_hor = Astronomy.RotateVector(zrot, vec_eqj);
|
|
diff = VectorDiff(another_hor, vec_hor);
|
|
console.log(`Test_EQD_HOR ${body}: EQJ inverse rotation diff = ${diff}`);
|
|
if (diff > 2.0e-15)
|
|
throw 'Test_EQD_HOR: EXCESSIVE EQJ INVERSE HORIZONTAL ERROR.';
|
|
}
|
|
|
|
const IdentityMatrix = Astronomy.MakeRotation([[1, 0, 0], [0, 1, 0], [0, 0, 1]]);
|
|
|
|
function CheckInverse(aname, bname, arot, brot) {
|
|
const crot = Astronomy.CombineRotation(arot, brot);
|
|
CompareMatrices(`CheckInverse(${aname},${bname})`, crot, IdentityMatrix, 2.0e-15);
|
|
}
|
|
|
|
function CheckCycle(cyclename, arot, brot, crot) {
|
|
const xrot = Astronomy.CombineRotation(arot, brot);
|
|
const irot = Astronomy.InverseRotation(xrot);
|
|
CompareMatrices(cyclename, crot, irot, 2.0e-15);
|
|
}
|
|
|
|
function Test_RotRoundTrip() {
|
|
const time = Astronomy.MakeTime(new Date('2067-05-30T14:45:00Z'));
|
|
const observer = Astronomy.MakeObserver(+28, -82, 0);
|
|
|
|
/*
|
|
In each round trip, calculate a forward rotation and a backward rotation.
|
|
Verify the two are inverse matrices.
|
|
*/
|
|
|
|
/* Round trip #1: EQJ <==> EQD. */
|
|
const eqj_eqd = Astronomy.Rotation_EQJ_EQD(time);
|
|
const eqd_eqj = Astronomy.Rotation_EQD_EQJ(time);
|
|
CheckInverse('eqj_eqd', 'eqd_eqj', eqj_eqd, eqd_eqj);
|
|
|
|
/* Round trip #2: EQJ <==> ECL. */
|
|
const eqj_ecl = Astronomy.Rotation_EQJ_ECL();
|
|
const ecl_eqj = Astronomy.Rotation_ECL_EQJ();
|
|
CheckInverse('eqj_ecl', 'ecl_eqj', eqj_ecl, ecl_eqj);
|
|
|
|
/* Round trip #3: EQJ <==> HOR. */
|
|
const eqj_hor = Astronomy.Rotation_EQJ_HOR(time, observer);
|
|
const hor_eqj = Astronomy.Rotation_HOR_EQJ(time, observer);
|
|
CheckInverse('eqj_hor', 'hor_eqj', eqj_hor, hor_eqj);
|
|
|
|
/* Round trip #4: EQD <==> HOR. */
|
|
const eqd_hor = Astronomy.Rotation_EQD_HOR(time, observer);
|
|
const hor_eqd = Astronomy.Rotation_HOR_EQD(time, observer);
|
|
CheckInverse('eqd_hor', 'hor_eqd', eqd_hor, hor_eqd);
|
|
|
|
/* Round trip #5: EQD <==> ECL. */
|
|
const eqd_ecl = Astronomy.Rotation_EQD_ECL(time);
|
|
const ecl_eqd = Astronomy.Rotation_ECL_EQD(time);
|
|
CheckInverse('eqd_ecl', 'ecl_eqd', eqd_ecl, ecl_eqd);
|
|
|
|
/* Round trip #6: HOR <==> ECL. */
|
|
const hor_ecl = Astronomy.Rotation_HOR_ECL(time, observer);
|
|
const ecl_hor = Astronomy.Rotation_ECL_HOR(time, observer);
|
|
CheckInverse('hor_ecl', 'ecl_hor', hor_ecl, ecl_hor);
|
|
|
|
/*
|
|
Verify that combining different sequences of rotations result
|
|
in the expected combination.
|
|
For example, (EQJ ==> HOR ==> ECL) must be the same matrix as (EQJ ==> ECL).
|
|
Each of these is a "triangle" of relationships between 3 orientations.
|
|
There are 4 possible ways to pick 3 orientations from the 4 to form a triangle.
|
|
Because we have just proved that each transformation is reversible,
|
|
we only need to verify the triangle in one cyclic direction.
|
|
*/
|
|
CheckCycle('eqj_ecl, ecl_eqd, eqd_eqj', eqj_ecl, ecl_eqd, eqd_eqj); /* excluded corner = HOR */
|
|
CheckCycle('eqj_hor, hor_ecl, ecl_eqj', eqj_hor, hor_ecl, ecl_eqj); /* excluded corner = EQD */
|
|
CheckCycle('eqj_hor, hor_eqd, eqd_eqj', eqj_hor, hor_eqd, eqd_eqj); /* excluded corner = ECL */
|
|
CheckCycle('ecl_eqd, eqd_hor, hor_ecl', ecl_eqd, eqd_hor, hor_ecl); /* excluded corner = EQJ */
|
|
|
|
console.log('Test_RotRoundTrip: PASS');
|
|
}
|
|
|
|
function RotationTest() {
|
|
Rotation_MatrixInverse();
|
|
Rotation_MatrixMultiply();
|
|
Test_EQJ_ECL();
|
|
|
|
Test_EQJ_EQD('Mercury');
|
|
Test_EQJ_EQD('Venus');
|
|
Test_EQJ_EQD('Mars');
|
|
Test_EQJ_EQD('Jupiter');
|
|
Test_EQJ_EQD('Saturn');
|
|
|
|
Test_EQD_HOR('Mercury');
|
|
Test_EQD_HOR('Venus');
|
|
Test_EQD_HOR('Mars');
|
|
Test_EQD_HOR('Jupiter');
|
|
Test_EQD_HOR('Saturn');
|
|
|
|
Test_RotRoundTrip();
|
|
}
|
|
|
|
function RefractionTest() {
|
|
for (let alt = -90.1; alt <= +90.1; alt += 0.001) {
|
|
const refr = Astronomy.Refraction('normal', alt);
|
|
const corrected = alt + refr;
|
|
const inv_refr = Astronomy.InverseRefraction('normal', corrected);
|
|
const check_alt = corrected + inv_refr;
|
|
const diff = Math.abs(check_alt - alt);
|
|
if (diff > 2.0e-14)
|
|
throw `JS RefractionTest: alt=${alt}, refr=${refr}, diff=${diff}`;
|
|
}
|
|
console.log('RefractionTest: PASS');
|
|
}
|
|
|
|
RotationTest();
|
|
RefractionTest();
|
|
console.log('rotation.js: success');
|
|
process.exit(0);
|