mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Fix pass calculation race condition and 1Hz distance updates
- Move _passAbortController = null to after response.json() so the retry scheduler cannot see a false idle state mid-parse, increment _passRequestId, and discard the in-flight response — this was causing non-ISS satellites to show no passes intermittently - Add _computeSlantRange() helper using 3D ECEF geometry - Update applyTelemetryPosition to compute slant range from SSE lat/lon/ altitude, giving distance updates at 1Hz instead of 5s HTTP poll rate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1024,6 +1024,23 @@
|
|||||||
return Number.isFinite(value);
|
return Number.isFinite(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute slant range (km) from observer on Earth's surface to satellite.
|
||||||
|
// Uses spherical Earth approximation; accurate enough for display.
|
||||||
|
function _computeSlantRange(obsLat, obsLon, satLat, satLon, satAltKm) {
|
||||||
|
const R = 6371;
|
||||||
|
const toRad = Math.PI / 180;
|
||||||
|
const lat1 = obsLat * toRad, lon1 = obsLon * toRad;
|
||||||
|
const lat2 = satLat * toRad, lon2 = satLon * toRad;
|
||||||
|
const rSat = R + satAltKm;
|
||||||
|
const ox = R * Math.cos(lat1) * Math.cos(lon1);
|
||||||
|
const oy = R * Math.cos(lat1) * Math.sin(lon1);
|
||||||
|
const oz = R * Math.sin(lat1);
|
||||||
|
const sx = rSat * Math.cos(lat2) * Math.cos(lon2);
|
||||||
|
const sy = rSat * Math.cos(lat2) * Math.sin(lon2);
|
||||||
|
const sz = rSat * Math.sin(lat2);
|
||||||
|
return Math.sqrt((sx-ox)**2 + (sy-oy)**2 + (sz-oz)**2);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeLivePosition(pos, previous = latestLivePosition) {
|
function normalizeLivePosition(pos, previous = latestLivePosition) {
|
||||||
if (!pos) return null;
|
if (!pos) return null;
|
||||||
const merged = {
|
const merged = {
|
||||||
@@ -1063,7 +1080,17 @@
|
|||||||
if (telAlt && _isFiniteNumber(normalized.altitude ?? normalized.alt)) telAlt.textContent = (normalized.altitude ?? normalized.alt).toFixed(0) + ' km';
|
if (telAlt && _isFiniteNumber(normalized.altitude ?? normalized.alt)) telAlt.textContent = (normalized.altitude ?? normalized.alt).toFixed(0) + ' km';
|
||||||
if (telEl && _isFiniteNumber(normalized.elevation ?? normalized.el)) telEl.textContent = (normalized.elevation ?? normalized.el).toFixed(1) + '°';
|
if (telEl && _isFiniteNumber(normalized.elevation ?? normalized.el)) telEl.textContent = (normalized.elevation ?? normalized.el).toFixed(1) + '°';
|
||||||
if (telAz && _isFiniteNumber(normalized.azimuth ?? normalized.az)) telAz.textContent = (normalized.azimuth ?? normalized.az).toFixed(1) + '°';
|
if (telAz && _isFiniteNumber(normalized.azimuth ?? normalized.az)) telAz.textContent = (normalized.azimuth ?? normalized.az).toFixed(1) + '°';
|
||||||
if (telDist && _isFiniteNumber(normalized.distance ?? normalized.dist)) telDist.textContent = (normalized.distance ?? normalized.dist).toFixed(0) + ' km';
|
// Compute slant range from the current lat/lon/altitude so the
|
||||||
|
// distance display updates at the same rate as position (1 Hz via
|
||||||
|
// SSE). Falls back to the Skyfield-computed value from the HTTP
|
||||||
|
// poll if geometry data is not available.
|
||||||
|
let displayDist = normalized.distance ?? normalized.dist;
|
||||||
|
const { lat: obsLat, lon: obsLon, valid: obsValid } = getObserverCoords();
|
||||||
|
const satAlt = normalized.altitude ?? normalized.alt;
|
||||||
|
if (obsValid && _isFiniteNumber(normalized.lat) && _isFiniteNumber(normalized.lon) && _isFiniteNumber(satAlt)) {
|
||||||
|
displayDist = _computeSlantRange(obsLat, obsLon, normalized.lat, normalized.lon, satAlt);
|
||||||
|
}
|
||||||
|
if (telDist && _isFiniteNumber(displayDist)) telDist.textContent = displayDist.toFixed(0) + ' km';
|
||||||
|
|
||||||
if (selectedPass == null && _isFiniteNumber(normalized.azimuth ?? normalized.az) && _isFiniteNumber(normalized.elevation ?? normalized.el)) {
|
if (selectedPass == null && _isFiniteNumber(normalized.azimuth ?? normalized.az) && _isFiniteNumber(normalized.elevation ?? normalized.el)) {
|
||||||
drawPolarPlotWithPosition(
|
drawPolarPlotWithPosition(
|
||||||
@@ -2068,9 +2095,10 @@
|
|||||||
clearTimeout(_passTimeoutId);
|
clearTimeout(_passTimeoutId);
|
||||||
_passTimeoutId = null;
|
_passTimeoutId = null;
|
||||||
}
|
}
|
||||||
if (_passAbortController === controller) {
|
// Keep _passAbortController set until AFTER response.json() so
|
||||||
_passAbortController = null;
|
// the retry scheduler doesn't incorrectly see the request as
|
||||||
}
|
// finished (passes=0 + no controller) and increment _passRequestId
|
||||||
|
// mid-parse, which would cause this response to be discarded.
|
||||||
if (_activePassRequestKey === requestKey) {
|
if (_activePassRequestKey === requestKey) {
|
||||||
_activePassRequestKey = null;
|
_activePassRequestKey = null;
|
||||||
}
|
}
|
||||||
@@ -2080,6 +2108,10 @@
|
|||||||
throw new Error('Unexpected response while calculating passes');
|
throw new Error('Unexpected response while calculating passes');
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
// Now safe to release the controller slot
|
||||||
|
if (_passAbortController === controller) {
|
||||||
|
_passAbortController = null;
|
||||||
|
}
|
||||||
if (requestId !== _passRequestId) return;
|
if (requestId !== _passRequestId) return;
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
const resolvedPasses = Array.isArray(data.passes) ? data.passes : [];
|
const resolvedPasses = Array.isArray(data.passes) ? data.passes : [];
|
||||||
|
|||||||
Reference in New Issue
Block a user