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):
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