@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/README.md +41 -41
- package/package.json +52 -51
- package/src/FrameConverter.js +7 -6
- package/src/LaunchNominalClass.js +57 -76
- package/src/OrbitUtils.js +6 -187
- package/src/PropagateUtils.js +9 -9
- package/src/astro.js +56 -68
- package/src/ballisticPropagator.js +1 -1
- package/src/checkNetwork.cjs +20 -20
- package/src/constants.js +3 -1
- package/src/launchNominal.js +1 -1
- package/src/loggerFactory.cjs +98 -98
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
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 {checkTle} from "./astro.js";
|
|
10
|
-
import {
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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 =
|
|
465
|
-
|
|
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 =
|
|
473
|
-
|
|
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 /
|
|
480
|
+
(1 / MU),
|
|
484
481
|
subtract(
|
|
485
|
-
(multiply(Math.pow(norm(v, 2), 2) - (
|
|
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
|
|
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 =
|
|
584
|
-
const v =
|
|
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
|
|
583
|
+
// Velocity Magnitude in km/s
|
|
587
584
|
const vMag = norm(v);
|
|
588
585
|
|
|
589
|
-
const r2 =
|
|
590
|
-
const v2 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1439
|
-
* @param {Array} v Velocity, in
|
|
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 /
|
|
1452
|
+
(1 / MU),
|
|
1456
1453
|
subtract(
|
|
1457
|
-
(multiply(Math.pow(norm(v, 2), 2) - (
|
|
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) - (
|
|
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 = -
|
|
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
|
|
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 *
|
|
1578
|
+
* @return {Object} An object containing the position and velocity in *kilometer* based units
|
|
1582
1579
|
*/
|
|
1583
|
-
const keplerianToCartesian = (elset, mu =
|
|
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:
|
|
1620
|
-
y:
|
|
1621
|
-
z:
|
|
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:
|
|
1626
|
-
- sqrtGravP * sinAnom * (cosPer * cosRaan - cosInc * sinRaan * sinPer)
|
|
1627
|
-
y:
|
|
1628
|
-
- sqrtGravP * sinAnom * (cosPer * sinRaan + cosInc * cosRaan * sinPer)
|
|
1629
|
-
z:
|
|
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
|
-
|
|
1642
|
-
|
|
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
|
|
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*
|
|
1751
|
-
c: Math.round(aResult.dv.c*
|
|
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*
|
|
1865
|
-
c: Math.round(planeChangeDeltaV(pv1, pv2).c*
|
|
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 =
|
|
2150
|
-
|
|
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
|
|
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
|
-
|
|
2421
|
-
|
|
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,
|
|
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, //
|
|
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,
|
|
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 =
|
|
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
|
|
16
|
+
Mu: MU, // km³/s²
|
|
17
17
|
J2: 1082.62999e-6,
|
|
18
18
|
J3: -2.53215e-6,
|
|
19
19
|
};
|
package/src/checkNetwork.cjs
CHANGED
|
@@ -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.
|
|
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.
|
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) {
|
|
198
198
|
throw new Error(`OPOP is more than ${opopDistThresholdKm}km from launch pad`);
|
|
199
199
|
}
|
|
200
200
|
|