@saber-usa/node-common 1.7.35 → 1.7.37
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/package.json +8 -7
- package/src/astro.js +39 -15
- package/src/index.js +3 -0
- package/src/tle/TLEValidator.js +5 -4
- package/src/tle/TleParseUtils.js +168 -0
- package/src/transform.js +13 -1
- package/src/udl.js +34 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saber-usa/node-common",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.37",
|
|
4
4
|
"description": "Common node functions for Saber",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"author": "Saber USA",
|
|
24
24
|
"license": "ISC",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@aws-sdk/client-s3": "^3.
|
|
26
|
+
"@aws-sdk/client-s3": "^3.1119.0",
|
|
27
27
|
"date-fns": "^4.4.0",
|
|
28
28
|
"lodash": "4.18.1",
|
|
29
29
|
"mathjs": "^15.2.0",
|
|
30
30
|
"pious-squid": "^2.3.0",
|
|
31
31
|
"plotly": "^1.0.6",
|
|
32
|
-
"satellite.js": "^7.0
|
|
32
|
+
"satellite.js": "^7.1.0",
|
|
33
33
|
"solar-calculator": "^0.3.0",
|
|
34
34
|
"three": "^0.185.1",
|
|
35
35
|
"winston": "3.19.0"
|
|
@@ -41,16 +41,17 @@
|
|
|
41
41
|
"@jest/globals": "^30.4.1",
|
|
42
42
|
"@sonar/scan": "^5.0.0",
|
|
43
43
|
"cross-env": "^10.1.0",
|
|
44
|
-
"eslint": "^10.
|
|
45
|
-
"eslint-plugin-jest": "^29.
|
|
46
|
-
"globals": "^17.
|
|
44
|
+
"eslint": "^10.9.1",
|
|
45
|
+
"eslint-plugin-jest": "^29.16.2",
|
|
46
|
+
"globals": "^17.11.0",
|
|
47
47
|
"jest": "^30.4.2",
|
|
48
48
|
"jest-diff": "^30.4.1",
|
|
49
49
|
"nodemon": "3.1.14"
|
|
50
50
|
},
|
|
51
51
|
"overrides": {
|
|
52
52
|
"braces": "3.0.3",
|
|
53
|
-
"brace-expansion": "2.1.
|
|
53
|
+
"brace-expansion": "2.1.4",
|
|
54
|
+
"js-yaml": "3.15.2",
|
|
54
55
|
"lodash": "4.18.1"
|
|
55
56
|
}
|
|
56
57
|
}
|
package/src/astro.js
CHANGED
|
@@ -40,6 +40,7 @@ import {DEG2RAD,
|
|
|
40
40
|
REGIMES,
|
|
41
41
|
GRAV_CONST,
|
|
42
42
|
EARTH_MASS,
|
|
43
|
+
MU,
|
|
43
44
|
WGS72_EARTH_EQUATORIAL_RADIUS_KM,
|
|
44
45
|
WGS84_EARTH_EQUATORIAL_RADIUS_KM,
|
|
45
46
|
MILLIS_PER_DAY,
|
|
@@ -48,6 +49,8 @@ import {DEG2RAD,
|
|
|
48
49
|
import TLEValidator from "./tle/TLEValidator.js";
|
|
49
50
|
import MultiLineTLEValidator from "./tle/MultiLineTLEValidator.js";
|
|
50
51
|
import {TLE_ERRORS} from "./tle/tleErrors.js";
|
|
52
|
+
import {TleParseUtils} from "./tle/TleParseUtils.js";
|
|
53
|
+
import {OrbitUtils} from "./OrbitUtils.js";
|
|
51
54
|
|
|
52
55
|
// Solar Terminator
|
|
53
56
|
// Returns sun latitude and longitude as -180/180
|
|
@@ -145,24 +148,24 @@ const validateTle = (line1, line2) => {
|
|
|
145
148
|
* @return {boolean} true if the propagation output is valid, false otherwise
|
|
146
149
|
*/
|
|
147
150
|
const isPropagateValid = (out) => {
|
|
148
|
-
if (out
|
|
151
|
+
if (!isDefined(out)) {
|
|
149
152
|
return false;
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
if (out.position) {
|
|
153
156
|
const pos = out.position;
|
|
154
|
-
if (pos.x
|
|
155
|
-
|| pos.y
|
|
156
|
-
|| pos.z
|
|
157
|
+
if (!isDefined(pos.x) || Number.isNaN(pos.x)
|
|
158
|
+
|| !isDefined(pos.y) || Number.isNaN(pos.y)
|
|
159
|
+
|| !isDefined(pos.z) || Number.isNaN(pos.z)) {
|
|
157
160
|
return false;
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
if (out.velocity) {
|
|
162
165
|
const vel = out.velocity;
|
|
163
|
-
if (vel.x
|
|
164
|
-
|| vel.y
|
|
165
|
-
|| vel.z
|
|
166
|
+
if (!isDefined(vel.x) || Number.isNaN(vel.x)
|
|
167
|
+
|| !isDefined(vel.y) || Number.isNaN(vel.y)
|
|
168
|
+
|| !isDefined(vel.z) || Number.isNaN(vel.z)) {
|
|
166
169
|
return false;
|
|
167
170
|
}
|
|
168
171
|
}
|
|
@@ -308,6 +311,28 @@ const getLonAndDrift = (line1, line2, datetime) => {
|
|
|
308
311
|
}
|
|
309
312
|
};
|
|
310
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Orbital period in minutes from semi-major axis and gravitational parameter.
|
|
316
|
+
* @param {number} aKm Semi-major axis (km)
|
|
317
|
+
* @param {number} muKm3s2 Standard gravitational parameter (km³/s²)
|
|
318
|
+
* @return {number|null} Period in minutes, or null if a ≤ 0
|
|
319
|
+
*/
|
|
320
|
+
const orbitalPeriodMinutes = (aKm, muKm3s2) => {
|
|
321
|
+
const periodSec = OrbitUtils.getPeriod(aKm, muKm3s2);
|
|
322
|
+
return periodSec === null ? null : periodSec / 60;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Mean motion in revolutions per day from semi-major axis and gravitational parameter.
|
|
327
|
+
* @param {number} aKm Semi-major axis (km)
|
|
328
|
+
* @param {number} muKm3s2 Standard gravitational parameter (km³/s²)
|
|
329
|
+
* @return {number} Mean motion in rev/day
|
|
330
|
+
*/
|
|
331
|
+
const orbitalMeanMotionRevPerDay = (aKm, muKm3s2) => {
|
|
332
|
+
const nRadS = OrbitUtils.getMeanMotion(aKm, muKm3s2);
|
|
333
|
+
return nRadS * 86400 / (2 * Math.PI);
|
|
334
|
+
};
|
|
335
|
+
|
|
311
336
|
/**
|
|
312
337
|
* Given eccentricity, inclination, meanmotion, and period
|
|
313
338
|
* Calculates & returns type of orbit to include LEO, MEO, HEO, GEO Drifter, GEO Inclined,GEO Stationary, and undetermined if applicable
|
|
@@ -1391,7 +1416,7 @@ const GetElsetUdlFromTle = (
|
|
|
1391
1416
|
// and we may be unsure of entering true or false for any reason.
|
|
1392
1417
|
if (isBoolean(isUct)) {
|
|
1393
1418
|
elset.uct = isUct;
|
|
1394
|
-
} else if (isUct
|
|
1419
|
+
} else if (!isDefined(isUct)) {
|
|
1395
1420
|
// Do nothing, do not populate nor put null.
|
|
1396
1421
|
} else {
|
|
1397
1422
|
throw new Error("Input uct flag is not of type boolean.");
|
|
@@ -1423,7 +1448,7 @@ const GetElsetUdlFromTle = (
|
|
|
1423
1448
|
const date = julianToGregorian(satrec.jdsatepoch);
|
|
1424
1449
|
elset.epoch = date.toISOString();
|
|
1425
1450
|
|
|
1426
|
-
elset.satNo =
|
|
1451
|
+
elset.satNo = TleParseUtils.getSatnoFromTle(satrec.satnum);
|
|
1427
1452
|
|
|
1428
1453
|
// Eccentricity
|
|
1429
1454
|
elset.eccentricity = satrec.ecco;
|
|
@@ -1747,11 +1772,9 @@ const cartesianToElsetElements = (pv, epoch) => {
|
|
|
1747
1772
|
ArgOfPerigee: kepl.w,
|
|
1748
1773
|
};
|
|
1749
1774
|
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
elset.MeanMotion = meanMotion / (2*Math.PI) * 60 * 60 * 24; // rads/s to revs per day
|
|
1775
|
+
const muKm3s2 = MU / 1e9;
|
|
1776
|
+
elset.MeanMotion = orbitalMeanMotionRevPerDay(elset.SemiMajorAxis, muKm3s2);
|
|
1777
|
+
elset.Period = orbitalPeriodMinutes(elset.SemiMajorAxis, muKm3s2);
|
|
1755
1778
|
|
|
1756
1779
|
const trueAnomaly = kepl.f;
|
|
1757
1780
|
|
|
@@ -1762,7 +1785,6 @@ const cartesianToElsetElements = (pv, epoch) => {
|
|
|
1762
1785
|
// Compute Mean Anomaly from Eccentric Anomaly and Eccentricity
|
|
1763
1786
|
elset.MeanAnomaly = (E - (elset.Eccentricity)*Math.sin(E)) * RAD2DEG;
|
|
1764
1787
|
|
|
1765
|
-
elset.Period = 2*Math.PI/meanMotion / 60; // period in minutes
|
|
1766
1788
|
elset.Apogee = elset.SemiMajorAxis * (1 + elset.Eccentricity); // km
|
|
1767
1789
|
elset.Perigee = elset.SemiMajorAxis * (1 - elset.Eccentricity); // km
|
|
1768
1790
|
|
|
@@ -3589,6 +3611,8 @@ export {
|
|
|
3589
3611
|
} from "satellite.js";
|
|
3590
3612
|
export {
|
|
3591
3613
|
calcRegime,
|
|
3614
|
+
orbitalPeriodMinutes,
|
|
3615
|
+
orbitalMeanMotionRevPerDay,
|
|
3592
3616
|
altToRegime,
|
|
3593
3617
|
cartesianToRIC,
|
|
3594
3618
|
angleBetween3DCoords,
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./OrbitUtils.js";
|
|
|
11
11
|
export * from "./PropagateUtils.js";
|
|
12
12
|
export * from "./ballisticPropagator.js";
|
|
13
13
|
export * from "./NodeVector3D.js";
|
|
14
|
+
export * from "./tle/TleParseUtils.js";
|
|
14
15
|
|
|
15
16
|
// UDL exports are grouped; re-export individually and as namespace if needed
|
|
16
17
|
import * as udl from "./udl.js";
|
|
@@ -30,6 +31,7 @@ import * as OrbitUtilsNS from "./OrbitUtils.js";
|
|
|
30
31
|
import * as PropagateUtilsNS from "./PropagateUtils.js";
|
|
31
32
|
import * as ballisticPropagatorNS from "./ballisticPropagator.js";
|
|
32
33
|
import * as NodeVector3DNS from "./NodeVector3D.js";
|
|
34
|
+
import * as TleParseUtilsNS from "./tle/TleParseUtils.js";
|
|
33
35
|
|
|
34
36
|
const aggregate = {
|
|
35
37
|
...loggerFactoryNS,
|
|
@@ -44,6 +46,7 @@ const aggregate = {
|
|
|
44
46
|
...PropagateUtilsNS,
|
|
45
47
|
...ballisticPropagatorNS,
|
|
46
48
|
...NodeVector3DNS,
|
|
49
|
+
...TleParseUtilsNS,
|
|
47
50
|
...udl,
|
|
48
51
|
};
|
|
49
52
|
|
package/src/tle/TLEValidator.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {formatChecksumError, formatLengthError, TLE_ERRORS} from "./tleErrors.js";
|
|
2
|
+
import {TleParseUtils} from "./TleParseUtils.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* NORAD TLE line-pair validator.
|
|
5
6
|
* Logic derived from whatswrongwithmytle.com (Dr. TS Kelso's field guide).
|
|
7
|
+
* Catalog-number grammar is owned by TleParseUtils (Alpha-5 aware).
|
|
6
8
|
*/
|
|
7
9
|
class TLEValidator {
|
|
8
10
|
constructor() {
|
|
9
|
-
this.lineOneRegexCatalogNumber = /[0-Z]{5}[CSU]/;
|
|
10
11
|
this.lineOneRegexInternationalDesignator = / (\d{5}[A-Z][ A-Z]{2}|\d{5}[ A-Z]{2}[A-Z]| {8})/;
|
|
11
12
|
this.lineOneRegexEpoch = / \d{5}\.\d{8}/;
|
|
12
13
|
this.lineOneRegexDMeanMotion = / [ +-]\.\d{8}/;
|
|
@@ -15,7 +16,6 @@ class TLEValidator {
|
|
|
15
16
|
this.lineOneRegexEphemType = / [0-5]/;
|
|
16
17
|
this.lineOneRegexSetNumber = / [ \d]{3}\d/;
|
|
17
18
|
|
|
18
|
-
this.lineTwoRegexCatalogNumber = /[0-Z]{5}/;
|
|
19
19
|
this.lineTwoRegexInclination = / [ \d]{3}\.\d{4}/;
|
|
20
20
|
this.lineTwoRegexRAAN = / [ \d]{3}\.\d{4}/;
|
|
21
21
|
this.lineTwoRegexEccentricity = / \d{7}/;
|
|
@@ -57,7 +57,8 @@ class TLEValidator {
|
|
|
57
57
|
if (line.length < 69) {
|
|
58
58
|
return formatLengthError(TLE_ERRORS.LINE1_LENGTH, line.length);
|
|
59
59
|
}
|
|
60
|
-
if (!
|
|
60
|
+
if (!TleParseUtils.isValidSatelliteNumberField(line.slice(2, 7))
|
|
61
|
+
|| !/[CSU]/.test(line.slice(7, 8))) {
|
|
61
62
|
return TLE_ERRORS.LINE1_CATALOG_NUMBER;
|
|
62
63
|
}
|
|
63
64
|
if (!this.lineOneRegexInternationalDesignator.test(line.slice(8, 17))) {
|
|
@@ -96,7 +97,7 @@ class TLEValidator {
|
|
|
96
97
|
if (line.length < 69) {
|
|
97
98
|
return formatLengthError(TLE_ERRORS.LINE2_LENGTH, line.length);
|
|
98
99
|
}
|
|
99
|
-
if (!
|
|
100
|
+
if (!TleParseUtils.isValidSatelliteNumberField(line.slice(2, 7))) {
|
|
100
101
|
return TLE_ERRORS.LINE2_CATALOG_NUMBER;
|
|
101
102
|
}
|
|
102
103
|
if (!this.lineTwoRegexInclination.test(line.slice(7, 16))) {
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {isDefined} from "../utils.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility class for TLE parsing, including Alpha-5 TLE satellite IDs.
|
|
5
|
+
* Ported from org.orekit.propagation.analytical.tle.ParseUtils / TLE.
|
|
6
|
+
*
|
|
7
|
+
* Alpha-5 extends the range of existing 5-digit TLE satellite numbers
|
|
8
|
+
* by allowing the first digit to be an upper case letter, ignoring 'I'
|
|
9
|
+
* and 'O' to avoid confusion with numbers '1' and '0'.
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.space-track.org/documentation#tle-alpha5
|
|
12
|
+
*/
|
|
13
|
+
export class TleParseUtils {
|
|
14
|
+
/** Maximum satellite number representable with 5 numeric digits. */
|
|
15
|
+
static MAX_NUMERIC_SATNUM = 99999;
|
|
16
|
+
|
|
17
|
+
/** Scaling factor for Alpha-5 numbers. */
|
|
18
|
+
static ALPHA5_SCALING = 10000;
|
|
19
|
+
|
|
20
|
+
/** Letter → number map for Alpha-5 satellite numbers. */
|
|
21
|
+
static ALPHA5_NUMBERS = new Map();
|
|
22
|
+
|
|
23
|
+
/** Number → letter map for Alpha-5 satellite numbers. */
|
|
24
|
+
static ALPHA5_LETTERS = new Map();
|
|
25
|
+
|
|
26
|
+
static {
|
|
27
|
+
const alpha5Letters = [
|
|
28
|
+
"A", "B", "C", "D", "E", "F", "G", "H", "J",
|
|
29
|
+
"K", "L", "M", "N", "P", "Q", "R", "S", "T",
|
|
30
|
+
"U", "V", "W", "X", "Y", "Z",
|
|
31
|
+
];
|
|
32
|
+
for (let i = 0; i < alpha5Letters.length; ++i) {
|
|
33
|
+
TleParseUtils.ALPHA5_NUMBERS.set(alpha5Letters[i], i + 10);
|
|
34
|
+
TleParseUtils.ALPHA5_LETTERS.set(i + 10, alpha5Letters[i]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Private constructor for a utility class. */
|
|
39
|
+
constructor() {
|
|
40
|
+
throw new Error("TleParseUtils is a utility class and cannot be instantiated");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* True if token is a valid 5-char TLE catalog field:
|
|
45
|
+
* five digits, or Alpha-5 letter (no I/O) plus four digits.
|
|
46
|
+
* @param {string} token
|
|
47
|
+
* @return {boolean}
|
|
48
|
+
*/
|
|
49
|
+
static isValidSatelliteNumberField(token) {
|
|
50
|
+
if (typeof token !== "string" || token.length !== 5) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (/^\d{5}$/.test(token)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (!TleParseUtils.ALPHA5_NUMBERS.has(token.charAt(0))) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return /^\d{4}$/.test(token.substring(1));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Stricter int parser: accepts an optional sign plus digits only, else throws.
|
|
64
|
+
* Unlike Number.parseInt, rejects partial parses (e.g. "12abc").
|
|
65
|
+
* @param {string} string
|
|
66
|
+
* @return {number}
|
|
67
|
+
*/
|
|
68
|
+
static #strictParseInt(string) {
|
|
69
|
+
if (!/^[+-]?\d+$/.test(string)) {
|
|
70
|
+
throw new Error(`Invalid integer: "${string}"`);
|
|
71
|
+
}
|
|
72
|
+
return Number.parseInt(string, 10);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Add padding characters to a string.
|
|
77
|
+
* Port of ParseUtils.addPadding.
|
|
78
|
+
* @param {string} name parameter name
|
|
79
|
+
* @param {string} string string to pad
|
|
80
|
+
* @param {string} c padding character
|
|
81
|
+
* @param {number} size desired size
|
|
82
|
+
* @param {boolean} rightJustified if true, pad on the left
|
|
83
|
+
* @param {number} satelliteNumber satellite number (for error context)
|
|
84
|
+
* @return {string} padded string
|
|
85
|
+
*/
|
|
86
|
+
static addPadding(name, string, c, size, rightJustified, satelliteNumber) {
|
|
87
|
+
if (string.length > size) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`TLE invalid parameter: satelliteNumber=${satelliteNumber}, name=${name}, value=${string}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const padding = c.repeat(size);
|
|
94
|
+
|
|
95
|
+
if (rightJustified) {
|
|
96
|
+
const concatenated = padding + string;
|
|
97
|
+
const l = concatenated.length;
|
|
98
|
+
return concatenated.substring(l - size, l);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (string + padding).substring(0, size);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build an Alpha-5 (or numeric) satellite number field.
|
|
106
|
+
* Port of ParseUtils.buildSatelliteNumber.
|
|
107
|
+
* @param {number} satelliteNumber satellite number, that may exceed the 99999 limit
|
|
108
|
+
* @param {string} name parameter name (used in error messages)
|
|
109
|
+
* @return {string} satellite number in alpha5 / 5-digit representation
|
|
110
|
+
*/
|
|
111
|
+
static buildSatelliteNumber(satelliteNumber, name) {
|
|
112
|
+
if (satelliteNumber > TleParseUtils.MAX_NUMERIC_SATNUM) {
|
|
113
|
+
const highDigits = Math.trunc(satelliteNumber / TleParseUtils.ALPHA5_SCALING);
|
|
114
|
+
const lowDigits = satelliteNumber - highDigits * TleParseUtils.ALPHA5_SCALING;
|
|
115
|
+
|
|
116
|
+
const alpha = TleParseUtils.ALPHA5_LETTERS.get(highDigits);
|
|
117
|
+
if (alpha === undefined) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`TLE invalid parameter: satelliteNumber=${satelliteNumber}, name=${name}, value=null`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return alpha + TleParseUtils.addPadding(
|
|
123
|
+
name, String(lowDigits), "0", 4, true, satelliteNumber,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return TleParseUtils.addPadding(
|
|
127
|
+
name, String(satelliteNumber), "0", 5, true, satelliteNumber,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parse a satellite number from a string field (supports Alpha-5).
|
|
133
|
+
* Port of TLE.parseSatelliteNumber / ParseUtils.parseSatelliteNumber.
|
|
134
|
+
* @param {string} satNumberString the string to parse (e.g., "25544" or "A0001")
|
|
135
|
+
* @return {number} the satellite number as an integer
|
|
136
|
+
*/
|
|
137
|
+
static getSatnoFromTle(satNumberString) {
|
|
138
|
+
if (!TleParseUtils.isValidSatelliteNumberField(satNumberString)) {
|
|
139
|
+
throw new Error(`Invalid satellite number field: "${satNumberString}"`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const alpha = TleParseUtils.ALPHA5_NUMBERS.get(satNumberString.charAt(0));
|
|
143
|
+
if (alpha !== undefined) {
|
|
144
|
+
return (alpha * TleParseUtils.ALPHA5_SCALING)
|
|
145
|
+
+ TleParseUtils.#strictParseInt(satNumberString.substring(1));
|
|
146
|
+
}
|
|
147
|
+
return TleParseUtils.#strictParseInt(satNumberString);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Lenient catalog NORAD ID decode: 5-char TLE field (digits or Alpha-5),
|
|
152
|
+
* or an unpadded plain integer string (e.g. "16", "900").
|
|
153
|
+
* Returns null for nullish / empty / non-numeric values instead of throwing.
|
|
154
|
+
* @param {string|number|null|undefined} value
|
|
155
|
+
* @return {number|null}
|
|
156
|
+
*/
|
|
157
|
+
static parseCatalogSatno(value) {
|
|
158
|
+
if (!isDefined(value) || value === "") {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const token = String(value).trim();
|
|
162
|
+
if (TleParseUtils.isValidSatelliteNumberField(token)) {
|
|
163
|
+
return TleParseUtils.getSatnoFromTle(token);
|
|
164
|
+
}
|
|
165
|
+
const n = Number.parseInt(token, 10);
|
|
166
|
+
return Number.isNaN(n) ? null : n;
|
|
167
|
+
}
|
|
168
|
+
}
|
package/src/transform.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
|
+
import {isDefined} from "./utils.js";
|
|
2
3
|
|
|
3
4
|
const transformObjectKeys = (transform, object, deep = true) => _.cond([
|
|
4
5
|
[
|
|
@@ -32,4 +33,15 @@ const pascalCaseObjectKeys = (transformItem, deep) => transformObjectKeys(
|
|
|
32
33
|
deep,
|
|
33
34
|
);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
// Write a true SQL NULL instead of the *string* "null" (JSON.stringify(null) === "null")
|
|
37
|
+
// when a value is absent, so nullable JSON/TEXT columns stay unambiguously empty.
|
|
38
|
+
const jsonOrNull = (value) => (isDefined(value) ? JSON.stringify(value) : null);
|
|
39
|
+
|
|
40
|
+
// As jsonOrNull, but additionally rejects non-array values (e.g. a provider returning the
|
|
41
|
+
// string "OTHER" in place of a real array), coercing them to null rather than serializing them.
|
|
42
|
+
const jsonArrayOrNull = (value) => (Array.isArray(value) ? JSON.stringify(value) : null);
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
transformObjectKeys, lowerCaseObjectKeys, pascalCaseObjectKeys, pascalCase,
|
|
46
|
+
jsonOrNull, jsonArrayOrNull,
|
|
47
|
+
};
|
package/src/udl.js
CHANGED
|
@@ -5,12 +5,15 @@ import {
|
|
|
5
5
|
getElsetUdlFromTle,
|
|
6
6
|
getLonAndDrift,
|
|
7
7
|
getRaanPrecession,
|
|
8
|
+
orbitalMeanMotionRevPerDay,
|
|
9
|
+
orbitalPeriodMinutes,
|
|
8
10
|
raDecToAzEl,
|
|
9
11
|
azElToRaDec,
|
|
10
12
|
raDecToGeodetic,
|
|
11
13
|
estimateSlantRange,
|
|
12
14
|
} from "./astro.js";
|
|
13
|
-
import {
|
|
15
|
+
import {REGIMES, MU} from "./constants.js";
|
|
16
|
+
import {lowerCaseObjectKeys, jsonArrayOrNull} from "./transform.js";
|
|
14
17
|
import {isDefined} from "./utils.js";
|
|
15
18
|
import _ from "lodash";
|
|
16
19
|
|
|
@@ -327,6 +330,28 @@ const getSVSatno = (udlRow) => {
|
|
|
327
330
|
return null;
|
|
328
331
|
};
|
|
329
332
|
|
|
333
|
+
const computeStateRegimeFromKeplerians = (keplerians) => {
|
|
334
|
+
const {a, e, i} = keplerians;
|
|
335
|
+
if (!isDefined(a) || a <= 0 || !isDefined(e) || e >= 1) {
|
|
336
|
+
return REGIMES.Undetermined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const muKm3s2 = MU / 1e9;
|
|
340
|
+
const period = orbitalPeriodMinutes(a, muKm3s2);
|
|
341
|
+
const meanmotion = orbitalMeanMotionRevPerDay(a, muKm3s2);
|
|
342
|
+
|
|
343
|
+
if (period === null) {
|
|
344
|
+
return REGIMES.Undetermined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return calcRegime({
|
|
348
|
+
eccentricity: e,
|
|
349
|
+
inclination: i,
|
|
350
|
+
meanmotion,
|
|
351
|
+
period,
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
|
|
330
355
|
const udlToNpsState = (udlRow) => {
|
|
331
356
|
const Xpos = _.get(udlRow, "xpos", null); // kilometers
|
|
332
357
|
const Ypos = _.get(udlRow, "ypos", null);
|
|
@@ -335,7 +360,7 @@ const udlToNpsState = (udlRow) => {
|
|
|
335
360
|
const Xvel = _.get(udlRow, "xvel", null); // kilometers/second
|
|
336
361
|
const Yvel = _.get(udlRow, "yvel", null);
|
|
337
362
|
const Zvel = _.get(udlRow, "zvel", null);
|
|
338
|
-
const
|
|
363
|
+
const rawCov = _.get(udlRow, "cov", null );
|
|
339
364
|
const covRefFrame = _.get(udlRow, "covReferenceFrame", null );
|
|
340
365
|
let keplerians = {a: null, e: null, i: null, raan: null, w: null, f: null};
|
|
341
366
|
|
|
@@ -351,6 +376,8 @@ const udlToNpsState = (udlRow) => {
|
|
|
351
376
|
keplerians = cartesianToKeplerian(pos, vel);
|
|
352
377
|
}
|
|
353
378
|
|
|
379
|
+
const regime = computeStateRegimeFromKeplerians(keplerians);
|
|
380
|
+
|
|
354
381
|
const npsState = lowerCaseObjectKeys({
|
|
355
382
|
satno: getSVSatno(udlRow),
|
|
356
383
|
Epoch: fixDate(_.get(udlRow, "epoch", null)),
|
|
@@ -368,7 +395,10 @@ const udlToNpsState = (udlRow) => {
|
|
|
368
395
|
Xvel: Xvel,
|
|
369
396
|
Yvel: Yvel,
|
|
370
397
|
Zvel: Zvel,
|
|
371
|
-
|
|
398
|
+
// Guard against providers returning a non-array `cov` (e.g. the string "null" or
|
|
399
|
+
// "OTHER" in place of a real covariance matrix), and write a true SQL NULL rather
|
|
400
|
+
// than the string "null" when there is no covariance at all.
|
|
401
|
+
Covariance: jsonArrayOrNull(rawCov),
|
|
372
402
|
CovarianceRefFrame: covRefFrame,
|
|
373
403
|
SemiMajorAxis: keplerians.a,
|
|
374
404
|
Eccentricity: keplerians.e,
|
|
@@ -385,7 +415,7 @@ const udlToNpsState = (udlRow) => {
|
|
|
385
415
|
Longitude: null,
|
|
386
416
|
LonDriftDegreesPerDay: null,
|
|
387
417
|
RaanPrecessionDegreesPerDay: null,
|
|
388
|
-
Regime:
|
|
418
|
+
Regime: regime,
|
|
389
419
|
});
|
|
390
420
|
return npsState;
|
|
391
421
|
};
|