Merge pull request #1186 from rudycelekli/contrib/gods-next-36-launch-null-coordinates-20261016

fix(launches): keep null pad and landing coordinates unknown instead of 0
This commit is contained in:
Said Turksever authored and GitHub committed 2026-10-17 17:14:19 +01:00
commit 30976f917e
4 files changed
+48 -9

No files matched your search

@@ -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).
+5
View File
@@ -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 and teardown. Source adapters retain the existing CelesTrak and Launch Library
endpoints; standalone entries provide scene services and the satellite dependency. endpoints; standalone entries provide scene services and the satellite dependency.
The mission factory separates paths, replay, camera, panel, overlays and placement. 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 Pending mission requests abort on disable/destruction and malformed launch
snapshots preserve the last accepted display. Catalog groups, propagation cadence, snapshots preserve the last accepted display. Catalog groups, propagation cadence,
tracking intent, replay timing, controls and source attribution remain unchanged. tracking intent, replay timing, controls and source attribution remain unchanged.
+31
View File
@@ -677,6 +677,37 @@ test('uses Launch Library 2 pad latitude/longitude fields', () => {
assert.equal(launches[0].lon, -120.611); 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', () => { test('normalizes detailed payload and stage recovery records', () => {
const launches = normalizeRocketLaunches({ results: [{ const launches = normalizeRocketLaunches({ results: [{
id: 'recovery-details', id: 'recovery-details',
+8 -9
View File
@@ -10,6 +10,9 @@ export const UNKNOWN_LAUNCH_STATUS = msg('Unknown');
export function createModel({ state: layerState, services, parts, source }) { export function createModel({ state: layerState, services, parts, source }) {
function finiteCoordinate(value) { 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); const number = Number(value);
return Number.isFinite(number) ? number : null; return Number.isFinite(number) ? number : null;
} }
@@ -184,15 +187,11 @@ export function createModel({ state: layerState, services, parts, source }) {
const pad = launch.pad || {}; const pad = launch.pad || {};
const location = pad.location || {}; const location = pad.location || {};
const coordinates = location.coordinates || ''; const coordinates = location.coordinates || '';
const [coordinateLon, coordinateLat] = String(coordinates) const [coordinateLon, coordinateLat] = String(coordinates).split(',');
.split(',') const lat =
.map(Number); finiteCoordinate(pad.latitude) ?? finiteCoordinate(coordinateLat);
const lat = Number.isFinite(Number(pad.latitude)) const lon =
? Number(pad.latitude) finiteCoordinate(pad.longitude) ?? finiteCoordinate(coordinateLon);
: coordinateLat;
const lon = Number.isFinite(Number(pad.longitude))
? Number(pad.longitude)
: coordinateLon;
const payloads = normalizePayloadFlights(launch); const payloads = normalizePayloadFlights(launch);
return { return {
id: String( id: String(