import matplotlib
import numpy as np
import pytest
matplotlib.use("Agg") import matplotlib.pyplot as plt
from astropy import units as u
from astrora.bodies import Earth
from astrora.plotting import StaticOrbitPlotter, plot_ground_track
from astrora.twobody import Orbit
class TestStaticOrbitPlotter:
def test_plotter_creation(self):
plotter = StaticOrbitPlotter()
assert plotter is not None
assert plotter.ax is not None
assert plotter.attractor is None
def test_plotter_with_custom_axes(self):
fig, ax = plt.subplots()
plotter = StaticOrbitPlotter(ax=ax)
assert plotter.ax is ax
plt.close(fig)
def test_plotter_dark_mode(self):
plotter = StaticOrbitPlotter(dark=True)
assert plotter._dark is True
plt.close(plotter.ax.figure)
def test_set_attractor(self):
plotter = StaticOrbitPlotter()
plotter.set_attractor(Earth)
assert plotter.attractor is Earth
assert plotter.attractor.name == "Earth"
plt.close(plotter.ax.figure)
def test_plot_circular_orbit(self):
r = np.array([7000e3, 0, 0]) v = np.array([0, 7546, 0]) orbit = Orbit.from_vectors(Earth, r, v)
plotter = StaticOrbitPlotter()
traj, pos = plotter.plot(orbit, label="Test Orbit")
assert len(traj) > 0
assert pos is not None
assert plotter.attractor is Earth
plt.close(plotter.ax.figure)
def test_plot_with_units(self):
orbit = Orbit.from_classical(
Earth,
a=7000 << u.km,
ecc=0.01 << u.one,
inc=51.6 << u.deg,
raan=0 << u.deg,
argp=0 << u.deg,
nu=0 << u.deg,
)
plotter = StaticOrbitPlotter()
traj, pos = plotter.plot(orbit, label="ISS-like")
assert len(traj) > 0
assert pos is not None
plt.close(plotter.ax.figure)
def test_plot_with_color(self):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
plotter = StaticOrbitPlotter()
traj, pos = plotter.plot(orbit, color="red")
assert traj[0].get_color() == "red"
plt.close(plotter.ax.figure)
def test_plot_with_trail(self):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
plotter = StaticOrbitPlotter()
traj, pos = plotter.plot(orbit, trail=True)
assert len(traj) > 1
plt.close(plotter.ax.figure)
def test_plot_multiple_orbits(self):
r1 = np.array([7000e3, 0, 0])
v1 = np.array([0, 7546, 0])
orbit1 = Orbit.from_vectors(Earth, r1, v1)
r2 = np.array([8000e3, 0, 0])
v2 = np.array([0, 7000, 0])
orbit2 = Orbit.from_vectors(Earth, r2, v2)
plotter = StaticOrbitPlotter()
plotter.plot(orbit1, label="Orbit 1")
plotter.plot(orbit2, label="Orbit 2")
assert plotter.attractor is Earth
plt.close(plotter.ax.figure)
def test_plot_elliptical_orbit(self):
orbit = Orbit.from_classical(
Earth,
a=10000e3, ecc=0.3, inc=np.deg2rad(45),
raan=0,
argp=0,
nu=0,
)
plotter = StaticOrbitPlotter()
traj, pos = plotter.plot(orbit)
assert len(traj) > 0
plt.close(plotter.ax.figure)
def test_plot_trajectory(self):
theta = np.linspace(0, 2 * np.pi, 100)
r = 7000e3 positions = np.column_stack([r * np.cos(theta), r * np.sin(theta), np.zeros_like(theta)])
plotter = StaticOrbitPlotter()
plotter.set_attractor(Earth)
traj, pos = plotter.plot_trajectory(positions, label="Custom trajectory")
assert len(traj) > 0
assert pos is not None
plt.close(plotter.ax.figure)
def test_plot_trajectory_with_units(self):
theta = np.linspace(0, 2 * np.pi, 100)
r = 7000 positions = (
np.column_stack([r * np.cos(theta), r * np.sin(theta), np.zeros_like(theta)]) << u.km
)
plotter = StaticOrbitPlotter()
plotter.set_attractor(Earth)
traj, pos = plotter.plot_trajectory(positions)
assert len(traj) > 0
plt.close(plotter.ax.figure)
def test_plot_trajectory_without_attractor_raises(self):
positions = np.random.randn(10, 3) * 7000e3
plotter = StaticOrbitPlotter()
with pytest.raises(ValueError, match="Must set attractor"):
plotter.plot_trajectory(positions)
plt.close(plotter.ax.figure)
def test_savefig(self, tmp_path):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
plotter = StaticOrbitPlotter()
plotter.plot(orbit, label="Test")
output_path = tmp_path / "test_orbit.png"
plotter.savefig(str(output_path))
assert output_path.exists()
assert output_path.stat().st_size > 0
plt.close(plotter.ax.figure)
class TestOrbitPlotter3D:
def test_import_plotly_required(self):
try:
import plotly
from astrora.plotting import OrbitPlotter3D
plotter = OrbitPlotter3D()
assert plotter is not None
except ImportError:
with pytest.raises(ImportError):
from astrora.plotting import OrbitPlotter3D
OrbitPlotter3D()
@pytest.mark.skipif(
not pytest.importorskip("plotly", reason="plotly not installed"),
reason="plotly not available",
)
def test_plotter3d_creation(self):
from astrora.plotting import OrbitPlotter3D
plotter = OrbitPlotter3D()
assert plotter is not None
assert plotter.fig is not None
@pytest.mark.skipif(
not pytest.importorskip("plotly", reason="plotly not installed"),
reason="plotly not available",
)
def test_plotter3d_dark_mode(self):
from astrora.plotting import OrbitPlotter3D
plotter = OrbitPlotter3D(dark=True)
assert plotter._dark is True
@pytest.mark.skipif(
not pytest.importorskip("plotly", reason="plotly not installed"),
reason="plotly not available",
)
def test_plot3d_orbit(self):
from astrora.plotting import OrbitPlotter3D
r = np.array([7000e3, 0, 0])
v = np.array([0, 0, 7546])
orbit = Orbit.from_vectors(Earth, r, v)
plotter = OrbitPlotter3D()
plotter.plot(orbit, label="Test 3D")
assert plotter.attractor is Earth
assert len(plotter.fig.data) > 0
class TestGroundTrackPlotting:
def test_plot_ground_track_basic(self):
orbit = Orbit.from_classical(
Earth,
a=6800 << u.km,
ecc=0.0001 << u.one,
inc=51.6 << u.deg,
raan=0 << u.deg,
argp=0 << u.deg,
nu=0 << u.deg,
)
ax = plot_ground_track(orbit, duration=orbit.period.value, dt=60)
assert ax is not None
assert ax.get_xlabel() == "Longitude (°)"
assert ax.get_ylabel() == "Latitude (°)"
plt.close(ax.figure)
def test_plot_ground_track_with_color(self):
r = np.array([6800e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
ax = plot_ground_track(orbit, duration=orbit.period, color="red", label="Test track")
assert ax is not None
plt.close(ax.figure)
def test_plot_ground_track_polar_orbit(self):
orbit = Orbit.from_classical(
Earth,
a=7000 << u.km,
ecc=0.001 << u.one,
inc=90 << u.deg, raan=0 << u.deg,
argp=0 << u.deg,
nu=0 << u.deg,
)
ax = plot_ground_track(orbit, duration=orbit.period.value, dt=60)
assert ax is not None
plt.close(ax.figure)
class TestPorkchopPlotting:
def test_porkchop_import(self):
from astrora.plotting import plot_porkchop, plot_porkchop_simple
assert plot_porkchop is not None
assert plot_porkchop_simple is not None
def test_plotting_module_exports():
from astrora import plotting
assert hasattr(plotting, "StaticOrbitPlotter")
assert hasattr(plotting, "OrbitPlotter3D")
assert hasattr(plotting, "plot_porkchop")
assert hasattr(plotting, "plot_ground_track")
assert hasattr(plotting, "plot_ground_track_3d")
class TestOrbitPlotter3DComprehensive:
def test_plotter3d_basic_creation(self):
try:
from astrora.plotting import OrbitPlotter3D
plotter = OrbitPlotter3D()
assert plotter is not None
assert plotter.fig is not None
except ImportError:
pytest.skip("Plotly not available")
def test_plotter3d_dark_mode(self):
try:
from astrora.plotting import OrbitPlotter3D
plotter = OrbitPlotter3D(dark=True)
assert plotter._dark is True
except ImportError:
pytest.skip("Plotly not available")
def test_plotter3d_plot_orbit(self):
try:
from astrora.plotting import OrbitPlotter3D
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
plotter = OrbitPlotter3D()
plotter.plot(orbit, label="Test Orbit")
except ImportError:
pytest.skip("Plotly not available")
def test_plotter3d_show_method(self):
try:
from astrora.plotting import OrbitPlotter3D
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
plotter = OrbitPlotter3D()
plotter.plot(orbit)
except ImportError:
pytest.skip("Plotly not available")
class TestAnimationComprehensive:
def test_animate_orbit_basic(self):
try:
from astrora.plotting import animate_orbit
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
anim = animate_orbit(orbit, num_frames=5)
assert anim is not None
except (ImportError, RuntimeError) as e:
pytest.skip(f"Animation not available: {e}")
def test_animate_orbit_with_options(self):
try:
from astrora.plotting import animate_orbit
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
anim = animate_orbit(orbit, num_frames=5, trail=True, interval=100)
assert anim is not None
except (ImportError, RuntimeError) as e:
pytest.skip(f"Animation with options not available: {e}")
def test_animate_multiple_orbits(self):
try:
from astrora.plotting import animate_orbit
r1 = np.array([7000e3, 0, 0])
v1 = np.array([0, 7546, 0])
orbit1 = Orbit.from_vectors(Earth, r1, v1)
r2 = np.array([8000e3, 0, 0])
v2 = np.array([0, 7200, 0])
orbit2 = Orbit.from_vectors(Earth, r2, v2)
anim = animate_orbit([orbit1, orbit2], num_frames=5)
assert anim is not None
except (ImportError, RuntimeError, TypeError) as e:
pytest.skip(f"Multiple orbit animation not available: {e}")
class TestGroundTrackComprehensive:
def test_ground_track_basic_leo(self):
from astrora.plotting import plot_ground_track
r = np.array([6778e3, 0, 0])
v = np.array([0, 7500, 0])
orbit = Orbit.from_vectors(Earth, r, v)
try:
duration = orbit.period.to(u.s).value
ax = plot_ground_track(orbit, duration=duration)
assert ax is not None
fig = ax.figure
assert fig is not None
plt.close(fig)
except ImportError as e:
pytest.skip(f"Cartopy or other dependency missing: {e}")
def test_ground_track_polar_orbit(self):
from astrora.plotting import plot_ground_track
r = np.array([7000e3, 0, 0])
v = np.array([0, 0, 7500])
orbit = Orbit.from_vectors(Earth, r, v)
try:
duration = orbit.period.to(u.s).value
ax = plot_ground_track(orbit, duration=duration)
assert ax is not None
plt.close(ax.figure)
except ImportError as e:
pytest.skip(f"Cartopy missing: {e}")
def test_ground_track_with_custom_ax(self):
from astrora.plotting import plot_ground_track
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
try:
custom_fig, custom_ax = plt.subplots()
duration = orbit.period.to(u.s).value
ax = plot_ground_track(orbit, duration=duration, ax=custom_ax)
assert ax is custom_ax
plt.close(custom_fig)
except ImportError as e:
pytest.skip(f"Cartopy missing: {e}")
class TestStaticPlotterComprehensive:
def test_plot_multiple_orbits(self):
plotter = StaticOrbitPlotter()
plotter.set_attractor(Earth)
r1 = np.array([7000e3, 0, 0])
v1 = np.array([0, 7546, 0])
orbit1 = Orbit.from_vectors(Earth, r1, v1)
plotter.plot(orbit1, label="Orbit 1")
r2 = np.array([8000e3, 0, 0])
v2 = np.array([0, 7200, 0])
orbit2 = Orbit.from_vectors(Earth, r2, v2)
plotter.plot(orbit2, label="Orbit 2")
plt.close(plotter.ax.figure)
def test_plot_trajectory(self):
plotter = StaticOrbitPlotter()
plotter.set_attractor(Earth)
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
times = np.linspace(0, orbit.period.to_value(u.s), 100)
try:
trajectory = orbit.sample(times)
except AttributeError:
pass
plt.close(plotter.ax.figure)
def test_plot_with_color_and_style(self):
plotter = StaticOrbitPlotter()
plotter.set_attractor(Earth)
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
plotter.plot(orbit, label="Custom", color="red")
plt.close(plotter.ax.figure)