Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/aerospace/orbital_mechanics.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{algorithm2e}
9
\usepackage{subcaption}
10
\usepackage[makestderr]{pythontex}
11
12
% Theorem environments for textbook style
13
\newtheorem{definition}{Definition}
14
\newtheorem{theorem}{Theorem}
15
\newtheorem{example}{Example}
16
\newtheorem{remark}{Remark}
17
18
\title{Orbital Mechanics: Hohmann Transfers, Orbital Elements, and Ground Tracks\\
19
\large A Comprehensive Analysis of Spacecraft Trajectory Design}
20
\author{Astrodynamics Division\\Computational Science Templates}
21
\date{\today}
22
23
\begin{document}
24
\maketitle
25
26
\begin{abstract}
27
This textbook-style analysis presents the fundamentals of orbital mechanics for spacecraft mission design. We examine Hohmann transfer orbits between circular orbits, compute orbital elements from state vectors, and generate ground tracks for various orbit types. The analysis covers delta-v budgets for LEO-to-GEO transfers, interplanetary trajectory concepts, and the effects of orbital inclination on ground coverage patterns.
28
\end{abstract}
29
30
\section{Introduction}
31
Orbital mechanics provides the mathematical foundation for spacecraft trajectory design. Understanding orbital transfers, perturbations, and ground coverage is essential for mission planning, satellite constellation design, and interplanetary exploration.
32
33
\begin{definition}[Keplerian Elements]
34
The six classical orbital elements are:
35
\begin{enumerate}
36
\item $a$ - Semi-major axis (orbit size)
37
\item $e$ - Eccentricity (orbit shape)
38
\item $i$ - Inclination (orbital plane tilt)
39
\item $\Omega$ - Right ascension of ascending node (RAAN)
40
\item $\omega$ - Argument of periapsis
41
\item $\nu$ - True anomaly (position in orbit)
42
\end{enumerate}
43
\end{definition}
44
45
\section{Mathematical Framework}
46
47
\subsection{Vis-Viva Equation}
48
The fundamental relation between orbital velocity and position:
49
\begin{equation}
50
v = \sqrt{\mu \left(\frac{2}{r} - \frac{1}{a}\right)}
51
\end{equation}
52
where $\mu = GM$ is the standard gravitational parameter.
53
54
\subsection{Hohmann Transfer}
55
\begin{theorem}[Hohmann Transfer Delta-V]
56
The minimum delta-v for transfer between two coplanar circular orbits:
57
\begin{align}
58
\Delta v_1 &= \sqrt{\frac{\mu}{r_1}}\left(\sqrt{\frac{2r_2}{r_1 + r_2}} - 1\right) \\
59
\Delta v_2 &= \sqrt{\frac{\mu}{r_2}}\left(1 - \sqrt{\frac{2r_1}{r_1 + r_2}}\right)
60
\end{align}
61
\end{theorem}
62
63
\subsection{Transfer Time}
64
The time of flight for a Hohmann transfer is half the period of the transfer ellipse:
65
\begin{equation}
66
t_{transfer} = \pi\sqrt{\frac{a_t^3}{\mu}} = \pi\sqrt{\frac{(r_1 + r_2)^3}{8\mu}}
67
\end{equation}
68
69
\subsection{Orbital Elements from State Vectors}
70
Given position $\mathbf{r}$ and velocity $\mathbf{v}$:
71
\begin{align}
72
\mathbf{h} &= \mathbf{r} \times \mathbf{v} \quad \text{(angular momentum)} \\
73
\mathbf{e} &= \frac{\mathbf{v} \times \mathbf{h}}{\mu} - \frac{\mathbf{r}}{r} \quad \text{(eccentricity vector)}
74
\end{align}
75
76
\section{Computational Analysis}
77
78
\begin{pycode}
79
import numpy as np
80
import matplotlib.pyplot as plt
81
from mpl_toolkits.mplot3d import Axes3D
82
plt.rc('text', usetex=True)
83
plt.rc('font', family='serif')
84
85
np.random.seed(42)
86
87
# Physical constants
88
mu_earth = 3.986004418e14 # Earth's gravitational parameter (m^3/s^2)
89
R_earth = 6.371e6 # Earth's radius (m)
90
omega_earth = 7.2921159e-5 # Earth's rotation rate (rad/s)
91
92
# Orbital radii
93
orbits = {
94
'LEO': R_earth + 400e3,
95
'MEO': R_earth + 20200e3,
96
'GEO': 42164e3,
97
'Lunar': 384400e3
98
}
99
100
# Function to compute circular orbital velocity
101
def circular_velocity(r):
102
return np.sqrt(mu_earth / r)
103
104
# Function to compute Hohmann transfer parameters
105
def hohmann_transfer(r1, r2):
106
a_transfer = (r1 + r2) / 2
107
108
# Velocities
109
v1_circular = circular_velocity(r1)
110
v2_circular = circular_velocity(r2)
111
v1_transfer = np.sqrt(mu_earth * (2/r1 - 1/a_transfer))
112
v2_transfer = np.sqrt(mu_earth * (2/r2 - 1/a_transfer))
113
114
# Delta-v
115
dv1 = abs(v1_transfer - v1_circular)
116
dv2 = abs(v2_circular - v2_transfer)
117
dv_total = dv1 + dv2
118
119
# Transfer time
120
t_transfer = np.pi * np.sqrt(a_transfer**3 / mu_earth)
121
122
return {
123
'dv1': dv1, 'dv2': dv2, 'dv_total': dv_total,
124
't_transfer': t_transfer, 'a_transfer': a_transfer
125
}
126
127
# Compute transfers from LEO to various orbits
128
transfers = {}
129
r_leo = orbits['LEO']
130
for name, r_target in orbits.items():
131
if name != 'LEO':
132
transfers[name] = hohmann_transfer(r_leo, r_target)
133
134
# Ground track computation
135
def compute_ground_track(a, e, i, omega, Omega, n_orbits=3, n_points=1000):
136
"""Compute ground track for an orbit."""
137
T = 2 * np.pi * np.sqrt(a**3 / mu_earth) # Orbital period
138
t = np.linspace(0, n_orbits * T, n_points)
139
140
# Mean motion and mean anomaly
141
n = np.sqrt(mu_earth / a**3)
142
M = n * t
143
144
# Solve Kepler's equation (simplified for small e)
145
E = M + e * np.sin(M) # First-order approximation
146
nu = 2 * np.arctan2(np.sqrt(1+e) * np.sin(E/2), np.sqrt(1-e) * np.cos(E/2))
147
148
# Orbital radius
149
r = a * (1 - e * np.cos(E))
150
151
# Position in orbital plane
152
x_orb = r * np.cos(nu)
153
y_orb = r * np.sin(nu)
154
155
# Rotation matrices
156
cos_O, sin_O = np.cos(Omega), np.sin(Omega)
157
cos_i, sin_i = np.cos(i), np.sin(i)
158
cos_w, sin_w = np.cos(omega), np.sin(omega)
159
160
# Transform to ECI coordinates
161
x_eci = (cos_O*cos_w - sin_O*sin_w*cos_i)*x_orb + (-cos_O*sin_w - sin_O*cos_w*cos_i)*y_orb
162
y_eci = (sin_O*cos_w + cos_O*sin_w*cos_i)*x_orb + (-sin_O*sin_w + cos_O*cos_w*cos_i)*y_orb
163
z_eci = (sin_w*sin_i)*x_orb + (cos_w*sin_i)*y_orb
164
165
# Convert to latitude/longitude (accounting for Earth rotation)
166
lat = np.arcsin(z_eci / r)
167
lon = np.arctan2(y_eci, x_eci) - omega_earth * t
168
169
# Wrap longitude to [-180, 180]
170
lon = np.rad2deg(lon)
171
lon = ((lon + 180) % 360) - 180
172
lat = np.rad2deg(lat)
173
174
return lon, lat, T
175
176
# Define orbit types for ground track comparison
177
orbit_configs = {
178
'ISS (LEO)': {'a': R_earth + 420e3, 'e': 0.0001, 'i': np.deg2rad(51.6)},
179
'Sun-Sync': {'a': R_earth + 700e3, 'e': 0.001, 'i': np.deg2rad(98.2)},
180
'GEO': {'a': 42164e3, 'e': 0.0001, 'i': np.deg2rad(0.1)},
181
'Molniya': {'a': 26600e3, 'e': 0.74, 'i': np.deg2rad(63.4)}
182
}
183
184
ground_tracks = {}
185
for name, config in orbit_configs.items():
186
lon, lat, T = compute_ground_track(
187
config['a'], config['e'], config['i'],
188
omega=0, Omega=0, n_orbits=2 if name != 'GEO' else 1
189
)
190
ground_tracks[name] = {'lon': lon, 'lat': lat, 'T': T}
191
192
# Bi-elliptic transfer comparison
193
def bielliptic_transfer(r1, r2, r_intermediate):
194
"""Compute bi-elliptic transfer delta-v."""
195
a1 = (r1 + r_intermediate) / 2
196
a2 = (r_intermediate + r2) / 2
197
198
v1 = circular_velocity(r1)
199
v1_t = np.sqrt(mu_earth * (2/r1 - 1/a1))
200
dv1 = abs(v1_t - v1)
201
202
v_int_1 = np.sqrt(mu_earth * (2/r_intermediate - 1/a1))
203
v_int_2 = np.sqrt(mu_earth * (2/r_intermediate - 1/a2))
204
dv2 = abs(v_int_2 - v_int_1)
205
206
v2_t = np.sqrt(mu_earth * (2/r2 - 1/a2))
207
v2 = circular_velocity(r2)
208
dv3 = abs(v2 - v2_t)
209
210
return dv1 + dv2 + dv3
211
212
# Compare Hohmann vs bi-elliptic for different intermediate radii
213
r_ratios = np.linspace(1.1, 20, 50)
214
hohmann_dv = transfers['GEO']['dv_total']
215
bielliptic_dvs = []
216
for ratio in r_ratios:
217
r_int = orbits['GEO'] * ratio
218
dv = bielliptic_transfer(orbits['LEO'], orbits['GEO'], r_int)
219
bielliptic_dvs.append(dv)
220
221
# Create comprehensive visualization
222
fig = plt.figure(figsize=(14, 12))
223
224
# Plot 1: Hohmann transfer orbits
225
ax1 = fig.add_subplot(2, 3, 1)
226
theta = np.linspace(0, 2*np.pi, 200)
227
228
# Earth
229
earth = plt.Circle((0, 0), R_earth/1e6, color='blue', alpha=0.6)
230
ax1.add_patch(earth)
231
232
# LEO
233
ax1.plot(orbits['LEO']/1e6 * np.cos(theta), orbits['LEO']/1e6 * np.sin(theta),
234
'g-', linewidth=2, label='LEO')
235
236
# GEO
237
ax1.plot(orbits['GEO']/1e6 * np.cos(theta), orbits['GEO']/1e6 * np.sin(theta),
238
'r-', linewidth=2, label='GEO')
239
240
# Transfer ellipse
241
a_t = transfers['GEO']['a_transfer']
242
e_t = (orbits['GEO'] - orbits['LEO']) / (orbits['GEO'] + orbits['LEO'])
243
r_t = a_t * (1 - e_t**2) / (1 + e_t * np.cos(theta))
244
theta_half = theta[theta <= np.pi]
245
r_t_half = a_t * (1 - e_t**2) / (1 + e_t * np.cos(theta_half))
246
ax1.plot(r_t_half/1e6 * np.cos(theta_half), r_t_half/1e6 * np.sin(theta_half),
247
'orange', linestyle='--', linewidth=2, label='Transfer')
248
249
ax1.set_xlim(-50, 50)
250
ax1.set_ylim(-50, 50)
251
ax1.set_xlabel('x (Mm)')
252
ax1.set_ylabel('y (Mm)')
253
ax1.set_title('Hohmann Transfer: LEO to GEO')
254
ax1.legend(fontsize=8)
255
ax1.set_aspect('equal')
256
ax1.grid(True, alpha=0.3)
257
258
# Plot 2: Delta-v comparison
259
ax2 = fig.add_subplot(2, 3, 2)
260
targets = list(transfers.keys())
261
dv1s = [transfers[t]['dv1'] for t in targets]
262
dv2s = [transfers[t]['dv2'] for t in targets]
263
264
x = np.arange(len(targets))
265
width = 0.35
266
ax2.bar(x - width/2, np.array(dv1s)/1000, width, label=r'$\Delta v_1$', color='steelblue')
267
ax2.bar(x + width/2, np.array(dv2s)/1000, width, label=r'$\Delta v_2$', color='coral')
268
ax2.set_xlabel('Target Orbit')
269
ax2.set_ylabel(r'$\Delta v$ (km/s)')
270
ax2.set_title('Delta-v Requirements from LEO')
271
ax2.set_xticks(x)
272
ax2.set_xticklabels(targets)
273
ax2.legend(fontsize=8)
274
ax2.grid(True, alpha=0.3, axis='y')
275
276
# Plot 3: Transfer times
277
ax3 = fig.add_subplot(2, 3, 3)
278
times = [transfers[t]['t_transfer']/3600 for t in targets]
279
colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(targets)))
280
bars = ax3.bar(targets, times, color=colors)
281
ax3.set_xlabel('Target Orbit')
282
ax3.set_ylabel('Transfer Time (hours)')
283
ax3.set_title('Hohmann Transfer Duration')
284
for bar, t in zip(bars, times):
285
if t > 100:
286
ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
287
f'{t/24:.1f}d', ha='center', va='bottom', fontsize=8)
288
else:
289
ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
290
f'{t:.1f}h', ha='center', va='bottom', fontsize=8)
291
ax3.grid(True, alpha=0.3, axis='y')
292
293
# Plot 4: Ground tracks
294
ax4 = fig.add_subplot(2, 3, 4)
295
track_colors = plt.cm.Set1(np.linspace(0, 0.8, len(ground_tracks)))
296
for (name, track), color in zip(ground_tracks.items(), track_colors):
297
# Split track at discontinuities
298
lon = track['lon']
299
lat = track['lat']
300
# Simple discontinuity detection
301
split_idx = np.where(np.abs(np.diff(lon)) > 180)[0] + 1
302
segments = np.split(np.arange(len(lon)), split_idx)
303
for j, seg in enumerate(segments):
304
if len(seg) > 1:
305
label = name if j == 0 else None
306
ax4.plot(lon[seg], lat[seg], color=color, linewidth=1.5,
307
alpha=0.7, label=label)
308
ax4.set_xlim(-180, 180)
309
ax4.set_ylim(-90, 90)
310
ax4.set_xlabel('Longitude (deg)')
311
ax4.set_ylabel('Latitude (deg)')
312
ax4.set_title('Ground Tracks (2 orbits)')
313
ax4.legend(fontsize=7, loc='lower left')
314
ax4.grid(True, alpha=0.3)
315
316
# Plot 5: Hohmann vs Bi-elliptic
317
ax5 = fig.add_subplot(2, 3, 5)
318
ax5.plot(r_ratios, np.array(bielliptic_dvs)/1000, 'b-', linewidth=2, label='Bi-elliptic')
319
ax5.axhline(y=hohmann_dv/1000, color='r', linestyle='--', linewidth=2, label='Hohmann')
320
ax5.set_xlabel(r'$r_{intermediate}/r_{GEO}$')
321
ax5.set_ylabel(r'Total $\Delta v$ (km/s)')
322
ax5.set_title('Hohmann vs Bi-elliptic Transfer')
323
ax5.legend(fontsize=8)
324
ax5.grid(True, alpha=0.3)
325
326
# Plot 6: Orbital period vs altitude
327
ax6 = fig.add_subplot(2, 3, 6)
328
altitudes = np.linspace(200, 40000, 100) * 1e3
329
radii = R_earth + altitudes
330
periods = 2 * np.pi * np.sqrt(radii**3 / mu_earth) / 3600 # hours
331
ax6.plot(altitudes/1e3, periods, 'b-', linewidth=2)
332
ax6.axhline(y=24, color='r', linestyle='--', alpha=0.7, label='24 hr (GEO)')
333
ax6.axhline(y=12, color='g', linestyle='--', alpha=0.7, label='12 hr (Molniya)')
334
ax6.set_xlabel('Altitude (km)')
335
ax6.set_ylabel('Orbital Period (hours)')
336
ax6.set_title('Period vs Altitude')
337
ax6.legend(fontsize=8)
338
ax6.grid(True, alpha=0.3)
339
ax6.set_xlim(0, 40000)
340
341
plt.tight_layout()
342
plt.savefig('orbital_mechanics_plot.pdf', bbox_inches='tight', dpi=150)
343
print(r'\begin{center}')
344
print(r'\includegraphics[width=\textwidth]{orbital_mechanics_plot.pdf}')
345
print(r'\end{center}')
346
plt.close()
347
348
# Key results
349
geo_transfer = transfers['GEO']
350
\end{pycode}
351
352
\section{Algorithm: State Vector to Orbital Elements}
353
354
\begin{algorithm}[H]
355
\SetAlgoLined
356
\KwIn{Position $\mathbf{r}$, velocity $\mathbf{v}$}
357
\KwOut{Classical orbital elements $(a, e, i, \Omega, \omega, \nu)$}
358
$\mathbf{h} \leftarrow \mathbf{r} \times \mathbf{v}$ \tcp{Angular momentum}
359
$\mathbf{n} \leftarrow \hat{k} \times \mathbf{h}$ \tcp{Node vector}
360
$\mathbf{e} \leftarrow \frac{1}{\mu}[(\|\mathbf{v}\|^2 - \frac{\mu}{r})\mathbf{r} - (\mathbf{r} \cdot \mathbf{v})\mathbf{v}]$\;
361
$a \leftarrow -\frac{\mu}{2\mathcal{E}}$ where $\mathcal{E} = \frac{v^2}{2} - \frac{\mu}{r}$\;
362
$e \leftarrow \|\mathbf{e}\|$\;
363
$i \leftarrow \arccos(h_z / \|\mathbf{h}\|)$\;
364
$\Omega \leftarrow \arccos(n_x / \|\mathbf{n}\|)$\;
365
$\omega \leftarrow \arccos(\mathbf{n} \cdot \mathbf{e} / (n \cdot e))$\;
366
$\nu \leftarrow \arccos(\mathbf{e} \cdot \mathbf{r} / (e \cdot r))$\;
367
\Return{$a, e, i, \Omega, \omega, \nu$}
368
\caption{Orbital Elements from State Vector}
369
\end{algorithm}
370
371
\section{Results and Discussion}
372
373
\subsection{Transfer Performance}
374
375
\begin{pycode}
376
# Generate results table
377
print(r'\begin{table}[h]')
378
print(r'\centering')
379
print(r'\caption{Hohmann Transfer Parameters from LEO}')
380
print(r'\begin{tabular}{lcccc}')
381
print(r'\toprule')
382
print(r'Target & $\Delta v_1$ & $\Delta v_2$ & Total $\Delta v$ & Transfer Time \\')
383
print(r' & (km/s) & (km/s) & (km/s) & \\')
384
print(r'\midrule')
385
for target in ['MEO', 'GEO', 'Lunar']:
386
t = transfers[target]
387
time_str = f"{t['t_transfer']/3600:.1f} hr" if t['t_transfer'] < 86400 else f"{t['t_transfer']/86400:.1f} days"
388
print(f"{target} & {t['dv1']/1000:.3f} & {t['dv2']/1000:.3f} & {t['dv_total']/1000:.3f} & {time_str} \\\\")
389
print(r'\bottomrule')
390
print(r'\end{tabular}')
391
print(r'\end{table}')
392
\end{pycode}
393
394
\begin{example}[LEO to GEO Transfer]
395
For a spacecraft in 400 km LEO transferring to GEO:
396
\begin{itemize}
397
\item Initial velocity: \py{f"{circular_velocity(orbits['LEO'])/1000:.3f}"} km/s
398
\item First burn (perigee): $\Delta v_1 = $ \py{f"{geo_transfer['dv1']/1000:.3f}"} km/s
399
\item Second burn (apogee): $\Delta v_2 = $ \py{f"{geo_transfer['dv2']/1000:.3f}"} km/s
400
\item Total $\Delta v$: \py{f"{geo_transfer['dv_total']/1000:.3f}"} km/s
401
\item Transfer time: \py{f"{geo_transfer['t_transfer']/3600:.2f}"} hours
402
\end{itemize}
403
\end{example}
404
405
\subsection{Ground Track Analysis}
406
407
\begin{pycode}
408
# Ground track table
409
print(r'\begin{table}[h]')
410
print(r'\centering')
411
print(r'\caption{Orbital Characteristics and Ground Coverage}')
412
print(r'\begin{tabular}{lccc}')
413
print(r'\toprule')
414
print(r'Orbit Type & Altitude & Period & Inclination \\')
415
print(r' & (km) & (min) & (deg) \\')
416
print(r'\midrule')
417
for name, config in orbit_configs.items():
418
alt = (config['a'] - R_earth) / 1000
419
period = ground_tracks[name]['T'] / 60
420
inc = np.rad2deg(config['i'])
421
print(f"{name} & {alt:.0f} & {period:.1f} & {inc:.1f} \\\\")
422
print(r'\bottomrule')
423
print(r'\end{tabular}')
424
print(r'\end{table}')
425
\end{pycode}
426
427
\begin{remark}[Ground Track Patterns]
428
\begin{itemize}
429
\item \textbf{LEO}: Ground track shifts westward each orbit due to Earth rotation
430
\item \textbf{Sun-synchronous}: Maintains constant local solar time at equator crossing
431
\item \textbf{GEO}: Appears stationary over a fixed longitude
432
\item \textbf{Molniya}: Figure-eight pattern with extended dwell time at high latitudes
433
\end{itemize}
434
\end{remark}
435
436
\subsection{Bi-elliptic Transfers}
437
For transfers with radius ratio $r_2/r_1 > 11.94$, the bi-elliptic transfer becomes more efficient than Hohmann. However, it requires significantly longer transfer time.
438
439
\section{Applications}
440
441
\subsection{Mission Design Considerations}
442
\begin{itemize}
443
\item \textbf{Communications}: GEO provides continuous coverage of specific regions
444
\item \textbf{Navigation}: MEO constellations (GPS, Galileo) balance coverage and signal strength
445
\item \textbf{Earth Observation}: Sun-synchronous LEO maintains consistent lighting
446
\item \textbf{High-latitude coverage}: Molniya orbits serve polar regions
447
\end{itemize}
448
449
\section{Limitations and Extensions}
450
451
\subsection{Model Limitations}
452
\begin{enumerate}
453
\item \textbf{Two-body}: Neglects perturbations (J2, drag, third-body)
454
\item \textbf{Impulsive burns}: Assumes instantaneous velocity changes
455
\item \textbf{Coplanar}: No plane change maneuvers included
456
\item \textbf{Simplified Kepler}: First-order solution for eccentric anomaly
457
\end{enumerate}
458
459
\subsection{Possible Extensions}
460
\begin{itemize}
461
\item Include J2 perturbation for realistic orbit propagation
462
\item Combined plane change and transfer maneuvers
463
\item Low-thrust trajectory optimization
464
\item Interplanetary patched conics
465
\end{itemize}
466
467
\section{Conclusion}
468
This analysis demonstrates fundamental orbital mechanics principles:
469
\begin{itemize}
470
\item Hohmann transfers provide minimum-fuel solutions for coplanar circular orbits
471
\item LEO-to-GEO requires \py{f"{geo_transfer['dv_total']/1000:.2f}"} km/s total delta-v
472
\item Ground track patterns depend strongly on orbital period and inclination
473
\item Bi-elliptic transfers can outperform Hohmann for large radius ratios
474
\end{itemize}
475
476
\section*{Further Reading}
477
\begin{itemize}
478
\item Vallado, D. A. (2013). \textit{Fundamentals of Astrodynamics and Applications}. Microcosm Press.
479
\item Bate, R. R., Mueller, D. D., \& White, J. E. (1971). \textit{Fundamentals of Astrodynamics}. Dover.
480
\item Curtis, H. D. (2020). \textit{Orbital Mechanics for Engineering Students}. Butterworth-Heinemann.
481
\end{itemize}
482
483
\end{document}
484
485