foxes 0.5.0.2__py3-none-any.whl → 0.5.2__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.
Potentially problematic release.
This version of foxes might be problematic. Click here for more details.
- foxes/VERSION +1 -1
- foxes/algorithms/downwind/downwind.py +41 -46
- foxes/algorithms/downwind/models/point_wakes_calc.py +4 -9
- foxes/algorithms/downwind/models/set_amb_point_results.py +5 -22
- foxes/core/algorithm.py +1 -1
- foxes/core/data_calc_model.py +26 -2
- foxes/core/partial_wakes_model.py +1 -1
- foxes/core/rotor_model.py +36 -2
- foxes/core/turbine_model.py +36 -0
- foxes/core/turbine_type.py +35 -1
- foxes/core/wake_frame.py +39 -3
- foxes/core/wake_model.py +36 -0
- foxes/models/model_book.py +129 -89
- foxes/models/turbine_models/rotor_centre_calc.py +1 -2
- foxes/models/turbine_types/CpCt_file.py +13 -3
- foxes/models/turbine_types/CpCt_from_two.py +14 -4
- foxes/models/vertical_profiles/abl_log_neutral_ws.py +32 -5
- foxes/models/vertical_profiles/abl_log_stable_ws.py +32 -4
- foxes/models/vertical_profiles/abl_log_unstable_ws.py +32 -4
- foxes/models/vertical_profiles/abl_log_ws.py +50 -18
- foxes/models/wake_frames/yawed_wakes.py +15 -9
- foxes/models/wake_models/induction/__init__.py +1 -1
- foxes/models/wake_models/induction/rankine_half_body.py +33 -7
- foxes/models/wake_models/ti/crespo_hernandez.py +6 -1
- foxes/models/wake_models/ti/iec_ti.py +5 -3
- foxes/models/wake_models/wind/__init__.py +2 -2
- foxes/models/wake_models/wind/{bastankhah.py → bastankhah14.py} +11 -14
- foxes/models/wake_models/wind/{porte_agel.py → bastankhah16.py} +24 -16
- foxes/models/wake_models/wind/turbopark.py +11 -22
- foxes/models/wake_superpositions/__init__.py +9 -5
- foxes/models/wake_superpositions/ti_linear.py +134 -0
- foxes/models/wake_superpositions/ti_max.py +134 -0
- foxes/models/wake_superpositions/{ti_superp.py → ti_pow.py} +15 -57
- foxes/models/wake_superpositions/ti_quadratic.py +134 -0
- foxes/models/wake_superpositions/ws_linear.py +170 -0
- foxes/models/wake_superpositions/ws_max.py +173 -0
- foxes/models/wake_superpositions/ws_pow.py +175 -0
- foxes/models/wake_superpositions/{product.py → ws_product.py} +43 -22
- foxes/models/wake_superpositions/ws_quadratic.py +170 -0
- foxes/output/__init__.py +4 -0
- foxes/output/calc_points.py +143 -0
- foxes/output/flow_plots_2d/__init__.py +1 -0
- foxes/output/flow_plots_2d/common.py +104 -1
- foxes/output/flow_plots_2d/flow_plots.py +237 -569
- foxes/output/flow_plots_2d/get_fig.py +183 -0
- foxes/output/flow_plots_2d/seq_flow_ani_plugin.py +0 -1
- foxes/output/grids.py +705 -0
- foxes/output/output.py +58 -11
- foxes/output/results_writer.py +101 -17
- foxes/output/round.py +10 -0
- foxes/output/slice_data.py +900 -0
- foxes/utils/__init__.py +5 -3
- foxes/utils/exec_python.py +56 -0
- foxes/utils/geopandas_utils.py +294 -0
- foxes/utils/pandas_utils.py +175 -0
- foxes/utils/plotly_utils.py +19 -0
- foxes/utils/xarray_utils.py +38 -0
- {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/METADATA +1 -1
- {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/RECORD +63 -49
- foxes/models/wake_superpositions/linear.py +0 -242
- foxes/models/wake_superpositions/max.py +0 -258
- foxes/models/wake_superpositions/quadratic.py +0 -252
- {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/LICENSE +0 -0
- {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/WHEEL +0 -0
- {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/top_level.txt +0 -0
- {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/zip-safe +0 -0
foxes/utils/__init__.py
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
General utilities.
|
|
3
3
|
"""
|
|
4
4
|
from .wind_dir import wd2uv, wd2wdvec, wdvec2wd, uv2wd, delta_wd
|
|
5
|
-
from .
|
|
5
|
+
from .pandas_utils import PandasFileHelper
|
|
6
|
+
from .xarray_utils import write_nc
|
|
6
7
|
from .subclasses import all_subclasses
|
|
7
8
|
from .dict import Dict
|
|
8
9
|
from .data_book import DataBook
|
|
9
|
-
from .
|
|
10
|
+
from .plotly_utils import show_plotly_fig
|
|
10
11
|
from .cubic_roots import cubic_roots
|
|
11
|
-
from .
|
|
12
|
+
from .geopandas_utils import read_shp, shp2csv, read_shp_polygons, shp2geom2d
|
|
12
13
|
from .load import import_module
|
|
14
|
+
from .exec_python import exec_python
|
|
13
15
|
|
|
14
16
|
from . import two_circles
|
|
15
17
|
from . import abl
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import xarray as xr
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
def exec_python(s, indicator="%", newline=";", globals=globals(), locals={}):
|
|
7
|
+
"""
|
|
8
|
+
Executes strings that start with the
|
|
9
|
+
indicator as python commands, returns one value
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
s = "%p%N=10;p=np.zeros((N,3));p[:,0]=50;p[:,1]=np.linspace(0,7000,N);p[:,2]=119"
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
s: list, dict or object
|
|
17
|
+
The source to by analyzed
|
|
18
|
+
indicator: str
|
|
19
|
+
The indicator that trigger python evaluation
|
|
20
|
+
newline: str
|
|
21
|
+
The new line indicator
|
|
22
|
+
globals: dict
|
|
23
|
+
The global namespace
|
|
24
|
+
locals: dict
|
|
25
|
+
The local namespace
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
out: list, dict or object
|
|
30
|
+
The same structure, but all python
|
|
31
|
+
strings evaluated
|
|
32
|
+
|
|
33
|
+
:group: utils
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(s, str):
|
|
37
|
+
L = len(indicator)
|
|
38
|
+
if len(s) > L and s[:L] == indicator:
|
|
39
|
+
a = s[L:]
|
|
40
|
+
if not indicator in a:
|
|
41
|
+
exec(a, globals, locals)
|
|
42
|
+
else:
|
|
43
|
+
ilist = a.split(indicator)
|
|
44
|
+
if len(ilist) != 2:
|
|
45
|
+
raise ValueError(f"Expecting at most 2 occurences of '{indicator}', found {len(ilist)}: {s}")
|
|
46
|
+
v, b = ilist
|
|
47
|
+
for c in b.split(newline):
|
|
48
|
+
exec(c, globals, locals)
|
|
49
|
+
return locals[v]
|
|
50
|
+
elif isinstance(s, list):
|
|
51
|
+
return [exec_python(a, indicator) for a in s]
|
|
52
|
+
elif isinstance(s, tuple):
|
|
53
|
+
return tuple(exec_python(list(s), indicator))
|
|
54
|
+
elif isinstance(s, dict):
|
|
55
|
+
return {k: exec_python(a, indicator) for k, a in s.items()}
|
|
56
|
+
return s
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import argparse
|
|
3
|
+
|
|
4
|
+
import foxes.constants as FC
|
|
5
|
+
from .dict import Dict
|
|
6
|
+
from .geom2d import AreaUnion, ClosedPolygon
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import geopandas as gpd
|
|
10
|
+
|
|
11
|
+
IMPORT_GPD_OK = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
gpd = None
|
|
14
|
+
IMPORT_GPD_OK = False
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import utm
|
|
18
|
+
|
|
19
|
+
IMPORT_UTM_OK = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
utm = None
|
|
22
|
+
IMPORT_UTM_OK = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_import_gpd():
|
|
26
|
+
"""
|
|
27
|
+
Checks if library import worked,
|
|
28
|
+
raises error otherwise.
|
|
29
|
+
"""
|
|
30
|
+
if not IMPORT_GPD_OK:
|
|
31
|
+
print("\n\nFailed to import geopandas. Please install, either via pip:\n")
|
|
32
|
+
print(" pip install geopandas\n")
|
|
33
|
+
print("or via conda:\n")
|
|
34
|
+
print(" conda install -c conda-forge geopandas\n")
|
|
35
|
+
raise ImportError("Failed to import geopandas")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_import_utm():
|
|
39
|
+
"""
|
|
40
|
+
Checks if library import worked,
|
|
41
|
+
raises error otherwise.
|
|
42
|
+
"""
|
|
43
|
+
if not IMPORT_UTM_OK:
|
|
44
|
+
print("\n\nFailed to import utm. Please install, either via pip:\n")
|
|
45
|
+
print(" pip install utm\n")
|
|
46
|
+
print("or via conda:\n")
|
|
47
|
+
print(" conda install -c conda-forge utm\n")
|
|
48
|
+
raise ImportError("Failed to import utm")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def read_shp(fname, **kwargs):
|
|
52
|
+
"""
|
|
53
|
+
Read a shape file
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
fname: str
|
|
58
|
+
Path to the .shp file
|
|
59
|
+
kwargs: dict, optional
|
|
60
|
+
Additional parameters for geopandas.read_file()
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
data: geopandas.GeoDataFrame
|
|
65
|
+
The data frame in WSG84
|
|
66
|
+
|
|
67
|
+
:group: utils
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
check_import_gpd()
|
|
71
|
+
gpdf = gpd.read_file(fname, **kwargs)
|
|
72
|
+
return gpdf.to_crs("EPSG:4326") # Convert to WGS84
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def shp2csv(ifile, ofile, in_kwargs={}, out_kwargs={}, verbosity=1):
|
|
76
|
+
"""
|
|
77
|
+
Read shape file, write csv file
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
iname: str
|
|
82
|
+
Path to the input .shp file
|
|
83
|
+
oname: str
|
|
84
|
+
Path to the output .csv file
|
|
85
|
+
in_kwargs: dict
|
|
86
|
+
Additional parameters for geopandas.read_file()
|
|
87
|
+
out_kwargs: dict
|
|
88
|
+
Additional parameters for geopandas to_csv()
|
|
89
|
+
verbosity: int
|
|
90
|
+
The verbosity level, 0 = silent
|
|
91
|
+
|
|
92
|
+
:group: utils
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
if verbosity > 0:
|
|
96
|
+
print("Reading file", ifile)
|
|
97
|
+
|
|
98
|
+
gpdf = read_shp(ifile, **in_kwargs)
|
|
99
|
+
|
|
100
|
+
if verbosity > 0:
|
|
101
|
+
print("Writing file", ofile)
|
|
102
|
+
|
|
103
|
+
gpdf.to_csv(ofile, **out_kwargs)
|
|
104
|
+
|
|
105
|
+
return gpdf
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _extract_poly_coords(geom):
|
|
109
|
+
"""
|
|
110
|
+
Helper function for shapefile reading
|
|
111
|
+
"""
|
|
112
|
+
if geom.geom_type == "Polygon":
|
|
113
|
+
exterior_coords = geom.exterior.coords[:]
|
|
114
|
+
interior_coords = []
|
|
115
|
+
for interior in geom.interiors:
|
|
116
|
+
interior_coords.append(interior.coords[:])
|
|
117
|
+
elif geom.geom_type == "MultiPolygon":
|
|
118
|
+
exterior_coords = []
|
|
119
|
+
interior_coords = []
|
|
120
|
+
for part in geom.geoms:
|
|
121
|
+
epe, epi = _extract_poly_coords(part) # Recursive call
|
|
122
|
+
exterior_coords.append(epe)
|
|
123
|
+
interior_coords.append(epi)
|
|
124
|
+
else:
|
|
125
|
+
raise ValueError("Unhandled geometry type: " + repr(geom.type))
|
|
126
|
+
return exterior_coords, interior_coords
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def read_shp_polygons(
|
|
130
|
+
fname,
|
|
131
|
+
names=None,
|
|
132
|
+
name_col="Name",
|
|
133
|
+
geom_col="geometry",
|
|
134
|
+
to_utm=True,
|
|
135
|
+
ret_utm_zone=False,
|
|
136
|
+
**kwargs,
|
|
137
|
+
):
|
|
138
|
+
"""
|
|
139
|
+
Reads the polygon points from a shp file.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
fname: str
|
|
144
|
+
Path to the .shp file
|
|
145
|
+
names: list: of str, optinal
|
|
146
|
+
The names of the polygons to be extracted. All by
|
|
147
|
+
default
|
|
148
|
+
name_col: int
|
|
149
|
+
Column that contains the area names
|
|
150
|
+
geom_col: str
|
|
151
|
+
The geometry column
|
|
152
|
+
to_utm: bool or str, optional
|
|
153
|
+
Convert to UTM coordinates. If str, then UTM zone
|
|
154
|
+
plus letter, e.g. "32U"
|
|
155
|
+
ret_utm_zone: bool
|
|
156
|
+
Return UTM zone plus letter as str
|
|
157
|
+
kwargs: dict, optional
|
|
158
|
+
Additional parameters for geopandas.read_shp()
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
point_dict_exterior: dict
|
|
163
|
+
Dict with list of array of points. Key: area name,
|
|
164
|
+
Value: list:np.ndarray, shape of latter: (n_points, 2)
|
|
165
|
+
point_dict_interior: dict
|
|
166
|
+
Dict with list of array of points. Key: area name,
|
|
167
|
+
Value: list:np.ndarray, shape of latter: (n_points, 2)
|
|
168
|
+
utm_zone_str: str, optional
|
|
169
|
+
The utem zone plus letter as str, e.g. "32U"
|
|
170
|
+
|
|
171
|
+
:group: utils
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
pdf = read_shp(fname, **kwargs)
|
|
176
|
+
pnames = list(pdf[name_col])
|
|
177
|
+
|
|
178
|
+
utmz = None
|
|
179
|
+
utml = None
|
|
180
|
+
apply_utm = False
|
|
181
|
+
if isinstance(to_utm, str) or to_utm == True:
|
|
182
|
+
apply_utm = True
|
|
183
|
+
check_import_utm()
|
|
184
|
+
utmz = int(to_utm[:-1]) if isinstance(to_utm, str) else None
|
|
185
|
+
utml = to_utm[-1] if isinstance(to_utm, str) else None
|
|
186
|
+
|
|
187
|
+
exterior = Dict()
|
|
188
|
+
interior = Dict()
|
|
189
|
+
names = pnames if names is None else names
|
|
190
|
+
for name in names:
|
|
191
|
+
if name == name: # exclude nan values
|
|
192
|
+
if not name in pnames:
|
|
193
|
+
raise KeyError(
|
|
194
|
+
f"Name '{name}' not found in file '{fname}'. Names: {pnames}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
a = pdf.loc[pnames.index(name), geom_col]
|
|
198
|
+
epe, epi = _extract_poly_coords(a)
|
|
199
|
+
|
|
200
|
+
def _to_utm(poly):
|
|
201
|
+
nonlocal utmz, utml
|
|
202
|
+
utm_poly = np.zeros_like(poly)
|
|
203
|
+
utm_poly[:, 0], utm_poly[:, 1], utmz, utml = utm.from_latlon(
|
|
204
|
+
poly[:, 1],
|
|
205
|
+
poly[:, 0],
|
|
206
|
+
force_zone_number=utmz,
|
|
207
|
+
force_zone_letter=utml,
|
|
208
|
+
)
|
|
209
|
+
return utm_poly
|
|
210
|
+
|
|
211
|
+
def _to_numpy(data):
|
|
212
|
+
if not len(data):
|
|
213
|
+
return []
|
|
214
|
+
if isinstance(data[0], tuple):
|
|
215
|
+
out = np.array(data, dtype=FC.DTYPE)
|
|
216
|
+
return _to_utm(out) if apply_utm else out
|
|
217
|
+
return [_to_numpy(d) for d in data]
|
|
218
|
+
|
|
219
|
+
exterior[name] = _to_numpy(epe)
|
|
220
|
+
interior[name] = _to_numpy(epi)
|
|
221
|
+
|
|
222
|
+
if ret_utm_zone:
|
|
223
|
+
return exterior, interior, f"{utmz}{utml}"
|
|
224
|
+
else:
|
|
225
|
+
return exterior, interior
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def shp2geom2d(*args, ret_utm_zone=False, **kwargs):
|
|
229
|
+
"""
|
|
230
|
+
Read shapefile into geom2d geometry
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
args: tuple, optional
|
|
235
|
+
Arguments for read_shp_polygons()
|
|
236
|
+
ret_utm_zone: bool
|
|
237
|
+
Return UTM zone plus letter as str
|
|
238
|
+
kwargs: dict, optional
|
|
239
|
+
Keyword arguments for read_shp_polygons()
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
geom: foxes.tools.geom2D.AreaGeometry
|
|
244
|
+
The geometry object
|
|
245
|
+
utm_zone_str: str, optional
|
|
246
|
+
The utem zone plus letter as str, e.g. "32U"
|
|
247
|
+
|
|
248
|
+
:group: utils
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
exint = read_shp_polygons(*args, ret_utm_zone=ret_utm_zone, **kwargs)
|
|
253
|
+
|
|
254
|
+
def _create_geom(data):
|
|
255
|
+
if not len(data):
|
|
256
|
+
return None
|
|
257
|
+
if isinstance(data, dict):
|
|
258
|
+
gs = [_create_geom(g) for g in data.values()]
|
|
259
|
+
gs = [g for g in gs if g is not None]
|
|
260
|
+
return AreaUnion(gs) if len(gs) else None
|
|
261
|
+
if isinstance(data, np.ndarray) and len(data.shape) == 2:
|
|
262
|
+
return ClosedPolygon(data)
|
|
263
|
+
gs = [_create_geom(g) for g in data]
|
|
264
|
+
gs = [g for g in gs if g is not None]
|
|
265
|
+
return AreaUnion(gs) if len(gs) else None
|
|
266
|
+
|
|
267
|
+
gext = _create_geom(exint[0])
|
|
268
|
+
gint = _create_geom(exint[1])
|
|
269
|
+
geom = gext - gint if gint is not None else gext
|
|
270
|
+
|
|
271
|
+
if ret_utm_zone:
|
|
272
|
+
return geom, exint[2]
|
|
273
|
+
else:
|
|
274
|
+
return geom
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
# define arguments and options:
|
|
279
|
+
parser = argparse.ArgumentParser()
|
|
280
|
+
parser.add_argument("shp_file", help="The input .shp file")
|
|
281
|
+
parser.add_argument("-n", "--names", help="Area names", default=None, nargs="+")
|
|
282
|
+
parser.add_argument(
|
|
283
|
+
"--no_utm", help="switch off conversion to UTM", action="store_true"
|
|
284
|
+
)
|
|
285
|
+
args = parser.parse_args()
|
|
286
|
+
|
|
287
|
+
g = shp2geom2d(args.shp_file, to_utm=not args.no_utm, names=args.names)
|
|
288
|
+
|
|
289
|
+
import matplotlib.pyplot as plt
|
|
290
|
+
|
|
291
|
+
fig, ax = plt.subplots()
|
|
292
|
+
g.add_to_figure(ax)
|
|
293
|
+
plt.show()
|
|
294
|
+
plt.close(fig)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import xarray
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
import foxes.variables as FV
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PandasFileHelper:
|
|
10
|
+
"""
|
|
11
|
+
This class helps reading and writing data
|
|
12
|
+
to files via pandas.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
DEFAULT_READING_PARAMETERS: dict
|
|
17
|
+
Default parameters for file reading
|
|
18
|
+
for the supported file formats
|
|
19
|
+
DEFAULT_WRITING_PARAMETERS: dict
|
|
20
|
+
Default parameters for file writing
|
|
21
|
+
for the supported file formats
|
|
22
|
+
DATA_FILE_FORMAT: list:str
|
|
23
|
+
The supported file formats for data export
|
|
24
|
+
DEFAULT_FORMAT_DICT: dict
|
|
25
|
+
Default column formatting
|
|
26
|
+
|
|
27
|
+
:group: utils
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
DEFAULT_READING_PARAMETERS = {
|
|
32
|
+
"csv": {},
|
|
33
|
+
"csv.gz": {},
|
|
34
|
+
"csv.bz2": {},
|
|
35
|
+
"csv.zip": {},
|
|
36
|
+
"h5": {},
|
|
37
|
+
"nc": {},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
DEFAULT_WRITING_PARAMETERS = {
|
|
41
|
+
"csv": {},
|
|
42
|
+
"csv.gz": {},
|
|
43
|
+
"csv.bz2": {},
|
|
44
|
+
"csv.zip": {},
|
|
45
|
+
"h5": {"key": "foxes", "mode": "w"},
|
|
46
|
+
"nc": {},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
DEFAULT_FORMAT_DICT = {
|
|
50
|
+
FV.WD: "{:.3f}",
|
|
51
|
+
FV.AMB_WD: "{:.3f}",
|
|
52
|
+
FV.YAW: "{:.3f}",
|
|
53
|
+
FV.AMB_YAW: "{:.3f}",
|
|
54
|
+
FV.WS: "{:.4f}",
|
|
55
|
+
FV.AMB_WS: "{:.4f}",
|
|
56
|
+
FV.REWS: "{:.4f}",
|
|
57
|
+
FV.AMB_REWS: "{:.4f}",
|
|
58
|
+
FV.REWS2: "{:.4f}",
|
|
59
|
+
FV.AMB_REWS2: "{:.4f}",
|
|
60
|
+
FV.REWS3: "{:.4f}",
|
|
61
|
+
FV.AMB_REWS3: "{:.4f}",
|
|
62
|
+
FV.TI: "{:.6f}",
|
|
63
|
+
FV.AMB_TI: "{:.6f}",
|
|
64
|
+
FV.RHO: "{:.5f}",
|
|
65
|
+
FV.AMB_RHO: "{:.5f}",
|
|
66
|
+
FV.P: "{:.3f}",
|
|
67
|
+
FV.AMB_P: "{:.3f}",
|
|
68
|
+
FV.CT: "{:.6f}",
|
|
69
|
+
FV.AMB_CT: "{:.6f}",
|
|
70
|
+
FV.T: "{:.3f}",
|
|
71
|
+
FV.AMB_T: "{:.3f}",
|
|
72
|
+
FV.YLD: "{:.3f}",
|
|
73
|
+
FV.AMB_YLD: "{:.3f}",
|
|
74
|
+
FV.CAP: "{:.5f}",
|
|
75
|
+
FV.AMB_CAP: "{:.5f}",
|
|
76
|
+
FV.EFF: "{:.5f}",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
DATA_FILE_FORMATS = list(DEFAULT_READING_PARAMETERS.keys())
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def read_file(cls, file_path, **kwargs):
|
|
83
|
+
"""
|
|
84
|
+
Helper for reading data according to file ending.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
file_path: str
|
|
89
|
+
The path to the file
|
|
90
|
+
**kwargs: dict, optional
|
|
91
|
+
Parameters forwarded to the pandas reading method.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
pandas.DataFrame :
|
|
96
|
+
The data
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
fpath = Path(file_path)
|
|
100
|
+
fname = fpath.name
|
|
101
|
+
sfx = ".".join(fname.split(".")[1:])
|
|
102
|
+
f = None
|
|
103
|
+
for fmt in cls.DATA_FILE_FORMATS:
|
|
104
|
+
if sfx[:3] == "csv":
|
|
105
|
+
f = pd.read_csv
|
|
106
|
+
elif sfx == "h5":
|
|
107
|
+
f = pd.read_hdf
|
|
108
|
+
elif sfx == "nc":
|
|
109
|
+
f = lambda fname, **pars: xarray.open_dataset(
|
|
110
|
+
fname, **pars
|
|
111
|
+
).to_dataframe()
|
|
112
|
+
|
|
113
|
+
if f is not None:
|
|
114
|
+
pars = deepcopy(cls.DEFAULT_READING_PARAMETERS[fmt])
|
|
115
|
+
pars.update(kwargs)
|
|
116
|
+
return f(file_path, **pars)
|
|
117
|
+
|
|
118
|
+
raise KeyError(
|
|
119
|
+
f"Unknown file format '{fname}'. Supported formats: {cls.DATA_FILE_FORMATS}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def write_file(cls, data, file_path, format_dict={}, **kwargs):
|
|
124
|
+
"""
|
|
125
|
+
Helper for writing data according to file ending.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
data: pandas.DataFrame
|
|
130
|
+
The data
|
|
131
|
+
file_path: str
|
|
132
|
+
The path to the file
|
|
133
|
+
format_dict: dict
|
|
134
|
+
Dictionary with format entries for
|
|
135
|
+
columns, e.g. '{:.4f}'
|
|
136
|
+
**kwargs: dict, optional
|
|
137
|
+
Parameters forwarded to the pandas writing method.
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
fdict = deepcopy(cls.DEFAULT_FORMAT_DICT)
|
|
142
|
+
fdict.update(format_dict)
|
|
143
|
+
|
|
144
|
+
out = pd.DataFrame(index=data.index, columns=data.columns)
|
|
145
|
+
for c in data.columns:
|
|
146
|
+
if c in fdict.keys():
|
|
147
|
+
out[c] = data[c].map(
|
|
148
|
+
lambda x: fdict[c].format(x) if not pd.isna(x) else x
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
out[c] = data[c]
|
|
152
|
+
|
|
153
|
+
fpath = Path(file_path)
|
|
154
|
+
fname = fpath.name
|
|
155
|
+
sfx = ".".join(fname.split(".")[1:])
|
|
156
|
+
f = None
|
|
157
|
+
for fmt in cls.DATA_FILE_FORMATS:
|
|
158
|
+
if sfx[:3] == "csv":
|
|
159
|
+
f = out.to_csv
|
|
160
|
+
elif sfx == "h5":
|
|
161
|
+
f = out.to_hdf
|
|
162
|
+
elif sfx == "nc":
|
|
163
|
+
f = out.to_netcdf
|
|
164
|
+
|
|
165
|
+
if f is not None:
|
|
166
|
+
pars = cls.DEFAULT_WRITING_PARAMETERS[fmt]
|
|
167
|
+
pars.update(kwargs)
|
|
168
|
+
|
|
169
|
+
f(file_path, **pars)
|
|
170
|
+
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
raise KeyError(
|
|
174
|
+
f"Unknown file format '{file_path}'. Supported formats: {cls.DATA_FILE_FORMATS}"
|
|
175
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import plotly.io as pio
|
|
2
|
+
import io
|
|
3
|
+
from PIL import Image
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def show_plotly_fig(fig):
|
|
7
|
+
"""
|
|
8
|
+
Displays a plotly figure in a window
|
|
9
|
+
|
|
10
|
+
Reference:
|
|
11
|
+
https://stackoverflow.com/questions/53570384/plotly-how-to-make-a-standalone-plot-in-a-window
|
|
12
|
+
|
|
13
|
+
:group: utils
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
buf = io.BytesIO()
|
|
17
|
+
pio.write_image(fig, buf)
|
|
18
|
+
img = Image.open(buf)
|
|
19
|
+
img.show()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
def write_nc(ds, fpath, round="auto", complevel=5, verbosity=1, **kwargs):
|
|
3
|
+
"""
|
|
4
|
+
Writes a dataset to netCFD file
|
|
5
|
+
|
|
6
|
+
Parameters
|
|
7
|
+
----------
|
|
8
|
+
fpath: str
|
|
9
|
+
Path to the output file, should be nc
|
|
10
|
+
round: dict or str, optional
|
|
11
|
+
The rounding definitions, or auto for
|
|
12
|
+
default settings
|
|
13
|
+
complevel: int
|
|
14
|
+
The compression level
|
|
15
|
+
verbosity: int
|
|
16
|
+
The verbosity level, 0 = silent
|
|
17
|
+
kwargs: dict, optional
|
|
18
|
+
Additional parameters for xarray.to_netcdf
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
if round is not None:
|
|
23
|
+
for v in ds.coords.keys():
|
|
24
|
+
if v in round:
|
|
25
|
+
if verbosity > 1:
|
|
26
|
+
print(f"Rounding {v} to {round[v]} decimals")
|
|
27
|
+
ds[v].data = ds[v].data.round(decimals=round[v])
|
|
28
|
+
for v in ds.data_vars.keys():
|
|
29
|
+
if v in round:
|
|
30
|
+
if verbosity > 1:
|
|
31
|
+
print(f"Rounding {v} to {round[v]} decimals")
|
|
32
|
+
ds[v].data = ds[v].data.round(decimals=round[v])
|
|
33
|
+
|
|
34
|
+
if verbosity > 0:
|
|
35
|
+
print("Writing file", fpath)
|
|
36
|
+
|
|
37
|
+
enc = {k: {"zlib": True, "complevel": complevel} for k in ds.data_vars}
|
|
38
|
+
ds.to_netcdf(fpath, encoding=enc, **kwargs)
|