_images/wavespectra_logo.png

Quick start

Wavespectra is an open source project for processing ocean wave spectral data. The library is built on top of xarray and provides reading and writing of different spectral data formats, calculation of common integrated wave paramaters, spectral partitioning and spectral manipulation in a package focussed on speed and efficiency for large numbers of spectra.

Reading spectra from files

Several methods are provided to read various file formats including spectral wave models like WW3, SWAN, WWM and observation instruments such as TRIAXYS and SPOTTER.

In [1]: import matplotlib.pyplot as plt

In [2]: from wavespectra import read_ww3

In [3]: dset = read_ww3("_static/ww3file.nc")

In [4]: dset
Out[4]: 
<xarray.Dataset>
Dimensions:  (dir: 24, time: 9, site: 2, freq: 25)
Coordinates:
  * dir      (dir) float32 270.0 255.0 240.0 225.0 ... 330.0 315.0 300.0 285.0
  * freq     (freq) float32 0.04118 0.0453 0.04983 ... 0.3352 0.3687 0.4056
  * site     (site) int32 1 2
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
Data variables:
    dpt      (time, site) float32 dask.array<chunksize=(9, 2), meta=np.ndarray>
    efth     (time, site, freq, dir) float32 dask.array<chunksize=(9, 2, 25, 24), meta=np.ndarray>
    lat      (site) float32 dask.array<chunksize=(2,), meta=np.ndarray>
    lon      (site) float32 dask.array<chunksize=(2,), meta=np.ndarray>
    wspd     (time, site) float32 dask.array<chunksize=(9, 2), meta=np.ndarray>
    wdir     (time, site) float32 dask.array<chunksize=(9, 2), meta=np.ndarray>

The spec namespace

Wavespectra defines a new namespace accessor called spec which is attached to xarray objects. This namespace provides access to methods from the two main objects in wavespectra:

which extend functionality from xarray’s DataArray and Dataset objects respectively.

SpecArray

In [5]: dset.efth.spec
Out[5]: 
<SpecArray 'efth' (time: 9, site: 2, freq: 25, dir: 24)>
dask.array<SpecArray shape=(9, 2, 25, 24), dtype=float32, chunksize=(9, 2, 25, 24), chunktype=numpy.ndarray>
Coordinates:
  * dir      (dir) float32 270.0 255.0 240.0 225.0 ... 330.0 315.0 300.0 285.0
  * freq     (freq) float32 0.04118 0.0453 0.04983 ... 0.3352 0.3687 0.4056
  * site     (site) int32 1 2
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
Attributes:
    standard_name:  sea_surface_wave_directional_variance_spectral_density
    units:          m2 s degree-1

SpecDset

In [6]: dset.spec
Out[6]: 
<SpecDataset>
Dimensions:  (dir: 24, time: 9, site: 2, freq: 25)
Coordinates:
  * dir      (dir) float32 270.0 255.0 240.0 225.0 ... 330.0 315.0 300.0 285.0
  * freq     (freq) float32 0.04118 0.0453 0.04983 ... 0.3352 0.3687 0.4056
  * site     (site) int32 1 2
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
Data variables:
    dpt      (time, site) float32 dask.array<SpecDataset>
    efth     (time, site, freq, dir) float32 dask.array<SpecDataset>
    lat      (site) float32 dask.array<SpecDataset>
    lon      (site) float32 dask.array<SpecDataset>
    wspd     (time, site) float32 dask.array<SpecDataset>
    wdir     (time, site) float32 dask.array<SpecDataset>

Spectral wave parameters

Several methods are available to calculate integrated wave parameters. They can be accessed from both SpecArray (efth variable) and SpecDset accessors:

In [7]: hs = dset.efth.spec.hs()

In [8]: hs
Out[8]: 
<xarray.DataArray 'hs' (time: 9, site: 2)>
dask.array<mul, shape=(9, 2), dtype=float64, chunksize=(9, 2), chunktype=numpy.ndarray>
Coordinates:
  * site     (site) int32 1 2
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
Attributes:
    standard_name:  sea_surface_wave_significant_height
    units:          m

In [9]: hs1 = dset.spec.hs()

In [10]: hs.identical(hs1)
Out[10]: True

In [11]: hs.plot.line(x="time");

In [12]: plt.draw()
_images/hs.png
In [13]: stats = dset.spec.stats(
   ....:     ["hs", "hmax", "tp", "tm01", "tm02", "dpm", "dm", "dspr", "swe"]
   ....: )
   ....: 

In [14]: stats
Out[14]: 
<xarray.Dataset>
Dimensions:  (site: 2, time: 9)
Coordinates:
  * site     (site) int32 1 2
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
Data variables:
    hs       (time, site) float64 dask.array<chunksize=(9, 2), meta=np.ndarray>
    hmax     (time, site) float64 dask.array<chunksize=(9, 2), meta=np.ndarray>
    tp       (time, site) float32 dask.array<chunksize=(9, 2), meta=np.ndarray>
    tm01     (time, site) float64 dask.array<chunksize=(9, 2), meta=np.ndarray>
    tm02     (time, site) float64 dask.array<chunksize=(9, 2), meta=np.ndarray>
    dpm      (time, site) float32 dask.array<chunksize=(9, 2), meta=np.ndarray>
    dm       (time, site) float32 dask.array<chunksize=(9, 2), meta=np.ndarray>
    dspr     (time, site) float64 dask.array<chunksize=(9, 2), meta=np.ndarray>
    swe      (time, site) float64 dask.array<chunksize=(9, 2), meta=np.ndarray>
Attributes:
    standard_name:  sea_surface_wave_significant_height
    units:          m

In [15]: fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6)) = plt.subplots(3, 2, figsize=(8, 6))

In [16]: stats.hs.plot.line(ax=ax1, x="time");

In [17]: stats.hmax.plot.line(ax=ax2, x="time");

In [18]: stats.dpm.plot.line(ax=ax3, x="time");

In [19]: stats.dspr.plot.line(ax=ax4, x="time");

In [20]: stats.tp.plot.line(ax=ax5, x="time");

In [21]: stats.tm01.plot.line(ax=ax6, x="time");

In [22]: plt.draw()
_images/many_stats.png

Partitioning

Two different partitioning techniques are available, a simple spectral split based on frequency / direction cutoffs and the watershed algorithm of Hanson et al. (2008).

Spectral split

In [23]: fcut = 1 / 8

In [24]: sea = dset.spec.split(fmin=fcut)

In [25]: swell = dset.spec.split(fmax=fcut)

In [26]: dset.freq.values
Out[26]: 
array([0.04118   , 0.045298  , 0.0498278 , 0.05481058, 0.06029164,
       0.06632081, 0.07295289, 0.08024818, 0.08827299, 0.09710029,
       0.10681032, 0.11749136, 0.1292405 , 0.14216454, 0.15638101,
       0.17201911, 0.18922101, 0.20814312, 0.22895744, 0.25185317,
       0.27703848, 0.30474234, 0.3352166 , 0.36873826, 0.40561208],
      dtype=float32)

In [27]: sea.freq.values
Out[27]: 
array([0.125     , 0.1292405 , 0.14216454, 0.15638101, 0.17201911,
       0.18922101, 0.20814312, 0.22895744, 0.25185317, 0.27703848,
       0.30474234, 0.33521661, 0.36873826, 0.40561208])

In [28]: swell.freq.values
Out[28]: 
array([0.04118   , 0.045298  , 0.0498278 , 0.05481058, 0.06029164,
       0.06632081, 0.07295289, 0.08024818, 0.08827299, 0.09710029,
       0.10681032, 0.11749136, 0.125     ])

In [29]: dset.spec.hs().isel(site=0).plot(label='Full spectrum', marker='o');

In [30]: sea.spec.hs().isel(site=0).plot(label='Sea', marker='o');

In [31]: swell.spec.hs().isel(site=0).plot(label='Swell', marker='o');

In [32]: plt.draw()
_images/freq_split_hs.png

Watershed

In [33]: dspart = dset.spec.partition(dset.wspd, dset.wdir, dset.dpt)

In [34]: pstats = dspart.spec.stats(["hs", "dpm"])

In [35]: pstats
Out[35]: 
<xarray.Dataset>
Dimensions:  (site: 2, time: 9, part: 4)
Coordinates:
  * site     (site) int32 1 2
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
  * part     (part) int64 0 1 2 3
Data variables:
    hs       (part, time, site) float64 dask.array<chunksize=(4, 9, 2), meta=np.ndarray>
    dpm      (part, time, site) float32 dask.array<chunksize=(4, 9, 2), meta=np.ndarray>
Attributes:
    standard_name:  sea_surface_wave_significant_height
    units:          m

In [36]: fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 8))

In [37]: hs.isel(site=0).plot(ax=ax1, label='Full spectrum', marker='o');

In [38]: pstats.hs.isel(part=0, site=0).plot(ax=ax1, label='Partition 0 (sea)', marker='o');

In [39]: pstats.hs.isel(part=1, site=0).plot(ax=ax1, label='Partition 1 (swell 1)', marker='o');

In [40]: pstats.hs.isel(part=2, site=0).plot(ax=ax1, label='Partition 2 (swell 2)', marker='o');

In [41]: pstats.hs.isel(part=3, site=0).plot(ax=ax1, label='Partition 3 (swell 3)', marker='o');

In [42]: dset.spec.dpm().isel(site=0).plot(ax=ax2, label='Full spectrum', marker='o');

In [43]: pstats.dpm.isel(part=0, site=0).plot(ax=ax2, label='Partition 0 (sea)', marker='o');

In [44]: pstats.dpm.isel(part=1, site=0).plot(ax=ax2, label='Partition 1 (swell 1)', marker='o');

In [45]: pstats.dpm.isel(part=2, site=0).plot(ax=ax2, label='Partition 2 (swell 2)', marker='o');

In [46]: pstats.dpm.isel(part=3, site=0).plot(ax=ax2, label='Partition 3 (swell 3)', marker='o');

In [47]: plt.draw()
_images/watershed_hs.png

Plotting

Wavespectra wraps the plotting functionality from xarray to allow easily defining frequency-direction spectral plots in polar coordinates.

In [48]: ds = dset.isel(site=0, time=[0, 1]).spec.split(fmin=0.05, fmax=2.0)

In [49]: ds.spec.plot(
   ....:     kind="contourf",
   ....:     col="time",
   ....:     as_period=False,
   ....:     normalised=True,
   ....:     logradius=True,
   ....:     add_colorbar=False,
   ....:     figsize=(8, 5)
   ....: );
   ....: 
_images/faceted_polar_plot.png

Plotting Hovmoller diagrams of frequency spectra timeseries can be done in only a few lines.

In [50]: import cmocean

In [51]: ds = dset.isel(site=0).spec.split(fmax=0.18).spec.oned().rename({"freq": "period"})

In [52]: ds = ds.assign_coords({"period": 1 / ds.period})

In [53]: ds.period.attrs.update({"standard_name": "sea_surface_wave_period", "units": "s"})

In [54]: ds.plot.contourf(x="time", y="period", vmax=1.25, cmap=cmocean.cm.thermal, levels=10);
_images/hovmoller_plot.png

Selecting

Wavespectra complements xarray’s selecting and interpolating functionality with functions to select and interpolate from site coordinates with the sel method.

In [55]: idw = dset.spec.sel(
   ....:     lons=[92.01, 92.05, 92.09],
   ....:     lats=[19.812, 19.875, 19.935],
   ....:     method="idw"
   ....: )
   ....: 

In [56]: idw
Out[56]: 
<xarray.Dataset>
Dimensions:  (dir: 24, freq: 25, time: 9, site: 3)
Coordinates:
  * dir      (dir) float32 270.0 255.0 240.0 225.0 ... 330.0 315.0 300.0 285.0
  * freq     (freq) float32 0.04118 0.0453 0.04983 ... 0.3352 0.3687 0.4056
  * time     (time) datetime64[ns] 2014-12-01 2014-12-01T12:00:00 ... 2014-12-05
  * site     (site) int64 0 1 2
Data variables:
    dpt      (time, site) float32 dask.array<chunksize=(9, 1), meta=np.ndarray>
    efth     (time, site, freq, dir) float32 dask.array<chunksize=(9, 1, 25, 24), meta=np.ndarray>
    lat      (site) float64 19.81 19.88 19.93
    lon      (site) float64 92.01 92.05 92.09
    wspd     (time, site) float32 dask.array<chunksize=(9, 1), meta=np.ndarray>
    wdir     (time, site) float32 dask.array<chunksize=(9, 1), meta=np.ndarray>

In [57]: p = plt.scatter(dset.lon, dset.lat, 80, dset.isel(time=0).spec.hs(), label="Dataset points");

In [58]: p = plt.scatter(idw.lon, idw.lat, 100, idw.isel(time=0).spec.hs(), marker="v", label="Interpolated point");

In [59]: plt.draw()
_images/interp_stations_plot.png

The nearest neighbour and bbox options are also available besides inverse distance weighting (idw).