# 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.md) 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 {py:class}`.AbstractKeplerianOrbitElements`, from which the concrete implementation {py: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):

```python

from astropy.time import Time
from astropy import units as u
from satmad.core.celestial_bodies_lib 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 {py: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.

```python

from astropy.time import Time
from astropy import units as u
from astropy.coordinates import GCRS, CartesianDifferential, CartesianRepresentation
from satmad.core.celestial_bodies_lib import EARTH
from satmad.coordinates.frames import init_pvt
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_pvt(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)

The conversions to and from the cartesian coordinates are based on GMAT [[OM3]](../references.md#orbital-mechanics). Note that the classical orbital elements have a number of singularities for a lot of common orbits (e.g. circular and/or equatorial). GMAT Mathematical Specifications handles these cases gracefully, but care must be taken when interpreting the results. In some cases, return-trip testing may not be successful. However, the code is tested against GMAT and is working as expected.

## Reference/API

```{eval-rst}
.. automodule:: satmad.propagation.classical_orb_elems
    :members:
    :undoc-members:
```