Orbital Mean-Element Messages¶
Orbital Mean-Element Messages (OMMs) are a standardized data product defined by CCSDS for exchanging satellite orbital elements in a machine-readable way. They contain the same orbital parameters as TLEs but in a more structured format, and are growing in popularity as the modern replacement for TLE distribution.
OMMs are available from CelesTrak and Space-Track in multiple encodings:
- JSON — the most common and straightforward format
- XML — more verbose with deeper hierarchy, but widely supported
- KVN — key-value notation; imposes very little structure
satkit supports SGP4 propagation of OMMs represented as Python dictionaries (from JSON) or parsed XML structures. KVN is not supported.
Example 1: JSON Format¶
Load a JSON OMM for the International Space Station from CelesTrak, propagate with SGP4, and convert to geodetic coordinates. The JSON format maps directly to a Python dictionary, making it simple to work with.
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'
try:
response = requests.get(url, headers={'User-Agent': 'satkit-docs'})
response.raise_for_status()
omm = response.json()
except Exception:
# Fallback OMM if CelesTrak is unavailable
omm = [{"OBJECT_NAME": "ISS (ZARYA)", "OBJECT_ID": "1998-067A", "EPOCH": "2026-02-23T06:28:41.856192",
"MEAN_MOTION": 15.49438695, "ECCENTRICITY": 0.0005991, "INCLINATION": 51.6372,
"RA_OF_ASC_NODE": 290.9753, "ARG_OF_PERICENTER": 34.6694, "MEAN_ANOMALY": 131.1947,
"EPHEMERIS_TYPE": 0, "CLASSIFICATION_TYPE": "U", "NORAD_CAT_ID": 25544,
"ELEMENT_SET_NO": 999, "REV_AT_EPOCH": 47914, "BSTAR": 0.00032055,
"MEAN_MOTION_DOT": 0.00020836, "MEAN_MOTION_DDOT": 0}]
# 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: XML Format¶
Same as above, but loading the OMM in XML format. The XML structure has more levels of nesting — the orbital data is buried under ndm > omm > body > segment > data. The xmltodict library converts this to a nested dictionary that satkit can propagate. The ISS ground track is plotted over approximately one orbit.
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'
try:
response = requests.get(url, headers={'User-Agent': 'satkit-docs'})
response.raise_for_status()
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']
except Exception:
# Fallback OMM dict if CelesTrak is unavailable
omm = {"meanElements": {"EPOCH": "2026-02-23T06:28:41.856192",
"MEAN_MOTION": "15.49438695", "ECCENTRICITY": "0.0005991",
"INCLINATION": "51.6372", "RA_OF_ASC_NODE": "290.9753",
"ARG_OF_PERICENTER": "34.6694", "MEAN_ANOMALY": "131.1947",
"EPHEMERIS_TYPE": "0", "NORAD_CAT_ID": "25544", "ELEMENT_SET_NO": "999",
"REV_AT_EPOCH": "47914", "CLASSIFICATION_TYPE": "U"},
"tleParameters": {"BSTAR": "0.00032055",
"MEAN_MOTION_DOT": "0.00020836", "MEAN_MOTION_DDOT": "0"}}
# 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()