diff --git a/src/layers/satellites/catalog.js b/src/layers/satellites/catalog.js index 1e7c939bb..bb20e646a 100644 --- a/src/layers/satellites/catalog.js +++ b/src/layers/satellites/catalog.js @@ -209,6 +209,14 @@ export function createCatalog({ state: layerState, services, parts, source }) { if (token !== layerState._denseLoadToken) return; layerState._params.catalog = 'core'; _removeDenseCatalog(); + // Back on core, Starlink is off: hide the core catalog's own Starlink + // points again, and say so in the class choice. + if (layerState._params.classes) + layerState._params.classes = { + ...layerState._params.classes, + starlink: false, + }; + parts.controls._applyClassVisibility?.(); layerState._denseStatus = 'failed'; layerState._denseError = reason; parts.controls._notifyRowControls(); diff --git a/src/layers/satellites/classes.test.mjs b/src/layers/satellites/classes.test.mjs index cf73fde14..b0cd9307d 100644 --- a/src/layers/satellites/classes.test.mjs +++ b/src/layers/satellites/classes.test.mjs @@ -258,3 +258,69 @@ test('SATCAT CSV parses, quoted commas included, keyed by NORAD id', () => { assert.equal(parseSatcatCsv('busy').size, 0); assert.equal(parseSatcatCsv('').size, 0); }); + +test('turning Stations off takes the ISS ring and label with its point', async () => { + const { createControls } = await import('./controls.js'); + let overlaySyncs = 0; + const ring = { primitive: { show: true } }; + const state = { + _enabled: true, + _params: { + catalog: 'core', + showOrbits: true, + classes: { ...DEFAULT_SATELLITE_CLASSES, stations: false }, + }, + _catalog: new Map([[25544, { satelliteClass: 'stations' }]]), + _points: new Map([[25544, { show: true }]]), + _orbitPaths: new Map([[25544, ring]]), + _detectionObjects: new Map(), + _trackedNorad: null, + }; + const controls = createControls({ + state, + services: { layerState: { isExplicitLayerStateOrigin: () => false } }, + parts: { labels: { _syncIssOverlay: () => (overlaySyncs += 1) } }, + source: {}, + }); + controls._applyClassVisibility(); + assert.equal(state._points.get(25544).show, false); + assert.equal(ring.primitive.show, false, 'the ring goes with the class'); + assert.ok(overlaySyncs >= 1, 'the label is re-synced'); + state._params.classes = { ...DEFAULT_SATELLITE_CLASSES }; + controls._applyClassVisibility(); + assert.equal(ring.primitive.show, true, 'and comes back with it'); +}); + +test('a failed Starlink load hides the core catalog Starlink points again', async () => { + const { createCatalog } = await import('./catalog.js'); + let applied = 0; + const state = { + _params: { + catalog: 'dense', + classes: { ...DEFAULT_SATELLITE_CLASSES, starlink: true }, + }, + _denseLoadToken: 3, + _denseIds: [], + _catalog: new Map(), + _points: new Map(), + _detectionObjects: new Map(), + _trackedNorad: null, + _activeUpdateControllers: new Set(), + }; + const catalog = createCatalog({ + state, + services: {}, + parts: { + controls: { + _applyClassVisibility: () => (applied += 1), + _notifyRowControls() {}, + }, + labels: { _syncIssOverlay() {} }, + }, + source: {}, + }); + catalog._denseLoadFailed(state._denseLoadToken, 'feed unreachable'); + assert.equal(state._params.catalog, 'core'); + assert.equal(state._params.classes.starlink, false, 'the choice says so'); + assert.ok(applied >= 1, 'class visibility is applied again'); +}); diff --git a/src/layers/satellites/controls.js b/src/layers/satellites/controls.js index 0baedfcd8..c4c26f2f8 100644 --- a/src/layers/satellites/controls.js +++ b/src/layers/satellites/controls.js @@ -48,16 +48,30 @@ export function createControls({ state: layerState, services, parts, source }) { /** * Show or hide every point by its class. The tracked satellite's point - * stays hidden (its entity draws the dot). + * stays hidden (its entity draws the dot). The ISS's ring and label go + * with its class. */ function _applyClassVisibility() { for (const [noradId, point] of layerState._points) { if (noradId === layerState._trackedNorad) continue; point.show = _classVisible(noradId); } + const issPath = layerState._orbitPaths.get(ISS_NORAD); + if (issPath) issPath.primitive.show = _issOrbitVisible(); + parts.labels?._syncIssOverlay?.(); layerState._detectionObjects.clear(); } + /** Whether the ISS's ring shows: orbits on, the layer drawing, its class on. */ + function _issOrbitVisible() { + return ( + satelliteVisualsVisible( + layerState._enabled, + layerState._params.showOrbits, + ) && _classVisible(ISS_NORAD) + ); + } + /** * The row's info line while the classes rest on old or missing SATCAT * evidence (`_satcatState`), and an empty one otherwise. @@ -530,11 +544,14 @@ export function createControls({ state: layerState, services, parts, source }) { } if (params.showOrbits !== undefined) { layerState._params.showOrbits = params.showOrbits !== false; - for (const path of layerState._orbitPaths.values()) - path.primitive.show = satelliteVisualsVisible( - layerState._enabled, - layerState._params.showOrbits, - ); + for (const [noradId, path] of layerState._orbitPaths) + path.primitive.show = + noradId === ISS_NORAD + ? _issOrbitVisible() + : satelliteVisualsVisible( + layerState._enabled, + layerState._params.showOrbits, + ); parts.labels._syncIssOverlay(); } if (catalogChanged && catalog === 'dense') { @@ -690,6 +707,7 @@ export function createControls({ state: layerState, services, parts, source }) { _classTally, _classVisible, _applyClassVisibility, + _issOrbitVisible, satelliteVisualsVisible, satelliteCatalogModeChanged, methods, diff --git a/src/layers/satellites/ingestion.js b/src/layers/satellites/ingestion.js index f65b476ee..e5fdb5caa 100644 --- a/src/layers/satellites/ingestion.js +++ b/src/layers/satellites/ingestion.js @@ -356,7 +356,10 @@ export function createIngestion({ if (layerState._catalog.has(ISS_NORAD)) { parts.rendering._showOrbitPath(ISS_NORAD, POINT_STYLES.iss.color); const issPath = layerState._orbitPaths.get(ISS_NORAD); - if (issPath) issPath.primitive.show = layerState._params.showOrbits; + if (issPath) + issPath.primitive.show = + layerState._params.showOrbits && + parts.controls._classVisible(ISS_NORAD); parts.labels._syncIssOverlay(); } diff --git a/src/layers/satellites/labels.js b/src/layers/satellites/labels.js index c62bbb308..612996968 100644 --- a/src/layers/satellites/labels.js +++ b/src/layers/satellites/labels.js @@ -83,6 +83,8 @@ export function createLabels({ state: layerState, services, parts, source }) { function _dockedCompanionNames() { const names = []; for (const noradId of layerState._dockedCompanions) { + // A hidden class is not named on the tracked card either. + if (parts.controls?._classVisible?.(noradId) === false) continue; names.push( layerState._catalog.get(noradId)?.name?.trim() || `SAT-${noradId}`, ); @@ -102,6 +104,7 @@ export function createLabels({ state: layerState, services, parts, source }) { layerState._trackedNorad !== ISS_NORAD && !layerState._dockedCompanions.has(ISS_NORAD) && layerState._catalog.has(ISS_NORAD) && + parts.controls?._classVisible?.(ISS_NORAD) !== false && _issDisplayCached(); if (!visible) { layerState._overlayHost.clearSource(ISS_OVERLAY_SOURCE_ID);