The Rainfall Runoff meteo layer¶
The meteo layer currently contains only support for the BUI file. The "bui" file contains the precipitation input data for Rainfall Runoff. It is represented by the classes below.
Model¶
BuiModel (ParsableFileModel)
pydantic-model
¶
Model that represents the file structure of a .bui file.
get_station_events(self, station: str) -> Dict[datetime.datetime, List[float]]
¶
Returns all the events (start time and precipitations) related to a given station.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
station |
str |
Name of the station to retrieve. |
required |
Exceptions:
Type | Description |
---|---|
ValueError |
If the station name does not exist in the BuiModel. |
Returns:
Type | Description |
---|---|
Dict[datetime, List[float]] |
Dictionary with the start time and its precipitations. |
Source code in hydrolib/core/io/rr/meteo/models.py
def get_station_events(self, station: str) -> Dict[datetime, List[float]]:
"""
Returns all the events (start time and precipitations) related to a given station.
Args:
station (str): Name of the station to retrieve.
Raises:
ValueError: If the station name does not exist in the BuiModel.
Returns:
Dict[datetime, List[float]]: Dictionary with the start time and its precipitations.
"""
if station not in self.name_of_stations:
raise ValueError("Station {} not found BuiModel.".format(station))
station_idx = self.name_of_stations.index(station)
station_events = {}
for event in self.precipitation_events:
start_time, precipitations = event.get_station_precipitations(station_idx)
station_events[start_time] = precipitations
return station_events
BuiPrecipitationEvent (BaseModel)
pydantic-model
¶
get_station_precipitations(self, station_idx: int) -> Tuple[datetime.datetime, List[float]]
¶
Returns all the precipitations related to the given station index (column).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
station_idx |
int |
Index of the column which values need to be retrieved. |
required |
Exceptions:
Type | Description |
---|---|
ValueError |
If the station index does not exist. |
Returns:
Type | Description |
---|---|
Tuple[datetime, List[float]] |
Tuple with the start time and its precipitations. |
Source code in hydrolib/core/io/rr/meteo/models.py
def get_station_precipitations(
self, station_idx: int
) -> Tuple[datetime, List[float]]:
"""
Returns all the precipitations related to the given station index (column).
Args:
station_idx (int): Index of the column which values need to be retrieved.
Raises:
ValueError: If the station index does not exist.
Returns:
Tuple[datetime, List[float]]: Tuple with the start time and its precipitations.
"""
number_of_stations = len(self.precipitation_per_timestep[0])
if station_idx >= number_of_stations:
raise ValueError(
"Station index not found, number of stations: {}".format(
number_of_stations
)
)
return (
self.start_time,
[
ts_precipitations[station_idx]
for ts_precipitations in self.precipitation_per_timestep
],
)
Parser¶
BuiEventListParser
¶
A parser for .bui events which are like this: StartTime (YYYY mm dd HH MM SS) TimeSeriesLength (dd HH MM SS) PrecipitationPerTimestep StartTime (YYYY mm dd HH MM SS) TimeSeriesLength (dd HH MM SS) PrecipitationPerTimestep Example given: 2021 12 20 0 0 0 1 0 4 20 4.2 4.2 4.2 2021 12 21 0 0 0 1 0 4 20 2.4 2.4 2.4
parse(raw_text: str, n_events: int, timestep: int) -> List[Dict]
staticmethod
¶
Parses a given raw text containing 0 to many text blocks representing a precipitation event.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
raw_text |
str |
Text blocks representing precipitation events. |
required |
n_events |
int |
Number of events contained in the text block. |
required |
timestep |
int |
Number of seconds conforming a timestep. |
required |
Returns:
Type | Description |
---|---|
List[Dict] |
List containing all the events represented as dictionaries. |
Source code in hydrolib/core/io/rr/meteo/parser.py
@staticmethod
def parse(raw_text: str, n_events: int, timestep: int) -> List[Dict]:
"""
Parses a given raw text containing 0 to many text blocks representing a precipitation event.
Args:
raw_text (str): Text blocks representing precipitation events.
n_events (int): Number of events contained in the text block.
timestep (int): Number of seconds conforming a timestep.
Returns:
List[Dict]: List containing all the events represented as dictionaries.
"""
def get_event_timestep_length(raw_line: str) -> int:
timereference = BuiEventParser.parse_event_time_reference(raw_line)
ts_length: timedelta = timereference["timeseries_length"]
return ts_length.total_seconds()
def get_multiple_events(raw_lines: List[str]) -> Iterator[BuiEventParser]:
n_line = 0
while n_line < len(raw_lines):
ts_seconds = get_event_timestep_length(raw_lines[n_line])
event_lines = int(ts_seconds / timestep) + 1
yield BuiEventParser.parse("\n".join(raw_lines[n_line:][:event_lines]))
n_line += event_lines
event_list = []
if n_events == 1:
event_list.append(BuiEventParser.parse(raw_text))
elif n_events > 1:
raw_lines = raw_text.splitlines(keepends=False)
event_list = list(get_multiple_events(raw_lines))
return event_list
BuiEventParser
¶
A parser for the precipitation event section within a .bui file. It resembles something like this: StartTime (YYYY mm dd HH MM SS) TimeSeriesLength (dd HH MM SS) PrecipitationPerTimestep Example given: 2021 12 20 0 0 0 1 0 4 20 4.2 2.4 4.2 2.4 4.2 2.4 (it should match the timeseries length based on the seconds per timstep.) Each column of the last three lines represents a station.
parse(raw_text: str) -> Dict
staticmethod
¶
Given text representing a single BuiPrecipitationEvent parses it into a dictionary.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
raw_text |
str |
Text containing a single precipitation event. |
required |
Returns:
Type | Description |
---|---|
Dict |
Mapped contents of the text. |
Source code in hydrolib/core/io/rr/meteo/parser.py
@staticmethod
def parse(raw_text: str) -> Dict:
"""
Given text representing a single BuiPrecipitationEvent parses it into a dictionary.
Args:
raw_text (str): Text containing a single precipitation event.
Returns:
Dict: Mapped contents of the text.
"""
def get_precipitations_per_ts(line: str) -> List[str]:
return [prec for prec in line.split()]
event_lines = raw_text.splitlines(keepends=False)
time_reference = BuiEventParser.parse_event_time_reference(event_lines[0])
return dict(
start_time=time_reference["start_time"],
timeseries_length=time_reference["timeseries_length"],
precipitation_per_timestep=list(
map(get_precipitations_per_ts, event_lines[1:])
),
)
parse_event_time_reference(raw_text: str) -> Dict
staticmethod
¶
Parses a single event time reference line containing both the start time and the timeseries length into a dictionary.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
raw_text |
str |
Line representing both start time and timeseries length. |
required |
Returns:
Type | Description |
---|---|
Dict |
Resulting dictionary with keys start_time and timeseries_length. |
Source code in hydrolib/core/io/rr/meteo/parser.py
@staticmethod
def parse_event_time_reference(raw_text: str) -> Dict:
"""
Parses a single event time reference line containing both the start time
and the timeseries length into a dictionary.
Args:
raw_text (str): Line representing both start time and timeseries length.
Returns:
Dict: Resulting dictionary with keys start_time and timeseries_length.
"""
def get_start_time(line: str) -> datetime:
return datetime.strptime(line, "%Y %m %d %H %M %S")
def get_timeseries_length(line: str) -> timedelta:
time_fields = line.split()
return timedelta(
days=int(time_fields[0]),
hours=int(time_fields[1]),
minutes=int(time_fields[2]),
seconds=int(time_fields[3]),
)
timeref = raw_text.split()
return dict(
start_time=get_start_time(" ".join(timeref[:6])),
timeseries_length=get_timeseries_length(" ".join(timeref[6:])),
)
BuiParser
¶
A parser for .bui files which are like this: * comments Dataset type to use (always 1). * comments Number of stations. * comments Name of stations * comments Number of events Number of seconds per timestep. * comments First datetime reference. Precipitation per timestep per station.
parse(filepath: Path) -> Dict
staticmethod
¶
Parses a given file, in case valid, into a dictionary which can later be mapped to the BuiModel.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filepath |
Path |
Path to file containing the data to parse. |
required |
Returns:
Type | Description |
---|---|
Dict |
Parsed values. |
Source code in hydrolib/core/io/rr/meteo/parser.py
@staticmethod
def parse(filepath: Path) -> Dict:
"""
Parses a given file, in case valid, into a dictionary which can later be mapped
to the BuiModel.
Args:
filepath (Path): Path to file containing the data to parse.
Returns:
Dict: Parsed values.
"""
def get_station_ids(line: str) -> List[str]:
return [s_id for s_id in line.split(",")]
def parse_events_and_timestep(line: str) -> Tuple[int, int]:
n_events_timestep = line.split()
return (int(n_events_timestep[0]), int(n_events_timestep[1]))
bui_lines = [
line
for line in filepath.read_text(encoding="utf8").splitlines()
if not line.startswith("*")
]
n_events, timestep = parse_events_and_timestep(bui_lines[3])
return dict(
default_dataset=bui_lines[0],
number_of_stations=bui_lines[1],
name_of_stations=get_station_ids(bui_lines[2]),
number_of_events=n_events,
seconds_per_timestep=timestep,
precipitation_events=BuiEventListParser.parse(
"\n".join(bui_lines[4:]), n_events, timestep
),
)
Serializer¶
BuiEventSerializer
¶
Serializer class to transform a bui event into a text block.
get_timedelta_fields(duration: timedelta) -> Dict
staticmethod
¶
Gets a dictionary containing the time delta in days, hours, minutes and seconds. This means that the seconds field does not contain the accumulative value of days hours and minutes.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
duration |
timedelta |
Timedelta to convert. |
required |
Returns:
Type | Description |
---|---|
Dict |
Dictionary containing all fields. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def get_timedelta_fields(duration: timedelta) -> Dict:
"""
Gets a dictionary containing the time delta in days, hours, minutes and seconds.
This means that the seconds field does not contain the accumulative value of days
hours and minutes.
Args:
duration (timedelta): Timedelta to convert.
Returns:
Dict: Dictionary containing all fields.
"""
total_hours = int(duration.seconds / (60 * 60))
total_minutes = int((duration.seconds / 60) - (total_hours * 60))
total_seconds = int(
duration.seconds - ((total_hours * 60 + total_minutes) * 60)
)
return dict(
d_seconds=total_seconds,
d_minutes=total_minutes,
d_hours=total_hours,
d_days=duration.days,
)
serialize(event_data: Dict) -> str
staticmethod
¶
Serializes a dictionary representing an event into a text block.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
event_data |
Dict |
Dictionary representing precipitation event. |
required |
Returns:
Type | Description |
---|---|
str |
Formatted string. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize(event_data: Dict) -> str:
"""
Serializes a dictionary representing an event into a text block.
Args:
event_data (Dict): Dictionary representing precipitation event.
Returns:
str: Formatted string.
"""
event_data["start_time"] = BuiEventSerializer.serialize_start_time(
event_data["start_time"]
)
ts_duration = event_data["timeseries_length"]
event_data = {
**event_data,
**BuiEventSerializer.get_timedelta_fields(ts_duration),
}
event_data[
"timeseries_length"
] = BuiEventSerializer.serialize_timeseries_length(
event_data["timeseries_length"]
)
event_data[
"precipitation_per_timestep"
] = BuiEventSerializer.serialize_precipitation_per_timestep(
event_data["precipitation_per_timestep"]
)
if "event_idx" not in event_data.keys():
event_data["event_idx"] = 1
return BuiEventSerializer.bui_event_template.format(**event_data)
serialize_precipitation_per_timestep(data_to_serialize: List[List[str]]) -> str
staticmethod
¶
Serialized the data containing all the precipitations per timestep (and station) into a single string ready to be mapped.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data_to_serialize |
List[List[str]] |
Data to be mapped. |
required |
Returns:
Type | Description |
---|---|
str |
Serialized string in .bui format. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize_precipitation_per_timestep(data_to_serialize: List[List[str]]) -> str:
"""
Serialized the data containing all the precipitations per timestep (and station)
into a single string ready to be mapped.
Args:
data_to_serialize (List[List[str]]): Data to be mapped.
Returns:
str: Serialized string in .bui format.
"""
serialized_data = str.join(
"\n",
[str.join(" ", map(str, listed_data)) for listed_data in data_to_serialize],
)
return serialized_data
serialize_start_time(data_to_serialize: datetime) -> str
staticmethod
¶
Serializes a datetime into the expected .bui format.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data_to_serialize |
datetime |
Datetime representing reference time. |
required |
Returns:
Type | Description |
---|---|
str |
Converted datetime into string. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize_start_time(data_to_serialize: datetime) -> str:
"""
Serializes a datetime into the expected .bui format.
Args:
data_to_serialize (datetime): Datetime representing reference time.
Returns:
str: Converted datetime into string.
"""
# Not using the following format because we only want one digit instead of
# double (day 1 -> 1, instead of 01).
# data_to_serialize.strftime("%Y %m %d %H %M %S")
dt = data_to_serialize
return f"{dt.year} {dt.month} {dt.day} {dt.hour} {dt.minute} {dt.second}"
serialize_timeseries_length(data_to_serialize: timedelta) -> str
staticmethod
¶
Serializes a given timedelta into the .bui format.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data_to_serialize |
timedelta |
Reference timespan to serialize. |
required |
Returns:
Type | Description |
---|---|
str |
Converted timedelta in string. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize_timeseries_length(data_to_serialize: timedelta) -> str:
"""
Serializes a given timedelta into the .bui format.
Args:
data_to_serialize (timedelta): Reference timespan to serialize.
Returns:
str: Converted timedelta in string.
"""
fields_dict = BuiEventSerializer.get_timedelta_fields(data_to_serialize)
total_hours = fields_dict["d_hours"]
total_minutes = fields_dict["d_minutes"]
total_seconds = fields_dict["d_seconds"]
return f"{data_to_serialize.days} {total_hours} {total_minutes} {total_seconds}"
BuiSerializer
¶
Serializer class to transform an object into a .bui file text format.
serialize(bui_data: Dict) -> str
staticmethod
¶
Formats the bui_template with the content of the given data. NOTE: It requires that caller injects file_path into bui_data prior to this call. Otherwise it will crash.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
bui_data |
Dict |
Data to serialize. |
required |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize(bui_data: Dict) -> str:
"""
Formats the bui_template with the content of the given data.
NOTE: It requires that caller injects file_path into bui_data prior to this call.
Otherwise it will crash.
Args:
bui_data (Dict): Data to serialize.
"""
bui_data["datetime_now"] = datetime.now().strftime("%d-%m-%y %H:%M:%S")
bui_data["name_of_stations"] = BuiSerializer.serialize_stations_ids(
bui_data["name_of_stations"]
)
bui_data["precipitation_events"] = BuiSerializer.serialize_event_list(
bui_data["precipitation_events"]
)
return BuiSerializer.bui_template.format(**bui_data)
serialize_event_list(data_to_serialize: List[Dict]) -> str
staticmethod
¶
Serializes a event list dictionary into a single text block.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data_to_serialize |
Dict |
Dictionary containing list of events. |
required |
Returns:
Type | Description |
---|---|
str |
Text block representing all precipitation events. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize_event_list(data_to_serialize: List[Dict]) -> str:
"""
Serializes a event list dictionary into a single text block.
Args:
data_to_serialize (Dict): Dictionary containing list of events.
Returns:
str: Text block representing all precipitation events.
"""
serialized_list = []
for n_event, event in enumerate(data_to_serialize):
event["event_idx"] = n_event + 1
serialized_list.append(BuiEventSerializer.serialize(event))
return "\n".join(serialized_list)
serialize_stations_ids(data_to_serialize: List[str]) -> str
staticmethod
¶
Serializes the stations ids into a single string as expected in a .bui file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data_to_serialize |
List[str] |
List of station ids. |
required |
Returns:
Type | Description |
---|---|
str |
Serialized string. |
Source code in hydrolib/core/io/rr/meteo/serializer.py
@staticmethod
def serialize_stations_ids(data_to_serialize: List[str]) -> str:
"""
Serializes the stations ids into a single string as expected in a .bui file.
Args:
data_to_serialize (List[str]): List of station ids.
Returns:
str: Serialized string.
"""
return str.join(" ", data_to_serialize)
write_bui_file(path: Path, data: Dict) -> None
¶
Writes a .bui file in the given path based on the data given in a dictionary.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
path |
Path |
Path where to output the text. |
required |
data |
Dict |
Data to serialize into the file. |
required |
Source code in hydrolib/core/io/rr/meteo/serializer.py
def write_bui_file(path: Path, data: Dict) -> None:
"""
Writes a .bui file in the given path based on the data given in a dictionary.
Args:
path (Path): Path where to output the text.
data (Dict): Data to serialize into the file.
"""
data["filepath"] = path # This is redundant as already exists in the data.
serialized_bui_data = BuiSerializer.serialize(data)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(serialized_bui_data, encoding="utf8")