@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 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. Run `npm publish && npm install`
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.7-alpha.2",
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/**/*"
@@ -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.i * DEG2RAD);
962
- const sini = Math.sin(keplerianElements.i * DEG2RAD);
963
- const cosRaan = Math.cos(keplerianElements.raan * DEG2RAD);
964
- const sinRaan = Math.sin(keplerianElements.raan * DEG2RAD);
965
- const cosw = Math.cos(keplerianElements.w * DEG2RAD);
966
- const sinw = Math.sin(keplerianElements.w * DEG2RAD);
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: cartesianToKeplerian(
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], mu),
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 - angles are in degrees
141
- const elements = cartesianToKeplerian(
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], mu);
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.i)
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 * DEG2RAD,
229
- initialNominal.states[0].elements.raan * DEG2RAD,
241
+ initialNominal.states[0].elements.inc,
242
+ initialNominal.states[0].elements.raan,
230
243
  targetEcc,
231
- targetArgOfPeriapsisDeg * DEG2RAD,
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 = keplerianToCartesian(ellipticalElements, mu);
249
+ const ellipticalStateVector = OrbitUtils.elementsToStateVector(ellipticalElements);
237
250
 
238
- const ellipticalPos = ellipticalStateVector.r;
239
- const ellipticalVel = ellipticalStateVector.v;
251
+ const ellipticalPos = ellipticalStateVector.position;
252
+ const ellipticalVel = ellipticalStateVector.velocity;
240
253
 
241
254
  const ellipticalState = {
242
- position: new NodeVector3D(ellipticalPos.x, ellipticalPos.y, ellipticalPos.z),
243
- velocity: new NodeVector3D(ellipticalVel.x, ellipticalVel.y, ellipticalVel.z),
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 = cartesianToKeplerian(
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 = cartesianToKeplerian(rElementConv, vElementConv, mu);
462
- const incDiff = elements0.i * DEG2RAD;
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 = [35786 + EARTH_RADIUS_KM, 0, 0, 0, 0, 0];
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: keplerianToCartesian(geoElements, mu),
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 = cartesianToKeplerian(
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], muEarth);
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 orbit2 = cartesianToKeplerian(
549
- [sv2.state.r.x, sv2.state.r.y, sv2.state.r.z],
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 state1 = keplerianToCartesian(Object.values(orbit1), muEarth);
553
- const state2 = keplerianToCartesian(Object.values(orbit2), muEarth);
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.a,
587
- orbit1.e);
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.f * DEG2RAD, 2 * Math.PI);
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.e);
603
- let e1 = OrbitUtils.trueAnomalyToEccentricAnomaly(n1, orbit1.e); // only matching to 2 sig figs w astrolib
604
- let e2 = OrbitUtils.trueAnomalyToEccentricAnomaly(n2, orbit1.e); // only matching to 3 sig figs w astrolib
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.a, 3) / muEarth)
618
- * (2 * k1 * Math.PI + (e1 - orbit1.e * Math.sin(e1))
619
- - (e0 - orbit1.e * Math.sin(e0)));
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.a, 3) / muEarth)
622
- * (2 * k2 * Math.PI + (e2 - orbit1.e * Math.sin(e2))
623
- - (e0 - orbit1.e * Math.sin(e0)));
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 = cartesianToKeplerian(
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.a,
716
- ecc: elements.e,
717
- inc: elements.i,
734
+ sma: elements.semiMajorAxis,
735
+ ecc: elements.eccentricity,
736
+ inc: elements.inclination,
718
737
  raan: elements.raan,
719
- argp: elements.w,
720
- ta: elements.f,
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, RAD2DEG} from "./constants.js";
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; // km³/s²
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 [semiMajorAxis, targetEcc, incRad * RAD2DEG,
142
- raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
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 [semiMajorAxis, targetEcc, incRad * RAD2DEG,
154
- raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
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
@@ -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 {cartesianToKeplerian, checkTle} from "./astro.js";
10
- import {DEG2RAD} from "./constants.js";
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 elementsObjReal = cartesianToKeplerian(
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
- elementsObjReal.a,
71
- elementsObjReal.e,
72
- elementsObjReal.i * DEG2RAD,
73
- elementsObjReal.raan * DEG2RAD,
74
- elementsObjReal.w * DEG2RAD,
75
- elementsObjReal.f * DEG2RAD,
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 km/s in RSW frame to perform a single impulsive
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
- const v = [pv1.velocity.x, pv1.velocity.y, pv1.velocity.z];
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
- const v2 = [pv2.velocity.x, pv2.velocity.y, pv2.velocity.z];
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 / MU),
483
+ (1 / mu),
481
484
  subtract(
482
- (multiply(Math.pow(norm(v, 2), 2) - (MU / norm(r, 2)), r)),
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 km/s to perform a pure inclination plane change at
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 km/s
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 kilometers
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; // kilometers
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 kilometers
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 kilometers
1436
- * @param {Array} v Velocity, in km/s
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, mu = MU) => {
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 / MU),
1455
+ (1 / mu),
1453
1456
  subtract(
1454
- (multiply(Math.pow(norm(v, 2), 2) - (MU / norm(r, 2)), r)),
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) - (MU / norm(r));
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 = -MU / zeta / 2;
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 *kilometer* based units
1581
+ * @return {Object} An object containing the position and velocity in *meter* based units
1579
1582
  */
1580
- const keplerianToCartesian = (elset, mu = 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 meanMotion = Math.sqrt(MU/Math.pow((elset.SemiMajorAxis), 3));
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*1000000)/1000, // Confirm rounding
1747
- c: Math.round(aResult.dv.c*1000000)/1000,
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*1000000)/1000, // Confirm rounding
1861
- c: Math.round(planeChangeDeltaV(pv1, pv2).c*1000000)/1000,
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
- const v = [pv.velocity.x, pv.velocity.y, pv.velocity.z];
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 n = Math.sqrt(MU/Math.pow((el.a), 3));
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, MU_GRS80);
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, // km/s to m/s
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, MU_GRS80);
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 = MU, outOfPlaneError = 0) => {
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 = 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.986004418e5; // km³/s² WGS-84 Earth Mu
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.
@@ -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