@saber-usa/node-common 1.7.6 → 1.7.7-alpha.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/src/OrbitUtils.js CHANGED
@@ -1,13 +1,10 @@
1
- import {MU} from "./constants.js";
1
+ import {MU, RAD2DEG} 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 / 1e9;
5
+ const EARTH_MU_KM = MU; // km³/s²
6
6
 
7
7
  class OrbitUtils {
8
- constructor() {
9
- // Prevent instantiation
10
- }
11
8
  /**
12
9
  * Get inclination based on azimuth and latitude.
13
10
  * @param {number} azimuthRad - Azimuth in radians
@@ -141,15 +138,8 @@ class OrbitUtils {
141
138
  if (Math.abs(b - a) < tolerance) {
142
139
  // Found minimum
143
140
  const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
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
- };
141
+ return [semiMajorAxis, targetEcc, incRad * RAD2DEG,
142
+ raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
153
143
  }
154
144
 
155
145
  xm = 0.5 * (a + b);
@@ -160,15 +150,8 @@ class OrbitUtils {
160
150
  if (min1 < tol1 || min2 < tol1) {
161
151
  // Found minimum
162
152
  const semiMajorAxis = targetPerigeeKm / (1 - targetEcc);
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
- };
153
+ return [semiMajorAxis, targetEcc, incRad * RAD2DEG,
154
+ raanRad * RAD2DEG, targetArgOfPeriapsisRad * RAD2DEG, c * RAD2DEG];
172
155
  }
173
156
 
174
157
  // Construct a trial parabolic fit
@@ -271,170 +254,6 @@ class OrbitUtils {
271
254
  return {position: r, velocity: v};
272
255
  }
273
256
 
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
-
438
257
  /**
439
258
  * Convert true anomaly to eccentric anomaly
440
259
  * @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 {checkTle} from "./astro.js";
10
- import {OrbitUtils} from "./OrbitUtils.js";
9
+ import {cartesianToKeplerian, checkTle} from "./astro.js";
10
+ import {DEG2RAD} from "./constants.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 elementsObj = OrbitUtils.stateVectorToElements(
65
+ const elementsObjReal = cartesianToKeplerian(
66
66
  initialState.position, initialState.velocity);
67
67
 
68
68
  const elements = new ClassicalElements(
69
69
  new EpochUTC(initialState.epoch),
70
- elementsObj.semiMajorAxis,
71
- elementsObj.eccentricity,
72
- elementsObj.inclination,
73
- elementsObj.raan,
74
- elementsObj.argOfPeriapsis,
75
- elementsObj.trueAnomaly,
70
+ elementsObjReal.a,
71
+ elementsObjReal.e,
72
+ elementsObjReal.i * DEG2RAD,
73
+ elementsObjReal.raan * DEG2RAD,
74
+ elementsObjReal.w * DEG2RAD,
75
+ elementsObjReal.f * DEG2RAD,
76
76
  );
77
77
 
78
78
  // Now pass to KeplerPropagator:
package/src/astro.js CHANGED
@@ -43,6 +43,9 @@ 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,
46
49
  ERROR_CODES} from "./constants.js";
47
50
 
48
51
  // Solar Terminator
@@ -454,25 +457,19 @@ const angleBetween3DCoords = (coord1, coord2, coord3) => {
454
457
  * which uses the In-track,Cross-Track axes to instantiate it.
455
458
  * @param {Object} pv1
456
459
  * @param {Object} pv2
457
- * @return {Object} Delta-v in m/s in RSW frame to perform a single impulsive
460
+ * @return {Object} Delta-v in km/s in RSW frame to perform a single impulsive
458
461
  * plane match maneuver of sat1 to sat2.
459
462
  */
460
463
  const planeChangeDeltaV = (pv1, pv2) => {
461
- const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
462
-
463
464
  // 1. Compute the angular momentum of each orbit from the pos vel vectors
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);
465
+ const r = [pv1.position.x, pv1.position.y, pv1.position.z];
466
+ const v = [pv1.velocity.x, pv1.velocity.y, pv1.velocity.z];
468
467
  const h1 = cross(r, v);
469
468
 
470
469
  const vMag = norm(v);
471
470
 
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);
471
+ const r2 = [pv2.position.x, pv2.position.y, pv2.position.z];
472
+ const v2 = [pv2.velocity.x, pv2.velocity.y, pv2.velocity.z];
476
473
  const h2 = cross(r2, v2);
477
474
 
478
475
  // 2. Compute the mutual line of nodes vector
@@ -480,9 +477,9 @@ const planeChangeDeltaV = (pv1, pv2) => {
480
477
 
481
478
  // 3. Compute the eccentricity vector of sat1
482
479
  const e = multiply(
483
- (1 / mu),
480
+ (1 / MU),
484
481
  subtract(
485
- (multiply(Math.pow(norm(v, 2), 2) - (mu / norm(r, 2)), r)),
482
+ (multiply(Math.pow(norm(v, 2), 2) - (MU / norm(r, 2)), r)),
486
483
  multiply(dot(r, v), v)),
487
484
  );
488
485
 
@@ -573,21 +570,21 @@ const planeChangeDeltaV = (pv1, pv2) => {
573
570
  *
574
571
  * @param {Object} pv1 Position and Velocity Vector of Satelltie 1 at Time = x
575
572
  * @param {Object} pv2 Position and velocity Vector of Satellite 2 at Time = x
576
- * @return {Object} Delta-v in m/s to perform a pure inclination plane change at
573
+ * @return {Object} Delta-v in km/s to perform a pure inclination plane change at
577
574
  * the sat1 asc or desc node.
578
575
  */
579
576
  const planeChangePureInclinationDeltaV = (pv1, pv2)=>{
580
577
  // 1. Get position and velocity vectors and magnitudes.
581
578
  // Note that the magnitude of the final velocity for sat1 will be EQUAL
582
579
  // to this initial velocity magnitude of sat1!
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);
580
+ const r = [pv1.position.x, pv1.position.y, pv1.position.z];
581
+ const v = [pv1.velocity.x, pv1.velocity.y, pv1.velocity.z];
585
582
 
586
- // Velocity Magnitude in m/s
583
+ // Velocity Magnitude in km/s
587
584
  const vMag = norm(v);
588
585
 
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);
586
+ const r2 = [pv2.position.x, pv2.position.y, pv2.position.z];
587
+ const v2 = [pv2.velocity.x, pv2.velocity.y, pv2.velocity.z];
591
588
 
592
589
  // 2. Compute the Keplerian Elements of both satellites.
593
590
  const el = cartesianToKeplerian(r, v);
@@ -1404,12 +1401,12 @@ const GetElsetUdlFromTle = (
1404
1401
  * @param {Number} lon1 The longitude of the first coordinate in degrees
1405
1402
  * @param {Number} lat2 The latitude of the second coordinate in degrees
1406
1403
  * @param {Number} lon2 The longitude of the second coordinate in degrees
1407
- * @return {Number} The distance between the two coordinates in meters
1404
+ * @return {Number} The distance between the two coordinates in kilometers
1408
1405
  *
1409
1406
  * Source: https://www.movable-type.co.uk/scripts/latlong.html
1410
1407
  */
1411
1408
  const distGeodetic = (lat1, lon1, lat2, lon2) => {
1412
- const R = WGS84_EARTH_EQUATORIAL_RADIUS_KM * 1000.0; // metres
1409
+ const R = WGS84_EARTH_EQUATORIAL_RADIUS_KM; // kilometers
1413
1410
  const phi1 = lat1 * DEG2RAD; // φ1 in formula
1414
1411
  const phi2 = lat2 * DEG2RAD; // φ2 in formula
1415
1412
  const deltaPhi = (lat2 - lat1) * DEG2RAD; // Δφ in formula
@@ -1422,7 +1419,7 @@ const distGeodetic = (lat1, lon1, lat2, lon2) => {
1422
1419
  * Math.sin(deltaLambda / 2);
1423
1420
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1424
1421
 
1425
- const d = R * c; // in metres
1422
+ const d = R * c; // in kilometers
1426
1423
 
1427
1424
  return d;
1428
1425
  };
@@ -1435,13 +1432,13 @@ const distGeodetic = (lat1, lon1, lat2, lon2) => {
1435
1432
  * NOTE 2: raan, argument of periapsis, and true anomaly are ALL in degrees!
1436
1433
  * NOTE 3: SMA (a) is in km
1437
1434
  *
1438
- * @param {Array} r Position, in meters
1439
- * @param {Array} v Velocity, in m/s
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
1440
1438
  * @return {Object} An object containing the Keplerian Elements, if successful. Otherwise, an empty object.
1441
1439
  */
1442
- const cartesianToKeplerian = (r, v) => {
1440
+ const cartesianToKeplerian = (r, v, mu = MU) => {
1443
1441
  try {
1444
- const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
1445
1442
  const tol = 1e-9;
1446
1443
 
1447
1444
  const h = cross(r, v);
@@ -1452,20 +1449,20 @@ const cartesianToKeplerian = (r, v) => {
1452
1449
  if (norm(v, 2) === 0) throw new Error("Velocity vector must not be zero.");
1453
1450
 
1454
1451
  const e = multiply(
1455
- (1 / mu),
1452
+ (1 / MU),
1456
1453
  subtract(
1457
- (multiply(Math.pow(norm(v, 2), 2) - (mu / norm(r, 2)), r)),
1454
+ (multiply(Math.pow(norm(v, 2), 2) - (MU / norm(r, 2)), r)),
1458
1455
  multiply(dot(r, v), v)),
1459
1456
  );
1460
1457
 
1461
- const zeta = 0.5 * Math.pow(norm(v), 2) - (mu / norm(r));
1458
+ const zeta = 0.5 * Math.pow(norm(v), 2) - (MU / norm(r));
1462
1459
 
1463
1460
  if (zeta === 0) throw new Error("Zeta cannot be zero.");
1464
1461
  if (Math.abs(1.0 - norm(e)) <= tol) {
1465
1462
  throw new Error("Parabolic orbit conversion is not supported.");
1466
1463
  }
1467
1464
 
1468
- const a = -mu / zeta / 2;
1465
+ const a = -MU / zeta / 2;
1469
1466
 
1470
1467
  if (Math.abs(a * (1 - norm(e))) < 1e-3) {
1471
1468
  throw new Error(`The state results in a singular conic section with
@@ -1555,7 +1552,7 @@ const cartesianToKeplerian = (r, v) => {
1555
1552
  }
1556
1553
 
1557
1554
  return {
1558
- a: a /1000.0, // km
1555
+ a: a, // km
1559
1556
  e: norm(e),
1560
1557
  i: i * RAD2DEG, // deg
1561
1558
  raan: raan * RAD2DEG, // deg
@@ -1578,9 +1575,9 @@ const cartesianToKeplerian = (r, v) => {
1578
1575
  *
1579
1576
  * @param {Array} elset Keplerian elements
1580
1577
  * @param {Number} mu The gravitational parameter for the celestial object that we orbit, in km based units
1581
- * @return {Object} An object containing the position and velocity in *meter* based units
1578
+ * @return {Object} An object containing the position and velocity in *kilometer* based units
1582
1579
  */
1583
- const keplerianToCartesian = (elset, mu = 398600.4418) => {
1580
+ const keplerianToCartesian = (elset, mu = MU) => {
1584
1581
  const INFINITE_TOL = 1e-10;
1585
1582
  const ORBIT_TOL = 1e-10;
1586
1583
  try {
@@ -1616,17 +1613,17 @@ const keplerianToCartesian = (elset, mu = 398600.4418) => {
1616
1613
  const sinPer = Math.sin(w);
1617
1614
 
1618
1615
  const r = {
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,
1616
+ x: rad * (cosPerAnom * cosRaan - cosInc * sinPerAnom * sinRaan),
1617
+ y: rad * (cosPerAnom * sinRaan + cosInc * sinPerAnom * cosRaan),
1618
+ z: rad * sinPerAnom * sinInc,
1622
1619
  };
1623
1620
 
1624
1621
  const v = {
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)),
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),
1630
1627
  };
1631
1628
 
1632
1629
  return {r, v};
@@ -1638,8 +1635,8 @@ const keplerianToCartesian = (elset, mu = 398600.4418) => {
1638
1635
  const cartesianToElsetElements = (pv, epoch) => {
1639
1636
  // sma, eccentricity, inclination, raan, argp, trueAnomaly
1640
1637
  const kepl = cartesianToKeplerian(
1641
- multiply(posToArray(pv.position), 1000.0), // km to m
1642
- multiply(posToArray(pv.velocity), 1000.0), // km/s to m/s
1638
+ posToArray(pv.position), // km
1639
+ posToArray(pv.velocity), // km/s
1643
1640
  );
1644
1641
 
1645
1642
  const elset = {
@@ -1651,8 +1648,7 @@ const cartesianToElsetElements = (pv, epoch) => {
1651
1648
  };
1652
1649
 
1653
1650
  // Mean motion in radians per second
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));
1651
+ const meanMotion = Math.sqrt(MU/Math.pow((elset.SemiMajorAxis), 3));
1656
1652
 
1657
1653
  elset.MeanMotion = meanMotion / (2*Math.PI) * 60 * 60 * 24; // rads/s to revs per day
1658
1654
 
@@ -1747,8 +1743,8 @@ const getLeoRpoData = (line1, line2, sats, startTime, endTime) => {
1747
1743
  aResult.di = angleBetweenPlanes(pv1, pv2);
1748
1744
  aResult.dv = planeChangeDeltaV(pv1, pv2);
1749
1745
  aResult.dv = {
1750
- i: Math.round(aResult.dv.i*1000)/1000,
1751
- c: Math.round(aResult.dv.c*1000)/1000,
1746
+ i: Math.round(aResult.dv.i*1000000)/1000, // Confirm rounding
1747
+ c: Math.round(aResult.dv.c*1000000)/1000,
1752
1748
  }; // Round to 3 decimals
1753
1749
 
1754
1750
  // Find the distance at each time step
@@ -1861,8 +1857,8 @@ const getGeoRpoData = (line1, line2, sats, startTime, endTime, lonTime) => {
1861
1857
  };
1862
1858
  aResult.di = angleBetweenPlanes(pv1, pv2);
1863
1859
  aResult.dv = {
1864
- i: Math.round(planeChangeDeltaV(pv1, pv2).i*1000)/1000,
1865
- c: Math.round(planeChangeDeltaV(pv1, pv2).c*1000)/1000,
1860
+ i: Math.round(planeChangeDeltaV(pv1, pv2).i*1000000)/1000, // Confirm rounding
1861
+ c: Math.round(planeChangeDeltaV(pv1, pv2).c*1000000)/1000,
1866
1862
  }; // Round to 3 decimals
1867
1863
 
1868
1864
  for (let i=0; i<pEphem.length; i++) {
@@ -2146,10 +2142,8 @@ const calculateGeoCrossingTimes = async (propagateBetween, start, end, stepMs =
2146
2142
  */
2147
2143
  const calculateNextApogeePerigeeTimesWithPropagation
2148
2144
  = async (pv, propagateTo, time, findApogee=true, findPerigee=true) => {
2149
- const r = multiply([pv.position.x, pv.position.y, pv.position.z],
2150
- 1000.0);
2151
- const v = multiply([pv.velocity.x, pv.velocity.y, pv.velocity.z],
2152
- 1000.0);
2145
+ const r = [pv.position.x, pv.position.y, pv.position.z];
2146
+ const v = [pv.velocity.x, pv.velocity.y, pv.velocity.z];
2153
2147
  const el = cartesianToKeplerian(r, v);
2154
2148
 
2155
2149
  // Compute Eccentric Anomaly from True Anomaly and Eccentricity
@@ -2159,8 +2153,7 @@ const calculateNextApogeePerigeeTimesWithPropagation
2159
2153
  const M = E - (el.e)*Math.sin(E);
2160
2154
 
2161
2155
  // Mean motion in radians per second
2162
- const mu = 3.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
2163
- const n = Math.sqrt(mu/Math.pow((el.a*1000.0), 3));
2156
+ const n = Math.sqrt(MU/Math.pow((el.a), 3));
2164
2157
 
2165
2158
  // Orbit Period
2166
2159
  const periodSecs = 2*Math.PI/n;
@@ -2417,8 +2410,8 @@ const getLeoWaterfallData = (elsets, startTime, endTime, stepMs = 10000) => {
2417
2410
  const ephem = prop(elset, segmentStart, segmentEnd, stepMs);
2418
2411
  satEphems[satIndex].push(...ephem.map((point, pointInd) => {
2419
2412
  const osculatingElements = cartesianToKeplerian(
2420
- multiply(posToArray(point.p), 1000.0), // km to m
2421
- multiply(posToArray(point.v), 1000.0), // km/s to m/s
2413
+ posToArray(point.p), // km
2414
+ posToArray(point.v), // km/s
2422
2415
  );
2423
2416
  return {
2424
2417
  ...point,
@@ -2501,9 +2494,7 @@ const getLeoWaterfallData = (elsets, startTime, endTime, stepMs = 10000) => {
2501
2494
  * @param {Date} startTime The start time of the maneuver as unix timestamp in milliseconds
2502
2495
  * @return {Object} An object containing the maneuver properties
2503
2496
  */
2504
- const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) =>{
2505
- const mu = 3.986004415e5; // km based
2506
-
2497
+ const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) => {
2507
2498
  let minDv = Infinity;
2508
2499
  let revNum = 0;
2509
2500
  let minIntercept = [0, 0, 0]; // The minimum dv to intercept the target.
@@ -2524,7 +2515,7 @@ const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) =>{
2524
2515
  const r2 = [sat2Pv.position.x, sat2Pv.position.y, sat2Pv.position.z]; // km based
2525
2516
 
2526
2517
 
2527
- const {v1, v2, vH1} = lambertThomsonAlgorithm(r1, r2, dt, rev, 0, v1Before, mu);
2518
+ const {v1, v2, vH1} = lambertThomsonAlgorithm(r1, r2, dt, rev, 0, v1Before, MU_GRS80);
2528
2519
  const deltaV1 = isDefined(v1) ? subtract(v1, v1Before) : [0, 0, 0];
2529
2520
  const deltaVH1 = isDefined(vH1) ? subtract(vH1, v1Before): [0, 0, 0];
2530
2521
  const v1Mag = norm(deltaV1);
@@ -2604,7 +2595,7 @@ const getInterceptRendezvousMinDv = (sat1Tle, sat2Tle, startTime) =>{
2604
2595
  interceptTime: new Date(startTime + (minDt*1000)).toISOString(),
2605
2596
  interceptManeuverECI_ms: {
2606
2597
  time: new Date(startTime).toISOString(),
2607
- x: minIntercept.x*1000, // km/s to m/s
2598
+ x: minIntercept.x*1000, // km/s to m/s
2608
2599
  y: minIntercept.y*1000,
2609
2600
  z: minIntercept.z*1000,
2610
2601
  },
@@ -2686,15 +2677,12 @@ const detectManeuverMinDv = (initialTLE, finalTLE) => {
2686
2677
  // Position of final TLE at its epoch
2687
2678
  const r2 = [pvFinal.position.x, pvFinal.position.y, pvFinal.position.z]; // km based
2688
2679
 
2689
- // Earth Gravitational Parameter
2690
- const mu = 3.986004415e5; // km based
2691
-
2692
2680
  // Loop for N until NaN
2693
2681
  let minNorm = Infinity;
2694
2682
  // let minIndex = -1;
2695
2683
  let minVector = [0, 0, 0];
2696
2684
  for (let i=0; i<10; i++) { // loop up to 10 revolutions, it is a safe upper limit to capture impulsive maneuvers
2697
- const {v1, vH1} = lambertThomsonAlgorithm(r1, r2, dt, i, 0, v1Before, mu);
2685
+ const {v1, vH1} = lambertThomsonAlgorithm(r1, r2, dt, i, 0, v1Before, MU_GRS80);
2698
2686
 
2699
2687
  const deltaV1 = isDefined(v1) ? subtract(v1, v1Before) : [0, 0, 0];
2700
2688
  const deltaVH1 = isDefined(vH1) ? subtract(vH1, v1Before): [0, 0, 0];
@@ -2756,7 +2744,7 @@ const detectManeuverMinDv = (initialTLE, finalTLE) => {
2756
2744
  *
2757
2745
  */
2758
2746
  const lambertThomsonAlgorithm
2759
- = (r1, r2, t, N, D = 0, v1Minus, mu = 3.986004415e14, outOfPlaneError = 0) => {
2747
+ = (r1, r2, t, N, D = 0, v1Minus, mu = MU_SI, outOfPlaneError = 0) => {
2760
2748
  // Out-Of-Plane error check
2761
2749
  const r1Mag = norm(r1);
2762
2750
  const r2Mag = norm(r2);
@@ -2950,7 +2938,7 @@ const lambertThomsonAlgorithm
2950
2938
  * @param {number} mu Gravitational parameter
2951
2939
  * @return {{v1: Array<number>, v2: Array<number>}} Initial and final velocity vectors
2952
2940
  */
2953
- const hodographVelocityAlgorithm = (r1, r2, t, v1Minus, theta, p, e, mu) => {
2941
+ const hodographVelocityAlgorithm = (r1, r2, t, v1Minus, theta, p, e, mu = MU_SI) => {
2954
2942
  // Line 2: Define L180 (in meters)
2955
2943
  const L180 = 1.0;
2956
2944
 
@@ -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 / 1e9, // Convert from m³/s² to km³/s² (398600.4418)
16
+ Mu: MU, // km³/s²
17
17
  J2: 1082.62999e-6,
18
18
  J3: -2.53215e-6,
19
19
  };
@@ -1,20 +1,20 @@
1
- const _ = require("lodash");
2
- const {resolve4} = require("dns").promises;
3
-
4
-
5
- const checkRecord = (hostName) => resolve4(hostName).
6
- then((addresss) => !_.isEmpty(addresss)).
7
- catch(_.stubFalse);
8
-
9
-
10
- module.exports.checkRecord = checkRecord;
11
- module.exports.checkNetwork = (domains) => Promise.all(
12
- _.map(
13
- domains,
14
- (hostName) => checkRecord(hostName).
15
- then((found) => found
16
- ? hostName
17
- : null,
18
- ),
19
- ),
20
- ).then((resolved) => _.flow(_.compact, _.first)(resolved) || null);
1
+ const _ = require("lodash");
2
+ const {resolve4} = require("dns").promises;
3
+
4
+
5
+ const checkRecord = (hostName) => resolve4(hostName).
6
+ then((addresss) => !_.isEmpty(addresss)).
7
+ catch(_.stubFalse);
8
+
9
+
10
+ module.exports.checkRecord = checkRecord;
11
+ module.exports.checkNetwork = (domains) => Promise.all(
12
+ _.map(
13
+ domains,
14
+ (hostName) => checkRecord(hostName).
15
+ then((found) => found
16
+ ? hostName
17
+ : null,
18
+ ),
19
+ ),
20
+ ).then((resolved) => _.flow(_.compact, _.first)(resolved) || null);
package/src/constants.js CHANGED
@@ -7,7 +7,9 @@ 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.986004418e14; // (m^3)/(s^2) WGS-84 Earth Mu
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
11
13
  export const GRAV_CONST = 6.6743e-11; // N⋅m2⋅kg−2
12
14
  export const EARTH_MASS = 5.97219e24; // kg
13
15
  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 * 1000) {
197
+ if (distGeodetic(opopLat, opopLon, padLat, padLon) > opopDistThresholdKm) {
198
198
  throw new Error(`OPOP is more than ${opopDistThresholdKm}km from launch pad`);
199
199
  }
200
200