Skip to content

API reference

orbit_formats

orbit-formats: lossless round-trip across orbital state-vector and ephemeris formats.

The public surface lives here. It exposes the canonical metamodel — the typed dataclass family downstream consumers adopt as the single format-agnostic representation — and the read / write / convert / detect_format entry points, with format detection and a registry the format readers and writers plug into as they land.

DEFAULT_UNITS module-attribute

DEFAULT_UNITS = UnitSpec()

The astro-tools canonical default units: kilometres, km/s, degrees, seconds.

Attitude dataclass

Attitude(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    attitude_type: str,
    epochs: NDArray[datetime64],
    records: NDArray[float64],
    frame_a: str | None = None,
    frame_b: str | None = None,
    euler_rot_seq: str | None = None,
)

Bases: Canonical

An attitude history (CCSDS AEM) or single attitude (CCSDS APM).

attitude_type is one of :data:ATTITUDE_TYPES; records is an (N, k) array of the attitude components (k fixed by the type), one row per epoch in epochs (N rows — many for an AEM time series, one for an APM). frame_a and frame_b are the two reference frames the rotation maps between (e.g. EME2000SC_BODY); euler_rot_seq is the rotation sequence for the Euler representation (e.g. "321"), None otherwise. Quaternion components are always stored scalar-last (Q1 Q2 Q3 QC).

columns property

columns: tuple[str, ...]

The component column names for this attitude's representation.

to_dataframe

to_dataframe() -> DataFrame

Project the attitude time series to a DataFrame.

Columns Epoch (datetime64[ns]) + the attitude type's component columns (float64) — Q1, Q2, Q3, QC for QUATERNION, ANGLE_1, ANGLE_2, ANGLE_3 for EULER_ANGLE, SPIN_ALPHA, SPIN_DELTA, SPIN_ANGLE, SPIN_ANGLE_VEL for SPIN — one row per epoch (many for an AEM history, one for an APM). df.attrs carries the shared metadata spine (object_name / central_body / time_scale / units / epoch_scales when known), matching :meth:Ephemeris.to_dataframe, plus the attitude-specific attitude_type and the frame_a / frame_b / euler_rot_seq tags when set. The frame pair lives on these attrs, not the spine's single coordinate_system, so that key is absent. No astropy objects leak — values are plain numpy.

Canonical dataclass

Canonical(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
)

Base for the federated canonical category types.

Carries the :class:Metadata spine and the optional source_native fidelity handle. Equality compares canonical content (metadata + numeric payload) and ignores source_native; instances are intentionally unhashable value objects.

Combined dataclass

Combined(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    messages: tuple[Canonical, ...],
    message_id: str | None = None,
    comments: tuple[str, ...] = (),
)

Bases: Canonical

A combined NDM: an ordered tuple of child canonical messages plus the wrapper header.

messages is the children in document order (within a message type; the XML wrapper groups children by type, so a mixed aggregate is normalised to that grouping on write). message_id and comments are the wrapper-level MESSAGE_ID and comments. Each child carries its own metadata and source_native; the aggregate's metadata spine names only the provenance. Equality is by wrapper header plus the child sequence (each compared by its own canonical content), ignoring source_native like every other category type.

Conjunction dataclass

Conjunction(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    tca: datetime64,
    miss_distance: float,
    objects: tuple[ConjunctionObject, ConjunctionObject],
    relative_speed: float | None = None,
    relative_position: NDArray[float64] | None = None,
    relative_velocity: NDArray[float64] | None = None,
)

Bases: Canonical

A two-object conjunction (CCSDS CDM): TCA, miss distance, relative state, two objects.

tca is the time of closest approach; miss_distance the separation at TCA in metres. relative_speed (m/s), relative_position and relative_velocity (each a (3,) RTN vector, metres and m/s) are present when the CDM carries the relative-state block, else None. objects is the (OBJECT1, OBJECT2) pair. The metadata spine tags the primary object and the originator; its time_scale is UTC (the CDM convention).

ConjunctionObject dataclass

ConjunctionObject(
    label: str,
    object_designator: str,
    ref_frame: str,
    state: NDArray[float64],
    covariance: NDArray[float64],
    object_name: str | None = None,
    catalog_name: str | None = None,
    international_designator: str | None = None,
)

One of the two objects in a conjunction: its identity, state, and RTN covariance.

label is the CDM slot ("OBJECT1" / "OBJECT2"); object_designator the catalogue designator. state is the (6,) Cartesian state at TCA — X, Y, Z (km) and X_DOT, Y_DOT, Z_DOT (km/s) — expressed in ref_frame; covariance is the (6, 6) symmetric position/velocity covariance in the RTN frame (metre-based, axis order R, T, N, Ṙ, Ṫ, Ṅ). The optional identity fields (object_name / catalog_name / international_designator) are present when the CDM states them.

Ephemeris dataclass

Ephemeris(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    epochs: NDArray[datetime64],
    positions: NDArray[float64],
    velocities: NDArray[float64],
    interpolation: str | None = None,
    interpolation_degree: int | None = None,
    maneuvers: tuple[Maneuver, ...] = (),
)

Bases: Canonical

A Cartesian state-vector time series.

epochs is a length-N datetime64[ns] array in the metadata's time scale; positions and velocities are (N, 3) arrays in the length / speed units. interpolation / interpolation_degree carry the source ephemeris's interpolation hint. A multi-segment source (e.g. a multi-segment OEM) is concatenated into one canonical ephemeris here; the per-segment detail is preserved on the source_native fidelity model. maneuvers holds the burns an OCM states (empty for any other source); each carries its own frame and Δv, and rides through the conversion layer but is dropped — with a named warning — by a write to a format that cannot express it.

to_dataframe

to_dataframe() -> DataFrame

Project to the gmat-run-identical state frame.

Columns Epoch (datetime64[ns]) + X, Y, Z, VX, VY, VZ (float64); df.attrs carries object_name / central_body / coordinate_system / time_scale (set when known), units, epoch_scales, and interpolation / interpolation_degree when present. The projection is the canonical edge form: provenance and the source_native handle live on the object, not in the DataFrame. No astropy objects leak — values are plain numpy.

from_dataframe classmethod

from_dataframe(df: DataFrame) -> Ephemeris

Reconstruct an Ephemeris from a canonical state frame.

The inverse of :meth:to_dataframe over the projected fields — the round-trip object -> to_dataframe -> from_dataframe reproduces the projected content without drift. Also the construction path a producer (e.g. the GMAT-report reader) uses to build an Ephemeris from a parsed table.

FidelityModel

Bases: ABC

Base for a one-faithful-model-per-format representation.

A fidelity model holds every field a format defines, so a same-format write can recover full fidelity from it and stay byte-lossless — it never down-projects. Per-format models (the in-house KVN OEM record, the thin sgp4 element set, the GMAT report table) live in their reader modules; this base only fixes the contract that every model declares the format_name the lossy-conversion and writer layers key their same-format-lossless logic off.

KeplerianElements dataclass

KeplerianElements(
    semi_major_axis: float,
    eccentricity: float,
    inclination: float,
    raan: float,
    arg_periapsis: float,
    true_anomaly: float,
)

Classical orbital elements at one epoch.

Angles (inclination / raan / arg_periapsis / true_anomaly) are in the metadata's angle unit (degrees by default); semi_major_axis is in the length unit. Conversion to and from Cartesian (given a gravitational parameter) is the conversion layer's job — this type only holds the Keplerian representation.

Maneuver dataclass

Maneuver(
    epoch_ignition: datetime64,
    ref_frame: str,
    duration: float = 0.0,
    delta_v: NDArray[float64] | None = None,
    delta_mass: float | None = None,
    comments: tuple[str, ...] = (),
)

One maneuver (impulsive or finite) read from an OPM MAN_* block or an OCM man line.

epoch_ignition is the burn's ignition time and ref_frame the frame its delta_v is expressed in (e.g. "RTN" / "EME2000") — the maneuver names its own frame rather than borrowing the parent object's, since a burn is commonly given in RTN while the state is inertial. duration is in seconds (0.0 ⇒ an impulsive maneuver). delta_v is the (3,) Δv vector in km/s when the source provides it, else None (an OCM man block that expresses, say, thrust but no Δv leaves it unset). delta_mass is the mass change in kg (non-positive) when stated. comments are the block's leading COMMENT lines.

MeanElementSet dataclass

MeanElementSet(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    epoch: datetime64,
    mean_motion: float,
    eccentricity: float,
    inclination: float,
    raan: float,
    arg_periapsis: float,
    mean_anomaly: float,
    bstar: float | None = None,
    mean_motion_dot: float | None = None,
    mean_motion_ddot: float | None = None,
    mean_element_theory: str | None = None,
)

Bases: Canonical

A mean-element set (TLE / SGP4 or CCSDS OMM): mean elements at an epoch.

These are mean — not osculating — elements; turning them into an osculating state is a propagation (an sgp4 model step), out of scope for the format layer. mean_motion is in revolutions/day and the angles are in degrees (the TLE/OMM convention). bstar and the mean-motion derivatives are the optional SGP4 drag terms; a pure Keplerian mean set leaves them None. TLE-text specifics (line numbers, checksums, classification) live on the source_native fidelity model, and the NORAD id rides metadata.object_id.

mean_element_theory records which mean-element theory produced the elements — :data:SGP4_MEAN_ELEMENT_THEORY for a TLE / OMM, :data:BROADCAST_MEAN_ELEMENT_THEORY for a RINEX broadcast record, None when unknown. It is a semantic provenance tag, not part of the numeric payload: it is excluded from equality and from the DataFrame projection (like source_native and the off-spine metadata), and it gates conversion to the SGP4 mean-element formats via :func:ensure_convertible_to_mean_format.

to_dataframe

to_dataframe() -> DataFrame

Project to a one-row mean-element frame, with the metadata spine on attrs.

from_dataframe classmethod

from_dataframe(df: DataFrame) -> MeanElementSet

Reconstruct a MeanElementSet from a one-row mean-element frame.

Metadata dataclass

Metadata(
    object_name: str | None = None,
    object_id: str | None = None,
    originator: str | None = None,
    reference_frame: str | None = None,
    central_body: str | None = None,
    time_scale: str | None = None,
    units: UnitSpec = DEFAULT_UNITS,
    provenance: Provenance | None = None,
)

The typed, validated tags every canonical object carries on the object itself.

A shared spine across the federated category types: object identity (object_name / object_id / originator), reference frame, central body, time scale, the units the numeric fields use, and provenance. reference_frame is always tagged and preserved; a rotation to an unsupported frame, or one requested from a canonical form with no Cartesian state, errors rather than guessing.

Provenance dataclass

Provenance(
    source_format: str | None = None,
    creation_date: str | None = None,
    header: str | None = None,
)

Where a canonical object came from — recorded, never reconstructed.

orbit_formats records what a source states; it does not infer the dynamics, perturbations, or maneuvers that produced a trajectory (force-model attribution is explicitly out of scope).

StateVector dataclass

StateVector(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    epoch: datetime64,
    position: NDArray[float64],
    velocity: NDArray[float64],
    keplerian: KeplerianElements | None = None,
    maneuvers: tuple[Maneuver, ...] = (),
)

Bases: Canonical

A single Cartesian state at one epoch, with optional Keplerian elements.

position and velocity are length-3 arrays in the metadata's length / speed units; keplerian is an optional parallel representation populated by the conversion layer. maneuvers holds the burns an OPM states (empty for any other source); each carries its own frame and Δv, and rides through the conversion layer but is dropped — with a named warning — by a write to a format that cannot express it. :meth:to_dataframe projects to the one-row canonical state frame.

to_dataframe

to_dataframe() -> DataFrame

Project to a one-row state frame (Epoch + X, Y, Z, VX, VY, VZ).

Same schema and attrs as :meth:Ephemeris.to_dataframe, with a single row.

from_dataframe classmethod

from_dataframe(df: DataFrame) -> StateVector

Reconstruct a StateVector from a one-row canonical state frame.

Tracking dataclass

Tracking(
    *,
    metadata: Metadata,
    source_native: FidelityModel | None = None,
    participants: tuple[str, ...],
    observations: tuple[TrackingObservation, ...],
)

Bases: Canonical

A tracking-data set (CCSDS TDM): its participants and a flat observation sequence.

participants is the ordered tuple of tracking participants (the TDM PARTICIPANT_1PARTICIPANT_5 — ground stations and the tracked spacecraft); observations is the full sequence of :class:TrackingObservation triples, in file order, concatenated across every segment of a multi-segment message. The metadata spine carries the originator and the time scale; everything format-specific rides on source_native.

TrackingObservation dataclass

TrackingObservation(
    observation_type: str, epoch: datetime64, value: float
)

One tracking observation: its measurement type, epoch, and scalar value.

observation_type is the CCSDS TDM observation keyword (e.g. "RANGE", "DOPPLER_INSTANTANEOUS", "ANGLE_1"); epoch is the measurement time; value is the scalar reading in the units the segment's metadata declares (range units, angle type, and so on live on the source_native model, not on each observation).

ConversionCapability dataclass

ConversionCapability(
    source_format: str,
    target_format: str,
    source_form: str,
    target_form: str,
    kind: ConversionKind,
)

The classification of one (source_format, target_format) conversion.

supported property

supported: bool

Whether the conversion is possible at all (it may still be lossy).

lossless_guaranteed property

lossless_guaranteed: bool

Whether the conversion is lossless for any input (only a same-format write is).

reason property

reason: str

A one-line explanation of the classification, for docs and error context.

ConversionKind

Bases: Enum

How a (source, target) format pair converts — the structural classification.

The first three are supported; the last three are unsupported and name why. See :attr:ConversionCapability.supported.

AmbiguousFormatError

AmbiguousFormatError(candidates: Iterable[str])

Bases: FormatDetectionError

More than one signature matched and no tiebreaker resolved it.

candidates holds the matching format ids so a caller can disambiguate by passing an explicit format=.

FormatDetectionError

Bases: OrbitFormatsError

Auto-detection could not settle on a single format.

FrameRotationUnsupportedError

FrameRotationUnsupportedError(
    source_frame: str, target_frame: str
)

Bases: OrbitFormatsError

A requested frame rotation could not be performed.

This is raised either because one side names a reference frame outside the supported set, or because the canonical form carries no Cartesian state to rotate (mean elements, for instance, would first require a propagation). source_frame and target_frame name the two frames involved.

IncompatibleMeanElementTheoryError

IncompatibleMeanElementTheoryError(
    source_theory: str, target_format: str
)

Bases: UnsupportedConversionError

A mean-element set whose theory the target mean-element format cannot represent.

Source and target share the mean-elements canonical form, so the form-keyed graph would pass the object straight through — but the theory behind the elements differs. A GNSS broadcast-navigation set (quasi-Keplerian parameters referenced to the time of ephemeris in an Earth-fixed datum, evaluated by the constellation's user algorithm) is not an SGP4/TEME mean-element set: writing it as a TLE or OMM would relabel numbers that mean different things. A faithful conversion would have to propagate the broadcast model and refit SGP4 elements — a propagation plus an orbit fit, both out of scope — so the conversion is refused rather than producing a syntactically valid but physically wrong message. source_theory is the source set's theory; target_format the refused target. Subclasses :class:UnsupportedConversionError, so an existing except UnsupportedConversionError still catches it.

MalformedSourceError

Bases: OrbitFormatsError

Recognised as a known format, but its content could not be parsed.

The format id is settled (detected or supplied) — this is not a detection failure — but the bytes are broken for that format: a TLE with an invalid checksum, a record missing a required line, internally inconsistent fields. Distinct from :class:UnsupportedFormatError (the format is fine, the operation is unavailable).

MissingOptionalDependencyError

MissingOptionalDependencyError(
    dependency: str, *, extra: str
)

Bases: OrbitFormatsError

A feature behind an optional extra was used without its dependency installed.

orbit-formats keeps heavy or niche backends behind optional extras so the base install stays lightweight and fully permissive. Reaching for such a feature — SPK read/write, which needs spiceypy from the [spk] extra — without the extra installed raises this, naming the pip install that resolves it. dependency is the missing import name and extra the extra that provides it.

OrbitFormatsError

Bases: Exception

Base class for every error orbit-formats raises deliberately.

UnknownFormatError

Bases: FormatDetectionError

No signature matched, or an explicitly requested format id is not recognised.

Covers both "I read the bytes and nothing matched" and "you passed format='foo' and 'foo' is not a format I know".

UnsupportedConversionError

UnsupportedConversionError(
    source_form: str, target_format: str, target_form: str
)

Bases: OrbitFormatsError

No available conversion from a source object to the requested target format.

source_form is the source object's canonical form, target_format the requested format id, and target_form the canonical form that format expects.

UnsupportedFormatError

Bases: OrbitFormatsError

A recognised format whose requested operation is not available.

Raised when no reader or writer is registered for an otherwise-known format, or when a write targets a read-only format.

Source dataclass

Source(
    data: bytes,
    path: Path | None = None,
    name: str | None = None,
    retain: bool = False,
)

A resolved input: raw bytes, plus the origin path/name when one was given.

data is the (possibly prefix-truncated, for detection) raw content. path is set only when the input was a filesystem path; name is a display name used for extension hints and error messages. Readers consume a Source rather than a raw path so the same reader works for a file and an in-memory buffer.

retain is the caller's opt-in (via :func:~orbit_formats.read's retain_source) to keep the raw bytes on the fidelity model for a verbatim, byte-identical same-format re-emit. It defaults to False so an ordinary read holds no extra copy; a reader that supports verbatim retention (e.g. the OEM reader) consults this flag to decide whether to stash the source bytes.

suffix property

suffix: str | None

The lowercased file extension (with leading dot), or None if unknown.

read_bytes

read_bytes() -> bytes

Return the raw bytes.

read_text

read_text(encoding: str = 'utf-8') -> str

Decode the raw bytes as text (UTF-8 by default).

UnitSpec dataclass

UnitSpec(
    length: str = "km",
    speed: str = "km/s",
    angle: str = "deg",
    time: str = "s",
)

The physical units a canonical object's numeric fields are expressed in.

Plain unit strings — not astropy objects — so the canonical schema stays astropy-free on the way out. length applies to position components, speed to velocity components, angle to orbital angles, and time to the time base of rates and durations.

DroppedField dataclass

DroppedField(name: str, reason: str)

One piece of information a conversion could not carry, and why.

name is the field that was lost or approximated; reason says why the target could not hold it. The pair is the structured unit every lossy warning is built from.

DroppedFieldWarning

DroppedFieldWarning(
    field: str,
    *,
    target_format: str | None = None,
    reason: str | None = None,
)

Bases: LossyConversionWarning

A field the target format structurally cannot represent.

The value is dropped outright, not approximated — the archetype is a covariance matrix written to a format with no covariance block.

LossyConversionWarning

LossyConversionWarning(
    message: str, *, dropped: Sequence[DroppedField]
)

Bases: UserWarning

Base for every warning a lossy conversion emits — catch this for the whole family.

Carries the dropped information as structured :class:DroppedField records on dropped. A lossy warning that names nothing is a contradiction in terms, so construction requires at least one dropped field.

fields property

fields: tuple[str, ...]

The names of the dropped fields, in declaration order.

MissingFieldWarning

MissingFieldWarning(
    fields: Sequence[str],
    *,
    source_format: str | None = None,
)

Bases: LossyConversionWarning

A canonical field the source did not provide, filled with NaN rather than fabricated.

The canonical form has a slot the source format left unpopulated — the archetype is a GMAT report (or an SP3 file) that lists position but no velocity. The slot is filled with NaN, never a guessed value, and this warning names exactly which fields are absent so a caller can decide whether the gap matters. Distinct from :class:DroppedFieldWarning, where the target cannot hold a value the source had; here the source omitted a value the canonical form has room for.

ModelApproximationWarning

ModelApproximationWarning(
    *,
    source_kind: str,
    target_kind: str,
    fields: Sequence[str],
    model: str | None = None,
)

Bases: LossyConversionWarning

A cross-category conversion that introduces a model step.

Projecting mean elements (a TLE / OMM) toward a state crosses the mean-element semantics: the result is model-dependent, not an exact restatement of the source.

PrecisionLossWarning

PrecisionLossWarning(
    field: str,
    *,
    target_format: str | None = None,
    detail: str | None = None,
)

Bases: LossyConversionWarning

A value narrowed to fit a target format's field width or numeric precision.

The value survives but some digits do not — e.g. an epoch written to a TLE's fixed column width.

read

read(
    source: SourceInput,
    *,
    format: str | None = None,
    retain_source: bool = False,
) -> Canonical

Read source into the appropriate canonical subtype.

source is a path or in-memory buffer; an explicit format= overrides detection. Raises :class:~orbit_formats.errors.UnsupportedFormatError if the resolved format has no registered reader, and the detection errors otherwise (see :func:~orbit_formats.detect.detect_format).

Set retain_source=True to keep the raw source bytes on the result's source_native fidelity model, so a later :func:write back to the same format reproduces the input byte-for-byte. It defaults to False — an ordinary read holds no extra copy, and a same-format write is then content-lossless (every field preserved, canonically formatted) rather than byte-identical. Only formats with a verbatim-capable fidelity model (the OEM reader today) honour it.

write

write(
    obj: Canonical,
    destination: str | PathLike[str],
    *,
    format: str | None = None,
) -> None

Write obj to destination in the given (or extension-inferred) format.

The target format comes from an explicit format= or, failing that, the destination's extension. For a format with more than one notation — CCSDS OEM in KVN vs XML — the destination extension also selects the notation (.oem → KVN, .xml → XML); since .xml does not name a single NDM message, writing one needs an explicit format= (e.g. write(eph, "sat.xml", format="ccsds-oem")). Raises :class:~orbit_formats.errors.UnsupportedFormatError for a read-only target or one with no registered writer, and :class:~orbit_formats.errors.UnknownFormatError if no format can be resolved.

capability_matrix

capability_matrix() -> tuple[ConversionCapability, ...]

The capability of every (source, writable target) pair, in catalog order.

Rows range over every known format (anything readable can be a source); columns over the writable formats (only those can be a conversion destination). This is the authoritative table docs/conversion-matrix.md documents and the doc-parity test asserts against.

conversion_capability

conversion_capability(
    source_format: str, target_format: str
) -> ConversionCapability

Classify converting source_format to target_format (both format ids).

Returns the structural :class:ConversionKind and a :attr:~ConversionCapability.reason, derived from the canonical form each format prefers, the registered cross-form edges, and the mean-element theory rule — the same facts :func:orbit_formats.convert.graph.route and :func:orbit_formats.api.convert act on. Raises :class:~orbit_formats.errors.UnknownFormatError for an unknown format id.

The result describes the routing a :func:~orbit_formats.api.convert call would take; it does not consider whether the target is writable (that is a :func:~orbit_formats.api.write concern), so a read-only target still classifies by form. :func:capability_matrix lists only writable targets, the ones that are conversion destinations.

detect_format

detect_format(source: SourceInput) -> str

Return the format id of source (a path or in-memory buffer).

Reads a header prefix and resolves the format from its content signature, falling back to the file extension. Raises :class:~orbit_formats.errors.UnknownFormatError if nothing matches and :class:~orbit_formats.errors.AmbiguousFormatError if several signatures tie. To force a known format instead of detecting, pass format= to :func:~orbit_formats.read (etc.), which validate it via :func:~orbit_formats.formats.normalize_format.

normalize_format

normalize_format(format: str) -> str

Validate and canonicalise an explicit format id (lowercased, trimmed).

This is the counterpart to detection: where :func:orbit_formats.detect.detect_format works out what a source is, this validates a format id a caller already supplied. A notation synonym ("3le" for "tle") resolves to its catalog id. Raises :class:~orbit_formats.errors.UnknownFormatError if it is not a known format.

register_reader

register_reader(format_id: str, reader: Reader) -> None

Register reader as the reader for format_id (must be a catalogued format).

register_writer

register_writer(format_id: str, writer: Writer) -> None

Register writer for format_id (must be a catalogued, writable format).

warn_lossy

warn_lossy(
    warning: LossyConversionWarning, *, stacklevel: int = 1
) -> None

Emit warning through the standard warnings machinery — the one sanctioned seam.

Every converter and writer that drops information routes through here, so the loss is always catchable as a :class:LossyConversionWarning and the no-silent-loss contract has a single point to assert against. stacklevel counts frames above the caller of :func:warn_lossy, so the warning is attributed to the conversion, not to this helper.