"""Helpers for building stable TLE lines from orbital elements.""" from __future__ import annotations from datetime import datetime from typing import Any, Optional def compute_tle_checksum(line: str) -> str: """Compute the standard modulo-10 checksum for a TLE line.""" total = 0 for char in line[:68]: if char.isdigit(): total += int(char) elif char == "-": total += 1 return str(total % 10) def _parse_epoch(value: Any) -> Optional[datetime]: if not value: return None if isinstance(value, datetime): return value if isinstance(value, str): return datetime.fromisoformat(value.replace("Z", "+00:00")) return None def build_tle_line1(norad_cat_id: Any, epoch: Any) -> Optional[str]: """Build a valid TLE line 1 from the NORAD id and epoch.""" epoch_date = _parse_epoch(epoch) if not norad_cat_id or epoch_date is None: return None epoch_year = epoch_date.year % 100 start_of_year = datetime(epoch_date.year, 1, 1, tzinfo=epoch_date.tzinfo) day_of_year = (epoch_date - start_of_year).days + 1 ms_of_day = ( epoch_date.hour * 3600000 + epoch_date.minute * 60000 + epoch_date.second * 1000 + int(epoch_date.microsecond / 1000) ) day_fraction = ms_of_day / 86400000 decimal_fraction = f"{day_fraction:.8f}"[1:] epoch_str = f"{epoch_year:02d}{day_of_year:03d}{decimal_fraction}" core = ( f"1 {int(norad_cat_id):05d}U 00001A {epoch_str}" " .00000000 00000-0 00000-0 0 999" ) return core + compute_tle_checksum(core) def build_tle_line2( norad_cat_id: Any, inclination: Any, raan: Any, eccentricity: Any, arg_of_perigee: Any, mean_anomaly: Any, mean_motion: Any, ) -> Optional[str]: """Build a valid TLE line 2 from the standard orbital elements.""" required = [ norad_cat_id, inclination, raan, eccentricity, arg_of_perigee, mean_anomaly, mean_motion, ] if any(value is None for value in required): return None eccentricity_digits = str(round(float(eccentricity) * 10_000_000)).zfill(7) core = ( f"2 {int(norad_cat_id):05d}" f" {float(inclination):8.4f}" f" {float(raan):8.4f}" f" {eccentricity_digits}" f" {float(arg_of_perigee):8.4f}" f" {float(mean_anomaly):8.4f}" f" {float(mean_motion):11.8f}" "00000" ) return core + compute_tle_checksum(core) def build_tle_lines_from_elements( *, norad_cat_id: Any, epoch: Any, inclination: Any, raan: Any, eccentricity: Any, arg_of_perigee: Any, mean_anomaly: Any, mean_motion: Any, ) -> tuple[Optional[str], Optional[str]]: """Build both TLE lines from a metadata payload.""" line1 = build_tle_line1(norad_cat_id, epoch) line2 = build_tle_line2( norad_cat_id, inclination, raan, eccentricity, arg_of_perigee, mean_anomaly, mean_motion, ) return line1, line2