Skip to content

Commit 756b2d6

Browse files
committed
Adding functionality to create a fieldset from a copernicusmarine dataset
1 parent 45aae65 commit 756b2d6

File tree

1 file changed

+244
-70
lines changed

1 file changed

+244
-70
lines changed

plasticparcels/constructors.py

Lines changed: 244 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import os
2+
import copernicusmarine
23
import numpy as np
4+
import xarray as xr
35

46
import pandas as pd
57
from parcels import FieldSet, Field, ParticleSet, JITParticle, Variable, AdvectionRK4, AdvectionRK4_3D
8+
import parcels
69
from parcels.tools.converters import Geographic, GeographicPolar
710

811
from 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+
90189
def 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

Comments
 (0)