Representing Orbits with Classical (Keplerian) Elements
==========================================================

Introduction to Keplerian Orbital Elements
--------------------------------------------

Perhaps the most well-known representation of the orbit is the Classical or Keplerian Elements. These elements are
based on 6 parameters (conic geometry and its orientation in space) and are equivalent to the 6 element cartesian
coordinates (position and velocity).

A long and detailed definition is well beyond the scope of this documentation. More information can be found in
the `TLE page in Wikipedia <https://en.wikipedia.org/wiki/Orbital_elements>`_.

However, it must be noted that, these elements have singularities and ill-defined
parameters for some special but common cases (such as true anomaly and argument of periapsis when the orbit is
equatorial), and the implementation generally handles them gracefully. That said, parabolic orbits
(eccentricity = 1.0) cannot be handled.

Mean vs Osculating Elements
-------------------------------

A not very obvious fact to those learning about the Keplerian Orbital Elements is that it is just a format to represent
the orbit; without further definition, it is not very useful. This is similar to cartesian coordinates needing a
coordinate system definition to actually represent a position in space.

The actual implementation of the Keplerian Elements rests on the force model under which they are generated.
For example, the `TLEs <tle.rst>`_ are Mean Keplerian Orbital Elements generated with a specific theory that includes
linearised J2, J3 and J4 zonal harmonics and the elements are defined in True Equator, Mean Equinox coordinate frame.
They would be incompatible with other Mean Elements generated with other
force models and theories. Osculating Keplerian Orbital Elements are generated with a two-body force model.

Therefore, over a trajectory generated with a two-body force model, the Osculating Keplerian Orbital Elements
(apart from true anomaly) should stay constant, limited by the accuracy of the trajectory generation algorithm.
On the other hand, osculating orbital elements should be used with care, particularly in contexts where
instantaneous orbital elements are computed on a trajectory generated by a non-two-body model
(e.g. including geopotentials). The orbital elements will *not* stay constant along the trajectory, simply because the
force model over successive points are not strictly two-body.


Initialising and Using the Keplerian Orbital Elements
------------------------------------------------------

There is an Abstract Base Class :class:`.AbstractKeplerianOrbitElements`, from which the concrete implementation
:class:`.OsculatingKeplerianOrbElems` derive. It represents the Osculating Orbital Elements for coordinates in the
local inertial frame (e.g. GCRS for the Earth).

The usual way to initialise the class is via initial orbital elements (note the initialisation with units):

.. code-block:: python
    :linenos:

    from astropy.time import Time
    from astropy import units as u
    from satmad.core.celestial_bodies import EARTH
    from satmad.propagation.classical_orb_elems import OsculatingKeplerianOrbElems

    time = Time("2020-01-11T11:00:00.000", scale="utc")
    central_body = EARTH

    sm_axis = 7055.95320378 * u.km
    ecc = 0.0020835 * u.dimensionless_unscaled
    incl = 1.71234602 * u.rad
    raan = 4.42402394 * u.rad
    arg_perigee = 5.23982923 * u.rad
    true_an = 1.5 * u.rad

    orb_elems = OsculatingKeplerianOrbElems(
        time, sm_axis, ecc, incl, raan, arg_perigee, true_an, central_body
    )

The second way to initialise the orbital elements is via cartesian coordinates, through the
:meth:`.OsculatingKeplerianOrbElems.from_cartesian` method. The initial cartesian coordinates can be in any frame, they
are automatically converted to the inertial frame of the central body.
Once the Osculating Keplerian Elements are initialised, it is possible to query the parameters and some derived
parameters like period or radius of periapsis and apoapsis.

.. code-block:: python
    :linenos:

    from astropy.time import Time
    from astropy import units as u
    from astropy.coordinates import GCRS, CartesianDifferential, CartesianRepresentation
    from satmad.core.celestial_bodies import EARTH
    from satmad.coordinates.frames import init_rvt
    from satmad.propagation.classical_orb_elems import OsculatingKeplerianOrbElems

    time = Time("2020-01-11T11:00:00.000", scale="utc")
    central_body = EARTH

    r = CartesianRepresentation([7.213392947764267e+03, 8.523654531348812e+01, 2.783146976770290e-16 ], unit=u.km)
    v = CartesianDifferential([5.902225938368851e-02, 7.421779936019859e+00, 1.595360086373873e-18], unit=u.km / u.s)
    rv_init = init_rvt(GCRS, time, r.with_differentials(v))

    orb_elems = OsculatingKeplerianOrbElems.from_cartesian(rv_init, central_body)

    # Query parameters
    print(f"Semimajor axis: {orb_elems.sm_axis}")
    print(f"Period: {orb_elems.period}")
    print(f"Radius of Periapsis: {orb_elems.periapsis}")
    print(f"Period: {orb_elems.true_anomaly.to(u.deg)}")


Another useful output of the Osculating Keplerian Elements to convert them to the cartesian position and velocity in
the inertial coordinate frame belonging to the orbital elements. Just appending the following line to the first example
will yield the cartesian coordinates in a `SkyCoord` object.

    >>> rv = orb_elems.to_cartesian()
    >>> print(rv)



Reference/API
-------------
.. automodule:: satmad.propagation.classical_orb_elems
    :members:
    :undoc-members: