dfm-tools 0.42.0__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 ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ Pre- and postprocessing D-FlowFM model input and output files
3
+ """
4
+
5
+ __author__ = """Jelmer Veenstra"""
6
+ __email__ = "jelmer.veenstra@deltares.nl"
7
+ __version__ = "0.42.0"
8
+
9
+ from dfm_tools.deprecated_functions import *
10
+ from dfm_tools.download import *
11
+ from dfm_tools.get_nc import *
12
+ from dfm_tools.get_nc_helpers import *
13
+ from dfm_tools.hydrolib_helpers import *
14
+ from dfm_tools.meshkernel_helpers import *
15
+ from dfm_tools.interpolate_grid2bnd import *
16
+ from dfm_tools.linebuilder import *
17
+ from dfm_tools.modplot import *
18
+ from dfm_tools.xarray_helpers import *
19
+ from dfm_tools.xugrid_helpers import *
20
+ from dfm_tools.bathymetry import *
21
+ from dfm_tools.coastlines import *
22
+ from dfm_tools import data
23
+ from dfm_tools.modelbuilder import *
24
+ from dfm_tools.observations import *
@@ -0,0 +1,101 @@
1
+ import numpy as np
2
+ import xarray as xr
3
+
4
+
5
+ def write_bathy_toasc(filename_asc,lon_sel_ext,lat_sel_ext,elev_sel_ext,asc_fmt='%9.2f',nodata_val=32767):
6
+ """
7
+ empty docstring
8
+ """
9
+
10
+ print('writing to asc file')
11
+ if elev_sel_ext.shape != (lat_sel_ext.shape[0], lon_sel_ext.shape[0]):
12
+ 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])))
13
+ if isinstance(elev_sel_ext,np.ma.core.MaskedArray): #masked has to be filled in order for the nans to be visible
14
+ elev_sel_ext = elev_sel_ext.filled(np.nan)
15
+ if np.isnan(elev_sel_ext).sum()>0:
16
+ elev_sel_ext = elev_sel_ext.copy()
17
+ elev_sel_ext[np.isnan(elev_sel_ext)] = nodata_val
18
+ print('replaced nan values with %d'%(nodata_val))
19
+ resinv_lonlat = np.round(1/np.diff(lon_sel_ext[:2]),2)[0]
20
+ resinv_lat = np.round(1/np.diff(lat_sel_ext[:2]),2)[0]
21
+ if resinv_lonlat!=resinv_lat:
22
+ raise ValueError('inconsistent resolution over lat/lon')
23
+
24
+ with open(filename_asc,'w') as file_asc:
25
+ file_asc.write('ncols %d\n'%(elev_sel_ext.shape[1]))
26
+ file_asc.write('nrows %d\n'%(elev_sel_ext.shape[0]))
27
+ file_asc.write('xllcenter %13.8f\n'%(lon_sel_ext[0]))
28
+ file_asc.write('yllcenter %13.8f\n'%(lat_sel_ext[0]))
29
+ file_asc.write('cellsize %16.11f\n'%(1/resinv_lonlat))
30
+ #file_asc.write('NODATA_value %d\n'%(nodata_val))
31
+ file_asc.write('NODATA_value '+asc_fmt%(nodata_val)+'\n')
32
+ with open(filename_asc,'a') as file_asc:
33
+ np.savetxt(file_asc,np.flip(elev_sel_ext,axis=0),fmt=asc_fmt)
34
+ print('...finished')
35
+
36
+
37
+ def read_asc(file_asc:str) -> xr.Dataset:
38
+ """
39
+ Reading asc file into a xarray.Dataset
40
+
41
+ Parameters
42
+ ----------
43
+ file_asc : str
44
+ asc file with header ncols, nrows, xllcenter, yllcenter, cellsize, NODATA_value.
45
+
46
+ Returns
47
+ -------
48
+ ds_asc : xr.Dataset
49
+ xarray.Dataset with the data from the asc file as an array and
50
+ the lat/lon coordinates as separate coordinate variables.
51
+
52
+ """
53
+ # read header
54
+ header_dict = {}
55
+ with open(file_asc) as f:
56
+ for linenum, line in enumerate(f, 1):
57
+ linesplit = line.split()
58
+ linestart = linesplit[0]
59
+ linestop = linesplit[-1]
60
+
61
+ try:
62
+ # try to convert it to float, this will fail for headerlines
63
+ # it will succeed for numeric lines, but then the loop breaks
64
+ float(linestart)
65
+ break
66
+ except ValueError:
67
+ # convert header values to int if possible or float if not
68
+ try:
69
+ header_value = int(linestop)
70
+ except ValueError:
71
+ header_value = float(linestop)
72
+ header_dict[linestart] = header_value
73
+ skiprows = linenum
74
+
75
+ # read data
76
+ asc_np = np.loadtxt(file_asc, skiprows=skiprows, dtype=float)
77
+
78
+ # derive x/y values and assert shape
79
+ num_x = header_dict['ncols']
80
+ num_y = header_dict['nrows']
81
+ start_x = header_dict['xllcenter']
82
+ start_y = header_dict['yllcenter']
83
+ step = header_dict['cellsize']
84
+ nodata = header_dict['NODATA_value']
85
+
86
+ assert asc_np.shape == (num_y, num_x)
87
+
88
+ x_vals = np.arange(0, num_x) * step + start_x
89
+ y_vals = np.arange(0, num_y) * step + start_y
90
+
91
+ # flip over latitude and replace nodata with nan
92
+ asc_np = np.flipud(asc_np)
93
+ asc_np[asc_np==nodata] = np.nan
94
+
95
+ ds_asc = xr.Dataset()
96
+ ds_asc['lon'] = xr.DataArray(x_vals, dims=('lon'))
97
+ ds_asc['lat'] = xr.DataArray(y_vals, dims=('lat'))
98
+ ds_asc['data'] = xr.DataArray(asc_np, dims=('lat','lon'))
99
+ ds_asc = ds_asc.assign_attrs(header_dict)
100
+ return ds_asc
101
+
@@ -0,0 +1,186 @@
1
+ import os
2
+ import geopandas as gpd
3
+ import matplotlib.pyplot as plt
4
+ import pandas as pd
5
+ import datetime as dt
6
+ from dfm_tools.data import gshhs_coastlines_shp
7
+
8
+ __all__ = ["get_coastlines_gdb",
9
+ "plot_coastlines",
10
+ "get_borders_gdb",
11
+ "plot_borders",
12
+ ]
13
+
14
+
15
+ def bbox_convert_crs(bbox, crs):
16
+ """
17
+ convert bbox from input crs to WGS84
18
+ """
19
+ bbox_points = gpd.points_from_xy(x=[bbox[0],bbox[2]], y=[bbox[1],bbox[3]], crs=crs)
20
+ bbox_points = bbox_points.to_crs('EPSG:4326') #convert to WGS84
21
+ bbox = (bbox_points.x[0], bbox_points.y[0], bbox_points.x[1], bbox_points.y[1])
22
+ return bbox
23
+
24
+
25
+ def get_coastlines_gdb(res:str='h', bbox:tuple = (-180, -90, 180, 90), min_area:float = 0, crs:str = None, columns:list = ['area']) -> gpd.geoseries.GeoSeries:
26
+ """
27
+ GSHHS coastlines: https://www.ngdc.noaa.gov/mgg/shorelines/data/gshhg/latest/readme.txt
28
+ geopandas docs https://geopandas.org/en/stable/docs/reference/api/geopandas.read_file.html
29
+
30
+ Parameters
31
+ ----------
32
+ res : str, optional
33
+ f(ull), h(igh), i(ntermediate), l(ow), c(oarse) resolution. The default is 'h'.
34
+ bbox : tuple, optional
35
+ (minx, miny, maxx, maxy), also includes shapes that are partly in the bbox. The default is (-180, -90, 180, 90).
36
+ min_area : float, optional
37
+ in km2, min_area>0 speeds up process. The default is 0.
38
+ crs : str, optional
39
+ coordinate reference system
40
+ columns : list, optional
41
+ which shapefile columns to include, None gives all. The default is ['area'].
42
+
43
+ Returns
44
+ -------
45
+ coastlines_gdb : TYPE
46
+ DESCRIPTION.
47
+
48
+ """
49
+
50
+ if res not in 'fhilc':
51
+ raise KeyError(f'invalid res="{res}", resolution options are f(ull), h(igh), i(ntermediate), l(ow), c(oarse)')
52
+ if crs is not None:
53
+ bbox = bbox_convert_crs(bbox, crs)
54
+
55
+ # download gshhs data if not present and return dir
56
+ dir_gshhs = gshhs_coastlines_shp()
57
+
58
+ file_shp_L1 = os.path.join(dir_gshhs,'GSHHS_shp',res,f'GSHHS_{res}_L1.shp') #coastlines
59
+ file_shp_L6 = os.path.join(dir_gshhs,'GSHHS_shp',res,f'GSHHS_{res}_L6.shp') #Antarctic grounding-line polygons
60
+ file_shp_L2 = os.path.join(dir_gshhs,'GSHHS_shp',res,f'GSHHS_{res}_L2.shp') #lakes
61
+ file_shp_L3 = os.path.join(dir_gshhs,'GSHHS_shp',res,f'GSHHS_{res}_L3.shp') #islands-in-lakes
62
+
63
+ print('>> reading coastlines: ',end='')
64
+ dtstart = dt.datetime.now()
65
+ coastlines_gdb_L1 = gpd.read_file(file_shp_L1, columns=columns, where=f"area>{min_area}", bbox=bbox)
66
+ coastlines_gdb_L6 = gpd.read_file(file_shp_L6, columns=columns, where=f"area>{min_area}", bbox=bbox)
67
+ coastlines_gdb_list = [coastlines_gdb_L1,coastlines_gdb_L6]
68
+ if len(coastlines_gdb_L1)<2: #if only one L1 polygon is selected, automatically add lakes and islands-in-lakes
69
+ coastlines_gdb_L2 = gpd.read_file(file_shp_L2, columns=columns, where=f"area>{min_area}", bbox=bbox)
70
+ coastlines_gdb_list.append(coastlines_gdb_L2)
71
+ coastlines_gdb_L3 = gpd.read_file(file_shp_L3, columns=columns, where=f"area>{min_area}", bbox=bbox)
72
+ coastlines_gdb_list.append(coastlines_gdb_L3)
73
+
74
+ # remove empty geodataframes from list to avoid FutureWarning
75
+ # escape for empty resulting list and concatenate otherwise
76
+ coastlines_gdb_list = [x for x in coastlines_gdb_list if not x.empty]
77
+ if not coastlines_gdb_list:
78
+ return gpd.GeoDataFrame()
79
+ coastlines_gdb = pd.concat(coastlines_gdb_list)
80
+ print(f'{(dt.datetime.now()-dtstart).total_seconds():.2f} sec')
81
+
82
+ if crs:
83
+ coastlines_gdb = coastlines_gdb.to_crs(crs)
84
+
85
+ return coastlines_gdb
86
+
87
+
88
+ def get_borders_gdb(res:str='h', bbox:tuple = (-180, -90, 180, 90), crs:str = None) -> gpd.geoseries.GeoSeries:
89
+ """
90
+ GSHHS coastlines: https://www.ngdc.noaa.gov/mgg/shorelines/data/gshhg/latest/readme.txt
91
+ geopandas docs https://geopandas.org/en/stable/docs/reference/api/geopandas.read_file.html
92
+
93
+ Parameters
94
+ ----------
95
+ res : str, optional
96
+ f(ull), h(igh), i(ntermediate), l(ow), c(oarse) resolution. The default is 'h'.
97
+ bbox : tuple, optional
98
+ (minx, miny, maxx, maxy), also includes shapes that are partly in the bbox. The default is (-180, -90, 180, 90).
99
+ crs : str, optional
100
+ coordinate reference system
101
+
102
+ Returns
103
+ -------
104
+ coastlines_gdb : TYPE
105
+ DESCRIPTION.
106
+
107
+ """
108
+
109
+ if res not in 'fhilc':
110
+ raise KeyError(f'invalid res="{res}", resolution options are f(ull), h(igh), i(ntermediate), l(ow), c(oarse)')
111
+ if crs is not None:
112
+ bbox = bbox_convert_crs(bbox, crs)
113
+
114
+ # download gshhs data if not present and return dir
115
+ dir_gshhs = gshhs_coastlines_shp()
116
+
117
+ file_shp_L1 = os.path.join(dir_gshhs,'WDBII_shp',res,f'WDBII_border_{res}_L1.shp') #borders
118
+
119
+ print('>> reading country borders: ',end='')
120
+ dtstart = dt.datetime.now()
121
+ coastlines_gdb = gpd.read_file(file_shp_L1, bbox=bbox)
122
+ print(f'{(dt.datetime.now()-dtstart).total_seconds():.2f} sec')
123
+
124
+ if crs:
125
+ coastlines_gdb = coastlines_gdb.to_crs(crs)
126
+
127
+ return coastlines_gdb
128
+
129
+
130
+ def plot_coastlines(ax=None, res:str='h', min_area:float = 0, crs=None, **kwargs):
131
+ """
132
+ get coastlines with get_coastlines_gdb and bbox depending on axlims, plot on ax and set axlims back to original values
133
+ """
134
+ #TODO: if ax is GeoAxis, get crs from ax
135
+
136
+ if ax is None:
137
+ ax = plt.gca()
138
+
139
+ xlim = ax.get_xlim()
140
+ ylim = ax.get_ylim()
141
+ bbox = (xlim[0], ylim[0], xlim[1], ylim[1])
142
+
143
+ if 'edgecolor' not in kwargs:
144
+ kwargs['edgecolor'] = 'k'
145
+ if 'facecolor' not in kwargs:
146
+ kwargs['facecolor'] = 'none'
147
+ if 'linewidth' not in kwargs:
148
+ kwargs['linewidth'] = 0.5
149
+
150
+ coastlines_gdb = get_coastlines_gdb(bbox=bbox, res=res, min_area=min_area, crs=crs)
151
+ if coastlines_gdb.empty:
152
+ return
153
+
154
+ coastlines_gdb.plot(ax=ax, **kwargs)
155
+
156
+ ax.set_xlim(xlim)
157
+ ax.set_ylim(ylim)
158
+
159
+
160
+ def plot_borders(ax=None, res:str='h', crs=None, **kwargs):
161
+ """
162
+ get borders with get_borders_gdb and bbox depending on axlims, plot on ax and set axlims back to original values
163
+ """
164
+ #TODO: if ax is GeoAxis, get crs from ax
165
+
166
+ if ax is None:
167
+ ax = plt.gca()
168
+
169
+ xlim = ax.get_xlim()
170
+ ylim = ax.get_ylim()
171
+ bbox = (xlim[0], ylim[0], xlim[1], ylim[1])
172
+
173
+ if 'edgecolor' not in kwargs:
174
+ kwargs['edgecolor'] = 'grey'
175
+ if 'linewidth' not in kwargs:
176
+ kwargs['linewidth'] = 0.5
177
+
178
+ coastlines_gdb = get_borders_gdb(bbox=bbox, res=res, crs=crs)
179
+ if coastlines_gdb.empty:
180
+ return
181
+
182
+ coastlines_gdb.plot(ax=ax, **kwargs)
183
+
184
+ ax.set_xlim(xlim)
185
+ ax.set_ylim(ylim)
186
+
dfm_tools/data.py ADDED
@@ -0,0 +1,336 @@
1
+ import os
2
+ import requests
3
+ import xarray as xr
4
+ import xugrid as xu
5
+ import pooch
6
+ import zipfile
7
+ from dfm_tools.xugrid_helpers import (open_partitioned_dataset,
8
+ open_dataset_delft3d4,
9
+ enrich_rst_with_map,
10
+ )
11
+ from dfm_tools.xarray_helpers import preprocess_hisnc
12
+
13
+ __all__ = ["fm_grevelingen_map",
14
+ "fm_grevelingen_his",
15
+ "fm_grevelingen_net",
16
+ "fm_curvedbend_map",
17
+ "fm_curvedbend_his",
18
+ "fm_westernscheldt_map",
19
+ "d3d_westernscheldt_trim",
20
+ "d3d_curvedbend_trim",
21
+ "d3d_curvedbend_trih",
22
+ ]
23
+
24
+
25
+ def get_dir_testdata():
26
+ # create cache dir like %USERPROFILE%/AppData/Local/dfm_tools/dfm_tools/Cache
27
+ # TODO: add SHA256 checking and more, like: https://github.com/Deltares/xugrid/blob/main/xugrid/data/sample_data.py
28
+ dir_testdata = str(pooch.os_cache('dfm_tools'))
29
+ os.makedirs(dir_testdata, exist_ok=True)
30
+ return dir_testdata
31
+
32
+
33
+ def maybe_download_opendap_data(file_nc,dir_subfolder=None):
34
+ if os.path.exists(file_nc): # only download if file does not exist already
35
+ return
36
+
37
+ #opendap catalog: https://opendap.deltares.nl/thredds/catalog/opendap/deltares/Delft3D/netcdf_example_files/catalog.html
38
+ opendap_url = 'https://opendap.deltares.nl/thredds/fileServer/opendap/deltares/Delft3D/netcdf_example_files'
39
+ if dir_subfolder is not None:
40
+ opendap_url = f'{opendap_url}/{dir_subfolder}'
41
+ fname = os.path.basename(file_nc)
42
+ file_url = f'{opendap_url}/{fname}'
43
+
44
+ print(f'downloading "{fname}" from opendap.deltares.nl to cachedir')
45
+ r = requests.get(file_url, allow_redirects=True)
46
+ r.raise_for_status() #raise HTTPError if url not exists
47
+ with open(file_nc, 'wb') as f:
48
+ f.write(r.content)
49
+
50
+
51
+ def fm_grevelingen_map(return_filepath:bool = False) -> xu.UgridDataset:
52
+
53
+ dir_subfolder = 'DFM_grevelingen_3D'
54
+ dir_testdata = get_dir_testdata()
55
+
56
+ file_nc_pat = os.path.join(dir_testdata,'Grevelingen-FM_0*_map.nc')
57
+
58
+ #download data if not present
59
+ for part in range(8):
60
+ file_nc = file_nc_pat.replace('0*',f'{part:04d}')
61
+ maybe_download_opendap_data(file_nc,dir_subfolder)
62
+
63
+ #potentially only return filepath of downloaded file(s)
64
+ filepath = file_nc_pat
65
+ if return_filepath:
66
+ return filepath
67
+
68
+ #open as xugrid.UgridDataset
69
+ uds = open_partitioned_dataset(filepath)
70
+ return uds
71
+
72
+
73
+ def fm_grevelingen_his(return_filepath:bool = False) -> xr.Dataset:
74
+
75
+ dir_subfolder = 'DFM_grevelingen_3D'
76
+ dir_testdata = get_dir_testdata()
77
+
78
+ #download data if not present
79
+ file_nc = os.path.join(dir_testdata,'Grevelingen-FM_0000_his.nc')
80
+ maybe_download_opendap_data(file_nc,dir_subfolder)
81
+
82
+ #potentially only return filepath of downloaded file(s)
83
+ filepath = file_nc
84
+ if return_filepath:
85
+ return filepath
86
+
87
+ #open as xarray.Dataset
88
+ ds = xr.open_mfdataset(filepath,preprocess=preprocess_hisnc)
89
+ return ds
90
+
91
+
92
+ def fm_grevelingen_net(return_filepath:bool = False) -> xu.UgridDataset:
93
+
94
+ dir_subfolder = 'DFM_grevelingen_3D'
95
+ dir_testdata = get_dir_testdata()
96
+
97
+ #download data if not present
98
+ file_nc = os.path.join(dir_testdata,'Grevelingen_FM_grid_20190603_net.nc')
99
+ maybe_download_opendap_data(file_nc,dir_subfolder)
100
+
101
+ #potentially only return filepath of downloaded file(s)
102
+ filepath = file_nc
103
+ if return_filepath:
104
+ return filepath
105
+
106
+ #open as xugrid.UgridDataset
107
+ uds = open_partitioned_dataset(filepath)
108
+ return uds
109
+
110
+
111
+ def fm_curvedbend_map(return_filepath:bool = False) -> xu.UgridDataset:
112
+
113
+ dir_subfolder = 'DFM_curvedbend_3D'
114
+ dir_testdata = get_dir_testdata()
115
+
116
+ #download data if not present
117
+ file_nc = os.path.join(dir_testdata,'cb_3d_map.nc')
118
+ maybe_download_opendap_data(file_nc,dir_subfolder)
119
+
120
+ #potentially only return filepath of downloaded file(s)
121
+ filepath = file_nc
122
+ if return_filepath:
123
+ return filepath
124
+
125
+ #open as xugrid.UgridDataset
126
+ uds = open_partitioned_dataset(filepath)
127
+ return uds
128
+
129
+
130
+ def fm_curvedbend_his(return_filepath:bool = False) -> xr.Dataset:
131
+
132
+ dir_subfolder = 'DFM_curvedbend_3D'
133
+ dir_testdata = get_dir_testdata()
134
+
135
+ #download data if not present
136
+ file_nc = os.path.join(dir_testdata,'cb_3d_his.nc')
137
+ maybe_download_opendap_data(file_nc,dir_subfolder)
138
+
139
+ #potentially only return filepath of downloaded file(s)
140
+ filepath = file_nc
141
+ if return_filepath:
142
+ return filepath
143
+
144
+ #open as xarray.Dataset
145
+ ds = xr.open_mfdataset(filepath,preprocess=preprocess_hisnc)
146
+ return ds
147
+
148
+
149
+ def fm_westernscheldt_map(return_filepath:bool = False) -> xu.UgridDataset:
150
+ # from p:\dflowfm\maintenance\JIRA\05000-05999\05477\c103_ws_3d_fourier
151
+
152
+ dir_subfolder = 'DFM_westernscheldt_3D'
153
+ dir_testdata = get_dir_testdata()
154
+
155
+ #download data if not present
156
+ file_nc = os.path.join(dir_testdata,'westerscheldt01_0subst_map.nc')
157
+ maybe_download_opendap_data(file_nc,dir_subfolder)
158
+
159
+ #potentially only return filepath of downloaded file(s)
160
+ filepath = file_nc
161
+ if return_filepath:
162
+ return filepath
163
+
164
+ #open as UgridDataset
165
+ uds = open_partitioned_dataset(filepath, remove_edges=True)
166
+ return uds
167
+
168
+
169
+ def fm_westernscheldt_fou(return_filepath:bool = False) -> xu.UgridDataset:
170
+ # from p:\dflowfm\maintenance\JIRA\05000-05999\05477\c103_ws_3d_fourier
171
+
172
+ dir_subfolder = 'DFM_westernscheldt_3D'
173
+ dir_testdata = get_dir_testdata()
174
+
175
+ #download data if not present
176
+ file_nc = os.path.join(dir_testdata,'westerscheldt01_0subst_fou.nc')
177
+ maybe_download_opendap_data(file_nc,dir_subfolder)
178
+
179
+ #potentially only return filepath of downloaded file(s)
180
+ filepath = file_nc
181
+ if return_filepath:
182
+ return filepath
183
+
184
+ #open as UgridDataset
185
+ uds = open_partitioned_dataset(filepath, remove_edges=True)
186
+ return uds
187
+
188
+
189
+ def fm_westernscheldt_rst(return_filepath:bool = False) -> xu.UgridDataset:
190
+ # from p:\dflowfm\maintenance\JIRA\05000-05999\05477\c103_ws_3d_fourier
191
+
192
+ dir_subfolder = 'DFM_westernscheldt_3D'
193
+ dir_testdata = get_dir_testdata()
194
+
195
+ #download data if not present
196
+ file_nc = os.path.join(dir_testdata,'westerscheldt01_0subst_20140101_004640_rst.nc')
197
+ maybe_download_opendap_data(file_nc,dir_subfolder)
198
+
199
+ #potentially only return filepath of downloaded file(s)
200
+ filepath = file_nc
201
+ if return_filepath:
202
+ return filepath
203
+
204
+ #open as UgridDataset
205
+ uds = open_partitioned_dataset(filepath, preprocess=enrich_rst_with_map)
206
+ return uds
207
+
208
+
209
+ def fm_westernscheldt_his(return_filepath:bool = False) -> xr.Dataset:
210
+ # from p:\dflowfm\maintenance\JIRA\05000-05999\05477\c103_ws_3d_fourier
211
+
212
+ dir_subfolder = 'DFM_westernscheldt_3D'
213
+ dir_testdata = get_dir_testdata()
214
+
215
+ #download data if not present
216
+ file_nc = os.path.join(dir_testdata,'westerscheldt01_0subst_his.nc')
217
+ maybe_download_opendap_data(file_nc,dir_subfolder)
218
+
219
+ #potentially only return filepath of downloaded file(s)
220
+ filepath = file_nc
221
+ if return_filepath:
222
+ return filepath
223
+
224
+ #open as xarray.Dataset
225
+ ds = xr.open_mfdataset(filepath,preprocess=preprocess_hisnc)
226
+ return ds
227
+
228
+
229
+ def d3d_westernscheldt_trim(return_filepath:bool = False) -> xu.UgridDataset:
230
+
231
+ dir_testdata = get_dir_testdata()
232
+
233
+ #download data if not present
234
+ file_nc = os.path.join(dir_testdata,'trim-westernscheldt_sph.nc')
235
+ maybe_download_opendap_data(file_nc)
236
+
237
+ #potentially only return filepath of downloaded file(s)
238
+ filepath = file_nc
239
+ if return_filepath:
240
+ return filepath
241
+
242
+ #open as UgridDataset
243
+ uds = open_dataset_delft3d4(filepath)
244
+ return uds
245
+
246
+
247
+ def d3d_curvedbend_trim(return_filepath:bool = False) -> xu.UgridDataset:
248
+
249
+ dir_testdata = get_dir_testdata()
250
+
251
+ #download data if not present
252
+ file_nc = os.path.join(dir_testdata,'trim-cb2-sal-added-3d.nc')
253
+ maybe_download_opendap_data(file_nc)
254
+
255
+ #potentially only return filepath of downloaded file(s)
256
+ filepath = file_nc
257
+ if return_filepath:
258
+ return filepath
259
+
260
+ #open as UgridDataset
261
+ uds = open_dataset_delft3d4(filepath)
262
+ return uds
263
+
264
+
265
+ def d3d_curvedbend_trih(return_filepath:bool = False) -> xr.Dataset:
266
+
267
+ dir_testdata = get_dir_testdata()
268
+
269
+ #download data if not present
270
+ file_nc = os.path.join(dir_testdata,'trih-cb2-sal-added-3d.nc')
271
+ maybe_download_opendap_data(file_nc)
272
+
273
+ #potentially only return filepath of downloaded file(s)
274
+ filepath = file_nc
275
+ if return_filepath:
276
+ return filepath
277
+
278
+ #open as UgridDataset
279
+ ds = xr.open_mfdataset(filepath,preprocess=preprocess_hisnc)
280
+ return ds
281
+
282
+
283
+ def gshhs_coastlines_shp() -> str:
284
+ """
285
+ Downloads and unzips GSHHS coastlines from NOAA if not present.
286
+ Checks presence of files for all five resolutions.
287
+
288
+ Returns
289
+ -------
290
+ str
291
+ DESCRIPTION.
292
+
293
+ """
294
+
295
+ dir_testdata = get_dir_testdata()
296
+
297
+ fname = 'gshhg-shp-2.3.7'
298
+ filepath_zip = os.path.join(dir_testdata, f"{fname}.zip")
299
+ dir_gshhs = os.path.join(dir_testdata, fname)
300
+
301
+ def download_gshhs(url):
302
+ url_base = url.split("/")[2]
303
+ fname_zip = url.split('/')[-1]
304
+ print(f'downloading "{fname_zip}" from {url_base} to cachedir')
305
+ # url is redirected to https://objects.githubusercontent.com
306
+ resp = requests.get(url, allow_redirects=True)
307
+ # raise HTTPError if url not exists
308
+ resp.raise_for_status()
309
+ return resp
310
+
311
+ # download zipfile if not present
312
+ if not os.path.exists(filepath_zip) and not os.path.exists(dir_gshhs):
313
+ file_url = f"https://github.com/GenericMappingTools/gshhg-gmt/releases/download/2.3.7/{fname}.zip"
314
+ resp = download_gshhs(file_url)
315
+ with open(filepath_zip, 'wb') as f:
316
+ f.write(resp.content)
317
+
318
+ # unzip zipfile if unzipped folder not present
319
+ if not os.path.exists(dir_gshhs):
320
+ print(f'unzipping "{fname}.zip"')
321
+ with zipfile.ZipFile(filepath_zip, 'r') as zip_ref:
322
+ zip_ref.extractall(dir_gshhs)
323
+
324
+ # construct filepath list and check existence of shapefiles
325
+ filepath_shp_list = [os.path.join(dir_gshhs,'GSHHS_shp',res,f'GSHHS_{res}_L1.shp') for res in ['f','h','i','l','c']]
326
+ for filepath_shp in filepath_shp_list:
327
+ assert os.path.exists(filepath_shp) #coastlines
328
+ assert os.path.exists(filepath_shp.replace('L1.shp','L2.shp')) #lakes
329
+ assert os.path.exists(filepath_shp.replace('L1.shp','L3.shp')) #islands-in-lakes
330
+ assert os.path.exists(filepath_shp.replace('L1.shp','L6.shp')) #Antarctic grounding-line polygons
331
+ filepath_shp_list = [os.path.join(dir_gshhs,'WDBII_shp',res,f'WDBII_border_{res}_L1.shp') for res in ['f','h','i','l','c']]
332
+ for filepath_shp in filepath_shp_list:
333
+ assert os.path.exists(filepath_shp) #coastlines
334
+
335
+ return dir_gshhs
336
+