Orbital Mean-Element Messages

Orbital Mean-Element Messages (OMMs) are standardized data products defined by CCSDS to exchange satellite orbital elements in a machine-readable way. They are growing in popularity and are now supported at https://www.celetrak.org and https://www.space-track.org.

The standard is described at: https://ccsds.org/Pubs/502x0b3e1.pdf though neigher of the web pages above appear to adhere to it rigidly.

OMMs can be encoded within:

  • JSON - very common and straightforward

  • XML - A bit more confusing, as there are more levels of heirarchy

  • KVN - Key-value notation, a terrible way for representing the data as it imposes very little structure

satkit supports SGP4 propagation of OMMs represented as python dictionaries or lists of python dictionaries

satkit does not support KVN

Example 1

The following example loads an OMM as json from https://www.celestrak.org , generates state vectors from the output, and converts the state vectors to geodetic positions

[1]:
import satkit as sk
import json
import requests

# Query the current ephemeris for the International Space Station (ISS)
url = 'https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=json'
with requests.get(url) as response:
    omm = response.json()

# Get a representative time from the output
epoch = sk.time(omm[0]['EPOCH'])
# crate a list of times .. once every 10 minutes
time_array = [epoch + sk.duration(minutes=i*10) for i in range(6)]

# TEME (inertial) output from SGP4
pTEME, _vTEME = sk.sgp4(omm[0], time_array)

# Rotate to Earth-fixed
pITRF = [sk.frametransform.qteme2itrf(t) * p for t, p in zip(time_array, pTEME)]

# Geodetic coordinates of space station at given times
coord = [sk.itrfcoord(x) for x in pITRF]

Example 2

Same as example above, except OMM is in XML. Note the additional complexity in the hierarchy

also, plot the ground track

[2]:
import satkit as sk
import xmltodict
import requests
import plotly.graph_objects as go
import numpy as np

# Query the current ephemeris for the International Space Station (ISS)
url = 'https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=xml'
with requests.get(url) as response:
    omm = xmltodict.parse(response.text)

# Navigate to the relevant part of the parsed XML
# Ugghh, what a terrible structure
omm = omm['ndm']['omm']['body']['segment']['data']


# Get a representative time from the output
epoch = sk.time(omm['meanElements']['EPOCH'])
# crate a list of times .. once every 10 minutes
time_array = [epoch + sk.duration(minutes=i) for i in range(97)]

# TEME (inertial) output from SGP4
pTEME, _vTEME = sk.sgp4(omm, time_array)

# Rotate to Earth-fixed
pITRF = [sk.frametransform.qteme2itrf(t) * p for t, p in zip(time_array, pTEME)]

coord = [sk.itrfcoord(x) for x in pITRF]

lat, lon, alt = zip(*[(c.latitude_deg, c.longitude_deg, c.altitude) for c in coord])

fig = go.Figure()
fig.add_trace(go.Scattergeo(lat=lat, lon=lon, mode='lines'))
fig.update_layout(margin={"r":0,"t":40,"l":0,"b":0}, title='ISS Ground Track', geo=dict(showland=True, showcountries=True))
fig.show()
fig = go.Figure()
fig.add_trace(go.Scatter(x=[t.datetime() for t in time_array], y=np.array(alt)/1e3, mode='lines'))
fig.update_layout(yaxis_title='Altitude (km)', xaxis_title='Time', font=dict(size=14), title='ISS Altitude vs Time')
fig.show()