Read a satellite's launch year from one TLE parser

The catalog stored the designator on satrec.intldesg for
internationalDesignatorYear, while the active-TLE lookup parsed the same
columns with tleLineLaunchYear. Each catalog entry now keeps launchYear from
tleLineLaunchYear, which moves to src/sources/tle.js beside the other TLE
field readers, and internationalDesignatorYear goes.

A blank designator now reads as an unknown year in both lookups; the
active-TLE lookup read it as 2000.
This commit is contained in:
Sameh Khamis committed 2026-11-01 22:53:35 -08:00
1 parent bfeec17c81
commit 4779432e48
5 files changed
+46 -23

No files matched your search

+2 -3
View File
@@ -1,6 +1,6 @@
import * as Cesium from 'cesium';
import { twoline2satrec } from 'satellite.js';
import { noradIdFromSatnum } from '../../sources/tle.js';
import { noradIdFromSatnum, tleLineLaunchYear } from '../../sources/tle.js';
import {
DENSE_REFRESH_FRAMES,
DENSE_GROUP_PATH,
@@ -114,8 +114,6 @@ export function createCatalog({ state: layerState, services, parts, source }) {
const entry = entries[i];
const satrec = twoline2satrec(entry.line1, entry.line2);
if (!satrec || satrec.error !== 0) continue;
// satellite.js omits the designator the launch-year match reads.
satrec.intldesg = entry.line1.slice(9, 17).trim();
const noradId = noradIdFromSatnum(satrec.satnum);
if (layerState._catalog.has(noradId)) continue; // core catalog keeps priority
const pos = parts.orbits.propagatePosition(satrec, now);
@@ -125,6 +123,7 @@ export function createCatalog({ state: layerState, services, parts, source }) {
name: entry.name,
satrec,
group: 'dense',
launchYear: tleLineLaunchYear(entry.line1),
});
const point = layerState._pointCollection.add({
position: Cesium.Cartesian3.fromDegrees(
+3 -3
View File
@@ -1,7 +1,7 @@
import { twoline2satrec } from 'satellite.js';
import * as Cesium from 'cesium';
import { msg } from '../../i18n/index.js';
import { noradIdFromSatnum } from '../../sources/tle.js';
import { noradIdFromSatnum, tleLineLaunchYear } from '../../sources/tle.js';
import { CATALOG_GROUPS, ISS_NORAD, POINT_STYLES } from './policy.js';
export function createIngestion({
@@ -119,8 +119,6 @@ export function createIngestion({
for (const entry of allEntries) {
const satrec = twoline2satrec(entry.line1, entry.line2);
if (!satrec || satrec.error !== 0) continue;
// satellite.js omits the designator the launch-year match reads.
satrec.intldesg = entry.line1.slice(9, 17).trim();
const noradId = noradIdFromSatnum(satrec.satnum);
if (seen.has(noradId)) continue;
@@ -131,6 +129,8 @@ export function createIngestion({
name: entry.name,
satrec,
group: entry.group,
// satellite.js does not keep the designator the year match reads.
launchYear: tleLineLaunchYear(entry.line1),
});
coreStale ||= entry.stale;
+22 -1
View File
@@ -1,6 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { parseTleText } from '../../sources/tle.js';
import { parseTleText, tleLineLaunchYear } from '../../sources/tle.js';
import { createIngestion } from './ingestion.js';
import { createOrbits } from './orbits.js';
@@ -98,3 +98,24 @@ test('a mission payload does not match a loaded satellite from another launch ye
assert.equal(track('2026-06-29T02:25:00Z')?.noradId, 69728);
assert.equal(track('2030-06-29T02:25:00Z'), null);
});
test('a satellite with a blank designator keeps matching by name', async (t) => {
t.mock.method(console, 'log', () => {});
t.mock.method(console, 'warn', () => {});
const { state, update } = ingestionFor(
[
'SXM-11',
'1 69728U 26208.65166678 .00000015 00000+0 00000+0 0 9990',
'2 69728 0.0784 264.9826 0001797 240.4588 273.9506 1.00271527 440',
].join('\n'),
);
await update();
const { getSatelliteOrbitTrack } = createOrbits({ state });
assert.equal(tleLineLaunchYear('1 69728U 26208.65166678'), null);
assert.equal(
getSatelliteOrbitTrack('Sirius SXM-11', {
launchTime: '2030-06-29T02:25:00Z',
})?.noradId,
69728,
);
});
+6 -16
View File
@@ -8,7 +8,11 @@ import {
twoline2satrec,
} from 'satellite.js';
import { findNextSatellitePass } from '../../data/satellitePass.js';
import { noradIdFromSatnum, parseTleText } from '../../sources/tle.js';
import {
noradIdFromSatnum,
parseTleText,
tleLineLaunchYear,
} from '../../sources/tle.js';
import { ORBIT_PATH_STEPS, ISS_NORAD } from './policy.js';
export function createOrbits({ state: layerState, services, parts, source }) {
@@ -258,13 +262,6 @@ export function createOrbits({ state: layerState, services, parts, source }) {
return score;
}
function internationalDesignatorYear(satrec) {
const match = String(satrec?.intldesg || '').match(/^(\d{2})/);
if (!match) return null;
const shortYear = Number(match[1]);
return shortYear >= 57 ? 1900 + shortYear : 2000 + shortYear;
}
function lookupTleEntries(tleText) {
if (tleText !== layerState._lookupTleText) {
layerState._lookupTleText = tleText;
@@ -273,12 +270,6 @@ export function createOrbits({ state: layerState, services, parts, source }) {
return layerState._lookupTleEntries;
}
function tleLineLaunchYear(line1) {
const shortYear = Number(String(line1 || '').slice(9, 11));
if (!Number.isFinite(shortYear)) return null;
return shortYear >= 57 ? 1900 + shortYear : 2000 + shortYear;
}
function orbitTrackFromRecord(name, satrec) {
const referenceDate = new Date();
const current = propagatePosition(satrec, referenceDate);
@@ -350,7 +341,7 @@ export function createOrbits({ state: layerState, services, parts, source }) {
: null;
let bestScore = 0;
for (const [id, sat] of layerState._catalog) {
const designatorYear = internationalDesignatorYear(sat.satrec);
const designatorYear = sat.launchYear ?? null;
if (
launchYear !== null &&
designatorYear !== null &&
@@ -379,7 +370,6 @@ export function createOrbits({ state: layerState, services, parts, source }) {
getNextSatellitePass,
resolveSatelliteForPass,
scoreSatelliteNameMatch,
internationalDesignatorYear,
lookupTleEntries,
tleLineLaunchYear,
orbitTrackFromRecord,
+13
View File
@@ -72,3 +72,16 @@ export function tleCatalogNumber(line) {
const field = String(line).slice(2, 7);
return decodeCatalogField(field.replace(/^ +/, ''));
}
/**
* Launch year from TLE line 1's international designator (columns 10-11);
* null when the designator is blank.
* @param {string} line1
* @returns {number|null}
*/
export function tleLineLaunchYear(line1) {
const digits = String(line1 || '').slice(9, 11);
if (!/^\d{2}$/.test(digits)) return null;
const shortYear = Number(digits);
return shortYear >= 57 ? 1900 + shortYear : 2000 + shortYear;
}