# `Sidereon.Format.TLE`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/format/tle.ex#L1)

Parse and encode Two-Line Element sets.

TLE is the legacy fixed-width format for satellite orbital elements,
designed for 80-column punch cards in the 1960s. Despite its age,
it remains the most widely used format for distributing orbital data.

The format grammar lives in the Rust core (`sidereon_core::astro::tle`): fixed-width
field extraction and validation, the modulo-10 checksum, the "assumed decimal"
drag-term codec, per-field number formatting, and the two-digit-year pivot.
This module keeps the Sidereon API shape: it marshals the epoch between its native
`DateTime` and the `(epoch_year, epoch_day_of_year)` pair the core exposes,
applies input defaults, logs advisory checksum warnings, and maps errors.

## Parsing

The parser is liberal in what it accepts:
- Trailing whitespace and extra characters are trimmed
- Leading dots in floats (`.123` → `0.123`)
- Spaces in numeric fields

Checksum validation is performed and reported but does not prevent parsing.

## Examples

    {:ok, elements} = Sidereon.Format.TLE.parse(line1, line2)
    {:ok, {line1, line2}} = Sidereon.Format.TLE.encode(elements)

# `encode_error`

```elixir
@type encode_error() ::
  {:missing_field, atom()}
  | {:invalid_field, atom(), term()}
  | {:encode_error, String.t()}
```

# `encode`

```elixir
@spec encode(Sidereon.Elements.t()) ::
  {:ok, {String.t(), String.t()}} | {:error, encode_error()}
```

Encode an `%Sidereon.Elements{}` struct as TLE-format strings.

Returns `{:ok, {line1, line2}}`: two 69-character strings with valid
checksums, or `{:error, reason}` for malformed elements. Round-trips are
character-exact for standard TLEs.

## Examples

    iex> l1 = "1 25544U 98067A   18184.80969102  .00001614  00000-0  31745-4 0  9993"
    iex> l2 = "2 25544  51.6414 295.8524 0003435 262.6267 204.2868 15.54005638121106"
    iex> {:ok, el} = Sidereon.Format.TLE.parse(l1, l2)
    iex> {:ok, {gen_l1, gen_l2}} = Sidereon.Format.TLE.encode(el)
    iex> gen_l1 == l1
    true
    iex> gen_l2 == l2
    true

# `encode!`

```elixir
@spec encode!(Sidereon.Elements.t()) :: {String.t(), String.t()}
```

Like `encode/1` but raises on malformed elements.

# `parse`

```elixir
@spec parse(String.t(), String.t()) ::
  {:ok, Sidereon.Elements.t()} | {:error, String.t()}
```

Parse a two-line element set into an `%Sidereon.Elements{}` struct.

Returns `{:ok, elements}` or `{:error, reason}`.
Logs a warning if checksums are invalid but still parses.

## Examples

    iex> {:ok, el} = Sidereon.Format.TLE.parse(
    ...>   "1 25544U 98067A   18184.80969102  .00001614  00000-0  31745-4 0  9993",
    ...>   "2 25544  51.6414 295.8524 0003435 262.6267 204.2868 15.54005638121106"
    ...> )
    iex> el.catalog_number
    "25544"
    iex> el.inclination_deg
    51.6414

# `parse_file`

```elixir
@spec parse_file(String.t()) ::
  {:ok,
   %{
     satellites: [%{name: String.t(), tle: Sidereon.Elements.t()}],
     skipped: non_neg_integer()
   }}
```

Parse a multi-record TLE file (CelesTrak / Space-Track style).

Accepts the common variants in a single pass: bare two-line element sets,
three-line sets (a name line followed by lines 1 and 2), and CelesTrak
`0 NAME` name lines. Blank lines, CRLF endings, and surrounding whitespace are
tolerated.

Returns `{:ok, %{satellites: [%{name: name, tle: %Sidereon.Elements{}}], skipped: n}}`.
Each `tle` is a fully populated `%Sidereon.Elements{}` (with `object_name` set
to the record's name, or `nil` for a bare two-line set) ready for
`Sidereon.propagate/2`, `Sidereon.look_angle/3`, and friends. `name` is the
empty string for a bare two-line record. `skipped` counts complete records
that were found but failed SGP4 initialization, so an empty file
(`satellites: []`, `skipped: 0`) is distinguishable from a fully corrupt one.

## Examples

    iex> text = """
    ...> ISS (ZARYA)
    ...> 1 25544U 98067A   18184.80969102  .00001614  00000-0  31745-4 0  9993
    ...> 2 25544  51.6414 295.8524 0003435 262.6267 204.2868 15.54005638121106
    ...> """
    iex> {:ok, %{satellites: [sat], skipped: 0}} = Sidereon.Format.TLE.parse_file(text)
    iex> sat.name
    "ISS (ZARYA)"
    iex> sat.tle.catalog_number
    "25544"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
