From 89a4409908dbe97ba22b1a1f35a2f2ac48b30e22 Mon Sep 17 00:00:00 2001 From: Andrew Phimister <157378252+Phim226@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:32:51 +0000 Subject: [PATCH 1/5] Removed PerifocalOrbitEq from Orbit --- src/orbit_visualiser/core/orbit.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/orbit_visualiser/core/orbit.py b/src/orbit_visualiser/core/orbit.py index 83ec712..8b4cf1f 100644 --- a/src/orbit_visualiser/core/orbit.py +++ b/src/orbit_visualiser/core/orbit.py @@ -1,25 +1,7 @@ -from typing import Callable from dataclasses import dataclass from math import pi import numpy as np -# TODO: Move PerifocalOrbitEq to satellite.py. -# TODO: Expand PerifocalOrbitEq to include velocity calculation as well. -class PerifocalOrbitEq(): - - def __init__(self, e: float, p: float): - self._x: Callable[[float], float] = lambda t : p*(np.cos(t)/(1+e*np.cos(t))) - self._y: Callable[[float], float] = lambda t : p*(np.sin(t)/(1+e*np.cos(t))) - - @property - def x(self) -> Callable[[float], float]: - return self._x - - @property - def y(self) -> Callable[[float], float]: - return self._y - - class Orbit(): @@ -81,10 +63,6 @@ def orbit_type(self) -> str: def is_closed(self) -> bool: return self._is_closed - @property - def orbit_eq(self) -> PerifocalOrbitEq: - return PerifocalOrbitEq(self._e, self._p) - def orbital_angles(self) -> tuple[float]: if self._e < 1: return 0, 2*pi From 67e14433a0cb33487e80fce2ff33478f450ac444 Mon Sep 17 00:00:00 2001 From: Andrew Phimister <157378252+Phim226@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:44:49 +0000 Subject: [PATCH 2/5] Create orbital formulae helper file --- .../core/common/orbit_formulae.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/orbit_visualiser/core/common/orbit_formulae.py diff --git a/src/orbit_visualiser/core/common/orbit_formulae.py b/src/orbit_visualiser/core/common/orbit_formulae.py new file mode 100644 index 0000000..d45d2ca --- /dev/null +++ b/src/orbit_visualiser/core/common/orbit_formulae.py @@ -0,0 +1,45 @@ +import numpy as np +from numpy.typing import NDArray +from typing import Callable + +def perifocal_position_eq(e: float, p: float) -> Callable[[float], NDArray[np.float64]]: + """ + Perifocal orbit equation factory. + + Parameters + ---------- + e : float + Eccentricity + p : float + Semi-parameter + + Returns + ------- + Callable[[float], NDArray[np.float64]] + The perifocal orbit equation, taking the true anomaly as an argument + """ + def _callable(nu: float) -> NDArray[np.float64]: + return p*(1/(1 + e*np.cos(nu)))*np.array([np.cos(nu), np.sin(nu)]) + return _callable + +def perifocal_velocity_eq(e: float, mu: float, h: float) -> Callable[[float], NDArray[np.float64]]: + """ + Perifocal velocity equation factory. + + Parameters + ---------- + e : float + Eccentricity + mu : float + Gravitational parameter + h : float + Specific angular momentum + + Returns + ------- + Callable[[float], NDArray[np.float64]] + The perifocal velocity equation, taking the true anomaly as an argument + """ + def _callable(nu: float) -> NDArray[np.float64]: + return (mu/h)*np.array([-np.sin(nu), e + np.cos(nu)]) + return _callable \ No newline at end of file From bcf97e2ab46b10e10253e1933e2e1f0f5c9507fc Mon Sep 17 00:00:00 2001 From: Andrew Phimister <157378252+Phim226@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:45:24 +0000 Subject: [PATCH 3/5] Moved perifocal equations into the Satellite class --- src/orbit_visualiser/core/satellite.py | 36 ++++++++++++++----- .../ui/figure/orbit_figure.py | 13 ++++--- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/orbit_visualiser/core/satellite.py b/src/orbit_visualiser/core/satellite.py index 9b1a3ec..11302b2 100644 --- a/src/orbit_visualiser/core/satellite.py +++ b/src/orbit_visualiser/core/satellite.py @@ -1,7 +1,9 @@ import numpy as np -from numpy.typing import ArrayLike +from numpy.typing import NDArray +from typing import Callable from math import pi -from orbit_visualiser.core import Orbit, CentralBody +from orbit_visualiser.core.common.orbit_formulae import perifocal_position_eq, perifocal_velocity_eq +from orbit_visualiser.core.orbit import Orbit, CentralBody class Satellite(): @@ -22,11 +24,19 @@ def nu(self, nu: float) -> None: self._nu = nu @property - def pos_pf(self) -> ArrayLike: + def pos_pf_eq(self) -> Callable[[float], NDArray[np.float64]]: + return self._pos_pf_eq + + @property + def vel_pf_eq(self) -> Callable[[float], NDArray[np.float64]]: + return self._vel_pf_eq + + @property + def pos_pf(self) -> NDArray: return self._pos_pf @property - def vel_pf(self) -> ArrayLike: + def vel_pf(self) -> NDArray: return self._vel_pf @property @@ -94,6 +104,10 @@ def update_satellite_properties(self) -> None: h, t_asymp, a = self._specific_ang_momentum(mu, p), self._orbit.t_asymp, self._orbit.a self._h = h + # Perifocal equations + self._pos_pf_eq = perifocal_position_eq(e, p) + self._vel_pf_eq = perifocal_velocity_eq(e, mu, h) + # Helper quantities den = 1 + e*np.cos(nu) # Denominator of the orbit equation mu_over_h = mu/h @@ -103,6 +117,7 @@ def update_satellite_properties(self) -> None: self._r = self._radius(nu, t_asymp, p, den) # Kinematics + self._vel_pf = self._pf_velocity(nu) v_azim = self._azimuthal_velocity(nu, t_asymp, mu_over_h, den) self._v_azim = v_azim v_radial = self._radial_velocity(mu_over_h, e, nu) @@ -130,15 +145,18 @@ def update_satellite_properties(self) -> None: def _specific_ang_momentum(mu: float, p: float) -> float: return np.sqrt(mu*p) - def _pf_position(self, nu: float, t_asymp: float) -> tuple[float]: - orbit_eq = self._orbit.orbit_eq + def _pf_position(self, nu: float, t_asymp: float) -> NDArray[np.float64]: + pos_eq = self._pos_pf_eq + if np.isclose(abs(nu), t_asymp, atol = 0.0001, rtol = 0): nu_offset = (nu/abs(nu))*np.deg2rad(0.01) - x_close_to_inf, y_close_to_inf = orbit_eq.x(nu - nu_offset), orbit_eq.y(nu - nu_offset) - return (x_close_to_inf/abs(x_close_to_inf))*np.inf, (y_close_to_inf/abs(y_close_to_inf))*np.inf + x_close_to_inf, y_close_to_inf = pos_eq(nu_offset) + return np.array([(x_close_to_inf/abs(x_close_to_inf))*np.inf, (y_close_to_inf/abs(y_close_to_inf))*np.inf]) - return orbit_eq.x(nu), orbit_eq.y(nu) + return pos_eq(nu) + def _pf_velocity(self, nu: float) -> NDArray[np.float64]: + return self._vel_pf_eq(nu) def _azimuthal_velocity(self, nu: float, t_asymp: float, mu_over_h: float, den: float) -> float: if np.isclose(abs(nu), t_asymp, atol = 0.0001, rtol = 0): diff --git a/src/orbit_visualiser/ui/figure/orbit_figure.py b/src/orbit_visualiser/ui/figure/orbit_figure.py index 4726cfc..61d6727 100644 --- a/src/orbit_visualiser/ui/figure/orbit_figure.py +++ b/src/orbit_visualiser/ui/figure/orbit_figure.py @@ -78,10 +78,9 @@ def _configure_axes(self) -> None: def _initialise_plot(self) -> None: # Plot the initial orbit - t = self._get_anomaly_data(self._orbit.orbital_angles()) - orbit_eq = self._orbit.orbit_eq - x, y = orbit_eq.x, orbit_eq.y - self._line, = self._ax.plot(x(t) , y(t), color = "#2F2F2F", alpha = 0.5, linewidth = 1.5) + nu = self._get_anomaly_data(self._orbit.orbital_angles()) + x, y = self._sat.pos_pf_eq(nu) + self._line, = self._ax.plot(x, y, color = "#2F2F2F", alpha = 0.5, linewidth = 1.5) # Plot the central body self._ax.add_patch( @@ -118,9 +117,9 @@ def _get_anomaly_data(self, angles: tuple[float]) -> NDArray[np.float64]: return np.linspace(lower_lim + delta, upper_lim - delta, OrbitFigure.NUM_POINTS) def redraw_orbit(self) -> None: - t = self._get_anomaly_data(self._orbit.orbital_angles()) - orbit_eq = self._orbit.orbit_eq - self._line.set_data(orbit_eq.x(t), orbit_eq.y(t)) + nu = self._get_anomaly_data(self._orbit.orbital_angles()) + x, y = self._sat.pos_pf_eq(nu) + self._line.set_data(x, y) self._canvas.draw_idle() From fc2937e30d57353f29daff3bb8a43ebd92e94196 Mon Sep 17 00:00:00 2001 From: Andrew Phimister <157378252+Phim226@users.noreply.github.com> Date: Tue, 10 Feb 2026 04:01:21 +0000 Subject: [PATCH 4/5] Wrote TODOs and planned changes into the changelog --- CHANGELOG.md | 1 + src/orbit_visualiser/core/orbit.py | 2 ++ src/orbit_visualiser/core/satellite.py | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b1a8f..2dab9a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented here. # Unreleased - Certain orbital parameters can be displayed in the plot - PerifocalOrbitEq refactored to live in the Satellite class +- Better separation of orbital entity behaviour and calculation # 0.4.3 - 09/02/2026 - Implemented automatic testing diff --git a/src/orbit_visualiser/core/orbit.py b/src/orbit_visualiser/core/orbit.py index 8b4cf1f..69e599a 100644 --- a/src/orbit_visualiser/core/orbit.py +++ b/src/orbit_visualiser/core/orbit.py @@ -2,6 +2,8 @@ from math import pi import numpy as np +# TODO: Split formulae from Orbit class. +# TODO: Update orbit properties when eccentricity of radius of periapsis is set rather than calling it explicitly outside the class class Orbit(): diff --git a/src/orbit_visualiser/core/satellite.py b/src/orbit_visualiser/core/satellite.py index 11302b2..79d567e 100644 --- a/src/orbit_visualiser/core/satellite.py +++ b/src/orbit_visualiser/core/satellite.py @@ -5,6 +5,8 @@ from orbit_visualiser.core.common.orbit_formulae import perifocal_position_eq, perifocal_velocity_eq from orbit_visualiser.core.orbit import Orbit, CentralBody +# TODO: Split formulae from Satellite class. +# TODO: Update satellite properties when true anomaly is set rather than calling it explicitly outside the class class Satellite(): From 6dfc3da51746be4de916059adeebfcf3ee53c238 Mon Sep 17 00:00:00 2001 From: Andrew Phimister <157378252+Phim226@users.noreply.github.com> Date: Tue, 10 Feb 2026 04:07:55 +0000 Subject: [PATCH 5/5] Wrote TODO to write docstrings --- src/orbit_visualiser/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orbit_visualiser/main.py b/src/orbit_visualiser/main.py index 48a2f0c..70f5b02 100644 --- a/src/orbit_visualiser/main.py +++ b/src/orbit_visualiser/main.py @@ -35,7 +35,7 @@ def __init__(self, root: Tk): # TODO: Write tests as I go. # TODO: Add variable presets (Earth - ISS, Earth - Geostationary, Mars - Phobos etc). -# Test change +# TODO: Write proper docstrings if __name__ == "__main__": root = Tk()