3.4 Local Winds

Local winds are driven by local heating differences rather than large-scale pressure systems. They include sea breezes, mountain-valley winds, and thermally-driven circulations.

Sea/Land Breeze

Sea Breeze (Day)

Land heats faster → rising air over land → low pressure → onshore flow from sea

Typically develops mid-morning, peaks afternoon

Land Breeze (Night)

Land cools faster → sinking air over land → high pressure → offshore flow

Weaker than sea breeze, develops after midnight

Mountain-Valley Winds

Anabatic (Upslope) Wind

Daytime: Sun heats mountain slopes → warm air rises → upslope flow

Katabatic (Downslope) Wind

Nighttime: Radiative cooling on slopes → dense cold air drains downhill

Can be very strong over ice sheets (Antarctica: 300 km/h!)

Foehn & Chinook Winds

Warm, dry winds on the lee side of mountains. Air forced over mountains loses moisture on the windward side (precipitation), then warms at the dry adiabatic rate descending.

Foehn

Alps (Europe)

Chinook

Rocky Mountains (N. America)

Santa Ana

Southern California

Python: Sea Breeze Circulation

#!/usr/bin/env python3
"""
local_winds.py - Model sea breeze circulation

Run: python3 local_winds.py
Requires: pip install numpy matplotlib
"""
import numpy as np
import matplotlib.pyplot as plt

# Simple 2D sea breeze model
# Based on differential heating between land and water

def sea_breeze_circulation(t_hour, land_sea_contrast=10):
    """
    Simple model of sea breeze circulation strength
    t_hour: time of day (hours)
    land_sea_contrast: max temperature difference (K)
    """
    # Land-sea temperature difference varies with time
    # Max around 2-3 PM, reverses at night
    phase = (t_hour - 14) * 2 * np.pi / 24
    delta_T = land_sea_contrast * np.cos(phase)
    return delta_T

# Create time array
hours = np.linspace(0, 24, 100)
delta_T = sea_breeze_circulation(hours)

# Create a simplified 2D circulation diagram
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Temperature difference over 24 hours
ax1 = axes[0, 0]
ax1.plot(hours, delta_T, 'r-', linewidth=2)
ax1.axhline(y=0, color='gray', linestyle='--')
ax1.fill_between(hours, 0, delta_T, where=(delta_T > 0), alpha=0.3, color='red', label='Land warmer')
ax1.fill_between(hours, 0, delta_T, where=(delta_T < 0), alpha=0.3, color='blue', label='Sea warmer')
ax1.set_xlabel('Hour of Day', fontsize=12)
ax1.set_ylabel('T(land) - T(sea) (K)', fontsize=12)
ax1.set_title('Land-Sea Temperature Difference', fontsize=14)
ax1.set_xlim(0, 24)
ax1.set_xticks(np.arange(0, 25, 3))
ax1.legend()
ax1.grid(True, alpha=0.3)

# Sea breeze schematic (afternoon)
ax2 = axes[0, 1]
x = np.linspace(-50, 50, 100)
z = np.linspace(0, 2000, 50)
X, Z = np.meshgrid(x, z)

# Simplified stream function for sea breeze
# Flow from sea to land at surface, return aloft
psi = np.sin(np.pi * X / 50) * np.exp(-Z / 800) * np.sign(50 - np.abs(X))

# Velocity components
u = np.gradient(psi, z[1]-z[0], axis=0)  # horizontal
w = -np.gradient(psi, x[1]-x[0], axis=1)  # vertical

ax2.streamplot(X, Z, u, w, color='blue', density=1.5)
ax2.axvline(x=0, color='brown', linewidth=3, label='Coastline')
ax2.fill_between(x[x >= 0], 0, -100, color='brown', alpha=0.3)
ax2.fill_between(x[x < 0], 0, -100, color='blue', alpha=0.3)
ax2.set_xlabel('Distance from coast (km)', fontsize=12)
ax2.set_ylabel('Height (m)', fontsize=12)
ax2.set_title('Sea Breeze Circulation (Afternoon)', fontsize=14)
ax2.set_ylim(0, 2000)
ax2.text(-25, 1500, 'SEA', fontsize=14, ha='center', color='blue')
ax2.text(25, 1500, 'LAND', fontsize=14, ha='center', color='brown')
ax2.annotate('', xy=(15, 200), xytext=(-15, 200),
             arrowprops=dict(arrowstyle='->', color='green', lw=3))
ax2.text(0, 300, 'Surface flow', ha='center', color='green', fontsize=10)

# Mountain valley wind schematic
ax3 = axes[1, 0]
# Create mountain profile
x_mtn = np.linspace(-50, 50, 200)
mountain = 2000 * np.exp(-x_mtn**2 / 200)

ax3.fill_between(x_mtn, 0, mountain, color='brown', alpha=0.5)
ax3.plot(x_mtn, mountain, 'k-', linewidth=2)

# Daytime upslope arrows
for x0 in [-30, -20, 20, 30]:
    ax3.annotate('', xy=(x0, mountain[np.argmin(np.abs(x_mtn-x0))]+200),
                 xytext=(x0, 200),
                 arrowprops=dict(arrowstyle='->', color='red', lw=2))

ax3.set_xlabel('Distance (km)', fontsize=12)
ax3.set_ylabel('Elevation (m)', fontsize=12)
ax3.set_title('Anabatic (Upslope) Wind - Daytime', fontsize=14)
ax3.set_ylim(0, 3000)
ax3.text(0, 2500, 'Warm air rises', ha='center', color='red', fontsize=10)

# Foehn effect
ax4 = axes[1, 1]
x_foehn = np.linspace(0, 100, 200)
mtn_profile = 3000 * np.exp(-(x_foehn-50)**2 / 200)

# Temperature profile
T_windward = 20 - 6 * mtn_profile / 1000  # moist adiabatic up
T_lee = T_windward.copy()
peak_idx = np.argmax(mtn_profile)
# Lee side: dry adiabatic descent (faster warming)
for i in range(peak_idx, len(x_foehn)):
    dz = (mtn_profile[i-1] - mtn_profile[i]) / 1000
    T_lee[i] = T_lee[i-1] + 9.8 * dz

ax4.fill_between(x_foehn, 0, mtn_profile, color='brown', alpha=0.3)
ax4.plot(x_foehn, mtn_profile, 'k-', linewidth=2)
ax4_twin = ax4.twinx()
ax4_twin.plot(x_foehn, T_windward, 'b--', linewidth=2, label='Windward (moist)')
ax4_twin.plot(x_foehn, T_lee, 'r-', linewidth=2, label='Lee (Foehn)')
ax4_twin.set_ylabel('Temperature (°C)', color='red', fontsize=12)
ax4.set_xlabel('Distance (km)', fontsize=12)
ax4.set_ylabel('Elevation (m)', fontsize=12)
ax4.set_title('Foehn Effect', fontsize=14)
ax4_twin.legend(loc='upper right')
ax4.annotate('', xy=(70, 1500), xytext=(30, 1500),
             arrowprops=dict(arrowstyle='->', color='purple', lw=3))
ax4.text(50, 1700, 'Prevailing wind', ha='center', color='purple', fontsize=10)

plt.tight_layout()
plt.savefig('local_winds.png', dpi=150, bbox_inches='tight')
plt.show()

print("Local Wind Types:")
print("  Sea Breeze: 5-10 m/s, penetrates 20-50 km inland")
print("  Land Breeze: 2-5 m/s, weaker than sea breeze")
print("  Katabatic: can exceed 50 m/s over ice sheets")
print("  Foehn/Chinook: can raise temperature 20°C in minutes")