rashdf 0.8.3__tar.gz → 0.9.0__tar.gz
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.
- {rashdf-0.8.3 → rashdf-0.9.0}/PKG-INFO +2 -2
- {rashdf-0.8.3 → rashdf-0.9.0}/pyproject.toml +2 -2
- {rashdf-0.8.3 → rashdf-0.9.0}/src/cli.py +1 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf/geom.py +63 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf/plan.py +52 -1
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf/utils.py +221 -43
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf.egg-info/PKG-INFO +2 -2
- {rashdf-0.8.3 → rashdf-0.9.0}/tests/test_geom.py +14 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/tests/test_plan.py +15 -0
- rashdf-0.9.0/tests/test_utils.py +231 -0
- rashdf-0.8.3/tests/test_utils.py +0 -75
- {rashdf-0.8.3 → rashdf-0.9.0}/LICENSE +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/README.md +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/setup.cfg +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf/__init__.py +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf/base.py +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf.egg-info/SOURCES.txt +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf.egg-info/dependency_links.txt +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf.egg-info/entry_points.txt +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf.egg-info/requires.txt +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/src/rashdf.egg-info/top_level.txt +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/tests/test_base.py +0 -0
- {rashdf-0.8.3 → rashdf-0.9.0}/tests/test_cli.py +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rashdf
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Read data from HEC-RAS HDF files.
|
|
5
5
|
Project-URL: repository, https://github.com/fema-ffrd/rashdf
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
7
7
|
Classifier: Intended Audience :: Developers
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: h5py
|
|
@@ -7,12 +7,12 @@ classifiers = [
|
|
|
7
7
|
"Intended Audience :: Developers",
|
|
8
8
|
"License :: OSI Approved :: MIT License",
|
|
9
9
|
"Programming Language :: Python :: 3",
|
|
10
|
-
"Programming Language :: Python :: 3.9",
|
|
11
10
|
"Programming Language :: Python :: 3.10",
|
|
12
11
|
"Programming Language :: Python :: 3.11",
|
|
13
12
|
"Programming Language :: Python :: 3.12",
|
|
13
|
+
"Programming Language :: Python :: 3.13",
|
|
14
14
|
]
|
|
15
|
-
version = "0.
|
|
15
|
+
version = "0.9.0"
|
|
16
16
|
dependencies = ["h5py", "geopandas>=1.0,<2.0", "pyarrow", "xarray<=2025.4.0"]
|
|
17
17
|
|
|
18
18
|
[project.optional-dependencies]
|
|
@@ -27,6 +27,8 @@ from .utils import (
|
|
|
27
27
|
convert_ras_hdf_value,
|
|
28
28
|
get_first_hdf_group,
|
|
29
29
|
hdf5_attrs_to_dict,
|
|
30
|
+
copy_lines_parallel,
|
|
31
|
+
experimental,
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
|
|
@@ -474,6 +476,67 @@ class RasGeomHdf(RasHdf):
|
|
|
474
476
|
def connections(self) -> GeoDataFrame: # noqa D102
|
|
475
477
|
raise NotImplementedError
|
|
476
478
|
|
|
479
|
+
@experimental
|
|
480
|
+
def generate_bridge_xs_lines(self, datetime_to_str: bool = False) -> GeoDataFrame:
|
|
481
|
+
"""[EXPERIMENTAL] Attempt to return the 2D bridge cross-section lines.
|
|
482
|
+
|
|
483
|
+
This method attempts to generate the cross-section lines for bridges modeled
|
|
484
|
+
within 2D mesh areas. It should be noted that these lines are not explicitly
|
|
485
|
+
stored within the HEC-RAS Geometry HDF file, and are instead generated based
|
|
486
|
+
on the bridge attributes and centerline geometry. As such, the accuracy of
|
|
487
|
+
these lines may vary depending on the complexity of the bridge geometry and
|
|
488
|
+
output from this method should be reviewed for accuracy.
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
datetime_to_str : bool, optional
|
|
493
|
+
If True, convert datetime values to string format (default: False).
|
|
494
|
+
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
GeoDataFrame
|
|
498
|
+
A GeoDataFrame containing the 2D bridge cross-section lines if they exist.
|
|
499
|
+
"""
|
|
500
|
+
profile_info = self.get(self.GEOM_STRUCTURES_PATH + "/Table Info")
|
|
501
|
+
structs = self.structures().merge(
|
|
502
|
+
pd.DataFrame(profile_info[()] if profile_info is not None else None),
|
|
503
|
+
left_index=True,
|
|
504
|
+
right_index=True,
|
|
505
|
+
)
|
|
506
|
+
if structs.empty:
|
|
507
|
+
return GeoDataFrame()
|
|
508
|
+
|
|
509
|
+
bridges = structs[structs["Mode"] == "Bridge Opening"].copy()
|
|
510
|
+
if bridges.empty:
|
|
511
|
+
return GeoDataFrame()
|
|
512
|
+
|
|
513
|
+
inside_buffer_widths = bridges["Weir Width"] / 2
|
|
514
|
+
inside_bridge_xs = copy_lines_parallel(
|
|
515
|
+
bridges, inside_buffer_widths.values, "struct_id"
|
|
516
|
+
)
|
|
517
|
+
inside_bridge_xs["level"] = "inside"
|
|
518
|
+
|
|
519
|
+
outside_buffer_widths = inside_buffer_widths + bridges["Upstream Distance"]
|
|
520
|
+
outside_bridge_xs = copy_lines_parallel(
|
|
521
|
+
bridges, outside_buffer_widths.values, "struct_id"
|
|
522
|
+
)
|
|
523
|
+
outside_bridge_xs["level"] = "outside"
|
|
524
|
+
|
|
525
|
+
br_xs = GeoDataFrame(
|
|
526
|
+
pd.concat([inside_bridge_xs, outside_bridge_xs], ignore_index=True),
|
|
527
|
+
geometry="geometry",
|
|
528
|
+
)
|
|
529
|
+
br_xs.side = br_xs.side.apply(
|
|
530
|
+
lambda x: "upstream" if x == "right" else "downstream"
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
if datetime_to_str:
|
|
534
|
+
br_xs[self.LAST_EDITED_COLUMN] = br_xs[self.LAST_EDITED_COLUMN].apply(
|
|
535
|
+
lambda x: pd.Timestamp.isoformat(x)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
return br_xs
|
|
539
|
+
|
|
477
540
|
def ic_points(self) -> GeoDataFrame: # noqa D102
|
|
478
541
|
"""Return initial conditions points.
|
|
479
542
|
|
|
@@ -1178,7 +1178,7 @@ class RasPlanHdf(RasGeomHdf):
|
|
|
1178
1178
|
xr.Dataset
|
|
1179
1179
|
An xarray Dataset with timeseries output data for boundary conditions lines.
|
|
1180
1180
|
"""
|
|
1181
|
-
df_bc_lines =
|
|
1181
|
+
df_bc_lines = super().bc_lines()
|
|
1182
1182
|
bc_lines_names = df_bc_lines["name"]
|
|
1183
1183
|
datasets = []
|
|
1184
1184
|
for bc_line_name in bc_lines_names:
|
|
@@ -1197,6 +1197,57 @@ class RasPlanHdf(RasGeomHdf):
|
|
|
1197
1197
|
)
|
|
1198
1198
|
return ds
|
|
1199
1199
|
|
|
1200
|
+
def bc_lines(
|
|
1201
|
+
self, include_output: bool = True, datetime_to_str: bool = False
|
|
1202
|
+
) -> GeoDataFrame:
|
|
1203
|
+
"""Return the boundary condition lines from a HEC-RAS HDF plan file.
|
|
1204
|
+
|
|
1205
|
+
Optionally include summary output data for each boundary condition line.
|
|
1206
|
+
|
|
1207
|
+
Parameters
|
|
1208
|
+
----------
|
|
1209
|
+
include_output : bool, optional
|
|
1210
|
+
If True, include summary output data in the GeoDataFrame. (default: True)
|
|
1211
|
+
datetime_to_str : bool, optional
|
|
1212
|
+
If True, convert datetime columns to strings. (default: False)
|
|
1213
|
+
|
|
1214
|
+
Returns
|
|
1215
|
+
-------
|
|
1216
|
+
GeoDataFrame
|
|
1217
|
+
A GeoDataFrame with boundary condition line geometry and summary output data.
|
|
1218
|
+
"""
|
|
1219
|
+
gdf = super().bc_lines()
|
|
1220
|
+
if include_output is False:
|
|
1221
|
+
return gdf
|
|
1222
|
+
|
|
1223
|
+
ds = self.bc_lines_timeseries_output()
|
|
1224
|
+
summary = {
|
|
1225
|
+
"bc_line_id": ds.coords["bc_line_id"].values,
|
|
1226
|
+
"name": ds.coords["bc_line_name"].values,
|
|
1227
|
+
"mesh_name": ds.coords["mesh_name"].values,
|
|
1228
|
+
"type": ds.coords["bc_line_type"].values,
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
for var in ds.data_vars:
|
|
1232
|
+
abbrev = "q" if var.lower() == "flow" else "ws"
|
|
1233
|
+
summary[f"max_{abbrev}"] = ds[var].max(dim="time").values
|
|
1234
|
+
summary[f"max_{abbrev}_time"] = (
|
|
1235
|
+
ds[var].time[ds[var].argmax(dim="time")].values
|
|
1236
|
+
)
|
|
1237
|
+
summary[f"min_{abbrev}"] = ds[var].min(dim="time").values
|
|
1238
|
+
summary[f"min_{abbrev}_time"] = (
|
|
1239
|
+
ds[var].time[ds[var].argmin(dim="time")].values
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
gdf_with_output = gdf.merge(
|
|
1243
|
+
pd.DataFrame(summary),
|
|
1244
|
+
on=["bc_line_id", "name", "mesh_name", "type"],
|
|
1245
|
+
how="left",
|
|
1246
|
+
)
|
|
1247
|
+
return (
|
|
1248
|
+
df_datetimes_to_str(gdf_with_output) if datetime_to_str else gdf_with_output
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1200
1251
|
def observed_timeseries_input(self, vartype: str = "Flow") -> xr.DataArray:
|
|
1201
1252
|
"""Return observed timeseries input data for reference lines and points from a HEC-RAS HDF plan file.
|
|
1202
1253
|
|
|
@@ -8,6 +8,68 @@ from datetime import datetime, timedelta
|
|
|
8
8
|
import re
|
|
9
9
|
from typing import Any, Callable, List, Tuple, Union, Optional
|
|
10
10
|
import warnings
|
|
11
|
+
from shapely import LineString, MultiLineString
|
|
12
|
+
import geopandas as gpd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def experimental(func) -> Callable:
|
|
16
|
+
"""
|
|
17
|
+
Declare a function to be experimental.
|
|
18
|
+
|
|
19
|
+
This is a decorator which can be used to mark functions as experimental.
|
|
20
|
+
It will result in a warning being emitted when the function is used.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
func: The function to be declared experimental.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
The decorated function.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def new_func(*args, **kwargs):
|
|
32
|
+
warnings.warn(
|
|
33
|
+
f"{func.__name__} is experimental and could change in the future. Please review output carefully.",
|
|
34
|
+
category=UserWarning,
|
|
35
|
+
stacklevel=2,
|
|
36
|
+
)
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
new_func.__name__ = func.__name__
|
|
40
|
+
new_func.__doc__ = func.__doc__
|
|
41
|
+
new_func.__dict__.update(func.__dict__)
|
|
42
|
+
return new_func
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def deprecated(func) -> Callable:
|
|
46
|
+
"""
|
|
47
|
+
Deprecate a function.
|
|
48
|
+
|
|
49
|
+
This is a decorator which can be used to mark functions as deprecated.
|
|
50
|
+
It will result in a warning being emitted when the function is used.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
func: The function to be deprecated.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
The decorated function.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def new_func(*args, **kwargs):
|
|
62
|
+
warnings.warn(
|
|
63
|
+
f"{func.__name__} is deprecated and will be removed in a future version.",
|
|
64
|
+
category=DeprecationWarning,
|
|
65
|
+
stacklevel=2,
|
|
66
|
+
)
|
|
67
|
+
return func(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
new_func.__name__ = func.__name__
|
|
70
|
+
new_func.__doc__ = func.__doc__
|
|
71
|
+
new_func.__dict__.update(func.__dict__)
|
|
72
|
+
return new_func
|
|
11
73
|
|
|
12
74
|
|
|
13
75
|
def parse_ras_datetime_ms(datetime_str: str) -> datetime:
|
|
@@ -36,24 +98,37 @@ def parse_ras_datetime(datetime_str: str) -> datetime:
|
|
|
36
98
|
|
|
37
99
|
Parameters
|
|
38
100
|
----------
|
|
39
|
-
datetime_str (str): The datetime string to be parsed.
|
|
101
|
+
datetime_str (str): The datetime string to be parsed.
|
|
40
102
|
|
|
41
103
|
Returns
|
|
42
104
|
-------
|
|
43
105
|
datetime: A datetime object representing the parsed datetime.
|
|
44
106
|
"""
|
|
45
|
-
|
|
107
|
+
date_formats = ["%d%b%Y", "%m/%d/%Y", "%m-%d-%Y", "%Y/%m/%d", "%Y-%m-%d"]
|
|
108
|
+
time_formats = ["%H:%M:%S", "%H%M"]
|
|
109
|
+
datetime_formats = [
|
|
110
|
+
f"{date} {time}" for date in date_formats for time in time_formats
|
|
111
|
+
]
|
|
46
112
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
parsed_dt += timedelta(days=1)
|
|
51
|
-
else:
|
|
52
|
-
parsed_dt = datetime.strptime(datetime_str, datetime_format)
|
|
113
|
+
is_2400 = datetime_str.endswith((" 24:00:00", " 2400", " 24:00"))
|
|
114
|
+
if is_2400:
|
|
115
|
+
datetime_str = datetime_str.split()[0] + " 00:00:00"
|
|
53
116
|
|
|
54
|
-
|
|
117
|
+
last_exception = None
|
|
118
|
+
for fmt in datetime_formats:
|
|
119
|
+
try:
|
|
120
|
+
parsed_dt = datetime.strptime(datetime_str, fmt)
|
|
121
|
+
if is_2400:
|
|
122
|
+
parsed_dt += timedelta(days=1)
|
|
123
|
+
return parsed_dt
|
|
124
|
+
except ValueError as e:
|
|
125
|
+
last_exception = e
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
raise ValueError(f"Invalid date format: {datetime_str}") from last_exception
|
|
55
129
|
|
|
56
130
|
|
|
131
|
+
@deprecated
|
|
57
132
|
def parse_ras_simulation_window_datetime(datetime_str) -> datetime:
|
|
58
133
|
"""
|
|
59
134
|
Parse a datetime string from a RAS simulation window into a datetime object.
|
|
@@ -139,32 +214,37 @@ def convert_ras_hdf_string(
|
|
|
139
214
|
a list of datetime strings, a timedelta objects, or the original string
|
|
140
215
|
if no other conditions are met.
|
|
141
216
|
"""
|
|
142
|
-
ras_datetime_format1_re = r"\d{2}\w{3}\d{4} \d{2}:\d{2}:\d{2}"
|
|
143
|
-
ras_datetime_format2_re = r"\d{2}\w{3}\d{4} \d{2}\d{2}"
|
|
144
217
|
ras_duration_format_re = r"\d{2}:\d{2}:\d{2}"
|
|
218
|
+
date_patterns_re = [
|
|
219
|
+
r"\d{2}\w{3}\d{4}",
|
|
220
|
+
r"\d{2}/\d{2}/\d{4}",
|
|
221
|
+
r"\d{2}-\d{2}-\d{4}",
|
|
222
|
+
r"\d{4}/\d{2}/\d{2}",
|
|
223
|
+
r"\d{4}-\d{2}-\d{2}",
|
|
224
|
+
]
|
|
225
|
+
time_patterns_re = [
|
|
226
|
+
r"\d{2}:\d{2}:\d{2}",
|
|
227
|
+
r"\d{4}",
|
|
228
|
+
]
|
|
229
|
+
datetime_patterns_re = [
|
|
230
|
+
f"{date} {time}" for date in date_patterns_re for time in time_patterns_re
|
|
231
|
+
]
|
|
145
232
|
s = value.decode("utf-8")
|
|
146
233
|
if s == "True":
|
|
147
234
|
return True
|
|
148
235
|
elif s == "False":
|
|
149
236
|
return False
|
|
150
|
-
elif re.match(rf"^{ras_datetime_format1_re}", s):
|
|
151
|
-
if re.match(rf"^{ras_datetime_format1_re} to {ras_datetime_format1_re}$", s):
|
|
152
|
-
split = s.split(" to ")
|
|
153
|
-
return [
|
|
154
|
-
parse_ras_datetime(split[0]),
|
|
155
|
-
parse_ras_datetime(split[1]),
|
|
156
|
-
]
|
|
157
|
-
return parse_ras_datetime(s)
|
|
158
|
-
elif re.match(rf"^{ras_datetime_format2_re}", s):
|
|
159
|
-
if re.match(rf"^{ras_datetime_format2_re} to {ras_datetime_format2_re}$", s):
|
|
160
|
-
split = s.split(" to ")
|
|
161
|
-
return [
|
|
162
|
-
parse_ras_simulation_window_datetime(split[0]),
|
|
163
|
-
parse_ras_simulation_window_datetime(split[1]),
|
|
164
|
-
]
|
|
165
|
-
return parse_ras_simulation_window_datetime(s)
|
|
166
237
|
elif re.match(rf"^{ras_duration_format_re}$", s):
|
|
167
238
|
return parse_duration(s)
|
|
239
|
+
for dt_re in datetime_patterns_re:
|
|
240
|
+
if re.match(rf"^{dt_re}", s):
|
|
241
|
+
if re.match(rf"^{dt_re} to {dt_re}$", s):
|
|
242
|
+
start, end = s.split(" to ")
|
|
243
|
+
return [
|
|
244
|
+
parse_ras_datetime(start),
|
|
245
|
+
parse_ras_datetime(end),
|
|
246
|
+
]
|
|
247
|
+
return parse_ras_datetime(s)
|
|
168
248
|
return s
|
|
169
249
|
|
|
170
250
|
|
|
@@ -308,31 +388,129 @@ def ras_timesteps_to_datetimes(
|
|
|
308
388
|
]
|
|
309
389
|
|
|
310
390
|
|
|
311
|
-
def
|
|
391
|
+
def remove_line_ends(
|
|
392
|
+
geom: Union[LineString, MultiLineString],
|
|
393
|
+
) -> Union[LineString, MultiLineString]:
|
|
312
394
|
"""
|
|
313
|
-
|
|
395
|
+
Remove endpoints from a LineString or each LineString in a MultiLineString if longer than 3 points.
|
|
314
396
|
|
|
315
|
-
|
|
316
|
-
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
geom : LineString or MultiLineString
|
|
400
|
+
The geometry to trim.
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
LineString or MultiLineString
|
|
405
|
+
The trimmed geometry, or original if not enough points to trim.
|
|
406
|
+
"""
|
|
407
|
+
if isinstance(geom, LineString):
|
|
408
|
+
coords = list(geom.coords)
|
|
409
|
+
if len(coords) > 3:
|
|
410
|
+
return LineString(coords[1:-1])
|
|
411
|
+
return geom
|
|
412
|
+
elif isinstance(geom, MultiLineString):
|
|
413
|
+
trimmed = []
|
|
414
|
+
for line in geom.geoms:
|
|
415
|
+
coords = list(line.coords)
|
|
416
|
+
if len(coords) > 3:
|
|
417
|
+
trimmed.append(LineString(coords[1:-1]))
|
|
418
|
+
else:
|
|
419
|
+
trimmed.append(line)
|
|
420
|
+
return MultiLineString(trimmed)
|
|
421
|
+
return geom
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def reverse_line(
|
|
425
|
+
line: Union[LineString, MultiLineString],
|
|
426
|
+
) -> Union[LineString, MultiLineString]:
|
|
427
|
+
"""
|
|
428
|
+
Reverse the order of coordinates in a LineString or each LineString in a MultiLineString.
|
|
317
429
|
|
|
318
430
|
Parameters
|
|
319
431
|
----------
|
|
320
|
-
|
|
432
|
+
line : LineString or MultiLineString
|
|
433
|
+
The geometry to reverse.
|
|
321
434
|
|
|
322
435
|
Returns
|
|
323
436
|
-------
|
|
324
|
-
|
|
437
|
+
LineString or MultiLineString
|
|
438
|
+
The reversed geometry.
|
|
325
439
|
"""
|
|
440
|
+
return (
|
|
441
|
+
MultiLineString([LineString(list(line.coords)[::-1]) for line in line.geoms])
|
|
442
|
+
if isinstance(line, MultiLineString)
|
|
443
|
+
else LineString(list(line.coords)[::-1])
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def copy_lines_parallel(
|
|
448
|
+
lines: gpd.GeoDataFrame,
|
|
449
|
+
offset_ft: Union[np.ndarray, float],
|
|
450
|
+
id_col: str = "id",
|
|
451
|
+
) -> gpd.GeoDataFrame:
|
|
452
|
+
"""
|
|
453
|
+
Create parallel copies of line geometries offset to the left and right, then trim and erase overlaps.
|
|
326
454
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
455
|
+
Parameters
|
|
456
|
+
----------
|
|
457
|
+
lines : gpd.GeoDataFrame
|
|
458
|
+
GeoDataFrame containing line geometries.
|
|
459
|
+
offset_ft : float or np.ndarray
|
|
460
|
+
Offset distance (in feet) for parallel lines.
|
|
461
|
+
id_col : str
|
|
462
|
+
Name of the column containing unique structure IDs. Default is "id".
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
gpd.GeoDataFrame
|
|
467
|
+
GeoDataFrame with trimmed, parallel left and right offset lines.
|
|
468
|
+
"""
|
|
469
|
+
# Offset lines to the left
|
|
470
|
+
left = lines.copy()
|
|
471
|
+
offset_ft = offset_ft.astype(float)
|
|
472
|
+
left.geometry = lines.buffer(
|
|
473
|
+
offset_ft, cap_style="flat", single_sided=True, resolution=3
|
|
474
|
+
).boundary
|
|
475
|
+
left["side"] = "left"
|
|
476
|
+
|
|
477
|
+
# Offset lines to the right (reverse direction first)
|
|
478
|
+
reversed_lines = lines.copy()
|
|
479
|
+
reversed_lines.geometry = reversed_lines.geometry.apply(reverse_line)
|
|
480
|
+
right = lines.copy()
|
|
481
|
+
right.geometry = reversed_lines.buffer(
|
|
482
|
+
offset_ft, cap_style="flat", single_sided=True, resolution=3
|
|
483
|
+
).boundary.apply(reverse_line)
|
|
484
|
+
right["side"] = "right"
|
|
485
|
+
|
|
486
|
+
# Combine left and right boundaries
|
|
487
|
+
boundaries = pd.concat([left, right], ignore_index=True)
|
|
488
|
+
boundaries_gdf = gpd.GeoDataFrame(boundaries, crs=lines.crs, geometry="geometry")
|
|
489
|
+
|
|
490
|
+
# Erase buffer caps
|
|
491
|
+
erase_buffer = 0.1
|
|
492
|
+
cleaned_list = []
|
|
493
|
+
eraser = gpd.GeoDataFrame(
|
|
494
|
+
{
|
|
495
|
+
id_col: lines[id_col],
|
|
496
|
+
"geometry": lines.buffer(
|
|
497
|
+
offset_ft - erase_buffer, cap_style="square", resolution=3
|
|
498
|
+
),
|
|
499
|
+
},
|
|
500
|
+
crs=lines.crs,
|
|
501
|
+
)
|
|
502
|
+
for id in lines[id_col].unique():
|
|
503
|
+
cleaned_list.append(
|
|
504
|
+
gpd.overlay(
|
|
505
|
+
boundaries_gdf[boundaries_gdf[id_col] == id],
|
|
506
|
+
eraser[eraser[id_col] == id],
|
|
507
|
+
how="difference",
|
|
508
|
+
)
|
|
332
509
|
)
|
|
333
|
-
|
|
510
|
+
cleaned = gpd.GeoDataFrame(
|
|
511
|
+
pd.concat(cleaned_list, ignore_index=True), crs=lines.crs, geometry="geometry"
|
|
512
|
+
)
|
|
334
513
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return new_func
|
|
514
|
+
# trim ends
|
|
515
|
+
cleaned["geometry"] = cleaned["geometry"].apply(remove_line_ends)
|
|
516
|
+
return cleaned
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rashdf
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Read data from HEC-RAS HDF files.
|
|
5
5
|
Project-URL: repository, https://github.com/fema-ffrd/rashdf
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
7
7
|
Classifier: Intended Audience :: Developers
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: h5py
|
|
@@ -17,6 +17,7 @@ TEST_JSON = TEST_DATA / "json"
|
|
|
17
17
|
BALD_EAGLE_P18_REF = TEST_DATA / "ras/BaldEagleDamBrk.reflines-refpts.p18.hdf"
|
|
18
18
|
LOWER_KANAWHA_P01_IC_POINTS = TEST_DATA / "ras/LowerKanawha.p01.icpoints.hdf"
|
|
19
19
|
LOWER_KANAWHA_P01_IC_POINTS_JSON = TEST_JSON / "LowerKanawha.p01.icpoints.geojson"
|
|
20
|
+
ROSEBERRY_G01 = TEST_DATA / "ras/Roseberry_Creek.g01.hdf"
|
|
20
21
|
|
|
21
22
|
TEST_ATTRS = {"test_attribute1": "test_str1", "test_attribute2": 500}
|
|
22
23
|
|
|
@@ -274,3 +275,16 @@ def test_ic_points():
|
|
|
274
275
|
valid_gdf,
|
|
275
276
|
check_dtype=False,
|
|
276
277
|
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_generate_bridge_xs_lines():
|
|
281
|
+
bridge_xs_json = TEST_JSON / "bridge_xs_lines.json"
|
|
282
|
+
with RasGeomHdf(ROSEBERRY_G01) as ghdf:
|
|
283
|
+
assert _gdf_matches_json(
|
|
284
|
+
ghdf.generate_bridge_xs_lines(datetime_to_str=True), bridge_xs_json
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_generate_bridge_xs_lines_not_found():
|
|
289
|
+
with RasGeomHdf(MUNCIE_G05) as ghdf:
|
|
290
|
+
assert ghdf.generate_bridge_xs_lines().empty
|
|
@@ -733,3 +733,18 @@ def test_invalid_group_reference_summary_output():
|
|
|
733
733
|
with RasPlanHdf(BALD_EAGLE_P18) as phdf:
|
|
734
734
|
with pytest.raises(ValueError):
|
|
735
735
|
phdf.reference_summary_output(reftype="Not supported type")
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def test_bc_lines_include_output_false():
|
|
739
|
+
bc_lines_json = TEST_JSON / "bc_lines.json"
|
|
740
|
+
with RasPlanHdf(MUNCIE_G05) as plan_hdf:
|
|
741
|
+
assert _gdf_matches_json(plan_hdf.bc_lines(include_output=False), bc_lines_json)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def test_bc_lines_include_output_true():
|
|
745
|
+
bc_lines_with_output_json = TEST_JSON / "bc_lines_with_output.json"
|
|
746
|
+
with RasPlanHdf(LOWER_KANAWHA_P01_BC_LINES) as plan_hdf:
|
|
747
|
+
assert _gdf_matches_json(
|
|
748
|
+
plan_hdf.bc_lines(include_output=True, datetime_to_str=True),
|
|
749
|
+
bc_lines_with_output_json,
|
|
750
|
+
)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
from src.rashdf import utils
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
from shapely.geometry import LineString, MultiLineString
|
|
9
|
+
import geopandas as gpd
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from . import _assert_geodataframes_close
|
|
13
|
+
|
|
14
|
+
TEST_DATA = Path("./tests/data")
|
|
15
|
+
TEST_JSON = TEST_DATA / "json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_convert_ras_hdf_value():
|
|
19
|
+
assert utils.convert_ras_hdf_value(b"True") is True
|
|
20
|
+
assert utils.convert_ras_hdf_value(b"False") is False
|
|
21
|
+
assert utils.convert_ras_hdf_value(np.float32(1.23)) == pytest.approx(1.23)
|
|
22
|
+
assert utils.convert_ras_hdf_value(np.int32(123)) == 123
|
|
23
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 16:39:01") == datetime(
|
|
24
|
+
2024, 3, 15, 16, 39, 1
|
|
25
|
+
)
|
|
26
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 24:00:00") == datetime(
|
|
27
|
+
2024, 3, 16, 0, 0, 0
|
|
28
|
+
)
|
|
29
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 16:39:01 to 16Mar2024 16:39:01") == [
|
|
30
|
+
datetime(2024, 3, 15, 16, 39, 1),
|
|
31
|
+
datetime(2024, 3, 16, 16, 39, 1),
|
|
32
|
+
]
|
|
33
|
+
assert utils.convert_ras_hdf_value(b"18Mar2024 24:00:00 to 19Mar2024 24:00:00") == [
|
|
34
|
+
datetime(2024, 3, 19, 0, 0, 0),
|
|
35
|
+
datetime(2024, 3, 20, 0, 0, 0),
|
|
36
|
+
]
|
|
37
|
+
assert utils.convert_ras_hdf_value(b"01:23:45") == timedelta(
|
|
38
|
+
hours=1, minutes=23, seconds=45
|
|
39
|
+
)
|
|
40
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 2400") == datetime(
|
|
41
|
+
2024, 3, 16, 0, 0, 0
|
|
42
|
+
)
|
|
43
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 2315") == datetime(
|
|
44
|
+
2024, 3, 15, 23, 15, 0
|
|
45
|
+
)
|
|
46
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 2400") == datetime(
|
|
47
|
+
2024, 3, 16, 0, 0, 0
|
|
48
|
+
)
|
|
49
|
+
assert utils.convert_ras_hdf_value(b"03/15/2024 2400") == datetime(
|
|
50
|
+
2024, 3, 16, 0, 0, 0
|
|
51
|
+
)
|
|
52
|
+
assert utils.convert_ras_hdf_value(b"03-15-2024 2400") == datetime(
|
|
53
|
+
2024, 3, 16, 0, 0, 0
|
|
54
|
+
)
|
|
55
|
+
assert utils.convert_ras_hdf_value(b"2024/03/15 2400") == datetime(
|
|
56
|
+
2024, 3, 16, 0, 0, 0
|
|
57
|
+
)
|
|
58
|
+
assert utils.convert_ras_hdf_value(b"2024-03-15 2400") == datetime(
|
|
59
|
+
2024, 3, 16, 0, 0, 0
|
|
60
|
+
)
|
|
61
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 0000") == datetime(
|
|
62
|
+
2024, 3, 15, 0, 0, 0
|
|
63
|
+
)
|
|
64
|
+
assert utils.convert_ras_hdf_value(b"03/15/2024 0000") == datetime(
|
|
65
|
+
2024, 3, 15, 0, 0, 0
|
|
66
|
+
)
|
|
67
|
+
assert utils.convert_ras_hdf_value(b"03-15-2024 0000") == datetime(
|
|
68
|
+
2024, 3, 15, 0, 0, 0
|
|
69
|
+
)
|
|
70
|
+
assert utils.convert_ras_hdf_value(b"2024/03/15 0000") == datetime(
|
|
71
|
+
2024, 3, 15, 0, 0, 0
|
|
72
|
+
)
|
|
73
|
+
assert utils.convert_ras_hdf_value(b"2024-03-15 0000") == datetime(
|
|
74
|
+
2024, 3, 15, 0, 0, 0
|
|
75
|
+
)
|
|
76
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 23:59:59") == datetime(
|
|
77
|
+
2024, 3, 15, 23, 59, 59
|
|
78
|
+
)
|
|
79
|
+
assert utils.convert_ras_hdf_value(b"03/15/2024 23:59:59") == datetime(
|
|
80
|
+
2024, 3, 15, 23, 59, 59
|
|
81
|
+
)
|
|
82
|
+
assert utils.convert_ras_hdf_value(b"03-15-2024 23:59:59") == datetime(
|
|
83
|
+
2024, 3, 15, 23, 59, 59
|
|
84
|
+
)
|
|
85
|
+
assert utils.convert_ras_hdf_value(b"2024/03/15 23:59:59") == datetime(
|
|
86
|
+
2024, 3, 15, 23, 59, 59
|
|
87
|
+
)
|
|
88
|
+
assert utils.convert_ras_hdf_value(b"2024-03-15 23:59:59") == datetime(
|
|
89
|
+
2024, 3, 15, 23, 59, 59
|
|
90
|
+
)
|
|
91
|
+
assert utils.convert_ras_hdf_value(b"15Mar2024 1639 to 16Mar2024 1639") == [
|
|
92
|
+
datetime(2024, 3, 15, 16, 39, 0),
|
|
93
|
+
datetime(2024, 3, 16, 16, 39, 0),
|
|
94
|
+
]
|
|
95
|
+
assert utils.convert_ras_hdf_value(b"18Mar2024 2400 to 19Mar2024 2400") == [
|
|
96
|
+
datetime(2024, 3, 19, 0, 0, 0),
|
|
97
|
+
datetime(2024, 3, 20, 0, 0, 0),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_df_datetimes_to_str():
|
|
102
|
+
df = pd.DataFrame(
|
|
103
|
+
{
|
|
104
|
+
"datetime": [
|
|
105
|
+
datetime(2024, 3, 15, 16, 39, 1),
|
|
106
|
+
datetime(2024, 3, 16, 16, 39, 1),
|
|
107
|
+
],
|
|
108
|
+
"asdf": [
|
|
109
|
+
0.123,
|
|
110
|
+
0.456,
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
assert df["datetime"].dtype.name == "datetime64[ns]"
|
|
115
|
+
df = utils.df_datetimes_to_str(df)
|
|
116
|
+
assert df["datetime"].dtype.name == "object"
|
|
117
|
+
assert df["datetime"].tolist() == ["2024-03-15T16:39:01", "2024-03-16T16:39:01"]
|
|
118
|
+
assert df["asdf"].tolist() == [0.123, 0.456]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_parse_ras_datetime_ms():
|
|
122
|
+
assert utils.parse_ras_datetime_ms("15Mar2024 16:39:01.123") == datetime(
|
|
123
|
+
2024, 3, 15, 16, 39, 1, 123000
|
|
124
|
+
)
|
|
125
|
+
assert utils.parse_ras_datetime_ms("15Mar2024 24:00:00.000") == datetime(
|
|
126
|
+
2024, 3, 16, 0, 0, 0, 0
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_trim_line():
|
|
131
|
+
gdf = gpd.GeoDataFrame(
|
|
132
|
+
{
|
|
133
|
+
"id": [1, 2, 3],
|
|
134
|
+
"geometry": [
|
|
135
|
+
LineString([(0, 0), (5, 5), (10, 10)]),
|
|
136
|
+
LineString([(0, 0), (5, 5), (10, 10), (15, 15)]),
|
|
137
|
+
MultiLineString(
|
|
138
|
+
[
|
|
139
|
+
[(0, 0), (3, 3)],
|
|
140
|
+
[(3, 3), (6, 6), (9, 9)],
|
|
141
|
+
[(3, 3), (6, 6), (9, 9), (12, 12), (15, 15)],
|
|
142
|
+
]
|
|
143
|
+
),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
assert gdf.geometry.apply(utils.remove_line_ends).equals(
|
|
148
|
+
gpd.GeoSeries(
|
|
149
|
+
[
|
|
150
|
+
LineString([(0, 0), (5, 5), (10, 10)]),
|
|
151
|
+
LineString(
|
|
152
|
+
[
|
|
153
|
+
(5, 5),
|
|
154
|
+
(10, 10),
|
|
155
|
+
]
|
|
156
|
+
),
|
|
157
|
+
MultiLineString(
|
|
158
|
+
[
|
|
159
|
+
[(0, 0), (3, 3)],
|
|
160
|
+
[(3, 3), (6, 6), (9, 9)],
|
|
161
|
+
[(6, 6), (9, 9), (12, 12)],
|
|
162
|
+
]
|
|
163
|
+
),
|
|
164
|
+
]
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_reverse_line():
|
|
170
|
+
gdf = gpd.GeoDataFrame(
|
|
171
|
+
{
|
|
172
|
+
"id": [1, 2, 3],
|
|
173
|
+
"geometry": [
|
|
174
|
+
LineString([(0, 0), (5, 5), (10, 10)]),
|
|
175
|
+
LineString([(0, 0), (5, 5), (10, 10), (15, 15)]),
|
|
176
|
+
MultiLineString(
|
|
177
|
+
[
|
|
178
|
+
[(0, 0), (3, 3)],
|
|
179
|
+
[(3, 3), (6, 6), (9, 9)],
|
|
180
|
+
[(3, 3), (6, 6), (9, 9), (12, 12), (15, 15)],
|
|
181
|
+
]
|
|
182
|
+
),
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
assert gdf.geometry.apply(utils.reverse_line).equals(
|
|
187
|
+
gpd.GeoSeries(
|
|
188
|
+
[
|
|
189
|
+
LineString([(10, 10), (5, 5), (0, 0)]),
|
|
190
|
+
LineString([(15, 15), (10, 10), (5, 5), (0, 0)]),
|
|
191
|
+
MultiLineString(
|
|
192
|
+
[
|
|
193
|
+
[(3, 3), (0, 0)],
|
|
194
|
+
[(9, 9), (6, 6), (3, 3)],
|
|
195
|
+
[(15, 15), (12, 12), (9, 9), (6, 6), (3, 3)],
|
|
196
|
+
]
|
|
197
|
+
),
|
|
198
|
+
]
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_copy_lines_parallel():
|
|
204
|
+
gdf = gpd.GeoDataFrame(
|
|
205
|
+
{
|
|
206
|
+
"id": [1, 2, 3],
|
|
207
|
+
"geometry": [
|
|
208
|
+
LineString([(0, 0), (5, 5), (10, 10)]),
|
|
209
|
+
LineString([(20, 20), (30, 30), (40, 40), (50, 50)]),
|
|
210
|
+
MultiLineString(
|
|
211
|
+
[
|
|
212
|
+
[(100.0, 100.0), (103.0, 103.0)],
|
|
213
|
+
[(103.0, 103.0), (106.0, 106.0), (109.0, 109.0)],
|
|
214
|
+
[
|
|
215
|
+
(103.0, 103.0),
|
|
216
|
+
(106.0, 106.0),
|
|
217
|
+
(109.0, 109.0),
|
|
218
|
+
(112.0, 112.0),
|
|
219
|
+
(115.0, 115.0),
|
|
220
|
+
],
|
|
221
|
+
]
|
|
222
|
+
),
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
)
|
|
226
|
+
offsets = np.array([1, 2, 3])
|
|
227
|
+
copied = utils.copy_lines_parallel(gdf, offsets)
|
|
228
|
+
expected = gpd.read_file(TEST_JSON / "copy_lines_parallel.json").set_crs(
|
|
229
|
+
None, allow_override=True
|
|
230
|
+
)
|
|
231
|
+
_assert_geodataframes_close(copied, expected, tol=1e-3)
|
rashdf-0.8.3/tests/test_utils.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
from src.rashdf import utils
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pandas as pd
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from datetime import datetime, timedelta
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_convert_ras_hdf_value():
|
|
11
|
-
assert utils.convert_ras_hdf_value(b"True") is True
|
|
12
|
-
assert utils.convert_ras_hdf_value(b"False") is False
|
|
13
|
-
assert utils.convert_ras_hdf_value(np.float32(1.23)) == pytest.approx(1.23)
|
|
14
|
-
assert utils.convert_ras_hdf_value(np.int32(123)) == 123
|
|
15
|
-
assert utils.convert_ras_hdf_value(b"15Mar2024 16:39:01") == datetime(
|
|
16
|
-
2024, 3, 15, 16, 39, 1
|
|
17
|
-
)
|
|
18
|
-
assert utils.convert_ras_hdf_value(b"15Mar2024 24:00:00") == datetime(
|
|
19
|
-
2024, 3, 16, 0, 0, 0
|
|
20
|
-
)
|
|
21
|
-
assert utils.convert_ras_hdf_value(b"15Mar2024 16:39:01 to 16Mar2024 16:39:01") == [
|
|
22
|
-
datetime(2024, 3, 15, 16, 39, 1),
|
|
23
|
-
datetime(2024, 3, 16, 16, 39, 1),
|
|
24
|
-
]
|
|
25
|
-
assert utils.convert_ras_hdf_value(b"18Mar2024 24:00:00 to 19Mar2024 24:00:00") == [
|
|
26
|
-
datetime(2024, 3, 19, 0, 0, 0),
|
|
27
|
-
datetime(2024, 3, 20, 0, 0, 0),
|
|
28
|
-
]
|
|
29
|
-
assert utils.convert_ras_hdf_value(b"01:23:45") == timedelta(
|
|
30
|
-
hours=1, minutes=23, seconds=45
|
|
31
|
-
)
|
|
32
|
-
assert utils.convert_ras_hdf_value(b"15Mar2024 2400") == datetime(
|
|
33
|
-
2024, 3, 16, 0, 0, 0
|
|
34
|
-
)
|
|
35
|
-
assert utils.convert_ras_hdf_value(b"15Mar2024 2315") == datetime(
|
|
36
|
-
2024, 3, 15, 23, 15, 0
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
assert utils.convert_ras_hdf_value(b"15Mar2024 1639 to 16Mar2024 1639") == [
|
|
40
|
-
datetime(2024, 3, 15, 16, 39, 0),
|
|
41
|
-
datetime(2024, 3, 16, 16, 39, 0),
|
|
42
|
-
]
|
|
43
|
-
assert utils.convert_ras_hdf_value(b"18Mar2024 2400 to 19Mar2024 2400") == [
|
|
44
|
-
datetime(2024, 3, 19, 0, 0, 0),
|
|
45
|
-
datetime(2024, 3, 20, 0, 0, 0),
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_df_datetimes_to_str():
|
|
50
|
-
df = pd.DataFrame(
|
|
51
|
-
{
|
|
52
|
-
"datetime": [
|
|
53
|
-
datetime(2024, 3, 15, 16, 39, 1),
|
|
54
|
-
datetime(2024, 3, 16, 16, 39, 1),
|
|
55
|
-
],
|
|
56
|
-
"asdf": [
|
|
57
|
-
0.123,
|
|
58
|
-
0.456,
|
|
59
|
-
],
|
|
60
|
-
}
|
|
61
|
-
)
|
|
62
|
-
assert df["datetime"].dtype.name == "datetime64[ns]"
|
|
63
|
-
df = utils.df_datetimes_to_str(df)
|
|
64
|
-
assert df["datetime"].dtype.name == "object"
|
|
65
|
-
assert df["datetime"].tolist() == ["2024-03-15T16:39:01", "2024-03-16T16:39:01"]
|
|
66
|
-
assert df["asdf"].tolist() == [0.123, 0.456]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_parse_ras_datetime_ms():
|
|
70
|
-
assert utils.parse_ras_datetime_ms("15Mar2024 16:39:01.123") == datetime(
|
|
71
|
-
2024, 3, 15, 16, 39, 1, 123000
|
|
72
|
-
)
|
|
73
|
-
assert utils.parse_ras_datetime_ms("15Mar2024 24:00:00.000") == datetime(
|
|
74
|
-
2024, 3, 16, 0, 0, 0, 0
|
|
75
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|