mirror of
https://github.com/smittix/intercept.git
synced 2026-04-26 07:40:01 -07:00
fix(sstv): fix inaccurate ISS orbit tracking — three root causes
1. iss_schedule() was importing TLE_SATELLITES directly from data/satellites.py (hardcoded, 446 days stale) instead of the live _tle_cache kept fresh by the 24h auto-refresh. Add get_cached_tle() to satellite.py and use it. 2. Ground track was a fake sine wave (inclination * sin(phase)) that mapped longitude offset directly to orbital phase, ignoring Earth's rotation under the satellite (~23° westward shift per orbit). Replace with a /sstv/iss-track endpoint that propagates the orbit via skyfield SGP4 over ±90 minutes, and update the frontend to call it. Past/future track rendered with separate polylines (dim solid vs bright dashed). 3. refresh_tle_data() updated _tle_cache in memory but never persisted back to data/satellites.py, so every restart reloaded the stale hardcoded TLE. Add _persist_tle_cache() called after each successful refresh. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,12 +13,14 @@ const SSTV = (function() {
|
||||
let issMap = null;
|
||||
let issMarker = null;
|
||||
let issTrackLine = null;
|
||||
let issTrackPast = null;
|
||||
let issPosition = null;
|
||||
let issUpdateInterval = null;
|
||||
let countdownInterval = null;
|
||||
let nextPassData = null;
|
||||
let pendingMapInvalidate = false;
|
||||
let locationListenersAttached = false;
|
||||
let issUpdateInterval = null;
|
||||
let issTrackInterval = null;
|
||||
let countdownInterval = null;
|
||||
let nextPassData = null;
|
||||
let pendingMapInvalidate = false;
|
||||
let locationListenersAttached = false;
|
||||
|
||||
// ISS frequency
|
||||
const ISS_FREQ = 145.800;
|
||||
@@ -93,12 +95,12 @@ const SSTV = (function() {
|
||||
if (latInput && storedLat) latInput.value = storedLat;
|
||||
if (lonInput && storedLon) lonInput.value = storedLon;
|
||||
|
||||
if (!locationListenersAttached) {
|
||||
if (latInput) latInput.addEventListener('change', saveLocationFromInputs);
|
||||
if (lonInput) lonInput.addEventListener('change', saveLocationFromInputs);
|
||||
locationListenersAttached = true;
|
||||
}
|
||||
}
|
||||
if (!locationListenersAttached) {
|
||||
if (latInput) latInput.addEventListener('change', saveLocationFromInputs);
|
||||
if (lonInput) lonInput.addEventListener('change', saveLocationFromInputs);
|
||||
locationListenersAttached = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save location from input fields
|
||||
@@ -250,12 +252,19 @@ const SSTV = (function() {
|
||||
// Create ISS marker (will be positioned when we get data)
|
||||
issMarker = L.marker([0, 0], { icon: issIcon }).addTo(issMap);
|
||||
|
||||
// Create ground track line
|
||||
// Past track (dimmer, solid)
|
||||
issTrackPast = L.polyline([], {
|
||||
color: '#00d4ff',
|
||||
weight: 1.5,
|
||||
opacity: 0.3,
|
||||
}).addTo(issMap);
|
||||
|
||||
// Future track (brighter, dashed)
|
||||
issTrackLine = L.polyline([], {
|
||||
color: '#00d4ff',
|
||||
weight: 2,
|
||||
opacity: 0.6,
|
||||
dashArray: '5, 5'
|
||||
opacity: 0.7,
|
||||
dashArray: '6, 4'
|
||||
}).addTo(issMap);
|
||||
|
||||
issMap.on('resize moveend zoomend', () => {
|
||||
@@ -272,9 +281,12 @@ const SSTV = (function() {
|
||||
*/
|
||||
function startIssTracking() {
|
||||
updateIssPosition();
|
||||
// Update every 5 seconds
|
||||
updateIssTrack();
|
||||
if (issUpdateInterval) clearInterval(issUpdateInterval);
|
||||
issUpdateInterval = setInterval(updateIssPosition, 5000);
|
||||
// Track refreshes every 5 minutes — one orbit is ~93 min so this keeps it current
|
||||
if (issTrackInterval) clearInterval(issTrackInterval);
|
||||
issTrackInterval = setInterval(updateIssTrack, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,6 +297,52 @@ const SSTV = (function() {
|
||||
clearInterval(issUpdateInterval);
|
||||
issUpdateInterval = null;
|
||||
}
|
||||
if (issTrackInterval) {
|
||||
clearInterval(issTrackInterval);
|
||||
issTrackInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and render the ISS ground track from the backend (TLE-propagated).
|
||||
*/
|
||||
async function updateIssTrack() {
|
||||
try {
|
||||
const response = await fetch('/sstv/iss-track');
|
||||
const data = await response.json();
|
||||
if (data.status !== 'ok' || !issTrackLine || !issTrackPast) return;
|
||||
|
||||
const pastPts = [], futurePts = [];
|
||||
for (const pt of data.track) {
|
||||
(pt.past ? pastPts : futurePts).push([pt.lat, pt.lon]);
|
||||
}
|
||||
|
||||
// Split future track at antimeridian crossings to avoid long horizontal lines
|
||||
const futureSegments = _splitAtAntimeridian(futurePts);
|
||||
const pastSegments = _splitAtAntimeridian(pastPts);
|
||||
|
||||
issTrackLine.setLatLngs(futureSegments);
|
||||
issTrackPast.setLatLngs(pastSegments);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch ISS track:', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split an array of [lat, lon] points into segments at antimeridian crossings.
|
||||
*/
|
||||
function _splitAtAntimeridian(points) {
|
||||
const segments = [];
|
||||
let current = [];
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
if (i > 0 && Math.abs(points[i][1] - points[i - 1][1]) > 180) {
|
||||
if (current.length > 1) segments.push(current);
|
||||
current = [];
|
||||
}
|
||||
current.push(points[i]);
|
||||
}
|
||||
if (current.length > 1) segments.push(current);
|
||||
return segments;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,55 +544,7 @@ const SSTV = (function() {
|
||||
issMarker.setLatLng([lat, lon]);
|
||||
}
|
||||
|
||||
// Calculate and draw ground track
|
||||
if (issTrackLine) {
|
||||
const trackPoints = [];
|
||||
const inclination = 51.6; // ISS orbital inclination in degrees
|
||||
|
||||
// Generate orbit track points
|
||||
for (let offset = -180; offset <= 180; offset += 3) {
|
||||
let trackLon = lon + offset;
|
||||
|
||||
// Normalize longitude
|
||||
while (trackLon > 180) trackLon -= 360;
|
||||
while (trackLon < -180) trackLon += 360;
|
||||
|
||||
// Calculate latitude based on orbital inclination
|
||||
const phase = (offset / 360) * 2 * Math.PI;
|
||||
const currentPhase = Math.asin(Math.max(-1, Math.min(1, lat / inclination)));
|
||||
let trackLat = inclination * Math.sin(phase + currentPhase);
|
||||
|
||||
// Clamp to valid range
|
||||
trackLat = Math.max(-inclination, Math.min(inclination, trackLat));
|
||||
|
||||
trackPoints.push([trackLat, trackLon]);
|
||||
}
|
||||
|
||||
// Split track at antimeridian to avoid line across map
|
||||
const segments = [];
|
||||
let currentSegment = [];
|
||||
|
||||
for (let i = 0; i < trackPoints.length; i++) {
|
||||
if (i > 0) {
|
||||
const prevLon = trackPoints[i - 1][1];
|
||||
const currLon = trackPoints[i][1];
|
||||
if (Math.abs(currLon - prevLon) > 180) {
|
||||
// Crossed antimeridian
|
||||
if (currentSegment.length > 0) {
|
||||
segments.push(currentSegment);
|
||||
}
|
||||
currentSegment = [];
|
||||
}
|
||||
}
|
||||
currentSegment.push(trackPoints[i]);
|
||||
}
|
||||
if (currentSegment.length > 0) {
|
||||
segments.push(currentSegment);
|
||||
}
|
||||
|
||||
// Use only the longest segment or combine if needed
|
||||
issTrackLine.setLatLngs(segments.length > 0 ? segments : []);
|
||||
}
|
||||
// Track is fetched separately by updateIssTrack() via /sstv/iss-track
|
||||
|
||||
// Pan map to follow ISS only when the map pane is currently renderable.
|
||||
if (isMapContainerVisible()) {
|
||||
|
||||
Reference in New Issue
Block a user