Files
2026-03-09 22:07:19 +01:00

106 lines
3.2 KiB
Python

from skyfield.api import load
import asyncio
SATELLITE_GROUPS = {
"military": "https://celestrak.org/NORAD/elements/gp.php?GROUP=military&FORMAT=tle",
"stations": "https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle",
"navigation": "https://celestrak.org/NORAD/elements/gp.php?GROUP=gnss&FORMAT=tle",
}
GROUP_LIMITS = {
"military": 800,
"stations": 50,
"navigation": 120,
}
satellites_data: dict = {}
def load_satellites():
"""
Blocking I/O: loads TLE data from Celestrak.
Must be called via asyncio.to_thread to avoid blocking the event loop.
"""
global satellites_data
try:
ts = load.timescale()
loaded = {}
for group, url in SATELLITE_GROUPS.items():
try:
filename = f"{group}.tle"
sats = load.tle_file(url, filename=filename)
loaded[group] = sats
print(f"[SPACE] {group}: {len(sats)} satellites loaded.")
except Exception as e:
print(f"[SPACE] Failed to load {group}: {e}")
satellites_data = {**loaded, "ts": ts}
except Exception as e:
print(f"[SPACE] Critical load failure: {e}")
def _compute_positions_sync(sats: list, category: str, t, limit: int) -> list:
"""
CPU-bound position computation — runs in a thread executor.
Returns dicts, does NOT mutate shared state.
"""
positions = []
for sat in sats[:limit]:
try:
geocentric = sat.at(t)
subpoint = geocentric.subpoint()
lat = subpoint.latitude.degrees
lon = subpoint.longitude.degrees
alt = subpoint.elevation.m
if not (-90 <= lat <= 90) or not (-180 <= lon <= 180):
continue
positions.append({
"id": sat.model.satnum,
"name": sat.name,
"lat": lat,
"lon": lon,
"alt": alt,
"category": category,
"type": "satellite"
})
except Exception:
pass # skip satellites with bad TLE data
return positions
async def get_satellite_positions() -> list:
if "ts" not in satellites_data:
# First run: load everything in a background thread
await asyncio.to_thread(load_satellites)
if "ts" not in satellites_data:
print("[SPACE] Satellite data unavailable.")
return []
t = satellites_data["ts"].now()
all_positions = []
for group, limit in GROUP_LIMITS.items():
if group in satellites_data:
result = await asyncio.to_thread(
_compute_positions_sync,
satellites_data[group],
group,
t,
limit
)
all_positions.extend(result)
print(f"[SPACE] {len(all_positions)} satellite positions computed.")
return all_positions
if __name__ == "__main__":
load_satellites()
positions = asyncio.run(get_satellite_positions())
print(f"Total: {len(positions)}")
from collections import Counter
for cat, count in Counter(p["category"] for p in positions).items():
print(f" {cat}: {count}")