Particles tracking¶
The OceanParcels project develops Parcels
(Probably A Really Computationally Efficient Lagrangian Simulator), a set of Python classes and methods to create customisable particle tracking simulations using output from Ocean Circulation models such as the eReefs one.
Note
Parcels can be used to track passive and active particulates such as:
water,
plankton,
plastic and
fish.
In this notebook, we will first cover how to run a set of particles from exported eReefs data. Then we will show how to use particles to sample a field such as temperature and how to write a kernel that tracks the distance travelled by the particles.
See also
The OceanParcels team has designed a series of Jupyter notebooks to help people to get started with Parcels
. You can find the tutorials on this link!
Load the required Python libraries¶
First of all, load the necessary libraries:
import os
import numpy as np
import xarray as xr
import cmocean
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'))
from parcels import FieldSet, Field, ParticleSet, Variable, JITParticle
from parcels import AdvectionRK4, plotTrajectoriesFile, ErrorCode
import math
from datetime import timedelta
from operator import attrgetter
from matplotlib import pyplot as plt
#%config InlineBackend.figure_format = 'retina'
plt.ion() # To trigger the interactive inline mode
INFO: Compiled ParcelsRandom ==> /tmp/parcels-1001/libparcels_random_4cae6ef5-3eba-46fd-ab4f-692a1b6b577c.so
Build multi-file dataset¶
We will use the open_mfdataset
function from xArray
to open multiple netCDF files into a single xarray Dataset.
We will query load the GBR4km dataset from the AIMS server, so let’s first define the base URL:
# For the hydro dataset
base_url = "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-"
For the sake of the demonstration, we will only use 1 month:
month_st = 1 # Starting month
month_ed = 1 # Ending month
year = 2018 # Year
# Based on the server the file naming convention
hydrofiles = [f"{base_url}{year}-{month:02}.nc" for month in range(month_st, month_ed+1)]
Loading dataset into xArray¶
Using xArray
, we open these files into a Dataset
:
ds_hydro = xr.open_mfdataset(hydrofiles)
Clip the Dataset¶
To reduce the Dataset
size we will clip the spatial extent based on longitudinal and latitudinal values.
This is easely done using the sel
function with the slice
method.
min_lon = 146 # lower left longitude
min_lat = -21 # lower left latitude
max_lon = 150 # upper right longitude
max_lat = -16 # upper right latitude
# Defining the boundaries
lon_bnds = [min_lon, max_lon]
lat_bnds = [min_lat, max_lat]
# Performing the reduction and only taking the surface dataset (k=-1)
ds_hydro_clip = ds_hydro.sel(latitude=slice(*lat_bnds), longitude=slice(*lon_bnds), k=-1)
ds_hydro_clip
<xarray.Dataset> Dimensions: (latitude: 167, longitude: 134, time: 31) Coordinates: zc float64 dask.array<chunksize=(), meta=np.ndarray> * time (time) datetime64[ns] 2017-12-31T14:00:00 ... 2018-01-30T14:... * latitude (latitude) float64 -20.99 -20.96 -20.93 ... -16.04 -16.01 * longitude (longitude) float64 146.0 146.0 146.1 ... 149.9 150.0 150.0 Data variables: mean_cur (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> salt (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> temp (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> u (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> v (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> mean_wspeed (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> eta (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> wspeed_u (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> wspeed_v (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> Attributes: (12/19) Conventions: CF-1.0 Run_ID: 2 _CoordSysBuilder: ucar.nc2.dataset.conv.CF1Convention aims_ncaggregate_buildDate: 2020-08-21T12:50:07+10:00 aims_ncaggregate_datasetId: products__ncaggregate__ereefs__gbr4_v2__... aims_ncaggregate_firstDate: 2018-01-01T00:00:00+10:00 ... ... paramhead: GBR 4km resolution grid shoc_version: v1.1 rev(5620) technical_guide_link: https://eatlas.org.au/pydio/public/aims-... technical_guide_publish_date: 2020-08-18 title: eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d... DODS_EXTRA.Unlimited_Dimension: time
- latitude: 167
- longitude: 134
- time: 31
- zc()float64dask.array<chunksize=(), meta=np.ndarray>
- positive :
- up
- coordinate_type :
- Z
- units :
- m
- long_name :
- Z coordinate
- axis :
- Z
- _CoordinateAxisType :
- Height
- _CoordinateZisPositive :
- up
Array Chunk Bytes 8 B 8 B Shape () () Count 3 Tasks 1 Chunks Type float64 numpy.ndarray - time(time)datetime64[ns]2017-12-31T14:00:00 ... 2018-01-...
- long_name :
- Time
- standard_name :
- time
- coordinate_type :
- time
- _CoordinateAxisType :
- Time
- _ChunkSizes :
- 1024
array(['2017-12-31T14:00:00.000000000', '2018-01-01T14:00:00.000000000', '2018-01-02T14:00:00.000000000', '2018-01-03T14:00:00.000000000', '2018-01-04T14:00:00.000000000', '2018-01-05T14:00:00.000000000', '2018-01-06T14:00:00.000000000', '2018-01-07T14:00:00.000000000', '2018-01-08T14:00:00.000000000', '2018-01-09T14:00:00.000000000', '2018-01-10T14:00:00.000000000', '2018-01-11T14:00:00.000000000', '2018-01-12T14:00:00.000000000', '2018-01-13T14:00:00.000000000', '2018-01-14T14:00:00.000000000', '2018-01-15T14:00:00.000000000', '2018-01-16T14:00:00.000000000', '2018-01-17T14:00:00.000000000', '2018-01-18T14:00:00.000000000', '2018-01-19T14:00:00.000000000', '2018-01-20T14:00:00.000000000', '2018-01-21T14:00:00.000000000', '2018-01-22T14:00:00.000000000', '2018-01-23T14:00:00.000000000', '2018-01-24T14:00:00.000000000', '2018-01-25T14:00:00.000000000', '2018-01-26T14:00:00.000000000', '2018-01-27T14:00:00.000000000', '2018-01-28T14:00:00.000000000', '2018-01-29T14:00:00.000000000', '2018-01-30T14:00:00.000000000'], dtype='datetime64[ns]')
- latitude(latitude)float64-20.99 -20.96 ... -16.04 -16.01
- long_name :
- Latitude
- standard_name :
- latitude
- units :
- degrees_north
- coordinate_type :
- latitude
- projection :
- geographic
- _CoordinateAxisType :
- Lat
array([-20.986022, -20.956022, -20.926022, -20.896022, -20.866022, -20.836022, -20.806022, -20.776022, -20.746022, -20.716022, -20.686022, -20.656022, -20.626022, -20.596022, -20.566022, -20.536022, -20.506022, -20.476022, -20.446022, -20.416022, -20.386022, -20.356022, -20.326022, -20.296022, -20.266022, -20.236022, -20.206022, -20.176022, -20.146022, -20.116022, -20.086022, -20.056022, -20.026022, -19.996022, -19.966022, -19.936022, -19.906022, -19.876022, -19.846022, -19.816022, -19.786022, -19.756022, -19.726022, -19.696022, -19.666022, -19.636022, -19.606022, -19.576022, -19.546022, -19.516022, -19.486022, -19.456022, -19.426022, -19.396022, -19.366022, -19.336022, -19.306022, -19.276022, -19.246022, -19.216022, -19.186022, -19.156022, -19.126022, -19.096022, -19.066022, -19.036022, -19.006022, -18.976022, -18.946022, -18.916022, -18.886022, -18.856022, -18.826022, -18.796022, -18.766022, -18.736022, -18.706022, -18.676022, -18.646022, -18.616022, -18.586022, -18.556022, -18.526022, -18.496022, -18.466022, -18.436022, -18.406022, -18.376022, -18.346022, -18.316022, -18.286022, -18.256022, -18.226022, -18.196022, -18.166022, -18.136022, -18.106022, -18.076022, -18.046022, -18.016022, -17.986022, -17.956022, -17.926022, -17.896022, -17.866022, -17.836022, -17.806022, -17.776022, -17.746022, -17.716022, -17.686022, -17.656022, -17.626022, -17.596022, -17.566022, -17.536022, -17.506022, -17.476022, -17.446022, -17.416022, -17.386022, -17.356022, -17.326022, -17.296022, -17.266022, -17.236022, -17.206022, -17.176022, -17.146022, -17.116022, -17.086022, -17.056022, -17.026022, -16.996022, -16.966022, -16.936022, -16.906022, -16.876022, -16.846022, -16.816022, -16.786022, -16.756022, -16.726022, -16.696022, -16.666022, -16.636022, -16.606022, -16.576022, -16.546022, -16.516022, -16.486022, -16.456022, -16.426022, -16.396022, -16.366022, -16.336022, -16.306022, -16.276022, -16.246022, -16.216022, -16.186022, -16.156022, -16.126022, -16.096022, -16.066022, -16.036022, -16.006022])
- longitude(longitude)float64146.0 146.0 146.1 ... 150.0 150.0
- standard_name :
- longitude
- long_name :
- Longitude
- units :
- degrees_east
- coordinate_type :
- longitude
- projection :
- geographic
- _CoordinateAxisType :
- Lon
array([146.008788, 146.038788, 146.068788, 146.098788, 146.128788, 146.158788, 146.188788, 146.218788, 146.248788, 146.278788, 146.308788, 146.338788, 146.368788, 146.398788, 146.428788, 146.458788, 146.488788, 146.518788, 146.548788, 146.578788, 146.608788, 146.638788, 146.668788, 146.698788, 146.728788, 146.758788, 146.788788, 146.818788, 146.848788, 146.878788, 146.908788, 146.938788, 146.968788, 146.998788, 147.028788, 147.058788, 147.088788, 147.118788, 147.148788, 147.178788, 147.208788, 147.238788, 147.268788, 147.298788, 147.328788, 147.358788, 147.388788, 147.418788, 147.448788, 147.478788, 147.508788, 147.538788, 147.568788, 147.598788, 147.628788, 147.658788, 147.688788, 147.718788, 147.748788, 147.778788, 147.808788, 147.838788, 147.868788, 147.898788, 147.928788, 147.958788, 147.988788, 148.018788, 148.048788, 148.078788, 148.108788, 148.138788, 148.168788, 148.198788, 148.228788, 148.258788, 148.288788, 148.318788, 148.348788, 148.378788, 148.408788, 148.438788, 148.468788, 148.498788, 148.528788, 148.558788, 148.588788, 148.618788, 148.648788, 148.678788, 148.708788, 148.738788, 148.768788, 148.798788, 148.828788, 148.858788, 148.888788, 148.918788, 148.948788, 148.978788, 149.008788, 149.038788, 149.068788, 149.098788, 149.128788, 149.158788, 149.188788, 149.218788, 149.248788, 149.278788, 149.308788, 149.338788, 149.368788, 149.398788, 149.428788, 149.458788, 149.488788, 149.518788, 149.548788, 149.578788, 149.608788, 149.638788, 149.668788, 149.698788, 149.728788, 149.758788, 149.788788, 149.818788, 149.848788, 149.878788, 149.908788, 149.938788, 149.968788, 149.998788])
- mean_cur(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- 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]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - salt(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- substanceOrTaxon_id :
- http://sweet.jpl.nasa.gov/2.2/matrWater.owl#SaltWater
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/practical_salinity
- short_name :
- salt
- aggregation :
- Daily
- units :
- PSU
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://environment.data.gov.au/water/quality/def/unit/PSU
- long_name :
- Salinity
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - temp(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- substanceOrTaxon_id :
- http://sweet.jpl.nasa.gov/2.2/matrWater.owl#SaltWater
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_water_temperature
- short_name :
- temp
- aggregation :
- Daily
- units :
- degrees C
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#DegreeCelsius
- long_name :
- Temperature
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - u(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- vector_components :
- u v
- substanceOrTaxon_id :
- http://environment.data.gov.au/def/feature/ocean_current
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_water_velocity_eastward
- short_name :
- u
- vector_name :
- Currents
- standard_name :
- eastward_sea_water_velocity
- aggregation :
- Daily
- units :
- ms-1
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#MeterPerSecond
- long_name :
- Eastward current
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - v(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- vector_components :
- u v
- substanceOrTaxon_id :
- http://environment.data.gov.au/def/feature/ocean_current
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_water_velocity_northward
- short_name :
- v
- vector_name :
- Currents
- standard_name :
- northward_sea_water_velocity
- aggregation :
- Daily
- units :
- ms-1
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#MeterPerSecond
- long_name :
- Northward current
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - mean_wspeed(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- units :
- ms-1
- short_name :
- mean_wspeed
- aggregation :
- mean_speed
- standard_name :
- mean_wind_speed
- long_name :
- mean_wind_speed
- _ChunkSizes :
- [ 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - eta(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- substanceOrTaxon_id :
- http://environment.data.gov.au/def/feature/ocean_near_surface
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_surface_elevation
- short_name :
- eta
- standard_name :
- sea_surface_height_above_sea_level
- aggregation :
- Daily
- units :
- metre
- positive :
- up
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#Meter
- long_name :
- Surface elevation
- _ChunkSizes :
- [ 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - wspeed_u(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- short_name :
- wspeed_u
- aggregation :
- Daily
- units :
- ms-1
- long_name :
- eastward_wind
- _ChunkSizes :
- [ 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - wspeed_v(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- short_name :
- wspeed_v
- aggregation :
- Daily
- units :
- ms-1
- long_name :
- northward_wind
- _ChunkSizes :
- [ 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray
- Conventions :
- CF-1.0
- Run_ID :
- 2
- _CoordSysBuilder :
- ucar.nc2.dataset.conv.CF1Convention
- aims_ncaggregate_buildDate :
- 2020-08-21T12:50:07+10:00
- aims_ncaggregate_datasetId :
- products__ncaggregate__ereefs__gbr4_v2__daily-monthly/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_daily-monthly-2018-01
- aims_ncaggregate_firstDate :
- 2018-01-01T00:00:00+10:00
- aims_ncaggregate_inputs :
- [products__ncaggregate__ereefs__gbr4_v2__raw/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_raw_2018-01::MD5:f3d285e0aaa945e64825e628387ac6d0]
- aims_ncaggregate_lastDate :
- 2018-01-31T00:00:00+10:00
- description :
- Aggregation of raw hourly input data (from eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 subset) to daily means. Also calculates mean magnitude of wind and ocean current speeds. Data is regridded from curvilinear (per input data) to rectilinear via inverse weighted distance from up to 4 closest cells.
- hasVocab :
- 1
- history :
- 2020-08-20T12:56:06+10:00: vendor: AIMS; processing: None summaries 2020-08-21T12:50:07+10:00: vendor: AIMS; processing: Daily summaries
- metadata_link :
- https://eatlas.org.au/data/uuid/350aed53-ae0f-436e-9866-d34db7f04d2e
- paramfile :
- in.prm
- paramhead :
- GBR 4km resolution grid
- shoc_version :
- v1.1 rev(5620)
- technical_guide_link :
- https://eatlas.org.au/pydio/public/aims-ereefs-platform-technical-guide-to-derived-products-from-csiro-ereefs-models-pdf
- technical_guide_publish_date :
- 2020-08-18
- title :
- eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 daily aggregation
- DODS_EXTRA.Unlimited_Dimension :
- time
We will now drop all unnecessary variables.
Basically we will only need the current velocities (u
and v
) and the variable we want to track with the parcels (here temp
):
surf_data = ds_hydro_clip.drop(['zc','mean_wspeed','salt',
'eta','wspeed_u','wspeed_v',
'mean_cur'])
surf_data
<xarray.Dataset> Dimensions: (latitude: 167, longitude: 134, time: 31) Coordinates: * time (time) datetime64[ns] 2017-12-31T14:00:00 ... 2018-01-30T14:00:00 * latitude (latitude) float64 -20.99 -20.96 -20.93 ... -16.07 -16.04 -16.01 * longitude (longitude) float64 146.0 146.0 146.1 146.1 ... 149.9 150.0 150.0 Data variables: temp (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> u (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> v (time, latitude, longitude) float32 dask.array<chunksize=(31, 167, 134), meta=np.ndarray> Attributes: (12/19) Conventions: CF-1.0 Run_ID: 2 _CoordSysBuilder: ucar.nc2.dataset.conv.CF1Convention aims_ncaggregate_buildDate: 2020-08-21T12:50:07+10:00 aims_ncaggregate_datasetId: products__ncaggregate__ereefs__gbr4_v2__... aims_ncaggregate_firstDate: 2018-01-01T00:00:00+10:00 ... ... paramhead: GBR 4km resolution grid shoc_version: v1.1 rev(5620) technical_guide_link: https://eatlas.org.au/pydio/public/aims-... technical_guide_publish_date: 2020-08-18 title: eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 d... DODS_EXTRA.Unlimited_Dimension: time
- latitude: 167
- longitude: 134
- time: 31
- time(time)datetime64[ns]2017-12-31T14:00:00 ... 2018-01-...
- long_name :
- Time
- standard_name :
- time
- coordinate_type :
- time
- _CoordinateAxisType :
- Time
- _ChunkSizes :
- 1024
array(['2017-12-31T14:00:00.000000000', '2018-01-01T14:00:00.000000000', '2018-01-02T14:00:00.000000000', '2018-01-03T14:00:00.000000000', '2018-01-04T14:00:00.000000000', '2018-01-05T14:00:00.000000000', '2018-01-06T14:00:00.000000000', '2018-01-07T14:00:00.000000000', '2018-01-08T14:00:00.000000000', '2018-01-09T14:00:00.000000000', '2018-01-10T14:00:00.000000000', '2018-01-11T14:00:00.000000000', '2018-01-12T14:00:00.000000000', '2018-01-13T14:00:00.000000000', '2018-01-14T14:00:00.000000000', '2018-01-15T14:00:00.000000000', '2018-01-16T14:00:00.000000000', '2018-01-17T14:00:00.000000000', '2018-01-18T14:00:00.000000000', '2018-01-19T14:00:00.000000000', '2018-01-20T14:00:00.000000000', '2018-01-21T14:00:00.000000000', '2018-01-22T14:00:00.000000000', '2018-01-23T14:00:00.000000000', '2018-01-24T14:00:00.000000000', '2018-01-25T14:00:00.000000000', '2018-01-26T14:00:00.000000000', '2018-01-27T14:00:00.000000000', '2018-01-28T14:00:00.000000000', '2018-01-29T14:00:00.000000000', '2018-01-30T14:00:00.000000000'], dtype='datetime64[ns]')
- latitude(latitude)float64-20.99 -20.96 ... -16.04 -16.01
- long_name :
- Latitude
- standard_name :
- latitude
- units :
- degrees_north
- coordinate_type :
- latitude
- projection :
- geographic
- _CoordinateAxisType :
- Lat
array([-20.986022, -20.956022, -20.926022, -20.896022, -20.866022, -20.836022, -20.806022, -20.776022, -20.746022, -20.716022, -20.686022, -20.656022, -20.626022, -20.596022, -20.566022, -20.536022, -20.506022, -20.476022, -20.446022, -20.416022, -20.386022, -20.356022, -20.326022, -20.296022, -20.266022, -20.236022, -20.206022, -20.176022, -20.146022, -20.116022, -20.086022, -20.056022, -20.026022, -19.996022, -19.966022, -19.936022, -19.906022, -19.876022, -19.846022, -19.816022, -19.786022, -19.756022, -19.726022, -19.696022, -19.666022, -19.636022, -19.606022, -19.576022, -19.546022, -19.516022, -19.486022, -19.456022, -19.426022, -19.396022, -19.366022, -19.336022, -19.306022, -19.276022, -19.246022, -19.216022, -19.186022, -19.156022, -19.126022, -19.096022, -19.066022, -19.036022, -19.006022, -18.976022, -18.946022, -18.916022, -18.886022, -18.856022, -18.826022, -18.796022, -18.766022, -18.736022, -18.706022, -18.676022, -18.646022, -18.616022, -18.586022, -18.556022, -18.526022, -18.496022, -18.466022, -18.436022, -18.406022, -18.376022, -18.346022, -18.316022, -18.286022, -18.256022, -18.226022, -18.196022, -18.166022, -18.136022, -18.106022, -18.076022, -18.046022, -18.016022, -17.986022, -17.956022, -17.926022, -17.896022, -17.866022, -17.836022, -17.806022, -17.776022, -17.746022, -17.716022, -17.686022, -17.656022, -17.626022, -17.596022, -17.566022, -17.536022, -17.506022, -17.476022, -17.446022, -17.416022, -17.386022, -17.356022, -17.326022, -17.296022, -17.266022, -17.236022, -17.206022, -17.176022, -17.146022, -17.116022, -17.086022, -17.056022, -17.026022, -16.996022, -16.966022, -16.936022, -16.906022, -16.876022, -16.846022, -16.816022, -16.786022, -16.756022, -16.726022, -16.696022, -16.666022, -16.636022, -16.606022, -16.576022, -16.546022, -16.516022, -16.486022, -16.456022, -16.426022, -16.396022, -16.366022, -16.336022, -16.306022, -16.276022, -16.246022, -16.216022, -16.186022, -16.156022, -16.126022, -16.096022, -16.066022, -16.036022, -16.006022])
- longitude(longitude)float64146.0 146.0 146.1 ... 150.0 150.0
- standard_name :
- longitude
- long_name :
- Longitude
- units :
- degrees_east
- coordinate_type :
- longitude
- projection :
- geographic
- _CoordinateAxisType :
- Lon
array([146.008788, 146.038788, 146.068788, 146.098788, 146.128788, 146.158788, 146.188788, 146.218788, 146.248788, 146.278788, 146.308788, 146.338788, 146.368788, 146.398788, 146.428788, 146.458788, 146.488788, 146.518788, 146.548788, 146.578788, 146.608788, 146.638788, 146.668788, 146.698788, 146.728788, 146.758788, 146.788788, 146.818788, 146.848788, 146.878788, 146.908788, 146.938788, 146.968788, 146.998788, 147.028788, 147.058788, 147.088788, 147.118788, 147.148788, 147.178788, 147.208788, 147.238788, 147.268788, 147.298788, 147.328788, 147.358788, 147.388788, 147.418788, 147.448788, 147.478788, 147.508788, 147.538788, 147.568788, 147.598788, 147.628788, 147.658788, 147.688788, 147.718788, 147.748788, 147.778788, 147.808788, 147.838788, 147.868788, 147.898788, 147.928788, 147.958788, 147.988788, 148.018788, 148.048788, 148.078788, 148.108788, 148.138788, 148.168788, 148.198788, 148.228788, 148.258788, 148.288788, 148.318788, 148.348788, 148.378788, 148.408788, 148.438788, 148.468788, 148.498788, 148.528788, 148.558788, 148.588788, 148.618788, 148.648788, 148.678788, 148.708788, 148.738788, 148.768788, 148.798788, 148.828788, 148.858788, 148.888788, 148.918788, 148.948788, 148.978788, 149.008788, 149.038788, 149.068788, 149.098788, 149.128788, 149.158788, 149.188788, 149.218788, 149.248788, 149.278788, 149.308788, 149.338788, 149.368788, 149.398788, 149.428788, 149.458788, 149.488788, 149.518788, 149.548788, 149.578788, 149.608788, 149.638788, 149.668788, 149.698788, 149.728788, 149.758788, 149.788788, 149.818788, 149.848788, 149.878788, 149.908788, 149.938788, 149.968788, 149.998788])
- temp(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- substanceOrTaxon_id :
- http://sweet.jpl.nasa.gov/2.2/matrWater.owl#SaltWater
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_water_temperature
- short_name :
- temp
- aggregation :
- Daily
- units :
- degrees C
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#DegreeCelsius
- long_name :
- Temperature
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - u(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- vector_components :
- u v
- substanceOrTaxon_id :
- http://environment.data.gov.au/def/feature/ocean_current
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_water_velocity_eastward
- short_name :
- u
- vector_name :
- Currents
- standard_name :
- eastward_sea_water_velocity
- aggregation :
- Daily
- units :
- ms-1
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#MeterPerSecond
- long_name :
- Eastward current
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray - v(time, latitude, longitude)float32dask.array<chunksize=(31, 167, 134), meta=np.ndarray>
- vector_components :
- u v
- substanceOrTaxon_id :
- http://environment.data.gov.au/def/feature/ocean_current
- scaledQuantityKind_id :
- http://environment.data.gov.au/def/property/sea_water_velocity_northward
- short_name :
- v
- vector_name :
- Currents
- standard_name :
- northward_sea_water_velocity
- aggregation :
- Daily
- units :
- ms-1
- medium_id :
- http://environment.data.gov.au/def/feature/ocean
- unit_id :
- http://qudt.org/vocab/unit#MeterPerSecond
- long_name :
- Northward current
- _ChunkSizes :
- [ 1 1 133 491]
Array Chunk Bytes 2.77 MB 2.77 MB Shape (31, 167, 134) (31, 167, 134) Count 3 Tasks 1 Chunks Type float32 numpy.ndarray
- Conventions :
- CF-1.0
- Run_ID :
- 2
- _CoordSysBuilder :
- ucar.nc2.dataset.conv.CF1Convention
- aims_ncaggregate_buildDate :
- 2020-08-21T12:50:07+10:00
- aims_ncaggregate_datasetId :
- products__ncaggregate__ereefs__gbr4_v2__daily-monthly/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_daily-monthly-2018-01
- aims_ncaggregate_firstDate :
- 2018-01-01T00:00:00+10:00
- aims_ncaggregate_inputs :
- [products__ncaggregate__ereefs__gbr4_v2__raw/EREEFS_AIMS-CSIRO_gbr4_v2_hydro_raw_2018-01::MD5:f3d285e0aaa945e64825e628387ac6d0]
- aims_ncaggregate_lastDate :
- 2018-01-31T00:00:00+10:00
- description :
- Aggregation of raw hourly input data (from eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 subset) to daily means. Also calculates mean magnitude of wind and ocean current speeds. Data is regridded from curvilinear (per input data) to rectilinear via inverse weighted distance from up to 4 closest cells.
- hasVocab :
- 1
- history :
- 2020-08-20T12:56:06+10:00: vendor: AIMS; processing: None summaries 2020-08-21T12:50:07+10:00: vendor: AIMS; processing: Daily summaries
- metadata_link :
- https://eatlas.org.au/data/uuid/350aed53-ae0f-436e-9866-d34db7f04d2e
- paramfile :
- in.prm
- paramhead :
- GBR 4km resolution grid
- shoc_version :
- v1.1 rev(5620)
- technical_guide_link :
- https://eatlas.org.au/pydio/public/aims-ereefs-platform-technical-guide-to-derived-products-from-csiro-ereefs-models-pdf
- technical_guide_publish_date :
- 2020-08-18
- title :
- eReefs AIMS-CSIRO GBR4 Hydrodynamic v2 daily aggregation
- DODS_EXTRA.Unlimited_Dimension :
- time
OceanParcels will need to read the file locally and we save it using the Xarray
funciton to_netcdf
. Otherwise you could use the from_xarray_dataset
function to directly read the dataset in Parcels
.
Note
Here the cell has been commented to make the notebook run faster, but you will need to uncomment it for running your how experiment…
data_name ='ereefdata.nc'
# try:
# os.remove(data_name)
# except OSError:
# pass
# surf_data.to_netcdf(path=data_name)
Reading velocities into Parcels¶
As we used the NetCDF
format, it is fairly easy to load the velocity fields into the FieldSet.from_netcdf()
function available in Parcels
:
filenames = {'U': data_name,
'V': data_name}
Then, define a dictionary of the variables (U
and V
) and dimensions (lon
, lat
and time
; note that in this case there is no depth because we only took the surface variables from the eReefs
datastet:
variables = {'U': 'u',
'V': 'v'}
dimensions = {'lat': 'latitude',
'lon': 'longitude',
'time': 'time'}
Finally, we read in the fieldset using the FieldSet.from_netcdf
function with the above-defined filenames
, variables
and dimensions
fieldset = FieldSet.from_netcdf(filenames, variables, dimensions)
WARNING: Casting lon data to np.float32
WARNING: Casting lat data to np.float32
WARNING: Casting depth data to np.float32
Now define a ParticleSet
, in this case with 5 particles starting on a line between (147E, 18.5S) and (148E, 17.5S) using the ParticleSet.from_line
constructor method:
pset = ParticleSet.from_line(fieldset=fieldset, pclass=JITParticle,
size=5, # releasing 5 particles
start=(147, -18.5), # releasing on a line: the start longitude and latitude
finish=(148, -17.5)) # releasing on a line: the end longitude and latitude
Note
Another approach if you want to list a series of initial positions consists in using the from_list
function which takes:
lon – List of initial longitude values for particles
lat – List of initial latitude values for particles
Now we want to advect the particles. However, the eReefs data that we loaded in is only for a limited, regional domain and particles might be able to leave this domain.
We therefore need to tell Parcels
that particles that leave the domain need to be deleted. We do that using a Recovery Kernel
, which will be invoked when a particle encounters an ErrorOutOfBounds error
:
def DeleteParticle(particle, fieldset, time):
particle.delete()
Now we can advect the particles by executing the ParticleSet for 30 days using 4th order Runge-Kutta (AdvectionRK4
):
output_nc = 'CurrentParticles.nc'
try:
os.remove(output_nc)
except OSError:
pass
output_file = pset.ParticleFile(name=output_nc,
outputdt=timedelta(hours=6))
pset.execute(AdvectionRK4,
runtime=timedelta(days=30),
dt=timedelta(minutes=5),
output_file=output_file,
recovery={ErrorCode.ErrorOutOfBounds: DeleteParticle})
INFO: Compiled JITParticleAdvectionRK4 ==> /tmp/parcels-1001/0afc2149946c974dbfdf33957dcb4b68_0.so
We now export the particles recorded every 6h in our output file:
output_file.export()
Plotting outputs¶
We open the particles file with Xarray
:
parcels = xr.open_dataset(output_nc)
parcels
<xarray.Dataset> Dimensions: (obs: 121, traj: 5) Dimensions without coordinates: obs, traj Data variables: trajectory (traj, obs) float64 0.0 0.0 0.0 0.0 0.0 ... 4.0 4.0 4.0 4.0 4.0 time (traj, obs) datetime64[ns] 2017-12-31T14:00:00 ... 2018-01-30... lat (traj, obs) float32 -18.5 -18.54 -18.57 ... -18.79 -18.8 -18.82 lon (traj, obs) float32 147.0 147.0 147.1 ... 146.8 146.8 146.9 z (traj, obs) float32 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 Attributes: feature_type: trajectory Conventions: CF-1.6/CF-1.7 ncei_template_version: NCEI_NetCDF_Trajectory_Template_v2.0 parcels_version: 2.2.2 parcels_mesh: spherical
- obs: 121
- traj: 5
- trajectory(traj, obs)float64...
- long_name :
- Unique identifier for each particle
- cf_role :
- trajectory_id
array([[ 0., 0., 0., ..., 0., 0., 0.], [ 1., 1., 1., ..., 1., 1., 1.], [ 2., 2., 2., ..., nan, nan, nan], [ 3., 3., 3., ..., 3., 3., 3.], [ 4., 4., 4., ..., 4., 4., 4.]])
- time(traj, obs)datetime64[ns]...
- long_name :
- standard_name :
- time
- axis :
- T
array([['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., 'NaT', 'NaT', 'NaT'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000']], dtype='datetime64[ns]')
- lat(traj, obs)float32...
- long_name :
- standard_name :
- latitude
- units :
- degrees_north
- axis :
- Y
array([[-18.5 , -18.536953, -18.572428, ..., -18.018187, -18.020998, -18.023403], [-18.25 , -18.332228, -18.405561, ..., -19.08771 , -19.095633, -19.103647], [-18. , -18.056112, -18.106573, ..., nan, nan, nan], [-17.75 , -17.754168, -17.761713, ..., -19.141693, -19.159647, -19.17944 ], [-17.5 , -17.515566, -17.52634 , ..., -18.786823, -18.800255, -18.815973]], dtype=float32)
- lon(traj, obs)float32...
- long_name :
- standard_name :
- longitude
- units :
- degrees_east
- axis :
- X
array([[147. , 147.03894, 147.08493, ..., 146.15564, 146.15479, 146.15369], [147.25 , 147.34767, 147.46211, ..., 146.91318, 146.90988, 146.9066 ], [147.5 , 147.59273, 147.68913, ..., nan, nan, nan], [147.75 , 147.79071, 147.83481, ..., 147.46735, 147.48056, 147.49503], [148. , 148.02367, 148.0556 , ..., 146.8316 , 146.84024, 146.85043]], dtype=float32)
- z(traj, obs)float32...
- long_name :
- standard_name :
- depth
- units :
- m
- positive :
- down
array([[ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., nan, nan, nan], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.]], dtype=float32)
- feature_type :
- trajectory
- Conventions :
- CF-1.6/CF-1.7
- ncei_template_version :
- NCEI_NetCDF_Trajectory_Template_v2.0
- parcels_version :
- 2.2.2
- parcels_mesh :
- spherical
We will now use the Xarray
plot
function as we have done in the past…
See also
Check out this link to see how to use Xarray
’s convenient matplotlib-backed plotting interface to visualize your datasets!
# Figure size
size = (9, 10)
# Color from cmocean
color = cmocean.cm.speed
# 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([min_lon, max_lon, min_lat, max_lat], ccrs.PlateCarree())
# Plotting using Matplotlib
# We plot the PH at the surface at the final recorded time interval
cf = ds_hydro_clip.mean_cur.isel(time=10).plot(
transform=ccrs.PlateCarree(), cmap=color,
vmin = 0.1, vmax = 1.0, alpha=0.2,
add_colorbar=False
)
# Color bar
cbar = fig.colorbar(cf, ax=ax, fraction=0.027, pad=0.045,
orientation="horizontal")
cbar.set_label(ds_hydro_clip.mean_cur.long_name+' '+ds_hydro_clip.mean_cur.units, rotation=0,
labelpad=5, fontsize=10)
cbar.ax.tick_params(labelsize=8)
# Title
plt.title('Parcels evolution',
fontsize=13
)
# 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)
for k in range(parcels.lon.shape[0]):
ax.scatter(parcels.lon.isel(traj=k), parcels.lat.isel(traj=k), s=40, edgecolors='w',
linewidth=0.2, transform=ccrs.PlateCarree()).set_zorder(11)
plt.tight_layout()
plt.show()
fig.clear()
plt.close(fig)
plt.clf()
<Figure size 432x288 with 0 Axes>
Sampling a Field with Particles¶
One typical use case of particle simulations is to sample a Field (such as temperature, salinity, surface hight) along a particle trajectory. In Parcels
, this is very easy to do, with a custom Kernel
.
Let’s define the FieldSet
as above:
filenames = {'U': data_name,
'V': data_name,
}
variables = {'U': 'u',
'V': 'v',
}
dimensions = {'lat': 'latitude',
'lon': 'longitude',
'time': 'time'}
Finally, we read in the fieldset using the FieldSet.from_netcdf
function with the above-defined filenames
, variables
and dimensions
fieldset = FieldSet.from_netcdf(filenames, variables, dimensions)
We now add the field temperature
to the FieldSet
filenames = {'lat': data_name,
'lon': data_name,
'data': data_name}
variable = ('T', 'temp')
dimensions = {'lat': 'latitude',
'lon': 'longitude',
'time': 'time'}
field = Field.from_netcdf(filenames, variable, dimensions)
fieldset.add_field(field)
Now define a new Particle class
that has an extra Variable: the temperature. We initialise this by sampling the fieldset2.T
field.
class SampleParticle(JITParticle): # Define a new particle class
t = Variable('t', initial=fieldset.T) # Variable 't' initialised by sampling the temperature
Now define a ParticleSet
using the from_line
method also used above. Plot the pset
and print their temperature values t
:
pset = ParticleSet.from_line(fieldset=fieldset, pclass=SampleParticle,
size=5, # releasing 5 particles
start=(147, -18.5), # releasing on a line: the start longitude and latitude
finish=(148, -17.5), # releasing on a line: the end longitude and latitude
time=0)
print('t values before execution:', [p.t for p in pset])
WARNING: Particle initialisation from field can be very slow as it is computed in scipy mode.
t values before execution: [28.90722, 29.133469, 29.18559, 29.126036, 29.002943]
Now create a custom function that samples the fieldset.T
field at the particle location. Cast this function to a Kernel.
def SampleT(particle, fieldset, time): # Custom function that samples fieldset2.T at particle location
particle.t = fieldset.T[time, particle.depth, particle.lat, particle.lon]
k_sample = pset.Kernel(SampleT) # Casting the SampleT function to a kernel.
Finally, execute the pset with a combination of the AdvectionRK4
and SampleT
kernels, plot the pset and print their new temperature values t
:
output_nc_temp = 'CurrentParticlesTemp.nc'
try:
os.remove(output_nc_temp)
except OSError:
pass
output_file_temp = pset.ParticleFile(name=output_nc_temp,
outputdt=timedelta(hours=6))
pset.execute(AdvectionRK4 + k_sample, # Add kernels using the + operator.
runtime=timedelta(days=30),
dt=timedelta(minutes=5),
output_file=output_file_temp,
recovery={ErrorCode.ErrorOutOfBounds: DeleteParticle})
output_file_temp.export()
INFO: Compiled SampleParticleAdvectionRK4SampleT ==> /tmp/parcels-1001/10e9ccb780cd6a66e3739935a7c2464b_0.so
Plotting the result¶
We can extract the netcdf file values…
parcels_temp = xr.open_dataset(output_nc_temp)
parcels_temp
<xarray.Dataset> Dimensions: (obs: 121, traj: 5) Dimensions without coordinates: obs, traj Data variables: trajectory (traj, obs) float64 5.0 5.0 5.0 5.0 5.0 ... 9.0 9.0 9.0 9.0 9.0 time (traj, obs) datetime64[ns] 2017-12-31T14:00:00 ... 2018-01-30... lat (traj, obs) float32 -18.5 -18.54 -18.57 ... -18.79 -18.8 -18.82 lon (traj, obs) float32 147.0 147.0 147.1 ... 146.8 146.8 146.9 z (traj, obs) float32 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 t (traj, obs) float32 28.91 28.92 28.91 ... 28.85 28.95 29.04 Attributes: feature_type: trajectory Conventions: CF-1.6/CF-1.7 ncei_template_version: NCEI_NetCDF_Trajectory_Template_v2.0 parcels_version: 2.2.2 parcels_mesh: spherical
- obs: 121
- traj: 5
- trajectory(traj, obs)float64...
- long_name :
- Unique identifier for each particle
- cf_role :
- trajectory_id
array([[ 5., 5., 5., ..., 5., 5., 5.], [ 6., 6., 6., ..., 6., 6., 6.], [ 7., 7., 7., ..., nan, nan, nan], [ 8., 8., 8., ..., 8., 8., 8.], [ 9., 9., 9., ..., 9., 9., 9.]])
- time(traj, obs)datetime64[ns]...
- long_name :
- standard_name :
- time
- axis :
- T
array([['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., 'NaT', 'NaT', 'NaT'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T20:00:00.000000000', '2018-01-01T02:00:00.000000000', ..., '2018-01-30T02:00:00.000000000', '2018-01-30T08:00:00.000000000', '2018-01-30T14:00:00.000000000']], dtype='datetime64[ns]')
- lat(traj, obs)float32...
- long_name :
- standard_name :
- latitude
- units :
- degrees_north
- axis :
- Y
array([[-18.5 , -18.536953, -18.572428, ..., -18.018187, -18.020998, -18.023403], [-18.25 , -18.332228, -18.405561, ..., -19.08771 , -19.095633, -19.103647], [-18. , -18.056112, -18.106573, ..., nan, nan, nan], [-17.75 , -17.754168, -17.761713, ..., -19.141693, -19.159647, -19.17944 ], [-17.5 , -17.515566, -17.52634 , ..., -18.786823, -18.800255, -18.815973]], dtype=float32)
- lon(traj, obs)float32...
- long_name :
- standard_name :
- longitude
- units :
- degrees_east
- axis :
- X
array([[147. , 147.03894, 147.08493, ..., 146.15564, 146.15479, 146.15369], [147.25 , 147.34767, 147.46211, ..., 146.91318, 146.90988, 146.9066 ], [147.5 , 147.59273, 147.68913, ..., nan, nan, nan], [147.75 , 147.79071, 147.83481, ..., 147.46735, 147.48056, 147.49503], [148. , 148.02367, 148.0556 , ..., 146.8316 , 146.84024, 146.85043]], dtype=float32)
- z(traj, obs)float32...
- long_name :
- standard_name :
- depth
- units :
- m
- positive :
- down
array([[ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., nan, nan, nan], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.]], dtype=float32)
- t(traj, obs)float32...
- long_name :
- standard_name :
- t
- units :
- unknown
array([[28.90722 , 28.916945, 28.908466, ..., 29.401653, 29.47714 , 29.552055], [29.133469, 29.104572, 29.055515, ..., 29.095577, 29.188328, 29.279562], [29.18559 , 29.12668 , 29.089144, ..., nan, nan, nan], [29.126036, 29.110771, 29.094656, ..., 28.989418, 29.122204, 29.246988], [29.002943, 28.962803, 28.936161, ..., 28.853973, 28.94527 , 29.044361]], dtype=float32)
- feature_type :
- trajectory
- Conventions :
- CF-1.6/CF-1.7
- ncei_template_version :
- NCEI_NetCDF_Trajectory_Template_v2.0
- parcels_version :
- 2.2.2
- parcels_mesh :
- spherical
And plot the resulting parcels output on a map:
# Figure size
size = (9, 10)
# Color from cmocean
color = cmocean.cm.speed
# 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([min_lon, max_lon, min_lat, max_lat], ccrs.PlateCarree())
# Plotting using Matplotlib
# We plot the PH at the surface at the final recorded time interval
cf = ds_hydro_clip.mean_cur.isel(time=10).plot(
transform=ccrs.PlateCarree(), cmap=color,
vmin = 0.1, vmax = 1.0, alpha=0.2,
add_colorbar=False
)
# Color bar
cbar = fig.colorbar(cf, ax=ax, fraction=0.027, pad=0.045,
orientation="horizontal")
cbar.set_label(ds_hydro_clip.mean_cur.long_name+' '+ds_hydro_clip.mean_cur.units, rotation=0,
labelpad=5, fontsize=10)
cbar.ax.tick_params(labelsize=8)
# Title
plt.title('Parcels evolution coloured by surface temperature',
fontsize=13
)
# 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)
distmin = parcels_temp.t.min().item()
distmax = parcels_temp.t.max().item()
for k in range(parcels_temp.lon.shape[0]):
sc = plt.scatter(parcels_temp.lon.isel(traj=k), parcels_temp.lat.isel(traj=k), s=40,
c=parcels_temp.t.isel(traj=k), edgecolors='k',
cmap=cmocean.cm.balance, vmin=distmin, vmax=distmax,
linewidth=0.2, transform=ccrs.PlateCarree()).set_zorder(11)
# Color bar
cbar2 = plt.colorbar(sc, ax=ax, fraction=0.027, pad=0.045)
cbar2.set_label('Surface temperature in degree C', rotation=90,
labelpad=5, fontsize=10)
cbar2.ax.tick_params(labelsize=8)
plt.tight_layout()
plt.show()
fig.clear()
plt.close(fig)
plt.clf()
<Figure size 432x288 with 0 Axes>
Calculating distance travelled¶
As a second example of what custom kernels can do, we will now show how to create a kernel that logs the total distance that particles have travelled.
First, we need to create a new Particle class
that includes three extra variables. The distance variable will be written to output, but the auxiliary variables prev_lon
and prev_lat
won’t be written to output (can be controlled using the to_write
keyword)
class DistParticle(JITParticle): # Define a new particle class that contains three extra variables
distance = Variable('distance', initial=0., dtype=np.float32) # the distance travelled
prev_lon = Variable('prev_lon', dtype=np.float32, to_write=False,
initial=attrgetter('lon')) # the previous longitude
prev_lat = Variable('prev_lat', dtype=np.float32, to_write=False,
initial=attrgetter('lat')) # the previous latitude.
Now define a new function TotalDistance
that calculates the sum of Euclidean distances between the old and new locations in each RK4
step:
def TotalDistance(particle, fieldset, time):
# Calculate the distance in latitudinal direction (using 1.11e2 kilometer per degree latitude)
lat_dist = (particle.lat - particle.prev_lat) * 1.11e2
# Calculate the distance in longitudinal direction, using cosine(latitude) - spherical earth
lon_dist = (particle.lon - particle.prev_lon) * 1.11e2 * math.cos(particle.lat * math.pi / 180)
# Calculate the total Euclidean distance travelled by the particle
particle.distance += math.sqrt(math.pow(lon_dist, 2) + math.pow(lat_dist, 2))
particle.prev_lon = particle.lon # Set the stored values for next iteration.
particle.prev_lat = particle.lat
Note
Here it is assumed that the latitude and longitude are measured in degrees North and East, respectively. However, some datasets give them measured in (kilo)meters, in which case we must not include the factor 1.11e2.
We will run the TotalDistance
function on a ParticleSet
containing the five particles within the eReefs fieldset similar to the one we did above.
Note that pclass=DistParticle
in this case.
filenames = {'U': data_name,
'V': data_name,
}
variables = {'U': 'u',
'V': 'v'}
dimensions = {'lat': 'latitude',
'lon': 'longitude',
'time': 'time'}
fieldset = FieldSet.from_netcdf(filenames, variables, dimensions)
pset = ParticleSet.from_line(fieldset=fieldset,
pclass=DistParticle,
size=5, # releasing 5 particles
start=(147, -18.5), # releasing on a line: the start longitude and latitude
finish=(148, -17.5), # releasing on a line: the end longitude and latitude
)
Again we define a new kernel to include the function written above and execute the ParticleSet
.
output_nc_dist = 'CurrentParticlesDist.nc'
try:
os.remove(output_nc_dist)
except OSError:
pass
file_dist = pset.ParticleFile(name=output_nc_dist,
outputdt=timedelta(hours=1))
k_dist = pset.Kernel(TotalDistance) # Casting the TotalDistance function to a kernel.
pset.execute(AdvectionRK4 + k_dist, # Add kernels using the + operator.
runtime=timedelta(days=30),
dt=timedelta(minutes=5),
output_file=file_dist,
recovery={ErrorCode.ErrorOutOfBounds: DeleteParticle})
file_dist.export()
INFO: Compiled DistParticleAdvectionRK4TotalDistance ==> /tmp/parcels-1001/db33ec2ecce7d6d05221b0c0912c7346_0.so
We can now print the distance in km that each particle has travelled:
print([p.distance for p in pset]) # the distances in km travelled by the particles
[278.97974, 459.3084, 507.74344, 508.9902]
Plotting the result¶
We can extract the netcdf file values…
parcels_dist = xr.open_dataset(output_nc_dist)
parcels_dist
<xarray.Dataset> Dimensions: (obs: 721, traj: 5) Dimensions without coordinates: obs, traj Data variables: trajectory (traj, obs) float64 10.0 10.0 10.0 10.0 ... 14.0 14.0 14.0 14.0 time (traj, obs) datetime64[ns] 2017-12-31T14:00:00 ... 2018-01-30... lat (traj, obs) float32 -18.5 -18.51 -18.51 ... -18.81 -18.81 -18.82 lon (traj, obs) float32 147.0 147.0 147.0 ... 146.8 146.8 146.9 z (traj, obs) float32 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 distance (traj, obs) float32 0.0 0.9391 1.884 2.838 ... 508.3 508.6 509.0 Attributes: feature_type: trajectory Conventions: CF-1.6/CF-1.7 ncei_template_version: NCEI_NetCDF_Trajectory_Template_v2.0 parcels_version: 2.2.2 parcels_mesh: spherical
- obs: 721
- traj: 5
- trajectory(traj, obs)float64...
- long_name :
- Unique identifier for each particle
- cf_role :
- trajectory_id
array([[10., 10., 10., ..., 10., 10., 10.], [11., 11., 11., ..., 11., 11., 11.], [12., 12., 12., ..., nan, nan, nan], [13., 13., 13., ..., 13., 13., 13.], [14., 14., 14., ..., 14., 14., 14.]])
- time(traj, obs)datetime64[ns]...
- long_name :
- standard_name :
- time
- axis :
- T
array([['2017-12-31T14:00:00.000000000', '2017-12-31T15:00:00.000000000', '2017-12-31T16:00:00.000000000', ..., '2018-01-30T12:00:00.000000000', '2018-01-30T13:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T15:00:00.000000000', '2017-12-31T16:00:00.000000000', ..., '2018-01-30T12:00:00.000000000', '2018-01-30T13:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T15:00:00.000000000', '2017-12-31T16:00:00.000000000', ..., 'NaT', 'NaT', 'NaT'], ['2017-12-31T14:00:00.000000000', '2017-12-31T15:00:00.000000000', '2017-12-31T16:00:00.000000000', ..., '2018-01-30T12:00:00.000000000', '2018-01-30T13:00:00.000000000', '2018-01-30T14:00:00.000000000'], ['2017-12-31T14:00:00.000000000', '2017-12-31T15:00:00.000000000', '2017-12-31T16:00:00.000000000', ..., '2018-01-30T12:00:00.000000000', '2018-01-30T13:00:00.000000000', '2018-01-30T14:00:00.000000000']], dtype='datetime64[ns]')
- lat(traj, obs)float32...
- long_name :
- standard_name :
- latitude
- units :
- degrees_north
- axis :
- Y
array([[-18.5 , -18.506329, -18.512539, ..., -18.022646, -18.023035, -18.023403], [-18.25 , -18.263754, -18.277433, ..., -19.101013, -19.10234 , -19.103647], [-18. , -18.009752, -18.019323, ..., nan, nan, nan], [-17.75 , -17.750454, -17.751007, ..., -19.17263 , -19.176006, -19.17944 ], [-17.5 , -17.502817, -17.50557 , ..., -18.8105 , -18.813204, -18.815973]], dtype=float32)
- lon(traj, obs)float32...
- long_name :
- standard_name :
- longitude
- units :
- degrees_east
- axis :
- X
array([[147. , 147.00592, 147.01205, ..., 146.15405, 146.15387, 146.15369], [147.25 , 147.26562, 147.28152, ..., 146.90768, 146.90714, 146.9066 ], [147.5 , 147.51506, 147.53032, ..., nan, nan, nan], [147.75 , 147.75659, 147.76323, ..., 147.49008, 147.49246, 147.49503], [148. , 148.00334, 148.00691, ..., 146.84683, 146.8486 , 146.85043]], dtype=float32)
- z(traj, obs)float32...
- long_name :
- standard_name :
- depth
- units :
- m
- positive :
- down
array([[ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., nan, nan, nan], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.]], dtype=float32)
- distance(traj, obs)float32...
- long_name :
- standard_name :
- distance
- units :
- unknown
array([[0.000000e+00, 9.391081e-01, 1.883644e+00, ..., 2.788873e+02, 2.789345e+02, 2.789797e+02], [0.000000e+00, 2.245824e+00, 4.507293e+00, ..., 4.589945e+02, 4.591527e+02, 4.593084e+02], [0.000000e+00, 1.923405e+00, 3.852935e+00, ..., nan, nan, nan], [0.000000e+00, 6.986855e-01, 1.403066e+00, ..., 5.068269e+02, 5.072771e+02, 5.077434e+02], [0.000000e+00, 4.721994e-01, 9.582659e-01, ..., 5.082744e+02, 5.086276e+02, 5.089902e+02]], dtype=float32)
- feature_type :
- trajectory
- Conventions :
- CF-1.6/CF-1.7
- ncei_template_version :
- NCEI_NetCDF_Trajectory_Template_v2.0
- parcels_version :
- 2.2.2
- parcels_mesh :
- spherical
And plot the resulting parcels output on a map:
# Figure size
size = (9, 10)
# Color from cmocean
color = cmocean.cm.speed
# 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([min_lon, max_lon, min_lat, max_lat], ccrs.PlateCarree())
# Plotting using Matplotlib
# We plot the PH at the surface at the final recorded time interval
cf = ds_hydro_clip.mean_cur.isel(time=10).plot(
transform=ccrs.PlateCarree(), cmap=color,
vmin = 0.1, vmax = 1.0, alpha=0.2,
add_colorbar=False
)
# Color bar
cbar = fig.colorbar(cf, ax=ax, fraction=0.027, pad=0.045,
orientation="horizontal")
cbar.set_label(ds_hydro_clip.mean_cur.long_name+' '+ds_hydro_clip.mean_cur.units, rotation=0,
labelpad=5, fontsize=10)
cbar.ax.tick_params(labelsize=8)
# Title
plt.title('Parcels evolution coloured by travelled distance',
fontsize=13
)
# 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)
distmin = parcels_dist.distance.min().item()
distmax = parcels_dist.distance.max().item()
for k in range(parcels_dist.lon.shape[0]):
sc = plt.scatter(parcels_dist.lon.isel(traj=k), parcels_dist.lat.isel(traj=k), s=40,
c=parcels_dist.distance.isel(traj=k), edgecolors='w',
cmap='jet', vmin=distmin, vmax=distmax,
linewidth=0.2, transform=ccrs.PlateCarree()).set_zorder(11)
# Color bar
cbar2 = plt.colorbar(sc, ax=ax, fraction=0.027, pad=0.045)
cbar2.set_label('Travelled distance in km', rotation=90,
labelpad=5, fontsize=10)
cbar2.ax.tick_params(labelsize=8)
plt.tight_layout()
plt.show()
fig.clear()
plt.close(fig)
plt.clf()
<Figure size 432x288 with 0 Axes>
See also
Tutorial on how to analyse Parcels output