"""CelesTrak TLE Collector Collects satellite TLE (Two-Line Element) data from CelesTrak.org. Free, no authentication required. """ import json from typing import Dict, Any, List import httpx from app.services.collectors.base import BaseCollector class CelesTrakTLECollector(BaseCollector): name = "celestrak_tle" priority = "P2" module = "L3" frequency_hours = 24 data_type = "satellite_tle" @property def base_url(self) -> str: return "https://celestrak.org/NORAD/elements/gp.php" async def fetch(self) -> List[Dict[str, Any]]: satellite_groups = [ "starlink", "gps-ops", "galileo", "glonass", "beidou", "leo", "geo", "iridium-next", ] all_satellites = [] async with httpx.AsyncClient(timeout=120.0) as client: for group in satellite_groups: try: url = f"https://celestrak.org/NORAD/elements/gp.php?GROUP={group}&FORMAT=json" response = await client.get(url) if response.status_code == 200: data = response.json() if isinstance(data, list): all_satellites.extend(data) print(f"CelesTrak: Fetched {len(data)} satellites from group '{group}'") except Exception as e: print(f"CelesTrak: Error fetching group '{group}': {e}") if not all_satellites: return self._get_sample_data() print(f"CelesTrak: Total satellites fetched: {len(all_satellites)}") # Return raw data - base.run() will call transform() return all_satellites def transform(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: transformed = [] for item in raw_data: transformed.append( { "name": item.get("OBJECT_NAME", "Unknown"), "reference_date": item.get("EPOCH", ""), "metadata": { "norad_cat_id": item.get("NORAD_CAT_ID"), "international_designator": item.get("OBJECT_ID"), "epoch": item.get("EPOCH"), "mean_motion": item.get("MEAN_MOTION"), "eccentricity": item.get("ECCENTRICITY"), "inclination": item.get("INCLINATION"), "raan": item.get("RA_OF_ASC_NODE"), "arg_of_perigee": item.get("ARG_OF_PERICENTER"), "mean_anomaly": item.get("MEAN_ANOMALY"), "classification_type": item.get("CLASSIFICATION_TYPE"), "bstar": item.get("BSTAR"), "mean_motion_dot": item.get("MEAN_MOTION_DOT"), "mean_motion_ddot": item.get("MEAN_MOTION_DDOT"), "ephemeris_type": item.get("EPHEMERIS_TYPE"), }, } ) return transformed def _get_sample_data(self) -> List[Dict[str, Any]]: return [ { "name": "STARLINK-1000", "norad_cat_id": 44720, "international_designator": "2019-029AZ", "epoch": "2026-03-13T00:00:00Z", "mean_motion": 15.79234567, "eccentricity": 0.0001234, "inclination": 53.0, }, ]