mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
fix(satellite): strip observer-relative fields from SSE tracker
SSE runs server-wide with DEFAULT_LAT/LON defaults of 0,0. Emitting elevation/azimuth/distance/visible from the tracker produced wrong values (always visible:False) that overwrote correct data from the per-client HTTP poll every second. The HTTP poll (/satellite/position) owns all observer-relative data. SSE now only emits lat/lon/altitude/groundTrack. Also removes the unused DEFAULT_LATITUDE/DEFAULT_LONGITUDE import.
This commit is contained in:
@@ -10,7 +10,7 @@ from datetime import datetime, timedelta
|
||||
import requests
|
||||
from flask import Blueprint, Response, jsonify, make_response, render_template, request
|
||||
|
||||
from config import DEFAULT_LATITUDE, DEFAULT_LONGITUDE, SHARED_OBSERVER_LOCATION_ENABLED
|
||||
from config import SHARED_OBSERVER_LOCATION_ENABLED
|
||||
from utils.sse import sse_stream_fanout
|
||||
from data.satellites import TLE_SATELLITES
|
||||
from utils.database import (
|
||||
@@ -218,11 +218,6 @@ def _start_satellite_tracker():
|
||||
now = ts.now()
|
||||
now_dt = now.utc_datetime()
|
||||
|
||||
obs_lat = DEFAULT_LATITUDE
|
||||
obs_lon = DEFAULT_LONGITUDE
|
||||
has_observer = (obs_lat != 0.0 or obs_lon != 0.0)
|
||||
observer = wgs84.latlon(obs_lat, obs_lon) if has_observer else None
|
||||
|
||||
tracked = get_tracked_satellites(enabled_only=True)
|
||||
positions = []
|
||||
|
||||
@@ -245,24 +240,18 @@ def _start_satellite_tracker():
|
||||
geocentric = satellite.at(now)
|
||||
subpoint = wgs84.subpoint(geocentric)
|
||||
|
||||
# SSE stream is server-wide and cannot know per-client observer
|
||||
# location. Observer-relative fields (elevation, azimuth, distance,
|
||||
# visible) are intentionally omitted here — the per-client HTTP poll
|
||||
# at /satellite/position owns those using the client's actual location.
|
||||
pos = {
|
||||
'satellite': sat_name,
|
||||
'norad_id': norad_id,
|
||||
'lat': float(subpoint.latitude.degrees),
|
||||
'lon': float(subpoint.longitude.degrees),
|
||||
'altitude': float(geocentric.distance().km - 6371),
|
||||
'visible': False,
|
||||
'altitude': float(subpoint.elevation.km),
|
||||
}
|
||||
|
||||
if has_observer and observer is not None:
|
||||
diff = satellite - observer
|
||||
topocentric = diff.at(now)
|
||||
alt, az, dist = topocentric.altaz()
|
||||
pos['elevation'] = float(alt.degrees)
|
||||
pos['azimuth'] = float(az.degrees)
|
||||
pos['distance'] = float(dist.km)
|
||||
pos['visible'] = bool(alt.degrees > 0)
|
||||
|
||||
# Ground track with caching (90 points, TTL 1800s)
|
||||
cache_key_track = (sat_name, tle1[:20])
|
||||
cached = _track_cache.get(cache_key_track)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import queue
|
||||
import threading
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -70,6 +72,55 @@ def test_get_satellite_position_skyfield_error(mock_load, client):
|
||||
assert response.status_code == 200
|
||||
assert response.json['positions'] == []
|
||||
|
||||
def test_tracker_position_has_no_observer_fields():
|
||||
"""SSE tracker positions must NOT include observer-relative fields.
|
||||
|
||||
The tracker runs server-side with a fixed (potentially wrong) observer
|
||||
location. Only the per-request /satellite/position endpoint, which
|
||||
receives the client's actual location, should emit elevation/azimuth/
|
||||
distance/visible.
|
||||
"""
|
||||
import sys
|
||||
from routes.satellite import _start_satellite_tracker
|
||||
|
||||
ISS_TLE = (
|
||||
'ISS (ZARYA)',
|
||||
'1 25544U 98067A 24001.00000000 .00016717 00000-0 30171-3 0 9993',
|
||||
'2 25544 51.6416 20.4567 0004561 45.3212 67.8912 15.49876543123457',
|
||||
)
|
||||
|
||||
sat_q = queue.Queue(maxsize=5)
|
||||
mock_app = MagicMock()
|
||||
mock_app.satellite_queue = sat_q
|
||||
|
||||
from skyfield.api import load as _real_load
|
||||
real_ts = _real_load.timescale(builtin=True)
|
||||
|
||||
# Pre-populate track cache so the tracker loop doesn't block computing 90 points
|
||||
tle_key = (ISS_TLE[0], ISS_TLE[1][:20])
|
||||
stub_track = [{'lat': 0.0, 'lon': float(i), 'past': i < 45} for i in range(91)]
|
||||
with patch('routes.satellite._tle_cache', {'ISS': ISS_TLE}), \
|
||||
patch('routes.satellite.get_tracked_satellites') as mock_tracked, \
|
||||
patch('routes.satellite._track_cache', {tle_key: (stub_track, 1e18)}), \
|
||||
patch('routes.satellite._get_timescale', return_value=real_ts), \
|
||||
patch.dict('sys.modules', {'app': mock_app}):
|
||||
mock_tracked.return_value = [{
|
||||
'name': 'ISS (ZARYA)', 'norad_id': 25544,
|
||||
'tle_line1': ISS_TLE[1], 'tle_line2': ISS_TLE[2],
|
||||
}]
|
||||
|
||||
t = threading.Thread(target=_start_satellite_tracker, daemon=True)
|
||||
t.start()
|
||||
msg = sat_q.get(timeout=10)
|
||||
|
||||
assert msg['type'] == 'positions'
|
||||
pos = msg['positions'][0]
|
||||
for forbidden in ('elevation', 'azimuth', 'distance', 'visible'):
|
||||
assert forbidden not in pos, f"SSE tracker must not emit '{forbidden}'"
|
||||
for required in ('lat', 'lon', 'altitude', 'satellite', 'norad_id'):
|
||||
assert required in pos, f"SSE tracker must emit '{required}'"
|
||||
|
||||
|
||||
# Logic Integration Test (Simulating prediction)
|
||||
def test_predict_passes_empty_cache(client):
|
||||
"""Verify that if the satellite is not in cache, no passes are returned."""
|
||||
|
||||
Reference in New Issue
Block a user