Tutorial 02: Coordinate Systems & Frame Transformations

This tutorial covers conversions between common spacecraft coordinate frames using the opengnc.utils.frame_conversion module.


Theory Prerequisites

1. Earth-Centered Inertial (ECI) vs Earth-Centered Fixed (ECEF)

  • ECI (\(I\)): Inertial frame originating at Earth’s center. X-axis points to Vernal Equinox (\(\Upsilon\)).

  • ECEF (\(E\)): Rotates with the Earth. X-axis points to Prime Meridian.

The transformation from ECI to ECEF is a rotation about the Z-axis by the Greenwich Mean Sidereal Time (GMST):

\[ R^{I \to E} = R_z(\theta_{GMST}) \]
\[ r_{ECEF} = R^{I \to E} r_{ECI} \]

2. Local-Vertical Local-Horizontal (LVLH)

The LVLH frame is a satellite-centered frame useful for attitude determination and relative navigation.

  • \(\hat{z}_{LVLH}\) (Nadir): \(-\frac{r}{|r|}\) (Points towards Earth center)

  • \(\hat{y}_{LVLH}\) (Orbit Normal): \(-\frac{h}{|h|}\) where \(h = r \times v\) is angular momentum

  • \(\hat{x}_{LVLH}\): \(\hat{y}_{LVLH} \times \hat{z}_{LVLH}\) (Completes right-handed system)


import numpy as np
from opengnc.utils.frame_conversion import eci2ecef, ecef2eci, eci2lvlh_dcm, eci2llh, llh2ecef
from opengnc.utils.time_utils import calc_gmst

print("Imports successful.")
Imports successful.

1. ECI to ECEF Conversion

Let’s define a spacecraft position and velocity in ECI (J2000) for a LEO satellite at JD = 2451545.0 (J2000 epoch).

# Define ECI state (J2000)
r_eci = np.array([6771000.0, 0.0, 0.0]) # [m]
v_eci = np.array([0.0, 7670.0, 0.0])   # [m/s]
jd = 2451545.0 # J2000 epoch

# Convert to ECEF
r_ecef, v_ecef = eci2ecef(r_eci, v_eci, jd)

print(f"ECI Position: {r_eci} m")
print(f"ECEF Position: {r_ecef} m")

# Verify round-trip conversion
r_eci_rt, v_eci_rt = ecef2eci(r_ecef, v_ecef, jd)
print(f"\nRound-trip Position Error: {np.linalg.norm(r_eci - r_eci_rt):.6e} m")
ECI Position: [6771000.       0.       0.] m
ECEF Position: [ 1229340.41068344 -6658465.52552919        0.        ] m

Round-trip Position Error: 3.755864e-11 m

2. ECI to LVLH Transformation (DCM)

The Rotation matrix or Direction Cosine Matrix (DCM) maps ECI vectors into the Local-Vertical Local-Horizontal frame.

# Calculate ECI to LVLH DCM
DCM_eci2lvlh = eci2lvlh_dcm(r_eci, v_eci)

print("DCM (ECI -> LVLH):")
print(DCM_eci2lvlh)

# Verify Orthogonality (DCM * DCM^T = I)
identity_check = DCM_eci2lvlh @ DCM_eci2lvlh.T
print(f"\nOrthogonality Check Error: {np.linalg.norm(identity_check - np.eye(3)):.6e}")
DCM (ECI -> LVLH):
[[ 0.  1.  0.]
 [-0. -0. -1.]
 [-1. -0. -0.]]

Orthogonality Check Error: 0.000000e+00

3. Geodetic Coordinates (LLH)

Convert ECI position to Latitude, Longitude, and Altitude (above WGS84 ellipsoid).

# Convert to LLH (Latitude, Longitude, Altitude)
lat, lon, alt = eci2llh(r_eci, jd)

print(f"Latitude:  {np.degrees(lat):.4f} deg")
print(f"Longitude: {np.degrees(lon):.4f} deg")
print(f"Altitude:  {alt/1000.0:.2f} km")

# Verify using llh2ecef
r_ecef_check = llh2ecef(lat, lon, alt)
print(f"\nECEF Position from LLH: {r_ecef_check} m")
print(f"Difference from original ECEF: {np.linalg.norm(r_ecef - r_ecef_check):.6e} m")
Latitude:  0.0000 deg
Longitude: -79.5394 deg
Altitude:  392.86 km

ECEF Position from LLH: [ 1229340.41068344 -6658465.52552919        0.        ] m
Difference from original ECEF: 0.000000e+00 m