Temperature, Salinity, and StratificationΒΆ

Now that we have learned the basic thermodynamics of seawater and understood how temperature and salinity combine to determine the stratifcation of water columns, we are ready to examine the global patterns of temperature, salinity and stratification. These patterns are fascinating and reveal much about the underlying ocean circulation. We will also learn how to identify distinct water masses both in geographic space and in temperature / salinity coordinates.

import xarray as xr
from matplotlib import pyplot as plt
import hvplot.xarray
import gsw
import numpy as np
import holoviews as hv
from IPython.display import Image
hv.extension('bokeh')
plt.rcParams['figure.figsize'] = (12,7)

Data Source: World Ocean AtlasΒΆ

We will use data from the NOAA World Ocean Atlas, stored on the IRI Data Library at http://iridl.ldeo.columbia.edu/SOURCES/.NOAA/.NODC/.WOA09/.

World Ocean Atlas 2009 (WOA09) is a set of objectively analyzed (1Β° grid) climatological fields of in situ temperature, salinity, dissolved oxygen, Apparent Oxygen Utilization (AOU), percent oxygen saturation, phosphate, silicate, and nitrate at standard depth levels for annual, seasonal, and monthly compositing periods for the World Ocean. It also includes associated statistical fields of observed oceanographic profile data interpolated to standard depth levels on both 1Β° and 5Β° grids .

# basin masks
woa_mask = xr.open_dataset('http://iridl.ldeo.columbia.edu/SOURCES/.NOAA/.NODC/.WOA09/'
                             '.Masks/.basin/dods/').rename({'X':'lon', 'Y':'lat', 'Z': 'depth'})
basin_names = woa_mask.basin.attrs['CLIST'].split('\n')
basins_main = { n: basin_names[n-1] for n in [1,2,3,10] }
basins_marginal = { n: basin_names[n-1] for n in [4,5,6,7,11] }
woa_mask
<xarray.Dataset>
Dimensions:  (depth: 33, lat: 180, lon: 360)
Coordinates:
  * depth    (depth) float32 0.0 10.0 20.0 30.0 ... 4e+03 4.5e+03 5e+03 5.5e+03
  * lon      (lon) float32 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
  * lat      (lat) float32 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
Data variables:
    basin    (depth, lat, lon) float32 ...
Attributes:
    Conventions:  IRIDL
# actual data
url_base = 'http://iridl.ldeo.columbia.edu/SOURCES/.NOAA/.NODC/.WOA09/.Grid-1x1/.Annual'
url_temp = url_base + '/.temperature/.t_an/dods'
url_salt = url_base + '/.salinity/.s_an/dods '
woa_temp = xr.open_dataset(url_temp)
woa_temp_nobs = xr.open_dataarray(url_base + '/.temperature/.t_dd/dods')
woa_salt = xr.open_dataset(url_salt)
woa = xr.merge([woa_temp, woa_salt, woa_temp_nobs, woa_mask]).isel(time=0)
woa.load()
<xarray.Dataset>
Dimensions:  (depth: 33, lat: 180, lon: 360)
Coordinates:
  * lon      (lon) float32 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
  * depth    (depth) float32 0.0 10.0 20.0 30.0 ... 4e+03 4.5e+03 5e+03 5.5e+03
  * lat      (lat) float32 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
    time     datetime64[ns] 2008-01-01
Data variables:
    t_an     (depth, lat, lon) float32 nan nan nan nan nan ... nan nan nan nan
    s_an     (depth, lat, lon) float32 nan nan nan nan nan ... nan nan nan nan
    t_dd     (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
    basin    (depth, lat, lon) float32 nan nan nan nan nan ... nan nan nan nan
Attributes:
    Conventions:  IRIDL

We will now augment the raw dataset with derived quantities, including pressure (calculated from depth), absolute salinity, conservative temperature, and potential density at various reference levels.

woa['pres'] = gsw.p_from_z(-woa.depth, 0)
woa['s_a'] = gsw.SA_from_SP(woa.s_an, woa.pres, woa.lon, woa.lat)
woa['c_t'] = gsw.CT_from_t(woa.s_a, woa.t_an, woa.pres)
woa['sigma_0'] = gsw.sigma0(woa.s_a, woa.c_t)
woa['sigma_2'] = gsw.sigma2(woa.s_a, woa.c_t)
woa['sigma_4'] = gsw.sigma4(woa.s_a, woa.c_t)
woa
<xarray.Dataset>
Dimensions:  (depth: 33, lat: 180, lon: 360)
Coordinates:
  * lon      (lon) float32 0.5 1.5 2.5 3.5 4.5 ... 355.5 356.5 357.5 358.5 359.5
  * depth    (depth) float32 0.0 10.0 20.0 30.0 ... 4e+03 4.5e+03 5e+03 5.5e+03
  * lat      (lat) float32 -89.5 -88.5 -87.5 -86.5 -85.5 ... 86.5 87.5 88.5 89.5
    time     datetime64[ns] 2008-01-01
Data variables:
    t_an     (depth, lat, lon) float32 nan nan nan nan nan ... nan nan nan nan
    s_an     (depth, lat, lon) float32 nan nan nan nan nan ... nan nan nan nan
    t_dd     (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
    basin    (depth, lat, lon) float32 nan nan nan nan nan ... nan nan nan nan
    pres     (depth) float64 0.0 10.06 20.11 ... 4.573e+03 5.087e+03 5.602e+03
    s_a      (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
    c_t      (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
    sigma_0  (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
    sigma_2  (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
    sigma_4  (depth, lat, lon) float64 nan nan nan nan nan ... nan nan nan nan
Attributes:
    Conventions:  IRIDL

Before diving into the actual data, let’s first examine the number of observations in each grid cell. This shows how some parts of the ocean are much better sampled than others.

woa.t_dd.where(woa.t_dd > 0).hvplot('lon', 'lat', logz=True, width=700, height=400)

Later we will split up the ocean by basin. Here is how WOA defines the different basins.

woa.basin.where(woa.basin<20).hvplot('lon', 'lat', logz=True, width=700, height=400, cmap='Category20')
print(woa.basin.attrs['CLIST'])
Atlantic Ocean
Pacific Ocean 
Indian Ocean
Mediterranean Sea
Baltic Sea
Black Sea
Red Sea
Persian Gulf
Hudson Bay
Southern Ocean
Arctic Ocean
Sea of Japan
Kara Sea
Sulu Sea
Baffin Bay
East Mediterranean
West Mediterranean
Sea of Okhotsk
Banda Sea
Caribbean Sea
Andaman Basin
North Caribbean
Gulf of Mexico
Beaufort Sea
South China Sea
Barents Sea
Celebes Sea
Aleutian Basin
Fiji Basin
North American Basin
West European Basin
Southeast Indian Basin
Coral Sea
East Indian Basin
Central Indian Basin
Southwest Atlantic Basin
Southeast Atlantic Basin
Southeast Pacific Basin
Guatemala Basin
East Caroline Basin
Marianas Basin
Philippine Sea
Arabian Sea
Chile Basin
Somali Basin
Mascarene Basin
Crozet Basin
Guinea Basin
Brazil Basin
Argentine Basin
Tasman Sea
Atlantic Indian Basin
Caspian Sea
Sulu Sea II
Venezuela Basin
Bay of Bengal
Java Sea
East Indian Atlantic Basin

3D TemperatureΒΆ

def map_section_plot(data, label, data_range, levels, cmap='viridis'):
    ds = hv.Dataset(data)
    img = ds.to(hv.Image, ['lon', 'lat'], label=label)
    img = img.redim.range(**{data.name: data_range})

    posx = hv.streams.PointerX()
    vline = hv.DynamicMap(lambda x: hv.VLine(x or 180.5), streams=[posx])

    lats = np.arange(-90, 91)
    def cross_section(x):
        mesh = hv.QuadMesh((lats, woa.depth, data.sel(lon=x if x else 180, method='nearest').data[:-1]),
                           kdims=['lat', 'depth'], label=(label + ' Section'))
        contours = hv.operation.contours(mesh, levels=levels,
                                         overlaid=False, filled=True)
        return contours

    
    crosssection = hv.DynamicMap(cross_section, streams=[posx])
    layout = (img * vline) + crosssection 
    
    # set view options
    dict_spec = {'Image': {'plot': dict(colorbar=True, toolbar='above', width=600),
                           'style': dict(cmap=cmap)},
                 'NdOverlay': {'plot': dict(bgcolor='grey', invert_yaxis=True, width=600, height=200)},
                 'Layout': {'plot': dict(show_title=False)},
                 'Polygons': {'plot': dict(colorbar=True, invert_yaxis=True, width=600),
                              'style': dict(cmap=cmap, line_width=0.1)}
                 }
    layout.cols(1)
    layout = layout.opts(dict_spec)   
    return layout
map_section_plot(woa.t_an, 'Temperature', (-2,30), np.arange(-2,31), cmap='RdBu_r')

ObservationsΒΆ

  • The warmest water is near the surface in the tropical regions, forming a bowl-shaped lens in the upper ~800 meters. This is called the thermocline.

  • There are clear zonal asymmetries in surface temperature. The western boundaries of the basins tend to be warmer, with colder water near the eastern boundaries.

  • There is something special going on at the equator, especially in the Pacific. It’s relatively cold, and the thermocline is shallow.

  • Below 1000 m, the water is mostly very cold (< 5 C).

  • In the polar regions, the coldest water is actually found near the surface! There is a mid-depth temperature maximum.

3D SalinityΒΆ

map_section_plot(woa.s_an, 'Salinity', (32,38), np.arange(32,38.1, 0.25))

ObservationsΒΆ

  • Salinity has a more complex structure than temperature.

  • The Atlantic is overall saltier than the Pacific.

  • The saltiest parts of the oceans are the extra-tropical-latitudes.

  • The equator is relatively fresh near the surface.

  • Salinity sections across the Atlantic reveal the interleaving of several different water masses including

    • Mediterranean Water

    • North Atlantic Deep Water

    • Antarctic Intermediate Water

    • Antarctic Bottom Water

3D Potential DensityΒΆ

map_section_plot(woa.sigma_0, 'Potential Density (sigma0)', (22.,28.), np.arange(22,28.1, 0.25))
map_section_plot(woa.sigma_4, 'Potential Density (sigma4)', (40.,46.5), np.arange(40,46.5, 0.25))