flood-adapt 0.3.9__py3-none-any.whl → 0.3.11__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.
- flood_adapt/__init__.py +26 -22
- flood_adapt/adapter/__init__.py +9 -9
- flood_adapt/adapter/fiat_adapter.py +1541 -1541
- flood_adapt/adapter/interface/hazard_adapter.py +70 -70
- flood_adapt/adapter/interface/impact_adapter.py +36 -36
- flood_adapt/adapter/interface/model_adapter.py +89 -89
- flood_adapt/adapter/interface/offshore.py +19 -19
- flood_adapt/adapter/sfincs_adapter.py +1853 -1848
- flood_adapt/adapter/sfincs_offshore.py +187 -193
- flood_adapt/config/config.py +248 -248
- flood_adapt/config/fiat.py +219 -219
- flood_adapt/config/gui.py +331 -331
- flood_adapt/config/sfincs.py +481 -336
- flood_adapt/config/site.py +129 -129
- flood_adapt/database_builder/database_builder.py +2210 -2210
- flood_adapt/database_builder/templates/default_units/imperial.toml +9 -9
- flood_adapt/database_builder/templates/default_units/metric.toml +9 -9
- flood_adapt/database_builder/templates/green_infra_table/green_infra_lookup_table.csv +10 -10
- flood_adapt/database_builder/templates/infographics/OSM/config_charts.toml +90 -90
- flood_adapt/database_builder/templates/infographics/OSM/config_people.toml +57 -57
- flood_adapt/database_builder/templates/infographics/OSM/config_risk_charts.toml +121 -121
- flood_adapt/database_builder/templates/infographics/OSM/config_roads.toml +65 -65
- flood_adapt/database_builder/templates/infographics/OSM/styles.css +45 -45
- flood_adapt/database_builder/templates/infographics/US_NSI/config_charts.toml +126 -126
- flood_adapt/database_builder/templates/infographics/US_NSI/config_people.toml +60 -60
- flood_adapt/database_builder/templates/infographics/US_NSI/config_risk_charts.toml +121 -121
- flood_adapt/database_builder/templates/infographics/US_NSI/config_roads.toml +65 -65
- flood_adapt/database_builder/templates/infographics/US_NSI/styles.css +45 -45
- flood_adapt/database_builder/templates/infometrics/OSM/metrics_additional_risk_configs.toml +4 -4
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config.toml +143 -143
- flood_adapt/database_builder/templates/infometrics/OSM/with_SVI/infographic_metrics_config_risk.toml +153 -153
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config.toml +127 -127
- flood_adapt/database_builder/templates/infometrics/OSM/without_SVI/infographic_metrics_config_risk.toml +57 -57
- flood_adapt/database_builder/templates/infometrics/US_NSI/metrics_additional_risk_configs.toml +4 -4
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config.toml +191 -191
- flood_adapt/database_builder/templates/infometrics/US_NSI/with_SVI/infographic_metrics_config_risk.toml +153 -153
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config.toml +178 -178
- flood_adapt/database_builder/templates/infometrics/US_NSI/without_SVI/infographic_metrics_config_risk.toml +57 -57
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config.toml +9 -9
- flood_adapt/database_builder/templates/infometrics/mandatory_metrics_config_risk.toml +65 -65
- flood_adapt/database_builder/templates/output_layers/bin_colors.toml +5 -5
- flood_adapt/database_builder.py +16 -16
- flood_adapt/dbs_classes/__init__.py +21 -21
- flood_adapt/dbs_classes/database.py +533 -684
- flood_adapt/dbs_classes/dbs_benefit.py +77 -76
- flood_adapt/dbs_classes/dbs_event.py +61 -59
- flood_adapt/dbs_classes/dbs_measure.py +112 -111
- flood_adapt/dbs_classes/dbs_projection.py +34 -34
- flood_adapt/dbs_classes/dbs_scenario.py +137 -137
- flood_adapt/dbs_classes/dbs_static.py +274 -273
- flood_adapt/dbs_classes/dbs_strategy.py +130 -129
- flood_adapt/dbs_classes/dbs_template.py +279 -278
- flood_adapt/dbs_classes/interface/database.py +107 -139
- flood_adapt/dbs_classes/interface/element.py +121 -121
- flood_adapt/dbs_classes/interface/static.py +47 -47
- flood_adapt/flood_adapt.py +1229 -1178
- flood_adapt/misc/database_user.py +16 -16
- flood_adapt/misc/exceptions.py +22 -0
- flood_adapt/misc/log.py +183 -183
- flood_adapt/misc/path_builder.py +54 -54
- flood_adapt/misc/utils.py +185 -185
- flood_adapt/objects/__init__.py +82 -82
- flood_adapt/objects/benefits/benefits.py +61 -61
- flood_adapt/objects/events/event_factory.py +135 -135
- flood_adapt/objects/events/event_set.py +88 -84
- flood_adapt/objects/events/events.py +236 -234
- flood_adapt/objects/events/historical.py +58 -58
- flood_adapt/objects/events/hurricane.py +68 -67
- flood_adapt/objects/events/synthetic.py +46 -50
- flood_adapt/objects/forcing/__init__.py +92 -92
- flood_adapt/objects/forcing/csv.py +68 -68
- flood_adapt/objects/forcing/discharge.py +66 -66
- flood_adapt/objects/forcing/forcing.py +150 -150
- flood_adapt/objects/forcing/forcing_factory.py +182 -182
- flood_adapt/objects/forcing/meteo_handler.py +93 -93
- flood_adapt/objects/forcing/netcdf.py +40 -40
- flood_adapt/objects/forcing/plotting.py +453 -429
- flood_adapt/objects/forcing/rainfall.py +98 -98
- flood_adapt/objects/forcing/tide_gauge.py +191 -191
- flood_adapt/objects/forcing/time_frame.py +90 -90
- flood_adapt/objects/forcing/timeseries.py +564 -564
- flood_adapt/objects/forcing/unit_system.py +580 -580
- flood_adapt/objects/forcing/waterlevels.py +108 -108
- flood_adapt/objects/forcing/wind.py +124 -124
- flood_adapt/objects/measures/measure_factory.py +92 -92
- flood_adapt/objects/measures/measures.py +551 -529
- flood_adapt/objects/object_model.py +74 -68
- flood_adapt/objects/projections/projections.py +103 -103
- flood_adapt/objects/scenarios/scenarios.py +22 -22
- flood_adapt/objects/strategies/strategies.py +89 -89
- flood_adapt/workflows/benefit_runner.py +579 -554
- flood_adapt/workflows/floodmap.py +85 -85
- flood_adapt/workflows/impacts_integrator.py +85 -85
- flood_adapt/workflows/scenario_runner.py +70 -70
- {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/LICENSE +674 -674
- {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/METADATA +867 -865
- flood_adapt-0.3.11.dist-info/RECORD +140 -0
- flood_adapt-0.3.9.dist-info/RECORD +0 -139
- {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/WHEEL +0 -0
- {flood_adapt-0.3.9.dist-info → flood_adapt-0.3.11.dist-info}/top_level.txt +0 -0
|
@@ -1,580 +1,580 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import math
|
|
3
|
-
from abc import ABC
|
|
4
|
-
from datetime import timedelta
|
|
5
|
-
from enum import Enum
|
|
6
|
-
from typing import Generic, Optional, Type, TypeVar, Union
|
|
7
|
-
|
|
8
|
-
from pydantic import BaseModel, Field, model_validator
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"ValueUnitPair",
|
|
12
|
-
"VerticalReference",
|
|
13
|
-
"UnitTypesLength",
|
|
14
|
-
"UnitTypesArea",
|
|
15
|
-
"UnitTypesVolume",
|
|
16
|
-
"UnitTypesVelocity",
|
|
17
|
-
"UnitTypesDirection",
|
|
18
|
-
"UnitTypesTime",
|
|
19
|
-
"UnitTypesDischarge",
|
|
20
|
-
"UnitTypesIntensity",
|
|
21
|
-
"UnitfulLength",
|
|
22
|
-
"UnitfulHeight",
|
|
23
|
-
"UnitfulLengthRefValue",
|
|
24
|
-
"UnitfulArea",
|
|
25
|
-
"UnitfulVelocity",
|
|
26
|
-
"UnitfulDirection",
|
|
27
|
-
"UnitfulDischarge",
|
|
28
|
-
"UnitfulIntensity",
|
|
29
|
-
"UnitfulVolume",
|
|
30
|
-
"UnitfulTime",
|
|
31
|
-
"ValueUnitPairs",
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
TUnit = TypeVar("TUnit", bound=enum.Enum)
|
|
35
|
-
TClass = TypeVar("TClass", bound="ValueUnitPair")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ValueUnitPair(ABC, BaseModel, Generic[TUnit]):
|
|
39
|
-
"""
|
|
40
|
-
Represents a value with associated units.
|
|
41
|
-
|
|
42
|
-
Attributes
|
|
43
|
-
----------
|
|
44
|
-
value: float
|
|
45
|
-
The numerical value.
|
|
46
|
-
units : Unit
|
|
47
|
-
The units of the value.
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
_DEFAULT_UNIT: TUnit
|
|
51
|
-
_CONVERSION_FACTORS: dict[TUnit, float]
|
|
52
|
-
|
|
53
|
-
value: float
|
|
54
|
-
units: TUnit
|
|
55
|
-
|
|
56
|
-
def convert(self, new_units: TUnit) -> float:
|
|
57
|
-
"""Return the value converted to the new units.
|
|
58
|
-
|
|
59
|
-
Parameters
|
|
60
|
-
----------
|
|
61
|
-
new_units : Unit
|
|
62
|
-
The new units.
|
|
63
|
-
|
|
64
|
-
Returns
|
|
65
|
-
-------
|
|
66
|
-
converted_value : float
|
|
67
|
-
The converted value.
|
|
68
|
-
"""
|
|
69
|
-
if new_units not in self._CONVERSION_FACTORS:
|
|
70
|
-
raise ValueError(f"Invalid units: {new_units}")
|
|
71
|
-
in_default_units = self.value / self._CONVERSION_FACTORS[self.units]
|
|
72
|
-
return in_default_units * self._CONVERSION_FACTORS[new_units]
|
|
73
|
-
|
|
74
|
-
def transform(self: TClass, new_units: TUnit) -> TClass:
|
|
75
|
-
"""Return a new ValueUnitPair instance with the value converted to the new units.
|
|
76
|
-
|
|
77
|
-
Parameters
|
|
78
|
-
----------
|
|
79
|
-
new_units : Unit
|
|
80
|
-
The new units.
|
|
81
|
-
|
|
82
|
-
Returns
|
|
83
|
-
-------
|
|
84
|
-
value_unit_pair : ValueUnitPair
|
|
85
|
-
The new ValueUnitPair instance with the value converted to the new units and the new units.
|
|
86
|
-
"""
|
|
87
|
-
return type(self)(value=self.convert(new_units), units=new_units)
|
|
88
|
-
|
|
89
|
-
@model_validator(mode="before")
|
|
90
|
-
@classmethod
|
|
91
|
-
def extract_unit_class(cls, data: Optional[dict]) -> "ValueUnitPair":
|
|
92
|
-
if cls is not ValueUnitPair:
|
|
93
|
-
return data # Already in the right subclass, don't interfere
|
|
94
|
-
|
|
95
|
-
if not isinstance(data, dict):
|
|
96
|
-
raise TypeError("Expected dictionary for deserialization.")
|
|
97
|
-
|
|
98
|
-
str_unit = data.get("units")
|
|
99
|
-
if not str_unit:
|
|
100
|
-
raise ValueError("Missing 'units' field in input data.")
|
|
101
|
-
|
|
102
|
-
for unit_enum_cls, vu_cls in UNIT_TO_CLASS.items():
|
|
103
|
-
try:
|
|
104
|
-
enum_unit = unit_enum_cls(str_unit)
|
|
105
|
-
data["units"] = enum_unit
|
|
106
|
-
return vu_cls(**data)
|
|
107
|
-
except ValueError:
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
raise ValueError(f"Unsupported or unknown unit: {str_unit}")
|
|
111
|
-
|
|
112
|
-
def __str__(self) -> str:
|
|
113
|
-
return f"{self.value} {self.units.value}"
|
|
114
|
-
|
|
115
|
-
def __repr__(self) -> str:
|
|
116
|
-
return f"{type(self).__name__}(value={self.value}, units={self.units})"
|
|
117
|
-
|
|
118
|
-
def __sub__(self: TClass, other: TClass) -> TClass:
|
|
119
|
-
if not isinstance(other, type(self)):
|
|
120
|
-
raise TypeError(
|
|
121
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
122
|
-
)
|
|
123
|
-
return type(self)(
|
|
124
|
-
value=self.value - other.convert(self.units), units=self.units
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
def __add__(self: TClass, other: TClass) -> TClass:
|
|
128
|
-
if not isinstance(other, type(self)):
|
|
129
|
-
raise TypeError(
|
|
130
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
131
|
-
)
|
|
132
|
-
return type(self)(
|
|
133
|
-
value=self.value + other.convert(self.units), units=self.units
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
def __eq__(self: TClass, other: TClass) -> bool:
|
|
137
|
-
if not isinstance(other, ValueUnitPair):
|
|
138
|
-
raise TypeError(
|
|
139
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
140
|
-
)
|
|
141
|
-
if not (hasattr(other, "value") and hasattr(other, "units")):
|
|
142
|
-
raise AttributeError(f"Incomplete UnitfulValue instance: {other}")
|
|
143
|
-
|
|
144
|
-
if not isinstance(other.units, type(self.units)):
|
|
145
|
-
raise TypeError(
|
|
146
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
return math.isclose(
|
|
150
|
-
self.value, other.convert(self.units), rel_tol=1e-2
|
|
151
|
-
) # 1% relative tolerance for equality. So 1.0 == 1.01 evaluates to True
|
|
152
|
-
|
|
153
|
-
def __lt__(self: TClass, other: TClass) -> bool:
|
|
154
|
-
if not isinstance(other, type(self)):
|
|
155
|
-
raise TypeError(
|
|
156
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
157
|
-
)
|
|
158
|
-
return self.value < other.convert(self.units)
|
|
159
|
-
|
|
160
|
-
def __gt__(self: TClass, other: TClass) -> bool:
|
|
161
|
-
if not isinstance(other, type(self)):
|
|
162
|
-
raise TypeError(
|
|
163
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
164
|
-
)
|
|
165
|
-
return self.value > other.convert(self.units)
|
|
166
|
-
|
|
167
|
-
def __ge__(self: TClass, other: TClass) -> bool:
|
|
168
|
-
if not isinstance(other, type(self)):
|
|
169
|
-
raise TypeError(
|
|
170
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
171
|
-
)
|
|
172
|
-
return (self > other) or (self == other)
|
|
173
|
-
|
|
174
|
-
def __le__(self: TClass, other: TClass) -> bool:
|
|
175
|
-
if not isinstance(other, type(self)):
|
|
176
|
-
raise TypeError(
|
|
177
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
178
|
-
)
|
|
179
|
-
return (self < other) or (self == other)
|
|
180
|
-
|
|
181
|
-
def __ne__(self: TClass, other: TClass) -> bool:
|
|
182
|
-
if not isinstance(other, type(self)):
|
|
183
|
-
raise TypeError(
|
|
184
|
-
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
185
|
-
)
|
|
186
|
-
return not (self == other)
|
|
187
|
-
|
|
188
|
-
def __mul__(self: TClass, other: int | float) -> TClass:
|
|
189
|
-
if isinstance(other, int) or isinstance(other, float):
|
|
190
|
-
return type(self)(value=self.value * other, units=self.units)
|
|
191
|
-
else:
|
|
192
|
-
raise TypeError(
|
|
193
|
-
f"Cannot multiply self: {type(self).__name__} with other: {type(other).__name__}. Only int and float are allowed."
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
def __div__(self: TClass, other: TClass | int | float) -> TClass | float:
|
|
197
|
-
if isinstance(other, int) or isinstance(other, float):
|
|
198
|
-
return type(self)(value=self.value / other, units=self.units)
|
|
199
|
-
elif isinstance(other, type(self)):
|
|
200
|
-
return self.value / other.convert(self.units)
|
|
201
|
-
else:
|
|
202
|
-
raise TypeError(
|
|
203
|
-
f"Cannot divide self: {type(self).__name__} with other: {type(other).__name__}. Only {type(self).__name__}, int and float are allowed."
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
def __truediv__(self: TClass, other: TClass | int | float) -> TClass | float:
|
|
207
|
-
if isinstance(other, int) or isinstance(other, float):
|
|
208
|
-
return type(self)(value=self.value / other, units=self.units)
|
|
209
|
-
elif isinstance(other, type(self)):
|
|
210
|
-
return self.value / other.convert(self.units)
|
|
211
|
-
else:
|
|
212
|
-
raise TypeError(
|
|
213
|
-
f"Cannot divide self: {type(self).__name__} with other: {type(other).__name__}. Only {type(self).__name__}, int and float are allowed."
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
class UnitTypesLength(str, Enum):
|
|
218
|
-
"""Units of length.
|
|
219
|
-
|
|
220
|
-
Attributes
|
|
221
|
-
----------
|
|
222
|
-
meters : meters
|
|
223
|
-
centimeters : centimeters
|
|
224
|
-
millimeters : millimeters
|
|
225
|
-
feet : feet
|
|
226
|
-
inch : inch
|
|
227
|
-
miles : miles
|
|
228
|
-
"""
|
|
229
|
-
|
|
230
|
-
meters = "meters"
|
|
231
|
-
centimeters = "centimeters"
|
|
232
|
-
millimeters = "millimeters"
|
|
233
|
-
feet = "feet"
|
|
234
|
-
inch = "inch"
|
|
235
|
-
miles = "miles"
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
class UnitTypesArea(str, Enum):
|
|
239
|
-
"""Units of area.
|
|
240
|
-
|
|
241
|
-
Attributes
|
|
242
|
-
----------
|
|
243
|
-
m2 : square meters
|
|
244
|
-
dm2 : square decimeters
|
|
245
|
-
cm2 : square centimeters
|
|
246
|
-
mm2 : square millimeters
|
|
247
|
-
sf : square feet
|
|
248
|
-
"""
|
|
249
|
-
|
|
250
|
-
m2 = "m2"
|
|
251
|
-
dm2 = "dm2"
|
|
252
|
-
cm2 = "cm2"
|
|
253
|
-
mm2 = "mm2"
|
|
254
|
-
sf = "sf"
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
class UnitTypesVolume(str, Enum):
|
|
258
|
-
"""Units of volume.
|
|
259
|
-
|
|
260
|
-
Attributes
|
|
261
|
-
----------
|
|
262
|
-
m3 : cubic meters
|
|
263
|
-
cf : cubic feet
|
|
264
|
-
"""
|
|
265
|
-
|
|
266
|
-
m3 = "m3"
|
|
267
|
-
cf = "cf"
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
class UnitTypesVelocity(str, Enum):
|
|
271
|
-
"""Units of velocity.
|
|
272
|
-
|
|
273
|
-
Attributes
|
|
274
|
-
----------
|
|
275
|
-
mps : meters per second
|
|
276
|
-
knots : nautical miles per hour
|
|
277
|
-
mph : miles per hour
|
|
278
|
-
"""
|
|
279
|
-
|
|
280
|
-
mps = "m/s"
|
|
281
|
-
knots = "knots"
|
|
282
|
-
mph = "mph"
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
class UnitTypesDirection(str, Enum):
|
|
286
|
-
"""Units of direction.
|
|
287
|
-
|
|
288
|
-
Attributes
|
|
289
|
-
----------
|
|
290
|
-
degrees : degrees
|
|
291
|
-
"""
|
|
292
|
-
|
|
293
|
-
degrees = "deg N"
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
class UnitTypesTime(str, Enum):
|
|
297
|
-
"""Units of time.
|
|
298
|
-
|
|
299
|
-
Attributes
|
|
300
|
-
----------
|
|
301
|
-
seconds : seconds
|
|
302
|
-
minutes : minutes
|
|
303
|
-
hours : hours
|
|
304
|
-
days : days
|
|
305
|
-
"""
|
|
306
|
-
|
|
307
|
-
seconds = "seconds"
|
|
308
|
-
minutes = "minutes"
|
|
309
|
-
hours = "hours"
|
|
310
|
-
days = "days"
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
class UnitTypesDischarge(str, Enum):
|
|
314
|
-
"""Units of discharge.
|
|
315
|
-
|
|
316
|
-
Attributes
|
|
317
|
-
----------
|
|
318
|
-
cfs : cubic feet per second
|
|
319
|
-
cms : cubic meters per second
|
|
320
|
-
"""
|
|
321
|
-
|
|
322
|
-
cfs = "cfs"
|
|
323
|
-
cms = "m3/s"
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
class UnitTypesIntensity(str, Enum):
|
|
327
|
-
"""Units of intensity.
|
|
328
|
-
|
|
329
|
-
Attributes
|
|
330
|
-
----------
|
|
331
|
-
inch_hr : inch per hour
|
|
332
|
-
mm_hr : millimeter per hour
|
|
333
|
-
"""
|
|
334
|
-
|
|
335
|
-
inch_hr = "inch/hr"
|
|
336
|
-
mm_hr = "mm/hr"
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
class VerticalReference(str, Enum):
|
|
340
|
-
"""Vertical reference for height.
|
|
341
|
-
|
|
342
|
-
Attributes
|
|
343
|
-
----------
|
|
344
|
-
floodmap : Use the floodmap as reference.
|
|
345
|
-
datum : Use the datum as reference.
|
|
346
|
-
"""
|
|
347
|
-
|
|
348
|
-
floodmap = "floodmap"
|
|
349
|
-
datum = "datum"
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
class UnitfulLength(ValueUnitPair[UnitTypesLength]):
|
|
353
|
-
"""Combination of length and unit.
|
|
354
|
-
|
|
355
|
-
Attributes
|
|
356
|
-
----------
|
|
357
|
-
value : float
|
|
358
|
-
The length value.
|
|
359
|
-
units : UnitTypesLength
|
|
360
|
-
The unit of length.
|
|
361
|
-
"""
|
|
362
|
-
|
|
363
|
-
_CONVERSION_FACTORS: dict[UnitTypesLength, float] = {
|
|
364
|
-
UnitTypesLength.meters: 1.0,
|
|
365
|
-
UnitTypesLength.centimeters: 100.0,
|
|
366
|
-
UnitTypesLength.millimeters: 1000.0,
|
|
367
|
-
UnitTypesLength.feet: 3.28084,
|
|
368
|
-
UnitTypesLength.inch: 1.0 / 0.0254,
|
|
369
|
-
UnitTypesLength.miles: 1 / 1609.344,
|
|
370
|
-
}
|
|
371
|
-
_DEFAULT_UNIT: UnitTypesLength = UnitTypesLength.meters
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
class UnitfulHeight(UnitfulLength):
|
|
375
|
-
"""Combination of height and unit.
|
|
376
|
-
|
|
377
|
-
Attributes
|
|
378
|
-
----------
|
|
379
|
-
value : float
|
|
380
|
-
The height value, must be greater than or equal to 0.
|
|
381
|
-
units : UnitTypesLength
|
|
382
|
-
The unit of height.
|
|
383
|
-
"""
|
|
384
|
-
|
|
385
|
-
value: float = Field(ge=0.0)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
class UnitfulLengthRefValue(UnitfulLength):
|
|
389
|
-
"""Combination of length and unit with a reference value.
|
|
390
|
-
|
|
391
|
-
Attributes
|
|
392
|
-
----------
|
|
393
|
-
value : float
|
|
394
|
-
The length value, must be greater than or equal to 0.
|
|
395
|
-
units : UnitTypesLength
|
|
396
|
-
The unit of length.
|
|
397
|
-
type : VerticalReference
|
|
398
|
-
The vertical reference for the length.
|
|
399
|
-
"""
|
|
400
|
-
|
|
401
|
-
type: VerticalReference
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
class UnitfulArea(ValueUnitPair[UnitTypesArea]):
|
|
405
|
-
"""Combination of area and unit.
|
|
406
|
-
|
|
407
|
-
Attributes
|
|
408
|
-
----------
|
|
409
|
-
value : float
|
|
410
|
-
The area value, must be greater than or equal to 0.
|
|
411
|
-
units : UnitTypesArea
|
|
412
|
-
The unit of area.
|
|
413
|
-
|
|
414
|
-
"""
|
|
415
|
-
|
|
416
|
-
_CONVERSION_FACTORS: dict[UnitTypesArea, float] = {
|
|
417
|
-
UnitTypesArea.m2: 1,
|
|
418
|
-
UnitTypesArea.dm2: 100,
|
|
419
|
-
UnitTypesArea.cm2: 10_000,
|
|
420
|
-
UnitTypesArea.mm2: 1_000_000,
|
|
421
|
-
UnitTypesArea.sf: 10.764,
|
|
422
|
-
}
|
|
423
|
-
_DEFAULT_UNIT: UnitTypesArea = UnitTypesArea.m2
|
|
424
|
-
value: float = Field(ge=0.0)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
class UnitfulVelocity(ValueUnitPair[UnitTypesVelocity]):
|
|
428
|
-
"""Combination of velocity and unit.
|
|
429
|
-
|
|
430
|
-
Attributes
|
|
431
|
-
----------
|
|
432
|
-
value : float
|
|
433
|
-
The velocity value, must be greater than or equal to 0.
|
|
434
|
-
units : UnitTypesVelocity
|
|
435
|
-
The unit of velocity.
|
|
436
|
-
"""
|
|
437
|
-
|
|
438
|
-
_CONVERSION_FACTORS: dict[UnitTypesVelocity, float] = {
|
|
439
|
-
UnitTypesVelocity.mph: 2.236936,
|
|
440
|
-
UnitTypesVelocity.mps: 1,
|
|
441
|
-
UnitTypesVelocity.knots: 1.943844,
|
|
442
|
-
}
|
|
443
|
-
_DEFAULT_UNIT: UnitTypesVelocity = UnitTypesVelocity.mps
|
|
444
|
-
value: float = Field(ge=0.0)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
class UnitfulDirection(ValueUnitPair[UnitTypesDirection]):
|
|
448
|
-
"""Combination of direction and unit.
|
|
449
|
-
|
|
450
|
-
Attributes
|
|
451
|
-
----------
|
|
452
|
-
value : float
|
|
453
|
-
The direction value, must be greater than or equal to 0 and less than or equal to 360.
|
|
454
|
-
units : UnitTypesDirection
|
|
455
|
-
The unit of direction.
|
|
456
|
-
"""
|
|
457
|
-
|
|
458
|
-
_CONVERSION_FACTORS: dict[UnitTypesDirection, float] = {
|
|
459
|
-
UnitTypesDirection.degrees: 1.0,
|
|
460
|
-
}
|
|
461
|
-
_DEFAULT_UNIT: UnitTypesDirection = UnitTypesDirection.degrees
|
|
462
|
-
|
|
463
|
-
value: float = Field(ge=0.0, le=360.0)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
class UnitfulDischarge(ValueUnitPair[UnitTypesDischarge]):
|
|
467
|
-
"""Combination of discharge and unit.
|
|
468
|
-
|
|
469
|
-
Attributes
|
|
470
|
-
----------
|
|
471
|
-
value : float
|
|
472
|
-
The discharge value, must be greater than or equal to 0.
|
|
473
|
-
units : UnitTypesDischarge
|
|
474
|
-
The unit of discharge.
|
|
475
|
-
"""
|
|
476
|
-
|
|
477
|
-
_CONVERSION_FACTORS: dict[UnitTypesDischarge, float] = {
|
|
478
|
-
UnitTypesDischarge.cfs: 35.314684921034,
|
|
479
|
-
UnitTypesDischarge.cms: 1,
|
|
480
|
-
}
|
|
481
|
-
_DEFAULT_UNIT: UnitTypesDischarge = UnitTypesDischarge.cms
|
|
482
|
-
|
|
483
|
-
value: float = Field(ge=0.0)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
class UnitfulIntensity(ValueUnitPair[UnitTypesIntensity]):
|
|
487
|
-
"""Combination of intensity and unit.
|
|
488
|
-
|
|
489
|
-
Attributes
|
|
490
|
-
----------
|
|
491
|
-
value : float
|
|
492
|
-
The intensity value, must be greater than or equal to 0.
|
|
493
|
-
units : UnitTypesIntensity
|
|
494
|
-
The unit of intensity.
|
|
495
|
-
"""
|
|
496
|
-
|
|
497
|
-
_CONVERSION_FACTORS: dict[UnitTypesIntensity, float] = {
|
|
498
|
-
UnitTypesIntensity.inch_hr: 1 / 25.39544832,
|
|
499
|
-
UnitTypesIntensity.mm_hr: 1,
|
|
500
|
-
}
|
|
501
|
-
_DEFAULT_UNIT: UnitTypesIntensity = UnitTypesIntensity.mm_hr
|
|
502
|
-
value: float = Field(ge=0.0)
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
class UnitfulVolume(ValueUnitPair[UnitTypesVolume]):
|
|
506
|
-
"""Combination of volume and unit.
|
|
507
|
-
|
|
508
|
-
Attributes
|
|
509
|
-
----------
|
|
510
|
-
value : float
|
|
511
|
-
The volume value, must be greater than or equal to 0.
|
|
512
|
-
units : UnitTypesVolume
|
|
513
|
-
The unit of volume.
|
|
514
|
-
"""
|
|
515
|
-
|
|
516
|
-
_CONVERSION_FACTORS: dict[UnitTypesVolume, float] = {
|
|
517
|
-
UnitTypesVolume.m3: 1.0,
|
|
518
|
-
UnitTypesVolume.cf: 35.3146667,
|
|
519
|
-
}
|
|
520
|
-
_DEFAULT_UNIT: UnitTypesVolume = UnitTypesVolume.m3
|
|
521
|
-
|
|
522
|
-
value: float = Field(ge=0.0)
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
class UnitfulTime(ValueUnitPair[UnitTypesTime]):
|
|
526
|
-
"""Combination of time and unit.
|
|
527
|
-
|
|
528
|
-
Attributes
|
|
529
|
-
----------
|
|
530
|
-
value : float
|
|
531
|
-
The time value.
|
|
532
|
-
units : UnitTypesTime
|
|
533
|
-
The unit of time.
|
|
534
|
-
"""
|
|
535
|
-
|
|
536
|
-
_CONVERSION_FACTORS: dict[UnitTypesTime, float] = {
|
|
537
|
-
UnitTypesTime.days: 1.0 / 24.0,
|
|
538
|
-
UnitTypesTime.hours: 1.0,
|
|
539
|
-
UnitTypesTime.minutes: 60.0,
|
|
540
|
-
UnitTypesTime.seconds: 60.0 * 60.0,
|
|
541
|
-
}
|
|
542
|
-
_DEFAULT_UNIT: UnitTypesTime = UnitTypesTime.hours
|
|
543
|
-
|
|
544
|
-
@staticmethod
|
|
545
|
-
def from_timedelta(td: timedelta) -> "UnitfulTime":
|
|
546
|
-
"""Convert given timedelta to UnitfulTime object."""
|
|
547
|
-
return UnitfulTime(value=td.total_seconds(), units=UnitTypesTime.seconds)
|
|
548
|
-
|
|
549
|
-
def to_timedelta(self) -> timedelta:
|
|
550
|
-
"""Convert given time to datetime.deltatime object, relative to UnitfulTime(0, Any).
|
|
551
|
-
|
|
552
|
-
Returns
|
|
553
|
-
-------
|
|
554
|
-
datetime.timedelta
|
|
555
|
-
datetime.timedelta object with representation: (days, seconds, microseconds)
|
|
556
|
-
"""
|
|
557
|
-
return timedelta(seconds=self.convert(UnitTypesTime.seconds))
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
ValueUnitPairs = Union[
|
|
561
|
-
UnitfulLength,
|
|
562
|
-
UnitfulTime,
|
|
563
|
-
UnitfulDischarge,
|
|
564
|
-
UnitfulDirection,
|
|
565
|
-
UnitfulVelocity,
|
|
566
|
-
UnitfulIntensity,
|
|
567
|
-
UnitfulArea,
|
|
568
|
-
UnitfulVolume,
|
|
569
|
-
]
|
|
570
|
-
|
|
571
|
-
UNIT_TO_CLASS: dict[enum.EnumMeta, Type[ValueUnitPairs]] = {
|
|
572
|
-
UnitTypesLength: UnitfulLength,
|
|
573
|
-
UnitTypesTime: UnitfulTime,
|
|
574
|
-
UnitTypesDischarge: UnitfulDischarge,
|
|
575
|
-
UnitTypesDirection: UnitfulDirection,
|
|
576
|
-
UnitTypesVelocity: UnitfulVelocity,
|
|
577
|
-
UnitTypesIntensity: UnitfulIntensity,
|
|
578
|
-
UnitTypesArea: UnitfulArea,
|
|
579
|
-
UnitTypesVolume: UnitfulVolume,
|
|
580
|
-
}
|
|
1
|
+
import enum
|
|
2
|
+
import math
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Generic, Optional, Type, TypeVar, Union
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ValueUnitPair",
|
|
12
|
+
"VerticalReference",
|
|
13
|
+
"UnitTypesLength",
|
|
14
|
+
"UnitTypesArea",
|
|
15
|
+
"UnitTypesVolume",
|
|
16
|
+
"UnitTypesVelocity",
|
|
17
|
+
"UnitTypesDirection",
|
|
18
|
+
"UnitTypesTime",
|
|
19
|
+
"UnitTypesDischarge",
|
|
20
|
+
"UnitTypesIntensity",
|
|
21
|
+
"UnitfulLength",
|
|
22
|
+
"UnitfulHeight",
|
|
23
|
+
"UnitfulLengthRefValue",
|
|
24
|
+
"UnitfulArea",
|
|
25
|
+
"UnitfulVelocity",
|
|
26
|
+
"UnitfulDirection",
|
|
27
|
+
"UnitfulDischarge",
|
|
28
|
+
"UnitfulIntensity",
|
|
29
|
+
"UnitfulVolume",
|
|
30
|
+
"UnitfulTime",
|
|
31
|
+
"ValueUnitPairs",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
TUnit = TypeVar("TUnit", bound=enum.Enum)
|
|
35
|
+
TClass = TypeVar("TClass", bound="ValueUnitPair")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ValueUnitPair(ABC, BaseModel, Generic[TUnit]):
|
|
39
|
+
"""
|
|
40
|
+
Represents a value with associated units.
|
|
41
|
+
|
|
42
|
+
Attributes
|
|
43
|
+
----------
|
|
44
|
+
value: float
|
|
45
|
+
The numerical value.
|
|
46
|
+
units : Unit
|
|
47
|
+
The units of the value.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
_DEFAULT_UNIT: TUnit
|
|
51
|
+
_CONVERSION_FACTORS: dict[TUnit, float]
|
|
52
|
+
|
|
53
|
+
value: float
|
|
54
|
+
units: TUnit
|
|
55
|
+
|
|
56
|
+
def convert(self, new_units: TUnit) -> float:
|
|
57
|
+
"""Return the value converted to the new units.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
new_units : Unit
|
|
62
|
+
The new units.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
converted_value : float
|
|
67
|
+
The converted value.
|
|
68
|
+
"""
|
|
69
|
+
if new_units not in self._CONVERSION_FACTORS:
|
|
70
|
+
raise ValueError(f"Invalid units: {new_units}")
|
|
71
|
+
in_default_units = self.value / self._CONVERSION_FACTORS[self.units]
|
|
72
|
+
return in_default_units * self._CONVERSION_FACTORS[new_units]
|
|
73
|
+
|
|
74
|
+
def transform(self: TClass, new_units: TUnit) -> TClass:
|
|
75
|
+
"""Return a new ValueUnitPair instance with the value converted to the new units.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
new_units : Unit
|
|
80
|
+
The new units.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
value_unit_pair : ValueUnitPair
|
|
85
|
+
The new ValueUnitPair instance with the value converted to the new units and the new units.
|
|
86
|
+
"""
|
|
87
|
+
return type(self)(value=self.convert(new_units), units=new_units)
|
|
88
|
+
|
|
89
|
+
@model_validator(mode="before")
|
|
90
|
+
@classmethod
|
|
91
|
+
def extract_unit_class(cls, data: Optional[dict]) -> "ValueUnitPair":
|
|
92
|
+
if cls is not ValueUnitPair:
|
|
93
|
+
return data # Already in the right subclass, don't interfere
|
|
94
|
+
|
|
95
|
+
if not isinstance(data, dict):
|
|
96
|
+
raise TypeError("Expected dictionary for deserialization.")
|
|
97
|
+
|
|
98
|
+
str_unit = data.get("units")
|
|
99
|
+
if not str_unit:
|
|
100
|
+
raise ValueError("Missing 'units' field in input data.")
|
|
101
|
+
|
|
102
|
+
for unit_enum_cls, vu_cls in UNIT_TO_CLASS.items():
|
|
103
|
+
try:
|
|
104
|
+
enum_unit = unit_enum_cls(str_unit)
|
|
105
|
+
data["units"] = enum_unit
|
|
106
|
+
return vu_cls(**data)
|
|
107
|
+
except ValueError:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
raise ValueError(f"Unsupported or unknown unit: {str_unit}")
|
|
111
|
+
|
|
112
|
+
def __str__(self) -> str:
|
|
113
|
+
return f"{self.value} {self.units.value}"
|
|
114
|
+
|
|
115
|
+
def __repr__(self) -> str:
|
|
116
|
+
return f"{type(self).__name__}(value={self.value}, units={self.units})"
|
|
117
|
+
|
|
118
|
+
def __sub__(self: TClass, other: TClass) -> TClass:
|
|
119
|
+
if not isinstance(other, type(self)):
|
|
120
|
+
raise TypeError(
|
|
121
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
122
|
+
)
|
|
123
|
+
return type(self)(
|
|
124
|
+
value=self.value - other.convert(self.units), units=self.units
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def __add__(self: TClass, other: TClass) -> TClass:
|
|
128
|
+
if not isinstance(other, type(self)):
|
|
129
|
+
raise TypeError(
|
|
130
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
131
|
+
)
|
|
132
|
+
return type(self)(
|
|
133
|
+
value=self.value + other.convert(self.units), units=self.units
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def __eq__(self: TClass, other: TClass) -> bool:
|
|
137
|
+
if not isinstance(other, ValueUnitPair):
|
|
138
|
+
raise TypeError(
|
|
139
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
140
|
+
)
|
|
141
|
+
if not (hasattr(other, "value") and hasattr(other, "units")):
|
|
142
|
+
raise AttributeError(f"Incomplete UnitfulValue instance: {other}")
|
|
143
|
+
|
|
144
|
+
if not isinstance(other.units, type(self.units)):
|
|
145
|
+
raise TypeError(
|
|
146
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return math.isclose(
|
|
150
|
+
self.value, other.convert(self.units), rel_tol=1e-2
|
|
151
|
+
) # 1% relative tolerance for equality. So 1.0 == 1.01 evaluates to True
|
|
152
|
+
|
|
153
|
+
def __lt__(self: TClass, other: TClass) -> bool:
|
|
154
|
+
if not isinstance(other, type(self)):
|
|
155
|
+
raise TypeError(
|
|
156
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
157
|
+
)
|
|
158
|
+
return self.value < other.convert(self.units)
|
|
159
|
+
|
|
160
|
+
def __gt__(self: TClass, other: TClass) -> bool:
|
|
161
|
+
if not isinstance(other, type(self)):
|
|
162
|
+
raise TypeError(
|
|
163
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
164
|
+
)
|
|
165
|
+
return self.value > other.convert(self.units)
|
|
166
|
+
|
|
167
|
+
def __ge__(self: TClass, other: TClass) -> bool:
|
|
168
|
+
if not isinstance(other, type(self)):
|
|
169
|
+
raise TypeError(
|
|
170
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
171
|
+
)
|
|
172
|
+
return (self > other) or (self == other)
|
|
173
|
+
|
|
174
|
+
def __le__(self: TClass, other: TClass) -> bool:
|
|
175
|
+
if not isinstance(other, type(self)):
|
|
176
|
+
raise TypeError(
|
|
177
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
178
|
+
)
|
|
179
|
+
return (self < other) or (self == other)
|
|
180
|
+
|
|
181
|
+
def __ne__(self: TClass, other: TClass) -> bool:
|
|
182
|
+
if not isinstance(other, type(self)):
|
|
183
|
+
raise TypeError(
|
|
184
|
+
f"Cannot compare self: {type(self).__name__} to other: {type(other).__name__}"
|
|
185
|
+
)
|
|
186
|
+
return not (self == other)
|
|
187
|
+
|
|
188
|
+
def __mul__(self: TClass, other: int | float) -> TClass:
|
|
189
|
+
if isinstance(other, int) or isinstance(other, float):
|
|
190
|
+
return type(self)(value=self.value * other, units=self.units)
|
|
191
|
+
else:
|
|
192
|
+
raise TypeError(
|
|
193
|
+
f"Cannot multiply self: {type(self).__name__} with other: {type(other).__name__}. Only int and float are allowed."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def __div__(self: TClass, other: TClass | int | float) -> TClass | float:
|
|
197
|
+
if isinstance(other, int) or isinstance(other, float):
|
|
198
|
+
return type(self)(value=self.value / other, units=self.units)
|
|
199
|
+
elif isinstance(other, type(self)):
|
|
200
|
+
return self.value / other.convert(self.units)
|
|
201
|
+
else:
|
|
202
|
+
raise TypeError(
|
|
203
|
+
f"Cannot divide self: {type(self).__name__} with other: {type(other).__name__}. Only {type(self).__name__}, int and float are allowed."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def __truediv__(self: TClass, other: TClass | int | float) -> TClass | float:
|
|
207
|
+
if isinstance(other, int) or isinstance(other, float):
|
|
208
|
+
return type(self)(value=self.value / other, units=self.units)
|
|
209
|
+
elif isinstance(other, type(self)):
|
|
210
|
+
return self.value / other.convert(self.units)
|
|
211
|
+
else:
|
|
212
|
+
raise TypeError(
|
|
213
|
+
f"Cannot divide self: {type(self).__name__} with other: {type(other).__name__}. Only {type(self).__name__}, int and float are allowed."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class UnitTypesLength(str, Enum):
|
|
218
|
+
"""Units of length.
|
|
219
|
+
|
|
220
|
+
Attributes
|
|
221
|
+
----------
|
|
222
|
+
meters : meters
|
|
223
|
+
centimeters : centimeters
|
|
224
|
+
millimeters : millimeters
|
|
225
|
+
feet : feet
|
|
226
|
+
inch : inch
|
|
227
|
+
miles : miles
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
meters = "meters"
|
|
231
|
+
centimeters = "centimeters"
|
|
232
|
+
millimeters = "millimeters"
|
|
233
|
+
feet = "feet"
|
|
234
|
+
inch = "inch"
|
|
235
|
+
miles = "miles"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class UnitTypesArea(str, Enum):
|
|
239
|
+
"""Units of area.
|
|
240
|
+
|
|
241
|
+
Attributes
|
|
242
|
+
----------
|
|
243
|
+
m2 : square meters
|
|
244
|
+
dm2 : square decimeters
|
|
245
|
+
cm2 : square centimeters
|
|
246
|
+
mm2 : square millimeters
|
|
247
|
+
sf : square feet
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
m2 = "m2"
|
|
251
|
+
dm2 = "dm2"
|
|
252
|
+
cm2 = "cm2"
|
|
253
|
+
mm2 = "mm2"
|
|
254
|
+
sf = "sf"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class UnitTypesVolume(str, Enum):
|
|
258
|
+
"""Units of volume.
|
|
259
|
+
|
|
260
|
+
Attributes
|
|
261
|
+
----------
|
|
262
|
+
m3 : cubic meters
|
|
263
|
+
cf : cubic feet
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
m3 = "m3"
|
|
267
|
+
cf = "cf"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class UnitTypesVelocity(str, Enum):
|
|
271
|
+
"""Units of velocity.
|
|
272
|
+
|
|
273
|
+
Attributes
|
|
274
|
+
----------
|
|
275
|
+
mps : meters per second
|
|
276
|
+
knots : nautical miles per hour
|
|
277
|
+
mph : miles per hour
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
mps = "m/s"
|
|
281
|
+
knots = "knots"
|
|
282
|
+
mph = "mph"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class UnitTypesDirection(str, Enum):
|
|
286
|
+
"""Units of direction.
|
|
287
|
+
|
|
288
|
+
Attributes
|
|
289
|
+
----------
|
|
290
|
+
degrees : degrees
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
degrees = "deg N"
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class UnitTypesTime(str, Enum):
|
|
297
|
+
"""Units of time.
|
|
298
|
+
|
|
299
|
+
Attributes
|
|
300
|
+
----------
|
|
301
|
+
seconds : seconds
|
|
302
|
+
minutes : minutes
|
|
303
|
+
hours : hours
|
|
304
|
+
days : days
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
seconds = "seconds"
|
|
308
|
+
minutes = "minutes"
|
|
309
|
+
hours = "hours"
|
|
310
|
+
days = "days"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class UnitTypesDischarge(str, Enum):
|
|
314
|
+
"""Units of discharge.
|
|
315
|
+
|
|
316
|
+
Attributes
|
|
317
|
+
----------
|
|
318
|
+
cfs : cubic feet per second
|
|
319
|
+
cms : cubic meters per second
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
cfs = "cfs"
|
|
323
|
+
cms = "m3/s"
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class UnitTypesIntensity(str, Enum):
|
|
327
|
+
"""Units of intensity.
|
|
328
|
+
|
|
329
|
+
Attributes
|
|
330
|
+
----------
|
|
331
|
+
inch_hr : inch per hour
|
|
332
|
+
mm_hr : millimeter per hour
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
inch_hr = "inch/hr"
|
|
336
|
+
mm_hr = "mm/hr"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class VerticalReference(str, Enum):
|
|
340
|
+
"""Vertical reference for height.
|
|
341
|
+
|
|
342
|
+
Attributes
|
|
343
|
+
----------
|
|
344
|
+
floodmap : Use the floodmap as reference.
|
|
345
|
+
datum : Use the datum as reference.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
floodmap = "floodmap"
|
|
349
|
+
datum = "datum"
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class UnitfulLength(ValueUnitPair[UnitTypesLength]):
|
|
353
|
+
"""Combination of length and unit.
|
|
354
|
+
|
|
355
|
+
Attributes
|
|
356
|
+
----------
|
|
357
|
+
value : float
|
|
358
|
+
The length value.
|
|
359
|
+
units : UnitTypesLength
|
|
360
|
+
The unit of length.
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
_CONVERSION_FACTORS: dict[UnitTypesLength, float] = {
|
|
364
|
+
UnitTypesLength.meters: 1.0,
|
|
365
|
+
UnitTypesLength.centimeters: 100.0,
|
|
366
|
+
UnitTypesLength.millimeters: 1000.0,
|
|
367
|
+
UnitTypesLength.feet: 3.28084,
|
|
368
|
+
UnitTypesLength.inch: 1.0 / 0.0254,
|
|
369
|
+
UnitTypesLength.miles: 1 / 1609.344,
|
|
370
|
+
}
|
|
371
|
+
_DEFAULT_UNIT: UnitTypesLength = UnitTypesLength.meters
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class UnitfulHeight(UnitfulLength):
|
|
375
|
+
"""Combination of height and unit.
|
|
376
|
+
|
|
377
|
+
Attributes
|
|
378
|
+
----------
|
|
379
|
+
value : float
|
|
380
|
+
The height value, must be greater than or equal to 0.
|
|
381
|
+
units : UnitTypesLength
|
|
382
|
+
The unit of height.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
value: float = Field(ge=0.0)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class UnitfulLengthRefValue(UnitfulLength):
|
|
389
|
+
"""Combination of length and unit with a reference value.
|
|
390
|
+
|
|
391
|
+
Attributes
|
|
392
|
+
----------
|
|
393
|
+
value : float
|
|
394
|
+
The length value, must be greater than or equal to 0.
|
|
395
|
+
units : UnitTypesLength
|
|
396
|
+
The unit of length.
|
|
397
|
+
type : VerticalReference
|
|
398
|
+
The vertical reference for the length.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
type: VerticalReference
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class UnitfulArea(ValueUnitPair[UnitTypesArea]):
|
|
405
|
+
"""Combination of area and unit.
|
|
406
|
+
|
|
407
|
+
Attributes
|
|
408
|
+
----------
|
|
409
|
+
value : float
|
|
410
|
+
The area value, must be greater than or equal to 0.
|
|
411
|
+
units : UnitTypesArea
|
|
412
|
+
The unit of area.
|
|
413
|
+
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
_CONVERSION_FACTORS: dict[UnitTypesArea, float] = {
|
|
417
|
+
UnitTypesArea.m2: 1,
|
|
418
|
+
UnitTypesArea.dm2: 100,
|
|
419
|
+
UnitTypesArea.cm2: 10_000,
|
|
420
|
+
UnitTypesArea.mm2: 1_000_000,
|
|
421
|
+
UnitTypesArea.sf: 10.764,
|
|
422
|
+
}
|
|
423
|
+
_DEFAULT_UNIT: UnitTypesArea = UnitTypesArea.m2
|
|
424
|
+
value: float = Field(ge=0.0)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class UnitfulVelocity(ValueUnitPair[UnitTypesVelocity]):
|
|
428
|
+
"""Combination of velocity and unit.
|
|
429
|
+
|
|
430
|
+
Attributes
|
|
431
|
+
----------
|
|
432
|
+
value : float
|
|
433
|
+
The velocity value, must be greater than or equal to 0.
|
|
434
|
+
units : UnitTypesVelocity
|
|
435
|
+
The unit of velocity.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
_CONVERSION_FACTORS: dict[UnitTypesVelocity, float] = {
|
|
439
|
+
UnitTypesVelocity.mph: 2.236936,
|
|
440
|
+
UnitTypesVelocity.mps: 1,
|
|
441
|
+
UnitTypesVelocity.knots: 1.943844,
|
|
442
|
+
}
|
|
443
|
+
_DEFAULT_UNIT: UnitTypesVelocity = UnitTypesVelocity.mps
|
|
444
|
+
value: float = Field(ge=0.0)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class UnitfulDirection(ValueUnitPair[UnitTypesDirection]):
|
|
448
|
+
"""Combination of direction and unit.
|
|
449
|
+
|
|
450
|
+
Attributes
|
|
451
|
+
----------
|
|
452
|
+
value : float
|
|
453
|
+
The direction value, must be greater than or equal to 0 and less than or equal to 360.
|
|
454
|
+
units : UnitTypesDirection
|
|
455
|
+
The unit of direction.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
_CONVERSION_FACTORS: dict[UnitTypesDirection, float] = {
|
|
459
|
+
UnitTypesDirection.degrees: 1.0,
|
|
460
|
+
}
|
|
461
|
+
_DEFAULT_UNIT: UnitTypesDirection = UnitTypesDirection.degrees
|
|
462
|
+
|
|
463
|
+
value: float = Field(ge=0.0, le=360.0)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class UnitfulDischarge(ValueUnitPair[UnitTypesDischarge]):
|
|
467
|
+
"""Combination of discharge and unit.
|
|
468
|
+
|
|
469
|
+
Attributes
|
|
470
|
+
----------
|
|
471
|
+
value : float
|
|
472
|
+
The discharge value, must be greater than or equal to 0.
|
|
473
|
+
units : UnitTypesDischarge
|
|
474
|
+
The unit of discharge.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
_CONVERSION_FACTORS: dict[UnitTypesDischarge, float] = {
|
|
478
|
+
UnitTypesDischarge.cfs: 35.314684921034,
|
|
479
|
+
UnitTypesDischarge.cms: 1,
|
|
480
|
+
}
|
|
481
|
+
_DEFAULT_UNIT: UnitTypesDischarge = UnitTypesDischarge.cms
|
|
482
|
+
|
|
483
|
+
value: float = Field(ge=0.0)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class UnitfulIntensity(ValueUnitPair[UnitTypesIntensity]):
|
|
487
|
+
"""Combination of intensity and unit.
|
|
488
|
+
|
|
489
|
+
Attributes
|
|
490
|
+
----------
|
|
491
|
+
value : float
|
|
492
|
+
The intensity value, must be greater than or equal to 0.
|
|
493
|
+
units : UnitTypesIntensity
|
|
494
|
+
The unit of intensity.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
_CONVERSION_FACTORS: dict[UnitTypesIntensity, float] = {
|
|
498
|
+
UnitTypesIntensity.inch_hr: 1 / 25.39544832,
|
|
499
|
+
UnitTypesIntensity.mm_hr: 1,
|
|
500
|
+
}
|
|
501
|
+
_DEFAULT_UNIT: UnitTypesIntensity = UnitTypesIntensity.mm_hr
|
|
502
|
+
value: float = Field(ge=0.0)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class UnitfulVolume(ValueUnitPair[UnitTypesVolume]):
|
|
506
|
+
"""Combination of volume and unit.
|
|
507
|
+
|
|
508
|
+
Attributes
|
|
509
|
+
----------
|
|
510
|
+
value : float
|
|
511
|
+
The volume value, must be greater than or equal to 0.
|
|
512
|
+
units : UnitTypesVolume
|
|
513
|
+
The unit of volume.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
_CONVERSION_FACTORS: dict[UnitTypesVolume, float] = {
|
|
517
|
+
UnitTypesVolume.m3: 1.0,
|
|
518
|
+
UnitTypesVolume.cf: 35.3146667,
|
|
519
|
+
}
|
|
520
|
+
_DEFAULT_UNIT: UnitTypesVolume = UnitTypesVolume.m3
|
|
521
|
+
|
|
522
|
+
value: float = Field(ge=0.0)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class UnitfulTime(ValueUnitPair[UnitTypesTime]):
|
|
526
|
+
"""Combination of time and unit.
|
|
527
|
+
|
|
528
|
+
Attributes
|
|
529
|
+
----------
|
|
530
|
+
value : float
|
|
531
|
+
The time value.
|
|
532
|
+
units : UnitTypesTime
|
|
533
|
+
The unit of time.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
_CONVERSION_FACTORS: dict[UnitTypesTime, float] = {
|
|
537
|
+
UnitTypesTime.days: 1.0 / 24.0,
|
|
538
|
+
UnitTypesTime.hours: 1.0,
|
|
539
|
+
UnitTypesTime.minutes: 60.0,
|
|
540
|
+
UnitTypesTime.seconds: 60.0 * 60.0,
|
|
541
|
+
}
|
|
542
|
+
_DEFAULT_UNIT: UnitTypesTime = UnitTypesTime.hours
|
|
543
|
+
|
|
544
|
+
@staticmethod
|
|
545
|
+
def from_timedelta(td: timedelta) -> "UnitfulTime":
|
|
546
|
+
"""Convert given timedelta to UnitfulTime object."""
|
|
547
|
+
return UnitfulTime(value=td.total_seconds(), units=UnitTypesTime.seconds)
|
|
548
|
+
|
|
549
|
+
def to_timedelta(self) -> timedelta:
|
|
550
|
+
"""Convert given time to datetime.deltatime object, relative to UnitfulTime(0, Any).
|
|
551
|
+
|
|
552
|
+
Returns
|
|
553
|
+
-------
|
|
554
|
+
datetime.timedelta
|
|
555
|
+
datetime.timedelta object with representation: (days, seconds, microseconds)
|
|
556
|
+
"""
|
|
557
|
+
return timedelta(seconds=self.convert(UnitTypesTime.seconds))
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
ValueUnitPairs = Union[
|
|
561
|
+
UnitfulLength,
|
|
562
|
+
UnitfulTime,
|
|
563
|
+
UnitfulDischarge,
|
|
564
|
+
UnitfulDirection,
|
|
565
|
+
UnitfulVelocity,
|
|
566
|
+
UnitfulIntensity,
|
|
567
|
+
UnitfulArea,
|
|
568
|
+
UnitfulVolume,
|
|
569
|
+
]
|
|
570
|
+
|
|
571
|
+
UNIT_TO_CLASS: dict[enum.EnumMeta, Type[ValueUnitPairs]] = {
|
|
572
|
+
UnitTypesLength: UnitfulLength,
|
|
573
|
+
UnitTypesTime: UnitfulTime,
|
|
574
|
+
UnitTypesDischarge: UnitfulDischarge,
|
|
575
|
+
UnitTypesDirection: UnitfulDirection,
|
|
576
|
+
UnitTypesVelocity: UnitfulVelocity,
|
|
577
|
+
UnitTypesIntensity: UnitfulIntensity,
|
|
578
|
+
UnitTypesArea: UnitfulArea,
|
|
579
|
+
UnitTypesVolume: UnitfulVolume,
|
|
580
|
+
}
|