eReefs dataset maps

We will now apply what we’ve learned to our eReefs dataset.

See also

Some examples of plotted maps from eReefs dataset can be found on the AIMS website.

Load the required Python libraries

First of all, load the necessary libraries. These are the ones we discussed previously:

  • numpy

  • matplotlib

  • cartopy

import os
import numpy as np

import datetime as dt

import netCDF4
from netCDF4 import Dataset, num2date

import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
cartopy.config['data_dir'] = os.getenv('CARTOPY_DIR', cartopy.config.get('data_dir'))

import cmocean

from matplotlib import pyplot as plt
# %config InlineBackend.figure_format = 'retina'
plt.ion()  # To trigger the interactive inline mode

Define which data to be plotted.

Note

We saw that via the OpenDAP protocol we can query eReefs dataset directly from source as NetCDF files. As you saw in the previous cell we have imported the Python netcdf4 library which provide series of functions and commands to load these files on our Jupyter environment.

We define which data we want to read and plot.

  • inputFile The netCDF input file. This can either be a downloaded file (see How to manually download derived data from THREDDS) or a OPeNDAP URL from the AIMS THREDDS server. For this tutorial we are using the OPeNDAP URL.

  • selectedVariable The name of the variable in the netCDF file.

  • selectedTimeIndex The time slice in the netCDF file. Note the index starts with 0. For example, in the netCDF file, the time steps are “days”. This means if you select selectedTimeIndex=1 it refers to the second day in the netCDF file.

  • selectedDepthIndex The depth slice in the netCDF file. Note the index starts with 0. See the following table for a mapping of index to value.

Connect to the OPeNDAP endpoint for a specified month.

We query the server based on the OPeNDAP URL for a specific file. We will use the data from the AIMS server.

  • gbr4: we use the 4km model and daily data for a month specified

NetCDF main functions

month = 3
year = 2020

netCDF_datestr = str(year)+'-'+format(month, '02')
print('File chosen time interval:',netCDF_datestr)

# GBR4 HYDRO
inputFile = "http://thredds.ereefs.aims.gov.au/thredds/dodsC/s3://aims-ereefs-public-prod/derived/ncaggregate/ereefs/gbr4_v2/daily-monthly/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_daily-monthly-"+netCDF_datestr+".nc"

# GBR4 BIO
#inputFile = "http://thredds.ereefs.aims.gov.au/thredds/dodsC/s3://aims-ereefs-public-prod/derived/ncaggregate/ereefs/GBR4_H2p0_B3p1_Cq3b_Dhnd/daily-monthly/EREEFS_AIMS-CSIRO_GBR4_H2p0_B3p1_Cq3b_Dhnd_bgc_daily-monthly-"+netCDF_datestr+".nc"
File chosen time interval: 2020-03

We now load the dataset within the Jupyter environment…

nc_data = Dataset(inputFile, 'r')  # Reading the file on the server

print('Get the list of variable in the file:')
print(list(nc_data.variables.keys()))

ncdata = nc_data.variables
Get the list of variable in the file:
['mean_cur', 'salt', 'temp', 'u', 'v', 'zc', 'time', 'latitude', 'longitude', 'mean_wspeed', 'eta', 'wspeed_u', 'wspeed_v']

To get information for a specific variables, we can use the following:

ncdata['mean_cur']
<class 'netCDF4._netCDF4.Variable'>
float32 mean_cur(time, k, latitude, longitude)
    coordinates: time zc latitude longitude
    substanceOrTaxon_id: http://environment.data.gov.au/def/feature/ocean_current
    units: ms-1
    medium_id: http://environment.data.gov.au/def/feature/ocean
    unit_id: http://qudt.org/vocab/unit#MeterPerSecond
    short_name: mean_cur
    aggregation: mean_speed
    standard_name: mean_current_speed
    long_name: mean_current_speed
    _ChunkSizes: [  1   1 133 491]
unlimited dimensions: time
current shape = (31, 17, 723, 491)
filling off
ncdata['mean_cur'].long_name
'mean_current_speed'
ncdata['mean_cur'].units
'ms-1'

Load variables

We then query the dataset in our Jupyter notebook:

# Starting with the spatial domain
lat = ncdata['latitude'][:]
lon = ncdata['longitude'][:]

print('eReefs model spatial extent:\n')
print(' - Longitudinal extent:',lon.min(),lon.max())
print(' - Latitudinal extent:',lat.min(),lat.max())
eReefs model spatial extent:

 - Longitudinal extent: 142.168788 156.868788
 - Latitudinal extent: -28.696022 -7.036022
# Get time span of the dataset
time_var = ncdata['time']

# Starting time
dtime = netCDF4.num2date(time_var[0],time_var.units)
daystr = dtime.strftime('%Y-%b-%d %H:%M')
print(' - start time: ',daystr,'\n')

# Ending time
dtime = netCDF4.num2date(time_var[-1],time_var.units)
daystr = dtime.strftime('%Y-%b-%d %H:%M')
print(' - end time: ',daystr,'\n')

ntime = len(time_var)

print(' - Number of time steps',ntime,'\n')
 - start time:  2020-Feb-29 14:00 

 - end time:  2020-Mar-30 14:00 

 - Number of time steps 31 

Let’s check that we actually have 16 layers for the z-coordinates:

# Number of vetical points along the z-coordinate model
zc = ncdata['zc'][:]
nlay = len(zc)

print('Number of vertical layers',nlay)

for k in range(nlay):
    print(f'  + vertical layer {k} is at {zc[k]} m')
Number of vertical layers 17
  + vertical layer 0 is at -145.0 m
  + vertical layer 1 is at -120.0 m
  + vertical layer 2 is at -103.0 m
  + vertical layer 3 is at -88.0 m
  + vertical layer 4 is at -73.0 m
  + vertical layer 5 is at -60.0 m
  + vertical layer 6 is at -49.0 m
  + vertical layer 7 is at -39.5 m
  + vertical layer 8 is at -31.0 m
  + vertical layer 9 is at -23.75 m
  + vertical layer 10 is at -17.75 m
  + vertical layer 11 is at -12.75 m
  + vertical layer 12 is at -8.8 m
  + vertical layer 13 is at -5.55 m
  + vertical layer 14 is at -3.0 m
  + vertical layer 15 is at -1.5 m
  + vertical layer 16 is at -0.5 m

Plotting map

We will now plot a first map of the mean surface current for a specific time.

Tip

We are going to use Matplotlib pcolormesh function and to display the current directions we will chose the quiver function.

First let’s create some parameters:

selectedVariable = 'mean_cur' # we pick the mean current 
selectedTimeIndex = 29        # remember we have 31 time records and here I chose the 29th
selectedDepthIndex = -1       # the surface is the last vertical layer at -0.5 m 

Let’s have a look at the mean surface current values for this day:

print('Data range: ')
print(np.nanmin(ncdata[selectedVariable][selectedTimeIndex, selectedDepthIndex, :,:]),
      np.nanmax(ncdata[selectedVariable][selectedTimeIndex, selectedDepthIndex, :,:]))
Data range: 
0.004236601 1.2370969

We will use the cmocean package that contains colormaps for commonly-used oceanographic variables.

# Used color
color = cmocean.cm.speed

# Variable range for the colorscale based on the range shown above
curlvl = [0.001,1.5]

# Vector field mapping information
veclenght = 1.
vecsample = 50

# Figure size
size = (9, 10)

Making the plot

Now that we have defined some variables let’s make our plot:

# Get data
data = ncdata[selectedVariable][selectedTimeIndex, selectedDepthIndex, :,:]

# Defining the figure
fig = plt.figure(figsize=size, facecolor='w', edgecolor='k')

# Axes with cartopy projection
ax = plt.axes(projection=ccrs.PlateCarree())
# and extent
ax.set_extent([142.4, 157, -7, -28.6], ccrs.PlateCarree())

# Ok now the map
cf = plt.pcolormesh(lon, lat, data, cmap=color, shading='auto',
                    vmin = curlvl[0], vmax = curlvl[1],
                    transform=ccrs.PlateCarree())

# Color bar
cbar = fig.colorbar(cf, ax=ax, fraction=0.027, pad=0.045, 
                    orientation="horizontal")
cbar.set_label(ncdata[selectedVariable].units, rotation=0, 
               labelpad=5, fontsize=10)
cbar.ax.tick_params(labelsize=8)

# Title
dtime = netCDF4.num2date(time_var[selectedTimeIndex],time_var.units)
daystr = dtime.strftime('%Y-%b-%d %H:%M')
plt.title(ncdata[selectedVariable].long_name+', %s UTC+10' % (daystr), 
          fontsize=11);

# Plot lat/lon grid 
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                  linewidth=0.1, color='k', alpha=1, 
                  linestyle='--')
gl.top_labels = False
gl.right_labels = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'size': 8}
gl.ylabel_style = {'size': 8} 

# Add map features with Cartopy 
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '10m', 
                                            edgecolor='face', 
                                            facecolor='lightgray'))
ax.coastlines(linewidth=1)

plt.tight_layout()
plt.show()
fig.clear()
plt.close(fig)
plt.clf()
/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/cartopy/io/__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/10m/physical/ne_10m_land.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
/usr/share/miniconda/envs/envireef/lib/python3.8/site-packages/cartopy/io/__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/10m/physical/ne_10m_coastline.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
../../_images/hydro_maps_26_2.png
<Figure size 432x288 with 0 Axes>

Adding the quiver plot on top

# Get data
data = ncdata[selectedVariable][selectedTimeIndex, selectedDepthIndex, :,:]

# Defining the figure
fig = plt.figure(figsize=size, facecolor='w', edgecolor='k')

# Axes with cartopy projection
ax = plt.axes(projection=ccrs.PlateCarree())
# and extent
ax.set_extent([142.4, 157, -7, -28.6], ccrs.PlateCarree())

# Ok now the map
cf = plt.pcolormesh(lon, lat, data, cmap=color, shading='auto',
                    vmin = curlvl[0], vmax = curlvl[1],
                    transform=ccrs.PlateCarree())

# For the quiver plot we will first need to create a mesh.
# We used numpy mesgrid function for this
loni, lati = np.meshgrid(lon, lat)

# The direction of the current is given by the u and v parameters in 
# the netcdf file
u = ncdata['u'][selectedTimeIndex, selectedDepthIndex, :,:]
v = ncdata['v'][selectedTimeIndex, selectedDepthIndex, :,:]

# Find non nans velocity points
# data2 = np.copy(data)
# data2[np.isnan(data2)] = 1000

# We look for the points in the dataset inside our specified range
# we basically don't take nan points
ind = np.where(np.logical_and(data.flatten()>curlvl[0],
                              data.flatten()<curlvl[1]))[0]
np.random.shuffle(ind)

# We then rnadomly chose a number of points, we don't take all of the
# points otherwise the figure will be unreadable
Nvec = int(len(ind) / vecsample)
idv = ind[:Nvec]

# We have everything we need we can now use the quiver function
Q = plt.quiver(loni.flatten()[idv],
               lati.flatten()[idv],
               u.flatten()[idv],
               v.flatten()[idv],
               transform=ccrs.PlateCarree(), 
               scale=20)

# We will add a vector scale as a legend to our plot 
maxstr = '%3.1f m/s' % veclenght
qk = plt.quiverkey(Q,0.1,0.1,veclenght,maxstr,labelpos='S')
        
        
# Color bar
cbar = fig.colorbar(cf, ax=ax, fraction=0.027, pad=0.045, 
                    orientation="horizontal")
cbar.set_label(ncdata[selectedVariable].units, rotation=0, 
               labelpad=5, fontsize=10)
cbar.ax.tick_params(labelsize=8)

# Title
dtime = netCDF4.num2date(time_var[selectedTimeIndex],
                         time_var.units)
daystr = dtime.strftime('%Y-%b-%d %H:%M')
plt.title(ncdata[selectedVariable].long_name+', %s UTC+10' % (daystr), 
          fontsize=11);

# Plot lat/lon grid 
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                  linewidth=0.1, color='k', alpha=1, 
                  linestyle='--')
gl.top_labels = False
gl.right_labels = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'size': 8}
gl.ylabel_style = {'size': 8} 

# Add map features with Cartopy 
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '10m', 
                                            edgecolor='face', 
                                            facecolor='lightgray'))
ax.coastlines(linewidth=1)

plt.tight_layout()
plt.show()
fig.clear()
plt.close(fig)
plt.clf()
../../_images/hydro_maps_28_0.png
<Figure size 432x288 with 0 Axes>

Build a function to automatise the process

Now we will use the function eReefs_GBR_Model below to plot the different variables at specific time step and depth.

We want the function to be reusable for:

  • different depth and time intervals

  • different variable names (temperature, salinity, …)

  • variable colorbar and figure size

  • possibility to zoom to a specific region

  • plotting quiver plots when current maps are visualised

  • showing and saving figures

def eReefs_map(ncdata, tstep, depth, dataname, datalvl, color, size, 
               fname, vecsample, veclenght, vecscale, zoom=None, 
               show=False, vecPlot=False, save=False):
    '''
    This function plots for a specified time index and depth the value of a variable from the eReefs netCDF
    file available on the AIMS OpenDAP server.
    
    args:
    
    - ncdata: netcdf dataset
    - tstep: specified time index
    - depth: specified depth layer
    - dataname: specified variable name 
    - datalvl: range of the variable values specified as a list [min,max] 
    - color: colormap to use for the plot (here one can use the cmocean library: https://matplotlib.org/cmocean/#installation)
    - size: figure size  
    - fname: figure name when saved on disk, it is worth noting that the specified time index and depth layer will be automatically added
    - vecsample: sampling on velocity arrows to plot on the maps when velocity verctor are used
    - veclenght: lenght of the reference vector (in m/s)
    - vecscale: vector scaling
    - zoom: study site to plot lower left and upper right corner [lon0,lat0, lon1, lat1]
    - show: set to True when the map is shown in the jupyter environment directly 
    - vecPlot: set to True when the current flow vector are plotted
    - save: set to True to save figure on disk
    '''
    
    # Get data
    data = ncdata[dataname][tstep, depth, :,:]
    
    fig = plt.figure(figsize=size, facecolor='w', edgecolor='k')

    ax = plt.axes(projection=ccrs.PlateCarree())
    ax.set_extent([142.4, 157, -7, -28.6], ccrs.PlateCarree())
    
    # Starting with the spatial domain
    lat = ncdata['latitude'][:]
    lon = ncdata['longitude'][:]
    cf = plt.pcolormesh(lon, lat, data, cmap=color, shading='auto',
                    vmin = datalvl[0], vmax = datalvl[1],
                    transform=ccrs.PlateCarree())

    # Plot velocity arrows 
    if vecPlot:
        loni, lati = np.meshgrid(lon, lat)
        u = ncdata['u'][tstep, depth, :,:]
        v = ncdata['v'][tstep, depth, :,:]
        
        if zoom is not None:
            # find non zeros velocity points
            dataid = np.where(np.logical_and(data.flatten()>datalvl[0],
                                          data.flatten()<datalvl[1]))[0]
            
            lonid = np.where(np.logical_and(loni.flatten()>zoom[0],
                                          loni.flatten()<zoom[2]))[0]
            
            latid = np.where(np.logical_and(lati.flatten()>zoom[1],
                                          lati.flatten()<zoom[3]))[0]
            
            tmpid = np.intersect1d(lonid, latid)
            ind = np.intersect1d(tmpid, dataid)
        else:
            # find non zeros velocity points
            ind = np.where(np.logical_and(data.flatten()>datalvl[0],
                                          data.flatten()<datalvl[1]))[0]
        np.random.shuffle(ind)
        Nvec = int(len(ind) / vecsample)
        idv = ind[:Nvec]
        Q = plt.quiver(loni.flatten()[idv],
                       lati.flatten()[idv],
                       u.flatten()[idv],
                       v.flatten()[idv],
                       transform=ccrs.PlateCarree(), 
                       scale=vecscale)

        maxstr='%3.1f m/s' % veclenght
        qk = plt.quiverkey(Q,0.1,0.1,veclenght,maxstr,labelpos='S')


    # Color bar
    cbar = fig.colorbar(cf, ax=ax, fraction=0.027, pad=0.045, 
                        orientation="horizontal")
    cbar.set_label(ncdata[dataname].units, rotation=0, 
                   labelpad=5, fontsize=10)
    cbar.ax.tick_params(labelsize=8)
    

    # Title
    dtime = netCDF4.num2date(ncdata['time'][tstep],ncdata['time'].units)
    daystr = dtime.strftime('%Y-%b-%d %H:%M')
    plt.title(ncdata[dataname].long_name+', %s UTC+10' % (daystr), 
              fontsize=11);

    # Plot lat/lon grid 
    gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                      linewidth=0.1, color='k', alpha=1, 
                      linestyle='--')
    gl.top_labels = False
    gl.right_labels = False
    gl.xformatter = LONGITUDE_FORMATTER
    gl.yformatter = LATITUDE_FORMATTER
    gl.xlabel_style = {'size': 8}
    gl.ylabel_style = {'size': 8} 
    
    # Add map features
    ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '10m', 
                                                edgecolor='face', 
                                                facecolor='lightgray'))
    ax.coastlines(linewidth=1)

    if zoom is not None:
        plt.xlim(zoom[0],zoom[2])
        plt.ylim(zoom[1],zoom[3])
    
    if show:
        if save:
            plt.savefig(f"{fname}_time{tstep:04}_zc{depth:04}.png",dpi=300, 
                    bbox_inches='tight')
        plt.tight_layout()
        plt.show()
    else:
        plt.savefig(f"{fname}_time{tstep:04}_zc{depth:04}.png",dpi=300, 
                bbox_inches='tight')

    fig.clear()
    plt.close(fig)
    plt.clf()
    
    return

Let’s use the function. First we will check for a specific time and a depth each variable range:

print(' ')
print('Temperature range: ')
print(np.nanmin(ncdata['temp'][selectedTimeIndex, selectedDepthIndex, :,:]),
      np.nanmax(ncdata['temp'][selectedTimeIndex, selectedDepthIndex, :,:]))

print(' ')
print('Salinity range: ')
print(np.nanmin(ncdata['salt'][selectedTimeIndex, selectedDepthIndex, :,:]),
      np.nanmax(ncdata['salt'][selectedTimeIndex, selectedDepthIndex, :,:]))
 
Temperature range: 
24.66653 31.789503
 
Salinity range: 
0.036854204 36.043182

We will plot the mean current like this:

selectedVariable = 'mean_cur' 


# Vector field mapping information
veclenght = 1.
vecsample = 50
vecscale = 20

# Figure size
size = (9, 10)

# Used color
color = cmocean.cm.speed

# Variable range for the colorscale
curlvl = [0.001,1.5]

# Saved file name
fname = 'GBRcurrent'

# Region to plot
zoom = None

# We now call the function
eReefs_map(nc_data, selectedTimeIndex, selectedDepthIndex, 
           selectedVariable, curlvl, color, size, fname, 
           vecsample, veclenght, vecscale, zoom, 
           show=True, vecPlot=True, save=False)
../../_images/hydro_maps_35_0.png
<Figure size 432x288 with 0 Axes>

Checking the zooming function

selectedVariable = 'mean_cur' 


# Vector field mapping information
veclenght = 0.5
vecsample = 30
vecscale = 15

# Figure size
size = (6, 6)

# Used color
color = cmocean.cm.speed

# Variable range for the colorscale
curlvl = [0.001,1.3]

# Saved file name
fname = 'GBRcurrent'

# Region to plot
zoom = [144,-15,148,-12]

# We now call the function
eReefs_map(nc_data, selectedTimeIndex, selectedDepthIndex, 
           selectedVariable, curlvl, color, size, fname, 
           vecsample, veclenght, vecscale, zoom, 
           show=True, vecPlot=True, save=False)
<ipython-input-15-3400d0661677>:118: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all axes decorations. 
  plt.tight_layout()
../../_images/hydro_maps_37_1.png
<Figure size 432x288 with 0 Axes>

For the salinity dataset…

selectedVariable = 'salt'

# Used color
color = cmocean.cm.haline

# Variable range for the colorscale
saltlvl = [34,35]

# Saved file name
fname = 'GBRsalinity'

# We now call the function
eReefs_map(nc_data, selectedTimeIndex, selectedDepthIndex, 
           selectedVariable, saltlvl, color, size, fname, 
           vecsample, veclenght, vecscale, zoom, 
           show=True, vecPlot=True, save=False)
<ipython-input-15-3400d0661677>:118: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all axes decorations. 
  plt.tight_layout()
../../_images/hydro_maps_39_1.png
<Figure size 432x288 with 0 Axes>

For the tempreature dataset…

selectedVariable = 'temp'

# Used color
color = cmocean.cm.thermal

# Variable range for the colorscale
templvl = [27,29]

# Saved file name
fname = 'GBRtemperature'

# We now call the function
eReefs_map(nc_data, selectedTimeIndex, selectedDepthIndex, 
           selectedVariable, templvl, color, size, fname, 
           vecsample, veclenght, vecscale, zoom, 
           show=True, vecPlot=True, save=False)
<ipython-input-15-3400d0661677>:118: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all axes decorations. 
  plt.tight_layout()
../../_images/hydro_maps_41_1.png
<Figure size 432x288 with 0 Axes>