diff --git a/changelog.d/1186-launch-unknown-coordinates.md b/changelog.d/1186-launch-unknown-coordinates.md new file mode 100644 index 000000000..d03c2d902 --- /dev/null +++ b/changelog.d/1186-launch-unknown-coordinates.md @@ -0,0 +1,4 @@ +- Keep missing launch-pad coordinates unknown so launches use the location + fallback or stay off the map instead of appearing at 0°N 0°E. Missing stage + flight numbers no longer show "FLIGHT 0" + (Rudy Celekli, @rudycelekli, #1186). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 8cb7b6d17..105fdd37f 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -791,6 +791,11 @@ and `./layers/launches`. Each owns its catalog, render state, tracking, interact and teardown. Source adapters retain the existing CelesTrak and Launch Library endpoints; standalone entries provide scene services and the satellite dependency. The mission factory separates paths, replay, camera, panel, overlays and placement. +Launch Library pad coordinates retain unknown values: missing fields use the +reported location coordinates when available, and launches without a known pad +position stay off the map. Genuine zero coordinates remain valid. Undisclosed +stage coordinates, downrange distance and flight numbers remain unknown; the +mission panel omits the flight-number label when no number was reported. Pending mission requests abort on disable/destruction and malformed launch snapshots preserve the last accepted display. Catalog groups, propagation cadence, tracking intent, replay timing, controls and source attribution remain unchanged. diff --git a/src/data/rocketLaunches.test.mjs b/src/data/rocketLaunches.test.mjs index ccfd280e2..b68a11db3 100644 --- a/src/data/rocketLaunches.test.mjs +++ b/src/data/rocketLaunches.test.mjs @@ -677,6 +677,37 @@ test('uses Launch Library 2 pad latitude/longitude fields', () => { assert.equal(launches[0].lon, -120.611); }); +test('null or blank pad coordinates stay unknown instead of becoming 0', () => { + const base = { net: '2026-07-20T10:00:00Z' }; + for (const blank of [null, '', ' ', false]) { + assert.deepEqual( + normalizeRocketLaunches([{ ...base, id: 'blank', pad: { latitude: blank, longitude: blank } }], NOW), + [], + ); + } +}); + +test('null pad coordinates use the location fallback instead of 0,0', () => { + const base = { net: '2026-07-20T10:00:00Z' }; + const [fallback] = normalizeRocketLaunches([{ ...base, id: 'fallback', + pad: { latitude: null, longitude: null, location: { coordinates: '-80.6,28.6' } } }], NOW); + assert.equal(fallback.lat, 28.6); + assert.equal(fallback.lon, -80.6); +}); + +test('an undisclosed landing keeps top-level position and stage numbers unknown', () => { + const base = { net: '2026-07-20T10:00:00Z' }; + const [launch] = normalizeRocketLaunches([{ ...base, id: 'landing', pad: { latitude: 1, longitude: 2 }, + rocket: { launcher_stage: [{ launcher_flight_number: null, + landing: { attempt: true, latitude: null, longitude: null, downrange_distance: null, + landing_location: { latitude: null, longitude: null } } }] } }], NOW); + const [stage] = launch.recoveryStages; + assert.equal(stage.lat, null); + assert.equal(stage.lon, null); + assert.equal(stage.downrangeKm, null); + assert.equal(stage.flightNumber, null); +}); + test('normalizes detailed payload and stage recovery records', () => { const launches = normalizeRocketLaunches({ results: [{ id: 'recovery-details', diff --git a/src/layers/launches/model.js b/src/layers/launches/model.js index 0a9dd67ea..5697e07e8 100644 --- a/src/layers/launches/model.js +++ b/src/layers/launches/model.js @@ -10,6 +10,9 @@ export const UNKNOWN_LAUNCH_STATUS = msg('Unknown'); export function createModel({ state: layerState, services, parts, source }) { function finiteCoordinate(value) { + // Number(null), Number('') and Number(false) are 0: absent is not zero. + if (value == null || typeof value === 'boolean') return null; + if (typeof value === 'string' && value.trim() === '') return null; const number = Number(value); return Number.isFinite(number) ? number : null; } @@ -184,15 +187,11 @@ export function createModel({ state: layerState, services, parts, source }) { const pad = launch.pad || {}; const location = pad.location || {}; const coordinates = location.coordinates || ''; - const [coordinateLon, coordinateLat] = String(coordinates) - .split(',') - .map(Number); - const lat = Number.isFinite(Number(pad.latitude)) - ? Number(pad.latitude) - : coordinateLat; - const lon = Number.isFinite(Number(pad.longitude)) - ? Number(pad.longitude) - : coordinateLon; + const [coordinateLon, coordinateLat] = String(coordinates).split(','); + const lat = + finiteCoordinate(pad.latitude) ?? finiteCoordinate(coordinateLat); + const lon = + finiteCoordinate(pad.longitude) ?? finiteCoordinate(coordinateLon); const payloads = normalizePayloadFlights(launch); return { id: String(