Note
Go to the end to download the full example code or to run this example in your browser via Binder
Compare Hycom3d with a GDP drifter
In this notebook, we show :
how to decode dataset so that it is easy to access generic coordinates and variables,
how to compute depths from layer thicknesses,
how to interpolate currents from U and V positions to T position on an arakawa C grid,
how to perform a 4D interpolation with a variable depth coordinate to random positions,
how to make an horizontal slice of a 4D variable with a variable depth coordinate.
Initialisations
import numpy as np
import pandas as pd
import xarray as xr
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import xoa
from xoa.grid import dz2depth, shift
from xoa.regrid import grid2loc, regrid1d
import xoa.cf as xcf
import xoa.geo as xgeo
from xoa.plot import plot_flow, plot_double_minimap
xr.set_options(display_style="text")
<xarray.core.options.set_options object at 0x7f37d1db17d0>
Register the xoa accessors:
xoa.register_accessors()
Register the Hycom naming specifications
xcf.register_cf_specs(xoa.get_data_sample("hycom.cfg"))
Here is what these CF specifications contain
[register]
name=hycom
[[attrs]]
nsigma = "[0-9]?"
[vertical]
positive=down
type=dz
[data_vars]
[[u]]
loc=u
[[[add_coords_loc]]]
lon=True
lat=True
x=True
y=True
[[v]]
loc=v
[[[add_coords_loc]]]
lon=True
lat=True
x=True
y=True
[[bathy]]
name=bathymetry
add_loc=True
[[dz]]
name=h
[coords]
[[x]]
name=X
[[y]]
name=Y
[[z]]
name=lev
Note that most of these specifications are not needed to decode the dataset, i.e to find known variables and coordinates, since the default specifications help for that.
Read and decode the datasets
Hycom velocities
U and V are stored in separate files at U and V locations on the Hycom Arakawa C-Grid.
We choose to rename the dataset contents so that we can use generic names to access it.
We call for that the decode()
accessor method.
Note that the staggered grid location is automatically appended to horizontal
coordinates and dimensions of the U and V velocity components since they are
placed at U and V locations. This prevent any conflict with the bathymetry and
layer thickness horizontal coordinates and dimensions, which point to the T location.
U velocity component
<xarray.DataArray 'u' (time: 50, z: 6, y_u: 11, x_u: 13)>
[42900 values with dtype=float32]
Coordinates:
lon_u (y_u, x_u) float32 ...
lat_u (y_u, x_u) float32 ...
* z (z) int32 1 2 3 4 5 6
* time (time) datetime64[ns] 2021-03-01 ... 2021-03-25T12:00:00
Dimensions without coordinates: y_u, x_u
Attributes:
long_name: Vitesse u
units: m/s
standard_name: sea_water_x_velocity
V velocity component
v_hycom = xoa.open_data_sample("hycom.gdp.v.nc").xoa.decode().v.squeeze(drop=True)
Layer thicknesses dataset
<xarray.Dataset>
Dimensions: (y: 11, x: 13, z: 6, time: 50)
Coordinates:
lon (y, x) float32 ...
lat (y, x) float32 ...
* z (z) int32 1 2 3 4 5 6
* time (time) datetime64[ns] 2021-03-01 ... 2021-03-25T12:00:00
Dimensions without coordinates: y, x
Data variables:
bathy (y, x) float32 ...
dz (time, z, y, x) float32 ...
Attributes: (12/26)
history: Fri Apr 9 12:15:22 2021: ncrcat -d X,472,508,...
Conventions: COARDS
nhybrd: 20
nsigma: 19
kapref: 2
hybflg: 0
... ...
dp00ft: [1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 ...
dp00xt: [21.874 21.414 21.414 13.197 7.85 3.85 4....
ds00t: [3.555 3.026 3.026 1.547 0.828 0.828 0.968 0.8...
ds00ft: [1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 ...
ds00xt: [21.874 21.414 21.414 7.197 3.85 3.85 4....
nco_openmp_thread_number: 1
We now compute the depths from the layer thicknesses at the T location
thanks to the xoa.grid.dz2depth()
function:
we integrate from a null SSH and interpolate the depth from W locations to T locations.
hycom.coords["depth"] = dz2depth(hycom.dz, centered=True)
Then we interpolate the velocity components to the T location with
the xoa.grid.shift()
function.
The call to reloc
helps removing the
staggered grid location prefixes.
ut_hycom = shift(u_hycom, {"x": "left", "y": "left"}).xoa.reloc(u=False)
vt_hycom = shift(v_hycom, {"x": "left", "y": "left"}).xoa.reloc(v=False)
hycom["u"] = ut_hycom.assign_coords(**hycom.coords)
hycom["v"] = vt_hycom.assign_coords(**hycom.coords)
So, finally we obtain:
print(hycom)
<xarray.Dataset>
Dimensions: (y: 11, x: 13, z: 6, time: 50)
Coordinates:
lon (y, x) float32 ...
lat (y, x) float32 ...
* z (z) int32 1 2 3 4 5 6
* time (time) datetime64[ns] 2021-03-01 ... 2021-03-25T12:00:00
depth (time, z, y, x) float32 1.511 1.511 1.51 ... 20.11 20.09 20.06
Dimensions without coordinates: y, x
Data variables:
bathy (y, x) float32 ...
dz (time, z, y, x) float32 3.021 3.021 3.02 3.019 ... 5.22 5.216 5.209
u (time, z, y, x) float32 0.03078 0.02984 0.03803 ... 0.1292 0.1364
v (time, z, y, x) float32 0.0497 0.07527 0.1111 ... 0.0498 0.05054
Attributes: (12/26)
history: Fri Apr 9 12:15:22 2021: ncrcat -d X,472,508,...
Conventions: COARDS
nhybrd: 20
nsigma: 19
kapref: 2
hybflg: 0
... ...
dp00ft: [1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 ...
dp00xt: [21.874 21.414 21.414 13.197 7.85 3.85 4....
ds00t: [3.555 3.026 3.026 1.547 0.828 0.828 0.968 0.8...
ds00ft: [1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 1.15 ...
ds00xt: [21.874 21.414 21.414 7.197 3.85 3.85 4....
nco_openmp_thread_number: 1
GDP drifter
The drifter comes as a csv file and we read it as pandas.DataFrame
instance.
csv_name = xoa.get_data_sample("gdp-6203641.csv")
drifter = pd.read_csv(
csv_name, header=0, skiprows=[1], parse_dates=[0], index_col=0, usecols=[2, 3, 4, 5, 6, 7]
)
Since the sampling is not that nice, we resample it to 3-hour intervals.
drifter = drifter.resample("3H").mean()
We convert it to and xarray.Dataset
, fix the time coordinate and decode it.
drifter = drifter.to_xarray().assign_coords(time=drifter.index.values).xoa.decode()
We drop missing values.
drifter = drifter.where(~drifter.lon.isnull() & ~drifter.lat.isnull(), drop=True)
We add a constant depth of 15 m.
drifter.coords["depth"] = drifter.lon * 0 + 15
Here is what we obtain.
print(drifter)
<xarray.Dataset>
Dimensions: (time: 159)
Coordinates:
lat (time) float64 44.68 44.72 44.72 44.72 ... 44.72 44.72 44.71 44.71
lon (time) float64 -2.544 -2.583 -2.592 -2.617 ... -2.523 -2.514 -2.505
* time (time) datetime64[ns] 2021-03-03T18:00:00 ... 2021-03-24T09:00:00
depth (time) float64 15.0 15.0 15.0 15.0 15.0 ... 15.0 15.0 15.0 15.0
Data variables:
sst (time) float64 13.09 12.79 12.77 12.78 ... 12.61 12.56 12.53 12.61
slp (time) float64 1.027e+03 1.024e+03 ... 1.025e+03 1.026e+03
lon360 (time) float64 357.5 357.4 357.4 357.4 ... 357.5 357.5 357.5 357.5
Compute and interpolate velocities
We compute the drifter velocity components
drifter["u"] = (
drifter.lon.differentiate("time", datetime_unit="s") * xgeo.EARTH_RADIUS * np.pi / 180
)
drifter["u"] *= np.cos(np.radians(drifter.lat.values))
drifter["v"] = (
drifter.lat.differentiate("time", datetime_unit="s") * xgeo.EARTH_RADIUS * np.pi / 180
)
We make a 4D linear interpolation the Hycom velocity to the drifter positions with xoa.regrid.grid2loc()
.
Instead of just showing the velocities along the drifter positions,
we can plot the mean model velocities over the same period as a background.
So we interpolate them at 15 m with xoa.regrid.regrid1d()
and compute a time average.
Plot
The geographic extent is easily computed with the xoa.geo.get_extent()
function.
The background currents are plotted with the xoa.plot.plot_flow()
function.
pmerc = ccrs.Mercator()
pcarr = ccrs.PlateCarree()
fig, ax = plt.subplots(figsize=(8, 7), subplot_kw={"facecolor": "teal", "projection": pmerc})
plt.subplots_adjust(right=0.85)
ax.gridlines(draw_labels=True, dms=True)
ax.set_extent(xgeo.get_extent(uh15))
kwqv = dict(scale_units="dots", scale=0.1 / 20, units="dots", transform=pcarr)
plot_flow(uh15, vh15, axes=ax, transform=pcarr, color="w", alpha=(0.3, 1), linewidth=0.6)
qv = ax.quiver(
uloc.lon.values,
uloc.lat.values,
uloc.values,
vloc.values,
color="w",
width=2,
label="Model",
**kwqv,
)
ax.plot(drifter.lon.values, drifter.lat.values, '-', color="C1", transform=pcarr, lw=0.5)
ax.quiver(
drifter.lon.values,
drifter.lat.values,
drifter.u.values,
drifter.v.values,
color="C1",
label="Drifter",
width=2,
**kwqv,
)
plt.quiverkey(qv, 0.1, 1.06, 0.1, r"0.1 $m\,s^{-1}$", color="k", alpha=1, labelpos="E")
plt.legend()
plot_double_minimap(drifter)
/home/docs/checkouts/readthedocs.org/user_builds/xoa/conda/latest/lib/python3.11/site-packages/xoa/dyn.py:166: UserWarning: Converting non-nanosecond precision datetime values to nanosecond precision. This behavior can eventually be relaxed in xarray, as it is an artifact from pandas which is now beginning to support non-nanosecond precision values. This warning is caused by passing non-nanosecond np.datetime64 or np.timedelta64 values to the DataArray or Variable constructor; it can be silenced by converting the values to nanosecond precision ahead of time.
time = xr.DataArray(tt * np.timedelta64(1, 's') + date, dims='time')
(<GeoAxes: >, <GeoAxes: >)
The discrepancies between the lagrangian and mean eulerian currents highlight the variability due to the mesocale activity. The differences between the observed and modeled currents are partly due to the lack of data assimilation in the model.
Total running time of the script: (0 minutes 24.493 seconds)