11import os
2+ import copernicusmarine
23import numpy as np
4+ import xarray as xr
35
46import pandas as pd
57from parcels import FieldSet , Field , ParticleSet , JITParticle , Variable , AdvectionRK4 , AdvectionRK4_3D
8+ import parcels
69from parcels .tools .converters import Geographic , GeographicPolar
710
811from plasticparcels .kernels import PolyTEOS10_bsq , StokesDrift , WindageDrift , SettlingVelocity , Biofouling , VerticalMixing , unbeaching , periodicBC , checkErrorThroughSurface , deleteParticle , checkThroughBathymetry
@@ -87,6 +90,102 @@ def create_hydrodynamic_fieldset(settings):
8790 return fieldset
8891
8992
93+ def create_copernicusmarine_dataset (data_request ):
94+ """A constructor method to create an xarray dataset from copernicusmarine
95+ data.
96+
97+ Parameters
98+ ----------
99+ data_request : dict
100+ A dictionary containing the parameters for the data request.
101+
102+ Returns
103+ -------
104+ xarray.Dataset
105+ An xarray dataset containing the requested copernicusmarine
106+ data.
107+ """
108+ ds = copernicusmarine .open_dataset (
109+ dataset_id = data_request ['dataset_id' ],
110+ minimum_longitude = min (data_request ['longitude' ]),
111+ maximum_longitude = max (data_request ['longitude' ]),
112+ minimum_latitude = min (data_request ['latitude' ]),
113+ maximum_latitude = max (data_request ['latitude' ]),
114+ variables = data_request ['variables' ],
115+ start_datetime = data_request ['time' ][0 ],
116+ end_datetime = data_request ['time' ][1 ],
117+ minimum_depth = data_request ['depth' ][0 ],
118+ maximum_depth = data_request ['depth' ][1 ]
119+ )
120+ return ds
121+
122+
123+ def create_copernicus_hydrodynamic_fieldset (settings ):
124+ """A constructor method to create a `Parcels.Fieldset` from copernicusmarine
125+ hydrodynamic model data.
126+
127+ Parameters
128+ ----------
129+ settings :
130+ A dictionary of settings that contains an ocean model directory, a filename style,
131+ and the location of the ocean model mesh file, used to create the fieldset.
132+
133+ Returns
134+ -------
135+ fieldset
136+ A `parcels.FieldSet` object.
137+ """
138+ # Create the ocean data request
139+ ocean_dict = settings ['ocean' ]
140+ ds_dict = {}
141+
142+ for key in ocean_dict ['variables' ].keys ():
143+ data_request = {
144+ 'dataset_id' : ocean_dict ['dataset_id' ][key ],
145+ 'longitude' : settings ['simulation' ]['boundingbox' ][:2 ],
146+ 'latitude' : settings ['simulation' ]['boundingbox' ][2 :],
147+ 'depth' : settings ['simulation' ]['depth_range' ],
148+ 'variables' : [ocean_dict ['variables' ][key ]],
149+ 'time' : [settings ['simulation' ]['startdate' ],
150+ settings ['simulation' ]['startdate' ] + settings ['simulation' ]['runtime' ]]
151+ }
152+ ds = create_copernicusmarine_dataset (data_request )
153+ ds_dict [key ] = ds
154+
155+ # Create the hydrodynamic fieldset:
156+ ds_ocean = []
157+ for key in settings ['ocean' ]['variables' ].keys ():
158+ ds_ocean .append (ds_dict [key ])
159+ ds_ocean = xr .merge (ds_ocean )
160+
161+ fieldset = parcels .FieldSet .from_xarray_dataset (ds_ocean ,settings ['ocean' ]['variables' ], settings ['ocean' ]['dimensions' ], mesh = 'spherical' )
162+
163+ # Create flags for custom particle behaviour
164+ fieldset .add_constant ('use_mixing' , settings ['use_mixing' ]) #TODO: check if copernicusmarine has any mixing data
165+ fieldset .add_constant ('use_biofouling' , settings ['use_biofouling' ])
166+ fieldset .add_constant ('use_stokes' , settings ['use_stokes' ])
167+ fieldset .add_constant ('use_wind' , settings ['use_wind' ])
168+ fieldset .add_constant ('G' , 9.81 ) # Gravitational constant [m s-1]
169+ fieldset .add_constant ('use_3D' , settings ['use_3D' ])
170+
171+ # Load in bathymetry
172+ if 'bathymetry' in ocean_dict ['dataset_id' ].keys ():
173+
174+ data_request = {
175+ 'dataset_id' : settings ['bathymetry' ]['dataset_id' ]['bathymetry' ],
176+ 'longitude' : settings ['simulation' ]['boundingbox' ][:2 ],
177+ 'latitude' : settings ['simulation' ]['boundingbox' ][2 :],
178+ 'depth' : settings ['simulation' ]['depth_range' ],
179+ 'variables' : [settings ['bathymetry' ]['variables' ]['bathymetry' ]],
180+ 'time' : [settings ['simulation' ]['startdate' ],
181+ settings ['simulation' ]['startdate' ] + settings ['simulation' ]['runtime' ]]
182+ }
183+ ds_bathymetry = create_copernicusmarine_dataset (data_request )
184+ fieldset_bathymetry = parcels .FieldSet .from_xarray_dataset (ds_bathymetry ,settings ['bathymetry' ]['variables' ], settings ['bathymetry' ]['dimensions' ], mesh = 'spherical' )
185+ fieldset .add_field (fieldset_bathymetry .bathymetry ) # type: ignore
186+
187+ return fieldset
188+
90189def create_fieldset (settings ):
91190 """A constructor method to create a `Parcels.Fieldset` with all fields
92191 necessary for a plasticparcels simulation (e.g., a hydrodynamic model
@@ -104,88 +203,163 @@ def create_fieldset(settings):
104203 fieldset
105204 A `parcels.FieldSet` object.
106205 """
107- # First create the hydrodynamic fieldset
108- fieldset = create_hydrodynamic_fieldset (settings )
206+ # First create the hydrodynamic fieldset - either from local data or from copernicusmarine
207+ if 'directory' in settings ['ocean' ].keys ():
208+ fieldset = create_hydrodynamic_fieldset (settings )
209+ elif 'dataset_id' in settings ['ocean' ].keys ():
210+ fieldset = create_copernicus_hydrodynamic_fieldset (settings )
211+ else :
212+ raise ValueError ('No valid ocean model information found in settings file.' )
109213
110214 # Now add the other fields
111215 # Start date and runtime of the simulation
112216 startdate = settings ['simulation' ]['startdate' ]
113217 runtime = int (np .ceil (settings ['simulation' ]['runtime' ].total_seconds ()/ 86400. )) # convert to days
114218
115- if fieldset .use_biofouling :
116- # MOi glossary: https://www.mercator-ocean.eu/wp-content/uploads/2021/11/Glossary.pdf
117- # and https://catalogue.marine.copernicus.eu/documents/PUM/CMEMS-GLO-PUM-001-028.pdf
219+ if fieldset .use_biofouling : # type: ignore
220+ if 'directory' in settings ['bgc' ].keys ():
221+ # MOi glossary: https://www.mercator-ocean.eu/wp-content/uploads/2021/11/Glossary.pdf
222+ # and https://catalogue.marine.copernicus.eu/documents/PUM/CMEMS-GLO-PUM-001-028.pdf
223+
224+ # Create a fieldset with local BGC data
225+ dirread_bgc = os .path .join (settings ['bgc' ]['directory' ], settings ['bgc' ]['filename_style' ])
226+ bgc_mesh = os .path .join (settings ['bgc' ]['directory' ], settings ['bgc' ]['bgc_mesh' ]) # mesh_mask_4th
227+
228+ dirread_model = os .path .join (settings ['ocean' ]['directory' ], settings ['ocean' ]['filename_style' ])
229+ wfiles = select_files (dirread_model , 'W_%4i*.nc' , startdate , runtime , dt_margin = 3 )
230+
231+ ppfiles = select_files (dirread_bgc , 'nppv_%4i*.nc' , startdate , runtime , dt_margin = 8 )
232+ phy1files = select_files (dirread_bgc , 'phy_%4i*.nc' , startdate , runtime , dt_margin = 8 )
233+ phy2files = select_files (dirread_bgc , 'phy2_%4i*.nc' , startdate , runtime , dt_margin = 8 )
234+
235+ filenames_bio = {'pp_phyto' : {'lon' : bgc_mesh , 'lat' : bgc_mesh , 'depth' : wfiles [0 ], 'data' : ppfiles }, # phytoplankton primary productivity
236+ 'bio_nanophy' : {'lon' : bgc_mesh , 'lat' : bgc_mesh , 'depth' : wfiles [0 ], 'data' : phy1files }, # nanopyhtoplankton concentration [mmol C m-3]
237+ 'bio_diatom' : {'lon' : bgc_mesh , 'lat' : bgc_mesh , 'depth' : wfiles [0 ], 'data' : phy2files }} # diatom concentration [mmol C m-3]
238+
239+ variables_bio = settings ['bgc' ]['variables' ]
240+ dimensions_bio = settings ['bgc' ]['dimensions' ]
241+
242+ # Create the BGC fieldset
243+ bio_fieldset = FieldSet .from_nemo (filenames_bio , variables_bio , dimensions_bio )
244+
245+ # Add the fields to the main fieldset
246+ for field in bio_fieldset .get_fields ():
247+ fieldset .add_field (field )
248+
249+ elif 'dataset_id' in settings ['bgc' ].keys ():
250+ # Create the bgc fieldset from copernicusmarine
251+ bgc_dict = settings ['bgc' ]
252+ ds_dict = {}
253+ for key in bgc_dict ['variables' ].keys ():
254+ data_request = {
255+ 'dataset_id' : bgc_dict ['dataset_id' ][key ],
256+ 'longitude' : settings ['simulation' ]['boundingbox' ][:2 ],
257+ 'latitude' : settings ['simulation' ]['boundingbox' ][2 :],
258+ 'depth' : settings ['simulation' ]['depth_range' ],
259+ 'variables' : [bgc_dict ['variables' ][key ]],
260+ 'time' : [settings ['simulation' ]['startdate' ],
261+ settings ['simulation' ]['startdate' ] + settings ['simulation' ]['runtime' ]]
262+ }
263+ ds = create_copernicusmarine_dataset (data_request )
264+ ds_dict [key ] = ds
265+
266+ # Create the bgc fieldset:
267+ ds_bgc = []
268+ for key in settings ['bgc' ]['variables' ].keys ():
269+ ds_bgc .append (ds_dict [key ])
270+ ds_bgc = xr .merge (ds_bgc )
271+ bio_fieldset = parcels .FieldSet .from_xarray_dataset (ds_bgc ,settings ['bgc' ]['variables' ], settings ['bgc' ]['dimensions' ], mesh = 'spherical' )
272+
273+ # Add the fields to the main fieldset
274+ for field in bio_fieldset .get_fields ():
275+ fieldset .add_field (field )
276+ else :
277+ raise ValueError ('No valid biogeochemical model information found in settings file.' )
118278
119279 # Add BGC constants to current fieldset
120280 for key in settings ['bgc' ]['constants' ]:
121281 fieldset .add_constant (key , settings ['bgc' ]['constants' ][key ])
122282
123- # Create a fieldset with BGC data
124- dirread_bgc = os .path .join (settings ['bgc' ]['directory' ], settings ['bgc' ]['filename_style' ])
125- bgc_mesh = os .path .join (settings ['bgc' ]['directory' ], settings ['bgc' ]['bgc_mesh' ]) # mesh_mask_4th
126-
127- dirread_model = os .path .join (settings ['ocean' ]['directory' ], settings ['ocean' ]['filename_style' ])
128- wfiles = select_files (dirread_model , 'W_%4i*.nc' , startdate , runtime , dt_margin = 3 )
129-
130- ppfiles = select_files (dirread_bgc , 'nppv_%4i*.nc' , startdate , runtime , dt_margin = 8 )
131- phy1files = select_files (dirread_bgc , 'phy_%4i*.nc' , startdate , runtime , dt_margin = 8 )
132- phy2files = select_files (dirread_bgc , 'phy2_%4i*.nc' , startdate , runtime , dt_margin = 8 )
133-
134- filenames_bio = {'pp_phyto' : {'lon' : bgc_mesh , 'lat' : bgc_mesh , 'depth' : wfiles [0 ], 'data' : ppfiles },
135- 'bio_nanophy' : {'lon' : bgc_mesh , 'lat' : bgc_mesh , 'depth' : wfiles [0 ], 'data' : phy1files },
136- 'bio_diatom' : {'lon' : bgc_mesh , 'lat' : bgc_mesh , 'depth' : wfiles [0 ], 'data' : phy2files }}
137-
138- variables_bio = settings ['bgc' ]['variables' ]
139- dimensions_bio = settings ['bgc' ]['dimensions' ]
140-
141- # Create the BGC fieldset
142- bio_fieldset = FieldSet .from_nemo (filenames_bio , variables_bio , dimensions_bio )
143-
144- # Add the fields to the main fieldset
145- fieldset .add_field (bio_fieldset .pp_phyto ) # phytoplankton primary productivity
146- fieldset .add_field (bio_fieldset .bio_nanophy ) # nanopyhtoplankton concentration [mmol C m-3]
147- fieldset .add_field (bio_fieldset .bio_diatom ) # diatom concentration [mmol C m-3]
148-
149- if fieldset .use_stokes :
150- dirread_Stokes = os .path .join (settings ['stokes' ]['directory' ], settings ['stokes' ]['filename_style' ])
151- wavesfiles = select_files (dirread_Stokes , '%4i*.nc' , startdate , runtime , dt_margin = 32 )
152-
153- filenames_Stokes = {'Stokes_U' : wavesfiles ,
154- 'Stokes_V' : wavesfiles ,
155- 'wave_Tp' : wavesfiles }
156-
157- variables_Stokes = settings ['stokes' ]['variables' ]
158- dimensions_Stokes = settings ['stokes' ]['dimensions' ]
159-
160- fieldset_Stokes = FieldSet .from_netcdf (filenames_Stokes , variables_Stokes , dimensions_Stokes , mesh = 'spherical' )
161- fieldset_Stokes .Stokes_U .units = GeographicPolar ()
162- fieldset_Stokes .Stokes_V .units = Geographic ()
163- fieldset_Stokes .add_periodic_halo (zonal = True )
164-
165- fieldset .add_field (fieldset_Stokes .Stokes_U )
166- fieldset .add_field (fieldset_Stokes .Stokes_V )
167- fieldset .add_field (fieldset_Stokes .wave_Tp )
168-
169- if fieldset .use_wind :
170- dirread_wind = os .path .join (settings ['wind' ]['directory' ], settings ['wind' ]['filename_style' ])
171- windfiles = select_files (dirread_wind , '%4i*.nc' , startdate , runtime , dt_margin = 32 )
172-
173- filenames_wind = {'Wind_U' : windfiles ,
174- 'Wind_V' : windfiles }
175-
176- variables_wind = settings ['wind' ]['variables' ]
177- dimensions_wind = settings ['wind' ]['dimensions' ]
178-
179- fieldset_wind = FieldSet .from_netcdf (filenames_wind , variables_wind , dimensions_wind , mesh = 'spherical' )
180- fieldset_wind .Wind_U .units = GeographicPolar ()
181- fieldset_wind .Wind_V .units = Geographic ()
182- fieldset_wind .add_periodic_halo (zonal = True )
183283
184- fieldset .add_field (fieldset_wind .Wind_U )
185- fieldset .add_field (fieldset_wind .Wind_V )
284+ if fieldset .use_stokes : # type: ignore
285+ if 'directory' in settings ['stokes' ].keys ():
286+ # Create the stokes fieldset from local data
287+ dirread_Stokes = os .path .join (settings ['stokes' ]['directory' ], settings ['stokes' ]['filename_style' ])
288+ wavesfiles = select_files (dirread_Stokes , '%4i*.nc' , startdate , runtime , dt_margin = 32 )
289+
290+ filenames_Stokes = {'Stokes_U' : wavesfiles ,
291+ 'Stokes_V' : wavesfiles ,
292+ 'wave_Tp' : wavesfiles }
293+
294+ variables_Stokes = settings ['stokes' ]['variables' ]
295+ dimensions_Stokes = settings ['stokes' ]['dimensions' ]
296+
297+ fieldset_Stokes = FieldSet .from_netcdf (filenames_Stokes , variables_Stokes , dimensions_Stokes , mesh = 'spherical' )
298+ fieldset_Stokes .Stokes_U .units = GeographicPolar ()
299+ fieldset_Stokes .Stokes_V .units = Geographic ()
300+ fieldset_Stokes .add_periodic_halo (zonal = True )
301+
302+ # Add the fields to the main fieldset
303+ for field in fieldset_Stokes .get_fields ():
304+ fieldset .add_field (field )
305+ elif 'dataset_id' in settings ['stokes' ].keys ():
306+ # Create the stokes fieldset from copernicusmarine
307+ stokes_dict = settings ['stokes' ]
308+ ds_dict = {}
309+ for key in stokes_dict ['variables' ].keys ():
310+ data_request = {
311+ 'dataset_id' : stokes_dict ['dataset_id' ][key ],
312+ 'longitude' : settings ['simulation' ]['boundingbox' ][:2 ],
313+ 'latitude' : settings ['simulation' ]['boundingbox' ][2 :],
314+ 'depth' : settings ['simulation' ]['depth_range' ],
315+ 'variables' : [stokes_dict ['variables' ][key ]],
316+ 'time' : [settings ['simulation' ]['startdate' ],
317+ settings ['simulation' ]['startdate' ] + settings ['simulation' ]['runtime' ]]
318+ }
319+ ds = create_copernicusmarine_dataset (data_request )
320+ ds_dict [key ] = ds
321+
322+ ds_stokes = []
323+ for key in settings ['stokes' ]['variables' ].keys ():
324+ ds_stokes .append (ds_dict [key ])
325+ ds_stokes = xr .merge (ds_stokes )
326+ fieldset_stokes = parcels .FieldSet .from_xarray_dataset (ds_stokes ,settings ['stokes' ]['variables' ], settings ['stokes' ]['dimensions' ], mesh = 'spherical' )
327+ fieldset_stokes .Stokes_U .units = GeographicPolar () # type: ignore
328+ fieldset_stokes .Stokes_V .units = Geographic () # type: ignore
329+ for field in fieldset_stokes .get_fields ():
330+ fieldset .add_field (field )
331+ else :
332+ raise ValueError ('No valid Stokes drift model information found in settings file.' )
333+
334+ if fieldset .use_wind : # type: ignore
335+ if 'wind' not in settings .keys ():
336+ raise ValueError ('Wind settings not found in settings file.' )
337+ elif 'directory' in settings ['wind' ].keys ():
338+ dirread_wind = os .path .join (settings ['wind' ]['directory' ], settings ['wind' ]['filename_style' ])
339+ windfiles = select_files (dirread_wind , '%4i*.nc' , startdate , runtime , dt_margin = 32 )
340+
341+ filenames_wind = {'Wind_U' : windfiles ,
342+ 'Wind_V' : windfiles }
343+
344+ variables_wind = settings ['wind' ]['variables' ]
345+ dimensions_wind = settings ['wind' ]['dimensions' ]
346+
347+ fieldset_wind = FieldSet .from_netcdf (filenames_wind , variables_wind , dimensions_wind , mesh = 'spherical' )
348+ fieldset_wind .Wind_U .units = GeographicPolar ()
349+ fieldset_wind .Wind_V .units = Geographic ()
350+ fieldset_wind .add_periodic_halo (zonal = True )
351+
352+ # Add the fields to the main fieldset
353+ for field in fieldset_wind .get_fields ():
354+ fieldset .add_field (field )
355+ elif 'dataset_id' in settings ['wind' ].keys ():
356+ raise NotImplementedError ('Copernicus Marine wind data request not yet implemented.' )
357+ else :
358+ raise ValueError ('No valid wind model information found in settings file.' )
186359
187360 # Apply unbeaching currents when Stokes/Wind can push particles into land cells
188- if fieldset .use_stokes or fieldset .use_wind > 0 :
361+ if (fieldset .use_stokes or fieldset .use_wind > 0 ) and 'directory' in settings ['ocean' ].keys (): # type: ignore
362+ # If using local hydrodynamic data, you can also provide unbeaching currents
189363 unbeachfiles = settings ['unbeaching' ]['filename' ]
190364 filenames_unbeach = {'unbeach_U' : unbeachfiles ,
191365 'unbeach_V' : unbeachfiles }
@@ -198,8 +372,8 @@ def create_fieldset(settings):
198372 fieldset_unbeach .unbeach_U .units = GeographicPolar ()
199373 fieldset_unbeach .unbeach_V .units = Geographic ()
200374
201- fieldset . add_field ( fieldset_unbeach .unbeach_U )
202- fieldset .add_field (fieldset_unbeach . unbeach_V )
375+ for field in fieldset_unbeach .get_fields ():
376+ fieldset .add_field (field )
203377
204378 fieldset .add_constant ('verbose_delete' , settings ['verbose_delete' ])
205379
0 commit comments