ootk 6.0.4 → 6.0.7
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/dist/main.cjs +462 -46
- package/dist/main.d.cts +307 -17
- package/dist/main.d.ts +307 -17
- package/dist/main.js +457 -47
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -79,6 +79,7 @@ var AngularDistanceMethod = /* @__PURE__ */ ((AngularDistanceMethod2) => {
|
|
|
79
79
|
|
|
80
80
|
// src/enums/CatalogSource.ts
|
|
81
81
|
var CatalogSource = /* @__PURE__ */ ((CatalogSource2) => {
|
|
82
|
+
CatalogSource2["UNKNOWN"] = "unknown";
|
|
82
83
|
CatalogSource2["USSF"] = "spacetrack";
|
|
83
84
|
CatalogSource2["CELESTRAK"] = "celestrak";
|
|
84
85
|
CatalogSource2["CELESTRAK_SUP"] = "celestrak-sup";
|
|
@@ -300,7 +301,7 @@ var Epoch = class {
|
|
|
300
301
|
const currentDateObj = this.toDateTime();
|
|
301
302
|
const epochYear = currentDateObj.getUTCFullYear().toString().slice(2, 4);
|
|
302
303
|
const epochDay = this.getDayOfYear_(currentDateObj);
|
|
303
|
-
const timeOfDay = (currentDateObj.getUTCHours() *
|
|
304
|
+
const timeOfDay = (currentDateObj.getUTCHours() * 3600 + currentDateObj.getUTCMinutes() * 60 + currentDateObj.getUTCSeconds() + currentDateObj.getUTCMilliseconds() / 1e3) / 86400;
|
|
304
305
|
const epochDayStr = (epochDay + timeOfDay).toFixed(8).padStart(12, "0");
|
|
305
306
|
return {
|
|
306
307
|
epochYr: epochYear,
|
|
@@ -2245,6 +2246,32 @@ function createSampleCovarianceFromTle(tleLine1, tleLine2, frame = "ric" /* RIC
|
|
|
2245
2246
|
const sample = new CovarianceSample(j2000State, covariance2, tle);
|
|
2246
2247
|
return frame === "ric" /* RIC */ ? sample.desampleRIC() : sample.desampleJ2000();
|
|
2247
2248
|
}
|
|
2249
|
+
var DEFAULT_SCREENING_SIGMA_CAPS = [1200, 1e3, 5e3];
|
|
2250
|
+
function cappedScreeningCovarianceFromTle(tleLine1, tleLine2, confidenceLevel, caps = DEFAULT_SCREENING_SIGMA_CAPS) {
|
|
2251
|
+
let elements;
|
|
2252
|
+
try {
|
|
2253
|
+
elements = createSampleCovarianceFromTle(tleLine1, tleLine2).matrix.elements;
|
|
2254
|
+
} catch {
|
|
2255
|
+
return buildFallbackScreeningCovariance_(caps);
|
|
2256
|
+
}
|
|
2257
|
+
const radialVar = elements[0][0];
|
|
2258
|
+
const intrackVar = elements[1][1];
|
|
2259
|
+
const crosstrackVar = elements[2][2];
|
|
2260
|
+
elements[0][0] = Math.min(Math.sqrt(radialVar) * confidenceLevel, caps[0]);
|
|
2261
|
+
elements[1][1] = Math.min(Math.sqrt(crosstrackVar) * confidenceLevel, caps[1]);
|
|
2262
|
+
elements[2][2] = Math.min(Math.sqrt(intrackVar) * confidenceLevel, caps[2]);
|
|
2263
|
+
if (!elements[0][0] || !elements[1][1] || !elements[2][2]) {
|
|
2264
|
+
return buildFallbackScreeningCovariance_(caps);
|
|
2265
|
+
}
|
|
2266
|
+
return new StateCovariance(new Matrix(elements), "eci" /* ECI */);
|
|
2267
|
+
}
|
|
2268
|
+
function buildFallbackScreeningCovariance_(caps) {
|
|
2269
|
+
const fallback = Array.from({ length: 6 }, () => new Array(6).fill(0));
|
|
2270
|
+
fallback[0][0] = caps[0];
|
|
2271
|
+
fallback[1][1] = caps[1];
|
|
2272
|
+
fallback[2][2] = caps[2];
|
|
2273
|
+
return new StateCovariance(new Matrix(fallback), "eci" /* ECI */);
|
|
2274
|
+
}
|
|
2248
2275
|
|
|
2249
2276
|
// src/utils/jacobian.ts
|
|
2250
2277
|
var jacobian = (f, m, x0, step = 1e-5) => {
|
|
@@ -2271,6 +2298,17 @@ function linearDistance(pos1, pos2) {
|
|
|
2271
2298
|
return Math.sqrt((pos1.x - pos2.x) ** 2 + (pos1.y - pos2.y) ** 2 + (pos1.z - pos2.z) ** 2);
|
|
2272
2299
|
}
|
|
2273
2300
|
|
|
2301
|
+
// src/utils/photometry.ts
|
|
2302
|
+
var RCS_VMAG_ESTIMATE_MIN = -5;
|
|
2303
|
+
var RCS_VMAG_ESTIMATE_MAX = 15;
|
|
2304
|
+
function estimateVmagFromRcs(rcs) {
|
|
2305
|
+
if (typeof rcs !== "number" || !Number.isFinite(rcs) || rcs <= 0) {
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
const vmag = -1.3 - 2.5 * Math.log10(rcs);
|
|
2309
|
+
return Math.min(Math.max(vmag, RCS_VMAG_ESTIMATE_MIN), RCS_VMAG_ESTIMATE_MAX);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2274
2312
|
// src/operations/Vector3D.ts
|
|
2275
2313
|
var Vector3D = class _Vector3D {
|
|
2276
2314
|
constructor(x, y, z) {
|
|
@@ -3255,7 +3293,7 @@ var Random = class {
|
|
|
3255
3293
|
}
|
|
3256
3294
|
};
|
|
3257
3295
|
|
|
3258
|
-
//
|
|
3296
|
+
// node_modules/astronomy-engine/esm/astronomy.js
|
|
3259
3297
|
var C_AUDAY = 173.1446326846693;
|
|
3260
3298
|
var KM_PER_AU2 = 14959787069098932e-8;
|
|
3261
3299
|
var DEG2RAD2 = 0.017453292519943295;
|
|
@@ -9028,6 +9066,7 @@ var OemParser = class _OemParser {
|
|
|
9028
9066
|
let currentBlock = null;
|
|
9029
9067
|
let currentMetadata = {};
|
|
9030
9068
|
let metadataComments = [];
|
|
9069
|
+
let userDefined = {};
|
|
9031
9070
|
let inMeta = false;
|
|
9032
9071
|
let inData = false;
|
|
9033
9072
|
let inCovariance = false;
|
|
@@ -9036,6 +9075,7 @@ var OemParser = class _OemParser {
|
|
|
9036
9075
|
if (line === "META_START") {
|
|
9037
9076
|
currentMetadata = {};
|
|
9038
9077
|
metadataComments = [];
|
|
9078
|
+
userDefined = {};
|
|
9039
9079
|
inMeta = true;
|
|
9040
9080
|
continue;
|
|
9041
9081
|
}
|
|
@@ -9043,6 +9083,9 @@ var OemParser = class _OemParser {
|
|
|
9043
9083
|
if (metadataComments.length > 0) {
|
|
9044
9084
|
currentMetadata.COMMENT = metadataComments;
|
|
9045
9085
|
}
|
|
9086
|
+
if (Object.keys(userDefined).length > 0) {
|
|
9087
|
+
currentMetadata.USER_DEFINED = userDefined;
|
|
9088
|
+
}
|
|
9046
9089
|
currentBlock = {
|
|
9047
9090
|
metadata: currentMetadata,
|
|
9048
9091
|
ephemeris: []
|
|
@@ -9081,7 +9124,9 @@ var OemParser = class _OemParser {
|
|
|
9081
9124
|
const eqIndex = line.indexOf("=");
|
|
9082
9125
|
const key = line.substring(0, eqIndex).trim();
|
|
9083
9126
|
const value = line.substring(eqIndex + 1).trim();
|
|
9084
|
-
if (key
|
|
9127
|
+
if (key.startsWith("USER_DEFINED_")) {
|
|
9128
|
+
userDefined[key.substring(13)] = value;
|
|
9129
|
+
} else if (key === "INTERPOLATION_DEGREE") {
|
|
9085
9130
|
currentMetadata[key] = parseInt(value, 10);
|
|
9086
9131
|
} else {
|
|
9087
9132
|
currentMetadata[key] = value;
|
|
@@ -9849,7 +9894,10 @@ var FormatTle = class _FormatTle {
|
|
|
9849
9894
|
*/
|
|
9850
9895
|
static createTle(tleParams) {
|
|
9851
9896
|
const { inc, meanmo, rasc, argPe, meana, ecen, epochyr, epochday, intl } = tleParams;
|
|
9852
|
-
|
|
9897
|
+
let scc = Tle.convert6DigitToA5(tleParams.scc);
|
|
9898
|
+
if (/^\d{1,4}$/u.test(scc)) {
|
|
9899
|
+
scc = scc.padStart(5, "0");
|
|
9900
|
+
}
|
|
9853
9901
|
const epochYrStr = String(epochyr).padStart(2, "0");
|
|
9854
9902
|
const epochdayStr = Number(epochday).toFixed(8).padStart(12, "0");
|
|
9855
9903
|
const incStr = _FormatTle.inclination(inc);
|
|
@@ -10362,7 +10410,7 @@ var Sgp4 = class _Sgp4 {
|
|
|
10362
10410
|
error: 0 /* NO_ERROR */
|
|
10363
10411
|
};
|
|
10364
10412
|
const xpdotp = 1440 / (2 * PI);
|
|
10365
|
-
satrec.satnum = tleLine1.substring(2, 7);
|
|
10413
|
+
satrec.satnum = tleLine1.charAt(7) === "V" ? "0" : tleLine1.substring(2, 7);
|
|
10366
10414
|
satrec.epochyr = parseInt(tleLine1.substring(18, 20));
|
|
10367
10415
|
satrec.epochdays = parseFloat(tleLine1.substring(20, 32));
|
|
10368
10416
|
satrec.ndot = parseFloat(tleLine1.substring(33, 43));
|
|
@@ -13880,7 +13928,7 @@ var Tle = class _Tle {
|
|
|
13880
13928
|
this.line1 = line1;
|
|
13881
13929
|
this.line2 = line2;
|
|
13882
13930
|
this.epoch = _Tle.parseEpoch_(line1.substring(18, 32));
|
|
13883
|
-
this.satnum =
|
|
13931
|
+
this.satnum = _Tle.satNum(this.line1);
|
|
13884
13932
|
this.satrec_ = Sgp4.createSatrec(line1, line2, gravConst, opsMode);
|
|
13885
13933
|
}
|
|
13886
13934
|
toString() {
|
|
@@ -13969,7 +14017,7 @@ ${this.line2}`;
|
|
|
13969
14017
|
epochYearFull = 1900 + epochYearShort;
|
|
13970
14018
|
}
|
|
13971
14019
|
const epochJday = epochDayOfYear + epochYearFull * 365;
|
|
13972
|
-
const currentJday = getDayOfYear() + currentYearFull * 365;
|
|
14020
|
+
const currentJday = getDayOfYear(nowInput) + currentYearFull * 365;
|
|
13973
14021
|
const currentTime = (nowInput.getUTCHours() * 3600 + nowInput.getUTCMinutes() * 60 + nowInput.getUTCSeconds()) / 86400;
|
|
13974
14022
|
const daysOld = currentJday + currentTime - epochJday;
|
|
13975
14023
|
switch (outputUnits) {
|
|
@@ -14117,7 +14165,7 @@ ${this.line2}`;
|
|
|
14117
14165
|
const BSTAR_PART_3 = _Tle.bstar_.start + 6;
|
|
14118
14166
|
const BSTAR_PART_4 = _Tle.bstar_.stop - 1;
|
|
14119
14167
|
const bstarSymbol = tleLine1.substring(_Tle.bstar_.start, BSTAR_PART_2);
|
|
14120
|
-
|
|
14168
|
+
const mantissaFraction = tleLine1.substring(BSTAR_PART_2, BSTAR_PART_3);
|
|
14121
14169
|
const exponentSymbol = tleLine1.substring(BSTAR_PART_3, BSTAR_PART_4);
|
|
14122
14170
|
let exponent = parseInt(tleLine1.substring(BSTAR_PART_4, _Tle.bstar_.stop));
|
|
14123
14171
|
if (exponentSymbol === "-") {
|
|
@@ -14125,12 +14173,16 @@ ${this.line2}`;
|
|
|
14125
14173
|
} else if (exponentSymbol !== "+" && exponentSymbol !== " " && exponentSymbol !== "0") {
|
|
14126
14174
|
throw new ParseError(`Invalid BSTAR exponent symbol: ${exponentSymbol}`, "TLE");
|
|
14127
14175
|
}
|
|
14128
|
-
|
|
14176
|
+
let sign2 = 1;
|
|
14177
|
+
let integerPart = "0";
|
|
14129
14178
|
if (bstarSymbol === "-") {
|
|
14130
|
-
|
|
14131
|
-
} else if (bstarSymbol === "+" || bstarSymbol === " " || bstarSymbol === "0") ; else {
|
|
14179
|
+
sign2 = -1;
|
|
14180
|
+
} else if (bstarSymbol === "+" || bstarSymbol === " " || bstarSymbol === "0") ; else if (/^\d$/u.test(bstarSymbol)) {
|
|
14181
|
+
integerPart = bstarSymbol;
|
|
14182
|
+
} else {
|
|
14132
14183
|
throw new ParseError(`Invalid BSTAR symbol: ${bstarSymbol}`, "TLE");
|
|
14133
14184
|
}
|
|
14185
|
+
const bstar1 = sign2 * parseFloat(`${integerPart}.${mantissaFraction}`) * 10 ** exponent;
|
|
14134
14186
|
return toPrecision(bstar1, 14);
|
|
14135
14187
|
}
|
|
14136
14188
|
/**
|
|
@@ -14185,22 +14237,25 @@ ${this.line2}`;
|
|
|
14185
14237
|
}
|
|
14186
14238
|
/**
|
|
14187
14239
|
* Private value - used by United States Space Force to reference the orbit model used to generate the Tle. Will
|
|
14188
|
-
* always be seen as zero externally (e.g. by "us", unless you are "them" - in which case, hello!).
|
|
14240
|
+
* almost always be seen as zero externally (e.g. by "us", unless you are "them" - in which case, hello!).
|
|
14189
14241
|
*
|
|
14190
|
-
*
|
|
14191
|
-
*
|
|
14242
|
+
* A value of 1 is tolerated in addition to 0: the field is informational and does not change how SGP4/SDP4
|
|
14243
|
+
* propagate the element set, and a handful of archival TLEs carry a 1 (originally an SGP marker).
|
|
14244
|
+
*
|
|
14245
|
+
* A value of 4 indicates the SGP4-XP model. Until that source code is released there is no way to support that
|
|
14246
|
+
* format in JavaScript or TypeScript, so it is rejected explicitly. Any other value is treated as malformed.
|
|
14192
14247
|
* @example 0
|
|
14193
14248
|
* @param tleLine1 The first line of the Tle to parse.
|
|
14194
|
-
* @returns The ephemeris type.
|
|
14249
|
+
* @returns The ephemeris type (0 or 1).
|
|
14195
14250
|
*/
|
|
14196
14251
|
static ephemerisType(tleLine1) {
|
|
14197
14252
|
const ephemerisType = parseInt(tleLine1.substring(_Tle.ephemerisType_.start, _Tle.ephemerisType_.stop));
|
|
14198
|
-
if (ephemerisType !== 0 && ephemerisType !== 4) {
|
|
14199
|
-
throw new ParseError(`Invalid ephemeris type: ${ephemerisType}`, "TLE");
|
|
14200
|
-
}
|
|
14201
14253
|
if (ephemerisType === 4) {
|
|
14202
14254
|
throw new ParseError("SGP4-XP ephemeris type is not supported", "TLE");
|
|
14203
14255
|
}
|
|
14256
|
+
if (ephemerisType !== 0 && ephemerisType !== 1) {
|
|
14257
|
+
throw new ParseError(`Invalid ephemeris type: ${ephemerisType}`, "TLE");
|
|
14258
|
+
}
|
|
14204
14259
|
return ephemerisType;
|
|
14205
14260
|
}
|
|
14206
14261
|
/**
|
|
@@ -14431,6 +14486,9 @@ ${this.line2}`;
|
|
|
14431
14486
|
*/
|
|
14432
14487
|
static satNum(tleLine) {
|
|
14433
14488
|
const satNumStr = tleLine.substring(_Tle.satNum_.start, _Tle.satNum_.stop);
|
|
14489
|
+
if (satNumStr.trim().length === 0) {
|
|
14490
|
+
return NaN;
|
|
14491
|
+
}
|
|
14434
14492
|
const sixDigitSatNum = _Tle.convertA5to6Digit(satNumStr);
|
|
14435
14493
|
return parseInt(sixDigitSatNum);
|
|
14436
14494
|
}
|
|
@@ -14521,16 +14579,23 @@ ${this.line2}`;
|
|
|
14521
14579
|
static parse(tleLine1, tleLine2) {
|
|
14522
14580
|
const line1 = _Tle.parseLine1(tleLine1);
|
|
14523
14581
|
const line2 = _Tle.parseLine2(tleLine2);
|
|
14524
|
-
|
|
14582
|
+
const isVimpel = line1.classification === "V";
|
|
14583
|
+
if (!isVimpel && line1.satNum !== line2.satNum) {
|
|
14584
|
+
console.info("Line 1 satNum:", line1.satNum);
|
|
14585
|
+
console.info("Line 2 satNum:", line2.satNum);
|
|
14525
14586
|
throw new ParseError("Satellite numbers do not match between TLE lines", "TLE");
|
|
14526
14587
|
}
|
|
14527
|
-
if (line1.satNumRaw !== line2.satNumRaw) {
|
|
14588
|
+
if (!isVimpel && line1.satNumRaw !== line2.satNumRaw) {
|
|
14589
|
+
console.info("Line 1 satNumRaw:", line1.satNumRaw);
|
|
14590
|
+
console.info("Line 2 satNumRaw:", line2.satNumRaw);
|
|
14528
14591
|
throw new ParseError("Raw satellite numbers do not match between TLE lines", "TLE");
|
|
14529
14592
|
}
|
|
14530
14593
|
if (line1.lineNumber1 !== 1) {
|
|
14594
|
+
console.info("Line 1 line number:", line1.lineNumber1);
|
|
14531
14595
|
throw new ParseError("First TLE line number must be 1", "TLE");
|
|
14532
14596
|
}
|
|
14533
14597
|
if (line2.lineNumber2 !== 2) {
|
|
14598
|
+
console.info("Line 2 line number:", line2.lineNumber2);
|
|
14534
14599
|
throw new ParseError("Second TLE line number must be 2", "TLE");
|
|
14535
14600
|
}
|
|
14536
14601
|
return {
|
|
@@ -14561,10 +14626,11 @@ ${this.line2}`;
|
|
|
14561
14626
|
static parseAll(tleLine1, tleLine2) {
|
|
14562
14627
|
const line1 = _Tle.parseLine1(tleLine1);
|
|
14563
14628
|
const line2 = _Tle.parseLine2(tleLine2);
|
|
14564
|
-
|
|
14629
|
+
const isVimpel = line1.classification === "V";
|
|
14630
|
+
if (!isVimpel && line1.satNum !== line2.satNum) {
|
|
14565
14631
|
throw new ParseError("Satellite numbers do not match between TLE lines", "TLE");
|
|
14566
14632
|
}
|
|
14567
|
-
if (line1.satNumRaw !== line2.satNumRaw) {
|
|
14633
|
+
if (!isVimpel && line1.satNumRaw !== line2.satNumRaw) {
|
|
14568
14634
|
throw new ParseError("Raw satellite numbers do not match between TLE lines", "TLE");
|
|
14569
14635
|
}
|
|
14570
14636
|
if (line1.lineNumber1 !== 1) {
|
|
@@ -14576,9 +14642,44 @@ ${this.line2}`;
|
|
|
14576
14642
|
return { ...line1, ...line2 };
|
|
14577
14643
|
}
|
|
14578
14644
|
/**
|
|
14579
|
-
*
|
|
14580
|
-
*
|
|
14581
|
-
* @
|
|
14645
|
+
* Classifies a satellite catalog number by representation. See {@link SatNumKind}.
|
|
14646
|
+
* Pure function; no side effects.
|
|
14647
|
+
* @param sccNum The catalog number string.
|
|
14648
|
+
* @returns The {@link SatNumKind} describing the input.
|
|
14649
|
+
*/
|
|
14650
|
+
static classifySatNum(sccNum) {
|
|
14651
|
+
if (typeof sccNum !== "string") {
|
|
14652
|
+
return "invalid";
|
|
14653
|
+
}
|
|
14654
|
+
const trimmed = sccNum.trim();
|
|
14655
|
+
if (trimmed.length === 0) {
|
|
14656
|
+
return "invalid";
|
|
14657
|
+
}
|
|
14658
|
+
const first = trimmed[0];
|
|
14659
|
+
if (trimmed.length === 5 && first in _Tle.alpha5_) {
|
|
14660
|
+
return /^\d{4}$/u.test(trimmed.slice(1)) ? "alpha5" : "invalid";
|
|
14661
|
+
}
|
|
14662
|
+
if (!/^\d+$/u.test(trimmed)) {
|
|
14663
|
+
return "invalid";
|
|
14664
|
+
}
|
|
14665
|
+
if (trimmed.length <= 5) {
|
|
14666
|
+
return "numeric5";
|
|
14667
|
+
}
|
|
14668
|
+
if (trimmed.length === 6) {
|
|
14669
|
+
return parseInt(trimmed, 10) <= 339999 ? "numeric6" : "extended";
|
|
14670
|
+
}
|
|
14671
|
+
return "extended";
|
|
14672
|
+
}
|
|
14673
|
+
/**
|
|
14674
|
+
* Converts a 6-digit numeric SCC number to its 5-character alpha-5 form.
|
|
14675
|
+
*
|
|
14676
|
+
* Inputs shorter than 6 chars or already in alpha-5 form pass through unchanged.
|
|
14677
|
+
*
|
|
14678
|
+
* @param sccNum The SCC number to convert.
|
|
14679
|
+
* @returns The 5-character alpha-5 representation.
|
|
14680
|
+
* @throws {ValidationError} If `sccNum` exceeds the alpha-5 range (numeric value > 339 999,
|
|
14681
|
+
* or length > 6). For such IDs the canonical value should be kept on
|
|
14682
|
+
* `Satellite.sccNum` and the TLE column populated with the last 5 digits.
|
|
14582
14683
|
*/
|
|
14583
14684
|
static convert6DigitToA5(sccNum) {
|
|
14584
14685
|
if (sccNum.length < 6) {
|
|
@@ -14587,9 +14688,27 @@ ${this.line2}`;
|
|
|
14587
14688
|
if (typeof sccNum[0] !== "string") {
|
|
14588
14689
|
throw new ParseError("Invalid SCC number format", "TLE");
|
|
14589
14690
|
}
|
|
14691
|
+
if (sccNum.length > 6) {
|
|
14692
|
+
throw new ValidationError(
|
|
14693
|
+
"SCC number exceeds TLE alpha-5 capacity (max 339999); use Satellite.sccNum for the canonical ID",
|
|
14694
|
+
"sccNum",
|
|
14695
|
+
sccNum
|
|
14696
|
+
);
|
|
14697
|
+
}
|
|
14590
14698
|
if (RegExp(/[A-Z]/iu, "u").test(sccNum[0])) {
|
|
14591
14699
|
return sccNum;
|
|
14592
14700
|
}
|
|
14701
|
+
const numericValue = parseInt(sccNum, 10);
|
|
14702
|
+
if (isNaN(numericValue)) {
|
|
14703
|
+
throw new ValidationError("SCC number must be numeric", "sccNum", sccNum);
|
|
14704
|
+
}
|
|
14705
|
+
if (numericValue > 339999) {
|
|
14706
|
+
throw new ValidationError(
|
|
14707
|
+
"SCC number exceeds TLE alpha-5 capacity (max 339999); use Satellite.sccNum for the canonical ID",
|
|
14708
|
+
"sccNum",
|
|
14709
|
+
sccNum
|
|
14710
|
+
);
|
|
14711
|
+
}
|
|
14593
14712
|
const rest = sccNum.slice(2, 6);
|
|
14594
14713
|
let first = parseInt(`${sccNum[0]}${sccNum[1]}`);
|
|
14595
14714
|
const iPlus = first >= 18 ? 1 : 0;
|
|
@@ -14598,9 +14717,17 @@ ${this.line2}`;
|
|
|
14598
14717
|
return `${String.fromCharCode(first + 55)}${rest}`;
|
|
14599
14718
|
}
|
|
14600
14719
|
/**
|
|
14601
|
-
* Converts a 5-
|
|
14602
|
-
*
|
|
14603
|
-
*
|
|
14720
|
+
* Converts a 5-character alpha-5 SCC number to its 6-digit numeric form.
|
|
14721
|
+
*
|
|
14722
|
+
* Inputs shorter than 5 chars pass through unchanged. Numeric inputs without
|
|
14723
|
+
* an alpha-5 leading letter pass through unchanged (5-digit numeric, in-range
|
|
14724
|
+
* 6-digit numeric, and extended 7+ digit numeric IDs are all identity).
|
|
14725
|
+
*
|
|
14726
|
+
* @param sccNum The SCC number to convert.
|
|
14727
|
+
* @returns The 6-digit numeric representation, or the input itself if it
|
|
14728
|
+
* is already a numeric form.
|
|
14729
|
+
* @throws {ValidationError} If `sccNum` is malformed: 6-digit numeric whose
|
|
14730
|
+
* value exceeds 339 999, or contains stray letters in a non-leading position.
|
|
14604
14731
|
*/
|
|
14605
14732
|
static convertA5to6Digit(sccNum) {
|
|
14606
14733
|
if (sccNum.length < 5) {
|
|
@@ -14611,10 +14738,33 @@ ${this.line2}`;
|
|
|
14611
14738
|
throw new ParseError("Invalid SCC number format", "TLE");
|
|
14612
14739
|
}
|
|
14613
14740
|
if (values[0] in _Tle.alpha5_) {
|
|
14741
|
+
if (!/^\d+$/u.test(sccNum.slice(1))) {
|
|
14742
|
+
throw new ValidationError(
|
|
14743
|
+
"Alpha-5 SCC number must have 4 trailing digits",
|
|
14744
|
+
"sccNum",
|
|
14745
|
+
sccNum
|
|
14746
|
+
);
|
|
14747
|
+
}
|
|
14614
14748
|
const firstLetter = values[0];
|
|
14615
14749
|
values[0] = _Tle.alpha5_[firstLetter];
|
|
14750
|
+
return values.join("");
|
|
14751
|
+
}
|
|
14752
|
+
if (!/^\s*\d+\s*$/u.test(sccNum)) {
|
|
14753
|
+
throw new ValidationError(
|
|
14754
|
+
"SCC number must be alpha-5 or all-numeric",
|
|
14755
|
+
"sccNum",
|
|
14756
|
+
sccNum
|
|
14757
|
+
);
|
|
14758
|
+
}
|
|
14759
|
+
const trimmed = sccNum.trim();
|
|
14760
|
+
if (trimmed.length === 6 && parseInt(trimmed, 10) > 339999) {
|
|
14761
|
+
throw new ValidationError(
|
|
14762
|
+
"6-digit SCC number exceeds TLE alpha-5 capacity (max 339999)",
|
|
14763
|
+
"sccNum",
|
|
14764
|
+
sccNum
|
|
14765
|
+
);
|
|
14616
14766
|
}
|
|
14617
|
-
return
|
|
14767
|
+
return sccNum;
|
|
14618
14768
|
}
|
|
14619
14769
|
};
|
|
14620
14770
|
|
|
@@ -16497,11 +16647,15 @@ var Satellite = class _Satellite extends SpaceObject {
|
|
|
16497
16647
|
period;
|
|
16498
16648
|
rightAscension;
|
|
16499
16649
|
satrec;
|
|
16500
|
-
/** The satellite catalog number
|
|
16650
|
+
/** The canonical satellite catalog number. May be a 5-digit numeric, alpha-5,
|
|
16651
|
+
* 6-digit numeric, or an extended (7+ digit) ID such as CelesTrak supplemental
|
|
16652
|
+
* 9-digit IDs. */
|
|
16501
16653
|
sccNum;
|
|
16502
|
-
/** The 5
|
|
16654
|
+
/** The 5-character alpha-5 representation, or `null` when {@link sccNum} is
|
|
16655
|
+
* an extended ID that exceeds the alpha-5 capacity (max numeric value 339 999). */
|
|
16503
16656
|
sccNum5;
|
|
16504
|
-
/** The 6
|
|
16657
|
+
/** The 6-digit numeric representation, or `null` when {@link sccNum} is
|
|
16658
|
+
* an extended ID that exceeds the alpha-5 capacity (max numeric value 339 999). */
|
|
16505
16659
|
sccNum6;
|
|
16506
16660
|
tle1;
|
|
16507
16661
|
tle2;
|
|
@@ -16683,13 +16837,57 @@ var Satellite = class _Satellite extends SpaceObject {
|
|
|
16683
16837
|
return new _Satellite({ omm, name: name ?? omm.OBJECT_NAME });
|
|
16684
16838
|
}
|
|
16685
16839
|
// ==================== TLE/OMM Parsing ====================
|
|
16840
|
+
/**
|
|
16841
|
+
* Converts an OMM international designator (e.g. `"2026-114A"`) to the
|
|
16842
|
+
* TLE intl-des column format (cols 10-17, e.g. `"26114A"`). Inputs that
|
|
16843
|
+
* don't match the OMM `YYYY-NNNL...` pattern pass through unchanged.
|
|
16844
|
+
*/
|
|
16845
|
+
static ommObjectIdToTleIntlDes_(objectId) {
|
|
16846
|
+
if (!objectId) {
|
|
16847
|
+
return "";
|
|
16848
|
+
}
|
|
16849
|
+
const match = objectId.match(/^\d{4}-(?<rest>\d{3}[A-Z]{1,3})$/u);
|
|
16850
|
+
if (match?.groups?.rest) {
|
|
16851
|
+
return objectId.slice(2, 4) + match.groups.rest;
|
|
16852
|
+
}
|
|
16853
|
+
return objectId;
|
|
16854
|
+
}
|
|
16855
|
+
/**
|
|
16856
|
+
* Normalizes {@link sccNum} to the display-canonical numeric form and derives
|
|
16857
|
+
* {@link sccNum5} and {@link sccNum6}. The class invariant is that
|
|
16858
|
+
* `Satellite.sccNum` is always numeric — never an alpha-5 string. Alpha-5
|
|
16859
|
+
* inputs ("T0001") are converted to their 6-digit numeric equivalent
|
|
16860
|
+
* ("270001"); the alpha-5 form is preserved on {@link sccNum5}.
|
|
16861
|
+
*
|
|
16862
|
+
* Extended (7+ digit) IDs that exceed the alpha-5 capacity (max 339 999)
|
|
16863
|
+
* leave sccNum5/sccNum6 set to `null`.
|
|
16864
|
+
*
|
|
16865
|
+
* Invalid input (empty string, malformed token) is passed through unchanged
|
|
16866
|
+
* so callers can store placeholder sccNums on notional / debris stubs.
|
|
16867
|
+
*/
|
|
16868
|
+
assignAlpha5Forms_() {
|
|
16869
|
+
try {
|
|
16870
|
+
this.sccNum = Tle.convertA5to6Digit(this.sccNum);
|
|
16871
|
+
} catch {
|
|
16872
|
+
}
|
|
16873
|
+
if (/^0+\d/u.test(this.sccNum)) {
|
|
16874
|
+
this.sccNum = this.sccNum.replace(/^0+/u, "");
|
|
16875
|
+
}
|
|
16876
|
+
const kind = Tle.classifySatNum(this.sccNum);
|
|
16877
|
+
if (kind === "numeric5" || kind === "numeric6") {
|
|
16878
|
+
this.sccNum5 = Tle.convert6DigitToA5(this.sccNum);
|
|
16879
|
+
this.sccNum6 = this.sccNum;
|
|
16880
|
+
} else {
|
|
16881
|
+
this.sccNum5 = null;
|
|
16882
|
+
this.sccNum6 = null;
|
|
16883
|
+
}
|
|
16884
|
+
}
|
|
16686
16885
|
parseTleAndUpdateOrbit_(tle1, tle2, sccNum) {
|
|
16687
16886
|
const tleData = Tle.parse(tle1, tle2);
|
|
16688
16887
|
this.tle1 = tle1;
|
|
16689
16888
|
this.tle2 = tle2;
|
|
16690
16889
|
this.sccNum = sccNum ?? tleData.satNum.toString();
|
|
16691
|
-
this.
|
|
16692
|
-
this.sccNum6 = Tle.convertA5to6Digit(this.sccNum5);
|
|
16890
|
+
this.assignAlpha5Forms_();
|
|
16693
16891
|
this.intlDes = tleData.intlDes;
|
|
16694
16892
|
this.epochYear = tleData.epochYear;
|
|
16695
16893
|
this.epochDay = tleData.epochDay;
|
|
@@ -16712,8 +16910,7 @@ var Satellite = class _Satellite extends SpaceObject {
|
|
|
16712
16910
|
parseOmmAndUpdateOrbit_(omm) {
|
|
16713
16911
|
const noradStr = String(omm.NORAD_CAT_ID);
|
|
16714
16912
|
this.sccNum = noradStr.padStart(5, "0");
|
|
16715
|
-
this.
|
|
16716
|
-
this.sccNum6 = Tle.convertA5to6Digit(this.sccNum5);
|
|
16913
|
+
this.assignAlpha5Forms_();
|
|
16717
16914
|
this.intlDes = omm.OBJECT_ID;
|
|
16718
16915
|
const YYYY = omm.EPOCH.slice(0, 4);
|
|
16719
16916
|
const MM = omm.EPOCH.slice(5, 7);
|
|
@@ -16773,8 +16970,10 @@ var Satellite = class _Satellite extends SpaceObject {
|
|
|
16773
16970
|
ecen: this.eccentricity,
|
|
16774
16971
|
epochyr: this.epochYear,
|
|
16775
16972
|
epochday: this.epochDay,
|
|
16776
|
-
intl: omm.OBJECT_ID,
|
|
16777
|
-
|
|
16973
|
+
intl: _Satellite.ommObjectIdToTleIntlDes_(omm.OBJECT_ID),
|
|
16974
|
+
// Extended (7+ digit) IDs don't fit TLE cols 3-7. Truncate to the last 5
|
|
16975
|
+
// digits for the TLE string; the canonical ID stays on this.sccNum.
|
|
16976
|
+
scc: Tle.classifySatNum(this.sccNum) === "extended" ? this.sccNum.slice(-5) : this.sccNum,
|
|
16778
16977
|
bstar: this.bstar,
|
|
16779
16978
|
meanMotionDot: this.meanMoDev1,
|
|
16780
16979
|
meanMotionDdot: this.meanMoDev2,
|
|
@@ -21566,6 +21765,82 @@ var ModifiedGoodingIOD = class {
|
|
|
21566
21765
|
}
|
|
21567
21766
|
};
|
|
21568
21767
|
|
|
21768
|
+
// src/orbit-determination/Rv2Tle.ts
|
|
21769
|
+
var angleDiff_ = (a, b) => {
|
|
21770
|
+
let diff = (a - b) % TAU;
|
|
21771
|
+
if (diff > Math.PI) {
|
|
21772
|
+
diff -= TAU;
|
|
21773
|
+
} else if (diff < -Math.PI) {
|
|
21774
|
+
diff += TAU;
|
|
21775
|
+
}
|
|
21776
|
+
return diff;
|
|
21777
|
+
};
|
|
21778
|
+
var wrapTau_ = (angle) => {
|
|
21779
|
+
let wrapped = angle % TAU;
|
|
21780
|
+
if (wrapped < 0) {
|
|
21781
|
+
wrapped += TAU;
|
|
21782
|
+
}
|
|
21783
|
+
return wrapped;
|
|
21784
|
+
};
|
|
21785
|
+
var toElements_ = (epoch, position, velocity) => new J20002(
|
|
21786
|
+
epoch,
|
|
21787
|
+
new Vector3D(position.x, position.y, position.z),
|
|
21788
|
+
new Vector3D(velocity.x, velocity.y, velocity.z)
|
|
21789
|
+
).toClassicalElements();
|
|
21790
|
+
var rv2tle = (epoch, position, velocity, options = {}) => {
|
|
21791
|
+
const maxIterations = options.maxIterations ?? 15;
|
|
21792
|
+
const toleranceKm = options.toleranceKm ?? 1e-4;
|
|
21793
|
+
const epochUtc = EpochUTC.fromDateTime(epoch);
|
|
21794
|
+
const target = toElements_(epochUtc, position, velocity);
|
|
21795
|
+
let mean2 = new ClassicalElements({
|
|
21796
|
+
epoch: epochUtc,
|
|
21797
|
+
semimajorAxis: target.semimajorAxis,
|
|
21798
|
+
eccentricity: target.eccentricity,
|
|
21799
|
+
inclination: target.inclination,
|
|
21800
|
+
rightAscension: target.rightAscension,
|
|
21801
|
+
argPerigee: target.argPerigee,
|
|
21802
|
+
trueAnomaly: target.trueAnomaly
|
|
21803
|
+
});
|
|
21804
|
+
let best = null;
|
|
21805
|
+
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
21806
|
+
let tle;
|
|
21807
|
+
let sv;
|
|
21808
|
+
try {
|
|
21809
|
+
tle = Tle.fromClassicalElements(mean2);
|
|
21810
|
+
const tsinceMin = (epoch.getTime() - tle.epoch.toDateTime().getTime()) / 6e4;
|
|
21811
|
+
sv = Sgp4.propagate(Sgp4.createSatrec(tle.line1, tle.line2), tsinceMin);
|
|
21812
|
+
} catch {
|
|
21813
|
+
return best;
|
|
21814
|
+
}
|
|
21815
|
+
if (!sv.position || typeof sv.position === "boolean" || !sv.velocity || typeof sv.velocity === "boolean") {
|
|
21816
|
+
return best;
|
|
21817
|
+
}
|
|
21818
|
+
const achievedPos = sv.position;
|
|
21819
|
+
const achievedVel = sv.velocity;
|
|
21820
|
+
const dx = achievedPos.x - position.x;
|
|
21821
|
+
const dy = achievedPos.y - position.y;
|
|
21822
|
+
const dz = achievedPos.z - position.z;
|
|
21823
|
+
const positionErrorKm = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
21824
|
+
if (!best || positionErrorKm < best.positionErrorKm) {
|
|
21825
|
+
best = { tle1: tle.line1, tle2: tle.line2, positionErrorKm, iterations: iteration };
|
|
21826
|
+
}
|
|
21827
|
+
if (positionErrorKm < toleranceKm) {
|
|
21828
|
+
break;
|
|
21829
|
+
}
|
|
21830
|
+
const achieved = toElements_(epochUtc, achievedPos, achievedVel);
|
|
21831
|
+
mean2 = new ClassicalElements({
|
|
21832
|
+
epoch: epochUtc,
|
|
21833
|
+
semimajorAxis: mean2.semimajorAxis + (target.semimajorAxis - achieved.semimajorAxis),
|
|
21834
|
+
eccentricity: Math.min(Math.max(mean2.eccentricity + (target.eccentricity - achieved.eccentricity), 0), 0.999),
|
|
21835
|
+
inclination: Math.min(Math.max(mean2.inclination + angleDiff_(target.inclination, achieved.inclination), 0), Math.PI),
|
|
21836
|
+
rightAscension: wrapTau_(mean2.rightAscension + angleDiff_(target.rightAscension, achieved.rightAscension)),
|
|
21837
|
+
argPerigee: wrapTau_(mean2.argPerigee + angleDiff_(target.argPerigee, achieved.argPerigee)),
|
|
21838
|
+
trueAnomaly: wrapTau_(mean2.trueAnomaly + angleDiff_(target.trueAnomaly, achieved.trueAnomaly))
|
|
21839
|
+
});
|
|
21840
|
+
}
|
|
21841
|
+
return best;
|
|
21842
|
+
};
|
|
21843
|
+
|
|
21569
21844
|
// src/covariance/CovarianceSample.ts
|
|
21570
21845
|
var CovarianceSample = class {
|
|
21571
21846
|
origin_;
|
|
@@ -21758,6 +22033,19 @@ var CovarianceSample = class {
|
|
|
21758
22033
|
}
|
|
21759
22034
|
};
|
|
21760
22035
|
|
|
22036
|
+
// src/covariance/displayRadii.ts
|
|
22037
|
+
function scaleAndClampRicSigmas(sigmas, confidence, caps) {
|
|
22038
|
+
const { radial, inTrack, crossTrack } = sigmas;
|
|
22039
|
+
if (![radial, inTrack, crossTrack].every((v) => Number.isFinite(v) && v >= 0)) {
|
|
22040
|
+
return null;
|
|
22041
|
+
}
|
|
22042
|
+
return {
|
|
22043
|
+
radial: Math.min(radial * confidence, caps.radial),
|
|
22044
|
+
inTrack: Math.min(inTrack * confidence, caps.inTrack),
|
|
22045
|
+
crossTrack: Math.min(crossTrack * confidence, caps.crossTrack)
|
|
22046
|
+
};
|
|
22047
|
+
}
|
|
22048
|
+
|
|
21761
22049
|
// src/conjunction/ConjunctionAssessment.ts
|
|
21762
22050
|
var ConjunctionAssessment = class {
|
|
21763
22051
|
constructor(primary, secondary) {
|
|
@@ -24182,12 +24470,12 @@ var HorizonsParser = class {
|
|
|
24182
24470
|
for (let i = 0; i < lines.length; i++) {
|
|
24183
24471
|
const line = lines[i].trim();
|
|
24184
24472
|
if (line.startsWith("Target body name:")) {
|
|
24185
|
-
targetName = this.
|
|
24473
|
+
targetName = this.extractTargetName_(line, "Target body name:");
|
|
24186
24474
|
metadata.targetName = targetName;
|
|
24187
24475
|
} else if (line.startsWith("Center body name:")) {
|
|
24188
24476
|
centerBody = this.extractValue_(line, "Center body name:");
|
|
24189
24477
|
metadata.centerBody = centerBody;
|
|
24190
|
-
} else if (line.startsWith("Center-site name:")) {
|
|
24478
|
+
} else if (line.startsWith("Center-site name:") && !centerBody) {
|
|
24191
24479
|
centerBody = this.extractValue_(line, "Center-site name:");
|
|
24192
24480
|
metadata.centerBody = centerBody;
|
|
24193
24481
|
} else if (line.startsWith("Output type:")) {
|
|
@@ -24242,7 +24530,7 @@ var HorizonsParser = class {
|
|
|
24242
24530
|
for (let i = 0; i < lines.length; i++) {
|
|
24243
24531
|
const line = lines[i].trim();
|
|
24244
24532
|
if (line.startsWith("Target body name:")) {
|
|
24245
|
-
targetName = this.
|
|
24533
|
+
targetName = this.extractTargetName_(line, "Target body name:");
|
|
24246
24534
|
} else if (line.startsWith("Center-site name:")) {
|
|
24247
24535
|
observerLocation = this.extractValue_(line, "Center-site name:");
|
|
24248
24536
|
}
|
|
@@ -24273,7 +24561,23 @@ var HorizonsParser = class {
|
|
|
24273
24561
|
*/
|
|
24274
24562
|
static extractValue_(line, key) {
|
|
24275
24563
|
const value = line.substring(key.length).trim();
|
|
24276
|
-
return value.split(/\s{2,}/)[0].trim();
|
|
24564
|
+
return value.split(/\s{2,}/u)[0].trim();
|
|
24565
|
+
}
|
|
24566
|
+
/**
|
|
24567
|
+
* Extracts the target body name, stripping parenthetical descriptors and IDs.
|
|
24568
|
+
*
|
|
24569
|
+
* Horizons format examples:
|
|
24570
|
+
* - "1 Ceres {source: astDys}" → "1 Ceres"
|
|
24571
|
+
* - "Artemis II (spacecraft) (-1024) {source: ...}" → "Artemis II"
|
|
24572
|
+
* - "Mars (499)" → "Mars"
|
|
24573
|
+
*/
|
|
24574
|
+
static extractTargetName_(line, key) {
|
|
24575
|
+
const raw = this.extractValue_(line, key);
|
|
24576
|
+
const parenIdx = raw.search(/\s\(/u);
|
|
24577
|
+
if (parenIdx > 0) {
|
|
24578
|
+
return raw.substring(0, parenIdx).trim();
|
|
24579
|
+
}
|
|
24580
|
+
return raw;
|
|
24277
24581
|
}
|
|
24278
24582
|
/**
|
|
24279
24583
|
* Parses a vector data line from Horizons output.
|
|
@@ -24343,7 +24647,9 @@ var HorizonsParser = class {
|
|
|
24343
24647
|
return null;
|
|
24344
24648
|
}
|
|
24345
24649
|
const posLine = allLines[nextIndex].trim();
|
|
24346
|
-
const
|
|
24650
|
+
const posMatchLabeled = posLine.match(/X\s*=\s*([-\d.E+]+)\s+Y\s*=\s*([-\d.E+]+)\s+Z\s*=\s*([-\d.E+]+)/iu);
|
|
24651
|
+
const posMatchBare = !posMatchLabeled ? posLine.match(/^\s*([-\d.E+]+)\s+([-\d.E+]+)\s+([-\d.E+]+)\s*$/iu) : null;
|
|
24652
|
+
const posMatch = posMatchLabeled ?? posMatchBare;
|
|
24347
24653
|
if (!posMatch) {
|
|
24348
24654
|
return null;
|
|
24349
24655
|
}
|
|
@@ -24355,7 +24661,9 @@ var HorizonsParser = class {
|
|
|
24355
24661
|
let velocity;
|
|
24356
24662
|
if (nextIndex < allLines.length) {
|
|
24357
24663
|
const velLine = allLines[nextIndex].trim();
|
|
24358
|
-
const
|
|
24664
|
+
const velMatchLabeled = velLine.match(/VX\s*=\s*([-\d.E+]+)\s+VY\s*=\s*([-\d.E+]+)\s+VZ\s*=\s*([-\d.E+]+)/iu);
|
|
24665
|
+
const velMatchBare = !velMatchLabeled ? velLine.match(/^\s*([-\d.E+]+)\s+([-\d.E+]+)\s+([-\d.E+]+)\s*$/iu) : null;
|
|
24666
|
+
const velMatch = velMatchLabeled ?? velMatchBare;
|
|
24359
24667
|
if (velMatch) {
|
|
24360
24668
|
velocity = new Vector3D(
|
|
24361
24669
|
parseFloat(velMatch[1]),
|
|
@@ -24365,6 +24673,17 @@ var HorizonsParser = class {
|
|
|
24365
24673
|
nextIndex++;
|
|
24366
24674
|
}
|
|
24367
24675
|
}
|
|
24676
|
+
while (nextIndex < allLines.length) {
|
|
24677
|
+
const extraLine = allLines[nextIndex].trim();
|
|
24678
|
+
if (extraLine === "$$EOE" || extraLine.length === 0 || /^\d+\.\d+\s*=\s*A\.D\./u.test(extraLine)) {
|
|
24679
|
+
break;
|
|
24680
|
+
}
|
|
24681
|
+
if (/^\s*[-\d.E+]+(\s+[-\d.E+]+)*\s*$/u.test(extraLine)) {
|
|
24682
|
+
nextIndex++;
|
|
24683
|
+
} else {
|
|
24684
|
+
break;
|
|
24685
|
+
}
|
|
24686
|
+
}
|
|
24368
24687
|
return {
|
|
24369
24688
|
data: { epoch, position, velocity },
|
|
24370
24689
|
nextIndex
|
|
@@ -25111,6 +25430,58 @@ var OdmExporter = class _OdmExporter {
|
|
|
25111
25430
|
return lines.join("\n");
|
|
25112
25431
|
}
|
|
25113
25432
|
// ==========================================================================
|
|
25433
|
+
// OEM from State Vectors — direct J2000 array export
|
|
25434
|
+
// ==========================================================================
|
|
25435
|
+
/**
|
|
25436
|
+
* Export an array of J2000 state vectors as OEM KVN.
|
|
25437
|
+
*
|
|
25438
|
+
* Unlike {@link formatOem}, which propagates a TLE-based satellite, this
|
|
25439
|
+
* method serializes pre-computed state vectors directly. Useful for
|
|
25440
|
+
* converting parsed ephemeris data (e.g., NASA Horizons) to CCSDS OEM.
|
|
25441
|
+
*
|
|
25442
|
+
* @param stateVectors - Array of J2000 state vectors to export
|
|
25443
|
+
* @param metadata - Object identification metadata
|
|
25444
|
+
* @param options - Export configuration
|
|
25445
|
+
* @returns OEM KVN format string
|
|
25446
|
+
*/
|
|
25447
|
+
static formatOemFromStateVectors(stateVectors, metadata, options = {}) {
|
|
25448
|
+
if (stateVectors.length === 0) {
|
|
25449
|
+
throw new Error("Cannot export OEM with no state vectors");
|
|
25450
|
+
}
|
|
25451
|
+
const refFrame = options.refFrame ?? "EME2000";
|
|
25452
|
+
const centerName = options.centerName ?? "EARTH";
|
|
25453
|
+
const startTime = stateVectors[0].epoch.toDateTime();
|
|
25454
|
+
const stopTime = stateVectors[stateVectors.length - 1].epoch.toDateTime();
|
|
25455
|
+
const lines = [];
|
|
25456
|
+
lines.push("CCSDS_OEM_VERS = 2.0");
|
|
25457
|
+
_OdmExporter.appendComments_(lines, options.comments);
|
|
25458
|
+
lines.push(`CREATION_DATE = ${_OdmExporter.formatDateTime_(/* @__PURE__ */ new Date())}`);
|
|
25459
|
+
lines.push(`ORIGINATOR = ${options.originator ?? "KeepTrack"}`);
|
|
25460
|
+
if (options.messageId) {
|
|
25461
|
+
lines.push(`MESSAGE_ID = ${options.messageId}`);
|
|
25462
|
+
}
|
|
25463
|
+
lines.push("");
|
|
25464
|
+
lines.push("META_START");
|
|
25465
|
+
lines.push(`OBJECT_NAME = ${metadata.objectName}`);
|
|
25466
|
+
lines.push(`OBJECT_ID = ${metadata.objectId}`);
|
|
25467
|
+
lines.push(`CENTER_NAME = ${centerName}`);
|
|
25468
|
+
lines.push(`REF_FRAME = ${refFrame}`);
|
|
25469
|
+
lines.push("TIME_SYSTEM = UTC");
|
|
25470
|
+
lines.push(`START_TIME = ${_OdmExporter.formatDateTime_(startTime)}`);
|
|
25471
|
+
lines.push(`STOP_TIME = ${_OdmExporter.formatDateTime_(stopTime)}`);
|
|
25472
|
+
lines.push(`INTERPOLATION = ${options.interpolation ?? "LAGRANGE"}`);
|
|
25473
|
+
lines.push(`INTERPOLATION_DEGREE = ${options.interpolationDegree ?? 7}`);
|
|
25474
|
+
lines.push("META_STOP");
|
|
25475
|
+
lines.push("");
|
|
25476
|
+
for (const sv of stateVectors) {
|
|
25477
|
+
const epoch = _OdmExporter.formatDateTime_(sv.epoch.toDateTime());
|
|
25478
|
+
lines.push(
|
|
25479
|
+
`${epoch} ${_OdmExporter.formatNumber_(sv.position.x)} ${_OdmExporter.formatNumber_(sv.position.y)} ${_OdmExporter.formatNumber_(sv.position.z)} ${_OdmExporter.formatNumber_(sv.velocity.x)} ${_OdmExporter.formatNumber_(sv.velocity.y)} ${_OdmExporter.formatNumber_(sv.velocity.z)}`
|
|
25480
|
+
);
|
|
25481
|
+
}
|
|
25482
|
+
return lines.join("\n");
|
|
25483
|
+
}
|
|
25484
|
+
// ==========================================================================
|
|
25114
25485
|
// OMM — Orbit Mean-Elements Message
|
|
25115
25486
|
// ==========================================================================
|
|
25116
25487
|
/**
|
|
@@ -29477,8 +29848,20 @@ var OrbitFinder = class _OrbitFinder {
|
|
|
29477
29848
|
this.debug_(`Found perigee at altitude ${perigeeParams.altitude} km`);
|
|
29478
29849
|
return perigeeParams;
|
|
29479
29850
|
}
|
|
29851
|
+
/**
|
|
29852
|
+
* Returns the 5-char satnum to embed in the synthesized TLEs.
|
|
29853
|
+
*
|
|
29854
|
+
* Why not {@link Satellite.sccNum}? For extended (7+ digit) IDs sccNum is
|
|
29855
|
+
* the full 9-digit canonical value, which would push the TLE past 69 chars
|
|
29856
|
+
* and break Sgp4.createSatrec. The input tle1 always has a valid 5-char
|
|
29857
|
+
* satnum at columns 2-6, and SGP4 itself doesn't care what value lives
|
|
29858
|
+
* there as long as line 1 and line 2 agree — so use that.
|
|
29859
|
+
*/
|
|
29860
|
+
tleSatNum_() {
|
|
29861
|
+
return this.sat_.tle1.substring(2, 7);
|
|
29862
|
+
}
|
|
29480
29863
|
generateTle1_() {
|
|
29481
|
-
return `1 ${this.
|
|
29864
|
+
return `1 ${this.tleSatNum_()}U ${this.sat_.tle1.substring(9, 17)} ${this.sat_.tle1.substring(18, 32)}${this.sat_.tle1.substring(32, 71)}`;
|
|
29482
29865
|
}
|
|
29483
29866
|
generateTle2_(newParams) {
|
|
29484
29867
|
const mergedParams = {
|
|
@@ -29493,7 +29876,7 @@ var OrbitFinder = class _OrbitFinder {
|
|
|
29493
29876
|
const argPer = mergedParams.argOfPerigee.toFixed(4).padStart(8, "0");
|
|
29494
29877
|
const meanA = mergedParams.meanAnomaly.toFixed(4).padStart(8, "0");
|
|
29495
29878
|
const meanMo = this.sat_.tle2.substring(52, 63);
|
|
29496
|
-
return `2 ${this.
|
|
29879
|
+
return `2 ${this.tleSatNum_()} ${inc} ${raan} ${ecc} ${argPer} ${meanA} ${meanMo} 10`;
|
|
29497
29880
|
}
|
|
29498
29881
|
};
|
|
29499
29882
|
|
|
@@ -29853,6 +30236,33 @@ var RepeatGroundTrack = class _RepeatGroundTrack {
|
|
|
29853
30236
|
* You should have received a copy of the GNU Affero General Public License along with
|
|
29854
30237
|
* Orbital Object ToolKit. If not, see <http://www.gnu.org/licenses/>.
|
|
29855
30238
|
*/
|
|
30239
|
+
/**
|
|
30240
|
+
* @author Theodore Kruczek.
|
|
30241
|
+
* @description Orbital Object ToolKit (ootk) is a collection of tools for working
|
|
30242
|
+
* with satellites and other orbital objects.
|
|
30243
|
+
*
|
|
30244
|
+
* @license MIT License
|
|
30245
|
+
*
|
|
30246
|
+
* @Copyright (c) 2025 Theodore Kruczek
|
|
30247
|
+
*
|
|
30248
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
30249
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
30250
|
+
* in the Software without restriction, including without limitation the rights
|
|
30251
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
30252
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
30253
|
+
* furnished to do so, subject to the following conditions:
|
|
30254
|
+
*
|
|
30255
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
30256
|
+
* copies or substantial portions of the Software.
|
|
30257
|
+
*
|
|
30258
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30259
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
30260
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30261
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30262
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30263
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30264
|
+
* SOFTWARE.
|
|
30265
|
+
*/
|
|
29856
30266
|
/**
|
|
29857
30267
|
* @author Theodore Kruczek
|
|
29858
30268
|
* @description Orbital Object ToolKit (ootk) is a collection of tools for working
|
|
@@ -30082,6 +30492,8 @@ exports.Quaternion = Quaternion;
|
|
|
30082
30492
|
exports.RAD2DEG = RAD2DEG;
|
|
30083
30493
|
exports.RADIUS_OF_EARTH = RADIUS_OF_EARTH;
|
|
30084
30494
|
exports.RAE = RAE;
|
|
30495
|
+
exports.RCS_VMAG_ESTIMATE_MAX = RCS_VMAG_ESTIMATE_MAX;
|
|
30496
|
+
exports.RCS_VMAG_ESTIMATE_MIN = RCS_VMAG_ESTIMATE_MIN;
|
|
30085
30497
|
exports.RIC = RIC;
|
|
30086
30498
|
exports.RadarSensor = RadarSensor;
|
|
30087
30499
|
exports.RadecGeocentric = RadecGeocentric;
|
|
@@ -30159,6 +30571,7 @@ exports.calcIncFromAz = calcIncFromAz;
|
|
|
30159
30571
|
exports.calcInertAz = calcInertAz;
|
|
30160
30572
|
exports.calculateFspl = calculateFspl;
|
|
30161
30573
|
exports.calculatePropagationDelay = calculatePropagationDelay;
|
|
30574
|
+
exports.cappedScreeningCovarianceFromTle = cappedScreeningCovarianceFromTle;
|
|
30162
30575
|
exports.clamp = clamp;
|
|
30163
30576
|
exports.concat = concat;
|
|
30164
30577
|
exports.copySign = copySign;
|
|
@@ -30183,6 +30596,7 @@ exports.eci2lla = eci2lla;
|
|
|
30183
30596
|
exports.eci2rae = eci2rae;
|
|
30184
30597
|
exports.enu2rf = enu2rf;
|
|
30185
30598
|
exports.estimateRcs = estimateRcs;
|
|
30599
|
+
exports.estimateVmagFromRcs = estimateVmagFromRcs;
|
|
30186
30600
|
exports.evalPoly = evalPoly;
|
|
30187
30601
|
exports.factorial = factorial;
|
|
30188
30602
|
exports.gamma = gamma;
|
|
@@ -30223,6 +30637,8 @@ exports.rae2raeOffBoresight = rae2raeOffBoresight;
|
|
|
30223
30637
|
exports.rae2ruv = rae2ruv;
|
|
30224
30638
|
exports.rae2sez = rae2sez;
|
|
30225
30639
|
exports.relativeVelocity = relativeVelocity;
|
|
30640
|
+
exports.rv2tle = rv2tle;
|
|
30641
|
+
exports.scaleAndClampRicSigmas = scaleAndClampRicSigmas;
|
|
30226
30642
|
exports.sec2day = sec2day;
|
|
30227
30643
|
exports.sec2deg = sec2deg;
|
|
30228
30644
|
exports.sec2min = sec2min;
|