OpenSimula 0.5.1__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.
Files changed (44) hide show
  1. opensimula/Child.py +19 -0
  2. opensimula/Component.py +214 -0
  3. opensimula/Iterative_process.py +47 -0
  4. opensimula/Message.py +19 -0
  5. opensimula/Parameter_container.py +80 -0
  6. opensimula/Parameters.py +755 -0
  7. opensimula/Project.py +674 -0
  8. opensimula/Simulation.py +212 -0
  9. opensimula/Variable.py +37 -0
  10. opensimula/__init__.py +8 -0
  11. opensimula/components/Building.py +511 -0
  12. opensimula/components/Building_surface.py +318 -0
  13. opensimula/components/Calculator.py +51 -0
  14. opensimula/components/Construction.py +309 -0
  15. opensimula/components/Day_schedule.py +64 -0
  16. opensimula/components/File_data.py +117 -0
  17. opensimula/components/File_met.py +1035 -0
  18. opensimula/components/Frame.py +35 -0
  19. opensimula/components/Glazing.py +122 -0
  20. opensimula/components/HVAC_DX_equipment.py +210 -0
  21. opensimula/components/HVAC_DX_system.py +254 -0
  22. opensimula/components/HVAC_MZW_system.py +554 -0
  23. opensimula/components/HVAC_SZW_system.py +338 -0
  24. opensimula/components/HVAC_coil_equipment.py +206 -0
  25. opensimula/components/HVAC_fan_equipment.py +44 -0
  26. opensimula/components/HVAC_perfect_system.py +137 -0
  27. opensimula/components/Material.py +14 -0
  28. opensimula/components/Opening.py +409 -0
  29. opensimula/components/Opening_type.py +90 -0
  30. opensimula/components/Solar_surface.py +114 -0
  31. opensimula/components/Space.py +388 -0
  32. opensimula/components/Space_type.py +81 -0
  33. opensimula/components/Surface.py +129 -0
  34. opensimula/components/Test_component.py +76 -0
  35. opensimula/components/Week_schedule.py +30 -0
  36. opensimula/components/Year_schedule.py +65 -0
  37. opensimula/components/__init__.py +40 -0
  38. opensimula/visual_3D/Environment_3D.py +257 -0
  39. opensimula/visual_3D/Polygon_3D.py +262 -0
  40. opensimula-0.5.1.dist-info/METADATA +49 -0
  41. opensimula-0.5.1.dist-info/RECORD +44 -0
  42. opensimula-0.5.1.dist-info/WHEEL +5 -0
  43. opensimula-0.5.1.dist-info/licenses/LICENSE +21 -0
  44. opensimula-0.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1035 @@
1
+ import numpy as np
2
+ import datetime as dt
3
+ import math
4
+ import re # Regular Expressions
5
+ import pandas as pd
6
+ import psychrolib as sicro
7
+ from opensimula.Message import Message
8
+ from opensimula.Parameters import Parameter_string, Parameter_options
9
+ from opensimula.Component import Component
10
+ from opensimula.Variable import Variable
11
+
12
+
13
+ class File_met(Component):
14
+ def __init__(self, name, project):
15
+ Component.__init__(self, name, project)
16
+ self.parameter("type").value = "File_met"
17
+ self.parameter("description").value = "Meteorological file"
18
+ # Parameters
19
+ self.add_parameter(Parameter_string("file_name", "name.met"))
20
+ self.add_parameter(Parameter_options(
21
+ "file_type", "MET", ["MET", "TMY3", "TMY2", "WYEC2"]))
22
+ self.add_parameter(Parameter_options(
23
+ "tilted_diffuse_model", "PEREZ", ["REINDL", "HAY-DAVIES", "ISOTROPIC", "PEREZ"]))
24
+
25
+ # Variables
26
+ self.add_variable(
27
+ Variable("sol_hour", "h", "Solar hour of the day (calculated)"))
28
+ self.add_variable(
29
+ Variable("temperature", "°C", "Dry bulb temperature"))
30
+ self.add_variable(Variable("sky_temperature", "°C",
31
+ "Sky temperature, for radiant heat exchange (read from MET files, calculated in TMY files)."))
32
+ self.add_variable(Variable("underground_temperature", "°C",
33
+ "Ground temperature, to be used as the temperature imposed on the outer surface of the enclosures in contact with the ground (currently not read from the file, it is calculated as the annual average air temperature)."))
34
+ self.add_variable(
35
+ Variable("rel_humidity", "%", "Air relative humidity."))
36
+ self.add_variable(Variable("abs_humidity", "g/kg",
37
+ "Air absolute humidity (calculated)."))
38
+ self.add_variable(Variable("dew_point_temp", "°C",
39
+ "Dew point air temperature (calculated)."))
40
+ self.add_variable(Variable("wet_bulb_temp", "°C",
41
+ "Wet bulb air temperature (calculated)."))
42
+ self.add_variable(Variable("sol_direct", "W/m²",
43
+ "Direct solar irradiance over horizontal surface."))
44
+ self.add_variable(Variable("sol_diffuse", "W/m²",
45
+ "Diffuse solar irradiance over horizontal surface."))
46
+ self.add_variable(Variable("wind_speed", "m/s", "Wind speed."))
47
+ self.add_variable(Variable("wind_direction", "°",
48
+ "Wind direction (degrees from north: E+, W-)."))
49
+ self.add_variable(Variable(
50
+ "sol_azimuth", "°", "Solar azimuth (degrees from south: E-, W+) (calculated)."))
51
+ self.add_variable(Variable("sol_altitude", "°",
52
+ "Solar altitude (degrees) (calculated)."))
53
+ self.add_variable(Variable(
54
+ "pressure", "Pa", " Ambient absolute pressure (read from TMY files, calculated using standard atmosphere for MET files)."))
55
+ self.add_variable(Variable("total_cloud_cover", "%",
56
+ "Percentage of the sky covered by all the visible clouds (read from TMY files, 0 for MET files)."))
57
+ self.add_variable(Variable("opaque_cloud_cover", "%",
58
+ "Percentage of the sky covered, used for infrared radiation an sky temperature estimation (read from TMY files, 0 for MET files)."))
59
+
60
+ # Las variables leidas las guardamos en numpy arrays
61
+ self.temperature = np.zeros(8760)
62
+ self.sky_temperature = np.zeros(8760)
63
+ self.sol_direct = np.zeros(8760)
64
+ self.sol_diffuse = np.zeros(8760)
65
+ self.rel_humidity = np.zeros(8760)
66
+ self.wind_speed = np.zeros(8760)
67
+ self.wind_direction = np.zeros(8760)
68
+ self.pressure = np.zeros(8760)
69
+ self.total_cloud_cover = np.zeros(8760)
70
+ self.opaque_cloud_cover = np.zeros(8760)
71
+
72
+ def check(self):
73
+ errors = super().check()
74
+ sicro.SetUnitSystem(sicro.SI)
75
+ if self.parameter("file_type").value == "MET":
76
+ errors = self._read_met_file(errors)
77
+ elif self.parameter("file_type").value == "TMY3":
78
+ errors = self._read_tmy3_file(errors)
79
+ elif self.parameter("file_type").value == "TMY2":
80
+ errors = self._read_tmy2_file(errors)
81
+ elif self.parameter("file_type").value == "WYEC2":
82
+ self.latitude = 0
83
+ self.longitude = 0
84
+ self.altitude = 0
85
+ self.reference_time_longitude = 0
86
+ # These four values must be changed later
87
+ errors = self._read_wyec2_file(errors)
88
+ return errors
89
+
90
+ def _read_met_file(self, errors):
91
+ # Read the file
92
+ try:
93
+ f = open(self.parameter("file_name").value, "r")
94
+ except OSError as error:
95
+ msg = f"Error in component: {self.parameter('name').value}, could not open/read file: {self.parameter('file_name').value}"
96
+ errors.append(Message(msg, "ERROR"))
97
+ return errors
98
+ with f:
99
+ f.readline()
100
+ line = f.readline()
101
+ valores = line.split()
102
+ self.latitude = float(valores[0])
103
+ self.longitude = float(valores[1])
104
+ self.altitude = float(valores[2])
105
+ self.reference_time_longitude = float(valores[3])
106
+ for t in range(8760):
107
+ line = f.readline()
108
+ valores = line.split()
109
+ self.temperature[t] = float(valores[3])
110
+ self.sky_temperature[t] = float(valores[4])
111
+ self.sol_direct[t] = float(valores[5])
112
+ self.sol_diffuse[t] = float(valores[6])
113
+ self.rel_humidity[t] = float(valores[8])
114
+ self.wind_speed[t] = float(valores[9])
115
+ self.wind_direction[t] = float(valores[10])
116
+ # Atmosfera estándar con T = 20ºC
117
+ self.pressure[t] = 101325 * math.exp(-1.1654e-4*self.altitude)
118
+
119
+ self._T_average = np.average(self.temperature)
120
+ return errors
121
+
122
+ def _read_tmy3_file(self, errors):
123
+ # Read the file
124
+ try:
125
+ data, metadata = read_tmy3(self.parameter("file_name").value)
126
+ self.latitude = metadata["latitude"]
127
+ self.longitude = metadata["longitude"]
128
+ self.altitude = metadata["altitude"]
129
+ self.reference_time_longitude = metadata["TZ"]*15
130
+
131
+ self.temperature = data["Dry-bulb (C)"].to_numpy()
132
+ self.sol_diffuse = data["DHI (W/m^2)"].to_numpy()
133
+ self.sol_direct = data["GHI (W/m^2)"].to_numpy() - self.sol_diffuse
134
+ self.rel_humidity = data["RHum (%)"].to_numpy()
135
+
136
+ self.wind_speed = data["Wspd (m/s)"].to_numpy()
137
+ self.wind_direction = data["Wdir (degrees)"].to_numpy()
138
+ # milibar to Pa
139
+ self.pressure = data["Pressure (mbar)"].to_numpy() * 100
140
+ self.total_cloud_cover = data["TotCld (tenths)"].to_numpy(
141
+ ) * 10 # tenth to %
142
+ self.opaque_cloud_cover = data["OpqCld (tenths)"].to_numpy(
143
+ ) * 10 # tenth to %
144
+ self._t_sky_calculation()
145
+ self._T_average = np.average(self.temperature)
146
+ return errors
147
+ except OSError as error:
148
+ msg =f"Error in component: {self.parameter('name').value}, could not open/read file: {self.parameter('file_name').value}"
149
+ errors.append(Message(msg, "ERROR"))
150
+ return errors
151
+
152
+ def _read_tmy2_file(self, errors):
153
+ # Read the file
154
+ try:
155
+ data, metadata = read_tmy2(self.parameter("file_name").value)
156
+ self.latitude = metadata["latitude"]
157
+ self.longitude = metadata["longitude"]
158
+ self.altitude = metadata["altitude"]
159
+ self.reference_time_longitude = metadata["TZ"]*15
160
+
161
+ self.temperature = data["DryBulb"].to_numpy()/10
162
+ self.sol_diffuse = data["DHI"].to_numpy()
163
+ self.sol_direct = data["GHI"].to_numpy() - self.sol_diffuse
164
+ self.sol_direct[self.sol_direct < 0] = 0 # Eliminate negative values if exist
165
+ self.rel_humidity = data["RHum"].to_numpy()
166
+
167
+ self.wind_speed = data["Wspd"].to_numpy()/10
168
+ self.wind_direction = data["Wdir"].to_numpy()
169
+ # milibar to Pa
170
+ self.pressure = data["Pressure"].to_numpy() * 100
171
+ self.total_cloud_cover = data["TotCld"].to_numpy(
172
+ ) * 10 # tenth to %
173
+ self.opaque_cloud_cover = data["OpqCld"].to_numpy(
174
+ ) * 10 # tenth to %
175
+ self._t_sky_calculation()
176
+ self._T_average = np.average(self.temperature)
177
+ return errors
178
+ except OSError as error:
179
+ msg = f"Error in component: {self.parameter('name').value}, could not open/read file: {self.parameter('file_name').value}"
180
+ errors.append(Message(msg, "ERROR"))
181
+ return errors
182
+
183
+ def _read_wyec2_file(self, errors):
184
+ # Read the file
185
+ try:
186
+ data = read_wyec2(self.parameter("file_name").value)
187
+ self.temperature = data["temperature"].to_numpy()
188
+ self.sol_diffuse = data["sol_diffuse"].to_numpy()
189
+ self.sol_direct = data["sol_direct"].to_numpy()
190
+ self.rel_humidity = data["rel_humidity"].to_numpy()
191
+ self.wind_speed = data["wind_speed"].to_numpy()
192
+ self.wind_direction = data["wind_direction"].to_numpy()
193
+ self.pressure = data["pressure"].to_numpy()
194
+ self.total_cloud_cover = data["total_cloud_cover"].to_numpy()
195
+ self.opaque_cloud_cover = data["opaque_cloud_cover"].to_numpy()
196
+ self._t_sky_calculation()
197
+ self._T_average = np.average(self.temperature)
198
+ return errors
199
+ except OSError as error:
200
+ msg = f"Error in component: {self.parameter('name').value}, could not open/read file: {self.parameter('file_name').value}"
201
+ errors.append(Message(msg, "ERROR"))
202
+ return errors
203
+
204
+ def set_location(self, latitude, longitude, altitude, reference_time_longitude) :
205
+ self.latitude = latitude
206
+ self.longitude = longitude
207
+ self.altitude = altitude
208
+ self.reference_time_longitude = reference_time_longitude
209
+
210
+ def pre_simulation(self, n_time_steps, delta_t):
211
+ super().pre_simulation(n_time_steps, delta_t)
212
+
213
+ def pre_iteration(self, time_index, date, daylight_saving):
214
+ super().pre_iteration(time_index, date, daylight_saving)
215
+ # solar_hour = self._solar_hour_(date)
216
+ # azi, alt = self.solar_pos(date, solar_hour)
217
+ azi, alt, solar_hour = self.sunpos(date)
218
+
219
+ self.variable("sol_hour").values[time_index] = solar_hour
220
+ if alt < 3:
221
+ self.variable("sol_azimuth").values[time_index] = float("nan")
222
+ self.variable("sol_altitude").values[time_index] = float("nan")
223
+ else:
224
+ self.variable("sol_azimuth").values[time_index] = azi
225
+ self.variable("sol_altitude").values[time_index] = alt
226
+ self.variable(
227
+ "underground_temperature").values[time_index] = self._T_average
228
+ if self.parameter("file_type").value == "MET":
229
+ i, j, f = self._get_solar_interpolation_tuple_(date, solar_hour)
230
+ elif self.parameter("file_type").value == "TMY3":
231
+ i, j, f = self._get_local_interpolation_tuple_(date)
232
+ elif self.parameter("file_type").value == "TMY2":
233
+ i, j, f = self._get_local_interpolation_tuple_(date)
234
+ elif self.parameter("file_type").value == "WYEC2":
235
+ i, j, f = self._get_local_interpolation_tuple_(date)
236
+
237
+ self._interpolate("temperature", self.temperature, time_index, i, j, f)
238
+ self._interpolate("rel_humidity", self.rel_humidity,
239
+ time_index, i, j, f)
240
+ self._interpolate("sol_direct", self.sol_direct, time_index, i, j, f)
241
+ self._interpolate("sol_diffuse", self.sol_diffuse,
242
+ time_index, i, j, f)
243
+ self._interpolate("wind_speed", self.wind_speed,
244
+ time_index, i, j, f)
245
+ self._interpolate("wind_direction", self.wind_direction,
246
+ time_index, i, j, f)
247
+ self._interpolate("sky_temperature", self.sky_temperature,
248
+ time_index, i, j, f)
249
+ self._interpolate("pressure", self.pressure,
250
+ time_index, i, j, f)
251
+ self._interpolate("total_cloud_cover", self.total_cloud_cover,
252
+ time_index, i, j, f)
253
+ self._interpolate("opaque_cloud_cover", self.opaque_cloud_cover,
254
+ time_index, i, j, f)
255
+ # Corregir la directa si el sol no ha salido, y con alturas solares pequeñas
256
+ if (alt < 3 and self.variable("sol_direct").values[time_index] > 0):
257
+ self.variable(
258
+ "sol_diffuse").values[time_index] += self.variable("sol_direct").values[time_index]
259
+ self.variable("sol_direct").values[time_index] = 0
260
+ # calculate the rest of the psychrometric variables with T, HR and p
261
+ T = self.variable("temperature").values[time_index]
262
+ HR = self.variable("rel_humidity").values[time_index]/100
263
+ p = self.variable("pressure").values[time_index]
264
+ self.variable("abs_humidity").values[time_index] = sicro.GetHumRatioFromRelHum(
265
+ T, HR, p)*1000
266
+ self.variable("dew_point_temp").values[time_index] = sicro.GetTDewPointFromRelHum(
267
+ T, HR)
268
+ self.variable("wet_bulb_temp").values[time_index] = sicro.GetTWetBulbFromRelHum(
269
+ T, HR, p)
270
+
271
+ def _interpolate(self, variable, array, time_i, i, j, f):
272
+ self.variable(
273
+ variable).values[time_i] = array[i] * (1 - f) + array[j] * f
274
+
275
+ def _get_solar_interpolation_tuple_(self, datetime, solar_hour):
276
+ day = datetime.timetuple().tm_yday # Day of the year
277
+ if (solar_hour - datetime.hour > 10): # solar hour from previous day
278
+ day = day - 1
279
+ elif (solar_hour - datetime.hour < -10): # solar hour next day
280
+ day = day + 1
281
+ # El primer valor es a las 00:30
282
+ index = solar_hour + (day-1)*24
283
+ if index < 0:
284
+ index = 0
285
+ elif index >= 8760:
286
+ index = 8760
287
+ i = math.floor(index)
288
+ j = i + 1
289
+ if j >= 8760:
290
+ j = 8760
291
+ f = index - i
292
+ return (i, j, f)
293
+
294
+ def _get_local_interpolation_tuple_(self, date):
295
+ # a las 0:30 del primer día
296
+ initial_date = dt.datetime(date.year, 1, 1, 0, 30)
297
+ seconds = (date-initial_date).total_seconds()
298
+ index = seconds / 3600
299
+ if index < 0:
300
+ index = 0
301
+ elif index >= 8760:
302
+ index = index - 8760
303
+ i = math.floor(index)
304
+ j = i + 1
305
+ if j >= 8760:
306
+ j = j - 8760
307
+ f = index - i
308
+ return (i, j, f)
309
+
310
+ def _t_sky_calculation(self):
311
+ """Calculation of Sky Temperature using the Clark & Allen correlaton (1978) and the correlation of Walton (1983)
312
+ """
313
+ SIGMA = 5.6697E-8
314
+ for i in range(len(self.temperature)):
315
+ dp_temp = sicro.GetTDewPointFromRelHum(
316
+ self.temperature[i], self.rel_humidity[i]/100)
317
+ epsilon_clear = 0.787 + 0.764 * \
318
+ math.log((dp_temp+273.15)/273) # Clark & Allen
319
+ N = self.opaque_cloud_cover[i]/10 # opaque cover sky in tenths
320
+ epsilon = epsilon_clear * \
321
+ (1+0.0224*N-0.0035*N**2+0.00028*N**3) # Walton
322
+ ir = SIGMA * epsilon * (self.temperature[i] + 273.15)**4
323
+ self.sky_temperature[i] = (ir/SIGMA)**0.25 - 273.15
324
+
325
+ def solar_direct_rad(self, time_index, surf_azimuth, surf_altitude):
326
+ """Solar Direct radiation over surface
327
+
328
+ Args:
329
+ time_index (int): Simulation time index
330
+ surf_azimuth (float): Surface azimuth
331
+ surf_altitude (_type_): Surface altitude
332
+
333
+ Returns:
334
+ float: Solar direct radiation over surface (W/m^2)
335
+ """
336
+ sol_direct = self.variable("sol_direct").values[time_index]
337
+ if sol_direct == 0:
338
+ return 0
339
+ theta = self.solar_surface_angle(
340
+ time_index, surf_azimuth, surf_altitude)
341
+ sol_altitude = self.variable("sol_altitude").values[time_index]
342
+ if theta is not None:
343
+ return sol_direct * math.cos(theta) / math.sin(math.radians(sol_altitude))
344
+ else:
345
+ return 0
346
+
347
+ def solar_diffuse_rad(self, time_index, surf_azimuth, surf_altitude):
348
+ """Solar Diffuse radiation over surface
349
+
350
+ Args:
351
+ time_index (int): Simulation time index
352
+ surf_azimuth (float): Surface azimuth
353
+ surf_altitude (_type_): Surface altitude
354
+
355
+ Returns:
356
+ float: Solar diffuse radiation over surface (W/m^2)
357
+ """
358
+ sol_diffuse = self.variable("sol_diffuse").values[time_index]
359
+ if (sol_diffuse == 0):
360
+ return 0
361
+ model = self.parameter("tilted_diffuse_model").value
362
+ beta = math.radians(surf_altitude)
363
+ if (model == "ISOTROPIC"):
364
+ # Isotropic diffuse radiation (Liu-Jordan model)
365
+ E_dif = sol_diffuse * (1 + math.sin(beta))/2
366
+ elif (model == "HAY-DAVIES"):
367
+ theta = self.solar_surface_angle(
368
+ time_index, surf_azimuth, surf_altitude)
369
+ if theta is not None:
370
+ sol_direct = self.variable("sol_direct").values[time_index]
371
+ sol_altitude = math.radians(self.variable(
372
+ "sol_altitude").values[time_index])
373
+ A = sol_direct/math.sin(sol_altitude)/1353
374
+ R_b = math.cos(theta) / math.sin(sol_altitude)
375
+ E_dif = sol_diffuse * A * R_b + sol_diffuse * \
376
+ (1-A) * (1 + math.sin(beta))/2
377
+ else:
378
+ E_dif = sol_diffuse * (1 + math.sin(beta))/2
379
+ elif (model == "REINDL"):
380
+ theta = self.solar_surface_angle(
381
+ time_index, surf_azimuth, surf_altitude)
382
+ sol_altitude = math.radians(self.variable(
383
+ "sol_altitude").values[time_index])
384
+ sol_direct = self.variable("sol_direct").values[time_index]
385
+ zenit = math.pi/2 - beta
386
+ f_horizon = 1 + \
387
+ math.sqrt(sol_direct/(sol_direct+sol_diffuse)) * \
388
+ (math.sin(zenit/2))**3
389
+ if theta is not None:
390
+ A = sol_direct/math.sin(sol_altitude)/1353
391
+ R_b = math.cos(theta) / math.sin(sol_altitude)
392
+ E_dif = sol_diffuse * A * R_b + sol_diffuse * \
393
+ (1-A) * (1 + math.sin(beta))/2 * f_horizon
394
+ else:
395
+ E_dif = sol_diffuse * (1 + math.sin(beta))/2 * f_horizon
396
+ elif (model == "PEREZ"):
397
+ theta = self.solar_surface_angle(
398
+ time_index, surf_azimuth, surf_altitude)
399
+ if theta is None: # No direct sun
400
+ E_dif = sol_diffuse * (1 + math.sin(beta))/2
401
+ else:
402
+ sol_altitude = math.radians(self.variable(
403
+ "sol_altitude").values[time_index])
404
+ sol_direct = self.variable("sol_direct").values[time_index]
405
+ zenit = math.pi/2 - beta
406
+ sol_zenit = math.pi/2 - sol_altitude
407
+ epsilon = ((sol_direct/math.sin(sol_altitude)+sol_diffuse) /
408
+ sol_diffuse + 1.041 * sol_zenit**3)/(1 + 1.041*sol_zenit**3)
409
+ m = 1 / math.cos(sol_zenit)
410
+ Delta = m*sol_diffuse/1353
411
+ f = self._perez_coef(epsilon)
412
+ F_2 = f[3]+f[4]*Delta+f[5]*sol_zenit
413
+ a = math.cos(theta)
414
+ b = max(0.087156, math.cos(sol_zenit))
415
+ F_1 = max(0, f[0]+f[1]*Delta+f[2]*sol_zenit)
416
+ E_dif = sol_diffuse * \
417
+ ((1-F_1)*(1+math.cos(zenit))/2 +
418
+ F_1 * a/b + F_2 * math.sin(zenit))
419
+ return E_dif
420
+
421
+ def _perez_coef(self, epsilon):
422
+ if epsilon > 1 and epsilon <= 1.065:
423
+ return [-0.0083117, 0.5877285, -0.0620636, -0.0596012, 0.0721249, -0.0220216]
424
+ elif epsilon > 1.065 and epsilon <= 1.23:
425
+ return [0.1299457, 0.6825954, -0.1513752, -0.0189325, 0.0659650, -0.0288748]
426
+ elif epsilon > 1.23 and epsilon <= 1.5:
427
+ return [0.3296958, 0.4868735, -0.2210958, 0.055414, -0.0639588, -0.0260542]
428
+ elif epsilon > 1.5 and epsilon <= 1.95:
429
+ return [0.5682053, 0.1874525, -0.295129, 0.1088631, -0.1519229, -0.0139754]
430
+ elif epsilon > 1.95 and epsilon <= 2.8:
431
+ return [0.873028, -0.3920403, -0.3616149, 0.2255647, -0.4620442, 0.0012448]
432
+ elif epsilon > 2.8 and epsilon <= 4.5:
433
+ return [1.1326077, -1.2367284, -0.4118494, 0.2877813, -0.8230357, 0.0558651]
434
+ elif epsilon > 4.5 and epsilon <= 6.2:
435
+ return [1.0601591, -1.5999137, -0.3589221, 0.2642124, -1.127234, 0.1310694]
436
+ elif epsilon > 6.2:
437
+ return [0.677747, -0.3272588, -0.2504286, 0.1561313, -1.3765031, 0.2506212]
438
+
439
+ def solar_surface_angle(self, time_index, surf_azimuth, surf_altitude):
440
+ """Relative angle between surface exterior normal and the sum
441
+
442
+ Args:
443
+ time_index (int): _description_
444
+ surf_azimuth (float): _description_
445
+ surf_altitude (float): _description_
446
+
447
+ Returns:
448
+ float: Angle in radians
449
+ """
450
+ azi_sur = math.radians(surf_azimuth)
451
+ alt_sur = math.radians(surf_altitude)
452
+ sol_direct = self.variable("sol_direct").values[time_index]
453
+ azi_sol = math.radians(self.variable("sol_azimuth").values[time_index])
454
+ alt_sol = math.radians(self.variable(
455
+ "sol_altitude").values[time_index])
456
+ if sol_direct > 0:
457
+ cos = math.cos(azi_sol)*math.cos(alt_sol) * math.cos(azi_sur) * math.cos(alt_sur) + math.sin(azi_sol) * \
458
+ math.cos(alt_sol) * math.sin(azi_sur) * \
459
+ math.cos(alt_sur) + math.sin(alt_sol) * math.sin(alt_sur)
460
+ if cos > .05: # angle < 87.13, problems with angles near to 90º
461
+ return math.acos(cos)
462
+ else:
463
+ return None
464
+ else:
465
+ return None
466
+
467
+ def sunpos(self, date):
468
+ # Extract the passed data
469
+ year = date.year
470
+ month = date.month
471
+ day = date.day
472
+ hour = date.hour
473
+ minute = date.minute
474
+ second = date.second
475
+ # Math typing shortcuts
476
+ rad, deg = math.radians, math.degrees
477
+ sin, cos, tan = math.sin, math.cos, math.tan
478
+ asin, atan2 = math.asin, math.atan2
479
+ # Convert latitude and longitude to radians
480
+ rlat = rad(self.latitude)
481
+ rlon = rad(self.longitude)
482
+ # Decimal hour of the day at Greenwich
483
+ timezone = self.reference_time_longitude/15
484
+ greenwichtime = hour - timezone + minute / 60 + second / 3600
485
+ # Days from J2000, accurate from 1901 to 2099
486
+ daynum = (
487
+ 367 * year
488
+ - 7 * (year + (month + 9) // 12) // 4
489
+ + 275 * month // 9
490
+ + day
491
+ - 730531.5
492
+ + greenwichtime / 24
493
+ )
494
+ # Mean longitude of the sun
495
+ mean_long = daynum * 0.01720279239 + 4.894967873
496
+ # Mean anomaly of the Sun
497
+ mean_anom = daynum * 0.01720197034 + 6.240040768
498
+ # Ecliptic longitude of the sun
499
+ eclip_long = (
500
+ mean_long
501
+ + 0.03342305518 * sin(mean_anom)
502
+ + 0.0003490658504 * sin(2 * mean_anom)
503
+ )
504
+ # Obliquity of the ecliptic
505
+ obliquity = 0.4090877234 - 0.000000006981317008 * daynum
506
+ # Right ascension of the sun
507
+ rasc = atan2(cos(obliquity) * sin(eclip_long), cos(eclip_long))
508
+ # Declination of the sun
509
+ decl = asin(sin(obliquity) * sin(eclip_long))
510
+ # Local sidereal time
511
+ sidereal = 4.894961213 + 6.300388099 * daynum + rlon
512
+ # Hour angle of the sun
513
+ hour_ang = sidereal - rasc
514
+ # Local elevation of the sun
515
+ elevation = asin(sin(decl) * sin(rlat) + cos(decl)
516
+ * cos(rlat) * cos(hour_ang))
517
+ # Local azimuth of the sun
518
+ azimuth = atan2(
519
+ -cos(decl) * cos(rlat) * sin(hour_ang),
520
+ sin(decl) - sin(rlat) * sin(elevation),
521
+ )
522
+ # Convert azimuth and elevation to degrees
523
+ azimuth = math.pi-azimuth # South: 0, East 90
524
+ azimuth = self._into_range_(deg(azimuth), -180, 180)
525
+ elevation = self._into_range_(deg(elevation), -180, 180)
526
+ # Refraction correction (optional)
527
+ targ = rad((elevation + (10.3 / (elevation + 5.11))))
528
+ elevation += (1.02 / tan(targ)) / 60
529
+
530
+ # Solar hour
531
+ hour_ang = self._into_range_(deg(hour_ang), -180, 180)
532
+ solar_hour = hour_ang/15 + 12
533
+ # Return azimuth and elevation in degrees
534
+ return (round(azimuth, 3), round(elevation, 3), round(solar_hour, 3))
535
+
536
+ def sun_cosines(self, date):
537
+ azi, alt, solar_hour = self.sunpos(date)
538
+ if alt < 0:
539
+ return []
540
+ else:
541
+ azi_rd = math.radians(azi)
542
+ alt_rd = math.radians(alt)
543
+ return np.array([math.cos(alt_rd)*math.sin(azi_rd), -math.cos(alt_rd)*math.cos(azi_rd), math.sin(alt_rd)])
544
+
545
+ def maximun_solar_angles(self):
546
+ azimuth = []
547
+ altitude = []
548
+ if (self.longitude > 0):
549
+ solstice = dt.datetime(2001, 6, 21)
550
+ else:
551
+ solstice = dt.datetime(2001, 6, 22)
552
+ for i in range(0, 23):
553
+ solstice = solstice.replace(hour=i, minute=30)
554
+ az, alt, hour = self.sunpos(solstice)
555
+ if alt < 0:
556
+ alt = 0
557
+ az = 0
558
+ azimuth.append(az)
559
+ altitude.append(alt)
560
+ return (max(azimuth), max(altitude))
561
+
562
+ def _into_range_(self, x, range_min, range_max):
563
+ shiftedx = x - range_min
564
+ delta = range_max - range_min
565
+ return (((shiftedx % delta) + delta) % delta) + range_min
566
+
567
+
568
+ def read_tmy3(filename, encoding=None):
569
+ """Read a TMY3 file into a pandas dataframe.
570
+
571
+ Note that values contained in the metadata dictionary are unchanged
572
+ from the TMY3 file (i.e. units are retained). In the case of any
573
+ discrepancies between this documentation and the TMY3 User's Manual
574
+ [1]_, the TMY3 User's Manual takes precedence.
575
+
576
+ The TMY3 files were updated in Jan. 2015. This function requires the
577
+ use of the updated files.
578
+
579
+ Parameters
580
+ ----------
581
+ filename : str
582
+ A relative file path or absolute file path.
583
+
584
+ encoding : str, optional
585
+ Encoding of the file. For files that contain non-UTF8 characters it may
586
+ be necessary to specify an alternative encoding, e.g., for
587
+ SolarAnywhere TMY3 files the encoding should be 'iso-8859-1'. Users
588
+ may also consider using the 'utf-8-sig' encoding.
589
+
590
+ Returns
591
+ -------
592
+ Tuple of the form (data, metadata).
593
+
594
+ data : DataFrame
595
+ A pandas dataframe with the columns described in the table
596
+ below. For more detailed descriptions of each component, please
597
+ consult the TMY3 User's Manual [1]_, especially tables 1-1
598
+ through 1-6.
599
+
600
+ metadata : dict
601
+ The site metadata available in the file.
602
+
603
+ Notes
604
+ -----
605
+ The returned structures have the following fields.
606
+
607
+ =============== ====== ===================
608
+ key format description
609
+ =============== ====== ===================
610
+ altitude Float site elevation
611
+ latitude Float site latitudeitude
612
+ longitude Float site longitudeitude
613
+ Name String site name
614
+ State String state
615
+ TZ Float UTC offset
616
+ USAF Int USAF identifier
617
+ =============== ====== ===================
618
+
619
+
620
+ ======================== ======================================================================================================================================================
621
+ field description
622
+ ======================== ======================================================================================================================================================
623
+ Index A pandas datetime index. NOTE, the index is timezone aware, and times are set to local standard time (daylight savings is not included)
624
+ ghi_extra Extraterrestrial horizontal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
625
+ dni_extra Extraterrestrial normal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
626
+ ghi Direct and diffuse horizontal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
627
+ GHI source See [1]_, Table 1-4
628
+ GHI uncert (%) Uncertainty based on random and bias error estimates see [2]_
629
+ dni Amount of direct normal radiation (modeled) recv'd during 60 mintues prior to timestamp, Wh/m^2
630
+ DNI source See [1]_, Table 1-4
631
+ DNI uncert (%) Uncertainty based on random and bias error estimates see [2]_
632
+ dhi Amount of diffuse horizontal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
633
+ DHI source See [1]_, Table 1-4
634
+ DHI uncert (%) Uncertainty based on random and bias error estimates see [2]_
635
+ GH illum (lx) Avg. total horizontal illuminance recv'd during the 60 minutes prior to timestamp, lx
636
+ GH illum source See [1]_, Table 1-4
637
+ GH illum uncert (%) Uncertainty based on random and bias error estimates see [2]_
638
+ DN illum (lx) Avg. direct normal illuminance recv'd during the 60 minutes prior to timestamp, lx
639
+ DN illum source See [1]_, Table 1-4
640
+ DN illum uncert (%) Uncertainty based on random and bias error estimates see [2]_
641
+ DH illum (lx) Avg. horizontal diffuse illuminance recv'd during the 60 minutes prior to timestamp, lx
642
+ DH illum source See [1]_, Table 1-4
643
+ DH illum uncert (%) Uncertainty based on random and bias error estimates see [2]_
644
+ Zenith lum (cd/m^2) Avg. luminance at the sky's zenith during the 60 minutes prior to timestamp, cd/m^2
645
+ Zenith lum source See [1]_, Table 1-4
646
+ Zenith lum uncert (%) Uncertainty based on random and bias error estimates see [1]_ section 2.10
647
+ TotCld (tenths) Amount of sky dome covered by clouds or obscuring phenonema at time stamp, tenths of sky
648
+ TotCld source See [1]_, Table 1-5
649
+ TotCld uncert (code) See [1]_, Table 1-6
650
+ OpqCld (tenths) Amount of sky dome covered by clouds or obscuring phenonema that prevent observing the sky at time stamp, tenths of sky
651
+ OpqCld source See [1]_, Table 1-5
652
+ OpqCld uncert (code) See [1]_, Table 1-6
653
+ temp_air Dry bulb temperature at the time indicated, deg C
654
+ Dry-bulb source See [1]_, Table 1-5
655
+ Dry-bulb uncert (code) See [1]_, Table 1-6
656
+ temp_dew Dew-point temperature at the time indicated, deg C
657
+ Dew-point source See [1]_, Table 1-5
658
+ Dew-point uncert (code) See [1]_, Table 1-6
659
+ relative_humidity Relatitudeive humidity at the time indicated, percent
660
+ RHum source See [1]_, Table 1-5
661
+ RHum uncert (code) See [1]_, Table 1-6
662
+ pressure Station pressure at the time indicated, 1 mbar
663
+ Pressure source See [1]_, Table 1-5
664
+ Pressure uncert (code) See [1]_, Table 1-6
665
+ wind_direction Wind direction at time indicated, degrees from north (360 = north; 0 = undefined,calm)
666
+ Wdir source See [1]_, Table 1-5
667
+ Wdir uncert (code) See [1]_, Table 1-6
668
+ wind_speed† Wind speed at the time indicated, meter/second
669
+ Wspd source See [1]_, Table 1-5
670
+ Wspd uncert (code) See [1]_, Table 1-6
671
+ Hvis (m) Distance to discernable remote objects at time indicated (7777=unlimited), meter
672
+ Hvis source See [1]_, Table 1-5
673
+ Hvis uncert (coe) See [1]_, Table 1-6
674
+ CeilHgt (m) Height of cloud base above local terrain (7777=unlimited), meter
675
+ CeilHgt source See [1]_, Table 1-5
676
+ CeilHgt uncert (code) See [1]_, Table 1-6
677
+ precipitable_water Total precipitable water contained in a column of unit cross section from earth to top of atmosphere, cm
678
+ Pwat source See [1]_, Table 1-5
679
+ Pwat uncert (code) See [1]_, Table 1-6
680
+ AOD The broadband aerosol optical depth per unit of air mass due to extinction by aerosol component of atmosphere, unitless
681
+ AOD source See [1]_, Table 1-5
682
+ AOD uncert (code) See [1]_, Table 1-6
683
+ albedo† The ratio of reflected solar irradiance to global horizontal irradiance, unitless
684
+ Alb source See [1]_, Table 1-5
685
+ Alb uncert (code) See [1]_, Table 1-6
686
+ Lprecip depth (mm) The amount of liquid precipitation observed at indicated time for the period indicated in the liquid precipitation quantity field, millimeter
687
+ Lprecip quantity (hr) The period of accumulatitudeion for the liquid precipitation depth field, hour
688
+ Lprecip source See [1]_, Table 1-5
689
+ Lprecip uncert (code) See [1]_, Table 1-6
690
+ PresWth (METAR code) Present weather code, see [2]_.
691
+ PresWth source Present weather code source, see [2]_.
692
+ PresWth uncert (code) Present weather code uncertainty, see [2]_.
693
+ ======================== ======================================================================================================================================================
694
+
695
+ .. admonition:: Midnight representation
696
+
697
+ The function is able to handle midnight represented as 24:00 (NREL TMY3
698
+ format, see [1]_) and as 00:00 (SolarAnywhere TMY3 format, see [3]_).
699
+
700
+ .. warning:: TMY3 irradiance data corresponds to the *previous* hour, so
701
+ the first index is 1AM, corresponding to the irradiance from midnight
702
+ to 1AM, and the last index is midnight of the *next* year. For example,
703
+ if the last index in the TMY3 file was 1988-12-31 24:00:00 this becomes
704
+ 1989-01-01 00:00:00.
705
+
706
+ References
707
+ ----------
708
+ .. [1] Wilcox, S and Marion, W. "Users Manual for TMY3 Data Sets".
709
+ NREL/TP-581-43156, Revised May 2008.
710
+ :doi:`10.2172/928611`
711
+ .. [2] Wilcox, S. (2007). National Solar Radiation Database 1991 2005
712
+ Update: Users Manual. 472 pp.; NREL Report No. TP-581-41364.
713
+ :doi:`10.2172/901864`
714
+ .. [3] `SolarAnywhere file formats
715
+ <https://www.solaranywhere.com/support/historical-data/file-formats/>`_
716
+ """ # noqa: E501
717
+ head = ['USAF', 'Name', 'State', 'TZ', 'latitude', 'longitude', 'altitude']
718
+
719
+ with open(str(filename), 'r', encoding=encoding) as fbuf:
720
+ # header information on the 1st line (0 indexing)
721
+ firstline = fbuf.readline()
722
+ # use pandas to read the csv file buffer
723
+ # header is actually the second line, but tell pandas to look for
724
+ data = pd.read_csv(fbuf, header=0)
725
+
726
+ meta = dict(zip(head, firstline.rstrip('\n').split(",")))
727
+ # convert metadata strings to numeric types
728
+ meta['altitude'] = float(meta['altitude'])
729
+ meta['latitude'] = float(meta['latitude'])
730
+ meta['longitude'] = float(meta['longitude'])
731
+ meta['TZ'] = float(meta['TZ'])
732
+ meta['USAF'] = int(meta['USAF'])
733
+
734
+ # get the date column as a pd.Series of numpy datetime64
735
+ data_ymd = pd.to_datetime(data['Date (MM/DD/YYYY)'], format='%m/%d/%Y')
736
+ # extract minutes
737
+ minutes = data['Time (HH:MM)'].str.split(':').str[1].astype(int)
738
+ # shift the time column so that midnite is 00:00 instead of 24:00
739
+ shifted_hour = data['Time (HH:MM)'].str.split(':').str[0].astype(int) % 24
740
+ # shift the dates at midnight (24:00) so they correspond to the next day.
741
+ # If midnight is specified as 00:00 do not shift date.
742
+ data_ymd[data['Time (HH:MM)'].str[:2] == '24'] += dt.timedelta(days=1) # noqa: E501
743
+ # NOTE: as of pandas>=0.24 the pd.Series.array has a month attribute, but
744
+ # in pandas-0.18.1, only DatetimeIndex has month, but indices are immutable
745
+ # so we need to continue to work with the panda series of dates `data_ymd`
746
+ data_index = pd.DatetimeIndex(data_ymd)
747
+ # use indices to check for a leap day and advance it to March 1st
748
+ leapday = (data_index.month == 2) & (data_index.day == 29)
749
+ data_ymd[leapday] += dt.timedelta(days=1)
750
+
751
+ # NOTE: as of pvlib-0.6.3, min req is pandas-0.18.1, so pd.to_timedelta
752
+ # unit must be in (D,h,m,s,ms,us,ns), but pandas>=0.24 allows unit='hour'
753
+ data.index = data_ymd + pd.to_timedelta(shifted_hour, unit='h') \
754
+ + pd.to_timedelta(minutes, unit='min')
755
+
756
+ data = data.tz_localize(int(meta['TZ'] * 3600))
757
+
758
+ return data, meta
759
+
760
+
761
+ def read_tmy2(filename):
762
+ """
763
+ Read a TMY2 file into a DataFrame.
764
+
765
+ Note that values contained in the DataFrame are unchanged from the
766
+ TMY2 file (i.e. units are retained). Time/Date and location data
767
+ imported from the TMY2 file have been modified to a "friendlier"
768
+ form conforming to modern conventions (e.g. N latitude is postive, E
769
+ longitude is positive, the "24th" hour of any day is technically the
770
+ "0th" hour of the next day). In the case of any discrepencies
771
+ between this documentation and the TMY2 User's Manual [1]_, the TMY2
772
+ User's Manual takes precedence.
773
+
774
+ Parameters
775
+ ----------
776
+ filename : str
777
+ A relative or absolute file path.
778
+
779
+ Returns
780
+ -------
781
+ Tuple of the form (data, metadata).
782
+
783
+ data : DataFrame
784
+ A dataframe with the columns described in the table below. For a
785
+ more detailed descriptions of each component, please consult the
786
+ TMY2 User's Manual [1]_, especially tables 3-1 through 3-6, and
787
+ Appendix B.
788
+
789
+ metadata : dict
790
+ The site metadata available in the file.
791
+
792
+ Notes
793
+ -----
794
+ The returned structures have the following fields.
795
+
796
+ ============= ==================================
797
+ key description
798
+ ============= ==================================
799
+ WBAN Site identifier code (WBAN number)
800
+ City Station name
801
+ State Station state 2 letter designator
802
+ TZ Hours from Greenwich
803
+ latitude Latitude in decimal degrees
804
+ longitude Longitude in decimal degrees
805
+ altitude Site elevation in meters
806
+ ============= ==================================
807
+
808
+ ============================ ==========================================================================================================================================================================
809
+ field description
810
+ ============================ ==========================================================================================================================================================================
811
+ index Pandas timeseries object containing timestamps
812
+ year
813
+ month
814
+ day
815
+ hour
816
+ ETR Extraterrestrial horizontal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
817
+ ETRN Extraterrestrial normal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
818
+ GHI Direct and diffuse horizontal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
819
+ GHISource See [1]_, Table 3-3
820
+ GHIUncertainty See [1]_, Table 3-4
821
+ DNI Amount of direct normal radiation (modeled) recv'd during 60 mintues prior to timestamp, Wh/m^2
822
+ DNISource See [1]_, Table 3-3
823
+ DNIUncertainty See [1]_, Table 3-4
824
+ DHI Amount of diffuse horizontal radiation recv'd during 60 minutes prior to timestamp, Wh/m^2
825
+ DHISource See [1]_, Table 3-3
826
+ DHIUncertainty See [1]_, Table 3-4
827
+ GHillum Avg. total horizontal illuminance recv'd during the 60 minutes prior to timestamp, units of 100 lux (e.g. value of 50 = 5000 lux)
828
+ GHillumSource See [1]_, Table 3-3
829
+ GHillumUncertainty See [1]_, Table 3-4
830
+ DNillum Avg. direct normal illuminance recv'd during the 60 minutes prior to timestamp, units of 100 lux
831
+ DNillumSource See [1]_, Table 3-3
832
+ DNillumUncertainty See [1]_, Table 3-4
833
+ DHillum Avg. horizontal diffuse illuminance recv'd during the 60 minutes prior to timestamp, units of 100 lux
834
+ DHillumSource See [1]_, Table 3-3
835
+ DHillumUncertainty See [1]_, Table 3-4
836
+ Zenithlum Avg. luminance at the sky's zenith during the 60 minutes prior to timestamp, units of 10 Cd/m^2 (e.g. value of 700 = 7,000 Cd/m^2)
837
+ ZenithlumSource See [1]_, Table 3-3
838
+ ZenithlumUncertainty See [1]_, Table 3-4
839
+ TotCld Amount of sky dome covered by clouds or obscuring phenonema at time stamp, tenths of sky
840
+ TotCldSource See [1]_, Table 3-5
841
+ TotCldUncertainty See [1]_, Table 3-6
842
+ OpqCld Amount of sky dome covered by clouds or obscuring phenonema that prevent observing the sky at time stamp, tenths of sky
843
+ OpqCldSource See [1]_, Table 3-5
844
+ OpqCldUncertainty See [1]_, Table 3-6
845
+ DryBulb Dry bulb temperature at the time indicated, in tenths of degree C (e.g. 352 = 35.2 C).
846
+ DryBulbSource See [1]_, Table 3-5
847
+ DryBulbUncertainty See [1]_, Table 3-6
848
+ DewPoint Dew-point temperature at the time indicated, in tenths of degree C (e.g. 76 = 7.6 C).
849
+ DewPointSource See [1]_, Table 3-5
850
+ DewPointUncertainty See [1]_, Table 3-6
851
+ RHum Relative humidity at the time indicated, percent
852
+ RHumSource See [1]_, Table 3-5
853
+ RHumUncertainty See [1]_, Table 3-6
854
+ Pressure Station pressure at the time indicated, 1 mbar
855
+ PressureSource See [1]_, Table 3-5
856
+ PressureUncertainty See [1]_, Table 3-6
857
+ Wdir Wind direction at time indicated, degrees from east of north (360 = 0 = north; 90 = East; 0 = undefined,calm)
858
+ WdirSource See [1]_, Table 3-5
859
+ WdirUncertainty See [1]_, Table 3-6
860
+ Wspd Wind speed at the time indicated, in tenths of meters/second (e.g. 212 = 21.2 m/s)
861
+ WspdSource See [1]_, Table 3-5
862
+ WspdUncertainty See [1]_, Table 3-6
863
+ Hvis Distance to discernable remote objects at time indicated (7777=unlimited, 9999=missing data), in tenths of kilometers (e.g. 341 = 34.1 km).
864
+ HvisSource See [1]_, Table 3-5
865
+ HvisUncertainty See [1]_, Table 3-6
866
+ CeilHgt Height of cloud base above local terrain (7777=unlimited, 88888=cirroform, 99999=missing data), in meters
867
+ CeilHgtSource See [1]_, Table 3-5
868
+ CeilHgtUncertainty See [1]_, Table 3-6
869
+ Pwat Total precipitable water contained in a column of unit cross section from Earth to top of atmosphere, in millimeters
870
+ PwatSource See [1]_, Table 3-5
871
+ PwatUncertainty See [1]_, Table 3-6
872
+ AOD The broadband aerosol optical depth (broadband turbidity) in thousandths on the day indicated (e.g. 114 = 0.114)
873
+ AODSource See [1]_, Table 3-5
874
+ AODUncertainty See [1]_, Table 3-6
875
+ SnowDepth Snow depth in centimeters on the day indicated, (999 = missing data).
876
+ SnowDepthSource See [1]_, Table 3-5
877
+ SnowDepthUncertainty See [1]_, Table 3-6
878
+ LastSnowfall Number of days since last snowfall (maximum value of 88, where 88 = 88 or greater days; 99 = missing data)
879
+ LastSnowfallSource See [1]_, Table 3-5
880
+ LastSnowfallUncertainty See [1]_, Table 3-6
881
+ PresentWeather See [1]_, Appendix B. Each string contains 10 numeric values. The string can be parsed to determine each of 10 observed weather metrics.
882
+ ============================ ==========================================================================================================================================================================
883
+
884
+ References
885
+ ----------
886
+ .. [1] Marion, W and Urban, K. "Wilcox, S and Marion, W. "User's Manual
887
+ for TMY2s". NREL 1995.
888
+ :doi:`10.2172/87130`
889
+ """ # noqa: E501
890
+ # paste in the column info as one long line
891
+ string = '%2d%2d%2d%2d%4d%4d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%2d%1s%1d%2d%1s%1d%4d%1s%1d%4d%1s%1d%3d%1s%1d%4d%1s%1d%3d%1s%1d%3d%1s%1d%4d%1s%1d%5d%1s%1d%10d%3d%1s%1d%3d%1s%1d%3d%1s%1d%2d%1s%1d' # noqa: E501
892
+ columns = 'year,month,day,hour,ETR,ETRN,GHI,GHISource,GHIUncertainty,DNI,DNISource,DNIUncertainty,DHI,DHISource,DHIUncertainty,GHillum,GHillumSource,GHillumUncertainty,DNillum,DNillumSource,DNillumUncertainty,DHillum,DHillumSource,DHillumUncertainty,Zenithlum,ZenithlumSource,ZenithlumUncertainty,TotCld,TotCldSource,TotCldUncertainty,OpqCld,OpqCldSource,OpqCldUncertainty,DryBulb,DryBulbSource,DryBulbUncertainty,DewPoint,DewPointSource,DewPointUncertainty,RHum,RHumSource,RHumUncertainty,Pressure,PressureSource,PressureUncertainty,Wdir,WdirSource,WdirUncertainty,Wspd,WspdSource,WspdUncertainty,Hvis,HvisSource,HvisUncertainty,CeilHgt,CeilHgtSource,CeilHgtUncertainty,PresentWeather,Pwat,PwatSource,PwatUncertainty,AOD,AODSource,AODUncertainty,SnowDepth,SnowDepthSource,SnowDepthUncertainty,LastSnowfall,LastSnowfallSource,LastSnowfallUncertaint' # noqa: E501
893
+ hdr_columns = 'WBAN,City,State,TZ,latitude,longitude,altitude'
894
+
895
+ tmy2, tmy2_meta = _read_tmy2(string, columns, hdr_columns, str(filename))
896
+
897
+ return tmy2, tmy2_meta
898
+
899
+
900
+ def _parsemeta_tmy2(columns, line):
901
+ """Retrieve metadata from the top line of the tmy2 file.
902
+
903
+ Parameters
904
+ ----------
905
+ columns : string
906
+ String of column headings in the header
907
+
908
+ line : string
909
+ Header string containing DataFrame
910
+
911
+ Returns
912
+ -------
913
+ meta : Dict of metadata contained in the header string
914
+ """
915
+ # Remove duplicated spaces, and read in each element
916
+ rawmeta = " ".join(line.split()).split(" ")
917
+ meta = rawmeta[:3] # take the first string entries
918
+ meta.append(int(rawmeta[3]))
919
+ # Convert to decimal notation with S negative
920
+ longitude = (
921
+ float(rawmeta[5]) + float(rawmeta[6])/60) * (2*(rawmeta[4] == 'N') - 1)
922
+ # Convert to decimal notation with W negative
923
+ latitude = (
924
+ float(rawmeta[8]) + float(rawmeta[9])/60) * (2*(rawmeta[7] == 'E') - 1)
925
+ meta.append(longitude)
926
+ meta.append(latitude)
927
+ meta.append(float(rawmeta[10]))
928
+
929
+ # Creates a dictionary of metadata
930
+ meta_dict = dict(zip(columns.split(','), meta))
931
+ return meta_dict
932
+
933
+
934
+ def _read_tmy2(string, columns, hdr_columns, fname):
935
+ head = 1
936
+ date = []
937
+ with open(fname) as infile:
938
+ fline = 0
939
+ for line in infile:
940
+ # Skip the header
941
+ if head != 0:
942
+ meta = _parsemeta_tmy2(hdr_columns, line)
943
+ head -= 1
944
+ continue
945
+ # Reset the cursor and array for each line
946
+ cursor = 1
947
+ part = []
948
+ for marker in string.split('%'):
949
+ # Skip the first line of markers
950
+ if marker == '':
951
+ continue
952
+
953
+ # Read the next increment from the marker list
954
+ increment = int(re.findall(r'\d+', marker)[0])
955
+ next_cursor = cursor + increment
956
+
957
+ # Extract the value from the line in the file
958
+ val = (line[cursor:next_cursor])
959
+ # increment the cursor by the length of the read value
960
+ cursor = next_cursor
961
+
962
+ # Determine the datatype from the marker string
963
+ if marker[-1] == 'd':
964
+ try:
965
+ val = float(val)
966
+ except ValueError:
967
+ raise ValueError('WARNING: In {} Read value is not an '
968
+ 'integer " {} " '.format(fname, val))
969
+ elif marker[-1] == 's':
970
+ try:
971
+ val = str(val)
972
+ except ValueError:
973
+ raise ValueError('WARNING: In {} Read value is not a '
974
+ 'string " {} " '.format(fname, val))
975
+ else:
976
+ raise Exception('WARNING: In {} Improper column DataFrame '
977
+ '" %{} " '.format(__name__, marker))
978
+
979
+ part.append(val)
980
+
981
+ if fline == 0:
982
+ axes = [part]
983
+ year = part[0] + 1900
984
+ fline = 1
985
+ else:
986
+ axes.append(part)
987
+
988
+ # Create datetime objects from read data
989
+ date.append(dt.datetime(year=int(year),
990
+ month=int(part[1]),
991
+ day=int(part[2]),
992
+ hour=(int(part[3]) - 1)))
993
+
994
+ data = pd.DataFrame(
995
+ axes, index=date,
996
+ columns=columns.split(',')).tz_localize(int(meta['TZ'] * 3600))
997
+
998
+ return data, meta
999
+
1000
+ def read_wyec2(file_path):
1001
+ """
1002
+ Lee un archivo meteorológico en formato WYEC2 y lo convierte en un DataFrame de pandas.
1003
+
1004
+ Parámetros:
1005
+ file_path (str): Ruta del archivo WYEC2.
1006
+
1007
+ Retorna:
1008
+ pd.DataFrame: DataFrame con los datos meteorológicos.
1009
+ """
1010
+ sicro.SetUnitSystem(sicro.SI)
1011
+ data = []
1012
+ with open(file_path, 'r') as file:
1013
+ for line in file:
1014
+ new_line = {"year": int(line[6:8]),
1015
+ "month": int(line[8:10]),
1016
+ "day": int(line[10:12]),
1017
+ "hour": int(line[12:14]),
1018
+ "temperature": float(line[89:93])*0.1,
1019
+ "sol_global": float(line[18:22])/3.6,
1020
+ "sol_diffuse": float(line[30:34])/3.6,
1021
+ "dew_temperature": float(line[94:98])*0.1,
1022
+ "wind_speed": float(line[103:107])*0.1,
1023
+ "wind_direction": float(line[99:102]),
1024
+ "pressure": float(line[83:88])*10,
1025
+ "total_cloud_cover": float(line[108:110])*10,
1026
+ "opaque_cloud_cover": float(line[111:113])*10,
1027
+ }
1028
+ data.append(new_line)
1029
+ new_line["sol_direct"]= new_line["sol_global"]-new_line["sol_diffuse"]
1030
+ if new_line["temperature"] < new_line["dew_temperature"]:
1031
+ new_line["dew_temperature"] = new_line["temperature"]
1032
+ new_line["rel_humidity"]= sicro.GetRelHumFromTDewPoint(new_line["temperature"], new_line["dew_temperature"])*100
1033
+
1034
+ df = pd.DataFrame(data)
1035
+ return df