Parse and encode CCSDS Orbit Mean-Elements Messages (OMM).
OMM is the modern standard format for orbital data, carrying the same elements as TLE plus metadata such as originator, reference frame, time system, and mean-element theory. CelesTrak and Space-Track distribute OMM messages as KVN, XML, and JSON.
parse_kvn/1, parse_xml/1, parse_json/1, and string parse/1 return a
typed %Sidereon.Format.OMM{} that keeps every item of CCSDS 502.0-B-3
tables 4-1 to 4-3 the message states: the header, metadata, mean elements,
spacecraft parameters, TLE-related parameters, covariance, USER_DEFINED_*
parameters and the comments of each block. A keyword the message does not
state is nil; no reader fills in a default. parse_xml_all/1 and
parse_json_array/1 read documents holding several OMMs. The legacy
decoded-map parse/1 clause is kept for CelesTrak JSON maps and returns
%Sidereon.Elements{}.
Each writer returns {:error, reason} for a message it cannot write so that
its reader returns it unchanged, such as text with a line break in KVN, a
character XML 1.0 cannot carry, a non-finite number, or, in GP JSON, a
comment other than the single header comment GP JSON carries.
Summary
Types
A reader, writer or element-set refusal from the core
(Sidereon.CCSDS.Error.omm/0), or {:invalid_field, field, value} for a
struct field that does not hold a value of its type.
A refusal of the decoded-map parse/1: a missing or unreadable key, named
as the key it reads.
A record parse_xml_all/1 or parse_json_array/1 could not read: its
zero-based position in the document and the reason.
A CCSDS OMM.
Functions
Encode an OMM value.
Encode a typed OMM struct as CCSDS/CelesTrak OMM JSON text.
Encode a typed OMM struct as GP JSON, leaving out every comment GP JSON
cannot carry: all but the single header comment. A spacecraft-parameters
block that held only comments is still written, as "MASS": null.
Encode a typed OMM struct as CCSDS OMM KVN text.
Encode a typed OMM struct as CCSDS OMM XML text.
Parse an OMM from text or from a decoded JSON map.
Parse CCSDS/CelesTrak OMM JSON text holding one record into a typed OMM struct.
Parse a CelesTrak/Space-Track GP JSON array of OMM records.
Parse CCSDS OMM KVN text into a typed OMM struct.
Parse CCSDS OMM XML text into a typed OMM struct.
Parse every OMM of a CCSDS OMM XML document: a single message or an NDM combined instantiation (CCSDS 505.0-B-3 4.11).
Convert a typed OMM struct to %Sidereon.Elements{} for SGP4 propagation.
Alias for encode_json/1, matching the core and Python binding terminology.
Alias for encode_kvn/1, matching the core and Python binding terminology.
Alias for encode_xml/1, matching the core and Python binding terminology.
Types
@type encode_error() :: error()
@type error() :: Sidereon.CCSDS.Error.omm() | {:invalid_field, atom(), term()}
A reader, writer or element-set refusal from the core
(Sidereon.CCSDS.Error.omm/0), or {:invalid_field, field, value} for a
struct field that does not hold a value of its type.
A refusal of the decoded-map parse/1: a missing or unreadable key, named
as the key it reads.
@type skipped_record() :: {non_neg_integer(), Sidereon.CCSDS.Error.omm()}
A record parse_xml_all/1 or parse_json_array/1 could not read: its
zero-based position in the document and the reason.
@type t() :: %Sidereon.Format.OMM{ agom_m2_kg: float() | nil, arg_of_pericenter_deg: float(), bstar: float() | nil, bterm_m2_kg: float() | nil, ccsds_omm_vers: String.t() | nil, center_name: String.t() | nil, classification: String.t() | nil, classification_type: String.t() | nil, comments: Sidereon.Format.OMM.Comments.t(), covariance: Sidereon.Format.OMM.Covariance.t() | nil, creation_date: String.t() | nil, eccentricity: float(), element_set_no: integer() | nil, ephemeris_type: integer() | nil, epoch: Sidereon.Format.OMM.Epoch.t(), exact_sgp4_epoch: {float(), float()} | nil, gm_km3_s2: float() | nil, inclination_deg: float(), mean_anomaly_deg: float(), mean_element_theory: String.t() | nil, mean_motion: float() | nil, mean_motion_ddot: float() | nil, mean_motion_dot: float() | nil, message_id: String.t() | nil, norad_cat_id: non_neg_integer() | nil, object_id: String.t() | nil, object_name: String.t() | nil, originator: String.t() | nil, quantize_tle_derived_fields: boolean(), ra_of_asc_node_deg: float(), ref_frame: String.t() | nil, ref_frame_epoch: String.t() | nil, rev_at_epoch: integer() | nil, semi_major_axis_km: float() | nil, spacecraft: Sidereon.Format.OMM.Spacecraft.t() | nil, time_system: String.t() | nil, user_defined: [Sidereon.Format.OMM.UserDefined.t()] }
A CCSDS OMM.
ccsds_omm_vers is the version the message states, or nil, as CelesTrak
GP JSON and CSV state none; each writer states it only when present. At
least one of mean_motion (rev/day) and semi_major_axis_km is present in
a message read from text. The TLE-related parameters are nil when the
message does not state them, since table 4-3 requires them only for
SGP/SGP4 element sets. bterm_m2_kg and agom_m2_kg are the SGP4-XP BTERM
and AGOM (m²/kg); gm_km3_s2 is GM (km³/s²).
exact_sgp4_epoch and quantize_tle_derived_fields retain the core OMM's
in-memory SGP4 conversion policy across the NIF boundary. They are not CCSDS
wire fields.
Functions
@spec encode( t(), keyword() ) :: {:ok, String.t()} | {:error, encode_error()}
Encode an OMM value.
A typed %Sidereon.Format.OMM{} is serialized as text. The :format option
may be :kvn, :xml, or :json and defaults to :kvn.
The legacy %Sidereon.Elements{} clause returns a JSON-compatible map with
standard OMM field names.
@spec encode_json(t()) :: {:ok, String.t()} | {:error, encode_error()}
Encode a typed OMM struct as CCSDS/CelesTrak OMM JSON text.
Returns {:ok, text} or {:error, reason}.
@spec encode_json_discarding_comments(t()) :: {:ok, String.t()} | {:error, encode_error()}
Encode a typed OMM struct as GP JSON, leaving out every comment GP JSON
cannot carry: all but the single header comment. A spacecraft-parameters
block that held only comments is still written, as "MASS": null.
encode_json/1 refuses such a record instead; use this function only when
that loss is acceptable. Returns {:ok, text} or {:error, reason}.
@spec encode_kvn(t()) :: {:ok, String.t()} | {:error, encode_error()}
Encode a typed OMM struct as CCSDS OMM KVN text.
Returns {:ok, text} or {:error, reason}.
@spec encode_xml(t()) :: {:ok, String.t()} | {:error, encode_error()}
Encode a typed OMM struct as CCSDS OMM XML text.
Returns {:ok, text} or {:error, reason}.
@spec parse(String.t()) :: {:ok, t()} | {:error, Sidereon.CCSDS.Error.omm()}
@spec parse(map()) :: {:ok, Sidereon.Elements.t()} | {:error, parse_error()}
Parse an OMM from text or from a decoded JSON map.
For binary input, the text format is auto-detected: a leading < selects XML,
a leading { or [ selects JSON, and all other input is parsed as KVN. Text
parsing returns {:ok, %Sidereon.Format.OMM{}} or {:error, reason}.
For map input, accepts decoded CelesTrak/Space-Track OMM JSON maps with field
names such as "NORAD_CAT_ID", "INCLINATION", and "MEAN_MOTION". This
legacy path returns {:ok, %Sidereon.Elements{}} and handles both numeric and
string values for numeric fields. "MEAN_MOTION" and "BSTAR", which SGP4
propagates with, are required; an unstated mean-motion derivative,
"CLASSIFICATION_TYPE", "EPHEMERIS_TYPE", "ELEMENT_SET_NO" or
"REV_AT_EPOCH" is nil.
Examples
iex> {:ok, el} = Sidereon.Format.OMM.parse(%{
...> "NORAD_CAT_ID" => 25544,
...> "OBJECT_NAME" => "ISS (ZARYA)",
...> "EPOCH" => "2024-01-01T00:00:00",
...> "INCLINATION" => 51.6,
...> "RA_OF_ASC_NODE" => 300.0,
...> "ECCENTRICITY" => 0.0007,
...> "ARG_OF_PERICENTER" => 90.0,
...> "MEAN_ANOMALY" => 270.0,
...> "MEAN_MOTION" => 15.5,
...> "BSTAR" => 0.0001
...> })
iex> el.catalog_number
"25544"
iex> el.object_name
"ISS (ZARYA)"
@spec parse_json(String.t()) :: {:ok, t()} | {:error, Sidereon.CCSDS.Error.omm()}
Parse CCSDS/CelesTrak OMM JSON text holding one record into a typed OMM struct.
JSON input may be a single OMM object or an array holding one object. A
document holding several records is refused; parse_json_array/1 reads
them.
Returns {:ok, %Sidereon.Format.OMM{}} or {:error, reason}.
@spec parse_json_array(String.t()) :: {:ok, [t()], [skipped_record()]} | {:error, Sidereon.CCSDS.Error.omm()}
Parse a CelesTrak/Space-Track GP JSON array of OMM records.
Returns {:ok, omms, skipped}, where skipped lists each array element that
could not be read as {index, reason}, or {:error, reason}.
@spec parse_kvn(String.t()) :: {:ok, t()} | {:error, Sidereon.CCSDS.Error.omm()}
Parse CCSDS OMM KVN text into a typed OMM struct.
Returns {:ok, %Sidereon.Format.OMM{}} or {:error, reason}.
@spec parse_xml(String.t()) :: {:ok, t()} | {:error, Sidereon.CCSDS.Error.omm()}
Parse CCSDS OMM XML text into a typed OMM struct.
Returns {:ok, %Sidereon.Format.OMM{}} or {:error, reason}.
@spec parse_xml_all(String.t()) :: {:ok, [t()], [skipped_record()]} | {:error, Sidereon.CCSDS.Error.omm()}
Parse every OMM of a CCSDS OMM XML document: a single message or an NDM combined instantiation (CCSDS 505.0-B-3 4.11).
Returns {:ok, omms, skipped}, where skipped lists each message that could
not be read as {index, reason}, or {:error, reason} for a document that
cannot be read at all.
@spec to_elements(t()) :: {:ok, Sidereon.Elements.t()} | {:error, encode_error()}
Convert a typed OMM struct to %Sidereon.Elements{} for SGP4 propagation.
The elements are the SGP4 element set the core forms from the OMM
(Omm::to_element_set); OMM-specific metadata remains available on the
original OMM struct. The core refuses:
- a stated
MEAN_ELEMENT_THEORYother thanSGP4,SGP/SGP4orSDP4,CENTER_NAMEother thanEARTH,REF_FRAMEother thanTEMEorTIME_SYSTEMother thanUTC(compared ignoring surrounding whitespace and letter case), in that order, with{:incompatible_metadata, field, value}, since the elements would then not be the Earth-centred TEME UTC SGP4 elements (CCSDS 502.0-B-3 4.2.4.6); an absent or blank value is not refused; - an OMM without
MEAN_MOTIONorBSTAR, which SGP4 propagates with, with{:missing_field, :mean_motion}or{:missing_field, :bstar}; - an epoch that names no UTC instant, or an element that is not finite or
out of range, with
{:invalid_field, field, kind}.
An OMM without NORAD_CAT_ID gives elements whose catalog_number is
nil. epoch_jd is the epoch as the core's split Julian date, with the
epoch's femtoseconds and a UTC leap second (23:59:60) kept, and
propagation uses it; epoch is that instant as a DateTime to the
microsecond, so a leap-second epoch reads as the start of the next day, the
same Julian date. An epoch of whole microseconds, which python-sgp4 reads,
gives elements with omm_epoch_days set, which SGP4 initialises as
python-sgp4 initialises the OMM, and bstar and mean_motion_double_dot
as stated; any other epoch bridges the OMM as a TLE, and bstar and
mean_motion_double_dot are quantized to the values the TLE fields hold.
A value no TLE field holds passes through unquantized. Unstated mean-motion
derivatives and bookkeeping fields stay nil.
Returns {:ok, elements} or {:error, reason}.
@spec to_json_string(t()) :: {:ok, String.t()} | {:error, encode_error()}
Alias for encode_json/1, matching the core and Python binding terminology.
@spec to_kvn_string(t()) :: {:ok, String.t()} | {:error, encode_error()}
Alias for encode_kvn/1, matching the core and Python binding terminology.
@spec to_xml_string(t()) :: {:ok, String.t()} | {:error, encode_error()}
Alias for encode_xml/1, matching the core and Python binding terminology.