mabuseif commited on
Commit
5c7d03c
·
verified ·
1 Parent(s): 4acdbac

Upload climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +162 -95
data/climate_data.py CHANGED
@@ -40,7 +40,52 @@ class ClimateLocation:
40
  summer_daily_range: float # Mean daily temperature range in summer (°C)
41
  monthly_temps: Dict[str, float] # Average monthly temperatures (°C)
42
  monthly_humidity: Dict[str, float] # Average monthly relative humidity (%)
 
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def to_dict(self) -> Dict[str, Any]:
45
  """Convert the climate location to a dictionary."""
46
  return {
@@ -59,7 +104,9 @@ class ClimateLocation:
59
  "summer_design_temp_wb": self.summer_design_temp_wb,
60
  "summer_daily_range": self.summer_daily_range,
61
  "monthly_temps": self.monthly_temps,
62
- "monthly_humidity": self.monthly_humidity
 
 
63
  }
64
 
65
  class ClimateData:
@@ -106,7 +153,8 @@ class ClimateData:
106
  "id", "country", "city", "latitude", "longitude", "elevation",
107
  "climate_zone", "heating_degree_days", "cooling_degree_days",
108
  "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
109
- "summer_daily_range", "monthly_temps", "monthly_humidity"
 
110
  ]
111
  month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
112
 
@@ -128,6 +176,10 @@ class ClimateData:
128
  return False
129
  if data["summer_daily_range"] < 0:
130
  return False
 
 
 
 
131
 
132
  for month in month_names:
133
  if month not in data["monthly_temps"] or month not in data["monthly_humidity"]:
@@ -159,7 +211,7 @@ class ClimateData:
159
  return wet_bulb
160
 
161
  def display_climate_input(self, session_state: Dict[str, Any]):
162
- """Display form for manual input or EPW upload in Streamlit."""
163
  st.title("Climate Data")
164
 
165
  if not session_state.building_info.get("country") or not session_state.building_info.get("city"):
@@ -168,10 +220,94 @@ class ClimateData:
168
  return
169
 
170
  st.subheader(f"Location: {session_state.building_info['country']}, {session_state.building_info['city']}")
171
- tab1, tab2 = st.tabs(["Manual Input", "Upload EPW File"])
172
 
173
- # Manual Input Tab
174
  with tab1:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  with st.form("manual_climate_form"):
176
  col1, col2 = st.columns(2)
177
  with col1:
@@ -222,10 +358,7 @@ class ClimateData:
222
  winter_design_temp = st.number_input(
223
  "Winter Design Temp (99.6%) (°C)",
224
  min_value=-50.0,
225
- max_value=20.0,
226
- value=0.0,
227
- step=0.5,
228
- help="Enter the 99.6% winter design temperature in °C (extreme cold condition)"
229
  )
230
  summer_design_temp_db = st.number_input(
231
  "Summer Design Temp DB (0.4%) (°C)",
@@ -250,6 +383,14 @@ class ClimateData:
250
  step=0.5,
251
  help="Enter the average daily temperature range in summer in °C"
252
  )
 
 
 
 
 
 
 
 
253
 
254
  # Monthly Data with clear titles (no help added here)
255
  monthly_temps = {}
@@ -278,7 +419,13 @@ class ClimateData:
278
  try:
279
  # Generate ID internally using country and city from session_state
280
  generated_id = f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}"
 
 
 
 
 
281
  location = ClimateLocation(
 
282
  id=generated_id,
283
  country=session_state.building_info["country"],
284
  state_province="N/A", # Default since input removed
@@ -289,7 +436,6 @@ class ClimateData:
289
  climate_zone=climate_zone,
290
  heating_degree_days=hdd,
291
  cooling_degree_days=cdd,
292
- winter_design_temp=winter_design_temp,
293
  summer_design_temp_db=summer_design_temp_db,
294
  summer_design_temp_wb=summer_design_temp_wb,
295
  summer_daily_range=summer_daily_range,
@@ -308,89 +454,6 @@ class ClimateData:
308
  except Exception as e:
309
  st.error(f"Error saving climate data: {str(e)}. Please check inputs and try again.")
310
 
311
- # EPW Upload Tab
312
- with tab2:
313
- uploaded_file = st.file_uploader("Upload EPW File", type=["epw"])
314
- if uploaded_file:
315
- try:
316
- epw_content = uploaded_file.read().decode("utf-8")
317
- epw_lines = epw_content.splitlines()
318
- header = next(line for line in epw_lines if line.startswith("LOCATION"))
319
- header_parts = header.split(",")
320
- latitude = float(header_parts[6])
321
- longitude = float(header_parts[7])
322
- elevation = float(header_parts[8])
323
-
324
- data_start_idx = next(i for i, line in enumerate(epw_lines) if line.startswith("DATA PERIODS")) + 1
325
- epw_data = pd.read_csv(StringIO("\n".join(epw_lines[data_start_idx:])), header=None, dtype=str)
326
- if len(epw_data) != 8760:
327
- raise ValueError(f"EPW file has {len(epw_data)} records, expected 8760.")
328
-
329
- for col in epw_data.columns:
330
- epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce')
331
-
332
- months = epw_data[1].values # Month
333
- dry_bulb = epw_data[6].values # Dry-bulb temperature (°C)
334
- humidity = epw_data[8].values # Relative humidity (%)
335
- pressure = epw_data[9].values # Atmospheric pressure (Pa)
336
-
337
- wet_bulb = self.calculate_wet_bulb(dry_bulb, humidity)
338
-
339
- if np.all(np.isnan(dry_bulb)) or np.all(np.isnan(humidity)) or np.all(np.isnan(wet_bulb)):
340
- raise ValueError("Dry bulb, humidity, or calculated wet bulb data is entirely NaN.")
341
-
342
- daily_temps = np.nanmean(dry_bulb.reshape(-1, 24), axis=1)
343
- hdd = round(np.nansum(np.maximum(18 - daily_temps, 0)))
344
- cdd = round(np.nansum(np.maximum(daily_temps - 18, 0)))
345
-
346
- winter_design_temp = round(np.nanpercentile(dry_bulb, 0.4), 1)
347
- summer_design_temp_db = round(np.nanpercentile(dry_bulb, 99.6), 1)
348
- summer_design_temp_wb = round(np.nanpercentile(wet_bulb, 99.6), 1)
349
- summer_mask = (months >= 6) & (months <= 8)
350
- summer_temps = dry_bulb[summer_mask].reshape(-1, 24)
351
- summer_daily_range = round(np.nanmean(np.nanmax(summer_temps, axis=1) - np.nanmin(summer_temps, axis=1)), 1)
352
-
353
- monthly_temps = {}
354
- monthly_humidity = {}
355
- month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
356
- for i in range(1, 13):
357
- month_mask = (months == i)
358
- monthly_temps[month_names[i-1]] = round(np.nanmean(dry_bulb[month_mask]), 1)
359
- monthly_humidity[month_names[i-1]] = round(np.nanmean(humidity[month_mask]), 1)
360
-
361
- avg_humidity = np.nanmean(humidity)
362
- climate_zone = self.assign_climate_zone(hdd, cdd, avg_humidity)
363
-
364
- location = ClimateLocation(
365
- id=f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}",
366
- country=session_state.building_info["country"],
367
- state_province="N/A",
368
- city=session_state.building_info["city"],
369
- latitude=latitude,
370
- longitude=longitude,
371
- elevation=elevation,
372
- climate_zone=climate_zone,
373
- heating_degree_days=hdd,
374
- cooling_degree_days=cdd,
375
- winter_design_temp=winter_design_temp,
376
- summer_design_temp_db=summer_design_temp_db,
377
- summer_design_temp_wb=summer_design_temp_wb,
378
- summer_daily_range=summer_daily_range,
379
- monthly_temps=monthly_temps,
380
- monthly_humidity=monthly_humidity
381
- )
382
- self.add_location(location)
383
- climate_data_dict = location.to_dict()
384
- if not self.validate_climate_data(climate_data_dict):
385
- raise ValueError("Invalid climate data extracted from EPW file.")
386
- session_state["climate_data"] = climate_data_dict # Save to session state
387
- st.success("Climate data extracted from EPW file with calculated Wet Bulb Temperature!")
388
- st.write(f"Debug: Saved climate data for {location.city} (ID: {location.id}): {climate_data_dict}") # Debug
389
- self.display_design_conditions(location)
390
- self.visualize_data(location, epw_data=epw_data)
391
- except Exception as e:
392
- st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
393
-
394
  col1, col2 = st.columns(2)
395
  with col1:
396
  st.button("Back to Building Information", on_click=lambda: setattr(session_state, "page", "Building Information"))
@@ -420,7 +483,9 @@ class ClimateData:
420
  "Winter Design Temperature (99.6%)",
421
  "Summer Design Dry-Bulb Temp (0.4%)",
422
  "Summer Design Wet-Bulb Temp (0.4%)",
423
- "Summer Daily Temperature Range"
 
 
424
  ],
425
  "Value": [
426
  f"{location.latitude}°",
@@ -432,7 +497,9 @@ class ClimateData:
432
  f"{location.winter_design_temp} °C",
433
  f"{location.summer_design_temp_db} °C",
434
  f"{location.summer_design_temp_wb} °C",
435
- f"{location.summer_daily_range} °C"
 
 
436
  ]
437
  })
438
 
 
40
  summer_daily_range: float # Mean daily temperature range in summer (°C)
41
  monthly_temps: Dict[str, float] # Average monthly temperatures (°C)
42
  monthly_humidity: Dict[str, float] # Average monthly relative humidity (%)
43
+ wind_speed: float # Mean wind speed (m/s)
44
+ pressure: float # Atmospheric pressure (Pa)
45
 
46
+ def __init__(self, epw_file=None, manual_data=None, **kwargs):
47
+ """Initialize ClimateLocation with EPW file or manual data."""
48
+ if epw_file:
49
+ # Extract from EPW (epw_data[6] for dry-bulb temperature)
50
+ temps = np.array(epw_file[6], dtype=float)
51
+ self.winter_design_temp = np.percentile(temps[~np.isnan(temps)], 0.4) # 99.6% percentile
52
+ self.wind_speed = round(np.nanmean(epw_file[13]), 1) # m/s
53
+ self.pressure = self.adjust_pressure_for_altitude(kwargs.get("elevation", 0.0))
54
+ # Populate other fields from EPW processing
55
+ self.id = kwargs.get("id")
56
+ self.country = kwargs.get("country")
57
+ self.state_province = kwargs.get("state_province")
58
+ self.city = kwargs.get("city")
59
+ self.latitude = kwargs.get("latitude")
60
+ self.longitude = kwargs.get("longitude")
61
+ self.elevation = kwargs.get("elevation")
62
+ self.climate_zone = kwargs.get("climate_zone")
63
+ self.heating_degree_days = kwargs.get("heating_degree_days")
64
+ self.cooling_degree_days = kwargs.get("cooling_degree_days")
65
+ self.summer_design_temp_db = kwargs.get("summer_design_temp_db")
66
+ self.summer_design_temp_wb = kwargs.get("summer_design_temp_wb")
67
+ self.summer_daily_range = kwargs.get("summer_daily_range")
68
+ self.monthly_temps = kwargs.get("monthly_temps")
69
+ self.monthly_humidity = kwargs.get("monthly_humidity")
70
+ elif manual_data:
71
+ self.winter_design_temp = manual_data.get("winter_temp", -10.0)
72
+ self.wind_speed = manual_data.get("wind_speed", 5.0)
73
+ self.pressure = manual_data.get("pressure", 101325.0)
74
+ # Populate other fields from manual data
75
+ for key, value in kwargs.items():
76
+ setattr(self, key, value)
77
+ else:
78
+ # Default initialization with kwargs
79
+ for key, value in kwargs.items():
80
+ setattr(self, key, value)
81
+ self.winter_design_temp = kwargs.get("winter_design_temp", -10.0)
82
+ self.wind_speed = kwargs.get("wind_speed", 5.0)
83
+ self.pressure = self.adjust_pressure_for_altitude(kwargs.get("elevation", 0.0))
84
+
85
+ def adjust_pressure_for_altitude(self, elevation: float) -> float:
86
+ """Calculate atmospheric pressure based on elevation."""
87
+ return 101325 * (1 - 2.25577e-5 * elevation)**5.25588
88
+
89
  def to_dict(self) -> Dict[str, Any]:
90
  """Convert the climate location to a dictionary."""
91
  return {
 
104
  "summer_design_temp_wb": self.summer_design_temp_wb,
105
  "summer_daily_range": self.summer_daily_range,
106
  "monthly_temps": self.monthly_temps,
107
+ "monthly_humidity": self.monthly_humidity,
108
+ "wind_speed": self.wind_speed,
109
+ "pressure": self.pressure
110
  }
111
 
112
  class ClimateData:
 
153
  "id", "country", "city", "latitude", "longitude", "elevation",
154
  "climate_zone", "heating_degree_days", "cooling_degree_days",
155
  "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
156
+ "summer_daily_range", "monthly_temps", "monthly_humidity",
157
+ "wind_speed", "pressure"
158
  ]
159
  month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
160
 
 
176
  return False
177
  if data["summer_daily_range"] < 0:
178
  return False
179
+ if not (0 <= data["wind_speed"] <= 20):
180
+ return False
181
+ if not (50000 <= data["pressure"] <= 120000):
182
+ return False
183
 
184
  for month in month_names:
185
  if month not in data["monthly_temps"] or month not in data["monthly_humidity"]:
 
211
  return wet_bulb
212
 
213
  def display_climate_input(self, session_state: Dict[str, Any]):
214
+ """Display form for EPW upload or manual input in Streamlit."""
215
  st.title("Climate Data")
216
 
217
  if not session_state.building_info.get("country") or not session_state.building_info.get("city"):
 
220
  return
221
 
222
  st.subheader(f"Location: {session_state.building_info['country']}, {session_state.building_info['city']}")
223
+ tab1, tab2 = st.tabs(["Upload EPW File", "Manual Input"])
224
 
225
+ # EPW Upload Tab
226
  with tab1:
227
+ uploaded_file = st.file_uploader("Upload EPW File", type=["epw"])
228
+ if uploaded_file:
229
+ try:
230
+ epw_content = uploaded_file.read().decode("utf-8")
231
+ epw_lines = epw_content.splitlines()
232
+ header = next(line for line in epw_lines if line.startswith("LOCATION"))
233
+ header_parts = header.split(",")
234
+ latitude = float(header_parts[6])
235
+ longitude = float(header_parts[7])
236
+ elevation = float(header_parts[8])
237
+
238
+ data_start_idx = next(i for i, line in enumerate(epw_lines) if line.startswith("DATA PERIODS")) + 1
239
+ epw_data = pd.read_csv(StringIO("\n".join(epw_lines[data_start_idx:])), header=None, dtype=str)
240
+ if len(epw_data) != 8760:
241
+ raise ValueError(f"EPW file has {len(epw_data)} records, expected 8760.")
242
+
243
+ for col in epw_data.columns:
244
+ epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce')
245
+
246
+ months = epw_data[1].values # Month
247
+ dry_bulb = epw_data[6].values # Dry-bulb temperature (°C)
248
+ humidity = epw_data[8].values # Relative humidity (%)
249
+ pressure = epw_data[9].values # Atmospheric pressure (Pa)
250
+ wind_speed = epw_data[13].values # Wind speed (m/s)
251
+
252
+ wet_bulb = self.calculate_wet_bulb(dry_bulb, humidity)
253
+
254
+ if np.all(np.isnan(dry_bulb)) or np.all(np.isnan(humidity)) or np.all(np.isnan(wet_bulb)):
255
+ raise ValueError("Dry bulb, humidity, or calculated wet bulb data is entirely NaN.")
256
+
257
+ daily_temps = np.nanmean(dry_bulb.reshape(-1, 24), axis=1)
258
+ hdd = round(np.nansum(np.maximum(18 - daily_temps, 0)))
259
+ cdd = round(np.nansum(np.maximum(daily_temps - 18, 0)))
260
+
261
+ winter_design_temp = round(np.nanpercentile(dry_bulb, 0.4), 1)
262
+ summer_design_temp_db = round(np.nanpercentile(dry_bulb, 99.6), 1)
263
+ summer_design_temp_wb = round(np.nanpercentile(wet_bulb, 99.6), 1)
264
+ summer_mask = (months >= 6) & (months <= 8)
265
+ summer_temps = dry_bulb[summer_mask].reshape(-1, 24)
266
+ summer_daily_range = round(np.nanmean(np.nanmax(summer_temps, axis=1) - np.nanmin(summer_temps, axis=1)), 1)
267
+
268
+ monthly_temps = {}
269
+ monthly_humidity = {}
270
+ month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
271
+ for i in range(1, 13):
272
+ month_mask = (months == i)
273
+ monthly_temps[month_names[i-1]] = round(np.nanmean(dry_bulb[month_mask]), 1)
274
+ monthly_humidity[month_names[i-1]] = round(np.nanmean(humidity[month_mask]), 1)
275
+
276
+ avg_humidity = np.nanmean(humidity)
277
+ climate_zone = self.assign_climate_zone(hdd, cdd, avg_humidity)
278
+
279
+ location = ClimateLocation(
280
+ epw_file=epw_data,
281
+ id=f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}",
282
+ country=session_state.building_info["country"],
283
+ state_province="N/A",
284
+ city=session_state.building_info["city"],
285
+ latitude=latitude,
286
+ longitude=longitude,
287
+ elevation=elevation,
288
+ climate_zone=climate_zone,
289
+ heating_degree_days=hdd,
290
+ cooling_degree_days=cdd,
291
+ summer_design_temp_db=summer_design_temp_db,
292
+ summer_design_temp_wb=summer_design_temp_wb,
293
+ summer_daily_range=summer_daily_range,
294
+ monthly_temps=monthly_temps,
295
+ monthly_humidity=monthly_humidity
296
+ )
297
+ self.add_location(location)
298
+ climate_data_dict = location.to_dict()
299
+ if not self.validate_climate_data(climate_data_dict):
300
+ raise ValueError("Invalid climate data extracted from EPW file.")
301
+ session_state["climate_data"] = climate_data_dict # Save to session state
302
+ st.success("Climate data extracted from EPW file with calculated Wet Bulb Temperature!")
303
+ st.write(f"Debug: Saved climate data for {location.city} (ID: {location.id}): {climate_data_dict}") # Debug
304
+ self.display_design_conditions(location)
305
+ self.visualize_data(location, epw_data=epw_data)
306
+ except Exception as e:
307
+ st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
308
+
309
+ # Manual Input Tab
310
+ with tab2:
311
  with st.form("manual_climate_form"):
312
  col1, col2 = st.columns(2)
313
  with col1:
 
358
  winter_design_temp = st.number_input(
359
  "Winter Design Temp (99.6%) (°C)",
360
  min_value=-50.0,
361
+ max
 
 
 
362
  )
363
  summer_design_temp_db = st.number_input(
364
  "Summer Design Temp DB (0.4%) (°C)",
 
383
  step=0.5,
384
  help="Enter the average daily temperature range in summer in °C"
385
  )
386
+ wind_speed = st.number_input(
387
+ "Wind Speed (m/s)",
388
+ min_value=0.0,
389
+ max_value=20.0,
390
+ value=5.0,
391
+ step=0.1,
392
+ help="Enter the average wind speed in meters per second"
393
+ )
394
 
395
  # Monthly Data with clear titles (no help added here)
396
  monthly_temps = {}
 
419
  try:
420
  # Generate ID internally using country and city from session_state
421
  generated_id = f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}"
422
+ manual_data = {
423
+ "winter_temp": winter_design_temp,
424
+ "wind_speed": wind_speed,
425
+ "pressure": ClimateLocation.adjust_pressure_for_altitude(None, elevation)
426
+ }
427
  location = ClimateLocation(
428
+ manual_data=manual_data,
429
  id=generated_id,
430
  country=session_state.building_info["country"],
431
  state_province="N/A", # Default since input removed
 
436
  climate_zone=climate_zone,
437
  heating_degree_days=hdd,
438
  cooling_degree_days=cdd,
 
439
  summer_design_temp_db=summer_design_temp_db,
440
  summer_design_temp_wb=summer_design_temp_wb,
441
  summer_daily_range=summer_daily_range,
 
454
  except Exception as e:
455
  st.error(f"Error saving climate data: {str(e)}. Please check inputs and try again.")
456
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  col1, col2 = st.columns(2)
458
  with col1:
459
  st.button("Back to Building Information", on_click=lambda: setattr(session_state, "page", "Building Information"))
 
483
  "Winter Design Temperature (99.6%)",
484
  "Summer Design Dry-Bulb Temp (0.4%)",
485
  "Summer Design Wet-Bulb Temp (0.4%)",
486
+ "Summer Daily Temperature Range",
487
+ "Wind Speed (m/s)",
488
+ "Atmospheric Pressure (Pa)"
489
  ],
490
  "Value": [
491
  f"{location.latitude}°",
 
497
  f"{location.winter_design_temp} °C",
498
  f"{location.summer_design_temp_db} °C",
499
  f"{location.summer_design_temp_wb} °C",
500
+ f"{location.summer_daily_range} °C",
501
+ f"{location.wind_speed} m/s",
502
+ f"{location.pressure} Pa"
503
  ]
504
  })
505