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

1""" 

2Converts core data to modelica TMY3Reader data 

3""" 

4import logging 

5import calendar 

6import datetime as dt 

7import pandas as pd 

8 

9from aixweather import definitions 

10from aixweather.imports.utils_import import MetaData 

11from aixweather.transformation_functions import auxiliary, time_observation_transformations, pass_through_handling 

12 

13 

14logger = logging.getLogger(__name__) 

15 

16""" 

17format_modelica_TMY3 information: 

18 

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. 

27 

28All changes here automatically change the calculations.  

29Exception: unit conversions have to be added manually. 

30 

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} 

66 

67 

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. 

78 

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 

90 

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 ) 

100 

101 df = core_df.copy() 

102 

103 ### measurement time conversion 

104 df = time_observation_transformations.shift_time_by_dict(format_modelica_TMY3, df) 

105 

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 ) 

110 

111 ### select only desired period 

112 df = time_observation_transformations.truncate_data_from_start_to_stop( 

113 df, start, stop 

114 ) 

115 

116 ### select the desired columns 

117 df = auxiliary.force_data_variable_convention(df, format_modelica_TMY3) 

118 

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 

129 

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] 

135 

136 # Convert the Series to a new DataFrame with a single row 

137 new_row_df = last_row.to_frame().T 

138 

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) 

142 

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) 

146 

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) 

154 

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 ) 

173 

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 ) 

202 

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 ) 

241 

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) 

249 

250 df.to_csv( 

251 filepath, 

252 sep="\t", 

253 float_format="%.2f", 

254 header=False, 

255 index_label="timeOfYear", 

256 index=False, 

257 ) 

258 

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}") 

264 

265 logger.info("MOS file saved to %s.", filepath) 

266 return df, filepath