Skip to content

Commit d905579

Browse files
authored
Merge pull request #679 from DHI/slim-dfsu
Dfsu 2.0
2 parents 6cef6af + eb08fb9 commit d905579

14 files changed

+537
-443
lines changed

mikeio/dfsu/_dfsu.py

Lines changed: 171 additions & 216 deletions
Large diffs are not rendered by default.

mikeio/dfsu/_layered.py

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import numpy as np
66
from mikecore.DfsuFile import DfsuFile, DfsuFileType
7+
import pandas as pd
78
from scipy.spatial import cKDTree
89
from tqdm import trange
910

@@ -18,18 +19,102 @@
1819
from .._interpolation import get_idw_interpolant, interp2d
1920
from ..spatial import GeometryFM3D, GeometryFMVerticalProfile, GeometryPoint3D
2021
from ..spatial._FM_utils import _plot_vertical_profile
21-
from ._dfsu import _Dfsu, get_nodes_from_source, get_elements_from_source
22+
from ._dfsu import (
23+
_get_dfsu_info,
24+
get_nodes_from_source,
25+
get_elements_from_source,
26+
_validate_elements_and_geometry_sel,
27+
)
2228

2329
if TYPE_CHECKING:
2430
from ..spatial._FM_geometry_layered import Layer
2531

2632

27-
class DfsuLayered(_Dfsu):
33+
class DfsuLayered:
34+
show_progress = False
35+
2836
def __init__(self, filename: str | Path) -> None:
29-
super().__init__(filename)
37+
info = _get_dfsu_info(filename)
38+
self._filename = info.filename
39+
self._type = info.type
40+
self._deletevalue = info.deletevalue
41+
self._time = info.time
42+
self._timestep = info.timestep
3043
self._geometry = self._read_geometry(self._filename)
44+
# 3d files have a zn item
3145
self._items = self._read_items(self._filename)
3246

47+
def __repr__(self):
48+
out = [f"<mikeio.{self.__class__.__name__}>"]
49+
50+
out.append(f"number of elements: {self.geometry.n_elements}")
51+
out.append(f"number of nodes: {self.geometry.n_nodes}")
52+
out.append(f"projection: {self.geometry.projection_string}")
53+
out.append(f"number of sigma layers: {self.geometry.n_sigma_layers}")
54+
if (
55+
self._type == DfsuFileType.DfsuVerticalProfileSigmaZ
56+
or self._type == DfsuFileType.Dfsu3DSigmaZ
57+
):
58+
out.append(
59+
f"max number of z layers: {self.geometry.n_layers - self.geometry.n_sigma_layers}"
60+
)
61+
if self.n_items < 10:
62+
out.append("items:")
63+
for i, item in enumerate(self.items):
64+
out.append(f" {i}: {item}")
65+
else:
66+
out.append(f"number of items: {self.geometry.n_items}")
67+
if self.n_timesteps == 1:
68+
out.append(f"time: time-invariant file (1 step) at {self.time[0]}")
69+
else:
70+
out.append(
71+
f"time: {str(self.time[0])} - {str(self.time[-1])} ({self.n_timesteps} records)"
72+
)
73+
return str.join("\n", out)
74+
75+
@property
76+
def deletevalue(self) -> float:
77+
"""File delete value"""
78+
return self._deletevalue
79+
80+
@property
81+
def n_items(self) -> int:
82+
"""Number of items"""
83+
return len(self.items)
84+
85+
@property
86+
def items(self) -> list[ItemInfo]:
87+
"""List of items"""
88+
return self._items
89+
90+
@property
91+
def start_time(self) -> pd.Timestamp:
92+
"""File start time"""
93+
return self._time[0]
94+
95+
@property
96+
def n_timesteps(self) -> int:
97+
"""Number of time steps"""
98+
return len(self._time)
99+
100+
@property
101+
def timestep(self) -> float:
102+
"""Time step size in seconds"""
103+
return self._timestep
104+
105+
@property
106+
def end_time(self) -> pd.Timestamp:
107+
"""File end time"""
108+
return self._time[-1]
109+
110+
@property
111+
def time(self) -> pd.DatetimeIndex:
112+
return self._time
113+
114+
@property
115+
def geometry(self) -> GeometryFM3D | GeometryFMVerticalProfile:
116+
return self._geometry
117+
33118
@staticmethod
34119
def _read_items(filename: str) -> list[ItemInfo]:
35120
dfs = DfsuFile.Open(filename)
@@ -148,7 +233,7 @@ def read(
148233

149234
single_time_selected, time_steps = _valid_timesteps(dfs, time)
150235

151-
self._validate_elements_and_geometry_sel(
236+
_validate_elements_and_geometry_sel(
152237
elements, area=area, layers=layers, x=x, y=y, z=z
153238
)
154239
if elements is None:
@@ -158,7 +243,7 @@ def read(
158243
or (area is not None)
159244
or (layers is not None)
160245
):
161-
elements = self.geometry.find_index(
246+
elements = self.geometry.find_index( # type: ignore
162247
x=x, y=y, z=z, area=area, layers=layers
163248
)
164249
if len(elements) == 0:
@@ -179,9 +264,11 @@ def read(
179264
node_ids = geometry.node_ids
180265

181266
item_numbers = _valid_item_numbers(
182-
dfs.ItemInfo, items, ignore_first=self.is_layered
267+
dfs.ItemInfo, items, ignore_first=self.geometry.is_layered
268+
)
269+
items = _get_item_info(
270+
dfs.ItemInfo, item_numbers, ignore_first=self.geometry.is_layered
183271
)
184-
items = _get_item_info(dfs.ItemInfo, item_numbers, ignore_first=self.is_layered)
185272
item_numbers = [it + 1 for it in item_numbers]
186273
if layered_data := hasattr(geometry, "is_layered") and geometry.is_layered:
187274
item_numbers.insert(0, 0)
@@ -341,8 +428,8 @@ def extract_surface_elevation_from_3d(self, n_nearest: int = 4) -> DataArray:
341428
# make 2d nodes-to-elements interpolator
342429
top_el = self.geometry.top_elements
343430
geom = self.geometry.elements_to_geometry(top_el, node_layers="top")
344-
xye = geom.element_coordinates[:, 0:2]
345-
xyn = geom.node_coordinates[:, 0:2]
431+
xye = geom.element_coordinates[:, 0:2] # type: ignore
432+
xyn = geom.node_coordinates[:, 0:2] # type: ignore
346433
tree2d = cKDTree(xyn)
347434
dist, node_ids = tree2d.query(xye, k=n_nearest)
348435
if n_nearest == 1:

mikeio/dfsu/_spectral.py

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,109 @@
88
from tqdm import trange
99

1010
from ..dataset import DataArray, Dataset
11+
from ..eum import ItemInfo
1112
from ..dfs._dfs import _get_item_info, _valid_item_numbers, _valid_timesteps
1213
from .._spectral import calc_m0_from_spectrum
13-
from ._dfsu import _Dfsu, get_elements_from_source, get_nodes_from_source
14+
from ._dfsu import (
15+
_get_dfsu_info,
16+
get_elements_from_source,
17+
get_nodes_from_source,
18+
_validate_elements_and_geometry_sel,
19+
)
1420
from ..spatial import (
1521
GeometryFMAreaSpectrum,
1622
GeometryFMLineSpectrum,
1723
GeometryFMPointSpectrum,
1824
)
1925

2026

21-
class DfsuSpectral(_Dfsu):
27+
class DfsuSpectral:
28+
show_progress = False
2229

2330
def __init__(self, filename: str | Path) -> None:
24-
super().__init__(filename)
31+
info = _get_dfsu_info(filename)
32+
self._filename = info.filename
33+
self._type = info.type
34+
self._deletevalue = info.deletevalue
35+
self._time = info.time
36+
self._timestep = info.timestep
37+
self._items = info.items
2538
self._geometry = self._read_geometry(self._filename)
2639

40+
def __repr__(self):
41+
out = [f"<mikeio.{self.__class__.__name__}>"]
42+
43+
if self._type is not DfsuFileType.DfsuSpectral0D:
44+
if self._type is not DfsuFileType.DfsuSpectral1D:
45+
out.append(f"number of elements: {self.geometry.n_elements}")
46+
out.append(f"number of nodes: {self.geometry.n_nodes}")
47+
if self.geometry.is_spectral:
48+
if self.geometry.n_directions > 0:
49+
out.append(f"number of directions: {self.geometry.n_directions}")
50+
if self.geometry.n_frequencies > 0:
51+
out.append(f"number of frequencies: {self.geometry.n_frequencies}")
52+
if self.geometry.projection_string:
53+
out.append(f"projection: {self.geometry.projection_string}")
54+
if self.n_items < 10:
55+
out.append("items:")
56+
for i, item in enumerate(self.items):
57+
out.append(f" {i}: {item}")
58+
else:
59+
out.append(f"number of items: {self.geometry.n_items}")
60+
if self.n_timesteps == 1:
61+
out.append(f"time: time-invariant file (1 step) at {self.time[0]}")
62+
else:
63+
out.append(
64+
f"time: {str(self.time[0])} - {str(self.time[-1])} ({self.n_timesteps} records)"
65+
)
66+
return str.join("\n", out)
67+
68+
@property
69+
def geometry(
70+
self,
71+
) -> GeometryFMPointSpectrum | GeometryFMLineSpectrum | GeometryFMAreaSpectrum:
72+
"""Geometry"""
73+
return self._geometry
74+
75+
@property
76+
def deletevalue(self) -> float:
77+
"""File delete value"""
78+
return self._deletevalue
79+
80+
@property
81+
def n_items(self) -> int:
82+
"""Number of items"""
83+
return len(self.items)
84+
85+
@property
86+
def items(self) -> list[ItemInfo]:
87+
"""List of items"""
88+
return self._items
89+
90+
@property
91+
def start_time(self) -> pd.Timestamp:
92+
"""File start time"""
93+
return self._time[0]
94+
95+
@property
96+
def n_timesteps(self) -> int:
97+
"""Number of time steps"""
98+
return len(self._time)
99+
100+
@property
101+
def timestep(self) -> float:
102+
"""Time step size in seconds"""
103+
return self._timestep
104+
105+
@property
106+
def end_time(self) -> pd.Timestamp:
107+
"""File end time"""
108+
return self._time[-1]
109+
110+
@property
111+
def time(self) -> pd.DatetimeIndex:
112+
return self._time
113+
27114
@staticmethod
28115
def _read_geometry(
29116
filename: str,
@@ -98,8 +185,8 @@ def _get_spectral_data_shape(
98185
self, n_steps: int, elements: Sized | None, dfsu_type: DfsuFileType
99186
) -> Tuple[Tuple[int, ...], Tuple[int, ...], Tuple[str, ...]]:
100187
dims = [] if n_steps == 1 else ["time"]
101-
n_freq = self.n_frequencies
102-
n_dir = self.n_directions
188+
n_freq = self.geometry.n_frequencies
189+
n_dir = self.geometry.n_directions
103190
shape: Tuple[int, ...] = (n_dir, n_freq)
104191
if n_dir == 0:
105192
shape = (n_freq,)
@@ -109,21 +196,21 @@ def _get_spectral_data_shape(
109196
read_shape = (n_steps, *shape)
110197
elif dfsu_type == DfsuFileType.DfsuSpectral1D:
111198
# node-based, FE-style
112-
n_nodes = self.n_nodes if elements is None else len(elements)
199+
n_nodes = self.geometry.n_nodes if elements is None else len(elements)
113200
if n_nodes == 1:
114201
read_shape = (n_steps, *shape)
115202
else:
116203
dims.append("node")
117204
read_shape = (n_steps, n_nodes, *shape)
118-
shape = (*shape, self.n_nodes)
205+
shape = (*shape, self.geometry.n_nodes)
119206
else:
120-
n_elems = self.n_elements if elements is None else len(elements)
207+
n_elems = self.geometry.n_elements if elements is None else len(elements)
121208
if n_elems == 1:
122209
read_shape = (n_steps, *shape)
123210
else:
124211
dims.append("element")
125212
read_shape = (n_steps, n_elems, *shape)
126-
shape = (*shape, self.n_elements)
213+
shape = (*shape, self.geometry.n_elements)
127214

128215
if n_dir > 1:
129216
dims.append("direction")
@@ -202,7 +289,7 @@ def read(
202289
single_time_selected, time_steps = _valid_timesteps(dfs, time)
203290

204291
if self._type == DfsuFileType.DfsuSpectral2D:
205-
self._validate_elements_and_geometry_sel(elements, area=area, x=x, y=y)
292+
_validate_elements_and_geometry_sel(elements, area=area, x=x, y=y)
206293
if elements is None:
207294
elements = self._parse_geometry_sel(area=area, x=x, y=y)
208295
else:
@@ -214,10 +301,8 @@ def read(
214301

215302
geometry, pts = self._parse_elements_nodes(elements, nodes)
216303

217-
item_numbers = _valid_item_numbers(
218-
dfs.ItemInfo, items, ignore_first=self.is_layered
219-
)
220-
items = _get_item_info(dfs.ItemInfo, item_numbers, ignore_first=self.is_layered)
304+
item_numbers = _valid_item_numbers(dfs.ItemInfo, items)
305+
items = _get_item_info(dfs.ItemInfo, item_numbers)
221306
n_items = len(item_numbers)
222307

223308
deletevalue = self.deletevalue

mikeio/spatial/_FM_geometry.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ def codes(self, v: np.ndarray) -> None:
381381
raise ValueError(f"codes must have length of nodes ({self.n_nodes})")
382382
self._codes = np.array(v, dtype=np.int32)
383383

384+
@property
385+
def boundary_codes(self):
386+
"""Unique list of boundary codes"""
387+
valid = list(set(self.codes))
388+
return [code for code in valid if code > 0]
389+
384390

385391
class GeometryFM2D(_GeometryFM):
386392
def __init__(

0 commit comments

Comments
 (0)