@saber-usa/node-common 1.7.22 → 1.7.24-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/package.json +1 -1
- package/src/PropagateUtils.js +5 -4
- package/src/astro.js +62 -5
- package/src/tle/MultiLineTLEValidator.js +114 -0
- package/src/tle/TLEValidator.js +135 -0
- package/src/tle/tleErrors.js +36 -0
- package/src/udl.js +81 -1
- package/src/wasmProp/wasmAstro.js +3 -3
package/package.json
CHANGED
package/src/PropagateUtils.js
CHANGED
|
@@ -6,7 +6,7 @@ import {RungeKutta4Propagator,
|
|
|
6
6
|
ClassicalElements} from "pious-squid";
|
|
7
7
|
import {sgp4} from "satellite.js";
|
|
8
8
|
import {NodeVector3D} from "./NodeVector3D.js";
|
|
9
|
-
import {
|
|
9
|
+
import {validateTle} from "./astro.js";
|
|
10
10
|
import {OrbitUtils} from "./OrbitUtils.js";
|
|
11
11
|
|
|
12
12
|
const ReferenceFrame = {
|
|
@@ -87,10 +87,11 @@ class PropagateUtils {
|
|
|
87
87
|
static tlePropagator(tle, time) {
|
|
88
88
|
const line1 = tle[0];
|
|
89
89
|
const line2 = tle[1];
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
92
|
-
throw new Error("
|
|
90
|
+
const tleResult = validateTle(line1, line2);
|
|
91
|
+
if (!tleResult.ok) {
|
|
92
|
+
throw new Error(tleResult.errors.join("; "));
|
|
93
93
|
}
|
|
94
|
+
const satrec = tleResult.satrec;
|
|
94
95
|
const result = sgp4(satrec, time);
|
|
95
96
|
return result;
|
|
96
97
|
}
|
package/src/astro.js
CHANGED
|
@@ -45,6 +45,9 @@ import {DEG2RAD,
|
|
|
45
45
|
MILLIS_PER_DAY,
|
|
46
46
|
GEO_ALTITUDE_KM,
|
|
47
47
|
ERROR_CODES} from "./constants.js";
|
|
48
|
+
import TLEValidator from "./tle/TLEValidator.js";
|
|
49
|
+
import MultiLineTLEValidator from "./tle/MultiLineTLEValidator.js";
|
|
50
|
+
import {TLE_ERRORS} from "./tle/tleErrors.js";
|
|
48
51
|
|
|
49
52
|
// Solar Terminator
|
|
50
53
|
// Returns sun latitude and longitude as -180/180
|
|
@@ -58,6 +61,11 @@ const sunPosAt = (dt) => {
|
|
|
58
61
|
];
|
|
59
62
|
};
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Use validateTle() for structured validation results.
|
|
66
|
+
* Retained with original semantics (satrec | false, no format checks)
|
|
67
|
+
* for external consumers of @saber-usa/node-common.
|
|
68
|
+
*/
|
|
61
69
|
const checkTle = (line1, line2) => {
|
|
62
70
|
try {
|
|
63
71
|
// Initialize a satellite record
|
|
@@ -77,6 +85,50 @@ const checkTle = (line1, line2) => {
|
|
|
77
85
|
}
|
|
78
86
|
};
|
|
79
87
|
|
|
88
|
+
const propagateTleToSatrec = (line1, line2) => {
|
|
89
|
+
const satrec = twoline2satrec(line1, line2);
|
|
90
|
+
satrec.epoch = dateToMySqlDate(julianToGregorian(satrec.jdsatepoch));
|
|
91
|
+
const pv = sgp4(satrec, 0);
|
|
92
|
+
if (Number.isNaN(pv.position.x)
|
|
93
|
+
|| Number.isNaN(pv.position.y)
|
|
94
|
+
|| Number.isNaN(pv.position.z)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return satrec;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validates a NORAD TLE line pair with format checks and propagation gate.
|
|
102
|
+
* @param {string} line1 TLE line 1
|
|
103
|
+
* @param {string} line2 TLE line 2
|
|
104
|
+
* @return {{ ok: 0|1, satrec: object|null, errors: string[] }}
|
|
105
|
+
*/
|
|
106
|
+
const validateTle = (line1, line2) => {
|
|
107
|
+
const l1 = String(line1 ?? "").trim();
|
|
108
|
+
const l2 = String(line2 ?? "").trim();
|
|
109
|
+
const errors = [];
|
|
110
|
+
const tleStatus = new TLEValidator().validate(l1, l2);
|
|
111
|
+
if (tleStatus.lineOneStatus !== null) {
|
|
112
|
+
errors.push(tleStatus.lineOneStatus);
|
|
113
|
+
}
|
|
114
|
+
if (tleStatus.lineTwoStatus !== null) {
|
|
115
|
+
errors.push(tleStatus.lineTwoStatus);
|
|
116
|
+
}
|
|
117
|
+
if (errors.length > 0) {
|
|
118
|
+
return {ok: 0, satrec: null, errors};
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const satrec = propagateTleToSatrec(l1, l2);
|
|
122
|
+
if (!satrec) {
|
|
123
|
+
return {ok: 0, satrec: null, errors: [TLE_ERRORS.PROPAGATE_FAILED]};
|
|
124
|
+
}
|
|
125
|
+
return {ok: 1, satrec, errors: []};
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.error(e);
|
|
128
|
+
return {ok: 0, satrec: null, errors: [TLE_ERRORS.PROPAGATE_FAILED]};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
80
132
|
/**
|
|
81
133
|
* Validates the output of the satellite.js propagate function.
|
|
82
134
|
* The satellite.js propagate function returns a object with the following properties:
|
|
@@ -1328,9 +1380,9 @@ const GetElsetUdlFromTle = (
|
|
|
1328
1380
|
descriptor = null,
|
|
1329
1381
|
) => {
|
|
1330
1382
|
try {
|
|
1331
|
-
|
|
1332
|
-
if (!
|
|
1333
|
-
throw new Error("
|
|
1383
|
+
const tleResult = validateTle(l1, l2);
|
|
1384
|
+
if (!tleResult.ok) {
|
|
1385
|
+
throw new Error(tleResult.errors.join("; "));
|
|
1334
1386
|
}
|
|
1335
1387
|
|
|
1336
1388
|
const elset = {};
|
|
@@ -1833,8 +1885,8 @@ const assembleLeoRpoResult = (s, pEphem, sEphem, includeRIC=false) => {
|
|
|
1833
1885
|
|
|
1834
1886
|
const getLeoRpoData = (line1, line2, sats, startTime, endTime, includeRIC=false) => {
|
|
1835
1887
|
const results = [];
|
|
1836
|
-
const
|
|
1837
|
-
if (!
|
|
1888
|
+
const tleResult = validateTle(line1, line2);
|
|
1889
|
+
if (!tleResult.ok) {return results;}
|
|
1838
1890
|
|
|
1839
1891
|
const start = new Date(startTime).getTime();
|
|
1840
1892
|
const end = new Date(endTime).getTime();
|
|
@@ -3474,6 +3526,11 @@ export {
|
|
|
3474
3526
|
getLonAndDrift,
|
|
3475
3527
|
getRaanPrecession,
|
|
3476
3528
|
checkTle,
|
|
3529
|
+
validateTle,
|
|
3530
|
+
propagateTleToSatrec,
|
|
3531
|
+
TLEValidator,
|
|
3532
|
+
MultiLineTLEValidator,
|
|
3533
|
+
TLE_ERRORS,
|
|
3477
3534
|
sunPosAt,
|
|
3478
3535
|
distGeodetic,
|
|
3479
3536
|
getSemiMajorAxis,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import TLEValidator from "./TLEValidator.js";
|
|
2
|
+
import {TLE_ERRORS} from "./tleErrors.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validates multiple TLE lines, pairing line 1 with line 2.
|
|
6
|
+
* Logic derived from whatswrongwithmytle.com (Dr. TS Kelso's field guide).
|
|
7
|
+
*/
|
|
8
|
+
class MultiLineTLEValidator {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.reset();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
reset() {
|
|
14
|
+
this.recentLine = null;
|
|
15
|
+
this.pairs = [];
|
|
16
|
+
this.orphans = [];
|
|
17
|
+
this.tleValidator = new TLEValidator();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
validateLines(lines) {
|
|
21
|
+
this.reset();
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
this.validateLine(line);
|
|
24
|
+
}
|
|
25
|
+
this.flushRecentLine();
|
|
26
|
+
return this.getResults();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
flushRecentLine() {
|
|
30
|
+
if (this.recentLine === null) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (this.recentLine.startsWith("1 ")) {
|
|
34
|
+
this.orphans.push({line: this.recentLine, error: TLE_ERRORS.ORPHAN_LINE1});
|
|
35
|
+
} else if (this.recentLine.startsWith("2 ")) {
|
|
36
|
+
this.orphans.push({line: this.recentLine, error: TLE_ERRORS.ORPHAN_LINE2});
|
|
37
|
+
} else {
|
|
38
|
+
this.orphans.push({line: this.recentLine, error: TLE_ERRORS.ORPHAN_LINE1});
|
|
39
|
+
}
|
|
40
|
+
this.recentLine = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validateLine(line = "") {
|
|
44
|
+
const sanitized = this.sanitizeLine(line);
|
|
45
|
+
if (sanitized === null) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (this.recentLine === null) {
|
|
50
|
+
this.recentLine = sanitized;
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (this.recentLine.startsWith("1 ")) {
|
|
55
|
+
if (sanitized.startsWith("2 ")) {
|
|
56
|
+
this.recordPair(this.recentLine, sanitized);
|
|
57
|
+
this.recentLine = null;
|
|
58
|
+
} else {
|
|
59
|
+
this.orphans.push({line: this.recentLine, error: TLE_ERRORS.ORPHAN_LINE1});
|
|
60
|
+
this.recentLine = sanitized;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.recentLine.startsWith("2 ")) {
|
|
66
|
+
this.orphans.push({line: this.recentLine, error: TLE_ERRORS.ORPHAN_LINE2});
|
|
67
|
+
this.recentLine = sanitized;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.orphans.push({line: this.recentLine, error: TLE_ERRORS.ORPHAN_LINE1});
|
|
72
|
+
this.recentLine = sanitized;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
recordPair(line1, line2) {
|
|
77
|
+
const tleStatus = this.tleValidator.validate(line1, line2);
|
|
78
|
+
const errors = [];
|
|
79
|
+
if (tleStatus.lineOneStatus !== null) {
|
|
80
|
+
errors.push(tleStatus.lineOneStatus);
|
|
81
|
+
}
|
|
82
|
+
if (tleStatus.lineTwoStatus !== null) {
|
|
83
|
+
errors.push(tleStatus.lineTwoStatus);
|
|
84
|
+
}
|
|
85
|
+
this.pairs.push({
|
|
86
|
+
line1,
|
|
87
|
+
line2,
|
|
88
|
+
ok: errors.length === 0,
|
|
89
|
+
errors,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
sanitizeLine(line) {
|
|
94
|
+
if (line.startsWith("✅") || line.startsWith("🚩")) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (line.includes("✅")) {
|
|
98
|
+
return line.slice(0, line.indexOf("✅"));
|
|
99
|
+
}
|
|
100
|
+
if (line.includes("🚩")) {
|
|
101
|
+
return line.slice(0, line.indexOf("🚩"));
|
|
102
|
+
}
|
|
103
|
+
return line;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getResults() {
|
|
107
|
+
return {
|
|
108
|
+
pairs: this.pairs,
|
|
109
|
+
orphans: this.orphans,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default MultiLineTLEValidator;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {formatChecksumError, formatLengthError, TLE_ERRORS} from "./tleErrors.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NORAD TLE line-pair validator.
|
|
5
|
+
* Logic derived from whatswrongwithmytle.com (Dr. TS Kelso's field guide).
|
|
6
|
+
*/
|
|
7
|
+
class TLEValidator {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.lineOneRegexCatalogNumber = /[0-Z]{5}[CSU]/;
|
|
10
|
+
this.lineOneRegexInternationalDesignator = / (\d{5}[A-Z][ A-Z]{2}|\d{5}[ A-Z]{2}[A-Z]| {8})/;
|
|
11
|
+
this.lineOneRegexEpoch = / \d{5}\.\d{8}/;
|
|
12
|
+
this.lineOneRegexDMeanMotion = / [ +-]\.\d{8}/;
|
|
13
|
+
this.lineOneRegexDDMeanMotion = / [ +-]\d{5}[-+]\d/;
|
|
14
|
+
this.lineOneRegexBSTAR = / [ +-]\d{5}[-+]\d/;
|
|
15
|
+
this.lineOneRegexEphemType = / [0-5]/;
|
|
16
|
+
this.lineOneRegexSetNumber = / [ \d]{3}\d/;
|
|
17
|
+
|
|
18
|
+
this.lineTwoRegexCatalogNumber = /[0-Z]{5}/;
|
|
19
|
+
this.lineTwoRegexInclination = / [ \d]{3}\.\d{4}/;
|
|
20
|
+
this.lineTwoRegexRAAN = / [ \d]{3}\.\d{4}/;
|
|
21
|
+
this.lineTwoRegexEccentricity = / \d{7}/;
|
|
22
|
+
this.lineTwoRegexArgumentOfPerigee = / [ \d]{3}\.\d{4}/;
|
|
23
|
+
this.lineTwoRegexMeanAnomaly = / [ \d]{3}\.\d{4}/;
|
|
24
|
+
this.lineTwoRegexMeanMotion = / [ \d]{2}\.\d{8}/;
|
|
25
|
+
this.lineTwoRegexRevNumber = /[ \d]{4}\d/;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
computeChecksum(line) {
|
|
29
|
+
let sum = 0;
|
|
30
|
+
for (let i = 0; i < 68; i += 1) {
|
|
31
|
+
const char = line[i];
|
|
32
|
+
const val = Number.parseInt(char, 10);
|
|
33
|
+
if (!Number.isNaN(val)) {
|
|
34
|
+
sum += val;
|
|
35
|
+
} else if (char === "-") {
|
|
36
|
+
sum += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return sum % 10;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
validate(lineOne, lineTwo) {
|
|
43
|
+
const lineOneStatus = this.validateLineOne(lineOne);
|
|
44
|
+
const lineTwoStatus = this.validateLineTwo(lineTwo);
|
|
45
|
+
if (lineOneStatus === null
|
|
46
|
+
&& lineTwoStatus === null
|
|
47
|
+
&& lineOne.slice(2, 7) !== lineTwo.slice(2, 7)) {
|
|
48
|
+
return {
|
|
49
|
+
lineOneStatus: null,
|
|
50
|
+
lineTwoStatus: TLE_ERRORS.CATALOG_MISMATCH,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {lineOneStatus, lineTwoStatus};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
validateLineOne(line) {
|
|
57
|
+
if (line.length < 69) {
|
|
58
|
+
return formatLengthError(TLE_ERRORS.LINE1_LENGTH, line.length);
|
|
59
|
+
}
|
|
60
|
+
if (!this.lineOneRegexCatalogNumber.test(line.slice(2, 8))) {
|
|
61
|
+
return TLE_ERRORS.LINE1_CATALOG_NUMBER;
|
|
62
|
+
}
|
|
63
|
+
if (!this.lineOneRegexInternationalDesignator.test(line.slice(8, 17))) {
|
|
64
|
+
return TLE_ERRORS.LINE1_INTERNATIONAL_DESIGNATOR;
|
|
65
|
+
}
|
|
66
|
+
if (!this.lineOneRegexEpoch.test(line.slice(17, 32))) {
|
|
67
|
+
return TLE_ERRORS.LINE1_EPOCH;
|
|
68
|
+
}
|
|
69
|
+
if (!this.lineOneRegexDMeanMotion.test(line.slice(32, 43))) {
|
|
70
|
+
return TLE_ERRORS.LINE1_D_MEAN_MOTION;
|
|
71
|
+
}
|
|
72
|
+
if (!this.lineOneRegexDDMeanMotion.test(line.slice(43, 52))) {
|
|
73
|
+
return TLE_ERRORS.LINE1_DD_MEAN_MOTION;
|
|
74
|
+
}
|
|
75
|
+
if (!this.lineOneRegexBSTAR.test(line.slice(52, 61))) {
|
|
76
|
+
return TLE_ERRORS.LINE1_BSTAR;
|
|
77
|
+
}
|
|
78
|
+
if (!this.lineOneRegexEphemType.test(line.slice(61, 63))) {
|
|
79
|
+
return TLE_ERRORS.LINE1_EPHEM_TYPE;
|
|
80
|
+
}
|
|
81
|
+
if (!this.lineOneRegexSetNumber.test(line.slice(63, 68))) {
|
|
82
|
+
return TLE_ERRORS.LINE1_SET_NUMBER;
|
|
83
|
+
}
|
|
84
|
+
if (line.length > 69) {
|
|
85
|
+
return TLE_ERRORS.LINE1_EXTRA_CHARS;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const checksum = this.computeChecksum(line);
|
|
89
|
+
if (checksum !== Number.parseInt(line[68], 10)) {
|
|
90
|
+
return formatChecksumError(TLE_ERRORS.LINE1_CHECKSUM, checksum);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
validateLineTwo(line) {
|
|
96
|
+
if (line.length < 69) {
|
|
97
|
+
return formatLengthError(TLE_ERRORS.LINE2_LENGTH, line.length);
|
|
98
|
+
}
|
|
99
|
+
if (!this.lineTwoRegexCatalogNumber.test(line.slice(2, 7))) {
|
|
100
|
+
return TLE_ERRORS.LINE2_CATALOG_NUMBER;
|
|
101
|
+
}
|
|
102
|
+
if (!this.lineTwoRegexInclination.test(line.slice(7, 16))) {
|
|
103
|
+
return TLE_ERRORS.LINE2_INCLINATION;
|
|
104
|
+
}
|
|
105
|
+
if (!this.lineTwoRegexRAAN.test(line.slice(16, 25))) {
|
|
106
|
+
return TLE_ERRORS.LINE2_RAAN;
|
|
107
|
+
}
|
|
108
|
+
if (!this.lineTwoRegexEccentricity.test(line.slice(25, 33))) {
|
|
109
|
+
return TLE_ERRORS.LINE2_ECCENTRICITY;
|
|
110
|
+
}
|
|
111
|
+
if (!this.lineTwoRegexArgumentOfPerigee.test(line.slice(33, 42))) {
|
|
112
|
+
return TLE_ERRORS.LINE2_ARGUMENT_OF_PERIGEE;
|
|
113
|
+
}
|
|
114
|
+
if (!this.lineTwoRegexMeanAnomaly.test(line.slice(42, 51))) {
|
|
115
|
+
return TLE_ERRORS.LINE2_MEAN_ANOMALY;
|
|
116
|
+
}
|
|
117
|
+
if (!this.lineTwoRegexMeanMotion.test(line.slice(51, 63))) {
|
|
118
|
+
return TLE_ERRORS.LINE2_MEAN_MOTION;
|
|
119
|
+
}
|
|
120
|
+
if (!this.lineTwoRegexRevNumber.test(line.slice(63, 68))) {
|
|
121
|
+
return TLE_ERRORS.LINE2_REV_NUMBER;
|
|
122
|
+
}
|
|
123
|
+
if (line.length > 69) {
|
|
124
|
+
return TLE_ERRORS.LINE2_EXTRA_CHARS;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const checksum = this.computeChecksum(line);
|
|
128
|
+
if (checksum !== Number.parseInt(line[68], 10)) {
|
|
129
|
+
return formatChecksumError(TLE_ERRORS.LINE2_CHECKSUM, checksum);
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default TLEValidator;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLE validation error message constants.
|
|
3
|
+
* Logic derived from whatswrongwithmytle.com (Dr. TS Kelso's field guide).
|
|
4
|
+
*/
|
|
5
|
+
export const TLE_ERRORS = {
|
|
6
|
+
LINE1_LENGTH: "TLE line 1 must be 69 characters",
|
|
7
|
+
LINE2_LENGTH: "TLE line 2 must be 69 characters",
|
|
8
|
+
LINE1_EXTRA_CHARS: "TLE line 1: extra character(s) detected",
|
|
9
|
+
LINE2_EXTRA_CHARS: "TLE line 2: extra character(s) detected",
|
|
10
|
+
LINE1_CATALOG_NUMBER: "TLE line 1: fix catalog number",
|
|
11
|
+
LINE1_INTERNATIONAL_DESIGNATOR: "TLE line 1: fix international designator or leave it blank",
|
|
12
|
+
LINE1_EPOCH: "TLE line 1: fix epoch",
|
|
13
|
+
LINE1_D_MEAN_MOTION: "TLE line 1: fix first derivative of mean motion",
|
|
14
|
+
LINE1_DD_MEAN_MOTION: "TLE line 1: fix second derivative of mean motion",
|
|
15
|
+
LINE1_BSTAR: "TLE line 1: fix BSTAR",
|
|
16
|
+
LINE1_EPHEM_TYPE: "TLE line 1: ephemeris type should be zero",
|
|
17
|
+
LINE1_SET_NUMBER: "TLE line 1: fix element set number",
|
|
18
|
+
LINE1_CHECKSUM: "TLE line 1: checksum should be",
|
|
19
|
+
LINE2_CATALOG_NUMBER: "TLE line 2: fix catalog number",
|
|
20
|
+
LINE2_INCLINATION: "TLE line 2: fix inclination",
|
|
21
|
+
LINE2_RAAN: "TLE line 2: fix right ascension",
|
|
22
|
+
LINE2_ECCENTRICITY: "TLE line 2: fix eccentricity",
|
|
23
|
+
LINE2_ARGUMENT_OF_PERIGEE: "TLE line 2: fix argument of perigee",
|
|
24
|
+
LINE2_MEAN_ANOMALY: "TLE line 2: fix mean anomaly",
|
|
25
|
+
LINE2_MEAN_MOTION: "TLE line 2: fix mean motion",
|
|
26
|
+
LINE2_REV_NUMBER: "TLE line 2: fix revolution number",
|
|
27
|
+
LINE2_CHECKSUM: "TLE line 2: checksum should be",
|
|
28
|
+
CATALOG_MISMATCH: "TLE catalog numbers must match between lines",
|
|
29
|
+
ORPHAN_LINE1: "Found line 1, missing line 2",
|
|
30
|
+
ORPHAN_LINE2: "Found line 2, missing line one",
|
|
31
|
+
PROPAGATE_FAILED: "TLE propagation failed",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const formatLengthError = (baseMessage, length) => `${baseMessage} (got ${length})`;
|
|
35
|
+
|
|
36
|
+
export const formatChecksumError = (baseMessage, checksum) => `${baseMessage} ${checksum}`;
|
package/src/udl.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {fixDate} from "./fixDate.js";
|
|
2
2
|
import {
|
|
3
3
|
calcRegime,
|
|
4
|
+
cartesianToKeplerian,
|
|
4
5
|
getElsetUdlFromTle,
|
|
5
6
|
getLonAndDrift,
|
|
6
7
|
getRaanPrecession,
|
|
@@ -310,4 +311,83 @@ const enrichUdlFields = (npsOb) => {
|
|
|
310
311
|
return npsOb;
|
|
311
312
|
};
|
|
312
313
|
|
|
313
|
-
|
|
314
|
+
const getSVSatno = (udlRow) => {
|
|
315
|
+
if (udlRow.satNo) {return udlRow.satNo;}
|
|
316
|
+
if (udlRow.idOnOrbit) {return udlRow.idOnOrbit;}
|
|
317
|
+
if (!udlRow.origObjectId) {return null;}
|
|
318
|
+
|
|
319
|
+
const id = udlRow.origObjectId;
|
|
320
|
+
if (
|
|
321
|
+
/^\d+$/.test(id)
|
|
322
|
+
|| /^\d+~~.+$/.test(id) // KBR
|
|
323
|
+
) {
|
|
324
|
+
return Number.parseInt(id); // parse int will ignore everything after an invalid character
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return null;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const udlToNpsState = (udlRow) => {
|
|
331
|
+
const Xpos = _.get(udlRow, "xpos", null); // kilometers
|
|
332
|
+
const Ypos = _.get(udlRow, "ypos", null);
|
|
333
|
+
const Zpos = _.get(udlRow, "zpos", null);
|
|
334
|
+
|
|
335
|
+
const Xvel = _.get(udlRow, "xvel", null); // kilometers/second
|
|
336
|
+
const Yvel = _.get(udlRow, "yvel", null);
|
|
337
|
+
const Zvel = _.get(udlRow, "zvel", null);
|
|
338
|
+
const cov = _.get(udlRow, "cov", null );
|
|
339
|
+
const covRefFrame = _.get(udlRow, "covReferenceFrame", null );
|
|
340
|
+
let keplerians = {a: null, e: null, i: null, raan: null, w: null, f: null};
|
|
341
|
+
|
|
342
|
+
if (Xpos !== null && Ypos !== null && Zpos !== null
|
|
343
|
+
&& Xvel !== null && Yvel !== null && Zvel !== null) {
|
|
344
|
+
const KM_TO_M = 1000;
|
|
345
|
+
|
|
346
|
+
const pos = [Xpos * KM_TO_M, Ypos * KM_TO_M, Zpos * KM_TO_M]; // Position, in meters
|
|
347
|
+
const vel = [Xvel * KM_TO_M, Yvel * KM_TO_M, Zvel * KM_TO_M]; // Velocity, in m/s
|
|
348
|
+
|
|
349
|
+
// [ a, e, i, Ω, ω, θ ]
|
|
350
|
+
// sma (km), eccentricity, inclination (deg), raan (deg), arg of perigee (deg), true anomaly (deg)
|
|
351
|
+
keplerians = cartesianToKeplerian(pos, vel);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const npsState = lowerCaseObjectKeys({
|
|
355
|
+
satno: getSVSatno(udlRow),
|
|
356
|
+
Epoch: fixDate(_.get(udlRow, "epoch", null)),
|
|
357
|
+
idStateVector: _.get(udlRow, "idStateVector", null),
|
|
358
|
+
classificationMarking: _.get(udlRow, "classificationMarking", null),
|
|
359
|
+
referenceFrame: _.get(udlRow, "referenceFrame", null),
|
|
360
|
+
Pedigree: _.get(udlRow, "pedigree", null),
|
|
361
|
+
uct: _.get(udlRow, "uct", null),
|
|
362
|
+
idOnOrbit: _.get(udlRow, "idOnOrbit", null),
|
|
363
|
+
origObjectId: _.get(udlRow, "origObjectId", null),
|
|
364
|
+
descriptor: _.get(udlRow, "descriptor", null),
|
|
365
|
+
Xpos: Xpos,
|
|
366
|
+
Ypos: Ypos,
|
|
367
|
+
Zpos: Zpos,
|
|
368
|
+
Xvel: Xvel,
|
|
369
|
+
Yvel: Yvel,
|
|
370
|
+
Zvel: Zvel,
|
|
371
|
+
Covariance: JSON.stringify(cov),
|
|
372
|
+
CovarianceRefFrame: covRefFrame,
|
|
373
|
+
SemiMajorAxis: keplerians.a,
|
|
374
|
+
Eccentricity: keplerians.e,
|
|
375
|
+
Inclination: keplerians.i,
|
|
376
|
+
Raan: keplerians.raan,
|
|
377
|
+
ArgOfPerigee: keplerians.w,
|
|
378
|
+
TrueAnomaly: keplerians.f,
|
|
379
|
+
|
|
380
|
+
source: _.get(udlRow, "source", null),
|
|
381
|
+
dataMode: _.get(udlRow, "dataMode", null),
|
|
382
|
+
createdat: fixDate(_.get(udlRow, "createdAt", null)),
|
|
383
|
+
|
|
384
|
+
Latitude: null,
|
|
385
|
+
Longitude: null,
|
|
386
|
+
LonDriftDegreesPerDay: null,
|
|
387
|
+
RaanPrecessionDegreesPerDay: null,
|
|
388
|
+
Regime: 1,
|
|
389
|
+
});
|
|
390
|
+
return npsState;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
export {udlToNpsElset, udlToNpsGroundSite, udlToNpsConjunction, udlToNpsState, enrichUdlFields};
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
buildWaterfallBreakpoints,
|
|
14
14
|
decorateWaterfallSegment,
|
|
15
15
|
assembleWaterfallRows,
|
|
16
|
-
|
|
16
|
+
validateTle,
|
|
17
17
|
getLonAndDrift,
|
|
18
18
|
getEclipseStatus,
|
|
19
19
|
} from "../astro.js";
|
|
@@ -53,8 +53,8 @@ import {RAD2DEG} from "../constants.js";
|
|
|
53
53
|
*/
|
|
54
54
|
const getLeoRpoDataBulk = async (line1, line2, sats, startTime, endTime, includeRIC=false) => {
|
|
55
55
|
const results = [];
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
56
|
+
const tleResult = validateTle(line1, line2);
|
|
57
|
+
if (!tleResult.ok) {return results;}
|
|
58
58
|
|
|
59
59
|
const start = new Date(startTime).getTime();
|
|
60
60
|
const end = new Date(endTime).getTime();
|