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. KVN is not supported. The helper sk.omm_from_url(url) fetches an OMM endpoint, auto-detects JSON vs XML, and returns a list of dictionaries that can be passed directly to sk.sgp4 — no external HTTP or XML libraries needed.
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
# Fetch the current ephemeris for the International Space Station (ISS).
# sk.omm_from_url auto-detects JSON vs XML response format and returns a list
# of OMM dictionaries that can be passed directly to sk.sgp4.
url = "https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=json"
omm = sk.omm_from_url(url)
# Get a representative time from the OMM epoch
epoch = sk.time(omm[0]["EPOCH"])
# Create 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¶
sk.omm_from_url automatically detects the response format. Passing an XML endpoint returns the same list-of-dicts shape as the JSON version, so the downstream SGP4 call is identical — no xmltodict plumbing or manual tree traversal required. Here we fetch the same ISS OMM in XML, propagate it over roughly one orbit, and plot the ground track.
import satkit as sk
import numpy as np
import matplotlib.pyplot as plt
import scienceplots # noqa: F401
plt.style.use(["science", "no-latex", "../satkit.mplstyle"])
%config InlineBackend.figure_formats = ['svg']
import warnings
warnings.filterwarnings("ignore", "Downloading")
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Fetch the current ISS ephemeris in XML format. sk.omm_from_url auto-detects
# the format and normalizes the result to the same list-of-dicts shape as the
# JSON example above — no xmltodict or hand-written tree walking needed.
url = "https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=xml"
omm = sk.omm_from_url(url)[0]
# Get a representative time from the OMM epoch
epoch = sk.time(omm["EPOCH"])
# Time array spanning roughly one orbit at 1-minute cadence
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])
# Break ground track at longitude discontinuities (date line crossings)
lon_arr, lat_arr = np.array(lon), np.array(lat)
breaks = np.where(np.abs(np.diff(lon_arr)) > 180)[0] + 1
lon_segs = np.split(lon_arr, breaks)
lat_segs = np.split(lat_arr, breaks)
fig, ax = plt.subplots(figsize=(10, 5), subplot_kw={"projection": ccrs.PlateCarree()})
ax.add_feature(cfeature.LAND, facecolor="lightgray")
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.add_feature(cfeature.COASTLINE, linewidth=0.5)
for lo, la in zip(lon_segs, lat_segs):
ax.plot(lo, la, linewidth=1.5, color="C0", transform=ccrs.PlateCarree())
ax.set_title("ISS Ground Track")
ax.set_global()
plt.tight_layout()
plt.show()
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot([t.datetime() for t in time_array], np.array(alt) / 1e3)
ax.set_xlabel("Time")
ax.set_ylabel("Altitude (km)")
ax.set_title("ISS Altitude vs Time")
fig.autofmt_xdate()
plt.tight_layout()
plt.show()