Coverage for aixweather/core_data_format_2_output_file/to_mos_TMY3.py: 100%
47 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-01-06 16:01 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-01-06 16:01 +0000
1"""
2Converts core data to modelica TMY3Reader data
3"""
4import logging
5import calendar
6import datetime as dt
7import pandas as pd
9from aixweather import definitions
10from aixweather.imports.utils_import import MetaData
11from aixweather.transformation_functions import auxiliary, time_observation_transformations, pass_through_handling
14logger = logging.getLogger(__name__)
16"""
17format_modelica_TMY3 information:
19Format info:
20key = output data point name
21core_name = corresponding name matching the format_core_data
22time_of_meas_shift = desired 30min shifting+interpolation to convert the value that is "at
23indicated time" to "average of preceding hour" (ind2prec).
24unit = unit of the output data following the naming convention of format_core_data
25nan = The default values stated from the AixLib TMY3 Reader, those values are
26filled if nan.
28All changes here automatically change the calculations.
29Exception: unit conversions have to be added manually.
31time_of_meas_shift´s checked by Martin Rätz (07.08.2023)
32unit´s checked by Martin Rätz (07.08.2023)
33"""
34format_modelica_TMY3 = {
35 'timeOfYear': {'core_name': '', 'unit': 's', 'time_of_meas_shift': None, 'nan': None},
36 'DryBulbTemp': {'core_name': 'DryBulbTemp', 'unit': 'degC', 'time_of_meas_shift': None, 'nan': 20.0},
37 'DewPointTemp': {'core_name': 'DewPointTemp', 'unit': 'degC', 'time_of_meas_shift': None, 'nan': 10.0},
38 'RelHum': {'core_name': 'RelHum', 'unit': 'percent', 'time_of_meas_shift': None, 'nan': 50},
39 'AtmPressure': {'core_name': 'AtmPressure', 'unit': 'Pa', 'time_of_meas_shift': None, 'nan': 101325},
40 'ExtHorRad': {'core_name': 'ExtHorRad', 'unit': 'Wh/m2', 'time_of_meas_shift': 'ind2prec', 'nan': '-0'},
41 'ExtDirNormRad': {'core_name': 'ExtDirNormRad', 'unit': 'Wh/m2', 'time_of_meas_shift': 'ind2prec', 'nan': '-0'},
42 'HorInfra': {'core_name': 'HorInfra', 'unit': 'Wh/m2', 'time_of_meas_shift': 'ind2prec', 'nan': 0},
43 'GlobHorRad': {'core_name': 'GlobHorRad', 'unit': 'Wh/m2', 'time_of_meas_shift': 'ind2prec', 'nan': 0},
44 'DirNormRad': {'core_name': 'DirNormRad', 'unit': 'Wh/m2', 'time_of_meas_shift': 'ind2prec', 'nan': 0},
45 'DiffHorRad': {'core_name': 'DiffHorRad', 'unit': 'Wh/m2', 'time_of_meas_shift': 'ind2prec', 'nan': 0},
46 'GlobHorIll': {'core_name': 'GlobHorIll', 'unit': 'lux', 'time_of_meas_shift': 'ind2prec', 'nan': '-0'},
47 'DirecNormIll': {'core_name': 'DirecNormIll', 'unit': 'lux', 'time_of_meas_shift': 'ind2prec', 'nan': '-0'},
48 'DiffuseHorIll': {'core_name': 'DiffuseHorIll', 'unit': 'lux', 'time_of_meas_shift': 'ind2prec', 'nan': '-0'},
49 'ZenithLum': {'core_name': 'ZenithLum', 'unit': 'Cd/m2', 'time_of_meas_shift': 'ind2prec', 'nan': '-0'},
50 'WindDir': {'core_name': 'WindDir', 'unit': 'deg', 'time_of_meas_shift': None, 'nan': '-0'},
51 'WindSpeed': {'core_name': 'WindSpeed', 'unit': 'm/s', 'time_of_meas_shift': None, 'nan': '-0'},
52 'TotalSkyCover': {'core_name': 'TotalSkyCover', 'unit': '1tenth', 'time_of_meas_shift': None, 'nan': 5},
53 'OpaqueSkyCover': {'core_name': 'OpaqueSkyCover', 'unit': '1tenth', 'time_of_meas_shift': None, 'nan': 5},
54 'Visibility': {'core_name': 'Visibility', 'unit': 'km', 'time_of_meas_shift': None, 'nan': '-0'},
55 'CeilingH': {'core_name': 'CeilingH', 'unit': 'm', 'time_of_meas_shift': None, 'nan': 20000.0},
56 'WeatherObs': {'core_name': '', 'unit': '', 'time_of_meas_shift': None, 'nan': '-0'},
57 'WeatherCode': {'core_name': '', 'unit': '', 'time_of_meas_shift': None, 'nan': '-0'},
58 'PrecWater': {'core_name': 'PrecWater', 'unit': 'mm', 'time_of_meas_shift': None, 'nan': '-0'},
59 'Aerosol': {'core_name': 'Aerosol', 'unit': '1thousandth', 'time_of_meas_shift': None, 'nan': '-0'},
60 'Snow': {'core_name': '', 'unit': 'cm', 'time_of_meas_shift': None, 'nan': '-0'},
61 'DaysSinceSnow': {'core_name': '', 'unit': 'days', 'time_of_meas_shift': None, 'nan': '-0'},
62 'Albedo': {'core_name': '', 'unit': '', 'time_of_meas_shift': None, 'nan': '-0'},
63 'LiquidPrecD': {'core_name': 'LiquidPrecD', 'unit': 'mm/h', 'time_of_meas_shift': None, 'nan': '-0'},
64 'LiquidPrepQuant': {'core_name': '', 'unit': '', 'time_of_meas_shift': None, 'nan': '-0'}
65}
68def to_mos(
69 core_df: pd.DataFrame,
70 meta: MetaData,
71 start: dt.datetime,
72 stop: dt.datetime,
73 fillna: bool,
74 result_folder: str = None,
75 filename: str = None
76) -> (pd.DataFrame, str):
77 """Create a MOS file from the core data.
79 Args:
80 core_df (pd.DataFrame): DataFrame containing core data.
81 meta (MetaData): Metadata associated with the weather data.
82 start (dt.datetime): Timestamp for the start of the MOS file.
83 stop (dt.datetime): Timestamp for the end of the MOS file.
84 fillna (bool): Boolean indicating whether NaN values should be filled.
85 result_folder (str):
86 Path to the folder where to save the file. Default will use
87 the `results_file_path` method.
88 filename (str): Name of the file to be saved. The default is constructed
89 based on the meta-data as well as start and stop time
91 Returns:
92 pd.DataFrame: DataFrame containing the weather data formatted for MOS export,
93 excluding metadata.
94 str: Path to the exported file.
95 """
96 ### evaluate correctness of format
97 auxiliary.evaluate_transformations(
98 core_format=definitions.format_core_data, other_format=format_modelica_TMY3
99 )
101 df = core_df.copy()
103 ### measurement time conversion
104 df = time_observation_transformations.shift_time_by_dict(format_modelica_TMY3, df)
106 ### if possible avoid back and forth interpolating -> pass through variables without shifting
107 df = pass_through_handling.pass_through_measurements_with_back_and_forth_interpolating(
108 df, format_modelica_TMY3
109 )
111 ### select only desired period
112 df = time_observation_transformations.truncate_data_from_start_to_stop(
113 df, start, stop
114 )
116 ### select the desired columns
117 df = auxiliary.force_data_variable_convention(df, format_modelica_TMY3)
119 # tmy3 data must start with 0 at the beginning of year (due to internal
120 # tmy3_reader sun inclination calculation)
121 min_year = min(df.index.year)
122 time_of_year = (
123 (df.index.year - min_year) * 365 * 24 * 3600
124 + calendar.leapdays(min_year, df.index.year) * 24 * 3600
125 + (df.index.dayofyear - 1) * 24 * 3600
126 + df.index.hour * 3600
127 )
128 df["timeOfYear"] = time_of_year
130 # to avoid having the one-year duration between start and end of data as
131 # it will make the tmy3_reader loop the data
132 if (df["timeOfYear"].iloc[-1] - df["timeOfYear"].iloc[0]) == 365 * 24 * 3600:
133 # copy last row
134 last_row = df.iloc[-1]
136 # Convert the Series to a new DataFrame with a single row
137 new_row_df = last_row.to_frame().T
139 # continue time values
140 new_row_df["timeOfYear"].iloc[0] = new_row_df["timeOfYear"].iloc[0] + 3600
141 new_row_df.index = new_row_df.index + dt.timedelta(hours=1)
143 # add new row to df, make sure its int
144 df = pd.concat([df, new_row_df])
145 df["timeOfYear"] = df["timeOfYear"].astype(int)
147 ### fill nan
148 if fillna:
149 # fill first and last lines nans (possibly lost through shifting)
150 df.iloc[0, :] = df.bfill().iloc[0, :]
151 df.iloc[-1, :] = df.ffill().iloc[-1, :]
152 # fill dummy values
153 auxiliary.fill_nan_from_format_dict(df, format_modelica_TMY3)
155 ### Create header
156 header_of = (
157 "#1:for TMY3reader"
158 + "\ndouble tab1("
159 + str(int(df.index.size))
160 + ","
161 + str(int(df.columns.size))
162 + ")"
163 )
164 header_of += f"\n#LOCATION,{meta.station_name},,,,,{str(meta.latitude)}," \
165 f"{str(meta.longitude)},0,something"
166 header_of += (
167 "\n#Explanation of Location line:"
168 + "\n# Element 7: latitude"
169 + "\n# Element 8: longitude"
170 + "\n# Element 9: time zone in hours from UTC"
171 + "\n#"
172 )
174 # Data periods
175 header_of += (
176 "\n#DATA PERIODS, data available from "
177 + str(dt.datetime.strptime(str(df.index[0]), "%Y-%m-%d %H:%M:%S"))
178 + " "
179 + "(second="
180 + str(df["timeOfYear"].iloc[0])
181 + ") to "
182 + str(dt.datetime.strptime(str(df.index[-1]), "%Y-%m-%d %H:%M:%S"))
183 + " "
184 + "(second="
185 + str(df["timeOfYear"].iloc[-1])
186 + ")"
187 + "\n# info: TMY3Reader requirement: Time 0 = 01.01. 00:00:00 at local"
188 " time (see time zone above)"
189 + "\n#"
190 )
191 # data source
192 header_of += (
193 "\n#USED DATA-COLLECTOR: "
194 + "ebc-weather-tool with input source "
195 + str(meta.input_source)
196 + " (collected at "
197 + str(dt.datetime.now())
198 + ') "-0" marks not available data'
199 "\n#Info: The AixLib/IBPSA TMY3reader requires the below mentioned units and"
200 " measurement times. Last Check: 28.02.2022"
201 )
203 # Information about the data
204 header_of += (
205 "\n#C1 Time in seconds. Beginning of a year is 0s."
206 + "\n#C2 Dry bulb temperature in Celsius at indicated time"
207 + "\n#C3 Dew point temperature in Celsius at indicated time"
208 + "\n#C4 Relative humidity in percent at indicated time"
209 + "\n#C5 Atmospheric station pressure in Pa at indicated time, TMY3Reader:"
210 " not used per default"
211 + "\n#C6 Extraterrestrial horizontal radiation in Wh/m2, TMY3Reader: not used"
212 + "\n#C7 Extraterrestrial direct normal radiation in Wh/m2, TMY3Reader: not used"
213 + "\n#C8 Horizontal infrared radiation intensity in Wh/m2"
214 + "\n#C9 Global horizontal radiation in Wh/m2"
215 + "\n#C10 Direct normal radiation in Wh/m2"
216 + "\n#C11 Diffuse horizontal radiation in Wh/m2"
217 + "\n#C12 Averaged global horizontal illuminance in lux during minutes preceding"
218 " the indicated time, TMY3Reader: not used"
219 + "\n#C13 Direct normal illuminance in lux during minutes preceding the"
220 " indicated time, TMY3Reader: not used"
221 + "\n#C14 Diffuse horizontal illuminance in lux during minutes preceding the"
222 " indicated time, TMY3Reader: not used"
223 + "\n#C15 Zenith luminance in Cd/m2 during minutes preceding the indicated"
224 " time, TMY3Reader: not used"
225 + "\n#C16 Wind direction at indicated time. N=0, E=90, S=180, W=270"
226 + "\n#C17 Wind speed in m/s at indicated time"
227 + "\n#C18 Total sky cover in tenth at indicated time"
228 + "\n#C19 Opaque sky cover tenth indicated time"
229 + "\n#C20 Visibility in km at indicated time, TMY3Reader: not used"
230 + "\n#C21 Ceiling height in m"
231 + "\n#C22 Present weather observation, TMY3Reader: not used"
232 + "\n#C23 Present weather codes, TMY3Reader: not used"
233 + "\n#C24 Precipitable water in mm, TMY3Reader: not used"
234 + "\n#C25 Aerosol optical depth, TMY3Reader: not used"
235 + "\n#C26 Snow depth in cm, TMY3Reader: not used"
236 + "\n#C27 Days since last snowfall, TMY3Reader: not used"
237 + "\n#C28 Albedo, TMY3Reader: not used"
238 + "\n#C29 Liquid precipitation depth in mm/h at indicated time, TMY3Reader: not used"
239 + "\n#C30 Liquid precipitation quantity, TMY3Reader: not used"
240 )
242 ### write to csv
243 if filename is None:
244 filename = (
245 f"{meta.station_id}_{start.strftime('%Y%m%d')}_{stop.strftime('%Y%m%d')}"
246 f"_{meta.station_name}.mos"
247 )
248 filepath = definitions.results_file_path(filename, result_folder)
250 df.to_csv(
251 filepath,
252 sep="\t",
253 float_format="%.2f",
254 header=False,
255 index_label="timeOfYear",
256 index=False,
257 )
259 # Read the contents and prepend the header_of to the file
260 with open(filepath, "r+") as file:
261 content = file.read()
262 file.seek(0, 0)
263 file.write(f"{header_of}\n{content}")
265 logger.info("MOS file saved to %s.", filepath)
266 return df, filepath