Home Estimating Upwelling from Surface Wind using Python
Post
Cancel

Estimating Upwelling from Surface Wind using Python

Upwelling region can be estimated from surface wind. This is done through Ekman Pumping Velocity, derivied from wind stress and wind stress curl. Following Simanjuntak and Lin (2022)1, this article will explain step by step calculation on how to derivied Ekman Pumping Velocity to determine upwelling region based on multidimensional gridded data.

Data

We will use data from BMKG-OFS, produced by Center for Marine Meteorology. This data contains $u$ and $v$ component of wind vector. It has $0.0625° \times 0.0625°$ spatial resolution and 3-hourly temporal resolution.

You can request the sample of the data by contacting me, or feel free to use your own data, you only need $u$ and $v$ component of wind vector in gridded field.

Surface Wind Stress ($\tau$)

The sea surface wind data can be converted into wind stress ($\tau$) based on the following equation.

\[\tau = \rho_a C_d U^{2}_{10}\]

where $\rho_a$ is the air density, $C_d$ is drag coeficient, and $U^{2}_{10}$ is wind speed at a 10m height.

Wind Stress Curl

Wind stress curl is calculated from wind stress vector component.

\[curl = \frac{\partial\tau_y}{\partial x} - \frac{\partial \tau_x}{\partial y}\]

Ekman Pumping Velocity

\[EPV = - \frac{curl}{\rho_w f}\]

where $\rho_w$ is the water density and $f$ is the coriolis parameter.

Scripting with Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np 
import xarray as xr 
from scipy.ndimage import gaussian_filter
from datetime import datetime, timedelta

class Upwelling:
    def __init__(self):
        self.__RHOA__  = 1.225
        self.__RHOW__  = 1028
        self.__DC__    = 1.25e-3
        self.__OMEGA__ = 7.29e-5

    def calc_wind_stress(self, dset:xr.Dataset, uwnd:str, vwnd:str) -> xr.Dataset:
        dset['uwnd_stress'] = (self.__RHOA__*self.__DC__*dset[uwnd])*(np.sqrt(dset[uwnd]**2 + dset[vwnd]**2))
        dset['vwnd_stress'] = (self.__RHOA__*self.__DC__*dset[vwnd])*(np.sqrt(dset[uwnd]**2 + dset[vwnd]**2))
        return dset 

    def calc_wind_curl(self, dset:xr.Dataset, uwnd_stress:str, vwnd_stress:str, lat:str) -> xr.Dataset:
        dy = 0.0625*111320 # adjust to your latitude resolution
        dx = 0.0625*111320 # adjust to your longitude resolution

        corioli = 2 * self.__OMEGA__ * np.sin(np.deg2rad(dset[lat]))
        dset['corioli'] = corioli.expand_dims({'time': dset.dims['time'], 'lon': dset.dims['lon']}, axis=(0, 2)) # adjust to your data, in this case the order is time-lat-lon
        d_tau_y_dx = np.gradient(dset[vwnd_stress], dx, axis=2)
        d_tau_x_dy = np.gradient(dset[uwnd_stress], dy, axis=1)
        wind_curl = (d_tau_y_dx - d_tau_x_dy)
        dset['wind_curl'] = (('time', 'lat', 'lon'), wind_curl)
        return dset

    def calc_epv(self, dset:xr.Dataset, wind_curl:str, corioli:str) -> xr.Dataset:
        epv = dset[wind_curl] / (self.__RHOW__ * dset[corioli])
        dset['epv'] = epv
        return dset

Execution and Plotting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class uwPlotter(plotter): 
    '''
    You can make your own plotter, this class is inherit from the other class (plotter) which contains hundreds of lines to plot the data
    '''
    def __init__(self):
        super().__init__()
        self.__TIMENOW__ = datetime.now()

    def gaussian_blur(self, dset, sigma=3):
        dset['epv'] = xr.where(np.isfinite(dset['epv']), dset['epv'], 0)
        dset['epv'] = xr.where((dset['epv'] > -0.00001) & (dset['epv'] < 0.00001), dset['epv'], 0)
        dset['epv'] = dset['epv']*100000
        blurred_data = gaussian_filter(dset['epv'].values, sigma=sigma)
        dset['epv'] = xr.DataArray(blurred_data, coords=dset['epv'].coords, dims=dset['epv'].dims)
        return dset
    
    def run_plot_uwp(self, baserun, tsel, ds, area_name, out_dir):
        ds = ds.isel(time=tsel)
        forecast = pd.to_datetime(ds.time.data)
        logging.info(f"======Running Plotter for {area_name} | baserun: {baserun} | forecast: {forecast}======")
        ds = self.gaussian_blur(ds)
        self.run_plot(
            ds=ds,
            var='epv',
            wilpel='wilpel',
            wilpel_name=area_name,
            map_area=area_name,
            out_dir=out_dir,
            baserun=baserun,
            forecast=forecast,
        )

The script above is a part of the production script to generate upwelling maps that can be seen through this link https://maritim.bmkg.go.id/ofs-static, select Model: Ina-flow and Parameter: Upwelling.

Sample of upwelling plot Sample of Upwelling maps

References

  1. Simanjuntak, F., & Lin, T. H. (2022). Monsoon effects on chlorophyll-a, sea surface temperature, and ekman dynamics variability along the southern coast of lesser Sunda islands and its relation to ENSO and IOD based on satellite observations. Remote Sensing, 14(7), 1682. https://doi.org/10.3390/rs14071682 

This post is licensed under CC BY 4.0 by the author.

Install WaveWatch3 versi 7.14

-

Comments powered by Disqus.