@interstellar-tools/equations 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/__tests__/compute-angle.int.spec.d.ts +2 -0
- package/dist/__tests__/compute-angle.int.spec.d.ts.map +1 -0
- package/dist/__tests__/compute-angle.int.spec.js +62 -0
- package/dist/__tests__/compute-angle.int.spec.js.map +1 -0
- package/dist/__tests__/compute-mean-anomaly.int.spec.d.ts +2 -0
- package/dist/__tests__/compute-mean-anomaly.int.spec.d.ts.map +1 -0
- package/dist/__tests__/compute-mean-anomaly.int.spec.js +175 -0
- package/dist/__tests__/compute-mean-anomaly.int.spec.js.map +1 -0
- package/dist/__tests__/eccentric-to-true-anomaly.spec.d.ts +2 -0
- package/dist/__tests__/eccentric-to-true-anomaly.spec.d.ts.map +1 -0
- package/dist/__tests__/eccentric-to-true-anomaly.spec.js +33 -0
- package/dist/__tests__/eccentric-to-true-anomaly.spec.js.map +1 -0
- package/dist/__tests__/solve-kepler-bisection.spec.d.ts +2 -0
- package/dist/__tests__/solve-kepler-bisection.spec.d.ts.map +1 -0
- package/dist/__tests__/solve-kepler-bisection.spec.js +41 -0
- package/dist/__tests__/solve-kepler-bisection.spec.js.map +1 -0
- package/dist/__tests__/solve-kepler-high-eccentricity.spec.d.ts +2 -0
- package/dist/__tests__/solve-kepler-high-eccentricity.spec.d.ts.map +1 -0
- package/dist/__tests__/solve-kepler-high-eccentricity.spec.js +81 -0
- package/dist/__tests__/solve-kepler-high-eccentricity.spec.js.map +1 -0
- package/dist/__tests__/solve-kepler-newton-raphson.spec.d.ts +2 -0
- package/dist/__tests__/solve-kepler-newton-raphson.spec.d.ts.map +1 -0
- package/dist/__tests__/solve-kepler-newton-raphson.spec.js +50 -0
- package/dist/__tests__/solve-kepler-newton-raphson.spec.js.map +1 -0
- package/dist/__tests__/solve-kepler.int.spec.d.ts +2 -0
- package/dist/__tests__/solve-kepler.int.spec.d.ts.map +1 -0
- package/dist/__tests__/solve-kepler.int.spec.js +76 -0
- package/dist/__tests__/solve-kepler.int.spec.js.map +1 -0
- package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.d.ts +2 -0
- package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.d.ts.map +1 -0
- package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.js +46 -0
- package/dist/__tests__/true-anomaly-to-mean-anomaly.int.spec.js.map +1 -0
- package/dist/__tests__/wrap-angle.spec.d.ts +2 -0
- package/dist/__tests__/wrap-angle.spec.d.ts.map +1 -0
- package/dist/__tests__/wrap-angle.spec.js +60 -0
- package/dist/__tests__/wrap-angle.spec.js.map +1 -0
- package/dist/compute-angle.d.ts +69 -0
- package/dist/compute-angle.d.ts.map +1 -0
- package/dist/compute-angle.js +79 -0
- package/dist/compute-angle.js.map +1 -0
- package/dist/compute-mean-anomaly.d.ts +47 -0
- package/dist/compute-mean-anomaly.d.ts.map +1 -0
- package/dist/compute-mean-anomaly.js +86 -0
- package/dist/compute-mean-anomaly.js.map +1 -0
- package/dist/eccentric-to-true-anomaly.d.ts +43 -0
- package/dist/eccentric-to-true-anomaly.d.ts.map +1 -0
- package/dist/eccentric-to-true-anomaly.js +63 -0
- package/dist/eccentric-to-true-anomaly.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/solve-kepler-bisection.d.ts +75 -0
- package/dist/solve-kepler-bisection.d.ts.map +1 -0
- package/dist/solve-kepler-bisection.js +94 -0
- package/dist/solve-kepler-bisection.js.map +1 -0
- package/dist/solve-kepler-high-eccentricity.d.ts +99 -0
- package/dist/solve-kepler-high-eccentricity.d.ts.map +1 -0
- package/dist/solve-kepler-high-eccentricity.js +150 -0
- package/dist/solve-kepler-high-eccentricity.js.map +1 -0
- package/dist/solve-kepler-newton-raphson.d.ts +87 -0
- package/dist/solve-kepler-newton-raphson.d.ts.map +1 -0
- package/dist/solve-kepler-newton-raphson.js +118 -0
- package/dist/solve-kepler-newton-raphson.js.map +1 -0
- package/dist/solve-kepler.d.ts +82 -0
- package/dist/solve-kepler.d.ts.map +1 -0
- package/dist/solve-kepler.js +99 -0
- package/dist/solve-kepler.js.map +1 -0
- package/dist/true-anomaly-to-mean-anomaly.d.ts +74 -0
- package/dist/true-anomaly-to-mean-anomaly.d.ts.map +1 -0
- package/dist/true-anomaly-to-mean-anomaly.js +91 -0
- package/dist/true-anomaly-to-mean-anomaly.js.map +1 -0
- package/dist/wrap-angle.d.ts +82 -0
- package/dist/wrap-angle.d.ts.map +1 -0
- package/dist/wrap-angle.js +97 -0
- package/dist/wrap-angle.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test, { describe } from 'node:test';
|
|
3
|
+
import { solveKepler } from '../solve-kepler';
|
|
4
|
+
const EPSILON = 1e-8; // Floating-point tolerance
|
|
5
|
+
const assertApproxEqual = (actual, expected) => {
|
|
6
|
+
assert.ok(Math.abs(actual - expected) < EPSILON, `Expected ~${expected}, got ${actual}`);
|
|
7
|
+
};
|
|
8
|
+
describe('solveKepler', () => {
|
|
9
|
+
test('Circular Orbit (e = 0)', () => {
|
|
10
|
+
const M = Math.PI / 4; // 45 degrees
|
|
11
|
+
const e = 0.0; // Circular orbit
|
|
12
|
+
const result = solveKepler(M, e);
|
|
13
|
+
assertApproxEqual(result, M); // E = M for circular orbits
|
|
14
|
+
});
|
|
15
|
+
test('Small Eccentricity (e = 0.1)', () => {
|
|
16
|
+
const M = Math.PI / 4;
|
|
17
|
+
const e = 0.1;
|
|
18
|
+
const expectedE = 0.8612648848681754;
|
|
19
|
+
const result = solveKepler(M, e);
|
|
20
|
+
assertApproxEqual(result, expectedE);
|
|
21
|
+
});
|
|
22
|
+
test('Moderate Eccentricity (e = 0.5)', () => {
|
|
23
|
+
const M = Math.PI / 2;
|
|
24
|
+
const e = 0.5;
|
|
25
|
+
const expectedE = 2.02097993808977;
|
|
26
|
+
const result = solveKepler(M, e);
|
|
27
|
+
assertApproxEqual(result, expectedE);
|
|
28
|
+
});
|
|
29
|
+
test('High Eccentricity (e = 0.8)', () => {
|
|
30
|
+
const M = Math.PI / 6;
|
|
31
|
+
const e = 0.8;
|
|
32
|
+
const expectedE = 1.2929083458551878;
|
|
33
|
+
const result = solveKepler(M, e);
|
|
34
|
+
assertApproxEqual(result, expectedE);
|
|
35
|
+
});
|
|
36
|
+
test('Nearly Parabolic Orbit (e = 0.99)', () => {
|
|
37
|
+
const M = Math.PI / 4;
|
|
38
|
+
const e = 0.99;
|
|
39
|
+
const expectedE = 1.758085607716896;
|
|
40
|
+
const result = solveKepler(M, e);
|
|
41
|
+
assertApproxEqual(result, expectedE);
|
|
42
|
+
});
|
|
43
|
+
test('Mean Anomaly at 0', () => {
|
|
44
|
+
const M = 0;
|
|
45
|
+
const e = 0.5;
|
|
46
|
+
const result = solveKepler(M, e);
|
|
47
|
+
assertApproxEqual(result, 0); // E = 0 when M = 0
|
|
48
|
+
});
|
|
49
|
+
test('Mean Anomaly at π', () => {
|
|
50
|
+
const M = Math.PI;
|
|
51
|
+
const e = 0.5;
|
|
52
|
+
const expectedE = Math.PI; // At M = π, E should also be π
|
|
53
|
+
const result = solveKepler(M, e);
|
|
54
|
+
assert.equal(result, expectedE);
|
|
55
|
+
});
|
|
56
|
+
test('Mean Anomaly at 2π', () => {
|
|
57
|
+
const M = 2 * Math.PI;
|
|
58
|
+
const e = 0.5;
|
|
59
|
+
const result = solveKepler(M, e);
|
|
60
|
+
assertApproxEqual(result, 0); // Should wrap to 0
|
|
61
|
+
});
|
|
62
|
+
test('Convergence with max iterations', () => {
|
|
63
|
+
const M = Math.PI / 3;
|
|
64
|
+
const e = 0.7;
|
|
65
|
+
const result = solveKepler(M, e, 100); // Force more iterations
|
|
66
|
+
assert.ok(result >= 0 && result < 2 * Math.PI, `Expected result in range [0, 2π], got ${result}`);
|
|
67
|
+
});
|
|
68
|
+
test('Invalid Eccentricity (e < 0) Throws Error', () => {
|
|
69
|
+
assert.throws(() => solveKepler(Math.PI / 4, -0.1), RangeError);
|
|
70
|
+
});
|
|
71
|
+
test('Invalid Eccentricity (e >= 1) Throws Error', () => {
|
|
72
|
+
assert.throws(() => solveKepler(Math.PI / 4, 1), RangeError);
|
|
73
|
+
assert.throws(() => solveKepler(Math.PI / 4, 1.1), RangeError);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=solve-kepler.int.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solve-kepler.int.spec.js","sourceRoot":"","sources":["../../src/__tests__/solve-kepler.int.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"true-anomaly-to-mean-anomaly.int.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/true-anomaly-to-mean-anomaly.int.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test, { describe } from 'node:test';
|
|
3
|
+
import { trueAnomalyToMeanAnomaly } from '../true-anomaly-to-mean-anomaly';
|
|
4
|
+
import { wrapAngle } from '../wrap-angle';
|
|
5
|
+
describe('trueAnomalyToMeanAnomaly', () => {
|
|
6
|
+
const EPSILON = 1e-10; // Floating-point tolerance
|
|
7
|
+
const assertApproxEqual = (actual, expected) => {
|
|
8
|
+
assert.ok(Math.abs(actual - expected) < EPSILON, `Expected ~${expected}, got ${actual}`);
|
|
9
|
+
};
|
|
10
|
+
test('Circular Orbit (e = 0)', () => {
|
|
11
|
+
const result = trueAnomalyToMeanAnomaly(Math.PI / 3, 0); // e = 0 (should be identity)
|
|
12
|
+
assertApproxEqual(result, wrapAngle(Math.PI / 3));
|
|
13
|
+
});
|
|
14
|
+
test('Zero True Anomaly (V = 0)', () => {
|
|
15
|
+
const result = trueAnomalyToMeanAnomaly(0, 0.5);
|
|
16
|
+
assertApproxEqual(result, 0);
|
|
17
|
+
});
|
|
18
|
+
test('True Anomaly at π/2 (90°)', () => {
|
|
19
|
+
const result = trueAnomalyToMeanAnomaly(Math.PI / 2, 0.5);
|
|
20
|
+
const expectedM = 0.6141848493043778; // Corrected expected value
|
|
21
|
+
assertApproxEqual(result, expectedM);
|
|
22
|
+
});
|
|
23
|
+
test('True Anomaly at π (Apoapsis)', () => {
|
|
24
|
+
const result = trueAnomalyToMeanAnomaly(Math.PI, 0.5);
|
|
25
|
+
const expectedM = wrapAngle(Math.PI); // Mean anomaly should be equal to π
|
|
26
|
+
assertApproxEqual(result, expectedM);
|
|
27
|
+
});
|
|
28
|
+
test('True Anomaly at 3π/2 (270°)', () => {
|
|
29
|
+
const result = trueAnomalyToMeanAnomaly((3 * Math.PI) / 2, 0.5);
|
|
30
|
+
const expectedM = -0.6141848493043782; // Corrected expected value
|
|
31
|
+
assertApproxEqual(result, expectedM);
|
|
32
|
+
});
|
|
33
|
+
test('True Anomaly at 2π (Periapsis)', () => {
|
|
34
|
+
const result = trueAnomalyToMeanAnomaly(2 * Math.PI, 0.5);
|
|
35
|
+
assertApproxEqual(result, 0); // Should return to zero
|
|
36
|
+
});
|
|
37
|
+
test('Eccentricity Out of Bounds Throws Error', () => {
|
|
38
|
+
assert.throws(() => trueAnomalyToMeanAnomaly(Math.PI / 3, -0.1), RangeError);
|
|
39
|
+
assert.throws(() => trueAnomalyToMeanAnomaly(Math.PI / 3, 1.1), RangeError);
|
|
40
|
+
});
|
|
41
|
+
test('Large Eccentricity (e close to 1)', () => {
|
|
42
|
+
const result = trueAnomalyToMeanAnomaly(Math.PI / 4, 0.99);
|
|
43
|
+
assert.ok(result >= 0 && result < 2 * Math.PI, `Expected result in range [0, 2π], got ${result}`);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=true-anomaly-to-mean-anomaly.int.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"true-anomaly-to-mean-anomaly.int.spec.js","sourceRoot":"","sources":["../../src/__tests__/true-anomaly-to-mean-anomaly.int.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap-angle.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/wrap-angle.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, test } from 'node:test';
|
|
3
|
+
import { wrapAngle } from '../wrap-angle';
|
|
4
|
+
const EPSILON = 1e-10; // Small tolerance for floating-point comparisons
|
|
5
|
+
const assertApproxEqual = (actual, expected) => {
|
|
6
|
+
assert.ok(Math.abs(actual - expected) < EPSILON, `Expected ~${expected}, got ${actual}`);
|
|
7
|
+
};
|
|
8
|
+
describe('wrapAngle', () => {
|
|
9
|
+
test('angles already in range [-2π, 2π]', () => {
|
|
10
|
+
assert.equal(wrapAngle(0), 0);
|
|
11
|
+
assert.equal(wrapAngle(Math.PI), Math.PI);
|
|
12
|
+
assert.equal(wrapAngle(-Math.PI), -Math.PI);
|
|
13
|
+
assert.equal(wrapAngle(1.5 * Math.PI), 1.5 * Math.PI);
|
|
14
|
+
assert.equal(wrapAngle(-1.5 * Math.PI), -1.5 * Math.PI);
|
|
15
|
+
});
|
|
16
|
+
test('negative and retrograde angles', () => {
|
|
17
|
+
assertApproxEqual(wrapAngle(-2 * Math.PI), 0);
|
|
18
|
+
assertApproxEqual(wrapAngle(-3 * Math.PI), -Math.PI);
|
|
19
|
+
assertApproxEqual(wrapAngle(-4 * Math.PI), 0);
|
|
20
|
+
assertApproxEqual(wrapAngle(-5 * Math.PI), -Math.PI);
|
|
21
|
+
assertApproxEqual(wrapAngle(-6 * Math.PI), 0);
|
|
22
|
+
});
|
|
23
|
+
test('angles greater than 2π', () => {
|
|
24
|
+
assertApproxEqual(wrapAngle(2 * Math.PI), 0);
|
|
25
|
+
assertApproxEqual(wrapAngle(3 * Math.PI), Math.PI);
|
|
26
|
+
assertApproxEqual(wrapAngle(4 * Math.PI), 0);
|
|
27
|
+
assertApproxEqual(wrapAngle(5 * Math.PI), Math.PI);
|
|
28
|
+
assertApproxEqual(wrapAngle(6 * Math.PI), 0);
|
|
29
|
+
});
|
|
30
|
+
test('extreme values', () => {
|
|
31
|
+
assertApproxEqual(wrapAngle(100 * Math.PI), 0);
|
|
32
|
+
assertApproxEqual(wrapAngle(-100 * Math.PI), 0);
|
|
33
|
+
assertApproxEqual(wrapAngle(1000), wrapAngle(1000 % (2 * Math.PI)));
|
|
34
|
+
assertApproxEqual(wrapAngle(-1000), wrapAngle(-1000 % (2 * Math.PI)));
|
|
35
|
+
});
|
|
36
|
+
test('boundary values', () => {
|
|
37
|
+
assertApproxEqual(wrapAngle(2 * Math.PI), 0);
|
|
38
|
+
assertApproxEqual(wrapAngle(-2 * Math.PI), 0);
|
|
39
|
+
assertApproxEqual(wrapAngle(2 * Math.PI + EPSILON), EPSILON);
|
|
40
|
+
assertApproxEqual(wrapAngle(-2 * Math.PI - EPSILON), -EPSILON);
|
|
41
|
+
});
|
|
42
|
+
test('floating-point drift prevention', () => {
|
|
43
|
+
assertApproxEqual(wrapAngle(Math.PI + EPSILON / 2), Math.PI + EPSILON / 2);
|
|
44
|
+
assertApproxEqual(wrapAngle(-Math.PI - EPSILON / 2), -Math.PI - EPSILON / 2);
|
|
45
|
+
assertApproxEqual(wrapAngle(2 * Math.PI - EPSILON / 2), -EPSILON / 2);
|
|
46
|
+
assertApproxEqual(wrapAngle(-2 * Math.PI + EPSILON / 2), EPSILON / 2);
|
|
47
|
+
});
|
|
48
|
+
test('large positive and negative angles', () => {
|
|
49
|
+
assertApproxEqual(wrapAngle(1e6 * Math.PI), 0);
|
|
50
|
+
assertApproxEqual(wrapAngle(-1e6 * Math.PI), 0);
|
|
51
|
+
assertApproxEqual(wrapAngle(1e9), wrapAngle(1e9 % (2 * Math.PI)));
|
|
52
|
+
assertApproxEqual(wrapAngle(-1e9), wrapAngle(-1e9 % (2 * Math.PI)));
|
|
53
|
+
});
|
|
54
|
+
test('angles near zero', () => {
|
|
55
|
+
assertApproxEqual(wrapAngle(EPSILON), EPSILON);
|
|
56
|
+
assertApproxEqual(wrapAngle(-EPSILON), -EPSILON);
|
|
57
|
+
assertApproxEqual(wrapAngle(0), 0);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=wrap-angle.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap-angle.spec.js","sourceRoot":"","sources":["../../src/__tests__/wrap-angle.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CelestialBodyType, Radians, TimeStepInterface } from '@interstellar-tools/types';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the orbital angle (true anomaly, $ν$) of a celestial body for a given date and time step.
|
|
4
|
+
*
|
|
5
|
+
* **Mathematical Explanation:**
|
|
6
|
+
*
|
|
7
|
+
* This function determines the **true anomaly** ($ν$) based on the time elapsed since the
|
|
8
|
+
* **J2000 epoch** (January 1, 2000) and the provided time step. The calculation follows these steps:
|
|
9
|
+
*
|
|
10
|
+
* **Step 1: Compute Time Elapsed Since J2000**
|
|
11
|
+
*
|
|
12
|
+
* The number of days since **J2000** is computed as:
|
|
13
|
+
* $$ \Delta T = \frac{t - t_{J2000}}{\text{MILLISECONDS\_PER\_DAY}} $$
|
|
14
|
+
* where:
|
|
15
|
+
* - $t$ is the current date in milliseconds.
|
|
16
|
+
* - $t_{J2000}$ is **J2000** (2000-01-01T00:00:00Z).
|
|
17
|
+
* - **MILLISECONDS_PER_DAY** is the number of milliseconds in one day.
|
|
18
|
+
*
|
|
19
|
+
* **Step 2: Compute the Mean Anomaly ($M$)**
|
|
20
|
+
*
|
|
21
|
+
* The **mean anomaly** is calculated as:
|
|
22
|
+
* $$ M = M_0 + n \cdot (\Delta T + \text{timeStep}) $$
|
|
23
|
+
* where:
|
|
24
|
+
* - $M_0$ is the initial mean anomaly at **J2000**.
|
|
25
|
+
* - $n$ is the mean motion (orbital angular velocity).
|
|
26
|
+
* - $\text{timeStep}$ is the time step in days.
|
|
27
|
+
*
|
|
28
|
+
* **Step 3: Solve Kepler’s Equation for Eccentric Anomaly ($E$)**
|
|
29
|
+
*
|
|
30
|
+
* Kepler’s equation:
|
|
31
|
+
* $$ M = E - e \sin(E) $$
|
|
32
|
+
* is solved numerically to obtain the **eccentric anomaly** ($E$).
|
|
33
|
+
*
|
|
34
|
+
* **Step 4: Convert Eccentric Anomaly ($E$) to True Anomaly ($ν$)**
|
|
35
|
+
*
|
|
36
|
+
* Using the relation:
|
|
37
|
+
* $$ ν = 2 \tan^{-1} \left( \sqrt{\frac{1+e}{1-e}} \tan\left(\frac{E}{2}\right) \right) $$
|
|
38
|
+
*
|
|
39
|
+
* **Step 5: Adjust the True Anomaly for Certain Bodies**
|
|
40
|
+
* - For **comets**, the final **true anomaly** is adjusted by subtracting the **eccentricity** ($e$).
|
|
41
|
+
* - For **stars and other celestial objects**, the computed **true anomaly** is used directly.
|
|
42
|
+
*
|
|
43
|
+
* @param {CelestialBodyType} body - The celestial body for which to compute the true anomaly.
|
|
44
|
+
* @param {TimeStepInterface} timeStep - The time step in days since the last frame.
|
|
45
|
+
* @returns {Radians} The computed true anomaly in radians.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { computeAngle } from './compute-angle';
|
|
50
|
+
*
|
|
51
|
+
* const earth: CelestialBodyType = {
|
|
52
|
+
* name: 'Earth',
|
|
53
|
+
* e: 0.0167, // Orbital eccentricity
|
|
54
|
+
* angle: 0, // Initial angle
|
|
55
|
+
* };
|
|
56
|
+
*
|
|
57
|
+
* const timeStep: TimeStepInterface = { days: 1 }; // Time step of 1 day
|
|
58
|
+
* const angle: Radians = computeAngle(earth, timeStep);
|
|
59
|
+
* console.log(angle); // Output: Computed true anomaly in radians
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @see [Kepler's Equation (Wikipedia)](https://en.wikipedia.org/wiki/Kepler%27s_equation)
|
|
63
|
+
* @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
|
|
64
|
+
* @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
|
|
65
|
+
* @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
|
|
66
|
+
* @category Angle
|
|
67
|
+
*/
|
|
68
|
+
export declare const computeAngle: (body: CelestialBodyType, timeStep: TimeStepInterface) => Radians;
|
|
69
|
+
//# sourceMappingURL=compute-angle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-angle.d.ts","sourceRoot":"","sources":["../src/compute-angle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AAMnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,iBAAiB,EACvB,UAAU,iBAAiB,KAC1B,OASF,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { computeMeanAnomaly } from './compute-mean-anomaly';
|
|
2
|
+
import { eccentricToTrueAnomaly } from './eccentric-to-true-anomaly';
|
|
3
|
+
import { solveKepler } from './solve-kepler';
|
|
4
|
+
/**
|
|
5
|
+
* Computes the orbital angle (true anomaly, $ν$) of a celestial body for a given date and time step.
|
|
6
|
+
*
|
|
7
|
+
* **Mathematical Explanation:**
|
|
8
|
+
*
|
|
9
|
+
* This function determines the **true anomaly** ($ν$) based on the time elapsed since the
|
|
10
|
+
* **J2000 epoch** (January 1, 2000) and the provided time step. The calculation follows these steps:
|
|
11
|
+
*
|
|
12
|
+
* **Step 1: Compute Time Elapsed Since J2000**
|
|
13
|
+
*
|
|
14
|
+
* The number of days since **J2000** is computed as:
|
|
15
|
+
* $$ \Delta T = \frac{t - t_{J2000}}{\text{MILLISECONDS\_PER\_DAY}} $$
|
|
16
|
+
* where:
|
|
17
|
+
* - $t$ is the current date in milliseconds.
|
|
18
|
+
* - $t_{J2000}$ is **J2000** (2000-01-01T00:00:00Z).
|
|
19
|
+
* - **MILLISECONDS_PER_DAY** is the number of milliseconds in one day.
|
|
20
|
+
*
|
|
21
|
+
* **Step 2: Compute the Mean Anomaly ($M$)**
|
|
22
|
+
*
|
|
23
|
+
* The **mean anomaly** is calculated as:
|
|
24
|
+
* $$ M = M_0 + n \cdot (\Delta T + \text{timeStep}) $$
|
|
25
|
+
* where:
|
|
26
|
+
* - $M_0$ is the initial mean anomaly at **J2000**.
|
|
27
|
+
* - $n$ is the mean motion (orbital angular velocity).
|
|
28
|
+
* - $\text{timeStep}$ is the time step in days.
|
|
29
|
+
*
|
|
30
|
+
* **Step 3: Solve Kepler’s Equation for Eccentric Anomaly ($E$)**
|
|
31
|
+
*
|
|
32
|
+
* Kepler’s equation:
|
|
33
|
+
* $$ M = E - e \sin(E) $$
|
|
34
|
+
* is solved numerically to obtain the **eccentric anomaly** ($E$).
|
|
35
|
+
*
|
|
36
|
+
* **Step 4: Convert Eccentric Anomaly ($E$) to True Anomaly ($ν$)**
|
|
37
|
+
*
|
|
38
|
+
* Using the relation:
|
|
39
|
+
* $$ ν = 2 \tan^{-1} \left( \sqrt{\frac{1+e}{1-e}} \tan\left(\frac{E}{2}\right) \right) $$
|
|
40
|
+
*
|
|
41
|
+
* **Step 5: Adjust the True Anomaly for Certain Bodies**
|
|
42
|
+
* - For **comets**, the final **true anomaly** is adjusted by subtracting the **eccentricity** ($e$).
|
|
43
|
+
* - For **stars and other celestial objects**, the computed **true anomaly** is used directly.
|
|
44
|
+
*
|
|
45
|
+
* @param {CelestialBodyType} body - The celestial body for which to compute the true anomaly.
|
|
46
|
+
* @param {TimeStepInterface} timeStep - The time step in days since the last frame.
|
|
47
|
+
* @returns {Radians} The computed true anomaly in radians.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { computeAngle } from './compute-angle';
|
|
52
|
+
*
|
|
53
|
+
* const earth: CelestialBodyType = {
|
|
54
|
+
* name: 'Earth',
|
|
55
|
+
* e: 0.0167, // Orbital eccentricity
|
|
56
|
+
* angle: 0, // Initial angle
|
|
57
|
+
* };
|
|
58
|
+
*
|
|
59
|
+
* const timeStep: TimeStepInterface = { days: 1 }; // Time step of 1 day
|
|
60
|
+
* const angle: Radians = computeAngle(earth, timeStep);
|
|
61
|
+
* console.log(angle); // Output: Computed true anomaly in radians
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @see [Kepler's Equation (Wikipedia)](https://en.wikipedia.org/wiki/Kepler%27s_equation)
|
|
65
|
+
* @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
|
|
66
|
+
* @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
|
|
67
|
+
* @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
|
|
68
|
+
* @category Angle
|
|
69
|
+
*/
|
|
70
|
+
export const computeAngle = (body, timeStep) => {
|
|
71
|
+
// Compute mean anomaly (M) with time step
|
|
72
|
+
const M = computeMeanAnomaly(body, timeStep);
|
|
73
|
+
// Solve Kepler's equation for eccentric anomaly (E)
|
|
74
|
+
const E = solveKepler(M, body.e);
|
|
75
|
+
// Convert eccentric anomaly (E) to true anomaly (ν)
|
|
76
|
+
const V = eccentricToTrueAnomaly(E, body.e);
|
|
77
|
+
return V;
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=compute-angle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-angle.js","sourceRoot":"","sources":["../src/compute-angle.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAuB,EACvB,QAA2B,EAClB,EAAE;IACX,0CAA0C;IAC1C,MAAM,CAAC,GAAY,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACtD,oDAAoD;IACpD,MAAM,CAAC,GAAY,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,oDAAoD;IACpD,MAAM,CAAC,GAAY,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAErD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CelestialBodyType, Radians, TemporalInterface } from '@interstellar-tools/types';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the **mean anomaly** ($M$) of a celestial body for a given time step.
|
|
4
|
+
*
|
|
5
|
+
* **Mathematical Explanation:**
|
|
6
|
+
*
|
|
7
|
+
* The **mean anomaly** is a measure of the position of an orbiting body as if it moved
|
|
8
|
+
* with uniform angular motion along a circular orbit with the same period as the actual
|
|
9
|
+
* elliptical orbit. It is computed as:
|
|
10
|
+
*
|
|
11
|
+
* $$ M = M_0 + n \cdot \Delta T $$
|
|
12
|
+
*
|
|
13
|
+
* where:
|
|
14
|
+
* - $M_0$ is the **initial mean anomaly** (converted from true anomaly if necessary).
|
|
15
|
+
* - $n$ is the **mean motion**, given by:
|
|
16
|
+
* $$ n = \frac{2\pi}{P} $$
|
|
17
|
+
* where $P$ is the orbital period in days.
|
|
18
|
+
* - $\Delta T$ is the time step.
|
|
19
|
+
*
|
|
20
|
+
* @param {CelestialBodyType} body - The celestial body for which to compute the mean anomaly.
|
|
21
|
+
* @param {TemporalInterface} timeStep - The time step over which to compute the change.
|
|
22
|
+
* @returns {Radians} The computed mean anomaly in radians.
|
|
23
|
+
*
|
|
24
|
+
* @throws {RangeError} If the body's eccentricity is outside the range $0 \leq e < 1$.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { computeMeanAnomaly } from './compute-mean-anomaly';
|
|
29
|
+
*
|
|
30
|
+
* const mars: CelestialBodyType = {
|
|
31
|
+
* name: 'Mars',
|
|
32
|
+
* e: 0.0934, // Eccentricity of Mars' orbit
|
|
33
|
+
* angle: 1.047, // Initial true anomaly (in radians)
|
|
34
|
+
* period: { value: 687, unit: 'day' }, // Orbital period in days
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* const timeStep: TemporalInterface = { value: 1, unit: 'day' }; // 1-day step
|
|
38
|
+
* const meanAnomaly = computeMeanAnomaly(mars, timeStep);
|
|
39
|
+
* console.log(meanAnomaly); // Output: Computed mean anomaly in radians
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
|
|
43
|
+
* @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
|
|
44
|
+
* @category Anomaly
|
|
45
|
+
*/
|
|
46
|
+
export declare const computeMeanAnomaly: (body: CelestialBodyType, timeStep: TemporalInterface) => Radians;
|
|
47
|
+
//# sourceMappingURL=compute-mean-anomaly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-mean-anomaly.d.ts","sourceRoot":"","sources":["../src/compute-mean-anomaly.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AAkBnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,iBAAiB,EACvB,UAAU,iBAAiB,KAC1B,OAoCF,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { TWO_PI } from '@interstellar-tools/constants';
|
|
2
|
+
import { convertTemporalUnit } from '@interstellar-tools/temporal';
|
|
3
|
+
import { trueAnomalyToMeanAnomaly } from './true-anomaly-to-mean-anomaly';
|
|
4
|
+
import { wrapAngle } from './wrap-angle';
|
|
5
|
+
const EPSILON = 1e-15; // Increased tolerance for high eccentricity
|
|
6
|
+
/**
|
|
7
|
+
* Compares two floating-point numbers for equality within a tolerance.
|
|
8
|
+
*
|
|
9
|
+
* @param {number} a - First number to compare.
|
|
10
|
+
* @param {number} b - Second number to compare.
|
|
11
|
+
* @param {number} [epsilon=EPSILON] - Tolerance for floating-point precision.
|
|
12
|
+
* @returns {boolean} `true` if values are approximately equal.
|
|
13
|
+
*/
|
|
14
|
+
const areEqual = (a, b, epsilon = EPSILON) => {
|
|
15
|
+
return Math.abs(a - b) < epsilon * Math.max(1, Math.abs(a), Math.abs(b));
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Computes the **mean anomaly** ($M$) of a celestial body for a given time step.
|
|
19
|
+
*
|
|
20
|
+
* **Mathematical Explanation:**
|
|
21
|
+
*
|
|
22
|
+
* The **mean anomaly** is a measure of the position of an orbiting body as if it moved
|
|
23
|
+
* with uniform angular motion along a circular orbit with the same period as the actual
|
|
24
|
+
* elliptical orbit. It is computed as:
|
|
25
|
+
*
|
|
26
|
+
* $$ M = M_0 + n \cdot \Delta T $$
|
|
27
|
+
*
|
|
28
|
+
* where:
|
|
29
|
+
* - $M_0$ is the **initial mean anomaly** (converted from true anomaly if necessary).
|
|
30
|
+
* - $n$ is the **mean motion**, given by:
|
|
31
|
+
* $$ n = \frac{2\pi}{P} $$
|
|
32
|
+
* where $P$ is the orbital period in days.
|
|
33
|
+
* - $\Delta T$ is the time step.
|
|
34
|
+
*
|
|
35
|
+
* @param {CelestialBodyType} body - The celestial body for which to compute the mean anomaly.
|
|
36
|
+
* @param {TemporalInterface} timeStep - The time step over which to compute the change.
|
|
37
|
+
* @returns {Radians} The computed mean anomaly in radians.
|
|
38
|
+
*
|
|
39
|
+
* @throws {RangeError} If the body's eccentricity is outside the range $0 \leq e < 1$.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { computeMeanAnomaly } from './compute-mean-anomaly';
|
|
44
|
+
*
|
|
45
|
+
* const mars: CelestialBodyType = {
|
|
46
|
+
* name: 'Mars',
|
|
47
|
+
* e: 0.0934, // Eccentricity of Mars' orbit
|
|
48
|
+
* angle: 1.047, // Initial true anomaly (in radians)
|
|
49
|
+
* period: { value: 687, unit: 'day' }, // Orbital period in days
|
|
50
|
+
* };
|
|
51
|
+
*
|
|
52
|
+
* const timeStep: TemporalInterface = { value: 1, unit: 'day' }; // 1-day step
|
|
53
|
+
* const meanAnomaly = computeMeanAnomaly(mars, timeStep);
|
|
54
|
+
* console.log(meanAnomaly); // Output: Computed mean anomaly in radians
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @see [Mean Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/Mean_anomaly)
|
|
58
|
+
* @see [Orbital Mechanics (NASA)](https://solarsystem.nasa.gov/basics/chapter2-2/)
|
|
59
|
+
* @category Anomaly
|
|
60
|
+
*/
|
|
61
|
+
export const computeMeanAnomaly = (body, timeStep) => {
|
|
62
|
+
const { e, angle, period } = body;
|
|
63
|
+
if (e < 0 || e >= 1) {
|
|
64
|
+
throw new RangeError(`Invalid eccentricity: ${e}. Eccentricity must be in the range [0, 1].`);
|
|
65
|
+
}
|
|
66
|
+
// Convert period to days and compute mean motion
|
|
67
|
+
const periodInDays = convertTemporalUnit(period, 'd').value;
|
|
68
|
+
const meanMotion = TWO_PI / Math.abs(periodInDays);
|
|
69
|
+
// Convert true anomaly (V) to mean anomaly (M0) if necessary
|
|
70
|
+
const M0 = e === 0 ? angle : trueAnomalyToMeanAnomaly(angle, e);
|
|
71
|
+
if (timeStep.value === 0) {
|
|
72
|
+
return M0;
|
|
73
|
+
}
|
|
74
|
+
// Clamping small time steps to prevent unnecessary updates
|
|
75
|
+
const minTimeStep = 1e-5; // Minimum allowed time step
|
|
76
|
+
const maxTimeStepFactor = e >= 0.9 ? 1 : 10; // Stricter limit for highly eccentric orbits
|
|
77
|
+
const clampedDeltaT = Math.max(minTimeStep, Math.min(timeStep.value, maxTimeStepFactor * Math.abs(periodInDays)));
|
|
78
|
+
// Compute new mean anomaly with hysteresis filter and sign preservation
|
|
79
|
+
const prevM = M0;
|
|
80
|
+
let newM = wrapAngle(M0 + meanMotion * clampedDeltaT);
|
|
81
|
+
if (areEqual(newM, prevM)) {
|
|
82
|
+
newM = prevM;
|
|
83
|
+
}
|
|
84
|
+
return newM;
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=compute-mean-anomaly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-mean-anomaly.js","sourceRoot":"","sources":["../src/compute-mean-anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAOnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,4CAA4C;AACnE;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,UAAkB,OAAO,EAAW,EAAE;IAC5E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,IAAuB,EACvB,QAA2B,EAClB,EAAE;IACX,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAElC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAClB,yBAAyB,CAAC,6CAA6C,CACxE,CAAC;IACJ,CAAC;IAED,iDAAiD;IACjD,MAAM,YAAY,GAAG,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC;IAC5D,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACnD,6DAA6D;IAC7D,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,wBAAwB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAEhE,IAAI,QAAQ,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,4BAA4B;IACtD,MAAM,iBAAiB,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,6CAA6C;IAC1F,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAC5B,WAAW,EACX,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CACrE,CAAC;IACF,wEAAwE;IACxE,MAAM,KAAK,GAAG,EAAE,CAAC;IAEjB,IAAI,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,UAAU,GAAG,aAAa,CAAC,CAAC;IAEtD,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,GAAG,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Radians } from '@interstellar-tools/types';
|
|
2
|
+
/**
|
|
3
|
+
* Converts **Eccentric Anomaly** ($E$) to **True Anomaly** ($V$) for an orbit.
|
|
4
|
+
*
|
|
5
|
+
* **Mathematical Explanation:**
|
|
6
|
+
*
|
|
7
|
+
* The **eccentric anomaly** ($E$) and the **true anomaly** ($V$) are related through:
|
|
8
|
+
* $$
|
|
9
|
+
* \tan \frac{V}{2} = \sqrt{\frac{1+e}{1-e}} \tan \frac{E}{2}
|
|
10
|
+
* $$
|
|
11
|
+
* where:
|
|
12
|
+
* - $E$ is the **eccentric anomaly** (in radians).
|
|
13
|
+
* - $V$ is the **true anomaly** (in radians).
|
|
14
|
+
* - $e$ is the **orbital eccentricity** ($0 \leq e < 1$ for elliptical orbits).
|
|
15
|
+
*
|
|
16
|
+
* This function handles:
|
|
17
|
+
* - **Circular orbits** ($e = 0$): True anomaly is equal to eccentric anomaly.
|
|
18
|
+
* - **Parabolic orbits** ($e = 1$): Uses the special case:
|
|
19
|
+
* $$
|
|
20
|
+
* V = 2 \tan^{-1} \left(\frac{E}{2}\right)
|
|
21
|
+
* $$
|
|
22
|
+
* - **Elliptical orbits** ($0 < e < 1$): Uses the standard conversion equation.
|
|
23
|
+
*
|
|
24
|
+
* Additionally, numerical stability is ensured when $E \approx \pi$.
|
|
25
|
+
*
|
|
26
|
+
* @param {Radians} E - Eccentric anomaly ($E$) in radians.
|
|
27
|
+
* @param {number} e - Orbital eccentricity ($0 \leq e < 1$).
|
|
28
|
+
* @returns {Radians} True anomaly ($V$) in radians.
|
|
29
|
+
*
|
|
30
|
+
* @throws {RangeError} If **eccentricity** is out of the range $0 \leq e \leq 1$.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* console.log(eccentricToTrueAnomaly(1.0, 0.5)); // Output: True anomaly in radians
|
|
35
|
+
* console.log(eccentricToTrueAnomaly(Math.PI, 0)); // Output: Math.PI (circular orbit)
|
|
36
|
+
* console.log(eccentricToTrueAnomaly(0, 1)); // Output: 0 (parabolic orbit)
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
|
|
40
|
+
* @category Anomaly
|
|
41
|
+
*/
|
|
42
|
+
export declare const eccentricToTrueAnomaly: (E: Radians, e: number) => Radians;
|
|
43
|
+
//# sourceMappingURL=eccentric-to-true-anomaly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eccentric-to-true-anomaly.d.ts","sourceRoot":"","sources":["../src/eccentric-to-true-anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,sBAAsB,GAAI,GAAG,OAAO,EAAE,GAAG,MAAM,KAAG,OAiC9D,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts **Eccentric Anomaly** ($E$) to **True Anomaly** ($V$) for an orbit.
|
|
3
|
+
*
|
|
4
|
+
* **Mathematical Explanation:**
|
|
5
|
+
*
|
|
6
|
+
* The **eccentric anomaly** ($E$) and the **true anomaly** ($V$) are related through:
|
|
7
|
+
* $$
|
|
8
|
+
* \tan \frac{V}{2} = \sqrt{\frac{1+e}{1-e}} \tan \frac{E}{2}
|
|
9
|
+
* $$
|
|
10
|
+
* where:
|
|
11
|
+
* - $E$ is the **eccentric anomaly** (in radians).
|
|
12
|
+
* - $V$ is the **true anomaly** (in radians).
|
|
13
|
+
* - $e$ is the **orbital eccentricity** ($0 \leq e < 1$ for elliptical orbits).
|
|
14
|
+
*
|
|
15
|
+
* This function handles:
|
|
16
|
+
* - **Circular orbits** ($e = 0$): True anomaly is equal to eccentric anomaly.
|
|
17
|
+
* - **Parabolic orbits** ($e = 1$): Uses the special case:
|
|
18
|
+
* $$
|
|
19
|
+
* V = 2 \tan^{-1} \left(\frac{E}{2}\right)
|
|
20
|
+
* $$
|
|
21
|
+
* - **Elliptical orbits** ($0 < e < 1$): Uses the standard conversion equation.
|
|
22
|
+
*
|
|
23
|
+
* Additionally, numerical stability is ensured when $E \approx \pi$.
|
|
24
|
+
*
|
|
25
|
+
* @param {Radians} E - Eccentric anomaly ($E$) in radians.
|
|
26
|
+
* @param {number} e - Orbital eccentricity ($0 \leq e < 1$).
|
|
27
|
+
* @returns {Radians} True anomaly ($V$) in radians.
|
|
28
|
+
*
|
|
29
|
+
* @throws {RangeError} If **eccentricity** is out of the range $0 \leq e \leq 1$.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* console.log(eccentricToTrueAnomaly(1.0, 0.5)); // Output: True anomaly in radians
|
|
34
|
+
* console.log(eccentricToTrueAnomaly(Math.PI, 0)); // Output: Math.PI (circular orbit)
|
|
35
|
+
* console.log(eccentricToTrueAnomaly(0, 1)); // Output: 0 (parabolic orbit)
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @see [True Anomaly (Wikipedia)](https://en.wikipedia.org/wiki/True_anomaly)
|
|
39
|
+
* @category Anomaly
|
|
40
|
+
*/
|
|
41
|
+
export const eccentricToTrueAnomaly = (E, e) => {
|
|
42
|
+
// Validate eccentricity range
|
|
43
|
+
if (e < 0 || e > 1) {
|
|
44
|
+
throw new RangeError(`Invalid eccentricity: ${e}. Eccentricity must be in the range [0, 1].`);
|
|
45
|
+
}
|
|
46
|
+
// Handle circular orbit (e = 0)
|
|
47
|
+
if (e === 0) {
|
|
48
|
+
return E; // True anomaly equals eccentric anomaly for circular orbits
|
|
49
|
+
}
|
|
50
|
+
// Handle parabolic orbit (e = 1)
|
|
51
|
+
if (e === 1) {
|
|
52
|
+
return 2 * Math.atan(E / 2); // Special formula for parabolic orbits
|
|
53
|
+
}
|
|
54
|
+
// Handle numerical instability near E = π
|
|
55
|
+
const epsilon = 1e-10; // Small threshold for numerical stability
|
|
56
|
+
if (Math.abs(Math.cos(E / 2)) < epsilon) {
|
|
57
|
+
return Math.PI; // True anomaly is π when E = π
|
|
58
|
+
}
|
|
59
|
+
// Convert eccentric anomaly (E) to true anomaly (V) for elliptical orbits
|
|
60
|
+
return (2 *
|
|
61
|
+
Math.atan2(Math.sqrt(1 + e) * Math.sin(E / 2), Math.sqrt(1 - e) * Math.cos(E / 2)));
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=eccentric-to-true-anomaly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eccentric-to-true-anomaly.js","sourceRoot":"","sources":["../src/eccentric-to-true-anomaly.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAU,EAAE,CAAS,EAAW,EAAE;IACvE,8BAA8B;IAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,UAAU,CAClB,yBAAyB,CAAC,6CAA6C,CACxE,CAAC;IACJ,CAAC;IAED,gCAAgC;IAChC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,CAAC,CAAC,4DAA4D;IACxE,CAAC;IAED,iCAAiC;IACjC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,uCAAuC;IACtE,CAAC;IAED,0CAA0C;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,0CAA0C;IAEjE,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,+BAA+B;IACjD,CAAC;IAED,0EAA0E;IAC1E,OAAO,CACL,CAAC;QACD,IAAI,CAAC,KAAK,CACR,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAClC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CACnC,CACF,CAAC;AACJ,CAAC,CAAC"}
|