🌊 Ekman Dynamics in Coastal Oceans

Comprehensive analysis of wind-driven ocean currents and coastal upwelling

Ekman Dynamics Theory

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.

Key Parameters

Wind Stress (τ)

Units: N/m² or Pa

Description: Force per unit area exerted by wind on the ocean surface

Formula: τ = ρ_air × C_d × U₁₀²

Coriolis Parameter (f)

Units: s⁻¹

Description: Effect of Earth's rotation on moving fluids

Formula: f = 2Ω sin(φ)

Ekman Depth (D_E)

Units: m

Description: Depth over which wind influence extends

Formula: D_E = π√(2A_z/f)

Mixed Layer Depth (MLD)

Units: m

Description: Depth of the well-mixed surface layer

Typical Range: 10-200 m

Eddy Viscosity (A_z)

Units: m²/s

Description: Vertical mixing coefficient

Typical Range: 0.01-0.1 m²/s

Ekman Transport (M_E)

Units: m²/s

Description: Net horizontal transport in Ekman layer

Direction: 90° to the right of wind (NH)

📊 Typical Coastal Ocean Values

  • Wind Speed: 5-15 m/s (coastal regions)
  • Wind Stress: 0.05-0.3 N/m²
  • Coriolis Parameter: 1×10⁻⁴ s⁻¹ (mid-latitudes)
  • Mixed Layer Depth: 20-100 m (seasonal variation)
  • Ekman Depth: 50-200 m
  • Upwelling Velocity: 1-10 m/day

Fundamental Equations

1. Wind Stress Calculation

$$\vec{\tau} = \rho_{air} C_d |\vec{U}_{10}| \vec{U}_{10}$$

Where: ρ_air = 1.225 kg/m³, C_d = 1.2×10⁻³ (drag coefficient), U₁₀ = wind speed at 10m

2. Coriolis Parameter

$$f = 2\Omega \sin(\phi)$$

Where: Ω = 7.27×10⁻⁵ rad/s (Earth's rotation), φ = latitude

3. Ekman Spiral Equations

$$\frac{\partial u}{\partial t} - fv = \frac{1}{\rho} \frac{\partial \tau_x}{\partial z} + A_z \frac{\partial^2 u}{\partial z^2}$$ $$\frac{\partial v}{\partial t} + fu = \frac{1}{\rho} \frac{\partial \tau_y}{\partial z} + A_z \frac{\partial^2 v}{\partial z^2}$$

4. Ekman Transport

$$M_x = \frac{\tau_y}{\rho f}, \quad M_y = -\frac{\tau_x}{\rho f}$$

5. Ekman Depth

$$D_E = \pi \sqrt{\frac{2A_z}{f}}$$

6. Coastal Upwelling Velocity

$$w = \frac{\tau_{\parallel}}{\rho f L}$$

Where: τ_∥ = alongshore wind stress, L = cross-shore length scale

7. Ekman Pumping

$$w_E = \frac{1}{\rho f} \left( \frac{\partial \tau_y}{\partial x} - \frac{\partial \tau_x}{\partial y} \right)$$

Python Implementation

Complete Ekman Dynamics Calculator
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")
Advanced Visualization Code
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")

Interactive Ekman Calculator

Calculate Ekman dynamics parameters using real oceanographic values:

🌍 Pre-set Locations

Interactive Ekman Spiral Visualization

Explore the 3D Ekman spiral at latitude -36° (Southern Hemisphere) with interactive Plotly visualization:

🌊 Spiral Parameters

📊 Understanding the Ekman Spiral

  • Surface Current: Flows 45° to the left of wind (Southern Hemisphere)
  • Spiral Structure: Current direction rotates clockwise with depth
  • Velocity Decay: Current speed decreases exponentially with depth
  • Net Transport: 90° to the left of wind direction (Southern Hemisphere)
  • Ekman Depth: Depth where current is ~4% of surface value
Python Code for Ekman Spiral at -36° Latitude
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")