"""
Orbital elements based on the position and velocity of Chandra at each 5 minute predictive
ephemeris state vector. In addition to the classical orbital elements, the orbit
perigee and apogee are available:
================ ====
MSID Unit
================ ====
semi_major_axis m
eccentricity --
inclination deg
ascending_node deg
argument_perigee deg
mean_anomaly deg
orbit_period sec
perigee_radius m
apogee_radius m
================ ====
Example::
>>> from Ska.engarchive import fetch_eng as fetch
>>> dat = fetch.Msidset(['inclination', 'perigee_radius'], '1999:200', stat='daily')
>>> subplot(2, 1, 1)
>>> dat['inclination'].plot()
>>> subplot(2, 1, 2)
>>> dat['perigee_radius'].plot()
The relevant equations were taken from http://www.castor2.ca/05_OD/01_Gauss/14_Kepler/index.html.
"""
import numpy as np
from . import base
from Chandra.Time import DateTime
ELEMENTS_CACHE = {}
R_E = 6378.137e3 # Earth Equatorial Radius (m)
M_G = 398600.44e9 # Gravitational parameter (m**3 / s**2)
[docs]def calc_orbital_elements(x, y, z, vx, vy, vz):
"""
Calculate orbital elements given input position (x, y, z) in m and
velocity (vx, vy, vz) in m/s. Orbit perigee and apogee are computed
as well.
Returns a dict with the following key values:
================ ====
Key name Unit
================ ====
semi_major_axis m
eccentricity --
inclination deg
ascending_node deg
argument_perigee deg
mean_anomaly deg
orbit_period sec
apogee_radius m
perigee_radius m
================ ====
"""
from numpy import sin, cos, arccos, sqrt, degrees, pi
def arccos_2pi(arg, reflect):
"""
Return arccos(arg) where reflect is False, 2 * pi - arccos(arg) where reflect is True
"""
np_err_handling = np.geterr()
np.seterr(all='raise')
try:
out = arccos(arg)
except FloatingPointError:
print('Bad argccos arg={}'.format(arg))
raise
np.seterr(**np_err_handling)
try:
# Check if arg is a non-scalar numpy array
len(arg) and isinstance(arg, np.ndarray)
except:
if reflect:
out = 2 * pi - out
else:
out[reflect] = 2 * pi - out[reflect]
return out
# Semi major axis
r = np.sqrt(x ** 2 + y ** 2 + z ** 2)
v2 = vx ** 2 + vy ** 2 + vz ** 2
a = 1 / (2 / r - v2 / M_G)
# a_alt = a - R_E
# Period
T = 2 * pi * sqrt(a ** 3 / M_G)
# n = 1 / T
# Eccentricity
f1 = (1 / r - 1 / a)
f2 = (x * vx + y * vy + z * vz) / M_G
ei = f1 * x - f2 * vx
ej = f1 * y - f2 * vy
ek = f1 * z - f2 * vz
e = sqrt(ei * ei + ej * ej + ek * ek)
# Orbit inclination
hi = y * vz - z * vy
hj = z * vx - x * vz
hk = x * vy - y * vx
h = sqrt(hi ** 2 + hj ** 2 + hk ** 2)
i = arccos(hk / h) # radians
# Ascending node
Wi = -hj
Wj = hi
W = sqrt(Wi ** 2 + Wj ** 2)
aw = arccos_2pi(Wi / W, Wj < 0)
# Argument of perigee
w = arccos_2pi((Wi * ei + Wj * ej) / (W * e), ek < 0)
# Mean Anomaly
n = arccos_2pi((x * ei + y * ej + z * ek) / (e * r), f2 < 0)
# Eccentric Anomaly
E = arccos_2pi((e + cos(n)) / (1 + e * cos(n)), n > pi)
# Mean Anomaly
M = E - e * sin(E)
i = degrees(i)
aw = degrees(aw)
w = degrees(w)
M = degrees(M)
perigee = a * (1 - e)
apogee = a * (1 + e)
return {'semi_major_axis': a,
'orbit_period': T,
'eccentricity': e,
'inclination': i,
'ascending_node': aw,
'argument_perigee': w,
'mean_anomaly': M,
'perigee_radius': perigee,
'apogee_radius': apogee}
[docs]class DerivedParameterOrbit(base.DerivedParameter):
content_root = 'orbit'
rootparams = ['orbitephem0_x', 'orbitephem0_y', 'orbitephem0_z',
'orbitephem0_vx', 'orbitephem0_vy', 'orbitephem0_vz']
time_step = 328.0
max_gap = 1000.0
[docs] def get_orbital_element(self, data):
# Lower case and chop off the initial "DP_"
param = self.__class__.__name__.lower()[3:]
start = DateTime(data.times[0]).date
stop = DateTime(data.times[-1]).date
if (start, stop) in ELEMENTS_CACHE:
return ELEMENTS_CACHE[start, stop][param]
# Get values in km and km/sec
x = data['orbitephem0_x'].vals
y = data['orbitephem0_y'].vals
z = data['orbitephem0_z'].vals
vx = data['orbitephem0_vx'].vals
vy = data['orbitephem0_vy'].vals
vz = data['orbitephem0_vz'].vals
elements = calc_orbital_elements(x, y, z, vx, vy, vz)
ELEMENTS_CACHE[start, stop] = elements
return elements[param]
[docs] def calc(self, data):
return self.get_orbital_element(data)
[docs]class DP_SEMI_MAJOR_AXIS(DerivedParameterOrbit):
"""
Orbital element: semi-major axis (m)
"""
[docs]class DP_ORBIT_PERIOD(DerivedParameterOrbit):
"""
Orbital element: period (sec)
"""
[docs]class DP_ECCENTRICITY(DerivedParameterOrbit):
"""
Orbital element: eccentricity
"""
[docs]class DP_INCLINATION(DerivedParameterOrbit):
"""
Orbital element: inclination (degrees)
"""
[docs]class DP_ASCENDING_NODE(DerivedParameterOrbit):
"""
Orbital element: right ascension of ascending node (degrees)
"""
[docs]class DP_ARGUMENT_PERIGEE(DerivedParameterOrbit):
"""
Orbital element: argument of perigee (degrees)
"""
[docs]class DP_MEAN_ANOMALY(DerivedParameterOrbit):
"""
Orbital element: mean anomaly (degrees)
"""
[docs]class DP_PERIGEE_RADIUS(DerivedParameterOrbit):
"""
Orbit perigee based on orbital elements (m)
Defined as ``semi_major_axis * (1 - eccentricity)``
"""
[docs]class DP_APOGEE_RADIUS(DerivedParameterOrbit):
"""
Orbit apogee based on orbital elements (m)
Defined as ``semi_major_axis * (1 + eccentricity)``
"""