Source code for m4opt.observer._tle
from typing import override
from astropy import units as u
from astropy.coordinates import TEME, SkyCoord
from satellite_tle import fetch_tle_from_celestrak
from sgp4.api import SGP4_ERRORS, Satrec
from ._core import ObserverLocation
[docs]
class TleObserverLocation(ObserverLocation):
"""An Earth satellite whose orbit is specified by its `two-line element (TLE) <https://www.space-track.org/documentation#tle>`_.
Notes
-----
The orbit propagation is based on the example code at
:ref:`astropy-coordinates-satellites`.
Examples
--------
Here is an example TLE for BurstCube (NORAD ID 59562):
>>> from astropy.time import Time
>>> from astropy import units as u
>>> import numpy as np
>>> from m4opt.observer import TleObserverLocation
>>> line1 = '1 59562U 98067WM 24220.55604657 .00200610 00000+0 13802-2 0 9999'
>>> line2 = '2 59562 51.6321 52.1851 0005233 205.4266 154.6476 15.73479335 17375'
>>> orbit = TleObserverLocation(line1, line2)
Evaluate the position and velocity of the satellite at one specific time:
>>> time = Time('2024-08-08 01:10:41')
>>> orbit(time)
<EarthLocation (4172.83539674, -514.29983197, -5254.79814474) km>
Or evaluate at an array of times:
>>> times = time + np.linspace(0 * u.min, 2 * u.min, 50)
>>> orbit(times).shape
(50,)
If any orbit propagation errors occur, then a :class:`RuntimeError` is
raised:
>>> time = Time('2025-01-01 00:00:00')
>>> orbit(time)
Traceback (most recent call last):
...
RuntimeError: mrt is less than 1.0 which indicates the satellite has decayed
""" # noqa: E501
def __init__(self, line1: str, line2: str):
"""Create a TLE from the text of its two lines."""
self._tle = Satrec.twoline2rv(line1, line2)
[docs]
@classmethod
def from_id(cls, norad_id: int) -> "TleObserverLocation":
"""Get the latest TLE for a satellite from Celestrak.
Examples
--------
Look up the latest TLE for the Fermi Gamma-Ray Space Telescope.
>>> from m4opt.observer import TleObserverLocation
>>> tle = TleObserverLocation.from_id(33053)
"""
*_, line1, line2 = fetch_tle_from_celestrak(norad_id)
return cls(line1, line2)
[docs]
@override
def __call__(self, time):
shape = time.shape
time = time.ravel()
time = time.utc
e, xyz, vxyz = self._tle.sgp4_array(time.jd1, time.jd2)
x, y, z = xyz.T
vx, vy, vz = vxyz.T
# If any errors occurred, only raise for the first error
e = e[e != 0]
if e.size > 0:
raise RuntimeError(SGP4_ERRORS[e[0]])
coord = SkyCoord(
x=x * u.km,
v_x=vx * u.km / u.s,
y=y * u.km,
v_y=vy * u.km / u.s,
z=z * u.km,
v_z=vz * u.km / u.s,
frame=TEME(obstime=time),
).itrs.earth_location
if shape:
coord = coord.reshape(shape)
else:
coord = coord[0]
return coord