dfm-tools 0.10.55__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dfm_tools/__init__.py +26 -0
- dfm_tools/bathymetry.py +38 -0
- dfm_tools/deprecated.py +50 -0
- dfm_tools/download.py +311 -0
- dfm_tools/energy_dissipation.py +73 -0
- dfm_tools/errors.py +9 -0
- dfm_tools/get_nc.py +563 -0
- dfm_tools/get_nc_helpers.py +196 -0
- dfm_tools/hydrolib_helpers.py +368 -0
- dfm_tools/interpolate_grid2bnd.py +469 -0
- dfm_tools/linebuilder.py +35 -0
- dfm_tools/meshkernel_helpers.py +163 -0
- dfm_tools/modelbuilder.py +224 -0
- dfm_tools/modplot.py +704 -0
- dfm_tools/regulargrid.py +190 -0
- dfm_tools/streamplot_COPY.py +712 -0
- dfm_tools/xarray_helpers.py +534 -0
- dfm_tools-0.10.55.dist-info/METADATA +84 -0
- dfm_tools-0.10.55.dist-info/RECORD +23 -0
- dfm_tools-0.10.55.dist-info/WHEEL +5 -0
- dfm_tools-0.10.55.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_dfm_tools.py +387 -0
dfm_tools/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. include:: ../README.md
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__author__ = """Jelmer Veenstra"""
|
|
6
|
+
__email__ = 'jelmer.veenstra@deltares.nl'
|
|
7
|
+
__version__ = '0.10.55'
|
|
8
|
+
|
|
9
|
+
from dfm_tools.deprecated import *
|
|
10
|
+
from dfm_tools.errors import *
|
|
11
|
+
from dfm_tools.download import *
|
|
12
|
+
from dfm_tools.get_nc import *
|
|
13
|
+
from dfm_tools.get_nc_helpers import *
|
|
14
|
+
from dfm_tools.hydrolib_helpers import *
|
|
15
|
+
from dfm_tools.meshkernel_helpers import *
|
|
16
|
+
from dfm_tools.interpolate_grid2bnd import *
|
|
17
|
+
from dfm_tools.linebuilder import *
|
|
18
|
+
from dfm_tools.modplot import *
|
|
19
|
+
from dfm_tools.regulargrid import *
|
|
20
|
+
from dfm_tools.xarray_helpers import *
|
|
21
|
+
from dfm_tools.energy_dissipation import *
|
|
22
|
+
from dfm_tools.bathymetry import *
|
|
23
|
+
#from dfm_tools.modelbuilder import * #commented since we do not want to expose these functions with hardcoded parameters
|
|
24
|
+
|
|
25
|
+
import warnings
|
|
26
|
+
warnings.filterwarnings('always',category=DeprecationWarning)
|
dfm_tools/bathymetry.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Mon May 31 17:17:09 2021
|
|
4
|
+
|
|
5
|
+
@author: veenstra
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def write_bathy_toasc(filename_asc,lon_sel_ext,lat_sel_ext,elev_sel_ext,asc_fmt='%9.2f',nodata_val=32767):
|
|
12
|
+
|
|
13
|
+
print('writing to asc file')
|
|
14
|
+
if elev_sel_ext.shape != (lat_sel_ext.shape[0], lon_sel_ext.shape[0]):
|
|
15
|
+
raise ValueError('resulting shape of elev_sel_ext %s is not consistent with lat_sel_ext/lon_sel_ext vars %s'%(elev_sel_ext.shape,(lat_sel_ext.shape[0], lon_sel_ext.shape[0])))
|
|
16
|
+
if isinstance(elev_sel_ext,np.ma.core.MaskedArray): #masked has to be filled in order for the nans to be visible
|
|
17
|
+
elev_sel_ext = elev_sel_ext.filled(np.nan)
|
|
18
|
+
if np.isnan(elev_sel_ext).sum()>0:
|
|
19
|
+
elev_sel_ext = elev_sel_ext.copy()
|
|
20
|
+
elev_sel_ext[np.isnan(elev_sel_ext)] = nodata_val
|
|
21
|
+
print('replaced nan values with %d'%(nodata_val))
|
|
22
|
+
resinv_lonlat = np.round(1/np.diff(lon_sel_ext[:2]),2)
|
|
23
|
+
resinv_lat = np.round(1/np.diff(lat_sel_ext[:2]),2)
|
|
24
|
+
if resinv_lonlat!=resinv_lat:
|
|
25
|
+
raise ValueError('inconsistent resolution over lat/lon')
|
|
26
|
+
|
|
27
|
+
with open(filename_asc,'w') as file_asc:
|
|
28
|
+
file_asc.write('ncols %d\n'%(elev_sel_ext.shape[1]))
|
|
29
|
+
file_asc.write('nrows %d\n'%(elev_sel_ext.shape[0]))
|
|
30
|
+
file_asc.write('xllcenter %13.8f\n'%(lon_sel_ext[0]))
|
|
31
|
+
file_asc.write('yllcenter %13.8f\n'%(lat_sel_ext[0]))
|
|
32
|
+
file_asc.write('cellsize %16.11f\n'%(1/resinv_lonlat))
|
|
33
|
+
#file_asc.write('NODATA_value %d\n'%(nodata_val))
|
|
34
|
+
file_asc.write('NODATA_value '+asc_fmt%(nodata_val)+'\n')
|
|
35
|
+
with open(filename_asc,'a') as file_asc:
|
|
36
|
+
np.savetxt(file_asc,np.flip(elev_sel_ext,axis=0),fmt=asc_fmt)
|
|
37
|
+
print('...finished')
|
|
38
|
+
|
dfm_tools/deprecated.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Sun Apr 23 22:42:27 2023
|
|
4
|
+
|
|
5
|
+
@author: veenstra
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def plot_background(ax=None, projection=None, google_style='satellite', resolution=1, features=None, nticks=6, latlon_format=False, gridlines=False, **kwargs):
|
|
10
|
+
raise DeprecationWarning('dfmt.plot_background() is deprecated, use contextily and cartopy instead like in https://github.com/Deltares/dfm_tools/blob/main/tests/examples_workinprogress/workinprogress_cartopy_satellite_coastlines.py')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_ugrid_verts(data_xr_map):
|
|
14
|
+
"""
|
|
15
|
+
getting ugrid verts from xugrid mapfile.
|
|
16
|
+
"""
|
|
17
|
+
raise DeprecationWarning('dfmt.get_ugrid_verts() is deprecated, use uds.grid.face_node_coordinates instead (https://github.com/Deltares/xugrid/issues/48)')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def scatter_to_regulargrid(xcoords, ycoords, values, ncellx=None, ncelly=None, reg_x_vec=None, reg_y_vec=None, method='nearest', maskland_dist=None):
|
|
21
|
+
raise DeprecationWarning('dfm_tools.regulargrid.scatter_to_regulargrid() is deprecated, use ds = dfmt.rasterize_ugrid(uds) instead')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_varnamefromattrs(data_xr, varname):
|
|
25
|
+
raise DeprecationWarning('dfmt.get_varnamefromattrs() will be deprecated in a future version of dfm_tools, ds=dfmt.rename_waqvars(ds) is a more convenient alternative')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Polygon:
|
|
29
|
+
def __init__(self, data, name, comments):
|
|
30
|
+
raise DeprecationWarning('the function dfm_tools.polygon.Polygon() is deprecated, please use the new hydrolib alternative.')
|
|
31
|
+
|
|
32
|
+
def fromfile(self, file_pol, pd_output=False, tekmap_output=False):
|
|
33
|
+
raise DeprecationWarning('the function dfm_tools.polygon.Polygon.fromfile() is deprecated, please use the new hydrolib alternative. Example script: https://github.com/Deltares/dfm_tools/blob/main/tests/examples/preprocess_hydrolib_readwritepol.py')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def write_bcfile(filename, datablocks, metadatas, refdate=None, tzone=0, float_format='%6.2f'):
|
|
37
|
+
raise DeprecationWarning('the function dfm_tools.io.bc.write_bcfile() is deprecated, please use the new hydrolib alternative. Example script: dfm_tools/tests/examples/CMEMS_interpolate_example.py')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_bcfile(filename, converttime=False):
|
|
41
|
+
raise DeprecationWarning('the function dfm_tools.io.bc.read_bcfile() is deprecated, please use the new hydrolib alternative. Example script: dfm_tools/tests/examples/hydrolib_readbc.py')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def write_timfile(filename, datablock, header, converttime=False, refdate=None, float_format='%6.2f'):
|
|
45
|
+
raise DeprecationWarning('the function dfm_tools.write_timfile() is deprecated, please use the new hydrolib alternative: https://github.com/Deltares/dfm_tools/blob/301-convert-timmodel-to-pandasdataframe/tests/examples/preprocess_hydrolib_readtim.py.')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_timfile(filename, converttime=False, refdate=None):
|
|
49
|
+
raise DeprecationWarning('the function dfm_tools.read_timfile() is deprecated, please use the new hydrolib alternative: https://github.com/Deltares/dfm_tools/blob/301-convert-timmodel-to-pandasdataframe/tests/examples/preprocess_hydrolib_readtim.py.')
|
|
50
|
+
|
dfm_tools/download.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Tue Oct 18 15:09:26 2022
|
|
4
|
+
|
|
5
|
+
@author: veenstra
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import xarray as xr
|
|
12
|
+
from pydap.client import open_url
|
|
13
|
+
from pydap.cas.get_cookies import setup_session
|
|
14
|
+
import warnings
|
|
15
|
+
from dfm_tools.errors import OutOfRangeError
|
|
16
|
+
import cdsapi
|
|
17
|
+
import cftime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def download_ERA5(varkey,
|
|
21
|
+
longitude_min, longitude_max, latitude_min, latitude_max,
|
|
22
|
+
date_min, date_max,
|
|
23
|
+
dir_output='.', overwrite=False):
|
|
24
|
+
|
|
25
|
+
#TODO: describe something about the .cdsapirc file
|
|
26
|
+
#TODO: make this function cdsapi generic, instead of ERA5 hardcoded (make flexible for product_type/name/name_output) (variables_dict is not used actively anymore, so this is possible)
|
|
27
|
+
|
|
28
|
+
c = cdsapi.Client() # import cdsapi and create a Client instance # https://cds.climate.copernicus.eu/api-how-to
|
|
29
|
+
|
|
30
|
+
#dictionary with ERA5 variables #this is not actively used
|
|
31
|
+
variables_dict = {'ssr':'surface_net_solar_radiation',
|
|
32
|
+
'sst':'sea_surface_temperature',
|
|
33
|
+
'strd':'surface_thermal_radiation_downwards',
|
|
34
|
+
'slhf':'surface_latent_heat_flux',
|
|
35
|
+
'sshf':'surface_sensible_heat_flux',
|
|
36
|
+
'str':'surface_net_thermal_radiation',
|
|
37
|
+
'chnk':'charnock',
|
|
38
|
+
'd2m':'2m_dewpoint_temperature',
|
|
39
|
+
't2m':'2m_temperature',
|
|
40
|
+
'tcc':'total_cloud_cover',
|
|
41
|
+
'msl':'mean_sea_level_pressure',
|
|
42
|
+
'u10':'10m_u_component_of_wind',
|
|
43
|
+
'u10n':'10m_u_component_of_neutral_wind',
|
|
44
|
+
'v10':'10m_v_component_of_wind',
|
|
45
|
+
'v10n':'10m_v_component_of_neutral_wind',
|
|
46
|
+
'mer':'mean_evaporation_rate',
|
|
47
|
+
'mtpr':'mean_total_precipitation_rate',
|
|
48
|
+
}
|
|
49
|
+
if varkey not in variables_dict.keys(): #TODO: how to get list of available vars? mean_sea_level_pressure and msl both return a dataset with msl varkey, but standard_name air_pressure_at_mean_sea_level returns an error
|
|
50
|
+
raise KeyError(f'"{varkey}" not available, choose from: {list(variables_dict.keys())}')
|
|
51
|
+
|
|
52
|
+
period_range = pd.period_range(date_min,date_max,freq='M')
|
|
53
|
+
print(f'retrieving data from {period_range[0]} to {period_range[-1]} (freq={period_range.freq})')
|
|
54
|
+
|
|
55
|
+
for date in period_range:
|
|
56
|
+
name_output = f'era5_{varkey}_{date.strftime("%Y-%m")}.nc'
|
|
57
|
+
file_out = Path(dir_output,name_output)
|
|
58
|
+
if file_out.is_file() and not overwrite:
|
|
59
|
+
print(f'"{name_output}" found and overwrite=False, continuing.')
|
|
60
|
+
continue
|
|
61
|
+
print (f'retrieving ERA5 data for variable "{varkey}" and month {date.strftime("%Y-%m")} (YYYY-MM)')
|
|
62
|
+
|
|
63
|
+
request_dict = {'product_type':'reanalysis',
|
|
64
|
+
'variable':variables_dict[varkey],
|
|
65
|
+
'year':date.strftime('%Y'),
|
|
66
|
+
'month':date.strftime('%m'),
|
|
67
|
+
#'month':[f'{x:02d}' for x in range(1,12+1)], #all months, but instead retrieving per month
|
|
68
|
+
'day':[f'{x:02d}' for x in range(1,31+1)], #all days
|
|
69
|
+
'time':[f'{x:02d}:00' for x in range(0,23+1)], #all times/hours
|
|
70
|
+
'area':[latitude_max,longitude_min,latitude_min,longitude_max], # north, west, south, east. default: global - option not available through the Climate Data Store (CDS) web interface (for cmip data)
|
|
71
|
+
#'grid': [1.0, 1.0], # latitude/longitude grid: east-west (longitude) and north-south resolution (latitude). default: 0.25 x 0.25 - option not available through the Climate Data Store (CDS) web interface
|
|
72
|
+
'format':'netcdf'}
|
|
73
|
+
|
|
74
|
+
c.retrieve(name='reanalysis-era5-single-levels', request=request_dict, target=file_out)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def download_CMEMS(varkey,
|
|
78
|
+
longitude_min, longitude_max, latitude_min, latitude_max,
|
|
79
|
+
date_min, date_max, freq='D',
|
|
80
|
+
dir_output='.', file_prefix='', overwrite=False,
|
|
81
|
+
credentials=None):
|
|
82
|
+
|
|
83
|
+
date_min = pd.Timestamp(date_min)-pd.Timedelta(days=1) #CMEMS has daily noon values (not midnight), so subtract one day from date_min to cover desired time extent
|
|
84
|
+
|
|
85
|
+
if 'my_datemax' not in globals(): #set multiyear date_max (my_datemax) as global variable, so it only has to be retreived once per download run (otherwise once per variable)
|
|
86
|
+
print('retrieving enddate of multiyear CMEMS dataset')
|
|
87
|
+
dataset_url = 'https://my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_my_0.083_P1D-m' #assuming here that physchem and bio reanalyisus/multiyear datasets have the same enddate, this seems safe
|
|
88
|
+
ds = open_OPeNDAP_xr(dataset_url=dataset_url, credentials=credentials)
|
|
89
|
+
my_times = ds.time.to_series()
|
|
90
|
+
global my_datemax
|
|
91
|
+
my_datemax = my_times.iloc[-1]
|
|
92
|
+
|
|
93
|
+
if pd.Timestamp(date_max) <= my_datemax:
|
|
94
|
+
product = 'reanalysis'
|
|
95
|
+
elif pd.Timestamp(date_min) > my_datemax:
|
|
96
|
+
product = 'analysisforecast'
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f'Requested timerange is {date_min} to {date_max}. Currently, it is only possible to query periods before OR after the multiyear/reanalysis enddate ({my_datemax}).')
|
|
99
|
+
|
|
100
|
+
Path(dir_output).mkdir(parents=True, exist_ok=True)
|
|
101
|
+
if varkey in ['bottomT','tob','mlotst','siconc','sithick','so','thetao','uo','vo','usi','vsi','zos']: #for physchem
|
|
102
|
+
if product == 'analysisforecast': #forecast: https://data.marine.copernicus.eu/product/GLOBAL_ANALYSISFORECAST_PHY_001_024/description
|
|
103
|
+
if varkey=='bottomT': #rename old to new anfc varname (still called bottomT in reanalysis)
|
|
104
|
+
varkey = 'tob'
|
|
105
|
+
if varkey in ['uo','vo']:
|
|
106
|
+
varkey_name = 'phy-cur'
|
|
107
|
+
elif varkey in ['so','thetao']:
|
|
108
|
+
varkey_name = 'phy-'+varkey
|
|
109
|
+
else:
|
|
110
|
+
varkey_name = 'phy'
|
|
111
|
+
dataset_url = f'https://nrt.cmems-du.eu/thredds/dodsC/cmems_mod_glo_{varkey_name}_anfc_0.083deg_P1D-m'#.html' #TODO: also PT6H-i timeresolution available but not for all variables and not for reanalysis
|
|
112
|
+
else: #reanalysis: https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_PHY_001_030/description
|
|
113
|
+
dataset_url = 'https://my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_my_0.083_P1D-m'
|
|
114
|
+
else: #for bio
|
|
115
|
+
if product == 'analysisforecast': #forecast: https://data.marine.copernicus.eu/product/GLOBAL_ANALYSISFORECAST_PHY_001_024/description
|
|
116
|
+
dataset_url = 'https://nrt.cmems-du.eu/thredds/dodsC/global-analysis-forecast-bio-001-028-daily' #contains ['chl','fe','no3','nppv','o2','ph','phyc','po4','si','spco2']
|
|
117
|
+
else: #https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_BGC_001_029/description
|
|
118
|
+
dataset_url = 'https://my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_bgc_my_0.25_P1D-m' #contains ['chl','no3','nppv','o2','po4','si']
|
|
119
|
+
|
|
120
|
+
download_OPeNDAP(dataset_url=dataset_url,
|
|
121
|
+
credentials=credentials, #credentials=['username','password'], or create "%USERPROFILE%/CMEMS_credentials.txt" with username on line 1 and password on line 2. Register at: https://resources.marine.copernicus.eu/registration-form'
|
|
122
|
+
varkey=varkey,
|
|
123
|
+
longitude_min=longitude_min, longitude_max=longitude_max, latitude_min=latitude_min, latitude_max=latitude_max,
|
|
124
|
+
date_min=date_min, date_max=date_max,
|
|
125
|
+
dir_output=dir_output, file_prefix=file_prefix, overwrite=overwrite)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def open_OPeNDAP_xr(dataset_url, credentials=None):
|
|
129
|
+
"""
|
|
130
|
+
How to get the opendap dataset_url (CMEMS example):
|
|
131
|
+
- https://data.marine.copernicus.eu/products
|
|
132
|
+
- go to the data access tab of a product, e.g.: https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_PHY_001_030/services
|
|
133
|
+
- click the opendap link of the dataset of your choice
|
|
134
|
+
- copy the dataset_url from the adress bar (excl .html), e.g.: https://my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_my_0.083_P1D-m
|
|
135
|
+
|
|
136
|
+
Example multiyear phys/chem:
|
|
137
|
+
https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_PHY_001_030/services
|
|
138
|
+
https://my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_phy_my_0.083_P1D-m
|
|
139
|
+
['bottomT','mlotst','siconc','sithick','so','thetao','uo','usi','vo','vsi','zos']
|
|
140
|
+
Example multiyear bio:
|
|
141
|
+
https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_BGC_001_029/services
|
|
142
|
+
https://my.cmems-du.eu/thredds/dodsC/cmems_mod_glo_bgc_my_0.25_P1D-m
|
|
143
|
+
['chl','no3','nppv','o2','po4','si']
|
|
144
|
+
Example forecast phys/chem:
|
|
145
|
+
https://data.marine.copernicus.eu/product/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/services
|
|
146
|
+
https://nrt.cmems-du.eu/thredds/dodsC/global-analysis-forecast-phy-001-024
|
|
147
|
+
['bottomT','mlotst','siconc','sithick','so','thetao','uo','usi','vo','vsi','zos']
|
|
148
|
+
Example forecast bio:
|
|
149
|
+
https://data.marine.copernicus.eu/product/GLOBAL_ANALYSIS_FORECAST_BIO_001_028/services
|
|
150
|
+
https://nrt.cmems-du.eu/thredds/dodsC/global-analysis-forecast-bio-001-028-daily
|
|
151
|
+
['chl','fe','no3','nppv','o2','ph','phyc','po4','si','spco2']
|
|
152
|
+
|
|
153
|
+
How to get the opendap dataset_url (HYCOM example):
|
|
154
|
+
- https://www.hycom.org/dataserver
|
|
155
|
+
- Select a product and search for THREDDS, e.g.: https://www.hycom.org/dataserver/gofs-3pt1/analysis
|
|
156
|
+
- find an opendap dataset_url, it depends per product/run where to find it.
|
|
157
|
+
Some examples:
|
|
158
|
+
https://tds.hycom.org/thredds/dodsC/GLBu0.08/expt_19.1/2010
|
|
159
|
+
https://tds.hycom.org/thredds/dodsC/GLBy0.08/expt_93.0
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def copernicusmarine_datastore(dataset_url, username, password):
|
|
164
|
+
#https://help.marine.copernicus.eu/en/articles/5182598-how-to-consume-the-opendap-api-and-cas-sso-using-python
|
|
165
|
+
cas_url = 'https://cmems-cas.cls.fr/cas/login'
|
|
166
|
+
session = setup_session(cas_url, username, password)
|
|
167
|
+
cookies_dict = session.cookies.get_dict()
|
|
168
|
+
if 'CASTGC' not in cookies_dict.keys():
|
|
169
|
+
raise KeyError('CASTGC key missing from session cookies_dict, probably authentication failure')
|
|
170
|
+
session.cookies.set("CASTGC", cookies_dict['CASTGC'])
|
|
171
|
+
#TODO: add check for wrong dataset_id (now always "AttributeError: You cannot set the charset when no content-type is defined")
|
|
172
|
+
DAP_dataset = open_url(dataset_url, session=session)#, user_charset='utf-8') # TODO: user_charset needs PyDAP >= v3.3.0 see https://github.com/pydap/pydap/pull/223/commits
|
|
173
|
+
data_store = xr.backends.PydapDataStore(DAP_dataset)
|
|
174
|
+
return data_store
|
|
175
|
+
|
|
176
|
+
if isinstance(dataset_url,list):
|
|
177
|
+
dataset_url_one = dataset_url[0]
|
|
178
|
+
else:
|
|
179
|
+
dataset_url_one = dataset_url
|
|
180
|
+
|
|
181
|
+
if 'cmems-du.eu' in dataset_url_one:
|
|
182
|
+
if isinstance(dataset_url,list):
|
|
183
|
+
raise TypeError('list not supported by opendap method used for cmems')
|
|
184
|
+
|
|
185
|
+
#parse credentials to username/password #TODO: now CMEMS specific, make more generic
|
|
186
|
+
if credentials is None:
|
|
187
|
+
file_credentials = f'{os.path.expanduser("~")}/CMEMS_credentials.txt'
|
|
188
|
+
if not os.path.exists(file_credentials):
|
|
189
|
+
raise FileNotFoundError(f'credentials argument not supplied and file_credentials not available ({file_credentials})')
|
|
190
|
+
with open(file_credentials) as fc:
|
|
191
|
+
username = fc.readline().strip()
|
|
192
|
+
password = fc.readline().strip()
|
|
193
|
+
else:
|
|
194
|
+
username,password = credentials
|
|
195
|
+
|
|
196
|
+
print(f'opening pydap connection to opendap dataset: {dataset_url}.html')
|
|
197
|
+
data_store = copernicusmarine_datastore(dataset_url=dataset_url, username=username, password=password)
|
|
198
|
+
print('xarray opening opendap dataset')
|
|
199
|
+
data_xr = xr.open_dataset(data_store)
|
|
200
|
+
elif 'hycom.org' in dataset_url_one:
|
|
201
|
+
if isinstance(dataset_url,list):
|
|
202
|
+
print(f'xarray opening opendap dataset like: {dataset_url[0]}.html ({len(dataset_url)} urls/years)')
|
|
203
|
+
data_xr = xr.open_mfdataset(dataset_url,decode_times=False) #TODO: for some reason decode_times does not work: "ValueError: unable to decode time units 'hours since analysis' with 'the default calendar'."
|
|
204
|
+
else:
|
|
205
|
+
print(f'xarray opening opendap dataset: {dataset_url}.html')
|
|
206
|
+
data_xr = xr.open_dataset(dataset_url,decode_times=False) #TODO: for some reason decode_times does not work: "ValueError: unable to decode time units 'hours since analysis' with 'the default calendar
|
|
207
|
+
data_xr['time'] = cftime.num2date(data_xr.time,units=data_xr.time.units,calendar=data_xr.time.calendar)
|
|
208
|
+
data_xr = data_xr.rename({'lon':'longitude','lat':'latitude'})
|
|
209
|
+
else:
|
|
210
|
+
print(f'unspecified dataset_url, might fail: {dataset_url}')
|
|
211
|
+
if isinstance(dataset_url,list):
|
|
212
|
+
print(f'xarray opening opendap dataset like: {dataset_url[0]}.html ({len(dataset_url)} urls/years)')
|
|
213
|
+
data_xr = xr.open_mfdataset(dataset_url)
|
|
214
|
+
else:
|
|
215
|
+
print(f'xarray opening opendap dataset: {dataset_url}.html')
|
|
216
|
+
data_xr = xr.open_dataset(dataset_url)
|
|
217
|
+
if 'lon' in data_xr.dims:
|
|
218
|
+
data_xr = data_xr.rename({'lon':'longitude','lat':'latitude'})
|
|
219
|
+
|
|
220
|
+
return data_xr
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def download_OpenDAP_gettimes(dataset_url,credentials=None):
|
|
224
|
+
ds = open_OPeNDAP_xr(dataset_url=dataset_url, credentials=credentials)
|
|
225
|
+
ds_time = ds.time.to_series()
|
|
226
|
+
return ds_time
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def download_OPeNDAP(dataset_url,
|
|
230
|
+
varkey,
|
|
231
|
+
longitude_min, longitude_max, latitude_min, latitude_max,
|
|
232
|
+
date_min, date_max, freq='D',
|
|
233
|
+
dir_output='.', file_prefix='', overwrite=False,
|
|
234
|
+
credentials=None):
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
dataset_url : TYPE
|
|
241
|
+
DESCRIPTION.
|
|
242
|
+
varkey : TYPE
|
|
243
|
+
DESCRIPTION.
|
|
244
|
+
longitude_min : TYPE
|
|
245
|
+
DESCRIPTION.
|
|
246
|
+
longitude_max : TYPE
|
|
247
|
+
DESCRIPTION.
|
|
248
|
+
latitude_min : TYPE
|
|
249
|
+
DESCRIPTION.
|
|
250
|
+
latitude_max : TYPE
|
|
251
|
+
DESCRIPTION.
|
|
252
|
+
date_min : TYPE
|
|
253
|
+
DESCRIPTION.
|
|
254
|
+
date_max : TYPE
|
|
255
|
+
DESCRIPTION.
|
|
256
|
+
freq : TYPE, optional
|
|
257
|
+
DESCRIPTION. The default is 'D'.
|
|
258
|
+
dir_output : TYPE, optional
|
|
259
|
+
DESCRIPTION. The default is '.'.
|
|
260
|
+
file_prefix : TYPE, optional
|
|
261
|
+
DESCRIPTION. The default is ''.
|
|
262
|
+
overwrite : TYPE, optional
|
|
263
|
+
DESCRIPTION. The default is False.
|
|
264
|
+
credentials : TYPE, optional
|
|
265
|
+
for CMEMS: credentials=['username','password'], or create "%USERPROFILE%/CMEMS_credentials.txt" with username on line 1 and password on line 2. Register at: https://resources.marine.copernicus.eu/registration-form'. The default is None.
|
|
266
|
+
|
|
267
|
+
Raises
|
|
268
|
+
------
|
|
269
|
+
KeyError
|
|
270
|
+
DESCRIPTION.
|
|
271
|
+
OutOfRangeError
|
|
272
|
+
DESCRIPTION.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
None.
|
|
277
|
+
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
data_xr = open_OPeNDAP_xr(dataset_url=dataset_url, credentials=credentials)
|
|
281
|
+
|
|
282
|
+
print(f'xarray subsetting data (variable \'{varkey}\' and lon/lat extents)')
|
|
283
|
+
if varkey not in data_xr.data_vars:
|
|
284
|
+
raise KeyError(f'"{varkey}" not found in dataset, available are: {list(data_xr.data_vars)}')
|
|
285
|
+
data_xr_var = data_xr[[varkey]]
|
|
286
|
+
data_xr_var = data_xr_var.sel(longitude=slice(longitude_min,longitude_max), #TODO: add depth selection?
|
|
287
|
+
latitude=slice(latitude_min,latitude_max))
|
|
288
|
+
data_xr_times = data_xr_var.time.to_series()
|
|
289
|
+
print(f'available time range in dataset from {data_xr_times.index[0]} to {data_xr_times.index[-1]}')
|
|
290
|
+
period_range = pd.period_range(date_min,date_max,freq=freq)
|
|
291
|
+
|
|
292
|
+
#check if date_min/date_max are available in dataset
|
|
293
|
+
if not (data_xr_times.index[0] <= period_range[0].to_timestamp() <= data_xr_times.index[-1]):
|
|
294
|
+
raise OutOfRangeError(f'date_min ({period_range[0]}) is outside available time range in dataset: {data_xr_times.index[0]} to {data_xr_times.index[-1]}')
|
|
295
|
+
if not (data_xr_times.index[0] <= period_range[-1].to_timestamp() <= data_xr_times.index[-1]):
|
|
296
|
+
raise OutOfRangeError(f'date_max ({period_range[-1]}) is outside available time range in dataset: {data_xr_times.index[0]} to {data_xr_times.index[-1]}')
|
|
297
|
+
|
|
298
|
+
for date in period_range:
|
|
299
|
+
date_str = str(date)
|
|
300
|
+
name_output = f'{file_prefix}{varkey}_{date_str}.nc'
|
|
301
|
+
file_out = Path(dir_output,name_output)
|
|
302
|
+
if file_out.is_file() and not overwrite:
|
|
303
|
+
print(f'"{name_output}" found and overwrite=False, continuing.')
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
print(f'xarray subsetting data per {period_range.freq}: {date_str}')
|
|
307
|
+
data_xr_var_seltime = data_xr_var.sel(time=slice(date_str,date_str)) #+' 12:00:00',
|
|
308
|
+
|
|
309
|
+
print(f'xarray writing netcdf file: {name_output}')
|
|
310
|
+
data_xr_var_seltime.to_netcdf(os.path.join(dir_output,name_output)) #TODO: add chunks={'time':1} or only possible with opening?
|
|
311
|
+
data_xr_var_seltime.close()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Dec 8 22:07:22 2022
|
|
4
|
+
|
|
5
|
+
@author: veenstra
|
|
6
|
+
"""
|
|
7
|
+
import datetime as dt
|
|
8
|
+
from dask.diagnostics import ProgressBar
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compute_energy_dissipation(data_xr_map,file_ED_computed):
|
|
12
|
+
"""
|
|
13
|
+
Example:
|
|
14
|
+
data_xr_map = dfmt.open_partitioned_dataset(file_nc_map,chunks={'time':100}) #TODO: important to have time>1, otherwise time-mean floods memory (100-200 seems optimal for GTSM 1month, but memory still floods so 1 year would probably be impossible).
|
|
15
|
+
data_xr_map = data_xr_map.sel(time=slice('2014-01-01','2014-02-01'))
|
|
16
|
+
dfmt.compute_energy_dissipation(data_xr_map,file_ED_computed)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
TODO: clean up these comments
|
|
20
|
+
|
|
21
|
+
Stel de bodemwrijving is (tau_x,tau_y) dan is het verlies aan energie in W/m^2
|
|
22
|
+
E_loss = tau_x*u_x + tau_y*u_y
|
|
23
|
+
|
|
24
|
+
Voor Chezy geldt (zo uit mijn hoofd):
|
|
25
|
+
tau_x = - (rho*g)/(C^2) u_x * sqrt(u_x^2+u_y^2)
|
|
26
|
+
tau_y = - (rho*g)/(C^2) u_y * sqrt(u_x^2+u_y^2)
|
|
27
|
+
|
|
28
|
+
En gecombineerd
|
|
29
|
+
E_loss = - (rho*g)/(C^2) * (u_x^2+u_y^2)^(3/2)
|
|
30
|
+
|
|
31
|
+
Approximation: using velocities in cell centers, this is averaged but easiest
|
|
32
|
+
|
|
33
|
+
E_loss(area) = sum_cells [ - (rho*g)/(C^2) * (sqrt(u_x^2+u_y^2))^3 ] * cell_area
|
|
34
|
+
|
|
35
|
+
Dit is dan voor een tijdstip. Ik zou middelen over een iets langere periode zodat het de variatie over het getij verdwijnt, want de waarde is bij springtij waarschijnlijk groter.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
rho = 1020 #kg/m3
|
|
39
|
+
g = 9.81 #m/s2
|
|
40
|
+
|
|
41
|
+
attrs_ED = {'long_name':'energy dissipation','units':'W'}
|
|
42
|
+
attrs_ED_pm2 = {'long_name':'energy dissipation','units':'W/m^2'}
|
|
43
|
+
attrs_ED_areasum = {'long_name':'area-sum energy dissipation', 'units':'W'}
|
|
44
|
+
attrs_ED_timemean = {'long_name':'springneaptide-mean energy dissipation', 'units': 'W'}
|
|
45
|
+
attrs_ED_pm2_timemean = {'long_name':'springneaptide-mean energy dissipation', 'units':'Wm^2'}
|
|
46
|
+
|
|
47
|
+
print('>> compute energyloss from C/u/area: ',end='')
|
|
48
|
+
dtstart = dt.datetime.now()
|
|
49
|
+
data_ucmag = data_xr_map.mesh2d_ucmag #m/s
|
|
50
|
+
data_czs = data_xr_map.mesh2d_czs #m0.5s-1
|
|
51
|
+
data_czs = data_czs.where(data_czs!=0) #0 will result in inf energyloss, so replace by nan
|
|
52
|
+
data_ba = data_xr_map.mesh2d_flowelem_ba #m2
|
|
53
|
+
data_xr_map['ED'] = ((rho*g)/(data_czs**2) * data_ucmag**3 * data_ba).assign_attrs(attrs_ED) #equation: (kg/m3)*(m/s2)/(m/s2)*(m3/s3)*m2 = kg/s3*m2 = W = J/s
|
|
54
|
+
data_xr_map['ED_pm2'] = (data_xr_map['ED'] / data_ba).assign_attrs(attrs_ED_pm2) #TODO: maybe include ba instead of supplying _pm2 separately?
|
|
55
|
+
|
|
56
|
+
#TODO: check da.sum(split_every=4): https://github.com/dask/dask/issues/883. Does not seem to work: "TypeError: nansum() got an unexpected keyword argument 'split_every'"
|
|
57
|
+
#TODO: chunking: float32 = 32 bits = 4 bytes >> use this to calculate chunking sizes (100-200MB per chunk is optimal)
|
|
58
|
+
data_xr_map['ED_areasum'] = data_xr_map.ED.sum(dim='mesh2d_nFaces').assign_attrs(attrs_ED_areasum)
|
|
59
|
+
data_xr_map['ED_timemean'] = data_xr_map.ED.mean(dim='time').assign_attrs(attrs_ED_timemean)
|
|
60
|
+
data_xr_map['ED_pm2_timemean'] = data_xr_map.ED_pm2.mean(dim='time').assign_attrs(attrs_ED_pm2_timemean)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
print(f'{(dt.datetime.now()-dtstart).total_seconds():.2f} sec')
|
|
64
|
+
|
|
65
|
+
data_xr_map_computed = data_xr_map[['mesh2d_flowelem_ba','ED_areasum','ED_timemean','ED_pm2_timemean']] #has to contain some var with cells (not only ED_areasum), otherwise ugrid accessor is not valid
|
|
66
|
+
#data_xr_map_computed.ED_timemean.data.visualize() #does not yet work >> visualizes tasks of dask array
|
|
67
|
+
|
|
68
|
+
print('>> compute and save data_xr_map_computed to netcdf: ')
|
|
69
|
+
with ProgressBar():
|
|
70
|
+
data_xr_map_computed.ugrid.to_netcdf(file_ED_computed)
|
|
71
|
+
|
|
72
|
+
data_xr_map_computed.close()
|
|
73
|
+
|