codexwind-correction-progress.py
wind-correction-progress.py
"""Original wind-triangle and ground-progress diagram; writes cwd PNG.
Tested with Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
"""
import numpy as np
import matplotlib.pyplot as plt
V, cross, head = 40.0, 8.0, 12.0
air = np.array([-cross, np.sqrt(V**2-cross**2)])
wind = np.array([cross, -head])
ground = air + wind
threshold = np.sqrt(V**2-head**2)
assert np.isclose(np.linalg.norm(air), V)
assert np.isclose(ground[0], 0) and ground[1] > 0
assert np.isclose(np.sqrt(V**2-threshold**2)-head, 0)
fig, (ax, bx) = plt.subplots(1, 2, figsize=(7.6, 4.4), dpi=100, facecolor='white', gridspec_kw={'width_ratios':[1,1.35]})
for origin, vector, color in [(np.zeros(2), air, '#235789'), (air, wind, '#b45222'), (np.zeros(2), ground, '#4c7c36')]:
ax.annotate('', xy=origin+vector, xytext=origin, arrowprops={'arrowstyle':'->','lw':2.2,'color':color})
ax.text(-10.5, 18, 'Air-relative\n40.0 m/s', color='#235789', fontsize=8, ha='right')
ax.text(-6, 40.5, 'Wind: 8 east, 12 south', color='#b45222', fontsize=8, ha='center')
ax.text(1.5, 17, 'Ground\n27.19 m/s\ndue north', color='#4c7c36', fontsize=8)
ax.scatter([0], [0], s=15, color='black')
ax.set(xlim=(-22, 18), ylim=(-2, 46), xlabel='Eastward velocity (m/s)', ylabel='Northward velocity (m/s)', title='Vector closure')
ax.set_aspect('equal', adjustable='box')
ax.grid(alpha=.2)
c = np.linspace(0, V, 500)
progress = np.sqrt(V**2-c**2)-head
bx.axhspan(-15, 0, color='#fff0e9')
bx.axvspan(V, 44, color='#eeeeee')
bx.plot(c, progress, color='#235789', lw=2)
bx.axhline(0, color='black', lw=.8)
bx.scatter([cross, threshold], [ground[1], 0], s=35, color=['#4c7c36','#b45222'], zorder=5)
bx.annotate(f'Zero progress\n{threshold:.2f} m/s crosswind', (threshold,0), (12,-12), fontsize=8, arrowprops={'arrowstyle':'->'})
bx.text(21, 20, '40 m/s true airspeed\n12 m/s headwind', fontsize=8)
bx.text(41.7, 10, 'No cancellation', rotation=90, ha='center', fontsize=8)
bx.set(xlim=(0,44), ylim=(-15,32), xlabel='Rightward wind component (m/s)', ylabel='Along-track groundspeed (m/s)', title='Cancellation and progress limits')
bx.grid(alpha=.2)
fig.suptitle('Crosswind cancellation and forward ground progress', fontsize=12)
fig.text(.5,.02,'Original uniform-wind, horizontal-flight model; the left velocity triangle uses equal scales.',ha='center',fontsize=8)
fig.tight_layout(rect=(0,.05,1,.94))
fig.savefig('wind-correction-progress.png', facecolor='white')
print({'correction_degrees':-np.degrees(np.arcsin(cross/V)), 'groundspeed':ground[1], 'progress_boundary_crosswind':threshold})