# Utils/Orbit

Two-body Keplerian orbit mechanics for satellite position propagation.

## Import and Use

Generally, we import the library itself:

```python
from gri_utils import orbit

elements = orbit.from_elements(
    semi_major_axis_m=6_778_000,
    eccentricity=0.0001,
    inclination_rad=0.9,
    raan_rad=0.0,
    arg_periapsis_rad=0.0,
    mean_anomaly_rad=0.0,
    epoch_unix_s=1704067200.0,
)
state = orbit.elements_to_state(elements, unix_s=1704070800.0)
```

But to distinguish from other libraries, the [Google Style Guide](https://google.github.io/styleguide/pyguide.html#22-imports) also recommends giving it a more unique name:

```python
from gri_utils import orbit as gri_orbit

elements = gri_orbit.from_elements(...)
```

## Core Types

The module uses two core dataclasses:

### OrbitalElements

Defines an orbit using classical Keplerian elements:

| Field | Description | Units |
|-------|-------------|-------|
| `semi_major_axis_m` | Semi-major axis | meters |
| `eccentricity` | Orbital eccentricity | dimensionless (0 = circular) |
| `inclination_rad` | Inclination | radians |
| `raan_rad` | Right Ascension of Ascending Node | radians |
| `arg_periapsis_rad` | Argument of periapsis | radians |
| `mean_anomaly_rad` | Mean anomaly at epoch | radians |
| `epoch_unix_s` | Epoch time | Unix seconds |

### OrbitState

Position, velocity, and acceleration at a specific time:

| Field | Description | Units |
|-------|-------------|-------|
| `position_eci_m` | Position in ECI frame | meters (3,) |
| `velocity_eci_m_s` | Velocity in ECI frame | m/s (3,) |
| `acceleration_eci_m_s2` | Acceleration in ECI frame | m/s^2 (3,) |
| `unix_s` | Time of state | Unix seconds |

### OrbitalProperties

Derived orbital characteristics:

| Field | Description | Units |
|-------|-------------|-------|
| `period_s` | Orbital period | seconds |
| `apogee_radius_m` | Apogee distance from Earth center | meters |
| `perigee_radius_m` | Perigee distance from Earth center | meters |
| `apogee_altitude_m` | Apogee altitude above Earth surface | meters |
| `perigee_altitude_m` | Perigee altitude above Earth surface | meters |
| `specific_energy_j_kg` | Specific orbital energy | J/kg |
| `specific_angular_momentum_m2_s` | Specific angular momentum | m^2/s |
| `mean_motion_rad_s` | Mean motion | rad/s |

## Creating Orbits

### From Keplerian Elements

```python
# Using radians (default)
elements = orbit.from_elements(
    semi_major_axis_m=6_778_000,
    eccentricity=0.0001,
    inclination_rad=0.9,
    raan_rad=0.0,
    arg_periapsis_rad=0.0,
    mean_anomaly_rad=0.0,
    epoch_unix_s=1704067200.0,
)

# Using degrees
elements = orbit.from_elements_deg(
    semi_major_axis_m=6_778_000,
    eccentricity=0.0001,
    inclination_deg=51.6,
    raan_deg=0.0,
    arg_periapsis_deg=0.0,
    mean_anomaly_deg=0.0,
    epoch_unix_s=1704067200.0,
)
```

### From State Vector

```python
# From position and velocity
elements = orbit.from_state(position_eci_m, velocity_eci_m_s, epoch_unix_s)

# Or using the alias
elements = orbit.from_position_velocity(position_eci_m, velocity_eci_m_s, epoch_unix_s)
```

### From TLE Data

```python
tle_line1 = "1 25544U 98067A   24001.50000000  .00016717  00000-0  10270-3 0  9993"
tle_line2 = "2 25544  51.6400 247.4627 0006703 130.5360 325.0288 15.72125391378563"

elements = orbit.from_tle(tle_line1, tle_line2)
```

### From Geometric Constraints

```python
# Circular orbit through an ECEF point
elements = orbit.from_circular(xyz_ecef_m, epoch_unix_s)

# Elliptical orbit from perigee location and apogee radius
elements = orbit.from_perigee_apogee(
    perigee_xyz_eci_m,
    apogee_radius_m,
    epoch_unix_s,
    prograde=True,
)
```

### From Multiple Observations

```python
# Fit orbit to multiple state observations
states = [state1, state2, state3]  # List of OrbitState
elements = orbit.from_states(states)
```

## Propagation

Propagate elements to get state at any time:

```python
# Get state at a specific time
state = orbit.elements_to_state(elements, unix_s=1704070800.0)

print(state.position_eci_m)      # ECI position in meters
print(state.velocity_eci_m_s)    # ECI velocity in m/s
print(state.acceleration_eci_m_s2)  # ECI acceleration in m/s^2
```

## Orbital Properties

Compute derived properties from elements:

```python
props = orbit.orbital_properties(elements)

print(f"Period: {props.period_s / 60:.1f} minutes")
print(f"Apogee altitude: {props.apogee_altitude_m / 1000:.1f} km")
print(f"Perigee altitude: {props.perigee_altitude_m / 1000:.1f} km")
```

## Coordinate Frames

All positions and velocities use the **ECI (Earth-Centered Inertial)** frame. To convert to/from ECEF:

```python
from gri_utils import conversion

# ECI to ECEF
xyz_ecef = conversion.eci_to_ecef(state.position_eci_m, state.unix_s)

# ECEF to ECI
xyz_eci = conversion.ecef_to_eci(xyz_ecef, unix_s)
```

## Limitations

This module implements **two-body Keplerian mechanics only**. It does not account for:

- J2 oblateness perturbation
- Atmospheric drag
- Solar radiation pressure
- Third-body gravitational effects
- Other perturbations

For high-fidelity propagation, especially of TLE data over extended periods, use a dedicated SGP4 library like `python-sgp4`.

## References

- Vallado, D. "Fundamentals of Astrodynamics and Applications", 4th ed.
- Bate, Mueller, White "Fundamentals of Astrodynamics"
