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.