tilupy 2.0.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.
- tilupy/__init__.py +23 -0
- tilupy/analytic_sol.py +2403 -0
- tilupy/benchmark.py +1563 -0
- tilupy/calibration.py +134 -0
- tilupy/cmd.py +177 -0
- tilupy/compare.py +195 -0
- tilupy/download_data.py +68 -0
- tilupy/initdata.py +207 -0
- tilupy/initsimus.py +50 -0
- tilupy/make_mass.py +111 -0
- tilupy/make_topo.py +468 -0
- tilupy/models/__init__.py +0 -0
- tilupy/models/lave2D/__init__.py +0 -0
- tilupy/models/lave2D/initsimus.py +665 -0
- tilupy/models/lave2D/read.py +264 -0
- tilupy/models/ravaflow/__init__.py +0 -0
- tilupy/models/ravaflow/initsimus.py +192 -0
- tilupy/models/ravaflow/read.py +273 -0
- tilupy/models/saval2D/__init__.py +0 -0
- tilupy/models/saval2D/read.py +298 -0
- tilupy/models/shaltop/__init__.py +0 -0
- tilupy/models/shaltop/initsimus.py +375 -0
- tilupy/models/shaltop/read.py +613 -0
- tilupy/notations.py +866 -0
- tilupy/plot.py +234 -0
- tilupy/raster.py +199 -0
- tilupy/read.py +2588 -0
- tilupy/utils.py +656 -0
- tilupy-2.0.0.dist-info/METADATA +876 -0
- tilupy-2.0.0.dist-info/RECORD +34 -0
- tilupy-2.0.0.dist-info/WHEEL +5 -0
- tilupy-2.0.0.dist-info/entry_points.txt +3 -0
- tilupy-2.0.0.dist-info/licenses/LICENSE +516 -0
- tilupy-2.0.0.dist-info/top_level.txt +1 -0
tilupy/utils.py
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
|
|
8
|
+
import shapely.geometry as geom
|
|
9
|
+
import shapely.ops
|
|
10
|
+
|
|
11
|
+
import tilupy.read
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def CSI(pred: np.ndarray, obs: np.ndarray) -> float:
|
|
15
|
+
"""Compute the Critical Success Index (CSI).
|
|
16
|
+
|
|
17
|
+
Measure the fraction of observed and/or predicted events that were correctly predicted.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
pred : numpy.ndarray
|
|
22
|
+
Array of predicted binary values (e.g., 1 for event predicted, 0 otherwise).
|
|
23
|
+
Shape and type must match :data:`obs`.
|
|
24
|
+
obs : numpy.ndarray
|
|
25
|
+
Array of observed binary values (e.g., 1 for event observed, 0 otherwise).
|
|
26
|
+
Shape and type must match :data:`pred`.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
float
|
|
31
|
+
The Critical Success Index (CSI), a score between 0 and 1.
|
|
32
|
+
A score of 1 indicates perfect prediction, while 0 indicates no skill.
|
|
33
|
+
"""
|
|
34
|
+
ipred = pred > 0
|
|
35
|
+
iobs = obs > 0
|
|
36
|
+
|
|
37
|
+
TP = np.sum(ipred * iobs)
|
|
38
|
+
FP = np.sum(ipred * ~iobs)
|
|
39
|
+
FN = np.sum(~ipred * iobs)
|
|
40
|
+
|
|
41
|
+
return TP / (TP + FP + FN)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def diff_runout(x_contour: np.ndarray,
|
|
45
|
+
y_contour: np.ndarray,
|
|
46
|
+
point_ref: tuple[float, float],
|
|
47
|
+
section: np.ndarray | shapely.geometry.LineString = None,
|
|
48
|
+
orientation: str = "W-E"
|
|
49
|
+
) -> float:
|
|
50
|
+
"""Compute runout distance difference between a reference point and its projection on a contour,
|
|
51
|
+
optionally along a specified section line.
|
|
52
|
+
|
|
53
|
+
This function calculates the distance from a reference point to a contour (e.g., a polygon boundary),
|
|
54
|
+
or the distance along a section line (e.g., a transect) between the reference point and its intersection
|
|
55
|
+
with the contour. The section line can be oriented in four cardinal directions.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
x_contour : numpy.ndarray
|
|
60
|
+
Array of x-coordinates defining the contour.
|
|
61
|
+
y_contour : numpy.ndarray
|
|
62
|
+
Array of y-coordinates defining the contour. Must be the same length as :data:`x_contour`.
|
|
63
|
+
point_ref : tuple[float, float]
|
|
64
|
+
Reference point coordinates (x, y) from which the distance is calculated.
|
|
65
|
+
section : numpy.ndarray or shapely.geometry.LineString, optional
|
|
66
|
+
Coordinates of the section line as an array of shape (N, 2) or a Shapely LineString.
|
|
67
|
+
If None, the function returns the Euclidean distance from :data:`point_ref` to the contour.
|
|
68
|
+
By default None.
|
|
69
|
+
orientation : str, optional
|
|
70
|
+
Orientation of the section line. Must be one of:
|
|
71
|
+
|
|
72
|
+
- "W-E" (West-East, default)
|
|
73
|
+
- "E-W" (East-West)
|
|
74
|
+
- "S-N" (South-North)
|
|
75
|
+
- "N-S" (North-South)
|
|
76
|
+
|
|
77
|
+
This determines how the intersection point is selected if multiple intersections exist.
|
|
78
|
+
By default "W-E".
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
float
|
|
83
|
+
If `section` is None: Euclidean distance from :data:`point_ref` to the contour.
|
|
84
|
+
If `section` is provided: Distance along the section line between :data:`point_ref` and the contour intersection.
|
|
85
|
+
The distance is signed, depending on the projection direction.
|
|
86
|
+
"""
|
|
87
|
+
npts = len(x_contour)
|
|
88
|
+
contour = geom.LineString(
|
|
89
|
+
[(x_contour[i], y_contour[i]) for i in range(npts)]
|
|
90
|
+
)
|
|
91
|
+
point = geom.Point(point_ref)
|
|
92
|
+
if section is None:
|
|
93
|
+
return point.distance(contour)
|
|
94
|
+
elif isinstance(section, np.ndarray):
|
|
95
|
+
section = geom.LineString(section)
|
|
96
|
+
|
|
97
|
+
assert isinstance(section, geom.LineString)
|
|
98
|
+
section = revert_line(section, orientation)
|
|
99
|
+
intersections = section.intersection(contour)
|
|
100
|
+
if isinstance(intersections, geom.MultiPoint):
|
|
101
|
+
intersections = geom.LineString(intersections.geoms)
|
|
102
|
+
intersections = np.array(intersections.coords)
|
|
103
|
+
if orientation == "W-E":
|
|
104
|
+
i = np.argmax(intersections[:, 0])
|
|
105
|
+
if orientation == "E-W":
|
|
106
|
+
i = np.argmin(intersections[:, 0])
|
|
107
|
+
if orientation == "S-N":
|
|
108
|
+
i = np.argmax(intersections[:, 1])
|
|
109
|
+
if orientation == "N-S":
|
|
110
|
+
i = np.argmin(intersections[:, 1])
|
|
111
|
+
intersection = geom.Point(intersections[i, :])
|
|
112
|
+
|
|
113
|
+
#######
|
|
114
|
+
# plt.figure()
|
|
115
|
+
# cont = np.array(contour.coords)
|
|
116
|
+
# sec = np.array(section.coords)
|
|
117
|
+
# inter = np.array(intersection.coords)
|
|
118
|
+
# pt = np.array(point.coords)
|
|
119
|
+
# plt.plot(cont[:,0], cont[:,1],
|
|
120
|
+
# sec[:,0], sec[:,1],
|
|
121
|
+
# pt[:,0], pt[:,1], 'o',
|
|
122
|
+
# inter[:,0], inter[:,1],'x')
|
|
123
|
+
#######
|
|
124
|
+
|
|
125
|
+
return section.project(intersection) - section.project(point)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def revert_line(line: shapely.geometry.LineString,
|
|
129
|
+
orientation: str = "W-E"
|
|
130
|
+
) -> shapely.geometry.LineString:
|
|
131
|
+
"""Revert a line geometry if its orientation does not match the specified direction.
|
|
132
|
+
|
|
133
|
+
This function checks the orientation of the input line (based on the coordinates of its first and last points)
|
|
134
|
+
and reverses it if necessary to ensure it matches the specified cardinal direction.
|
|
135
|
+
The line is reversed in-place if the initial orientation is opposite to the specified one.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
line : shapely.geometry.LineString
|
|
140
|
+
Input line geometry to be checked and potentially reverted.
|
|
141
|
+
orientation : str, optional
|
|
142
|
+
Desired cardinal direction for the line. Must be one of:
|
|
143
|
+
|
|
144
|
+
- "W-E" (West-East, default): The line should start at the west end (smaller x-coordinate).
|
|
145
|
+
- "E-W" (East-West): The line should start at the east end (larger x-coordinate).
|
|
146
|
+
- "S-N" (South-North): The line should start at the south end (smaller y-coordinate).
|
|
147
|
+
- "N-S" (North-South): The line should start at the north end (larger y-coordinate).
|
|
148
|
+
By default "W-E".
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
shapely.geometry.LineString
|
|
153
|
+
The input line, potentially reverted to match the specified orientation.
|
|
154
|
+
If the line already matches the orientation, it is returned unchanged.
|
|
155
|
+
"""
|
|
156
|
+
pt_init = line.coords[0]
|
|
157
|
+
pt_end = line.coords[-1]
|
|
158
|
+
if orientation == "W-E":
|
|
159
|
+
if pt_init[0] > pt_end[0]:
|
|
160
|
+
line = shapely.ops.substring(line, 1, 0, normalized=True)
|
|
161
|
+
elif orientation == "E-W":
|
|
162
|
+
if pt_init[0] < pt_end[0]:
|
|
163
|
+
line = shapely.ops.substring(line, 1, 0, normalized=True)
|
|
164
|
+
if orientation == "S-N":
|
|
165
|
+
if pt_init[1] > pt_end[1]:
|
|
166
|
+
line = shapely.ops.substring(line, 1, 0, normalized=True)
|
|
167
|
+
elif orientation == "N-S":
|
|
168
|
+
if pt_init[1] < pt_end[1]:
|
|
169
|
+
line = shapely.ops.substring(line, 1, 0, normalized=True)
|
|
170
|
+
|
|
171
|
+
return line
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_contour(x: np.ndarray,
|
|
175
|
+
y: np.ndarray,
|
|
176
|
+
z: np.ndarray,
|
|
177
|
+
zlevels: list[float],
|
|
178
|
+
indstep: int = 1,
|
|
179
|
+
maxdist: float = 30,
|
|
180
|
+
closed_contour: bool = True
|
|
181
|
+
) -> tuple[dict[float, np.ndarray],
|
|
182
|
+
dict[float, np.ndarray]]:
|
|
183
|
+
"""Extract contour lines from a 2D grid of values at specified levels.
|
|
184
|
+
|
|
185
|
+
This function computes contour lines for a given 2D field :data:`z` defined on the grid :data:`(x, y)`,
|
|
186
|
+
at the specified levels :data:`zlevels`. It can optionally ensure that contours are closed by padding
|
|
187
|
+
the input arrays, and filter out contours that are not closed within a maximum distance threshold.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
x : numpy.ndarray
|
|
192
|
+
1D array of x-coordinates defining the grid.
|
|
193
|
+
y : numpy.ndarray
|
|
194
|
+
1D array of y-coordinates defining the grid.
|
|
195
|
+
z : numpy.ndarray
|
|
196
|
+
2D array of values defined on the grid :data:`(x, y)`.
|
|
197
|
+
zlevels : list[float]
|
|
198
|
+
List of contour levels at which to extract the contours.
|
|
199
|
+
indstep : int, optional
|
|
200
|
+
Step used to subsample the contour points, by default 1 (no subsampling).
|
|
201
|
+
maxdist : float, optional
|
|
202
|
+
Maximum allowed distance between the first and last points of a contour line.
|
|
203
|
+
If the distance exceeds this value and `closed_contour` is False, the contour is discarded,
|
|
204
|
+
by default 30.
|
|
205
|
+
closed_contour : bool, optional
|
|
206
|
+
If True, the input arrays are padded to ensure closed contours, by default True.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
tuple[dict[float, np.ndarray], dict[float, np.ndarray]]
|
|
211
|
+
xcontour : dict
|
|
212
|
+
Maps each contour level to an array of x-coordinates of the contour line.
|
|
213
|
+
ycontour : dict
|
|
214
|
+
Maps each contour level to an array of y-coordinates of the contour line.
|
|
215
|
+
If a contour is discarded due to `maxdist`, the corresponding value is `None`.
|
|
216
|
+
"""
|
|
217
|
+
# Add sup_value at the border of the array, to make sure contour
|
|
218
|
+
# lines are closed
|
|
219
|
+
if closed_contour:
|
|
220
|
+
z2 = z.copy()
|
|
221
|
+
ni = z2.shape[0]
|
|
222
|
+
nj = z2.shape[1]
|
|
223
|
+
z2 = np.vstack([np.zeros((1, nj)), z2, np.zeros((1, nj))])
|
|
224
|
+
z2 = np.hstack([np.zeros((ni + 2, 1)), z2, np.zeros((ni + 2, 1))])
|
|
225
|
+
dxi = x[1] - x[0]
|
|
226
|
+
dxf = x[-1] - x[-2]
|
|
227
|
+
dyi = y[1] - y[0]
|
|
228
|
+
dyf = y[-1] - y[-2]
|
|
229
|
+
x2 = np.insert(np.append(x, x[-1] + dxf), 0, x[0] - dxi)
|
|
230
|
+
y2 = np.insert(np.append(y, y[-1] + dyf), 0, y[0] - dyi)
|
|
231
|
+
else:
|
|
232
|
+
x2, y2, z2 = x, y, z
|
|
233
|
+
|
|
234
|
+
backend = plt.get_backend()
|
|
235
|
+
plt.switch_backend("Agg")
|
|
236
|
+
fig = plt.figure()
|
|
237
|
+
ax = plt.gca()
|
|
238
|
+
cs = ax.contour(x2, y2, np.flip(z2, 0), zlevels)
|
|
239
|
+
nn1 = 1
|
|
240
|
+
v1 = np.zeros((1, 2))
|
|
241
|
+
xcontour = {}
|
|
242
|
+
ycontour = {}
|
|
243
|
+
for indlevel in range(len(zlevels)):
|
|
244
|
+
levels = cs.allsegs[indlevel]
|
|
245
|
+
for p in levels:
|
|
246
|
+
if p.shape[0] > nn1:
|
|
247
|
+
v1 = p
|
|
248
|
+
nn1 = p.shape[0]
|
|
249
|
+
xc = [v1[::indstep, 0]]
|
|
250
|
+
yc = [v1[::indstep, 1]]
|
|
251
|
+
if maxdist is not None and not closed_contour:
|
|
252
|
+
ddx = np.abs(v1[0, 0] - v1[-1, 0])
|
|
253
|
+
ddy = np.abs(v1[0, 1] - v1[-1, 1])
|
|
254
|
+
dd = np.sqrt(ddx**2 + ddy**2)
|
|
255
|
+
if dd > maxdist:
|
|
256
|
+
xc[0] = None
|
|
257
|
+
yc[0] = None
|
|
258
|
+
xcontour[zlevels[indlevel]] = xc[0]
|
|
259
|
+
ycontour[zlevels[indlevel]] = yc[0]
|
|
260
|
+
plt.close(fig)
|
|
261
|
+
plt.switch_backend(backend)
|
|
262
|
+
return xcontour, ycontour
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def get_profile(data: tilupy.read.TemporalResults2D | tilupy.read.StaticResults2D,
|
|
266
|
+
extraction_mode: str = "axis",
|
|
267
|
+
data_threshold: float = 1e-3,
|
|
268
|
+
**extraction_params,
|
|
269
|
+
) -> tuple[tilupy.read.TemporalResults1D | tilupy.read.StaticResults1D,
|
|
270
|
+
float | tuple[np.ndarray] | np.ndarray]:
|
|
271
|
+
"""Extract profile with different modes and options.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
data : tilupy.read.TemporalResults2D or tilupy.read.StaticResults2D
|
|
276
|
+
Data to extract the profile from.
|
|
277
|
+
extraction_mode : str, optional
|
|
278
|
+
Method to extract profiles:
|
|
279
|
+
|
|
280
|
+
- "axis": Extracts a profile along an axis.
|
|
281
|
+
- "coordinates": Extracts a profile along specified coordinates.
|
|
282
|
+
- "shapefile": Extracts a profile along a shapefile (polylines).
|
|
283
|
+
|
|
284
|
+
Be default "axis".
|
|
285
|
+
|
|
286
|
+
data_threshold : float, optional
|
|
287
|
+
Minimum value to consider as part of the profile, by default 1e-3.
|
|
288
|
+
|
|
289
|
+
extraction_params : dict, optional
|
|
290
|
+
Different parameters to be entered depending on the extraction method chosen:
|
|
291
|
+
|
|
292
|
+
- If :data:`extraction_mode == "axis"`:
|
|
293
|
+
|
|
294
|
+
- :data:`axis`: str, optional
|
|
295
|
+
Axis where to extract the profile ['X', 'Y'], by default 'Y'.
|
|
296
|
+
- :data:`profile_position`: float, optional
|
|
297
|
+
Position where to extract the profile. If None choose the median.
|
|
298
|
+
By default None.
|
|
299
|
+
Must be read: profile in :data:`axis = profile_position m`.
|
|
300
|
+
|
|
301
|
+
- If :data:`extraction_mode == "coordinates"`:
|
|
302
|
+
|
|
303
|
+
- :data:`xcoord`: numpy.ndarray, optional
|
|
304
|
+
X coordinates of the profile, by default :attr:`_x`.
|
|
305
|
+
- :data:`ycoord`: numpy.ndarray, optional
|
|
306
|
+
Y coordinates of the profile, by default :data:`[0., 0., ...]`.
|
|
307
|
+
|
|
308
|
+
- If :data:`extraction_mode == "shapefile"`:
|
|
309
|
+
|
|
310
|
+
- :data:`path`: str
|
|
311
|
+
Path to the shapefile.
|
|
312
|
+
- :data:`x_origin`: float, optional
|
|
313
|
+
Value of the X coordinate of the origin (top-left corner) in the shapefile's coordinate system, by default 0.0 (EPSG:2154).
|
|
314
|
+
- :data:`y_origin`: float, optional
|
|
315
|
+
Value of the y coordinate of the origin (top-left corner) in the shapefile's coordinate system, by default :data:`_y[-1]` (EPSG:2154).
|
|
316
|
+
- :data:`x_pixsize`: float, optional
|
|
317
|
+
Size of a pixel along the X coordinate in the shapefile's coordinate system, by default :data:`_x[1] - _x[0]` (EPSG:2154).
|
|
318
|
+
- :data:`y_pixsize`: float, optional
|
|
319
|
+
Size of a pixel along the Y coordinate in the shapefile's coordinate system, by default :data:`_y[1] - _y[0]` (EPSG:2154).
|
|
320
|
+
- :data:`step`: float, optional
|
|
321
|
+
Spatial step between profile points, by default 10.0.
|
|
322
|
+
|
|
323
|
+
By default None
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
tuple[tilupy.read.TemporalResults1D or tilupy.read.StaticResults1D, float or tuple[np.ndarray] or np.ndarray]
|
|
328
|
+
tilupy.read.TemporalResults1D or tilupy.read.StaticResults1D
|
|
329
|
+
Extracted profiles.
|
|
330
|
+
float or tuple[np.ndarray] or numpy.ndarray
|
|
331
|
+
Specific output depending on :data:`extraction_mode`:
|
|
332
|
+
|
|
333
|
+
- If :data:`extraction_mode == "axis"`: float
|
|
334
|
+
Position of the profile.
|
|
335
|
+
- If :data:`extraction_mode == "coordinates"`: tuple[numpy.ndarray]
|
|
336
|
+
X coordinates, Y coordinates and distance values.
|
|
337
|
+
- If :data:`extraction_mode == "shapefile"`: numpy.ndarray
|
|
338
|
+
Distance values.
|
|
339
|
+
|
|
340
|
+
Raises
|
|
341
|
+
------
|
|
342
|
+
ValueError
|
|
343
|
+
If :data:`extraction_mode == "axis"` and if invalid :data:`axis`.
|
|
344
|
+
ValueError
|
|
345
|
+
If :data:`extraction_mode == "axis"` and if invalid format for :data:`profile_position`.
|
|
346
|
+
ValueError
|
|
347
|
+
If :data:`extraction_mode == "axis"` and if no value position found in axis.
|
|
348
|
+
ValueError
|
|
349
|
+
If :data:`extraction_mode == "coordinates"` and if invalid format for :data:`xcoord` or :data:`ycoord`.
|
|
350
|
+
ValueError
|
|
351
|
+
If :data:`extraction_mode == "coordinates"` and if invalid dimension for :data:`xcoord` or :data:`ycoord`.
|
|
352
|
+
ValueError
|
|
353
|
+
If :data:`extraction_mode == "coordinates"` and if :data:`xcoord` and :data:`ycoord` doesn't have same size.
|
|
354
|
+
ValueError
|
|
355
|
+
If :data:`extraction_mode == "shapefile"` and if no :data`path` is given.
|
|
356
|
+
ValueError
|
|
357
|
+
If :data:`extraction_mode == "shapefile"` and if invalid format for :data:`x_origin`, :data:`y_origin`, :data:`x_pixsize`, :data:`y_pixsize` or :data:`step`.
|
|
358
|
+
TypeError
|
|
359
|
+
If :data:`extraction_mode == "shapefile"` and if invalid geometry for the shapefile.
|
|
360
|
+
ValueError
|
|
361
|
+
If :data:`extraction_mode == "shapefile"` and if no linestring found in the shapefile.
|
|
362
|
+
ValueError
|
|
363
|
+
If invalid :data:`extraction_mode`.
|
|
364
|
+
"""
|
|
365
|
+
if not isinstance(data, tilupy.read.TemporalResults2D) and not isinstance(data, tilupy.read.StaticResults2D):
|
|
366
|
+
raise ValueError("Can only extract profile from 2D data.")
|
|
367
|
+
|
|
368
|
+
y_coord, x_coord = data.y, data.x
|
|
369
|
+
y_size, x_size, = len(y_coord), len(x_coord)
|
|
370
|
+
data_field = data.d.copy()
|
|
371
|
+
|
|
372
|
+
# Apply mask on data
|
|
373
|
+
data_field[data_field <= data_threshold] = 0
|
|
374
|
+
|
|
375
|
+
extraction_params = {} if extraction_params is None else extraction_params
|
|
376
|
+
|
|
377
|
+
if extraction_mode == "axis":
|
|
378
|
+
# Create specific params if not given
|
|
379
|
+
if "axis" not in extraction_params:
|
|
380
|
+
extraction_params["axis"] = 'Y'
|
|
381
|
+
if "profile_position" not in extraction_params:
|
|
382
|
+
extraction_params["profile_position"] = None
|
|
383
|
+
|
|
384
|
+
# Check errors
|
|
385
|
+
if extraction_params["axis"] not in ['x', 'X', 'y', 'Y']:
|
|
386
|
+
raise ValueError("Invalid axis: 'X' or 'Y'.")
|
|
387
|
+
extraction_params["axis"] = extraction_params["axis"].upper()
|
|
388
|
+
|
|
389
|
+
# Depending on "profile_position" type, choose median or position value
|
|
390
|
+
if extraction_params["profile_position"] is None:
|
|
391
|
+
if extraction_params["axis"] == 'X':
|
|
392
|
+
extraction_params["profile_index"] = x_size//2
|
|
393
|
+
closest_value=x_coord[extraction_params["profile_index"]]
|
|
394
|
+
else:
|
|
395
|
+
extraction_params["profile_index"] = y_size//2
|
|
396
|
+
closest_value=y_coord[extraction_params["profile_index"]]
|
|
397
|
+
|
|
398
|
+
elif isinstance(extraction_params["profile_position"], float) or isinstance(extraction_params["profile_position"], int):
|
|
399
|
+
coord_val = extraction_params["profile_position"]
|
|
400
|
+
x_index, y_index = None, None
|
|
401
|
+
|
|
402
|
+
if extraction_params["axis"] == 'X':
|
|
403
|
+
if not isinstance(x_coord, np.ndarray):
|
|
404
|
+
x_coord = np.array(x_coord)
|
|
405
|
+
|
|
406
|
+
x_index = np.argmin(np.abs(x_coord - coord_val))
|
|
407
|
+
closest_value = x_coord[x_index]
|
|
408
|
+
|
|
409
|
+
if x_index is None:
|
|
410
|
+
raise ValueError(f"Find no values, must be: {x_coord}")
|
|
411
|
+
extraction_params["profile_index"] = x_index
|
|
412
|
+
else:
|
|
413
|
+
if not isinstance(y_coord, np.ndarray):
|
|
414
|
+
y_coord = np.array(y_coord)
|
|
415
|
+
|
|
416
|
+
y_index = np.argmin(np.abs(y_coord - coord_val))
|
|
417
|
+
closest_value = y_coord[y_index]
|
|
418
|
+
|
|
419
|
+
if y_index is None:
|
|
420
|
+
raise ValueError(f"Find no values, must be: {y_coord}")
|
|
421
|
+
extraction_params["profile_index"] = y_index
|
|
422
|
+
|
|
423
|
+
else:
|
|
424
|
+
raise ValueError("Invalid format for 'profile_position'. Must be None or float position.")
|
|
425
|
+
|
|
426
|
+
# Return profiles
|
|
427
|
+
if extraction_params["axis"] == 'X':
|
|
428
|
+
if isinstance(data, tilupy.read.TemporalResults2D):
|
|
429
|
+
return (tilupy.read.TemporalResults1D(name=data.name,
|
|
430
|
+
d=data_field[:, extraction_params["profile_index"], :],
|
|
431
|
+
t=data.t,
|
|
432
|
+
coords=data.y,
|
|
433
|
+
coords_name='y',
|
|
434
|
+
notation=data.notation),
|
|
435
|
+
closest_value)
|
|
436
|
+
else:
|
|
437
|
+
return (tilupy.read.StaticResults1D(name=data.name,
|
|
438
|
+
d=data_field[:, extraction_params["profile_index"]],
|
|
439
|
+
coords=data.y,
|
|
440
|
+
coords_name='y',
|
|
441
|
+
notation=data.notation),
|
|
442
|
+
closest_value)
|
|
443
|
+
else:
|
|
444
|
+
if isinstance(data, tilupy.read.TemporalResults2D):
|
|
445
|
+
return (tilupy.read.TemporalResults1D(name=data.name,
|
|
446
|
+
d=data_field[extraction_params["profile_index"], :, :],
|
|
447
|
+
t=data.t,
|
|
448
|
+
coords=data.x,
|
|
449
|
+
coords_name='x',
|
|
450
|
+
notation=data.notation),
|
|
451
|
+
closest_value)
|
|
452
|
+
else:
|
|
453
|
+
return (tilupy.read.StaticResults1D(name=data.name,
|
|
454
|
+
d=data_field[extraction_params["profile_index"], :],
|
|
455
|
+
coords=data.x,
|
|
456
|
+
coords_name='x',
|
|
457
|
+
notation=data.notation),
|
|
458
|
+
closest_value)
|
|
459
|
+
|
|
460
|
+
elif extraction_mode == "coordinates":
|
|
461
|
+
if "xcoord" not in extraction_params:
|
|
462
|
+
extraction_params["xcoord"] = x_coord[:]
|
|
463
|
+
if "ycoord" not in extraction_params:
|
|
464
|
+
extraction_params["ycoord"] = [0] * len(x_coord)
|
|
465
|
+
|
|
466
|
+
# Check errors
|
|
467
|
+
if not isinstance(extraction_params["xcoord"], np.ndarray):
|
|
468
|
+
if isinstance(extraction_params["xcoord"], list):
|
|
469
|
+
extraction_params["xcoord"] = np.array(extraction_params["xcoord"])
|
|
470
|
+
else:
|
|
471
|
+
raise ValueError("Invalid format for 'xcoord'. Must be a numpy array.")
|
|
472
|
+
if not isinstance(extraction_params["ycoord"], np.ndarray):
|
|
473
|
+
if isinstance(extraction_params["ycoord"], list):
|
|
474
|
+
extraction_params["ycoord"] = np.array(extraction_params["ycoord"])
|
|
475
|
+
else:
|
|
476
|
+
raise ValueError("Invalid format for 'ycoord'. Must be a numpy array.")
|
|
477
|
+
|
|
478
|
+
if extraction_params["xcoord"].ndim != 1:
|
|
479
|
+
raise ValueError("Invild dimension. 'xcoord' must be a 1d array.")
|
|
480
|
+
if extraction_params["ycoord"].ndim != 1:
|
|
481
|
+
raise ValueError("Invild dimension. 'ycoord' must be a 1d array.")
|
|
482
|
+
|
|
483
|
+
if len(extraction_params["xcoord"]) != len(extraction_params["ycoord"]):
|
|
484
|
+
raise ValueError(f"'xcoord' and 'ycoord' must have same size: ({len(extraction_params['xcoord'])}, {len(extraction_params['ycoord'])})")
|
|
485
|
+
|
|
486
|
+
# Extract index from nearest value of xcoord and ycoord
|
|
487
|
+
x_distances = np.abs(x_coord[None, :] - extraction_params["xcoord"][:, None])
|
|
488
|
+
y_distances = np.abs(y_coord[None, :] - extraction_params["ycoord"][:, None])
|
|
489
|
+
|
|
490
|
+
x_indexes = np.argmin(x_distances, axis=1)
|
|
491
|
+
y_indexes = np.argmin(y_distances, axis=1)
|
|
492
|
+
|
|
493
|
+
# Compute distance
|
|
494
|
+
x_values = x_coord[x_indexes]
|
|
495
|
+
y_values = y_coord[y_indexes]
|
|
496
|
+
|
|
497
|
+
dx = np.diff(x_values)
|
|
498
|
+
dy = np.diff(y_values)
|
|
499
|
+
|
|
500
|
+
distance = np.sqrt(dx**2 + dy**2)
|
|
501
|
+
distance = np.concatenate(([0], np.cumsum(distance)))
|
|
502
|
+
|
|
503
|
+
# Return profile
|
|
504
|
+
if isinstance(data, tilupy.read.TemporalResults2D):
|
|
505
|
+
return (tilupy.read.TemporalResults1D(name=data.name,
|
|
506
|
+
d=data_field[y_indexes, x_indexes, :],
|
|
507
|
+
t=data.t,
|
|
508
|
+
coords=distance,
|
|
509
|
+
coords_name='d'),
|
|
510
|
+
(x_values,
|
|
511
|
+
y_values,
|
|
512
|
+
distance))
|
|
513
|
+
else:
|
|
514
|
+
return (tilupy.read.StaticResults1D(name=data.name,
|
|
515
|
+
d=data_field[y_indexes, x_indexes],
|
|
516
|
+
coords=distance,
|
|
517
|
+
coords_name='d'),
|
|
518
|
+
(x_values,
|
|
519
|
+
y_values,
|
|
520
|
+
distance))
|
|
521
|
+
|
|
522
|
+
elif extraction_mode == "shapefile" :
|
|
523
|
+
if "path" not in extraction_params:
|
|
524
|
+
extraction_params["path"] = None
|
|
525
|
+
if "x_origin" not in extraction_params:
|
|
526
|
+
extraction_params["x_origin"] = 0
|
|
527
|
+
if "y_origin" not in extraction_params:
|
|
528
|
+
extraction_params["y_origin"] = y_coord[-1]
|
|
529
|
+
if "x_pixsize" not in extraction_params:
|
|
530
|
+
extraction_params["x_pixsize"] = x_coord[1] - x_coord[0]
|
|
531
|
+
if "y_pixsize" not in extraction_params:
|
|
532
|
+
extraction_params["y_pixsize"] = y_coord[1] - y_coord[0]
|
|
533
|
+
if "step" not in extraction_params:
|
|
534
|
+
extraction_params["step"] = 10
|
|
535
|
+
|
|
536
|
+
# Check errors
|
|
537
|
+
if extraction_params["path"] is None:
|
|
538
|
+
raise ValueError("No path to the shape file given.")
|
|
539
|
+
|
|
540
|
+
if not isinstance(extraction_params["x_origin"], float) and not isinstance(extraction_params["x_origin"], int):
|
|
541
|
+
raise ValueError("'x_origin' must be float.")
|
|
542
|
+
if not isinstance(extraction_params["y_origin"], float) and not isinstance(extraction_params["y_origin"], int):
|
|
543
|
+
raise ValueError("'y_origin' must be float.")
|
|
544
|
+
if not isinstance(extraction_params["x_pixsize"], float) and not isinstance(extraction_params["x_pixsize"], int):
|
|
545
|
+
raise ValueError("'x_pixsize' must be float.")
|
|
546
|
+
if not isinstance(extraction_params["y_pixsize"], float) and not isinstance(extraction_params["y_pixsize"], int):
|
|
547
|
+
raise ValueError("'y_pixsize' must be float.")
|
|
548
|
+
|
|
549
|
+
if not isinstance(extraction_params["step"], float) and not isinstance(extraction_params["step"], int):
|
|
550
|
+
raise ValueError("'step' must be float.")
|
|
551
|
+
|
|
552
|
+
# Import specific module and define extraction function
|
|
553
|
+
from shapely.geometry import LineString, MultiLineString
|
|
554
|
+
from shapely.ops import linemerge
|
|
555
|
+
import geopandas as gpd
|
|
556
|
+
from affine import Affine
|
|
557
|
+
|
|
558
|
+
def extract_lines_from_shp_file(shapefile_path):
|
|
559
|
+
"""Extract LineString objects from a shapefile.
|
|
560
|
+
"""
|
|
561
|
+
gdf = gpd.read_file(shapefile_path)
|
|
562
|
+
lines = []
|
|
563
|
+
for geom in gdf.geometry:
|
|
564
|
+
if isinstance(geom, LineString):
|
|
565
|
+
lines.append(geom)
|
|
566
|
+
elif isinstance(geom, MultiLineString):
|
|
567
|
+
lines.extend(list(geom))
|
|
568
|
+
else:
|
|
569
|
+
raise TypeError(f"Invalid geometry: {type(geom)}")
|
|
570
|
+
if not lines:
|
|
571
|
+
raise ValueError("No Linestring found.")
|
|
572
|
+
return lines
|
|
573
|
+
|
|
574
|
+
lines = extract_lines_from_shp_file(extraction_params["path"])
|
|
575
|
+
|
|
576
|
+
# If multiple lines, merge them together
|
|
577
|
+
merged = linemerge(lines)
|
|
578
|
+
if isinstance(merged, LineString):
|
|
579
|
+
profile_line = merged
|
|
580
|
+
else:
|
|
581
|
+
profile_line = LineString([pt for line in merged for pt in line.coords])
|
|
582
|
+
|
|
583
|
+
# Construct the affine transformation
|
|
584
|
+
transform = (Affine.translation(extraction_params["x_origin"],
|
|
585
|
+
extraction_params["y_origin"])
|
|
586
|
+
* Affine.scale(extraction_params["x_pixsize"],
|
|
587
|
+
-extraction_params["y_pixsize"]))
|
|
588
|
+
inv = ~transform # invert : (x, y) -> (row, col)
|
|
589
|
+
|
|
590
|
+
distances = np.arange(0,
|
|
591
|
+
profile_line.length,
|
|
592
|
+
extraction_params["step"])
|
|
593
|
+
points = [profile_line.interpolate(d) for d in distances]
|
|
594
|
+
|
|
595
|
+
# Conversion coordinates -> indexes
|
|
596
|
+
rowcols = [inv * (pt.x, pt.y) for pt in points]
|
|
597
|
+
rowcols = [(int(round(r)), int(round(c))) for c, r in rowcols]
|
|
598
|
+
|
|
599
|
+
# Extract values
|
|
600
|
+
all_values = []
|
|
601
|
+
for t in range(len(data.t)):
|
|
602
|
+
values = []
|
|
603
|
+
valid_distances = []
|
|
604
|
+
for d, (r, c) in zip(distances, rowcols):
|
|
605
|
+
if 0 <= r < data_field.shape[0] and 0 <= c < data_field.shape[1]:
|
|
606
|
+
values.append(data_field[r, c, t])
|
|
607
|
+
valid_distances.append(d)
|
|
608
|
+
all_values.append(values)
|
|
609
|
+
|
|
610
|
+
all_values = np.array(all_values)
|
|
611
|
+
valid_distances = np.array(valid_distances)
|
|
612
|
+
|
|
613
|
+
# Return profile
|
|
614
|
+
if isinstance(data, tilupy.read.TemporalResults2D):
|
|
615
|
+
return (tilupy.read.TemporalResults1D(name=data.name,
|
|
616
|
+
d=all_values.T,
|
|
617
|
+
t=data.t,
|
|
618
|
+
coords=valid_distances,
|
|
619
|
+
coords_name="d",
|
|
620
|
+
notation=data.notation),
|
|
621
|
+
valid_distances)
|
|
622
|
+
else:
|
|
623
|
+
return (tilupy.read.StaticResults1D(name=data.name,
|
|
624
|
+
d=all_values.T,
|
|
625
|
+
coords=valid_distances,
|
|
626
|
+
coords_name="d",
|
|
627
|
+
notation=data.notation),
|
|
628
|
+
valid_distances)
|
|
629
|
+
else :
|
|
630
|
+
raise ValueError("Invalid 'extraction_mode': 'axis', 'coordinates' or 'shapefile'.")
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def format_path_linux(path: str) -> str:
|
|
634
|
+
"""
|
|
635
|
+
Change a Windows-type path to a path formatted for Linux. \\ are changed
|
|
636
|
+
to /, and partitions like "C:" are changed to "/mnt/c/"
|
|
637
|
+
|
|
638
|
+
Parameters
|
|
639
|
+
----------
|
|
640
|
+
path : string
|
|
641
|
+
String with the path to be modified.
|
|
642
|
+
|
|
643
|
+
Returns
|
|
644
|
+
-------
|
|
645
|
+
path2 : string
|
|
646
|
+
Formatted path.
|
|
647
|
+
|
|
648
|
+
"""
|
|
649
|
+
if path[1] == ":":
|
|
650
|
+
path2 = "/mnt/{:s}/".format(path[0].lower()) + path[2:]
|
|
651
|
+
else:
|
|
652
|
+
path2 = path
|
|
653
|
+
path2 = path2.replace("\\", "/")
|
|
654
|
+
if " " in path2:
|
|
655
|
+
path2 = '"' + path2 + '"'
|
|
656
|
+
return path2
|