@saber-usa/node-common 1.7.7-alpha.2 → 1.7.8
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 +2 -1
- package/package.json +2 -3
- package/src/FrameConverter.js +6 -7
- package/src/LaunchNominalClass.js +76 -57
- package/src/OrbitUtils.js +187 -6
- package/src/PropagateUtils.js +9 -9
- package/src/astro.js +71 -57
- package/src/ballisticPropagator.js +1 -1
- package/src/constants.js +1 -3
- package/src/launchNominal.js +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,8 @@ A transformer for object keys is provided. This will take an object and transfor
|
|
|
36
36
|
## Publishing changes
|
|
37
37
|
|
|
38
38
|
1. Change the version in package.json
|
|
39
|
-
2.
|
|
39
|
+
2. Check you are logged in to npm `npm whoami`
|
|
40
|
+
2. Run `npm install` then `npm publish`
|
|
40
41
|
3. Enable GPG in GIT: `git config --global commit.gpgsign true`
|
|
41
42
|
4. Run `gpg --list-keys` to get your <id>
|
|
42
43
|
5. Tell the GIT to use your key `git config user.signingkey <id>`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber-usa/node-common",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "Common node functions for Saber",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"lint:fix": "eslint . --ext js --fix",
|
|
10
10
|
"test": "jest --no-coverage --silent",
|
|
11
11
|
"test:unit": "jest --coverage --runInBand --no-watch",
|
|
12
|
-
"sonar": "node --experimental-vm-modules sonar-project.js"
|
|
13
|
-
"prepub:alpha": "npm version prerelease --preid=alpha && npm publish --tag alpha"
|
|
12
|
+
"sonar": "node --experimental-vm-modules sonar-project.js"
|
|
14
13
|
},
|
|
15
14
|
"files": [
|
|
16
15
|
"src/**/*"
|
package/src/FrameConverter.js
CHANGED
|
@@ -3,7 +3,6 @@ import {Matrix3D, Vector3D} from "pious-squid";
|
|
|
3
3
|
import {LLA} from "./LLA.js";
|
|
4
4
|
import {TimeConverter, RaDec} from "./TimeConverter.js";
|
|
5
5
|
import {norm, cross} from "mathjs";
|
|
6
|
-
import {DEG2RAD} from "./constants.js";
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
// Enums (assuming these exist elsewhere)
|
|
@@ -958,12 +957,12 @@ class FrameConverter {
|
|
|
958
957
|
}
|
|
959
958
|
|
|
960
959
|
static perifocalToInertial(keplerianElements) {
|
|
961
|
-
const cosi = Math.cos(keplerianElements.
|
|
962
|
-
const sini = Math.sin(keplerianElements.
|
|
963
|
-
const cosRaan = Math.cos(keplerianElements.raan
|
|
964
|
-
const sinRaan = Math.sin(keplerianElements.raan
|
|
965
|
-
const cosw = Math.cos(keplerianElements.
|
|
966
|
-
const sinw = Math.sin(keplerianElements.
|
|
960
|
+
const cosi = Math.cos(keplerianElements.inclination);
|
|
961
|
+
const sini = Math.sin(keplerianElements.inclination);
|
|
962
|
+
const cosRaan = Math.cos(keplerianElements.raan);
|
|
963
|
+
const sinRaan = Math.sin(keplerianElements.raan);
|
|
964
|
+
const cosw = Math.cos(keplerianElements.argOfPeriapsis);
|
|
965
|
+
const sinw = Math.sin(keplerianElements.argOfPeriapsis);
|
|
967
966
|
|
|
968
967
|
// The matrix elements
|
|
969
968
|
const r11 = cosRaan * cosw - sinRaan * sinw * cosi;
|
|
@@ -5,14 +5,12 @@ import {WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
|
5
5
|
GRAV_CONST,
|
|
6
6
|
EARTH_MASS,
|
|
7
7
|
MU,
|
|
8
|
-
EARTH_RADIUS_KM
|
|
9
|
-
DEG2RAD} from "./constants.js";
|
|
8
|
+
EARTH_RADIUS_KM} from "./constants.js";
|
|
10
9
|
import {FrameConverter} from "./FrameConverter.js";
|
|
11
10
|
import {OrbitUtils} from "./OrbitUtils.js";
|
|
12
11
|
import {wrapOneRevUnsigned} from "./utils.js";
|
|
13
12
|
import {BallisticPropagator} from "./ballisticPropagator.js";
|
|
14
13
|
import {norm, cross, dot} from "mathjs";
|
|
15
|
-
import {cartesianToKeplerian, keplerianToCartesian} from "./astro.js";
|
|
16
14
|
|
|
17
15
|
// Reference frames enum
|
|
18
16
|
const ReferenceFrame = {
|
|
@@ -22,8 +20,6 @@ const ReferenceFrame = {
|
|
|
22
20
|
GCRF: "GCRF",
|
|
23
21
|
};
|
|
24
22
|
|
|
25
|
-
const mu = GRAV_CONST * EARTH_MASS / 1e9; // km^3/s^2
|
|
26
|
-
|
|
27
23
|
class LaunchNominalClass {
|
|
28
24
|
/** Get position at a given time in specified reference frame
|
|
29
25
|
*
|
|
@@ -58,6 +54,7 @@ class LaunchNominalClass {
|
|
|
58
54
|
let bearingRad = degreesToRadians(bearingDeg);
|
|
59
55
|
bearingRad = wrapOneRevUnsigned(bearingRad);
|
|
60
56
|
|
|
57
|
+
// const latRad = degreesToRadians(this.groundSiteLat);
|
|
61
58
|
const latRad = llaVals.Latitude.Radians;
|
|
62
59
|
const targetInclination = OrbitUtils.getInclination(bearingRad, latRad);
|
|
63
60
|
|
|
@@ -78,7 +75,23 @@ class LaunchNominalClass {
|
|
|
78
75
|
ReferenceFrame.J2000,
|
|
79
76
|
llaVals,
|
|
80
77
|
);
|
|
81
|
-
|
|
78
|
+
// Trying something here
|
|
79
|
+
|
|
80
|
+
// const transformMatrix = FrameConverter.j2000ToTEME(orbitInsertionTime);
|
|
81
|
+
// const initialTemeState = {
|
|
82
|
+
// position: this.transformVector(
|
|
83
|
+
// new Vector3D(initialStateVector.position[0],
|
|
84
|
+
// initialStateVector.position[1], initialStateVector.position[2]),
|
|
85
|
+
// transformMatrix
|
|
86
|
+
// ),
|
|
87
|
+
// velocity: this.transformVector(
|
|
88
|
+
// new Vector3D(initialStateVector.velocity[0],
|
|
89
|
+
// initialStateVector.velocity[1], initialStateVector.velocity[2]),
|
|
90
|
+
// transformMatrix
|
|
91
|
+
// ),
|
|
92
|
+
// };
|
|
93
|
+
|
|
94
|
+
// End of something
|
|
82
95
|
const initialJ2000State = new J2000(
|
|
83
96
|
EpochUTC.fromDateString(orbitInsertionTime.toISOString()),
|
|
84
97
|
new Vector3D(initialStateVector.position[0],
|
|
@@ -91,11 +104,11 @@ class LaunchNominalClass {
|
|
|
91
104
|
|
|
92
105
|
let sat = {
|
|
93
106
|
stateVector: initialStateVector,
|
|
94
|
-
elements:
|
|
107
|
+
elements: OrbitUtils.stateVectorToElements(
|
|
95
108
|
[initialTemeState.position.x, initialTemeState.position.y,
|
|
96
109
|
initialTemeState.position.z],
|
|
97
110
|
[initialTemeState.velocity.x, initialTemeState.velocity.y,
|
|
98
|
-
initialTemeState.velocity.z]
|
|
111
|
+
initialTemeState.velocity.z]),
|
|
99
112
|
epoch: orbitInsertionTime,
|
|
100
113
|
};
|
|
101
114
|
|
|
@@ -137,12 +150,12 @@ class LaunchNominalClass {
|
|
|
137
150
|
|
|
138
151
|
const temeState = propagatedJ2000State.toTEME();
|
|
139
152
|
|
|
140
|
-
// Convert to orbital elements
|
|
141
|
-
const elements =
|
|
153
|
+
// Convert to orbital elements
|
|
154
|
+
const elements = OrbitUtils.stateVectorToElements(
|
|
142
155
|
[temeState.position.x, temeState.position.y,
|
|
143
156
|
temeState.position.z],
|
|
144
157
|
[temeState.velocity.x, temeState.velocity.y,
|
|
145
|
-
temeState.velocity.z]
|
|
158
|
+
temeState.velocity.z]);
|
|
146
159
|
|
|
147
160
|
sat = {
|
|
148
161
|
stateVector: propagated,
|
|
@@ -152,7 +165,7 @@ class LaunchNominalClass {
|
|
|
152
165
|
|
|
153
166
|
// Calculate current inclination
|
|
154
167
|
oldInclinationDelta = inclinationDelta;
|
|
155
|
-
inclinationDelta = Math.abs((sat.elements.
|
|
168
|
+
inclinationDelta = Math.abs(radiansToDegrees(sat.elements.inclination)
|
|
156
169
|
- radiansToDegrees(targetInclination));
|
|
157
170
|
|
|
158
171
|
if (oldInclinationDelta < inclinationDelta) {
|
|
@@ -225,22 +238,22 @@ class LaunchNominalClass {
|
|
|
225
238
|
// Transform to elliptical orbit
|
|
226
239
|
const ellipticalElements = OrbitUtils.transformElliptical(
|
|
227
240
|
svInitial,
|
|
228
|
-
initialNominal.states[0].elements.inc
|
|
229
|
-
initialNominal.states[0].elements.raan
|
|
241
|
+
initialNominal.states[0].elements.inc,
|
|
242
|
+
initialNominal.states[0].elements.raan,
|
|
230
243
|
targetEcc,
|
|
231
|
-
targetArgOfPeriapsisDeg
|
|
244
|
+
degreesToRadians(targetArgOfPeriapsisDeg),
|
|
232
245
|
targetPerigeeKm + WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
233
246
|
);
|
|
234
247
|
|
|
235
248
|
// Convert elliptical elements to cartesian state
|
|
236
|
-
const ellipticalStateVector =
|
|
249
|
+
const ellipticalStateVector = OrbitUtils.elementsToStateVector(ellipticalElements);
|
|
237
250
|
|
|
238
|
-
const ellipticalPos = ellipticalStateVector.
|
|
239
|
-
const ellipticalVel = ellipticalStateVector.
|
|
251
|
+
const ellipticalPos = ellipticalStateVector.position;
|
|
252
|
+
const ellipticalVel = ellipticalStateVector.velocity;
|
|
240
253
|
|
|
241
254
|
const ellipticalState = {
|
|
242
|
-
position: new NodeVector3D(ellipticalPos
|
|
243
|
-
velocity: new NodeVector3D(ellipticalVel
|
|
255
|
+
position: new NodeVector3D(ellipticalPos[0], ellipticalPos[1], ellipticalPos[2]),
|
|
256
|
+
velocity: new NodeVector3D(ellipticalVel[0], ellipticalVel[1], ellipticalVel[2]),
|
|
244
257
|
epochUtc: orbitInsertionTime,
|
|
245
258
|
referenceFrame: "J2000",
|
|
246
259
|
};
|
|
@@ -253,7 +266,7 @@ class LaunchNominalClass {
|
|
|
253
266
|
const ellipNomVel = ellipticalNominal.velocity;
|
|
254
267
|
|
|
255
268
|
// Convert to orbital elements
|
|
256
|
-
const elements =
|
|
269
|
+
const elements = OrbitUtils.stateVectorToElements(
|
|
257
270
|
[ellipNomPos.x, ellipNomPos.y, ellipNomPos.z],
|
|
258
271
|
[ellipNomVel.x, ellipNomVel.y, ellipNomVel.z]);
|
|
259
272
|
|
|
@@ -393,7 +406,7 @@ class LaunchNominalClass {
|
|
|
393
406
|
}
|
|
394
407
|
|
|
395
408
|
static hohmannTransferWithIncZeroing(state0, targetSMA,
|
|
396
|
-
burnAtNodes = 1, mu = MU) {
|
|
409
|
+
burnAtNodes = 1, mu = MU / 1e9) {
|
|
397
410
|
let t0 = state0.epochUtc;
|
|
398
411
|
let i = null;
|
|
399
412
|
|
|
@@ -458,8 +471,8 @@ class LaunchNominalClass {
|
|
|
458
471
|
|
|
459
472
|
const rElementConv = [state0.position.x, state0.position.y, state0.position.z];
|
|
460
473
|
const vElementConv = [state0.velocity.x, state0.velocity.y, state0.velocity.z];
|
|
461
|
-
const elements0 =
|
|
462
|
-
const incDiff = elements0.
|
|
474
|
+
const elements0 = OrbitUtils.stateVectorToElements(rElementConv, vElementConv);
|
|
475
|
+
const incDiff = elements0.inclination;
|
|
463
476
|
|
|
464
477
|
const h = cross([state0.position.x, state0.position.y, state0.position.z],
|
|
465
478
|
[state0.velocity.x, state0.velocity.y, state0.velocity.z]);
|
|
@@ -525,34 +538,41 @@ class LaunchNominalClass {
|
|
|
525
538
|
}
|
|
526
539
|
|
|
527
540
|
static #getNextNodeCrossingGeoOrbit(sv) {
|
|
528
|
-
const geoElements =
|
|
541
|
+
const geoElements = {
|
|
542
|
+
semiMajorAxis: 35786.0 + EARTH_RADIUS_KM,
|
|
543
|
+
eccentricity: 0,
|
|
544
|
+
inclination: 0,
|
|
545
|
+
raan: 0,
|
|
546
|
+
argOfPeriapsis: 0,
|
|
547
|
+
trueAnomaly: 0,
|
|
548
|
+
};
|
|
529
549
|
const geoOrbitState = {
|
|
530
|
-
state:
|
|
550
|
+
state: OrbitUtils.elementsToStateVector(geoElements),
|
|
531
551
|
epochUtc: sv.epochUtc,
|
|
532
552
|
};
|
|
533
553
|
const nextNode = this.#findMutualNodeTimes(sv, geoOrbitState);
|
|
534
554
|
return nextNode.nextNodeTime;
|
|
535
555
|
}
|
|
536
556
|
|
|
537
|
-
static #findMutualNodeTimes(sv1, sv2, muEarth = MU) {
|
|
557
|
+
static #findMutualNodeTimes(sv1, sv2, muEarth = MU / 1e9) {
|
|
538
558
|
// Implementation for finding mutual node times between two state vectors
|
|
539
559
|
|
|
540
560
|
if (!(sv1.epochUtc === sv2.epochUtc)) {
|
|
541
561
|
return "lol what on earth";
|
|
542
562
|
}
|
|
543
563
|
|
|
544
|
-
const orbit1 =
|
|
564
|
+
const orbit1 = OrbitUtils.stateVectorToElements(
|
|
545
565
|
[sv1.position.x, sv1.position.y, sv1.position.z],
|
|
546
|
-
[sv1.velocity.x, sv1.velocity.y, sv1.velocity.z]
|
|
566
|
+
[sv1.velocity.x, sv1.velocity.y, sv1.velocity.z]);
|
|
567
|
+
const orbit2 = OrbitUtils.stateVectorToElements(
|
|
568
|
+
sv2.state.position,
|
|
569
|
+
sv2.state.velocity);
|
|
547
570
|
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
[sv2.state.v.x, sv2.state.v.y, sv2.state.v.z], muEarth);
|
|
571
|
+
const state1 = OrbitUtils.elementsToStateVector(orbit1);
|
|
572
|
+
const state2 = OrbitUtils.elementsToStateVector(orbit2);
|
|
551
573
|
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
const h1 = cross(Object.values(state1.r), Object.values(state1.v));
|
|
555
|
-
const h2 = cross(Object.values(state2.r), Object.values(state2.v));
|
|
574
|
+
const h1 = cross(state1.position, state1.velocity);
|
|
575
|
+
const h2 = cross(state2.position, state2.velocity);
|
|
556
576
|
const h1Norm = norm(h1);
|
|
557
577
|
const h2Norm = norm(h2);
|
|
558
578
|
|
|
@@ -565,8 +585,7 @@ class LaunchNominalClass {
|
|
|
565
585
|
return Math.acos(dotProduct / normsProduct);
|
|
566
586
|
}
|
|
567
587
|
|
|
568
|
-
const ascendingFirst
|
|
569
|
-
= getAngleBetween2Vectors(Object.values(state1.r), h2unit) > Math.PI / 2;
|
|
588
|
+
const ascendingFirst = getAngleBetween2Vectors(state1.position, h2unit) > Math.PI / 2;
|
|
570
589
|
|
|
571
590
|
const nodalLine = cross(h1unit, h2unit);
|
|
572
591
|
const nodalLineUnit = nodalLine.map((component) => component / norm(nodalLine));
|
|
@@ -581,10 +600,10 @@ class LaunchNominalClass {
|
|
|
581
600
|
|
|
582
601
|
const mutualNodeLinePerifocal = rTilde1inverse.multiplyVector3D(nodalLineUnitVector3D);
|
|
583
602
|
const slopeMutualNodalLinePerifocal = mutualNodeLinePerifocal.y / mutualNodeLinePerifocal.x;
|
|
584
|
-
const mutualNode = this.#findIntersectionEllipseLine(
|
|
603
|
+
const mutualNode = this.#findIntersectionEllipseLine( // inputs here are causing an error increase
|
|
585
604
|
slopeMutualNodalLinePerifocal,
|
|
586
|
-
orbit1.
|
|
587
|
-
orbit1.
|
|
605
|
+
orbit1.semiMajorAxis,
|
|
606
|
+
orbit1.eccentricity);
|
|
588
607
|
|
|
589
608
|
const q1 = mutualNode.vec1;
|
|
590
609
|
const q2 = mutualNode.vec2;
|
|
@@ -595,13 +614,13 @@ class LaunchNominalClass {
|
|
|
595
614
|
return ((n % m) + m) % m;
|
|
596
615
|
}
|
|
597
616
|
|
|
598
|
-
const n0 = mod(orbit1.
|
|
617
|
+
const n0 = mod(orbit1.trueAnomaly, 2 * Math.PI);
|
|
599
618
|
const n1 = mod(Math.atan2(dot(q1unit, [0, 1, 0]), dot(q1unit, [1, 0, 0])), 2 * Math.PI);
|
|
600
619
|
const n2 = mod(Math.atan2(dot(q2unit, [0, 1, 0]), dot(q2unit, [1, 0, 0])), 2 * Math.PI);
|
|
601
620
|
|
|
602
|
-
const e0 = OrbitUtils.trueAnomalyToEccentricAnomaly(n0, orbit1.
|
|
603
|
-
let e1 = OrbitUtils.trueAnomalyToEccentricAnomaly(n1, orbit1.
|
|
604
|
-
let e2 = OrbitUtils.trueAnomalyToEccentricAnomaly(n2, orbit1.
|
|
621
|
+
const e0 = OrbitUtils.trueAnomalyToEccentricAnomaly(n0, orbit1.eccentricity);
|
|
622
|
+
let e1 = OrbitUtils.trueAnomalyToEccentricAnomaly(n1, orbit1.eccentricity); // only matching to 2 sig figs w astrolib
|
|
623
|
+
let e2 = OrbitUtils.trueAnomalyToEccentricAnomaly(n2, orbit1.eccentricity); // only matching to 3 sig figs w astrolib
|
|
605
624
|
|
|
606
625
|
if (e1 < 0) {
|
|
607
626
|
e1 += 2 * Math.PI;
|
|
@@ -614,13 +633,13 @@ class LaunchNominalClass {
|
|
|
614
633
|
const k1 = n1 < n0 ? 1 : 0;
|
|
615
634
|
const k2 = n2 < n0 ? 1 : 0;
|
|
616
635
|
|
|
617
|
-
const dt1 = Math.sqrt(Math.pow(orbit1.
|
|
618
|
-
* (2 * k1 * Math.PI + (e1 - orbit1.
|
|
619
|
-
- (e0 - orbit1.
|
|
636
|
+
const dt1 = Math.sqrt(Math.pow(orbit1.semiMajorAxis, 3) / muEarth)
|
|
637
|
+
* (2 * k1 * Math.PI + (e1 - orbit1.eccentricity * Math.sin(e1))
|
|
638
|
+
- (e0 - orbit1.eccentricity * Math.sin(e0)));
|
|
620
639
|
const dt2 = Math.sqrt(
|
|
621
|
-
Math.pow(orbit1.
|
|
622
|
-
* (2 * k2 * Math.PI + (e2 - orbit1.
|
|
623
|
-
- (e0 - orbit1.
|
|
640
|
+
Math.pow(orbit1.semiMajorAxis, 3) / muEarth)
|
|
641
|
+
* (2 * k2 * Math.PI + (e2 - orbit1.eccentricity * Math.sin(e2))
|
|
642
|
+
- (e0 - orbit1.eccentricity * Math.sin(e0)));
|
|
624
643
|
let first = null;
|
|
625
644
|
let second = null;
|
|
626
645
|
|
|
@@ -700,7 +719,7 @@ class LaunchNominalOutput {
|
|
|
700
719
|
const j2000ToTeme = FrameConverter.getTransform("J2000", "TEME", epoch);
|
|
701
720
|
const posTeme = FrameConverter.transformVector(pos, j2000ToTeme);
|
|
702
721
|
const velTeme = FrameConverter.transformVector(vel, j2000ToTeme);
|
|
703
|
-
elements =
|
|
722
|
+
elements = OrbitUtils.stateVectorToElements(
|
|
704
723
|
[posTeme.x, posTeme.y, posTeme.z],
|
|
705
724
|
[velTeme.x, velTeme.y, velTeme.z]);
|
|
706
725
|
}
|
|
@@ -712,12 +731,12 @@ class LaunchNominalOutput {
|
|
|
712
731
|
v: [vel.x, vel.y, vel.z],
|
|
713
732
|
},
|
|
714
733
|
elements: {
|
|
715
|
-
sma: elements.
|
|
716
|
-
ecc: elements.
|
|
717
|
-
inc: elements.
|
|
734
|
+
sma: elements.semiMajorAxis,
|
|
735
|
+
ecc: elements.eccentricity,
|
|
736
|
+
inc: elements.inclination,
|
|
718
737
|
raan: elements.raan,
|
|
719
|
-
argp: elements.
|
|
720
|
-
ta: elements.
|
|
738
|
+
argp: elements.argOfPeriapsis,
|
|
739
|
+
ta: elements.trueAnomaly,
|
|
721
740
|
},
|
|
722
741
|
// Placeholder for tle
|
|
723
742
|
});
|
package/src/OrbitUtils.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {MU
|
|
1
|
+
import {MU} from "./constants.js";
|
|
2
2
|
import {cross, norm} from "mathjs";
|
|
3
3
|
import {wrapHalfRevUnsigned, wrapOneRevUnsigned} from "./utils.js";
|
|
4
4
|
|
|
5
|
-
const EARTH_MU_KM = MU
|
|
5
|
+
const EARTH_MU_KM = MU / 1e9;
|
|
6
6
|
|
|
7
7
|
class OrbitUtils {
|
|
8
|
+
constructor() {
|
|
9
|
+
// Prevent instantiation
|
|
10
|
+
}
|
|
8
11
|
/**
|
|
9
12
|
* Get inclination based on azimuth and latitude.
|
|
10
13
|
* @param {number} azimuthRad - Azimuth in radians
|
|
@@ -138,8 +141,15 @@ class OrbitUtils {
|
|
|
138
141
|
if (Math.abs(b - a) < tolerance) {
|
|
139
142
|
// Found minimum
|
|
140
143
|
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
141
|
-
return
|
|
142
|
-
|
|
144
|
+
return {
|
|
145
|
+
semiMajorAxis: semiMajorAxis,
|
|
146
|
+
eccentricity: targetEcc,
|
|
147
|
+
inclination: incRad,
|
|
148
|
+
raan: raanRad,
|
|
149
|
+
argOfPeriapsis: targetArgOfPeriapsisRad,
|
|
150
|
+
trueAnomaly: c,
|
|
151
|
+
epoch: svInitial.epoch,
|
|
152
|
+
};
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
xm = 0.5 * (a + b);
|
|
@@ -150,8 +160,15 @@ class OrbitUtils {
|
|
|
150
160
|
if (min1 < tol1 || min2 < tol1) {
|
|
151
161
|
// Found minimum
|
|
152
162
|
const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
|
|
153
|
-
return
|
|
154
|
-
|
|
163
|
+
return {
|
|
164
|
+
semiMajorAxis: semiMajorAxis,
|
|
165
|
+
eccentricity: targetEcc,
|
|
166
|
+
inclination: incRad,
|
|
167
|
+
raan: raanRad,
|
|
168
|
+
argOfPeriapsis: targetArgOfPeriapsisRad,
|
|
169
|
+
trueAnomaly: c,
|
|
170
|
+
epoch: svInitial.epoch,
|
|
171
|
+
};
|
|
155
172
|
}
|
|
156
173
|
|
|
157
174
|
// Construct a trial parabolic fit
|
|
@@ -254,6 +271,170 @@ class OrbitUtils {
|
|
|
254
271
|
return {position: r, velocity: v};
|
|
255
272
|
}
|
|
256
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Convert state vector (position, velocity) to classical orbital elements
|
|
276
|
+
* @param {Array} r - Position vector [x, y, z] in km
|
|
277
|
+
* @param {Array} v - Velocity vector [vx, vy, vz] in km/s
|
|
278
|
+
* @param {number} mu - Standard gravitational parameter (default: Earth)
|
|
279
|
+
* @return {Object} Orbital elements
|
|
280
|
+
*/
|
|
281
|
+
static stateVectorToElements(r, v, mu = EARTH_MU_KM) {
|
|
282
|
+
const tol = 1e-9;
|
|
283
|
+
|
|
284
|
+
if (mu < 1e-30) {
|
|
285
|
+
throw new Error("Mu must be greater than 1e-30.");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Helper functions
|
|
289
|
+
function cross(a, b) {
|
|
290
|
+
return [
|
|
291
|
+
a[1] * b[2] - a[2] * b[1],
|
|
292
|
+
a[2] * b[0] - a[0] * b[2],
|
|
293
|
+
a[0] * b[1] - a[1] * b[0],
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function dot(a, b) {
|
|
298
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function norm(v) {
|
|
302
|
+
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function clamp(value, min, max) {
|
|
306
|
+
return Math.min(Math.max(value, min), max);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Calculate basic vectors
|
|
310
|
+
const h = cross(r, v);
|
|
311
|
+
const n = cross([0, 0, 1], h);
|
|
312
|
+
|
|
313
|
+
const rLength = norm(r);
|
|
314
|
+
const vLength = norm(v);
|
|
315
|
+
|
|
316
|
+
if (rLength === 0) throw new Error("Position vector must not be zero.");
|
|
317
|
+
if (vLength === 0) throw new Error("Velocity vector must not be zero.");
|
|
318
|
+
|
|
319
|
+
// Eccentricity vector calculation (corrected formula)
|
|
320
|
+
const vLengthSq = vLength * vLength;
|
|
321
|
+
const muOverR = mu / rLength;
|
|
322
|
+
const rvDot = dot(r, v);
|
|
323
|
+
|
|
324
|
+
const e = [
|
|
325
|
+
(1 / mu) * ((vLengthSq - muOverR) * r[0] - rvDot * v[0]),
|
|
326
|
+
(1 / mu) * ((vLengthSq - muOverR) * r[1] - rvDot * v[1]),
|
|
327
|
+
(1 / mu) * ((vLengthSq - muOverR) * r[2] - rvDot * v[2]),
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
const zeta = 0.5 * vLengthSq - muOverR;
|
|
331
|
+
|
|
332
|
+
if (zeta === 0) throw new Error("Zeta cannot be zero.");
|
|
333
|
+
|
|
334
|
+
const eLength = norm(e);
|
|
335
|
+
if (Math.abs(1.0 - eLength) <= tol) {
|
|
336
|
+
throw new Error("Parabolic orbit conversion is not supported.");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const a = -mu / (zeta * 2);
|
|
340
|
+
|
|
341
|
+
if (Math.abs(a * (1 - eLength)) < 1e-3) {
|
|
342
|
+
throw new Error("The state results in a singular conic section "
|
|
343
|
+
+ "with radius of periapsis less than 1 m.");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const hLength = norm(h);
|
|
347
|
+
if (hLength === 0) {
|
|
348
|
+
throw new Error(`Cannot convert from Cartesian to Keplerian
|
|
349
|
+
- angular momentum is zero.`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const i = Math.acos(h[2] / hLength);
|
|
353
|
+
|
|
354
|
+
if (i >= Math.PI - tol) {
|
|
355
|
+
throw new Error("Cannot convert orbit with inclination of 180 degrees.");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let raan = 0;
|
|
359
|
+
let w = 0;
|
|
360
|
+
let f = 0;
|
|
361
|
+
|
|
362
|
+
const nLength = norm(n);
|
|
363
|
+
|
|
364
|
+
// CASE 1: Non-circular, Inclined Orbit
|
|
365
|
+
if (eLength >= 1e-11 && i >= 1e-11 && i <= Math.PI - 1e-11) {
|
|
366
|
+
if (nLength === 0.0) {
|
|
367
|
+
throw new Error("Cannot convert from Cartesian to Keplerian "
|
|
368
|
+
+ "- line-of-nodes vector is a zero vector.");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
raan = Math.acos(n[0] / nLength);
|
|
372
|
+
if (n[1] < 0) raan = 2 * Math.PI - raan;
|
|
373
|
+
|
|
374
|
+
w = Math.acos(dot(n, e) / (nLength * eLength));
|
|
375
|
+
if (e[2] < 0) w = 2 * Math.PI - w;
|
|
376
|
+
|
|
377
|
+
f = Math.acos(clamp(dot(e, r) / (eLength * rLength), -1, 1));
|
|
378
|
+
if (rvDot < 0) f = 2 * Math.PI - f;
|
|
379
|
+
} else if (eLength >= 1e-11 && (i < 1e-11 || i > Math.PI - 1e-11)) { // CASE 2: Non-circular, Equatorial Orbit
|
|
380
|
+
if (eLength === 0.0) {
|
|
381
|
+
throw new Error(`Cannot convert from Cartesian to Keplerian
|
|
382
|
+
- eccentricity is zero.`);
|
|
383
|
+
}
|
|
384
|
+
raan = 0;
|
|
385
|
+
w = Math.acos(e[0] / eLength);
|
|
386
|
+
if (e[1] < 0) w = 2 * Math.PI - w;
|
|
387
|
+
|
|
388
|
+
// For GMT-4446 fix (LOJ: 2014.03.21)
|
|
389
|
+
if (i > Math.PI - 1e-11) w *= -1.0;
|
|
390
|
+
if (w < 0.0) w += 2 * Math.PI;
|
|
391
|
+
|
|
392
|
+
f = Math.acos(clamp(dot(e, r) / (eLength * rLength), -1, 1));
|
|
393
|
+
if (rvDot < 0) f = 2 * Math.PI - f;
|
|
394
|
+
} else if (eLength < 1e-11 && i >= 1e-11 && i <= Math.PI - 1e-11) { // CASE 3: Circular, Inclined Orbit
|
|
395
|
+
if (nLength === 0.0) {
|
|
396
|
+
throw new Error("Cannot convert from Cartesian to Keplerian "
|
|
397
|
+
+ "- line-of-nodes vector is a zero vector.");
|
|
398
|
+
}
|
|
399
|
+
raan = Math.acos(n[0] / nLength);
|
|
400
|
+
if (n[1] < 0) raan = 2 * Math.PI - raan;
|
|
401
|
+
|
|
402
|
+
w = 0;
|
|
403
|
+
|
|
404
|
+
f = Math.acos(clamp(dot(n, r) / (nLength * rLength), -1, 1));
|
|
405
|
+
if (r[2] < 0) f = 2 * Math.PI - f;
|
|
406
|
+
} else if (eLength < 1e-11 && (i < 1e-11 || i > Math.PI - 1e-11)) { // CASE 4: Circular, Equatorial Orbit
|
|
407
|
+
raan = 0;
|
|
408
|
+
w = 0;
|
|
409
|
+
f = Math.acos(clamp(r[0] / rLength, -1, 1));
|
|
410
|
+
if (r[1] < 0) f = 2 * Math.PI - f;
|
|
411
|
+
|
|
412
|
+
// For GMT-4446 fix (LOJ: 2014.03.21)
|
|
413
|
+
if (i > Math.PI - 1e-11) f *= -1.0;
|
|
414
|
+
if (f < 0.0) f += 2 * Math.PI;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Calculate additional orbital parameters
|
|
418
|
+
const period = 2 * Math.PI * Math.sqrt(Math.pow(Math.abs(a), 3) / mu);
|
|
419
|
+
const meanMotion = 2 * Math.PI / period;
|
|
420
|
+
|
|
421
|
+
// Convert true anomaly to mean anomaly properly
|
|
422
|
+
const eccentricAnomaly = this.trueAnomalyToEccentricAnomaly(f, eLength);
|
|
423
|
+
const meanAnomaly = this.eccentricAnomalyToMeanAnomaly(eccentricAnomaly, eLength);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
semiMajorAxis: a, // km
|
|
427
|
+
eccentricity: eLength, // dimensionless
|
|
428
|
+
inclination: i, // radians
|
|
429
|
+
raan: raan, // radians (Right Ascension of Ascending Node)
|
|
430
|
+
argOfPeriapsis: w, // radians (Argument of Periapsis)
|
|
431
|
+
trueAnomaly: f, // radians
|
|
432
|
+
meanAnomaly: meanAnomaly, // radians (simplified)
|
|
433
|
+
period: period, // seconds
|
|
434
|
+
meanMotion: meanMotion, // rad/s
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
257
438
|
/**
|
|
258
439
|
* Convert true anomaly to eccentric anomaly
|
|
259
440
|
* @param {number} nu - true anomaly
|
package/src/PropagateUtils.js
CHANGED
|
@@ -6,8 +6,8 @@ import {RungeKutta4Propagator,
|
|
|
6
6
|
ClassicalElements} from "pious-squid";
|
|
7
7
|
import {sgp4} from "satellite.js";
|
|
8
8
|
import {NodeVector3D} from "./NodeVector3D.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import {checkTle} from "./astro.js";
|
|
10
|
+
import {OrbitUtils} from "./OrbitUtils.js";
|
|
11
11
|
|
|
12
12
|
const ReferenceFrame = {
|
|
13
13
|
ITRF: "ITRF",
|
|
@@ -62,17 +62,17 @@ class PropagateUtils {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
static keplerianPropagator(initialState, time) {
|
|
65
|
-
const
|
|
65
|
+
const elementsObj = OrbitUtils.stateVectorToElements(
|
|
66
66
|
initialState.position, initialState.velocity);
|
|
67
67
|
|
|
68
68
|
const elements = new ClassicalElements(
|
|
69
69
|
new EpochUTC(initialState.epoch),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
elementsObj.semiMajorAxis,
|
|
71
|
+
elementsObj.eccentricity,
|
|
72
|
+
elementsObj.inclination,
|
|
73
|
+
elementsObj.raan,
|
|
74
|
+
elementsObj.argOfPeriapsis,
|
|
75
|
+
elementsObj.trueAnomaly,
|
|
76
76
|
);
|
|
77
77
|
|
|
78
78
|
// Now pass to KeplerPropagator:
|
package/src/astro.js
CHANGED
|
@@ -43,9 +43,6 @@ import {DEG2RAD,
|
|
|
43
43
|
WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
44
44
|
MILLIS_PER_DAY,
|
|
45
45
|
GEO_ALTITUDE_KM,
|
|
46
|
-
MU,
|
|
47
|
-
MU_GRS80,
|
|
48
|
-
MU_SI,
|
|
49
46
|
ERROR_CODES} from "./constants.js";
|
|
50
47
|
|
|
51
48
|
// Solar Terminator
|
|
@@ -457,19 +454,25 @@ const angleBetween3DCoords = (coord1, coord2, coord3) => {
|
|
|
457
454
|
* which uses the In-track,Cross-Track axes to instantiate it.
|
|
458
455
|
* @param {Object} pv1
|
|
459
456
|
* @param {Object} pv2
|
|
460
|
-
* @return {Object} Delta-v in
|
|
457
|
+
* @return {Object} Delta-v in m/s in RSW frame to perform a single impulsive
|
|
461
458
|
* plane match maneuver of sat1 to sat2.
|
|
462
459
|
*/
|
|
463
460
|
const planeChangeDeltaV = (pv1, pv2) => {
|
|
461
|
+
const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
|
|
462
|
+
|
|
464
463
|
// 1. Compute the angular momentum of each orbit from the pos vel vectors
|
|
465
|
-
const r = [pv1.position.x, pv1.position.y, pv1.position.z]
|
|
466
|
-
|
|
464
|
+
const r = multiply([pv1.position.x, pv1.position.y, pv1.position.z],
|
|
465
|
+
1000.0);
|
|
466
|
+
const v = multiply([pv1.velocity.x, pv1.velocity.y, pv1.velocity.z],
|
|
467
|
+
1000.0);
|
|
467
468
|
const h1 = cross(r, v);
|
|
468
469
|
|
|
469
470
|
const vMag = norm(v);
|
|
470
471
|
|
|
471
|
-
const r2 = [pv2.position.x, pv2.position.y, pv2.position.z]
|
|
472
|
-
|
|
472
|
+
const r2 = multiply([pv2.position.x, pv2.position.y, pv2.position.z],
|
|
473
|
+
1000.0);
|
|
474
|
+
const v2 = multiply([pv2.velocity.x, pv2.velocity.y, pv2.velocity.z],
|
|
475
|
+
1000.0);
|
|
473
476
|
const h2 = cross(r2, v2);
|
|
474
477
|
|
|
475
478
|
// 2. Compute the mutual line of nodes vector
|
|
@@ -477,9 +480,9 @@ const planeChangeDeltaV = (pv1, pv2) => {
|
|
|
477
480
|
|
|
478
481
|
// 3. Compute the eccentricity vector of sat1
|
|
479
482
|
const e = multiply(
|
|
480
|
-
(1 /
|
|
483
|
+
(1 / mu),
|
|
481
484
|
subtract(
|
|
482
|
-
(multiply(Math.pow(norm(v, 2), 2) - (
|
|
485
|
+
(multiply(Math.pow(norm(v, 2), 2) - (mu / norm(r, 2)), r)),
|
|
483
486
|
multiply(dot(r, v), v)),
|
|
484
487
|
);
|
|
485
488
|
|
|
@@ -570,21 +573,21 @@ const planeChangeDeltaV = (pv1, pv2) => {
|
|
|
570
573
|
*
|
|
571
574
|
* @param {Object} pv1 Position and Velocity Vector of Satelltie 1 at Time = x
|
|
572
575
|
* @param {Object} pv2 Position and velocity Vector of Satellite 2 at Time = x
|
|
573
|
-
* @return {Object} Delta-v in
|
|
576
|
+
* @return {Object} Delta-v in m/s to perform a pure inclination plane change at
|
|
574
577
|
* the sat1 asc or desc node.
|
|
575
578
|
*/
|
|
576
579
|
const planeChangePureInclinationDeltaV = (pv1, pv2)=>{
|
|
577
580
|
// 1. Get position and velocity vectors and magnitudes.
|
|
578
581
|
// Note that the magnitude of the final velocity for sat1 will be EQUAL
|
|
579
582
|
// to this initial velocity magnitude of sat1!
|
|
580
|
-
const r = [pv1.position.x, pv1.position.y, pv1.position.z];
|
|
581
|
-
const v = [pv1.velocity.x, pv1.velocity.y, pv1.velocity.z];
|
|
583
|
+
const r = multiply([pv1.position.x, pv1.position.y, pv1.position.z], 1000.0);
|
|
584
|
+
const v = multiply([pv1.velocity.x, pv1.velocity.y, pv1.velocity.z], 1000.0);
|
|
582
585
|
|
|
583
|
-
// Velocity Magnitude in
|
|
586
|
+
// Velocity Magnitude in m/s
|
|
584
587
|
const vMag = norm(v);
|
|
585
588
|
|
|
586
|
-
const r2 = [pv2.position.x, pv2.position.y, pv2.position.z];
|
|
587
|
-
const v2 = [pv2.velocity.x, pv2.velocity.y, pv2.velocity.z];
|
|
589
|
+
const r2 = multiply([pv2.position.x, pv2.position.y, pv2.position.z], 1000.0);
|
|
590
|
+
const v2 = multiply([pv2.velocity.x, pv2.velocity.y, pv2.velocity.z], 1000.0);
|
|
588
591
|
|
|
589
592
|
// 2. Compute the Keplerian Elements of both satellites.
|
|
590
593
|
const el = cartesianToKeplerian(r, v);
|
|
@@ -1401,12 +1404,12 @@ const GetElsetUdlFromTle = (
|
|
|
1401
1404
|
* @param {Number} lon1 The longitude of the first coordinate in degrees
|
|
1402
1405
|
* @param {Number} lat2 The latitude of the second coordinate in degrees
|
|
1403
1406
|
* @param {Number} lon2 The longitude of the second coordinate in degrees
|
|
1404
|
-
* @return {Number} The distance between the two coordinates in
|
|
1407
|
+
* @return {Number} The distance between the two coordinates in meters
|
|
1405
1408
|
*
|
|
1406
1409
|
* Source: https://www.movable-type.co.uk/scripts/latlong.html
|
|
1407
1410
|
*/
|
|
1408
1411
|
const distGeodetic = (lat1, lon1, lat2, lon2) => {
|
|
1409
|
-
const R = WGS84_EARTH_EQUATORIAL_RADIUS_KM; //
|
|
1412
|
+
const R = WGS84_EARTH_EQUATORIAL_RADIUS_KM * 1000.0; // metres
|
|
1410
1413
|
const phi1 = lat1 * DEG2RAD; // φ1 in formula
|
|
1411
1414
|
const phi2 = lat2 * DEG2RAD; // φ2 in formula
|
|
1412
1415
|
const deltaPhi = (lat2 - lat1) * DEG2RAD; // Δφ in formula
|
|
@@ -1419,7 +1422,7 @@ const distGeodetic = (lat1, lon1, lat2, lon2) => {
|
|
|
1419
1422
|
* Math.sin(deltaLambda / 2);
|
|
1420
1423
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
1421
1424
|
|
|
1422
|
-
const d = R * c; // in
|
|
1425
|
+
const d = R * c; // in metres
|
|
1423
1426
|
|
|
1424
1427
|
return d;
|
|
1425
1428
|
};
|
|
@@ -1432,13 +1435,13 @@ const distGeodetic = (lat1, lon1, lat2, lon2) => {
|
|
|
1432
1435
|
* NOTE 2: raan, argument of periapsis, and true anomaly are ALL in degrees!
|
|
1433
1436
|
* NOTE 3: SMA (a) is in km
|
|
1434
1437
|
*
|
|
1435
|
-
* @param {Array} r Position, in
|
|
1436
|
-
* @param {Array} v Velocity, in
|
|
1437
|
-
* @param {Number} mu Gravitational Parameter, defaults to Earth's mu in km^3/s^2
|
|
1438
|
+
* @param {Array} r Position, in meters
|
|
1439
|
+
* @param {Array} v Velocity, in m/s
|
|
1438
1440
|
* @return {Object} An object containing the Keplerian Elements, if successful. Otherwise, an empty object.
|
|
1439
1441
|
*/
|
|
1440
|
-
const cartesianToKeplerian = (r, v
|
|
1442
|
+
const cartesianToKeplerian = (r, v) => {
|
|
1441
1443
|
try {
|
|
1444
|
+
const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
|
|
1442
1445
|
const tol = 1e-9;
|
|
1443
1446
|
|
|
1444
1447
|
const h = cross(r, v);
|
|
@@ -1449,20 +1452,20 @@ const cartesianToKeplerian = (r, v, mu = MU) => {
|
|
|
1449
1452
|
if (norm(v, 2) === 0) throw new Error("Velocity vector must not be zero.");
|
|
1450
1453
|
|
|
1451
1454
|
const e = multiply(
|
|
1452
|
-
(1 /
|
|
1455
|
+
(1 / mu),
|
|
1453
1456
|
subtract(
|
|
1454
|
-
(multiply(Math.pow(norm(v, 2), 2) - (
|
|
1457
|
+
(multiply(Math.pow(norm(v, 2), 2) - (mu / norm(r, 2)), r)),
|
|
1455
1458
|
multiply(dot(r, v), v)),
|
|
1456
1459
|
);
|
|
1457
1460
|
|
|
1458
|
-
const zeta = 0.5 * Math.pow(norm(v), 2) - (
|
|
1461
|
+
const zeta = 0.5 * Math.pow(norm(v), 2) - (mu / norm(r));
|
|
1459
1462
|
|
|
1460
1463
|
if (zeta === 0) throw new Error("Zeta cannot be zero.");
|
|
1461
1464
|
if (Math.abs(1.0 - norm(e)) <= tol) {
|
|
1462
1465
|
throw new Error("Parabolic orbit conversion is not supported.");
|
|
1463
1466
|
}
|
|
1464
1467
|
|
|
1465
|
-
const a = -
|
|
1468
|
+
const a = -mu / zeta / 2;
|
|
1466
1469
|
|
|
1467
1470
|
if (Math.abs(a * (1 - norm(e))) < 1e-3) {
|
|
1468
1471
|
throw new Error(`The state results in a singular conic section with
|
|
@@ -1552,7 +1555,7 @@ const cartesianToKeplerian = (r, v, mu = MU) => {
|
|
|
1552
1555
|
}
|
|
1553
1556
|
|
|
1554
1557
|
return {
|
|
1555
|
-
a: a, // km
|
|
1558
|
+
a: a /1000.0, // km
|
|
1556
1559
|
e: norm(e),
|
|
1557
1560
|
i: i * RAD2DEG, // deg
|
|
1558
1561
|
raan: raan * RAD2DEG, // deg
|
|
@@ -1575,9 +1578,9 @@ const cartesianToKeplerian = (r, v, mu = MU) => {
|
|
|
1575
1578
|
*
|
|
1576
1579
|
* @param {Array} elset Keplerian elements
|
|
1577
1580
|
* @param {Number} mu The gravitational parameter for the celestial object that we orbit, in km based units
|
|
1578
|
-
* @return {Object} An object containing the position and velocity in *
|
|
1581
|
+
* @return {Object} An object containing the position and velocity in *meter* based units
|
|
1579
1582
|
*/
|
|
1580
|
-
const keplerianToCartesian = (elset, mu =
|
|
1583
|
+
const keplerianToCartesian = (elset, mu = 398600.4418) => {
|
|
1581
1584
|
const INFINITE_TOL = 1e-10;
|
|
1582
1585
|
const ORBIT_TOL = 1e-10;
|
|
1583
1586
|
try {
|
|
@@ -1613,17 +1616,17 @@ const keplerianToCartesian = (elset, mu = MU) => {
|
|
|
1613
1616
|
const sinPer = Math.sin(w);
|
|
1614
1617
|
|
|
1615
1618
|
const r = {
|
|
1616
|
-
x: rad * (cosPerAnom * cosRaan - cosInc * sinPerAnom * sinRaan),
|
|
1617
|
-
y: rad * (cosPerAnom * sinRaan + cosInc * sinPerAnom * cosRaan),
|
|
1618
|
-
z: rad * sinPerAnom * sinInc,
|
|
1619
|
+
x: 1000.0 * rad * (cosPerAnom * cosRaan - cosInc * sinPerAnom * sinRaan),
|
|
1620
|
+
y: 1000.0 * rad * (cosPerAnom * sinRaan + cosInc * sinPerAnom * cosRaan),
|
|
1621
|
+
z: 1000.0 * rad * sinPerAnom * sinInc,
|
|
1619
1622
|
};
|
|
1620
1623
|
|
|
1621
1624
|
const v = {
|
|
1622
|
-
x: sqrtGravP * cosAnomPlusE * (-sinPer * cosRaan - cosInc * sinRaan * cosPer)
|
|
1623
|
-
- sqrtGravP * sinAnom * (cosPer * cosRaan - cosInc * sinRaan * sinPer),
|
|
1624
|
-
y: sqrtGravP * cosAnomPlusE * (-sinPer * sinRaan + cosInc * cosRaan * cosPer)
|
|
1625
|
-
- sqrtGravP * sinAnom * (cosPer * sinRaan + cosInc * cosRaan * sinPer),
|
|
1626
|
-
z: sqrtGravP * (cosAnomPlusE * sinInc * cosPer - sinAnom * sinInc * sinPer),
|
|
1625
|
+
x: 1000.0 * (sqrtGravP * cosAnomPlusE * (-sinPer * cosRaan - cosInc * sinRaan * cosPer)
|
|
1626
|
+
- sqrtGravP * sinAnom * (cosPer * cosRaan - cosInc * sinRaan * sinPer)),
|
|
1627
|
+
y: 1000.0 * (sqrtGravP * cosAnomPlusE * (-sinPer * sinRaan + cosInc * cosRaan * cosPer)
|
|
1628
|
+
- sqrtGravP * sinAnom * (cosPer * sinRaan + cosInc * cosRaan * sinPer)),
|
|
1629
|
+
z: 1000.0 * (sqrtGravP * (cosAnomPlusE * sinInc * cosPer - sinAnom * sinInc * sinPer)),
|
|
1627
1630
|
};
|
|
1628
1631
|
|
|
1629
1632
|
return {r, v};
|
|
@@ -1635,8 +1638,8 @@ const keplerianToCartesian = (elset, mu = MU) => {
|
|
|
1635
1638
|
const cartesianToElsetElements = (pv, epoch) => {
|
|
1636
1639
|
// sma, eccentricity, inclination, raan, argp, trueAnomaly
|
|
1637
1640
|
const kepl = cartesianToKeplerian(
|
|
1638
|
-
posToArray(pv.position), // km
|
|
1639
|
-
posToArray(pv.velocity), // km/s
|
|
1641
|
+
multiply(posToArray(pv.position), 1000.0), // km to m
|
|
1642
|
+
multiply(posToArray(pv.velocity), 1000.0), // km/s to m/s
|
|
1640
1643
|
);
|
|
1641
1644
|
|
|
1642
1645
|
const elset = {
|
|
@@ -1648,7 +1651,8 @@ const cartesianToElsetElements = (pv, epoch) => {
|
|
|
1648
1651
|
};
|
|
1649
1652
|
|
|
1650
1653
|
// Mean motion in radians per second
|
|
1651
|
-
const
|
|
1654
|
+
const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
|
|
1655
|
+
const meanMotion = Math.sqrt(mu/Math.pow((elset.SemiMajorAxis*1000.0), 3));
|
|
1652
1656
|
|
|
1653
1657
|
elset.MeanMotion = meanMotion / (2*Math.PI) * 60 * 60 * 24; // rads/s to revs per day
|
|
1654
1658
|
|
|
@@ -1699,6 +1703,7 @@ const getLeoRpoData = (line1, line2, sats, startTime, endTime) => {
|
|
|
1699
1703
|
Line2: line2,
|
|
1700
1704
|
};
|
|
1701
1705
|
const pEphem = prop(pElset, start, end, 10000);
|
|
1706
|
+
if(pEphem.length === 0) return results; // Primary may have re-entered the atmosphere
|
|
1702
1707
|
|
|
1703
1708
|
sats.forEach( (s) => {
|
|
1704
1709
|
const sEphem = prop(s, start, end, 10000);
|
|
@@ -1743,8 +1748,8 @@ const getLeoRpoData = (line1, line2, sats, startTime, endTime) => {
|
|
|
1743
1748
|
aResult.di = angleBetweenPlanes(pv1, pv2);
|
|
1744
1749
|
aResult.dv = planeChangeDeltaV(pv1, pv2);
|
|
1745
1750
|
aResult.dv = {
|
|
1746
|
-
i: Math.round(aResult.dv.i*
|
|
1747
|
-
c: Math.round(aResult.dv.c*
|
|
1751
|
+
i: Math.round(aResult.dv.i*1000)/1000,
|
|
1752
|
+
c: Math.round(aResult.dv.c*1000)/1000,
|
|
1748
1753
|
}; // Round to 3 decimals
|
|
1749
1754
|
|
|
1750
1755
|
// Find the distance at each time step
|
|
@@ -1783,7 +1788,8 @@ const getGeoRpoData = (line1, line2, sats, startTime, endTime, lonTime) => {
|
|
|
1783
1788
|
Line1: line1,
|
|
1784
1789
|
Line2: line2,
|
|
1785
1790
|
}, start, end, 60000);
|
|
1786
|
-
|
|
1791
|
+
if(pEphem.length === 0) return results; // Primary may have re-entered the atmosphere
|
|
1792
|
+
|
|
1787
1793
|
const lonEvalTime = lonTime ? new Date(lonTime) : new Date(end);
|
|
1788
1794
|
|
|
1789
1795
|
const pLonAndDrift = getLonAndDrift(line1, line2, lonEvalTime);
|
|
@@ -1857,8 +1863,8 @@ const getGeoRpoData = (line1, line2, sats, startTime, endTime, lonTime) => {
|
|
|
1857
1863
|
};
|
|
1858
1864
|
aResult.di = angleBetweenPlanes(pv1, pv2);
|
|
1859
1865
|
aResult.dv = {
|
|
1860
|
-
i: Math.round(planeChangeDeltaV(pv1, pv2).i*
|
|
1861
|
-
c: Math.round(planeChangeDeltaV(pv1, pv2).c*
|
|
1866
|
+
i: Math.round(planeChangeDeltaV(pv1, pv2).i*1000)/1000,
|
|
1867
|
+
c: Math.round(planeChangeDeltaV(pv1, pv2).c*1000)/1000,
|
|
1862
1868
|
}; // Round to 3 decimals
|
|
1863
1869
|
|
|
1864
1870
|
for (let i=0; i<pEphem.length; i++) {
|
|
@@ -2142,8 +2148,10 @@ const calculateGeoCrossingTimes = async (propagateBetween, start, end, stepMs =
|
|
|
2142
2148
|
*/
|
|
2143
2149
|
const calculateNextApogeePerigeeTimesWithPropagation
|
|
2144
2150
|
= async (pv, propagateTo, time, findApogee=true, findPerigee=true) => {
|
|
2145
|
-
const r = [pv.position.x, pv.position.y, pv.position.z]
|
|
2146
|
-
|
|
2151
|
+
const r = multiply([pv.position.x, pv.position.y, pv.position.z],
|
|
2152
|
+
1000.0);
|
|
2153
|
+
const v = multiply([pv.velocity.x, pv.velocity.y, pv.velocity.z],
|
|
2154
|
+
1000.0);
|
|
2147
2155
|
const el = cartesianToKeplerian(r, v);
|
|
2148
2156
|
|
|
2149
2157
|
// Compute Eccentric Anomaly from True Anomaly and Eccentricity
|
|
@@ -2153,7 +2161,8 @@ const calculateNextApogeePerigeeTimesWithPropagation
|
|
|
2153
2161
|
const M = E - (el.e)*Math.sin(E);
|
|
2154
2162
|
|
|
2155
2163
|
// Mean motion in radians per second
|
|
2156
|
-
const
|
|
2164
|
+
const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
|
|
2165
|
+
const n = Math.sqrt(mu/Math.pow((el.a*1000.0), 3));
|
|
2157
2166
|
|
|
2158
2167
|
// Orbit Period
|
|
2159
2168
|
const periodSecs = 2*Math.PI/n;
|
|
@@ -2410,8 +2419,8 @@ const getLeoWaterfallData = (elsets, startTime, endTime, stepMs = 10000) => {
|
|
|
2410
2419
|
const ephem = prop(elset, segmentStart, segmentEnd, stepMs);
|
|
2411
2420
|
satEphems[satIndex].push(...ephem.map((point, pointInd) => {
|
|
2412
2421
|
const osculatingElements = cartesianToKeplerian(
|
|
2413
|
-
posToArray(point.p), // km
|
|
2414
|
-
posToArray(point.v), // km/s
|
|
2422
|
+
multiply(posToArray(point.p), 1000.0), // km to m
|
|
2423
|
+
multiply(posToArray(point.v), 1000.0), // km/s to m/s
|
|
2415
2424
|
);
|
|
2416
2425
|
return {
|
|
2417
2426
|
...point,
|
|
@@ -2494,7 +2503,9 @@ const getLeoWaterfallData = (elsets, startTime, endTime, stepMs = 10000) => {
|
|
|
2494
2503
|
* @param {Date} startTime The start time of the maneuver as unix timestamp in milliseconds
|
|
2495
2504
|
* @return {Object} An object containing the maneuver properties
|
|
2496
2505
|
*/
|
|
2497
|
-
const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) =>
|
|
2506
|
+
const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) =>{
|
|
2507
|
+
const mu = 3.986004415e5; // km based
|
|
2508
|
+
|
|
2498
2509
|
let minDv = Infinity;
|
|
2499
2510
|
let revNum = 0;
|
|
2500
2511
|
let minIntercept = [0, 0, 0]; // The minimum dv to intercept the target.
|
|
@@ -2515,7 +2526,7 @@ const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) => {
|
|
|
2515
2526
|
const r2 = [sat2Pv.position.x, sat2Pv.position.y, sat2Pv.position.z]; // km based
|
|
2516
2527
|
|
|
2517
2528
|
|
|
2518
|
-
const {v1, v2, vH1} = lambertThomsonAlgorithm(r1, r2, dt, rev, 0, v1Before,
|
|
2529
|
+
const {v1, v2, vH1} = lambertThomsonAlgorithm(r1, r2, dt, rev, 0, v1Before, mu);
|
|
2519
2530
|
const deltaV1 = isDefined(v1) ? subtract(v1, v1Before) : [0, 0, 0];
|
|
2520
2531
|
const deltaVH1 = isDefined(vH1) ? subtract(vH1, v1Before): [0, 0, 0];
|
|
2521
2532
|
const v1Mag = norm(deltaV1);
|
|
@@ -2595,7 +2606,7 @@ const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) => {
|
|
|
2595
2606
|
interceptTime: new Date(startTime + (minDt*1000)).toISOString(),
|
|
2596
2607
|
interceptManeuverECI_ms: {
|
|
2597
2608
|
time: new Date(startTime).toISOString(),
|
|
2598
|
-
x: minIntercept.x*1000, //
|
|
2609
|
+
x: minIntercept.x*1000, // km/s to m/s
|
|
2599
2610
|
y: minIntercept.y*1000,
|
|
2600
2611
|
z: minIntercept.z*1000,
|
|
2601
2612
|
},
|
|
@@ -2677,12 +2688,15 @@ const detectManeuverMinDv = (initialTLE, finalTLE) => {
|
|
|
2677
2688
|
// Position of final TLE at its epoch
|
|
2678
2689
|
const r2 = [pvFinal.position.x, pvFinal.position.y, pvFinal.position.z]; // km based
|
|
2679
2690
|
|
|
2691
|
+
// Earth Gravitational Parameter
|
|
2692
|
+
const mu = 3.986004415e5; // km based
|
|
2693
|
+
|
|
2680
2694
|
// Loop for N until NaN
|
|
2681
2695
|
let minNorm = Infinity;
|
|
2682
2696
|
// let minIndex = -1;
|
|
2683
2697
|
let minVector = [0, 0, 0];
|
|
2684
2698
|
for (let i=0; i<10; i++) { // loop up to 10 revolutions, it is a safe upper limit to capture impulsive maneuvers
|
|
2685
|
-
const {v1, vH1} = lambertThomsonAlgorithm(r1, r2, dt, i, 0, v1Before,
|
|
2699
|
+
const {v1, vH1} = lambertThomsonAlgorithm(r1, r2, dt, i, 0, v1Before, mu);
|
|
2686
2700
|
|
|
2687
2701
|
const deltaV1 = isDefined(v1) ? subtract(v1, v1Before) : [0, 0, 0];
|
|
2688
2702
|
const deltaVH1 = isDefined(vH1) ? subtract(vH1, v1Before): [0, 0, 0];
|
|
@@ -2744,7 +2758,7 @@ const detectManeuverMinDv = (initialTLE, finalTLE) => {
|
|
|
2744
2758
|
*
|
|
2745
2759
|
*/
|
|
2746
2760
|
const lambertThomsonAlgorithm
|
|
2747
|
-
= (r1, r2, t, N, D = 0, v1Minus, mu =
|
|
2761
|
+
= (r1, r2, t, N, D = 0, v1Minus, mu = 3.986004415e14, outOfPlaneError = 0) => {
|
|
2748
2762
|
// Out-Of-Plane error check
|
|
2749
2763
|
const r1Mag = norm(r1);
|
|
2750
2764
|
const r2Mag = norm(r2);
|
|
@@ -2938,7 +2952,7 @@ const lambertThomsonAlgorithm
|
|
|
2938
2952
|
* @param {number} mu Gravitational parameter
|
|
2939
2953
|
* @return {{v1: Array<number>, v2: Array<number>}} Initial and final velocity vectors
|
|
2940
2954
|
*/
|
|
2941
|
-
const hodographVelocityAlgorithm = (r1, r2, t, v1Minus, theta, p, e, mu
|
|
2955
|
+
const hodographVelocityAlgorithm = (r1, r2, t, v1Minus, theta, p, e, mu) => {
|
|
2942
2956
|
// Line 2: Define L180 (in meters)
|
|
2943
2957
|
const L180 = 1.0;
|
|
2944
2958
|
|
|
@@ -13,7 +13,7 @@ import {NodeVector3D} from "./NodeVector3D.js";
|
|
|
13
13
|
// Earth constants using existing constants.js values
|
|
14
14
|
const EarthConstants = {
|
|
15
15
|
EquatorialRadiusKm: WGS84_EARTH_EQUATORIAL_RADIUS_KM, // 6378.137 km
|
|
16
|
-
Mu: MU, // km³/s²
|
|
16
|
+
Mu: MU / 1e9, // Convert from m³/s² to km³/s² (398600.4418)
|
|
17
17
|
J2: 1082.62999e-6,
|
|
18
18
|
J3: -2.53215e-6,
|
|
19
19
|
};
|
package/src/constants.js
CHANGED
|
@@ -7,9 +7,7 @@ export const MILLIS_PER_DAY = 24 * 60 * 60 * 1000; // Number of milliseconds in
|
|
|
7
7
|
export const SUN_RADIUS_KM = 695701.0; // Sun radius in kilometers
|
|
8
8
|
export const AU_KM = 149597870.7; // Astronomical Unit in kilometers
|
|
9
9
|
|
|
10
|
-
export const MU = 3.
|
|
11
|
-
export const MU_GRS80 = 3.986004415e5; // km³/s² Earth Mu in GRS-80
|
|
12
|
-
export const MU_SI = 3.986004415e14; // m³/s² Earth Mu in SI units, GRS-80
|
|
10
|
+
export const MU = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
|
|
13
11
|
export const GRAV_CONST = 6.6743e-11; // N⋅m2⋅kg−2
|
|
14
12
|
export const EARTH_MASS = 5.97219e24; // kg
|
|
15
13
|
export const WGS72_EARTH_EQUATORIAL_RADIUS_KM = 6378.135; // in km. Use this when calculations are done with SGP4 which uses WGS72 assumptions.
|
package/src/launchNominal.js
CHANGED
|
@@ -194,7 +194,7 @@ const getOpopOtop = (
|
|
|
194
194
|
const opopLat = degreesLat(opop.latitude);
|
|
195
195
|
const opopLon = degreesLong(opop.longitude);
|
|
196
196
|
|
|
197
|
-
if (distGeodetic(opopLat, opopLon, padLat, padLon) > opopDistThresholdKm) {
|
|
197
|
+
if (distGeodetic(opopLat, opopLon, padLat, padLon) > opopDistThresholdKm * 1000) {
|
|
198
198
|
throw new Error(`OPOP is more than ${opopDistThresholdKm}km from launch pad`);
|
|
199
199
|
}
|
|
200
200
|
|