9import matplotlib.pyplot
as plt
12from matplotlib.animation
import FuncAnimation
16 parser = argparse.ArgumentParser(
17 description=
"Generate an animated MP4 video of satellite orbital traces from CSV data. "
18 "The script reads satellite position data over time and creates a 3D animation "
19 "showing Earth as a wireframe sphere with satellite positions as red dots. "
20 "Optionally (-a)includes antenna orientation vectors as orange lines.",
22 " python plot-orbital-traces.py -i traces.csv -o animation.mp4\n"
23 " python plot-orbital-traces.py -i traces.csv -o animation.mp4 -a\n"
26 " Time(ns),Satellite,x,y,z[,x_2,y_2,z_2]\n"
27 " 1000000000,1,6371000,0,0[,6372000,0,0]\n"
29 "Requires: pandas, matplotlib, numpy, ffmpeg",
35 help=
"Path to the input CSV file containing satellite trace data. "
36 "Expected columns: Time (with 'ns' suffix), Satellite, x, y, z. "
37 "For antenna traces: also x_2, y_2, z_2 columns.",
43 help=
"Path for the output MP4 video file (e.g., 'orbital_animation.mp4'). "
44 "Requires ffmpeg to be installed.",
50 help=
"Include antenna orientation vectors in the animation. "
51 "Requires x_2, y_2, z_2 columns in the input CSV for each satellite.",
59 help=
"Render every Nth frame (default: 1). "
60 "Use to speed up animation for large traces (e.g., -d 2 halves frame count).",
63 args = parser.parse_args()
65 df = pd.read_csv(args.input)
67 df[
"Time"] = df[
"Time"].astype(str).str.rstrip(
"ns").astype(float) * 1e-9
70 times = np.sort(df[
"Time"].unique())
73 groups = df.groupby(
"Time")
77 groups.get_group(t)[
"x"].values,
78 groups.get_group(t)[
"y"].values,
79 groups.get_group(t)[
"z"].values,
85 if args.antenna_trace:
88 g = groups.get_group(t)
90 for sat
in g[
"Satellite"].unique():
91 sg = g[g[
"Satellite"] == sat]
92 frame_antenna[t][sat] = (
93 sg[[
"x",
"y",
"z"]].values[0],
94 sg[[
"x_2",
"y_2",
"z_2"]].values[0],
99 ax = fig.add_subplot(111, projection=
"3d")
103 u, v = np.mgrid[0 : 2 * np.pi : 50j, 0 : np.pi : 25j]
104 xs = radius * np.cos(u) * np.sin(v)
105 ys = radius * np.sin(u) * np.sin(v)
106 zs = radius * np.cos(v)
107 ax.plot_wireframe(xs, ys, zs, color=
"lightblue", linewidth=0.6, alpha=0.7)
110 scatter = ax.scatter([], [], [], color=
"red", s=5)
113 if args.antenna_trace:
115 for sat
in df[
"Satellite"].unique():
116 (line,) = ax.plot([], [], [], alpha=0.7, lw=0.7, color=
"orange")
118 all_sats = list(df[
"Satellite"].unique())
121 step = max(1, args.downsample)
123 def make_update(frame_step, times, frame_data, frame_antenna):
125 i = frame * frame_step
126 current_time = times[i]
127 x, y, z = frame_data[i]
128 scatter._offsets3d = (x, y, z)
129 if args.antenna_trace:
130 for sat, line
in zip(all_sats, lines.values()):
131 if sat
in frame_antenna.get(current_time, {}):
132 pos, ant = frame_antenna[current_time][sat]
133 line.set_data_3d([pos[0], ant[0]], [pos[1], ant[1]], [pos[2], ant[2]])
135 line.set_data_3d([], [], [])
136 ax.set_title(f
"Time = {current_time:.2f} s")
137 if args.antenna_trace:
138 return scatter, *lines.values()
146 make_update(step, times, frame_data, frame_antenna),
147 frames=len(times) // step,
151 ani.save(args.output, writer=
"ffmpeg", fps=30)
154if __name__ ==
"__main__":