mabuseif commited on
Commit
562b976
·
verified ·
1 Parent(s): 11a22ed

Update data/climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +134 -15
data/climate_data.py CHANGED
@@ -41,11 +41,13 @@ class ClimateLocation:
41
  summer_design_temp_wb: float # 0.4% cooling design wet-bulb temperature (°C)
42
  summer_daily_range: float # Mean daily temperature range in summer (°C)
43
  wind_speed: float # Mean wind speed (m/s)
44
- pressure: float # Atmospheric pressure (Pa)
45
  hourly_data: List[Dict] # Hourly data for integration with main.py
 
 
46
 
47
- def __init__(self, epw_file: pd.DataFrame, **kwargs):
48
- """Initialize ClimateLocation with EPW file data."""
49
  self.id = kwargs.get("id")
50
  self.country = kwargs.get("country")
51
  self.state_province = kwargs.get("state_province", "N/A")
@@ -53,44 +55,63 @@ class ClimateLocation:
53
  self.latitude = kwargs.get("latitude")
54
  self.longitude = kwargs.get("longitude")
55
  self.elevation = kwargs.get("elevation")
 
 
56
 
 
57
  months = pd.to_numeric(epw_file[1], errors='coerce').values
 
 
58
  dry_bulb = pd.to_numeric(epw_file[6], errors='coerce').values
59
  humidity = pd.to_numeric(epw_file[8], errors='coerce').values
60
  pressure = pd.to_numeric(epw_file[9], errors='coerce').values
61
- wind_speed = pd.to_numeric(epw_file[21], errors='coerce').values
62
- wind_direction = pd.to_numeric(epw_file[20], errors='coerce').values
63
  global_radiation = pd.to_numeric(epw_file[13], errors='coerce').values
 
 
64
 
 
65
  wet_bulb = ClimateData.calculate_wet_bulb(dry_bulb, humidity)
66
 
 
67
  self.winter_design_temp = round(np.nanpercentile(dry_bulb, 0.4), 1)
68
  self.summer_design_temp_db = round(np.nanpercentile(dry_bulb, 99.6), 1)
69
  self.summer_design_temp_wb = round(np.nanpercentile(wet_bulb, 99.6), 1)
70
 
 
71
  daily_temps = np.nanmean(dry_bulb.reshape(-1, 24), axis=1)
72
  self.heating_degree_days = round(np.nansum(np.maximum(18 - daily_temps, 0)))
73
  self.cooling_degree_days = round(np.nansum(np.maximum(daily_temps - 18, 0)))
74
 
 
75
  summer_mask = (months >= 6) & (months <= 8)
76
  summer_temps = dry_bulb[summer_mask].reshape(-1, 24)
77
  self.summer_daily_range = round(np.nanmean(np.nanmax(summer_temps, axis=1) - np.nanmin(summer_temps, axis=1)), 1)
78
 
 
79
  self.wind_speed = round(np.nanmean(wind_speed), 1)
80
  self.pressure = round(np.nanmean(pressure), 1)
 
 
81
  self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
82
 
83
- # Store hourly data for main.py integration
84
  self.hourly_data = [
85
  {
86
  "month": int(months[i]),
87
- "hour": i % 24,
 
88
  "dry_bulb": float(dry_bulb[i]),
89
  "relative_humidity": float(humidity[i]),
 
90
  "global_horizontal_radiation": float(global_radiation[i]),
91
  "wind_speed": float(wind_speed[i]),
92
  "wind_direction": float(wind_direction[i])
93
- } for i in range(len(months)) if not any(np.isnan([months[i], dry_bulb[i], humidity[i], global_radiation[i], wind_speed[i], wind_direction[i]]))
 
 
 
 
 
94
  ]
95
 
96
  def to_dict(self) -> Dict[str, Any]:
@@ -112,7 +133,9 @@ class ClimateLocation:
112
  "summer_daily_range": self.summer_daily_range,
113
  "wind_speed": self.wind_speed,
114
  "pressure": self.pressure,
115
- "hourly_data": self.hourly_data
 
 
116
  }
117
 
118
  class ClimateData:
@@ -159,7 +182,8 @@ class ClimateData:
159
  "id", "country", "city", "latitude", "longitude", "elevation",
160
  "climate_zone", "heating_degree_days", "cooling_degree_days",
161
  "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
162
- "summer_daily_range", "wind_speed", "pressure", "hourly_data"
 
163
  ]
164
 
165
  for field in required_fields:
@@ -182,18 +206,24 @@ class ClimateData:
182
  return False
183
  if not (0 <= data["wind_speed"] <= 20):
184
  return False
185
- if not (50000 <= data["pressure"] <= 120000):
186
  return False
187
 
188
  if not data["hourly_data"] or len(data["hourly_data"]) != 8760:
189
  return False
190
  for record in data["hourly_data"]:
191
- if not (1 <= record["month"] <= 12 and 0 <= record["hour"] <= 23):
 
 
 
 
192
  return False
193
  if not (-50 <= record["dry_bulb"] <= 50):
194
  return False
195
  if not (0 <= record["relative_humidity"] <= 100):
196
  return False
 
 
197
  if not (0 <= record["global_horizontal_radiation"] <= 1200):
198
  return False
199
  if not (0 <= record["wind_speed"] <= 20):
@@ -201,6 +231,22 @@ class ClimateData:
201
  if not (0 <= record["wind_direction"] <= 360):
202
  return False
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  return True
205
 
206
  @staticmethod
@@ -237,6 +283,8 @@ class ClimateData:
237
  # Process new EPW file
238
  epw_content = uploaded_file.read().decode("utf-8")
239
  epw_lines = epw_content.splitlines()
 
 
240
  header = next(line for line in epw_lines if line.startswith("LOCATION"))
241
  header_parts = header.split(",")
242
  city = header_parts[1].strip()
@@ -246,6 +294,45 @@ class ClimateData:
246
  longitude = float(header_parts[7])
247
  elevation = float(header_parts[8])
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  data_start_idx = next(i for i, line in enumerate(epw_lines) if line.startswith("DATA PERIODS")) + 1
250
  epw_data = pd.read_csv(StringIO("\n".join(epw_lines[data_start_idx:])), header=None, dtype=str)
251
 
@@ -254,13 +341,16 @@ class ClimateData:
254
  if len(epw_data.columns) != 35:
255
  raise ValueError(f"EPW file has {len(epw_data.columns)} columns, expected 35.")
256
 
257
- for col in [1, 6, 8, 9, 13, 20, 21]:
258
  epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce')
259
  if epw_data[col].isna().all():
260
  raise ValueError(f"Column {col} contains only non-numeric or missing data.")
261
 
 
262
  location = ClimateLocation(
263
  epw_file=epw_data,
 
 
264
  id=f"{country[:1].upper()}{city[:3].upper()}",
265
  country=country,
266
  state_province=state_province,
@@ -287,9 +377,11 @@ class ClimateData:
287
  hourly_data = climate_data_dict["hourly_data"]
288
  epw_data = pd.DataFrame({
289
  1: [d["month"] for d in hourly_data], # Month
 
 
290
  6: [d["dry_bulb"] for d in hourly_data], # Dry-bulb temperature
291
  8: [d["relative_humidity"] for d in hourly_data], # Relative humidity
292
- 9: [climate_data_dict["pressure"]] * len(hourly_data), # Pressure (mean value)
293
  13: [d["global_horizontal_radiation"] for d in hourly_data], # Global horizontal radiation
294
  20: [d["wind_direction"] for d in hourly_data], # Wind direction
295
  21: [d["wind_speed"] for d in hourly_data], # Wind speed
@@ -298,6 +390,8 @@ class ClimateData:
298
  # Create ClimateLocation with reconstructed epw_data
299
  location = ClimateLocation(
300
  epw_file=epw_data,
 
 
301
  id=climate_data_dict["id"],
302
  country=climate_data_dict["country"],
303
  state_province=climate_data_dict["state_province"],
@@ -662,7 +756,32 @@ class ClimateData:
662
  data = json.load(f)
663
  climate_data = cls()
664
  for loc_id, loc_dict in data.items():
665
- location = ClimateLocation(epw_file=pd.DataFrame(), **loc_dict)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  climate_data.add_location(location)
667
  return climate_data
668
 
 
41
  summer_design_temp_wb: float # 0.4% cooling design wet-bulb temperature (°C)
42
  summer_daily_range: float # Mean daily temperature range in summer (°C)
43
  wind_speed: float # Mean wind speed (m/s)
44
+ pressure: float # Mean atmospheric pressure (Pa)
45
  hourly_data: List[Dict] # Hourly data for integration with main.py
46
+ typical_extreme_periods: Dict[str, Dict] # Typical/extreme periods (summer/winter)
47
+ ground_temperatures: Dict[str, List[float]] # Monthly ground temperatures by depth
48
 
49
+ def __init__(self, epw_file: pd.DataFrame, typical_extreme_periods: Dict, ground_temperatures: Dict, **kwargs):
50
+ """Initialize ClimateLocation with EPW file data and header information."""
51
  self.id = kwargs.get("id")
52
  self.country = kwargs.get("country")
53
  self.state_province = kwargs.get("state_province", "N/A")
 
55
  self.latitude = kwargs.get("latitude")
56
  self.longitude = kwargs.get("longitude")
57
  self.elevation = kwargs.get("elevation")
58
+ self.typical_extreme_periods = typical_extreme_periods
59
+ self.ground_temperatures = ground_temperatures
60
 
61
+ # Extract columns from EPW data
62
  months = pd.to_numeric(epw_file[1], errors='coerce').values
63
+ days = pd.to_numeric(epw_file[2], errors='coerce').values
64
+ hours = pd.to_numeric(epw_file[3], errors='coerce').values
65
  dry_bulb = pd.to_numeric(epw_file[6], errors='coerce').values
66
  humidity = pd.to_numeric(epw_file[8], errors='coerce').values
67
  pressure = pd.to_numeric(epw_file[9], errors='coerce').values
 
 
68
  global_radiation = pd.to_numeric(epw_file[13], errors='coerce').values
69
+ wind_direction = pd.to_numeric(epw_file[20], errors='coerce').values
70
+ wind_speed = pd.to_numeric(epw_file[21], errors='coerce').values
71
 
72
+ # Calculate wet-bulb temperature
73
  wet_bulb = ClimateData.calculate_wet_bulb(dry_bulb, humidity)
74
 
75
+ # Calculate design conditions
76
  self.winter_design_temp = round(np.nanpercentile(dry_bulb, 0.4), 1)
77
  self.summer_design_temp_db = round(np.nanpercentile(dry_bulb, 99.6), 1)
78
  self.summer_design_temp_wb = round(np.nanpercentile(wet_bulb, 99.6), 1)
79
 
80
+ # Calculate degree days
81
  daily_temps = np.nanmean(dry_bulb.reshape(-1, 24), axis=1)
82
  self.heating_degree_days = round(np.nansum(np.maximum(18 - daily_temps, 0)))
83
  self.cooling_degree_days = round(np.nansum(np.maximum(daily_temps - 18, 0)))
84
 
85
+ # Calculate summer daily temperature range (June–August, Southern Hemisphere)
86
  summer_mask = (months >= 6) & (months <= 8)
87
  summer_temps = dry_bulb[summer_mask].reshape(-1, 24)
88
  self.summer_daily_range = round(np.nanmean(np.nanmax(summer_temps, axis=1) - np.nanmin(summer_temps, axis=1)), 1)
89
 
90
+ # Calculate mean wind speed and pressure
91
  self.wind_speed = round(np.nanmean(wind_speed), 1)
92
  self.pressure = round(np.nanmean(pressure), 1)
93
+
94
+ # Assign climate zone
95
  self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
96
 
97
+ # Store hourly data with enhanced fields
98
  self.hourly_data = [
99
  {
100
  "month": int(months[i]),
101
+ "day": int(days[i]),
102
+ "hour": int(hours[i]),
103
  "dry_bulb": float(dry_bulb[i]),
104
  "relative_humidity": float(humidity[i]),
105
+ "atmospheric_pressure": float(pressure[i]),
106
  "global_horizontal_radiation": float(global_radiation[i]),
107
  "wind_speed": float(wind_speed[i]),
108
  "wind_direction": float(wind_direction[i])
109
+ }
110
+ for i in range(len(months))
111
+ if not any(np.isnan([
112
+ months[i], days[i], hours[i], dry_bulb[i], humidity[i],
113
+ pressure[i], global_radiation[i], wind_speed[i], wind_direction[i]
114
+ ]))
115
  ]
116
 
117
  def to_dict(self) -> Dict[str, Any]:
 
133
  "summer_daily_range": self.summer_daily_range,
134
  "wind_speed": self.wind_speed,
135
  "pressure": self.pressure,
136
+ "hourly_data": self.hourly_data,
137
+ "typical_extreme_periods": self.typical_extreme_periods,
138
+ "ground_temperatures": self.ground_temperatures
139
  }
140
 
141
  class ClimateData:
 
182
  "id", "country", "city", "latitude", "longitude", "elevation",
183
  "climate_zone", "heating_degree_days", "cooling_degree_days",
184
  "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
185
+ "summer_daily_range", "wind_speed", "pressure", "hourly_data",
186
+ "typical_extreme_periods", "ground_temperatures"
187
  ]
188
 
189
  for field in required_fields:
 
206
  return False
207
  if not (0 <= data["wind_speed"] <= 20):
208
  return False
209
+ if not (80000 <= data["pressure"] <= 110000):
210
  return False
211
 
212
  if not data["hourly_data"] or len(data["hourly_data"]) != 8760:
213
  return False
214
  for record in data["hourly_data"]:
215
+ if not (1 <= record["month"] <= 12):
216
+ return False
217
+ if not (1 <= record["day"] <= 31):
218
+ return False
219
+ if not (1 <= record["hour"] <= 24):
220
  return False
221
  if not (-50 <= record["dry_bulb"] <= 50):
222
  return False
223
  if not (0 <= record["relative_humidity"] <= 100):
224
  return False
225
+ if not (80000 <= record["atmospheric_pressure"] <= 110000):
226
+ return False
227
  if not (0 <= record["global_horizontal_radiation"] <= 1200):
228
  return False
229
  if not (0 <= record["wind_speed"] <= 20):
 
231
  if not (0 <= record["wind_direction"] <= 360):
232
  return False
233
 
234
+ # Validate typical/extreme periods
235
+ expected_periods = ["summer_extreme", "summer_typical", "winter_extreme", "winter_typical"]
236
+ if not all(key in data["typical_extreme_periods"] for key in expected_periods):
237
+ return False
238
+ for period in data["typical_extreme_periods"].values():
239
+ for date in ["start", "end"]:
240
+ if not (1 <= period[date]["month"] <= 12 and 1 <= period[date]["day"] <= 31):
241
+ return False
242
+
243
+ # Validate ground temperatures
244
+ if not data["ground_temperatures"]:
245
+ return False
246
+ for depth, temps in data["ground_temperatures"].items():
247
+ if len(temps) != 12 or not all(0 <= t <= 50 for t in temps):
248
+ return False
249
+
250
  return True
251
 
252
  @staticmethod
 
283
  # Process new EPW file
284
  epw_content = uploaded_file.read().decode("utf-8")
285
  epw_lines = epw_content.splitlines()
286
+
287
+ # Parse header
288
  header = next(line for line in epw_lines if line.startswith("LOCATION"))
289
  header_parts = header.split(",")
290
  city = header_parts[1].strip()
 
294
  longitude = float(header_parts[7])
295
  elevation = float(header_parts[8])
296
 
297
+ # Parse TYPICAL/EXTREME PERIODS
298
+ typical_extreme_periods = {}
299
+ for line in epw_lines:
300
+ if line.startswith("TYPICAL/EXTREME PERIODS"):
301
+ parts = line.strip().split(',')
302
+ num_periods = int(parts[1])
303
+ for i in range(num_periods):
304
+ period_name = parts[2 + i*4]
305
+ period_type = parts[3 + i*4]
306
+ start_date = parts[4 + i*4]
307
+ end_date = parts[5 + i*4]
308
+ if period_name in [
309
+ "Summer - Week Nearest Max Temperature For Period",
310
+ "Summer - Week Nearest Average Temperature For Period",
311
+ "Winter - Week Nearest Min Temperature For Period",
312
+ "Winter - Week Nearest Average Temperature For Period"
313
+ ]:
314
+ key = f"{'summer' if 'Summer' in period_name else 'winter'}_{'extreme' if 'Max' in period_name else 'typical' if 'Average' in period_name else ''}"
315
+ start_month, start_day = map(int, start_date.split('/'))
316
+ end_month, end_day = map(int, end_date.replace(' ', '').split('/'))
317
+ typical_extreme_periods[key] = {
318
+ "start": {"month": start_month, "day": start_day},
319
+ "end": {"month": end_month, "day": end_day}
320
+ }
321
+ break
322
+
323
+ # Parse GROUND TEMPERATURES
324
+ ground_temperatures = {}
325
+ for line in epw_lines:
326
+ if line.startswith("GROUND TEMPERATURES"):
327
+ parts = line.strip().split(',')
328
+ num_depths = int(parts[1])
329
+ for i in range(num_depths):
330
+ depth = parts[2 + i*16]
331
+ temps = [float(t) for t in parts[6 + i*16:18 + i*16]]
332
+ ground_temperatures[depth] = temps
333
+ break
334
+
335
+ # Read data section
336
  data_start_idx = next(i for i, line in enumerate(epw_lines) if line.startswith("DATA PERIODS")) + 1
337
  epw_data = pd.read_csv(StringIO("\n".join(epw_lines[data_start_idx:])), header=None, dtype=str)
338
 
 
341
  if len(epw_data.columns) != 35:
342
  raise ValueError(f"EPW file has {len(epw_data.columns)} columns, expected 35.")
343
 
344
+ for col in [1, 2, 3, 6, 8, 9, 13, 20, 21]:
345
  epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce')
346
  if epw_data[col].isna().all():
347
  raise ValueError(f"Column {col} contains only non-numeric or missing data.")
348
 
349
+ # Create ClimateLocation
350
  location = ClimateLocation(
351
  epw_file=epw_data,
352
+ typical_extreme_periods=typical_extreme_periods,
353
+ ground_temperatures=ground_temperatures,
354
  id=f"{country[:1].upper()}{city[:3].upper()}",
355
  country=country,
356
  state_province=state_province,
 
377
  hourly_data = climate_data_dict["hourly_data"]
378
  epw_data = pd.DataFrame({
379
  1: [d["month"] for d in hourly_data], # Month
380
+ 2: [d["day"] for d in hourly_data], # Day
381
+ 3: [d["hour"] for d in hourly_data], # Hour
382
  6: [d["dry_bulb"] for d in hourly_data], # Dry-bulb temperature
383
  8: [d["relative_humidity"] for d in hourly_data], # Relative humidity
384
+ 9: [d["atmospheric_pressure"] for d in hourly_data], # Pressure
385
  13: [d["global_horizontal_radiation"] for d in hourly_data], # Global horizontal radiation
386
  20: [d["wind_direction"] for d in hourly_data], # Wind direction
387
  21: [d["wind_speed"] for d in hourly_data], # Wind speed
 
390
  # Create ClimateLocation with reconstructed epw_data
391
  location = ClimateLocation(
392
  epw_file=epw_data,
393
+ typical_extreme_periods=climate_data_dict["typical_extreme_periods"],
394
+ ground_temperatures=climate_data_dict["ground_temperatures"],
395
  id=climate_data_dict["id"],
396
  country=climate_data_dict["country"],
397
  state_province=climate_data_dict["state_province"],
 
756
  data = json.load(f)
757
  climate_data = cls()
758
  for loc_id, loc_dict in data.items():
759
+ # Rebuild epw_data from hourly_data
760
+ hourly_data = loc_dict["hourly_data"]
761
+ epw_data = pd.DataFrame({
762
+ 1: [d["month"] for d in hourly_data],
763
+ 2: [d["day"] for d in hourly_data],
764
+ 3: [d["hour"] for d in hourly_data],
765
+ 6: [d["dry_bulb"] for d in hourly_data],
766
+ 8: [d["relative_humidity"] for d in hourly_data],
767
+ 9: [d["atmospheric_pressure"] for d in hourly_data],
768
+ 13: [d["global_horizontal_radiation"] for d in hourly_data],
769
+ 20: [d["wind_direction"] for d in hourly_data],
770
+ 21: [d["wind_speed"] for d in hourly_data],
771
+ })
772
+ location = ClimateLocation(
773
+ epw_file=epw_data,
774
+ typical_extreme_periods=loc_dict["typical_extreme_periods"],
775
+ ground_temperatures=loc_dict["ground_temperatures"],
776
+ id=loc_dict["id"],
777
+ country=loc_dict["country"],
778
+ state_province=loc_dict["state_province"],
779
+ city=loc_dict["city"],
780
+ latitude=loc_dict["latitude"],
781
+ longitude=loc_dict["longitude"],
782
+ elevation=loc_dict["elevation"]
783
+ )
784
+ location.hourly_data = loc_dict["hourly_data"] # Ensure consistency
785
  climate_data.add_location(location)
786
  return climate_data
787