Notes about TLEs - poliastro/poliastro GitHub Wiki
class TLE:
...
iss = TLE.from_norad("4524")
# iss.propagate(Time.now() + 10 * u.h) # ???
# Cannot give a TLE
osc0 = iss.get_osculating(Time.now() + 10 * u.h) # SGP4 -> Orbit
osc0.propagate() # You're on your own!
# iss.sample(...) # ????
# Instead:
iss.to_ephem(time_range(...)) # Ephem(erides)
osc0.a # *Osculating* semi-major axis
iss.a # *Mean* (Brouwer/Kozai/whatever) semi-major axis
Orbit.from_classical( # Expects oscultating
iss.a, # Mean elements
iss.ecc,
...
) # VERY WRONG
iss.a # Raise an error
iss.mean_a
iss.get_osculating(...).a
Orbit = State + Epoch
osc0.state.a
osc0.a # Syntactic sugar
- What is the difference between osculating vs mean orbits?
- What typical problems happen?
Would be cool to have a JSON-like (also Fiona-like?) interface:
>>> from poliastro import gp
>>> with open("iss.tle") as fh:
>>> iss_tle = gp.load(fh)
>>> iss_tle = gp.loads(httpx.get("https://example.com/iss.tle").text)
Already started implementing some of these ideas in https://github.com/poliastro/poliastro/blob/e9917a9/contrib/satgpio.py. The function load_gp_from_celestrak
is more complex because it makes assumptions about the origin of the data (in this case, Celestrak). But the gp.load(s)
approach stated above could be made compatible with that (in other words: load_gp_from_celestrak
could use one of those functions)
- Do not read ccsds_ndm, it's GPL!
- Depending on beyond is not that straightforward because we don't like unicode attributes, we might have to copy-paste the code. (And the tests?)
- jplephem is a good starting point, but it doesn't support writes, or KVN either for read or write, and finally I don't quite like its Satrec objects
We should have a static, immutable representation of Mean Orbits/General Perturbations data, assuming that both a TLE and an OMM can share the same class.
Notice that OMM contains all the information needed to produce a TLE, and viceversa. Notice also that 3LE (often confused with TLEs) need an extra attribute, the name.
The question is, should we store all the extra data from the OMM? Especially covariances, custom data.
import attr
@attr.frozen
class GP: # Name TBD!
satnum: int
classification: str
epoch: str # Alternatively, dt.datetime
ndot: float
ndotdot: float
bstar: float
inc: float
raan: float
ecc: float
argp: float
M: float
n: float
revnum: int
# Evil?
TLE = GP
OMM = GP
Possible I/O API:
def load(fh, *, format=None):
return loads(fh.read(), format=format)
def loads(s, *, format=None):
format = format or _infer_format(s)
if not format:
raise ValueError("format not given and could not be inferred from input data")
...
def _infer_format(s):
...
def _loads_tle(s):
lines = s.splitlines()
if len(lines) == 2:
line1, line2 = lines
elif len(lines) == 3:
line0, line1, line2 = lines
else:
raise ValueError("Invalid TLE")
...
def _loads_omm_xml(s):
...
Notice that, at the moment, Orbit
maps to CCSDS OPM and Ephem
maps to CCSDS OEM. Which means that with 1 extra class, we would support all 3 use cases envisioned by CCSDS 502.0-B.
Naming is important though! Ideas: OMM()
, GP()
, MeanOrbit()
, ?
Same question as above: Orbit
objects probably don't have all things needed to write to an OPM. This probably means that:
- If we want to keep simple abstractions, they might not be generally useful, and having them in poliastro wouldn't harm.
- If we want to offer generally useful abstractions, they should support the full standard, which would take more time and effort and overlaps with things like ccsds_ndm.
Going back to the question above:
What is the difference between osculating vs mean orbits?
They both can be represented with a (Classical)State
, but they mean (no pun intended) different things, and can't be compared together.
By the way, CCSDS 502.0-B has a list of preferred units.