mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-09 04:44:01 +00:00
fix(satellites): classes read every group a TLE came in, and current SATCAT
Deduplication kept only the first group's tag, so a recent imaging satellite in both visual and resource, with no UCS record or known name, fell to Probes. The catalog entry now keeps every group, and the last-resort class is the first of them that names one. SATCAT groups merged in group order, so a stale cached copy read later overwrote a current record of the same object: a satellite SATCAT now calls dead stayed in Imaging. A stale copy no longer overrules a current read, from the same merge or an earlier one.
This commit is contained in:
1 parent
c84cfcadeb
commit
8bd16b0be9
3 files changed
+87
-9
No files matched your search
@@ -147,10 +147,14 @@ function launchYearOf(launchDate) {
|
||||
/**
|
||||
* The class a satellite belongs to.
|
||||
* @param {{noradId?: number, name?: string, group?: string|null,
|
||||
* groups?: string[]|null,
|
||||
* ucs?: {purpose?: string|null}|null,
|
||||
* satcat?: {objectType?: string, opsStatus?: string,
|
||||
* launchDate?: string|null}|null,
|
||||
* launchYear?: number|null, nowYear?: number}} subject `launchYear` is
|
||||
* launchYear?: number|null, nowYear?: number}} subject `groups` is every
|
||||
* CelesTrak group the TLE came in (`group` is the one it is shown under):
|
||||
* the first of them that names a class gives the last-resort class, so a
|
||||
* satellite in both `visual` and `resource` is still imaging. `launchYear` is
|
||||
* the TLE designator's; SATCAT's launch date wins where it has one (an
|
||||
* object deployed from the ISS carries the station's 1998 designator).
|
||||
* @returns {'stations'|'imaging'|'telecom'|'starlink'|'probes'|'debris'}
|
||||
@@ -161,6 +165,7 @@ export function classifySatellite({
|
||||
group = null,
|
||||
ucs = null,
|
||||
satcat = null,
|
||||
groups = null,
|
||||
launchYear = null,
|
||||
nowYear = new Date().getUTCFullYear(),
|
||||
} = {}) {
|
||||
@@ -199,7 +204,10 @@ export function classifySatellite({
|
||||
)
|
||||
return 'debris';
|
||||
|
||||
return GROUP_CLASS[group] || 'probes';
|
||||
const memberships = groups?.length ? groups : [group];
|
||||
for (const membership of memberships)
|
||||
if (GROUP_CLASS[membership]) return GROUP_CLASS[membership];
|
||||
return 'probes';
|
||||
}
|
||||
|
||||
/** Whether a class may be ridden in the Cockpit's orbital view. */
|
||||
|
||||
@@ -72,8 +72,16 @@ export function createIngestion({
|
||||
continue;
|
||||
}
|
||||
if (group.stale && satcatState === 'current') satcatState = 'stale';
|
||||
for (const [noradId, record] of group.records)
|
||||
layerState._satcat.set(noradId, record);
|
||||
for (const [noradId, record] of group.records) {
|
||||
// A group served from the proxy's last copy never overrules a
|
||||
// current read of the same object, from this merge or an earlier one.
|
||||
const held = layerState._satcat.get(noradId);
|
||||
if (group.stale && held && !held.stale) continue;
|
||||
layerState._satcat.set(
|
||||
noradId,
|
||||
group.stale ? { ...record, stale: true } : record,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!groups.length) return;
|
||||
const changed = layerState._satcatState !== satcatState;
|
||||
@@ -100,6 +108,7 @@ export function createIngestion({
|
||||
noradId,
|
||||
name: sat.name,
|
||||
group: sat.group,
|
||||
groups: sat.groups,
|
||||
ucs: sat.ucs,
|
||||
satcat,
|
||||
launchYear: sat.launchYear,
|
||||
@@ -259,6 +268,18 @@ export function createIngestion({
|
||||
allEntries.push({ ...e, group: r.tag, stale: r.stale });
|
||||
}
|
||||
|
||||
// Every group each TLE came in, keyed by its catalog-number column:
|
||||
// the shown tag is the first, but classification reads them all.
|
||||
const groupsByNumber = new Map();
|
||||
for (const entry of allEntries) {
|
||||
const number = String(entry.line1 || '')
|
||||
.slice(2, 7)
|
||||
.trim();
|
||||
const groups = groupsByNumber.get(number);
|
||||
if (!groups) groupsByNumber.set(number, [entry.group]);
|
||||
else if (!groups.includes(entry.group)) groups.push(entry.group);
|
||||
}
|
||||
|
||||
// Deduplicate by NORAD ID — first (most specific) group tag wins
|
||||
const seen = new Set();
|
||||
let coreStale = false;
|
||||
@@ -275,10 +296,16 @@ export function createIngestion({
|
||||
const launchYear = tleLineLaunchYear(entry.line1);
|
||||
const ucs = layerState._ucs?.get(noradId) || null;
|
||||
const satcat = layerState._satcat.get(noradId) || null;
|
||||
const groups = groupsByNumber.get(
|
||||
String(entry.line1 || '')
|
||||
.slice(2, 7)
|
||||
.trim(),
|
||||
) || [entry.group];
|
||||
layerState._catalog.set(noradId, {
|
||||
name: entry.name,
|
||||
satrec,
|
||||
group: entry.group,
|
||||
groups,
|
||||
// satellite.js does not keep the designator the year match reads.
|
||||
launchYear,
|
||||
line1: entry.line1,
|
||||
@@ -287,6 +314,7 @@ export function createIngestion({
|
||||
noradId,
|
||||
name: entry.name,
|
||||
group: entry.group,
|
||||
groups,
|
||||
ucs,
|
||||
satcat,
|
||||
launchYear,
|
||||
|
||||
@@ -61,8 +61,10 @@ function ingestionFor(catalogText, { source: sourceExtras = {} } = {}) {
|
||||
catalog: {},
|
||||
};
|
||||
const source = {
|
||||
async readGroup() {
|
||||
return { ok: true, text: catalogText };
|
||||
async readGroup(path) {
|
||||
const text =
|
||||
typeof catalogText === 'function' ? catalogText(path) : catalogText;
|
||||
return { ok: true, text };
|
||||
},
|
||||
...sourceExtras,
|
||||
};
|
||||
@@ -141,9 +143,10 @@ test('a hanging SATCAT read neither holds the catalog nor is lost when it lands'
|
||||
const satcatAnswer = new Promise((resolve) => {
|
||||
answer = resolve;
|
||||
});
|
||||
const { state, update } = ingestionFor(tleSet('OLD STAGE', '40115'), {
|
||||
source: { readSatcat: () => satcatAnswer },
|
||||
});
|
||||
const { state, update } = ingestionFor(
|
||||
(path) => (path === 'stations' ? tleSet('OLD STAGE', '40115') : ''),
|
||||
{ source: { readSatcat: () => satcatAnswer } },
|
||||
);
|
||||
state._enabled = true;
|
||||
const settle = async () => {
|
||||
for (let turn = 0; turn < 20; turn += 1)
|
||||
@@ -170,6 +173,45 @@ test('a hanging SATCAT read neither holds the catalog nor is lost when it lands'
|
||||
assert.equal(state._satcatState, 'stale', 'its staleness is kept');
|
||||
});
|
||||
|
||||
test('a satellite in visual and resource is imaging, whichever group shows it', async (t) => {
|
||||
t.mock.method(console, 'log', () => {});
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
// A name no pattern knows, no UCS record and no SATCAT: only its groups.
|
||||
const { state, update } = ingestionFor((path) =>
|
||||
path === 'visual' || path === 'resource' ? tleSet('ZZ TESTSAT', '50001') : '',
|
||||
);
|
||||
await update();
|
||||
const sat = state._catalog.get(50001);
|
||||
assert.equal(sat.group, 'visual', 'shown under the first group');
|
||||
assert.deepEqual(sat.groups, ['visual', 'resource']);
|
||||
assert.equal(sat.satelliteClass, 'imaging');
|
||||
});
|
||||
|
||||
test('a stale SATCAT copy never overrules a current read of the same object', async (t) => {
|
||||
t.mock.method(console, 'log', () => {});
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
const header =
|
||||
'OBJECT_NAME,OBJECT_ID,NORAD_CAT_ID,OBJECT_TYPE,OPS_STATUS_CODE,OWNER\n';
|
||||
const { state, update } = ingestionFor(tleSet('ZZ TESTSAT', '50002'), {
|
||||
source: {
|
||||
// visual is current and says it died; resource, a cached copy, still
|
||||
// says it works.
|
||||
readSatcat: async (path) =>
|
||||
path === 'visual'
|
||||
? { ok: true, text: `${header}ZZ TESTSAT,2015-001A,50002,PAY,-,US` }
|
||||
: path === 'resource'
|
||||
? {
|
||||
ok: true,
|
||||
stale: true,
|
||||
text: `${header}ZZ TESTSAT,2015-001A,50002,PAY,+,US`,
|
||||
}
|
||||
: { ok: true, text: header },
|
||||
},
|
||||
});
|
||||
await update();
|
||||
assert.equal(state._catalog.get(50002).satelliteClass, 'debris');
|
||||
});
|
||||
|
||||
test('a failed SATCAT read is told on the layer row, not only the console', async (t) => {
|
||||
t.mock.method(console, 'log', () => {});
|
||||
t.mock.method(console, 'warn', () => {});
|
||||
|
||||
Reference in new issue
Block a user