Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions pyorbital/orbital.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ class OrbitElements:
def __init__(self, tle):
"""Initialize the class."""
self.epoch = tle.epoch
self.excentricity = tle.excentricity
self.eccentricity = tle.eccentricity
self.inclination = np.deg2rad(tle.inclination)
self.right_ascension = np.deg2rad(tle.right_ascension)
self.arg_perigee = np.deg2rad(tle.arg_perigee)
Expand All @@ -593,20 +593,26 @@ def __init__(self, tle):
self.right_ascension_lon = self.right_ascension - astronomy.gmst(self.epoch)
self.right_ascension_lon = np.fmod(self.right_ascension_lon + np.pi, 2 * np.pi) - np.pi

@property
def excentricity(self):
"""Get 'eccentricity' using legacy 'excentricity' name."""
warnings.warn("The 'eccentricity' property is deprecated in favor of 'eccentricity'", stacklevel=2)
return self.eccentricity

@property
def apogee(self):
"""Compute apogee altitude in kilometers."""
return ((self.semi_major_axis * (1 + self.excentricity)) / AE - AE) * XKMPER
return ((self.semi_major_axis * (1 + self.eccentricity)) / AE - AE) * XKMPER

@property
def perigee(self):
"""Compute perigee altitude in kilometers."""
return ((self.semi_major_axis * (1 - self.excentricity)) / AE - AE) * XKMPER
return ((self.semi_major_axis * (1 - self.eccentricity)) / AE - AE) * XKMPER

@property
def is_circular(self):
"""Check if orbit is nearly circular."""
return self.excentricity < 1e-3
return self.eccentricity < 1e-3

@property
def is_retrograde(self):
Expand All @@ -616,7 +622,7 @@ def is_retrograde(self):
def _get_true_anomaly(self):
"""Computes the True Anomaly (nu) from Mean Anomaly (M) and Eccentricity (e)."""
M = self.mean_anomaly
e = self.excentricity
e = self.eccentricity
E = M # Initial guess for Eccentric Anomaly (E)

# Iteratively solve Kepler's Equation (M = E - e*sin(E))
Expand Down Expand Up @@ -644,8 +650,8 @@ def position_vector_in_orbital_plane(self):
true_anomaly = self._get_true_anomaly()

# Calculate radius (r) and coordinates using the True Anomaly
r = self.semi_major_axis * (1 - self.excentricity**2) / \
(1 + self.excentricity * np.cos(true_anomaly))
r = self.semi_major_axis * (1 - self.eccentricity**2) / \
(1 + self.eccentricity * np.cos(true_anomaly))

x = r * np.cos(true_anomaly)
y = r * np.sin(true_anomaly)
Expand All @@ -671,11 +677,11 @@ def _get_velocity_at_apsis(self, e_1, e_2):

def velocity_at_perigee(self):
"""Compute orbital velocity at perigee in km/s."""
return self._get_velocity_at_apsis(1 - self.excentricity, 1 + self.excentricity)
return self._get_velocity_at_apsis(1 - self.eccentricity, 1 + self.eccentricity)

def velocity_at_apogee(self):
"""Compute orbital velocity at apogee in km/s."""
return self._get_velocity_at_apsis(1 + self.excentricity, 1 - self.excentricity)
return self._get_velocity_at_apsis(1 + self.eccentricity, 1 - self.eccentricity)

def _calculate_mean_motion_and_semi_major_axis(self):
"""Apply SGP4 perturbation corrections to mean motion and semi-major axis.
Expand All @@ -684,10 +690,10 @@ def _calculate_mean_motion_and_semi_major_axis(self):
"""
a_1 = (XKE / self.mean_motion) ** (2.0 / 3)
delta_1 = (3 / 2.0) * (CK2 / a_1**2) * ((3 * np.cos(self.inclination)**2 - 1) /
(1 - self.excentricity**2)**(3 / 2))
(1 - self.eccentricity**2)**(3 / 2))
a_0 = a_1 * (1 - delta_1 / 3 - delta_1**2 - (134.0 / 81) * delta_1**3)
delta_0 = (3 / 2.0) * (CK2 / a_0**2) * ((3 * np.cos(self.inclination)**2 - 1) /
(1 - self.excentricity**2)**(3 / 2))
(1 - self.eccentricity**2)**(3 / 2))

corrected_mean_motion = self.mean_motion / (1 + delta_0)
corrected_semi_major_axis = a_0 / (1 - delta_0)
Expand All @@ -704,7 +710,7 @@ def __init__(self, orbit_elements):

_check_orbital_elements(orbit_elements)

self.eo = orbit_elements.excentricity
self.eo = orbit_elements.eccentricity
self.xincl = orbit_elements.inclination
self.xno = orbit_elements.original_mean_motion
self.bstar = orbit_elements.bstar
Expand Down Expand Up @@ -1290,8 +1296,8 @@ def _collect_return_values(self):


def _check_orbital_elements(orbit_elements):
if not (0 < orbit_elements.excentricity < ECC_LIMIT_HIGH):
raise OrbitalError("Eccentricity out of range: %e" % orbit_elements.excentricity)
if not (0 < orbit_elements.eccentricity < ECC_LIMIT_HIGH):
raise OrbitalError("Eccentricity out of range: %e" % orbit_elements.eccentricity)
if not ((0.0035 * 2 * np.pi / XMNPDA) < orbit_elements.original_mean_motion < (18 * 2 * np.pi / XMNPDA)):
raise OrbitalError("Mean motion out of range: %e" % orbit_elements.original_mean_motion)
if not (0 < orbit_elements.inclination < np.pi):
Expand Down
32 changes: 16 additions & 16 deletions pyorbital/tests/test_orbit_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class MockTLE:
def __init__(self):
"""Initialize mock TLE values for testing."""
self.epoch = datetime.datetime(2025, 9, 30, 12, 0, 0)
self.excentricity = 0.001
self.eccentricity = 0.001
self.inclination = 98.7
self.right_ascension = 120.0
self.arg_perigee = 87.0
Expand All @@ -65,10 +65,10 @@ def test_orbit_elements_computation():
assert -np.pi <= orbit.right_ascension_lon <= np.pi


def test_zero_excentricity():
def test_zero_eccentricity():
"""Test perigee calculation with zero eccentricity."""
tle = MockTLE()
tle.excentricity = 0.0
tle.eccentricity = 0.0
orbit = OrbitElements(tle)
# Perigee == Apogee altitude when e=0
assert orbit.perigee == pytest.approx(
Expand Down Expand Up @@ -134,7 +134,7 @@ def test_apogee_computation():
tle = MockTLE()
orbit = OrbitElements(tle)
expected_apogee = (
(orbit.semi_major_axis * (1 + orbit.excentricity)) / AE - AE
(orbit.semi_major_axis * (1 + orbit.eccentricity)) / AE - AE
) * XKMPER
assert orbit.apogee == pytest.approx(expected_apogee, abs=1e-3)

Expand Down Expand Up @@ -178,7 +178,7 @@ def test_velocity_at_apogee_accuracy():
def test_position_vector_in_orbital_plane_perigee_accuracy():
"""Test position vector at perigee (Mean Anomaly = 0°)."""
tle = MockTLE()
tle.excentricity = 0.001
tle.eccentricity = 0.001
tle.mean_anomaly = 0.0
orbit = OrbitElements(tle)
pos = orbit.position_vector_in_orbital_plane()
Expand Down Expand Up @@ -208,16 +208,16 @@ def test_position_vector_in_orbital_plane_varied(mean_anomaly_deg, expected_radi


@pytest.mark.parametrize(
("excentricity", "expected"),
("eccentricity", "expected"),
[
(0.0005, True),
(0.01, False),
],
)
def test_is_circular_property(excentricity, expected):
def test_is_circular_property(eccentricity, expected):
"""Test circular orbit detection based on eccentricity."""
tle = MockTLE()
tle.excentricity = excentricity
tle.eccentricity = eccentricity
orbit = OrbitElements(tle)
assert orbit.is_circular == expected

Expand All @@ -237,22 +237,22 @@ def test_is_retrograde_property(inclination_deg, expected):
assert orbit.is_retrograde == expected


@pytest.mark.parametrize("excentricity", [0.001, 0.01, 0.1, 0.5])
def test_velocity_perigee_greater_than_apogee(excentricity):
@pytest.mark.parametrize("eccentricity", [0.001, 0.01, 0.1, 0.5])
def test_velocity_perigee_greater_than_apogee(eccentricity):
"""Ensure velocity at perigee is greater than at apogee for elliptical orbits."""
tle = MockTLE()
tle.excentricity = excentricity
tle.eccentricity = eccentricity
orbit = OrbitElements(tle)
v_perigee = orbit.velocity_at_perigee()
v_apogee = orbit.velocity_at_apogee()
assert v_perigee > v_apogee


@pytest.mark.parametrize("excentricity", [0.0, ECC_EPS / 10, ECC_EPS])
def test_velocity_equal_for_circular_orbits(excentricity):
@pytest.mark.parametrize("eccentricity", [0.0, ECC_EPS / 10, ECC_EPS])
def test_velocity_equal_for_circular_orbits(eccentricity):
"""Ensure velocity at perigee equals velocity at apogee for nearly circular orbits."""
tle = MockTLE()
tle.excentricity = excentricity
tle.eccentricity = eccentricity
orbit = OrbitElements(tle)
v_perigee = orbit.velocity_at_perigee()
v_apogee = orbit.velocity_at_apogee()
Expand All @@ -262,7 +262,7 @@ def test_velocity_equal_for_circular_orbits(excentricity):
def test_high_eccentricity_limit():
"""Test behavior near the upper eccentricity limit."""
tle = MockTLE()
tle.excentricity = ECC_LIMIT_HIGH
tle.eccentricity = ECC_LIMIT_HIGH
orbit = OrbitElements(tle)
assert orbit.semi_major_axis > 0
assert orbit.velocity_at_perigee() > orbit.velocity_at_apogee()
Expand All @@ -281,7 +281,7 @@ def __init__(self):
super().__init__()
"""Initialize reference TLE values for SGP4 unnormalization test."""
self.inclination = 98.7408 # degrees
self.excentricity = 0.001140
self.eccentricity = 0.001140
self.mean_motion = 14.28634842 # rev/day
self.epoch = datetime.datetime(2200, 1, 1, 0, 0, 0)

Expand Down
4 changes: 2 additions & 2 deletions pyorbital/tests/test_tlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def check_example(self, tle):
# line 2
assert tle.inclination == 51.6416
assert tle.right_ascension == 247.4627
assert tle.excentricity == 0.0006703
assert tle.eccentricity == 0.0006703
assert tle.arg_perigee == 130.536
assert tle.mean_anomaly == 325.0288
assert tle.mean_motion == 15.72125391
Expand Down Expand Up @@ -877,6 +877,6 @@ def test_tle_instance_printing():

tle = Tle("ISS", line1=LINE1, line2=LINE2)

expected = "{'arg_perigee': 130.536,\n 'bstar': -1.1606e-05,\n 'classification': 'U',\n 'element_number': 292,\n 'ephemeris_type': 0,\n 'epoch': np.datetime64('2008-09-20T12:25:40.104192'),\n 'epoch_day': 264.51782528,\n 'epoch_year': '08',\n 'excentricity': 0.0006703,\n 'id_launch_number': '067',\n 'id_launch_piece': 'A ',\n 'id_launch_year': '98',\n 'inclination': 51.6416,\n 'mean_anomaly': 325.0288,\n 'mean_motion': 15.72125391,\n 'mean_motion_derivative': -2.182e-05,\n 'mean_motion_sec_derivative': 0.0,\n 'orbit': 56353,\n 'right_ascension': 247.4627,\n 'satnumber': '25544'}" # noqa
expected = "{'arg_perigee': 130.536,\n 'bstar': -1.1606e-05,\n 'classification': 'U',\n 'eccentricity': 0.0006703,\n 'element_number': 292,\n 'ephemeris_type': 0,\n 'epoch': np.datetime64('2008-09-20T12:25:40.104192'),\n 'epoch_day': 264.51782528,\n 'epoch_year': '08',\n 'id_launch_number': '067',\n 'id_launch_piece': 'A ',\n 'id_launch_year': '98',\n 'inclination': 51.6416,\n 'mean_anomaly': 325.0288,\n 'mean_motion': 15.72125391,\n 'mean_motion_derivative': -2.182e-05,\n 'mean_motion_sec_derivative': 0.0,\n 'orbit': 56353,\n 'right_ascension': 247.4627,\n 'satnumber': '25544'}" # noqa

assert str(tle) == expected
12 changes: 9 additions & 3 deletions pyorbital/tlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def __init__(self, platform, tle_file=None, line1=None, line2=None):
self.element_number = None
self.inclination = None
self.right_ascension = None
self.excentricity = None
self.eccentricity = None
self.arg_perigee = None
self.mean_anomaly = None
self.mean_motion = None
Expand All @@ -200,6 +200,12 @@ def __init__(self, platform, tle_file=None, line1=None, line2=None):
self._checksum()
self._parse_tle()

@property
def excentricity(self):
"""Get 'eccentricity' using legacy 'excentricity' name."""
warnings.warn("The 'eccentricity' property is deprecated in favor of 'eccentricity'", stacklevel=2)
return self.eccentricity

@property
def line1(self):
"""Return first TLE line."""
Expand Down Expand Up @@ -275,7 +281,7 @@ def _read_tle_decimal(rep):

self.inclination = float(self._line2[8:16])
self.right_ascension = float(self._line2[17:25])
self.excentricity = int(self._line2[26:33]) * 10 ** -7
self.eccentricity = int(self._line2[26:33]) * 10 ** -7
self.arg_perigee = float(self._line2[34:42])
self.mean_anomaly = float(self._line2[43:51])
self.mean_motion = float(self._line2[52:63])
Expand All @@ -300,7 +306,7 @@ def to_dict(self):
"element_number": self.element_number,
"inclination": self.inclination,
"right_ascension": self.right_ascension,
"excentricity": self.excentricity,
"eccentricity": self.eccentricity,
"arg_perigee": self.arg_perigee,
"mean_anomaly": self.mean_anomaly,
"mean_motion": self.mean_motion,
Expand Down
Loading