OMM Object#
The OMM (i.e. Orbit Mean-Elements Message, as defined by the CCSDS Recommended Standard 502.0-B-3) is the format Space-Track distributes together with the TLEs. It carries the very same SGP4 mean elements, but without the constraints of the two fixed-width lines: this makes it possible to represent objects whose catalog number does not fit the TLE format, and to store the elements at their full precision.
In this notebook we discuss the usage of the OMM object: this allows the user to create an OMM object from a dictionary, a string or a file, in any of the four serializations of the standard (JSON, XML, KVN and CSV). Since an OMM object is a dsgp4.tle.TLE object under the hood, it can be used everywhere a TLE is expected (for a tutorial on the propagation see: TLE propagation).
Imports#
import dsgp4
import torch
Load OMM from dict#
Here, we show how to load an OMM from a dictionary of OMM fields: this is, for instance, what the Space-Track API returns when the JSON format is requested:
omm_fields = {
"CCSDS_OMM_VERS": "3.0",
"CREATION_DATE": "2022-02-28T09:16:12",
"ORIGINATOR": "18 SPCS",
"OBJECT_NAME": "SENTINEL-1A",
"OBJECT_ID": "2014-016A",
"CENTER_NAME": "EARTH",
"REF_FRAME": "TEME",
"TIME_SYSTEM": "UTC",
"MEAN_ELEMENT_THEORY": "SGP4",
"EPOCH": "2022-02-28T01:57:54.918432",
"MEAN_MOTION": "14.59199732",
"ECCENTRICITY": "0.0001341",
"INCLINATION": "98.1819",
"RA_OF_ASC_NODE": "68.1874",
"ARG_OF_PERICENTER": "82.4703",
"MEAN_ANOMALY": "277.6657",
"EPHEMERIS_TYPE": "0",
"CLASSIFICATION_TYPE": "U",
"NORAD_CAT_ID": "39634",
"ELEMENT_SET_NO": "999",
"REV_AT_EPOCH": "42107",
"BSTAR": "0.000021846",
"MEAN_MOTION_DOT": "0.00000057",
"MEAN_MOTION_DDOT": "0",
}
#let us construct the OMM object
omm = dsgp4.omm.OMM(omm_fields)
print(omm)
OMM(
CCSDS_OMM_VERS = 3.0
CREATION_DATE = 2022-02-28T09:16:12
ORIGINATOR = 18 SPCS
OBJECT_NAME = SENTINEL-1A
OBJECT_ID = 2014-016A
CENTER_NAME = EARTH
REF_FRAME = TEME
TIME_SYSTEM = UTC
MEAN_ELEMENT_THEORY = SGP4
EPOCH = 2022-02-28T01:57:54.918432
MEAN_MOTION = 14.59199732
ECCENTRICITY = 0.0001341
INCLINATION = 98.1819
RA_OF_ASC_NODE = 68.1874
ARG_OF_PERICENTER = 82.4703
MEAN_ANOMALY = 277.6657
EPHEMERIS_TYPE = 0
CLASSIFICATION_TYPE = U
NORAD_CAT_ID = 39634
ELEMENT_SET_NO = 999
REV_AT_EPOCH = 42107
BSTAR = 0.000021846
MEAN_MOTION_DOT = 0.00000057
MEAN_MOTION_DDOT = 0
)
All right, we can now access the elements as if it was a dictionary, or as attributes of the class, exactly as with a TLE object (note that the elements are stored in SI units, while the OMM fields are in degrees and rev/day):
print("OMM elements:")
print(f"Object name: {omm.name}")
print(f"Satellite catalog number: {omm.satellite_catalog_number}")
print(f"International designator: {omm.international_designator}")
print(f"Epoch year: {omm.epoch_year}")
print(f"Epoch day: {omm.epoch_days}")
print(f"Epoch (MJD): {omm.date_mjd}")
print(f"Inclination [rad]: {omm._inclo}")
print(f"Right ascension of the ascending node [rad]: {omm._nodeo}")
print(f"Eccentricity [-]: {omm._ecco}")
print(f"Argument of perigee [rad]: {omm._argpo}")
print(f"Mean anomaly [rad]: {omm._mo}")
print(f"Mean motion [rad/min]: {omm._no_kozai}")
print(f"BSTAR drag term: {omm._bstar}")
#the raw OMM fields, as they were read, remain available:
print(f"\nOriginator: {omm._fields['ORIGINATOR']}, mean motion [rev/day]: {omm._fields['MEAN_MOTION']}")
OMM elements:
Object name: SENTINEL-1A
Satellite catalog number: 39634
International designator: 14016A
Epoch year: 2022
Epoch day: 59.081885629999995
Epoch (MJD): 59638.0818856298
Inclination [rad]: 1.7135974208638207
Right ascension of the ascending node [rad]: 1.1900946383743813
Eccentricity [-]: 0.0001341
Argument of perigee [rad]: 1.4393782701074795
Mean anomaly [rad]: 4.8461806848548195
Mean motion [rad/min]: 0.06366959941904715
BSTAR drag term: 2.1846e-05
Originator: 18 SPCS, mean motion [rev/day]: 14.59199732
The handy methods of the TLE object are available here too:
#let's first define the Earth radius according to WSG-84:
r_earth=dsgp4.util.get_gravity_constants('wgs-84')[2].numpy()*1e3
print(f"Semi-major axis [km]: {omm.semi_major_axis*1e-3}")
print(f"Apogee altitude [km]: {omm.apogee_alt(r_earth)*1e-3}")
print(f"Perigee altitude [km]: {omm.perigee_alt(r_earth)*1e-3}")
Semi-major axis [km]: 7073.8965064402
Apogee altitude [km]: 696.7081159617128
Perigee altitude [km]: 694.8108969186861
Load OMM from str#
The standard defines four serializations: JSON, XML, KVN (i.e. keyword-value notation) and CSV. dsgp4 reads and writes all of them, and detects the format automatically:
kvn = dsgp4.omm.dumps(omm, file_format='kvn')
print(kvn)
#the format does not have to be specified, it is detected from the content:
print(f"Detected format: {dsgp4.omm.detect_format(kvn)}")
omm_from_kvn = dsgp4.omm.OMM(kvn)
print(f"Same mean motion: {omm_from_kvn._no_kozai == omm._no_kozai}")
CCSDS_OMM_VERS = 3.0
CREATION_DATE = 2022-02-28T09:16:12
ORIGINATOR = 18 SPCS
OBJECT_NAME = SENTINEL-1A
OBJECT_ID = 2014-016A
CENTER_NAME = EARTH
REF_FRAME = TEME
TIME_SYSTEM = UTC
MEAN_ELEMENT_THEORY = SGP4
EPOCH = 2022-02-28T01:57:54.918432
MEAN_MOTION = 14.59199732
ECCENTRICITY = 0.0001341
INCLINATION = 98.1819
RA_OF_ASC_NODE = 68.1874
ARG_OF_PERICENTER = 82.4703
MEAN_ANOMALY = 277.6657
EPHEMERIS_TYPE = 0
CLASSIFICATION_TYPE = U
NORAD_CAT_ID = 39634
ELEMENT_SET_NO = 999
REV_AT_EPOCH = 42107
BSTAR = 0.000021846
MEAN_MOTION_DOT = 0.00000057
MEAN_MOTION_DDOT = 0
Detected format: kvn
Same mean motion: True
#the same holds for the other three serializations:
for file_format in ['json', 'xml', 'csv']:
text = dsgp4.omm.dumps(omm, file_format=file_format)
other = dsgp4.omm.OMM(text)
print(f"{file_format}: {len(text)} characters, satellite catalog number {other.satellite_catalog_number}")
json: 715 characters, satellite catalog number 39634
xml: 1052 characters, satellite catalog number 39634
csv: 521 characters, satellite catalog number 39634
Load OMMs from file#
Here, we load the OMMs directly from file. We assume the user has downloaded the OMM data, for instance from Space-Track, and has placed such data in the example_omm.json file.
Then, we can simply load all OMMs using dsgp4. This will construct a list of OMM objects:
Note
The format is detected from the file extension and content, but it can also be passed explicitly via the file_format argument.
omms = dsgp4.omm.load('example_omm.json')
print(f"Loaded {len(omms)} OMMs: {[o.name for o in omms]}")
print(f"Catalog numbers: {[o.satellite_catalog_number for o in omms]}")
Loaded 3 OMMs: ['COSMOS 2251 DEB', 'COSMOS 2251 DEB', 'COSMOS 2251 DEB']
Catalog numbers: [34454, 34456, 34457]
From OMM to TLE, and back#
Since the two formats carry the same elements, an OMM object can be converted into a TLE object and vice versa:
tle = omm.to_tle()
print(tle)
#and the other way around:
print(tle.to_omm()._fields['EPOCH'])
TLE(
1 39634U 14016A 22059.08188563 .00000057 00000-0 21846-4 0 9991
2 39634 98.1819 68.1874 0001341 82.4703 277.6657 14.59199732421074
)
2022-02-28T01:57:54.918432
There is one thing the OMM format can do that the TLE format cannot: the two lines only have five characters for the satellite catalog number, so, even with the Alpha-5 convention, they cannot represent objects numbered above 339999. Such objects are only distributed as OMMs:
big_catalog_number = dsgp4.omm.OMM(dict(omm_fields, NORAD_CAT_ID='700123'))
print(f"Satellite catalog number: {big_catalog_number.satellite_catalog_number}")
try:
big_catalog_number.to_tle()
except ValueError as e:
print(f"As expected, this object cannot be written as a TLE:\n{e}")
Satellite catalog number: 700123
As expected, this object cannot be written as a TLE:
Satellite catalog number 700123 cannot be represented in the TLE format: the Alpha-5 convention only covers numbers up to 339999. Use the OMM format (XML/JSON/KVN/CSV) for this object.
Propagation#
Finally, OMM objects are propagated exactly like TLE objects, both one at a time and in batches:
#we initialize the propagator, and propagate at 0, 10 and 100 minutes since the OMM epoch:
dsgp4.initialize_tle(omm)
states = dsgp4.propagate(omm, torch.tensor([0., 10., 100.]))
print(f"Position [km]:\n{states[:, 0, :]}")
print(f"Velocity [km/s]:\n{states[:, 1, :]}")
#and, in batch, over the OMMs we loaded from file:
batch_states = dsgp4.propagate_batch(omms, torch.tensor([0., 10., 100.]), initialized=False)
print(f"\nBatch of states: {batch_states.shape}")
Position [km]:
tensor([[2.6295e+03, 6.5701e+03, 1.3713e-01],
[2.6675e+03, 5.0552e+03, 4.1623e+03],
[2.6879e+03, 6.5217e+03, 5.6004e+02]])
Velocity [km/s]:
tensor([[ 0.9879, -0.4050, 7.4305],
[-0.8656, -4.4729, 5.9700],
[ 0.7627, -0.9605, 7.4066]])
Batch of states: torch.Size([3, 2, 3])
Since the OMM object is a TLE object, everything else in dsgp4 (e.g. the partial derivatives, the covariance transformations, or the ML-dSGP4 model) works with OMMs without any change.