A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
leo-orbital-manipulations.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3LEO Orbital Manipulations Visualization Script
4
5@file
6@ingroup leo
7
8This script demonstrates how different orbital parameters affect the shape and orientation
9of Low Earth Orbit (LEO) satellite trajectories. It generates 3D visualizations showing
10the effects of various parameters on orbital planes and satellite positions over time.
11
12The script mirrors the orbital mechanics implemented in the ns-3 LeoCircularOrbitMobilityModel,
13providing a visual understanding of how each parameter influences satellite motion.
14
15USAGE:
16 python leo-orbital-manipulations.py
17
18 The script will display a 3D matplotlib plot showing:
19 - Satellite positions sampled over one orbital period (blue dots)
20 - Starting position (green dot)
21 - Ending position (red dot)
22 - Earth reference circle (black dashed line)
23
24PARAMETER EFFECTS:
25
261. orbit_alt_km (Orbit Altitude):
27 - Controls the distance from Earth's center
28 - Higher altitude = larger orbit radius = slower orbital speed
29 - Affects orbital period (T ∝ r^(3/2))
30
312. time_step_sec (Time Resolution):
32 - Controls sampling frequency of orbital positions
33 - Smaller values = more position samples = smoother orbit visualization
34 - Larger values = fewer samples = coarser orbit representation
35
363. inclination_deg (Orbital Inclination):
37 - Angle between orbital plane and Earth's equatorial plane
38 - 0° = equatorial orbit (over equator)
39 - 90° = polar orbit (over poles)
40 - >90° = retrograde orbit (opposite direction)
41
424. raan_deg (Right Ascension of Ascending Node):
43 - Longitude where the satellite crosses the equator going north
44 - Rotates the entire orbital plane around Earth's Z-axis
45 - 0° = orbital plane aligned with prime meridian
46 - 90° = orbital plane rotated 90° east
47
485. plane_rotation_deg (Plane Rotation):
49 - Additional rotation of the orbital plane about its normal vector
50 - Affects the starting position within the orbital plane
51 - Equivalent to rotating the satellite's position along its orbit
52
53GENERATED FIGURES:
54This script was used to generate the figures in src/mobility/doc/figures/:
55- leo-orbit-res-*.png: Various parameter combinations showing orbital effects
56
57DEPENDENCIES:
58- numpy
59- matplotlib
60- mpl_toolkits.mplot3d
61
62EXAMPLE OUTPUT:
63The script produces a 3D plot with:
64- Blue dots: Satellite positions over one orbit
65- Green dot: Starting position
66- Red dot: Ending position
67- Black dashed circle: Earth's equatorial radius reference
68"""
69
70import argparse
71
72import matplotlib.pyplot as plt
73import numpy as np
74from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (needed for 3D projection)
75
76KM_TO_M = 1000.0
77LEO_EARTH_GGC = 398600.7 # Earth gravitational parameter μ in km^3/s^2
78
79# WGS84 equatorial radius in kilometers (a.k.a. semi-major axis a)
80EARTH_RADIUS_KM = 6378.137
81
82
84 orbit_alt_km: float, time_step_sec: float, inc_deg: float = 28.0
85) -> np.ndarray:
86 """
87 Generate an array of angular offsets ("progress vector") that advance around a circular orbit
88 by a fixed time step.
89
90 This logic:
91 - Assumes a circular orbit with radius r = (Earth radius + altitude).
92 - Uses circular-orbit speed v = sqrt(μ / r).
93 - Converts time step into arc-length step: step_size = v * dt.
94 - Converts arc-length to fraction of orbital circumference, then to an angle increment.
95
96 Parameters
97 ----------
98 orbit_alt_km
99 Orbit altitude above the reference Earth radius, in kilometers.
100 time_step_sec
101 Simulation sampling interval (dt), in seconds. One progress-vector entry is produced per dt.
102 inc_deg
103 Inclination in degrees, used only to replicate the sign convention (flip for retrograde
104 if inclination > 90 deg).
105
106 Returns
107 -------
108 np.ndarray
109 Array of angles (radians) from 0 up to ~2π (one orbital revolution), sampled at dt.
110 The length is computed so that the last step is approximately one full orbit.
111 """
112 # Orbit radius from Earth's equatorial radius (WGS84) + altitude
113 orbit_radius_km = EARTH_RADIUS_KM + orbit_alt_km
114
115 # Circular-orbit speed (km/s): v = sqrt(μ / r)
116 node_speed_kms = np.sqrt(LEO_EARTH_GGC / orbit_radius_km)
117
118 # Orbital circumference for a circular orbit: 2πr (km)
119 orbit_perimeter_km = orbit_radius_km * 2.0 * np.pi
120
121 # Distance traveled in one simulation step (km)
122 step_size_km = node_speed_kms * time_step_sec
123
124 # Fraction of full orbit per step (dimensionless)
125 step_fraction = step_size_km / orbit_perimeter_km
126
127 # Number of steps to cover ~one full orbit
128 steps = int(round(1.0 / step_fraction))
129
130 # Angle advanced per step (radians), before applying sign
131 step_angle_base = 2.0 * np.pi * step_fraction
132
133 # Match C++ sign convention: flip direction for inclinations > 90° (retrograde)
134 sign = 1 if np.deg2rad(inc_deg) <= (np.pi / 2.0) else -1
135 step_angle = sign * step_angle_base
136
137 # Angles from 0, step_angle, 2*step_angle, ...
138 return np.array([k * step_angle for k in range(steps)], dtype=float)
139
140
142 x: np.ndarray, y: np.ndarray, z: np.ndarray, raan_deg: float, inclination_deg: float
143):
144 """
145 Rotate orbit points from the initial XY plane (z=0) into a tilted/oriented orbital plane.
146
147 The transform applied is:
148 r_new = Rz(Ω) * Rx(i) * r
149 where:
150 Ω (RAAN) rotates the line of nodes around the +Z axis,
151 i (inclination) tilts the plane by rotating around the +X axis.
152
153 Parameters
154 ----------
155 x, y, z
156 Arrays (N,) representing point coordinates (km).
157 raan_deg
158 Right ascension of the ascending node Ω, in degrees (rotation about +Z).
159 inclination_deg
160 Inclination i, in degrees (rotation about +X).
161
162 Returns
163 -------
164 (x2, y2, z2)
165 Rotated coordinate arrays (km).
166 """
167 Om = np.deg2rad(raan_deg)
168 i = np.deg2rad(inclination_deg)
169
170 # Rotation about Z axis (RAAN)
171 Rz = np.array(
172 [
173 [np.cos(Om), -np.sin(Om), 0.0],
174 [np.sin(Om), np.cos(Om), 0.0],
175 [0.0, 0.0, 1.0],
176 ]
177 )
178
179 # Rotation about X axis (inclination tilt)
180 Rx = np.array(
181 [
182 [1.0, 0.0, 0.0],
183 [0.0, np.cos(i), -np.sin(i)],
184 [0.0, np.sin(i), np.cos(i)],
185 ]
186 )
187
188 pts = np.vstack([x, y, z]) # shape (3, N)
189 pts_new = (Rz @ Rx) @ pts # shape (3, N)
190 return pts_new[0], pts_new[1], pts_new[2]
191
192
194 x: np.ndarray,
195 y: np.ndarray,
196 z: np.ndarray,
197 inclination_deg: float,
198 raan_deg: float,
199 plane_rotation_deg: float,
200):
201 """
202 Apply an additional rigid rotation to all points using Rodrigues' rotation formula.
203
204 This rotates each 3D point about a user-defined axis vector `n` by `plane_rotation_deg`.
205 Rodrigues’ formula (for unit axis n) is: v' = v*cos(a) + (n×v)*sin(a) + n*(n·v)*(1-cos(a)).
206
207 Notes
208 -----
209 - The axis `n` you compute below is treated as the rotation axis in the same coordinate frame
210 as (x,y,z). Ensure the axis definition matches the physical meaning you want.
211 - `n` is normalized internally; it must not be the zero vector.
212
213 Parameters
214 ----------
215 x, y, z
216 Arrays (N,) of point coordinates (km).
217 inclination_deg
218 Inclination used in the axis construction below (degrees).
219 raan_deg
220 RAAN used in the axis construction below (degrees).
221 plane_rotation_deg
222 Rotation angle about axis n, in degrees.
223
224 Returns
225 -------
226 (x2, y2, z2)
227 Rotated coordinate arrays (km).
228 """
229 P = np.column_stack([x, y, z]).astype(float) # shape (N,3)
230
231 inc = np.deg2rad(inclination_deg)
232 Om = np.deg2rad(raan_deg)
233 a = np.deg2rad(plane_rotation_deg)
234
235 # Rotate the plane axis (derived from RAAN).
236 n = np.array([np.sin(Om) * np.sin(inc), -np.cos(Om) * np.sin(inc), np.cos(inc)], dtype=float)
237
238 n_norm = np.linalg.norm(n)
239 if n_norm == 0.0:
240 raise ValueError("Rotation axis n is zero; check inclination/raan inputs.")
241 n = n / n_norm # must be unit axis for Rodrigues
242
243 c = np.cos(a)
244 s = np.sin(a)
245
246 # Vectorized Rodrigues rotation for all points:
247 # P2 = P*c + (n×P)*s + n*(P·n)*(1-c)
248 P2 = P * c + np.cross(n, P) * s + (n * (P @ n)[:, None]) * (1.0 - c)
249
250 return P2[:, 0], P2[:, 1], P2[:, 2]
251
252
253# ----------------------------
254# Example parameters - Modify these to see different orbital effects
255# ----------------------------
256
257
258def main(
259 orbit_alt_km: float = 500.0,
260 time_step_sec: float = 600.0,
261 inclination_deg: float = 30.0,
262 raan_deg: float = 0.0,
263 plane_rotation_deg: float = 60.0,
264):
265 # ----------------------------
266 # Generate and plot the orbital trajectory
267 # ----------------------------
268
269 # 1) Generate angular progress over ~one orbit based on orbital mechanics
270 angles = generate_progress_vector(orbit_alt_km, time_step_sec, inc_deg=inclination_deg)
271
272 # 2) Create initial circular orbit in XY plane (equatorial)
273 r_km = EARTH_RADIUS_KM + orbit_alt_km
274 x = r_km * np.cos(angles)
275 y = r_km * np.sin(angles)
276 z = np.zeros_like(angles)
277
278 # 3) Apply orbital plane orientation (RAAN + inclination)
280 x, y, z, raan_deg=raan_deg, inclination_deg=inclination_deg
281 )
282
283 # 4) Apply additional rotation within the orbital plane
284 x, y, z = rodrigues_rotate_points(
285 x,
286 y,
287 z,
288 inclination_deg=inclination_deg,
289 raan_deg=raan_deg,
290 plane_rotation_deg=plane_rotation_deg,
291 )
292
293 # ----------------------------
294 # Create 3D visualization
295 # ----------------------------
296
297 fig = plt.figure(figsize=(10, 8))
298 ax = fig.add_subplot(111, projection="3d")
299
300 # Plot satellite positions over the orbit
301 ax.scatter(x, y, z, color="tab:blue", s=8, label="Satellite positions")
302 ax.scatter(x[0], y[0], z[0], color="green", s=50, label="Starting position")
303 ax.scatter(x[-1], y[-1], z[-1], color="red", s=50, label="Ending position")
304
305 # Add Earth reference circle in equatorial plane
306 theta = np.linspace(0.0, 2.0 * np.pi, 200)
307 ax.plot(
308 EARTH_RADIUS_KM * np.cos(theta),
309 EARTH_RADIUS_KM * np.sin(theta),
310 np.zeros_like(theta),
311 "k--",
312 linewidth=1.0,
313 label="Earth's equator",
314 )
315
316 # Configure plot appearance
317 ax.set_xlabel("X (km)")
318 ax.set_ylabel("Y (km)")
319 ax.set_zlabel("Z (km)")
320 ax.set_title(
321 "LEO Satellite Orbit Visualization\n"
322 f"Altitude: {orbit_alt_km} km | Time step: {time_step_sec} s | Samples: {len(angles)}\n"
323 f"Inclination: {inclination_deg}° | RAAN: {raan_deg}° | Plane rotation: {plane_rotation_deg}°"
324 )
325 ax.legend()
326 ax.grid(True, alpha=0.3)
327
328 # Set equal aspect ratio for proper 3D visualization
329 max_range = np.array([x.max() - x.min(), y.max() - y.min(), z.max() - z.min()]).max() / 2.0
330 mid_x = (x.max() + x.min()) * 0.5
331 mid_y = (y.max() + y.min()) * 0.5
332 mid_z = (z.max() + z.min()) * 0.5
333 ax.set_xlim(mid_x - max_range, mid_x + max_range)
334 ax.set_ylim(mid_y - max_range, mid_y + max_range)
335 ax.set_zlim(mid_z - max_range, mid_z + max_range)
336
337 plt.tight_layout()
338 fig_name = f"leo-orbit-res-{time_step_sec}s-inc-{inclination_deg}deg-raan-{raan_deg}deg-planeRot-{plane_rotation_deg}deg.png"
339 plt.savefig(fig_name, dpi=300)
340 # plt.show()
341 return 0
342
343
344if __name__ == "__main__":
345 parser = argparse.ArgumentParser(
346 description="LEO Orbital Manipulations Visualization Script\n\n"
347 "This script demonstrates how different orbital parameters affect the shape and orientation "
348 "of Low Earth Orbit (LEO) satellite trajectories. It generates 3D visualizations showing "
349 "the effects of various parameters on orbital planes and satellite positions over time.\n\n"
350 "The script mirrors the orbital mechanics implemented in the ns-3 LeoCircularOrbitMobilityModel, "
351 "providing a visual understanding of how each parameter influences satellite motion.\n\n"
352 "Output: 3D matplotlib plot showing satellite positions over one orbital period, "
353 "with Earth reference circle and start/end position markers.",
354 formatter_class=argparse.RawDescriptionHelpFormatter,
355 )
356 parser.add_argument(
357 "--orbit-alt-km", type=float, default=500.0, help="Altitude above Earth's surface (km)"
358 )
359 parser.add_argument(
360 "--time-step-sec", type=float, default=600.0, help="Time between position samples (seconds)"
361 )
362 parser.add_argument(
363 "--inclination-deg", type=float, default=30.0, help="Angle from equatorial plane (degrees)"
364 )
365 parser.add_argument(
366 "--raan-deg", type=float, default=0.0, help="Longitude of ascending node (degrees)"
367 )
368 parser.add_argument(
369 "--plane-rotation-deg",
370 type=float,
371 default=60.0,
372 help="Additional rotation within plane (degrees)",
373 )
374
375 args = parser.parse_args()
376
377 # Call main with parsed arguments
378 main(
379 args.orbit_alt_km,
380 args.time_step_sec,
381 args.inclination_deg,
382 args.raan_deg,
383 args.plane_rotation_deg,
384 )
apply_raan_and_inclination(np.ndarray x, np.ndarray y, np.ndarray z, float raan_deg, float inclination_deg)
rodrigues_rotate_points(np.ndarray x, np.ndarray y, np.ndarray z, float inclination_deg, float raan_deg, float plane_rotation_deg)
np.ndarray generate_progress_vector(float orbit_alt_km, float time_step_sec, float inc_deg=28.0)