|
10 | 10 | from datetime import datetime, timedelta
|
11 | 11 | from shutil import copyfile
|
12 | 12 | from collections.abc import Iterable, Sequence
|
13 |
| -from typing import Callable, Union |
| 13 | +from typing import Callable, Mapping, Union |
14 | 14 | import warnings
|
15 | 15 |
|
16 | 16 |
|
|
30 | 30 | from mikecore.eum import eumQuantity
|
31 | 31 | from tqdm import tqdm, trange
|
32 | 32 |
|
| 33 | + |
33 | 34 | from . import __dfs_version__
|
34 | 35 | from .dfs._dfs import _get_item_info, _valid_item_numbers
|
35 |
| -from .eum import ItemInfo |
| 36 | +from .eum import ItemInfo, EUMType, EUMUnit |
36 | 37 | import mikeio
|
37 | 38 |
|
38 | 39 |
|
@@ -94,7 +95,7 @@ def _clone(
|
94 | 95 | outfilename: str | pathlib.Path,
|
95 | 96 | start_time: datetime | None = None,
|
96 | 97 | timestep: float | None = None,
|
97 |
| - items: Sequence[int | DfsDynamicItemInfo] | None = None, |
| 98 | + items: Sequence[int | DfsDynamicItemInfo | ItemInfo] | None = None, |
98 | 99 | datatype: int | None = None,
|
99 | 100 | ) -> DfsFile:
|
100 | 101 | source = DfsFileFactory.DfsGenericOpen(str(infilename))
|
@@ -1011,3 +1012,127 @@ def change_datatype(
|
1011 | 1012 |
|
1012 | 1013 | dfs_out.Close()
|
1013 | 1014 | dfs_in.Close()
|
| 1015 | + |
| 1016 | + |
| 1017 | +class DerivedItem: |
| 1018 | + """Item derived from other items. |
| 1019 | +
|
| 1020 | + Parameters |
| 1021 | + ---------- |
| 1022 | + item: ItemInfo |
| 1023 | + ItemInfo object for the derived item |
| 1024 | + func: Callable[[Mapping[str, np.ndarray]], np.ndarray] | None |
| 1025 | + Function to compute the derived item from a mapping of item names to data arrays. |
| 1026 | + If None, the item data will be returned directly from the mapping using the item's name. |
| 1027 | + Default is None. |
| 1028 | +
|
| 1029 | + Example |
| 1030 | + ------- |
| 1031 | + ```{python} |
| 1032 | + import numpy as np |
| 1033 | + import mikeio |
| 1034 | + from mikeio.generic import DerivedItem |
| 1035 | +
|
| 1036 | + item = DerivedItem( |
| 1037 | + item=ItemInfo("Current Speed", mikeio.EUMType.Current_Speed), |
| 1038 | + func=lambda x: np.sqrt(x["U velocity"] ** 2 + x["V velocity"] ** 2), |
| 1039 | + ) |
| 1040 | +
|
| 1041 | + """ |
| 1042 | + |
| 1043 | + def __init__( |
| 1044 | + self, |
| 1045 | + name: str, |
| 1046 | + type: EUMType | None = None, |
| 1047 | + unit: EUMUnit | None = None, |
| 1048 | + func: Callable[[Mapping[str, np.ndarray]], np.ndarray] | None = None, |
| 1049 | + ) -> None: |
| 1050 | + """Create a DerivedItem. |
| 1051 | +
|
| 1052 | + Parameters |
| 1053 | + ---------- |
| 1054 | + name: str |
| 1055 | + Name of the derived item. |
| 1056 | + type: EUMType |
| 1057 | + EUMType of the derived item. |
| 1058 | + unit: EUMUnit | None, optional |
| 1059 | + EUMUnit of the derived item, pass None to use the default unit for the type. |
| 1060 | + Default is None. |
| 1061 | + func: Callable[[Mapping[str, np.ndarray]], np.ndarray] | None, optional |
| 1062 | + Function to compute the derived item from a mapping of item names to data arrays. |
| 1063 | +
|
| 1064 | + """ |
| 1065 | + self.item = ItemInfo(name, type, unit) |
| 1066 | + self.func = func |
| 1067 | + |
| 1068 | + |
| 1069 | +def transform( |
| 1070 | + infilename: str | pathlib.Path, |
| 1071 | + outfilename: str | pathlib.Path, |
| 1072 | + vars: Sequence[DerivedItem], |
| 1073 | + keep_existing_items: bool = True, |
| 1074 | +) -> None: |
| 1075 | + """Transform a dfs file by applying functions to items. |
| 1076 | +
|
| 1077 | + Parameters |
| 1078 | + ---------- |
| 1079 | + infilename: str | pathlib.Path |
| 1080 | + full path to the input file |
| 1081 | + outfilename: str | pathlib.Path |
| 1082 | + full path to the output file |
| 1083 | + vars: Sequence[DerivedItem] |
| 1084 | + List of derived items to compute. |
| 1085 | + keep_existing_items: bool, optional |
| 1086 | + If True, existing items in the input file will be kept in the output file. |
| 1087 | + If False, only the derived items will be written to the output file. |
| 1088 | + Default is True. |
| 1089 | +
|
| 1090 | + """ |
| 1091 | + dfs_i = DfsFileFactory.DfsGenericOpen(str(infilename)) |
| 1092 | + |
| 1093 | + item_numbers = _valid_item_numbers(dfs_i.ItemInfo) |
| 1094 | + n_items = len(item_numbers) |
| 1095 | + |
| 1096 | + items = [v.item for v in vars] |
| 1097 | + funcs = {v.item.name: v.func for v in vars} |
| 1098 | + |
| 1099 | + if keep_existing_items: |
| 1100 | + existing_items = [ |
| 1101 | + ItemInfo.from_mikecore_dynamic_item_info( |
| 1102 | + dfs_i.ItemInfo[i], |
| 1103 | + ) |
| 1104 | + for i in item_numbers |
| 1105 | + ] |
| 1106 | + items = existing_items + items |
| 1107 | + |
| 1108 | + dfs = _clone( |
| 1109 | + str(infilename), |
| 1110 | + str(outfilename), |
| 1111 | + items=items, |
| 1112 | + ) |
| 1113 | + |
| 1114 | + n_time_steps = dfs_i.FileInfo.TimeAxis.NumberOfTimeSteps |
| 1115 | + |
| 1116 | + for timestep in range(n_time_steps): |
| 1117 | + data = {} |
| 1118 | + for item in range(n_items): |
| 1119 | + name = dfs_i.ItemInfo[item].Name |
| 1120 | + data[name] = dfs_i.ReadItemTimeStep(item_numbers[item] + 1, timestep).Data |
| 1121 | + |
| 1122 | + for item in items: |
| 1123 | + func = funcs.get(item.name, None) |
| 1124 | + if func is None: |
| 1125 | + darray = data[item.name] |
| 1126 | + else: |
| 1127 | + try: |
| 1128 | + darray = func(data) |
| 1129 | + except KeyError as e: |
| 1130 | + missing_key = e.args[0] |
| 1131 | + keys = ", ".join(data.keys()) |
| 1132 | + raise KeyError( |
| 1133 | + f"Item '{missing_key}' is not available in the file. Available items: {keys}" |
| 1134 | + ) |
| 1135 | + dfs.WriteItemTimeStepNext(0.0, darray) |
| 1136 | + |
| 1137 | + dfs_i.Close() |
| 1138 | + dfs.Close() |
0 commit comments