# SGP4, Two-Line Element Sets (TLEs), and Orbital Mean-Element Messages (OMMs)
## Satellite Catalog
The United States maintains a public catalog of Earth-orbiting objects, including active satellites, rocket bodies, and debris.
There are multiple places to access the catalog, including:
- <https://www.celestrak.org/>
- <https://www.space-track.org/>
## SGP4
**SGP4 (Simplified General Perturbations No. 4)** is an analytical orbital propagation model created in the **1960s-1970s by NORAD** to efficiently predict the motion of **Earth-orbiting satellites** from **Two-Line Element (TLE)** data.
It was developed to support U.S. space surveillance as a fast, closed-form alternative to numerical integration, modeling Earth's oblateness (J2-J4), atmospheric drag via the TLE *B\** term, and key secular and periodic perturbations. The model is documented in Spacetrack Report No. 3 ([Hoots & Roehrich 1980](references.md#hoots1980)) and, in its modern reference form with test cases, in [Vallado et al. (2006)](references.md#vallado2006); satkit's implementation is a line-by-line Rust port of the C++ code accompanying the latter and is verified against its test vectors.
satkit implements classic SGP4 only. Element sets flagged as **SGP4-XP** (ephemeris type 4 in TLE line 1, column 63, or `EPHEMERIS_TYPE: 4` in an OMM) parse normally, but `sgp4()` raises an error rather than propagating them: SGP4-XP is a different theory whose line 1 stores agom and a B term where a classic TLE stores nddot and $B^*$, and its reference implementation is distributed by the U.S. Space Force as binaries only. Such sets are rare in public catalogs.
Today, SGP4 is the standard propagator for TLEs published by organizations like NORAD and CelesTrak, and is widely used for satellite tracking, visualization, conjunction screening, and mission planning — though its accuracy is fundamentally limited by TLE quality and simplifying assumptions.
## Ephemeris Representation
### TLE
A **Two-Line Element Set (TLE)** is a compact, legacy format (originally constrained by punch-card era line lengths) for describing satellite orbits. Despite its age, it remains widely used because it is easy to publish and often provides sufficient accuracy for many applications.
In addition to the familiar (mean) Keplerian elements, TLEs include parameters related to perturbations (e.g., atmospheric drag via $B^*$, and derivatives of mean motion).
TLEs are designed to be used with **SGP4**, an analytic model that produces orbital state vectors (position and velocity) from the augmented mean elements in the TLE. The perturbations modeled include Earth oblateness (which produces precession) and drag.
TLEs are often preceded by an additional "line 0" containing the satellite name. The field-by-field format is documented in [Vallado et al. (2006)](references.md#vallado2006), Appendix A, and in CelesTrak's [TLE format description](https://celestrak.org/NORAD/documentation/tle-fmt.php).
### Orbital Mean-Element Messages
**Orbital Mean-Element Messages (OMMs)** are a more modern way of representing mean-element ephemerides. They are described by the CCSDS *Orbit Data Messages* standard ([CCSDS 502.0-B-3](references.md#ccsds502)), although real-world sources may not adhere to the standard perfectly.
OMMs are commonly published as:
- JSON
- XML
- KVN (key-value notation)
satkit reads the JSON and XML forms (KVN is not supported). In Python an OMM is a plain dictionary keyed by the CCSDS field names: `satkit.omm_from_url()`, `satkit.omm_from_file()` and `satkit.omm_from_text()` return a list of them, with every field the source provided (Space-Track's catalog extras such as `OBJECT_TYPE` and `RCS_SIZE` included) and numbers converted from Space-Track's quoted strings. `satkit.sgp4()` accepts these dictionaries directly, as well as the raw output of `json.load` on a CelesTrak or Space-Track response. `satkit.TLE.from_omm()` and `satkit.TLE.to_omm()` convert between the two representations.
A message whose `MEAN_ELEMENT_THEORY` is not SGP4, whose `TIME_SYSTEM` is not UTC, or whose `EPHEMERIS_TYPE` is 4 (SGP4-XP, which Space-Track distributes alongside classic SGP4 sets) is rejected rather than propagated with the wrong theory.
In Rust the same message is the `satkit::omm::OMM` struct, which implements `SGP4Source`, serializes back to JSON with `serde`, and converts with `OMM::from_tle` / `OMM::to_tle`.
## Loading from URLs
Both TLEs and OMMs can be loaded directly from a URL:
```python
import satkit as sk
# Load TLEs from a URL
tles = sk.TLE.from_url("https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle")
# Load OMMs from a URL (auto-detects JSON vs XML)
omms = sk.omm_from_url("https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=json")
# OMMs work directly with sgp4()
pos, vel = sk.sgp4(omms[0], sk.time(2024, 6, 1))
```
## Example Usage
### SGP4 from TLE lines
```python
import satkit as sk
# The two-line element set
# Let's pick a random Starlink satellite
tle_lines = [
'0 STARLINK-30477',
'1 57912U 23146X 24099.49439401 .00006757 00000+0 51475-3 0 9997',
'2 57912 43.0018 157.5807 0001420 272.5369 87.5310 15.02537576 31746'
]
# Create a TLE object
starlink30477 = sk.TLE.from_lines(tle_lines)
# The state is output in the "TEME" frame
pTEME, _vTEME = sk.sgp4(starlink30477, sk.time(2024, 4, 9, 12, 0, 0))
# Rotate to Earth-fixed (ITRF) and get geodetic coordinates. Keyword
# arguments make the direction explicit at first sight; the same call with
# positional args is `rotation(sk.frame.TEME, sk.frame.ITRF, thetime)`.
thetime = sk.time(2024, 4, 9, 12, 0, 0)
pITRF = sk.frametransform.rotation(
from_frame=sk.frame.TEME, to_frame=sk.frame.ITRF, tm=thetime,
) * pTEME
coord = sk.itrfcoord(pITRF)
print(coord)
# ITRFCoord(lat: 29.3890 deg, lon: 170.8051 deg, hae: 560.11 km)
```
### SGP4 from a URL (TLE)
```python
import satkit as sk
# Load all space station TLEs directly from CelesTrak
tles = sk.TLE.from_url("https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle")
iss = tles[0] # ISS is first
pos, vel = sk.sgp4(iss, sk.time(2024, 6, 1))
```
### SGP4 from a URL (OMM)
```python
import satkit as sk
# Load ISS ephemeris as OMM from CelesTrak
omms = sk.omm_from_url("https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=json")
epoch = sk.time(omms[0]['EPOCH'])
time_array = [epoch + sk.duration(minutes=i*10) for i in range(6)]
# SGP4 propagation
pTEME, _vTEME = sk.sgp4(omms[0], time_array)
# Rotate to Earth-fixed and get geodetic coordinates
pITRF = [
sk.frametransform.rotation(sk.frame.TEME, sk.frame.ITRF, t) * p
for t, p in zip(time_array, pTEME)
]
coord = [sk.itrfcoord(x) for x in pITRF]
```
XML format works the same way -- just change the URL:
```python
omms = sk.omm_from_url("https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=xml")
```
### OMM from a local file, and conversion to a TLE
```python
import satkit as sk
# JSON or XML, detected from the content; one dict per message
omms = sk.omm_from_file("gp.json")
# Everything the source provided is in the dict
print(omms[0]["OBJECT_NAME"], omms[0].get("OBJECT_TYPE"), omms[0].get("RCS_SIZE"))
# The same element set as a TLE object, and back again
tle = sk.TLE.from_omm(omms[0])
print("\n".join(tle.to_2line()))
assert sk.TLE.from_omm(tle.to_omm()).to_2line() == tle.to_2line()
```