Comprehensive analysis of wind-driven ocean currents and coastal upwelling
Ekman dynamics describes the motion of ocean water driven by wind stress, accounting for the Coriolis effect. In coastal regions, this creates complex circulation patterns including upwelling and downwelling.
Units: N/m² or Pa
Description: Force per unit area exerted by wind on the ocean surface
Formula: τ = ρ_air × C_d × U₁₀²
Units: s⁻¹
Description: Effect of Earth's rotation on moving fluids
Formula: f = 2Ω sin(φ)
Units: m
Description: Depth over which wind influence extends
Formula: D_E = π√(2A_z/f)
Units: m
Description: Depth of the well-mixed surface layer
Typical Range: 10-200 m
Units: m²/s
Description: Vertical mixing coefficient
Typical Range: 0.01-0.1 m²/s
Units: m²/s
Description: Net horizontal transport in Ekman layer
Direction: 90° to the right of wind (NH)
Where: ρ_air = 1.225 kg/m³, C_d = 1.2×10⁻³ (drag coefficient), U₁₀ = wind speed at 10m
Where: Ω = 7.27×10⁻⁵ rad/s (Earth's rotation), φ = latitude
Where: τ_∥ = alongshore wind stress, L = cross-shore length scale
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
import pandas as pd
class EkmanDynamics:
"""
Comprehensive Ekman dynamics calculator for coastal oceans
"""
def __init__(self):
# Physical constants
self.rho_air = 1.225 # kg/m³
self.rho_water = 1025 # kg/m³
self.omega = 7.27e-5 # rad/s (Earth's rotation)
self.cd = 1.2e-3 # drag coefficient
def wind_stress(self, u10, v10=0):
"""
Calculate wind stress from 10m wind speed
Parameters:
u10, v10: wind components at 10m height (m/s)
Returns:
tau_x, tau_y: wind stress components (N/m²)
"""
wind_speed = np.sqrt(u10**2 + v10**2)
tau_x = self.rho_air * self.cd * wind_speed * u10
tau_y = self.rho_air * self.cd * wind_speed * v10
return tau_x, tau_y
def coriolis_parameter(self, latitude):
"""
Calculate Coriolis parameter
Parameters:
latitude: latitude in degrees
Returns:
f: Coriolis parameter (s⁻¹)
"""
return 2 * self.omega * np.sin(np.radians(latitude))
def ekman_depth(self, Az, f):
"""
Calculate Ekman depth
Parameters:
Az: vertical eddy viscosity (m²/s)
f: Coriolis parameter (s⁻¹)
Returns:
DE: Ekman depth (m)
"""
return np.pi * np.sqrt(2 * Az / abs(f))
def ekman_transport(self, tau_x, tau_y, f):
"""
Calculate Ekman transport
Parameters:
tau_x, tau_y: wind stress components (N/m²)
f: Coriolis parameter (s⁻¹)
Returns:
Mx, My: Ekman transport components (m²/s)
"""
Mx = tau_y / (self.rho_water * f)
My = -tau_x / (self.rho_water * f)
return Mx, My
def ekman_spiral(self, z, tau_x, tau_y, f, Az):
"""
Calculate Ekman spiral velocity profile
Parameters:
z: depth array (m, negative downward)
tau_x, tau_y: surface wind stress (N/m²)
f: Coriolis parameter (s⁻¹)
Az: vertical eddy viscosity (m²/s)
Returns:
u, v: velocity components (m/s)
"""
# Ekman depth scale
delta = np.sqrt(2 * Az / abs(f))
# Surface velocity components
u0 = tau_x / (self.rho_water * f * delta)
v0 = tau_y / (self.rho_water * f * delta)
# Ekman spiral solution
exp_factor = np.exp(z / delta)
cos_factor = np.cos(z / delta)
sin_factor = np.sin(z / delta)
if f > 0: # Northern Hemisphere
u = u0 * exp_factor * cos_factor
v = v0 * exp_factor * sin_factor
else: # Southern Hemisphere
u = u0 * exp_factor * cos_factor
v = -v0 * exp_factor * sin_factor
return u, v
def coastal_upwelling_velocity(self, tau_alongshore, f, L=50000):
"""
Calculate coastal upwelling velocity
Parameters:
tau_alongshore: alongshore wind stress (N/m²)
f: Coriolis parameter (s⁻¹)
L: cross-shore length scale (m)
Returns:
w: upwelling velocity (m/s)
"""
return tau_alongshore / (self.rho_water * f * L)
# Example with real oceanographic data
def example_california_coast():
"""
Example calculation for California coast upwelling
"""
ekman = EkmanDynamics()
# Real data from California coast (summer upwelling season)
print("🌊 California Coast Upwelling Analysis")
print("=" * 50)
# Location: Monterey Bay (36.6°N, 121.9°W)
latitude = 36.6
# Typical summer conditions
wind_speed = 8.0 # m/s (northwesterly)
wind_direction = 315 # degrees (from NW)
# Convert wind to components
u10 = wind_speed * np.cos(np.radians(wind_direction - 90))
v10 = wind_speed * np.sin(np.radians(wind_direction - 90))
# Calculate wind stress
tau_x, tau_y = ekman.wind_stress(u10, v10)
# Coriolis parameter
f = ekman.coriolis_parameter(latitude)
# Typical values
Az = 0.05 # m²/s
mixed_layer_depth = 30 # m
# Calculations
DE = ekman.ekman_depth(Az, f)
Mx, My = ekman.ekman_transport(tau_x, tau_y, f)
# Alongshore wind stress (positive = upwelling favorable)
tau_alongshore = -tau_y # Northward wind creates upwelling
w_upwelling = ekman.coastal_upwelling_velocity(tau_alongshore, f)
# Results
print(f"Wind Speed: {wind_speed:.1f} m/s from {wind_direction}°")
print(f"Wind Stress: τx = {tau_x:.4f}, τy = {tau_y:.4f} N/m²")
print(f"Coriolis Parameter: f = {f:.2e} s⁻¹")
print(f"Ekman Depth: {DE:.1f} m")
print(f"Mixed Layer Depth: {mixed_layer_depth} m")
print(f"Ekman Transport: Mx = {Mx:.3f}, My = {My:.3f} m²/s")
print(f"Upwelling Velocity: {w_upwelling*86400:.2f} m/day")
# Create Ekman spiral plot
z = np.linspace(0, -200, 100)
u, v = ekman.ekman_spiral(z, tau_x, tau_y, f, Az)
return {
'depth': z,
'u_velocity': u,
'v_velocity': v,
'ekman_depth': DE,
'upwelling_velocity': w_upwelling * 86400, # m/day
'ekman_transport': (Mx, My)
}
def example_peru_coast():
"""
Example calculation for Peru coast upwelling
"""
ekman = EkmanDynamics()
print("\n🌊 Peru Coast Upwelling Analysis")
print("=" * 50)
# Location: Off Lima, Peru (12°S, 77°W)
latitude = -12.0 # Southern Hemisphere
# Typical trade wind conditions
wind_speed = 6.5 # m/s (southeasterly)
wind_direction = 135 # degrees (from SE)
# Convert to components
u10 = wind_speed * np.cos(np.radians(wind_direction - 90))
v10 = wind_speed * np.sin(np.radians(wind_direction - 90))
# Calculations
tau_x, tau_y = ekman.wind_stress(u10, v10)
f = ekman.coriolis_parameter(latitude)
Az = 0.03 # m²/s (lower mixing)
DE = ekman.ekman_depth(Az, f)
Mx, My = ekman.ekman_transport(tau_x, tau_y, f)
# For Southern Hemisphere, southward wind creates upwelling
tau_alongshore = tau_y
w_upwelling = ekman.coastal_upwelling_velocity(tau_alongshore, f)
print(f"Wind Speed: {wind_speed:.1f} m/s from {wind_direction}°")
print(f"Wind Stress: τx = {tau_x:.4f}, τy = {tau_y:.4f} N/m²")
print(f"Coriolis Parameter: f = {f:.2e} s⁻¹")
print(f"Ekman Depth: {DE:.1f} m")
print(f"Ekman Transport: Mx = {Mx:.3f}, My = {My:.3f} m²/s")
print(f"Upwelling Velocity: {w_upwelling*86400:.2f} m/day")
def seasonal_analysis():
"""
Analyze seasonal variations in Ekman dynamics
"""
ekman = EkmanDynamics()
print("\n📅 Seasonal Ekman Dynamics Analysis")
print("=" * 50)
# Monthly data for California coast
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
# Typical monthly wind speeds (m/s) and MLD (m)
wind_speeds = [5.2, 5.8, 6.5, 7.2, 8.1, 8.8,
9.2, 8.9, 7.8, 6.9, 5.9, 5.1]
mld_values = [85, 95, 75, 55, 35, 25,
20, 22, 30, 45, 65, 80]
latitude = 36.6
f = ekman.coriolis_parameter(latitude)
results = []
for i, (month, ws, mld) in enumerate(zip(months, wind_speeds, mld_values)):
# Assume northwesterly winds (upwelling favorable)
u10 = ws * np.cos(np.radians(315 - 90))
v10 = ws * np.sin(np.radians(315 - 90))
tau_x, tau_y = ekman.wind_stress(u10, v10)
tau_alongshore = -tau_y
w_upwelling = ekman.coastal_upwelling_velocity(tau_alongshore, f)
results.append({
'Month': month,
'Wind_Speed': ws,
'MLD': mld,
'Upwelling_Velocity': w_upwelling * 86400,
'Wind_Stress': np.sqrt(tau_x**2 + tau_y**2)
})
df = pd.DataFrame(results)
print(df.to_string(index=False, float_format='%.2f'))
# Run examples
if __name__ == "__main__":
# California coast example
ca_results = example_california_coast()
# Peru coast example
example_peru_coast()
# Seasonal analysis
seasonal_analysis()
print("\n✅ Analysis complete! Check the results above.")
print("💡 Tip: Positive upwelling velocity indicates water rising to surface")
print("🌊 Typical upwelling rates: 1-10 m/day in active upwelling regions")
import matplotlib.pyplot as plt
import numpy as np
def plot_ekman_spiral(depth, u, v, title="Ekman Spiral"):
"""
Plot Ekman spiral velocity profile
"""
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
# Velocity profiles
ax1.plot(u, depth, 'b-', linewidth=2, label='u (eastward)')
ax1.plot(v, depth, 'r-', linewidth=2, label='v (northward)')
ax1.set_xlabel('Velocity (m/s)')
ax1.set_ylabel('Depth (m)')
ax1.set_title('Velocity Profiles')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Hodograph (spiral view)
ax2.plot(u, v, 'g-', linewidth=2)
ax2.plot(u[0], v[0], 'ro', markersize=8, label='Surface')
ax2.plot(u[-1], v[-1], 'bo', markersize=8, label='Deep')
ax2.set_xlabel('u velocity (m/s)')
ax2.set_ylabel('v velocity (m/s)')
ax2.set_title('Hodograph (Spiral View)')
ax2.legend()
ax2.grid(True, alpha=0.3)
ax2.axis('equal')
# Speed profile
speed = np.sqrt(u**2 + v**2)
ax3.plot(speed, depth, 'purple', linewidth=2)
ax3.set_xlabel('Speed (m/s)')
ax3.set_ylabel('Depth (m)')
ax3.set_title('Speed Profile')
ax3.grid(True, alpha=0.3)
plt.suptitle(title, fontsize=16)
plt.tight_layout()
plt.show()
def plot_seasonal_upwelling():
"""
Plot seasonal upwelling patterns
"""
months = np.arange(1, 13)
upwelling = [2.1, 2.8, 3.9, 5.2, 7.1, 8.9,
9.8, 9.2, 7.5, 5.8, 3.9, 2.5]
plt.figure(figsize=(12, 6))
plt.plot(months, upwelling, 'o-', linewidth=3, markersize=8, color='steelblue')
plt.fill_between(months, upwelling, alpha=0.3, color='lightblue')
plt.xlabel('Month')
plt.ylabel('Upwelling Velocity (m/day)')
plt.title('Seasonal Upwelling Cycle - California Coast')
plt.xticks(months, ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Example usage:
# ca_results = example_california_coast()
# plot_ekman_spiral(ca_results['depth'], ca_results['u_velocity'],
# ca_results['v_velocity'], "California Coast Ekman Spiral")
Calculate Ekman dynamics parameters using real oceanographic values:
Explore the 3D Ekman spiral at latitude -36° (Southern Hemisphere) with interactive Plotly visualization:
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def ekman_spiral_southern_hemisphere():
"""
Calculate and plot Ekman spiral for latitude -36° (Southern Hemisphere)
Example: Off Argentina coast or Southern Ocean
"""
# Parameters for latitude -36°
latitude = -36.0 # degrees
wind_speed = 10.0 # m/s
wind_direction = 270 # degrees (westerly wind)
Az = 0.05 # m²/s (eddy viscosity)
# Physical constants
rho_air = 1.225 # kg/m³
rho_water = 1025 # kg/m³
omega = 7.27e-5 # rad/s
cd = 1.2e-3 # drag coefficient
# Calculate Coriolis parameter (negative in SH)
f = 2 * omega * np.sin(np.radians(latitude))
print(f"Coriolis parameter f = {f:.2e} s⁻¹")
# Wind stress calculation
u_wind = wind_speed * np.cos(np.radians(wind_direction - 90))
v_wind = wind_speed * np.sin(np.radians(wind_direction - 90))
tau_x = rho_air * cd * wind_speed * u_wind
tau_y = rho_air * cd * wind_speed * v_wind
print(f"Wind stress: τx = {tau_x:.4f}, τy = {tau_y:.4f} N/m²")
# Ekman depth scale
delta = np.sqrt(2 * Az / abs(f))
ekman_depth = np.pi * delta
print(f"Ekman depth scale δ = {delta:.1f} m")
print(f"Ekman depth DE = {ekman_depth:.1f} m")
# Depth array (negative downward)
z = np.linspace(0, -300, 150)
# Surface velocity components
u0 = tau_x / (rho_water * abs(f) * delta)
v0 = tau_y / (rho_water * abs(f) * delta)
print(f"Surface velocity: u0 = {u0:.3f}, v0 = {v0:.3f} m/s")
# Ekman spiral solution for Southern Hemisphere
exp_factor = np.exp(z / delta)
cos_factor = np.cos(z / delta)
sin_factor = np.sin(z / delta)
# Southern Hemisphere: clockwise rotation with depth
u = u0 * exp_factor * cos_factor
v = -v0 * exp_factor * sin_factor # Note the negative sign for SH
# Calculate current direction and speed
current_speed = np.sqrt(u**2 + v**2)
current_direction = np.degrees(np.arctan2(v, u))
# Surface current direction (45° to left of wind in SH)
surface_current_dir = current_direction[0]
wind_current_angle = surface_current_dir - wind_direction
if wind_current_angle > 180:
wind_current_angle -= 360
elif wind_current_angle < -180:
wind_current_angle += 360
print(f"Surface current direction: {surface_current_dir:.1f}°")
print(f"Angle relative to wind: {wind_current_angle:.1f}°")
return z, u, v, current_speed, current_direction, ekman_depth
def create_3d_spiral_plot(z, u, v):
"""
Create interactive 3D Ekman spiral plot using Plotly
"""
# Create subplots
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('3D Ekman Spiral', 'Velocity Profiles',
'Hodograph (Plan View)', 'Current Speed vs Depth'),
specs=[[{"type": "scene", "colspan": 2}, None],
[{"type": "xy"}, {"type": "xy"}]],
vertical_spacing=0.08
)
# 3D Spiral plot
fig.add_trace(
go.Scatter3d(
x=u, y=v, z=z,
mode='lines+markers',
line=dict(color=z, colorscale='Viridis', width=6),
marker=dict(size=3, color=z, colorscale='Viridis'),
name='Ekman Spiral',
hovertemplate='Depth: %{z:.1f} m
' +
'u velocity: %{x:.3f} m/s
' +
'v velocity: %{y:.3f} m/s '
),
row=1, col=1
)
# Add surface and deep markers
fig.add_trace(
go.Scatter3d(
x=[u[0]], y=[v[0]], z=[z[0]],
mode='markers',
marker=dict(size=10, color='red'),
name='Surface',
showlegend=False
),
row=1, col=1
)
fig.add_trace(
go.Scatter3d(
x=[u[-1]], y=[v[-1]], z=[z[-1]],
mode='markers',
marker=dict(size=8, color='blue'),
name='Deep',
showlegend=False
),
row=1, col=1
)
# Velocity profiles
fig.add_trace(
go.Scatter(x=u, y=z, mode='lines', name='u (eastward)',
line=dict(color='blue', width=3)),
row=2, col=1
)
fig.add_trace(
go.Scatter(x=v, y=z, mode='lines', name='v (northward)',
line=dict(color='red', width=3)),
row=2, col=1
)
# Hodograph
fig.add_trace(
go.Scatter(x=u, y=v, mode='lines+markers', name='Hodograph',
line=dict(color='green', width=3),
marker=dict(size=4, color=z, colorscale='Plasma')),
row=2, col=2
)
# Add surface and deep points to hodograph
fig.add_trace(
go.Scatter(x=[u[0]], y=[v[0]], mode='markers', name='Surface',
marker=dict(size=12, color='red', symbol='star')),
row=2, col=2
)
fig.add_trace(
go.Scatter(x=[u[-1]], y=[v[-1]], mode='markers', name='Deep',
marker=dict(size=10, color='blue', symbol='circle')),
row=2, col=2
)
# Update layout
fig.update_layout(
title='Ekman Spiral Analysis - Latitude -36° (Southern Hemisphere)',
height=800,
showlegend=True
)
# Update 3D scene
fig.update_scenes(
xaxis_title='u velocity (m/s)',
yaxis_title='v velocity (m/s)',
zaxis_title='Depth (m)',
camera=dict(eye=dict(x=1.5, y=1.5, z=0.5))
)
# Update 2D subplots
fig.update_xaxes(title_text='Velocity (m/s)', row=2, col=1)
fig.update_yaxes(title_text='Depth (m)', row=2, col=1)
fig.update_xaxes(title_text='u velocity (m/s)', row=2, col=2)
fig.update_yaxes(title_text='v velocity (m/s)', row=2, col=2)
return fig
# Run the analysis
if __name__ == "__main__":
print("🌊 Ekman Spiral Analysis for Latitude -36°")
print("=" * 50)
z, u, v, speed, direction, ekman_depth = ekman_spiral_southern_hemisphere()
# Create and show the plot
fig = create_3d_spiral_plot(z, u, v)
fig.show()
print(f"\n📊 Key Results:")
print(f"Ekman depth: {ekman_depth:.1f} m")
print(f"Surface current speed: {speed[0]:.3f} m/s")
print(f"Current at Ekman depth: {speed[int(len(speed)*0.63)]:.3f} m/s")
print(f"Deep current (300m): {speed[-1]:.4f} m/s")