mabuseif commited on
Commit
e4eaf88
·
verified ·
1 Parent(s): 8c9085b

Update data/climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +84 -65
data/climate_data.py CHANGED
@@ -4,7 +4,7 @@ Extracts climate data from EPW files and provides visualizations inspired by Cli
4
 
5
  Author: Dr Majed Abuseif
6
  Date: May 2025
7
- Version: 2.0.0
8
  """
9
 
10
  from typing import Dict, List, Any, Optional
@@ -18,7 +18,6 @@ import plotly.graph_objects as go
18
  from io import StringIO
19
  import pvlib
20
  from datetime import datetime, timedelta
21
- from plotly.subplots import make_subplots
22
 
23
  # Define paths
24
  DATA_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -263,7 +262,7 @@ class ClimateData:
263
 
264
  location = ClimateLocation(
265
  epw_file=epw_data,
266
- id=f"{country[:2].upper()}-{city[:3].upper()}",
267
  country=country,
268
  state_province=state_province,
269
  city=city,
@@ -312,10 +311,6 @@ class ClimateData:
312
  st.button("Continue to Building Components", on_click=lambda: setattr(session_state, "page", "Building Components"))
313
  else:
314
  st.button("Continue to Building Components", disabled=True)
315
-
316
- if "climate_data" in session_state and session_state["climate_data"]:
317
- st.subheader("Saved Climate Data")
318
- st.json(session_state["climate_data"])
319
 
320
  def display_design_conditions(self, location: ClimateLocation):
321
  """Display design conditions for HVAC calculations using Markdown."""
@@ -365,7 +360,7 @@ class ClimateData:
365
  return "8"
366
 
367
  def plot_psychrometric_chart(self, location: ClimateLocation, epw_data: pd.DataFrame):
368
- """Plot psychrometric chart with ASHRAE 55 comfort zone."""
369
  st.subheader("Psychrometric Chart")
370
 
371
  dry_bulb = pd.to_numeric(epw_data[6], errors='coerce').values
@@ -378,24 +373,27 @@ class ClimateData:
378
  pressure = location.pressure / 1000 # kPa
379
  saturation_pressure = 6.1078 * 10 ** (7.5 * dry_bulb / (dry_bulb + 237.3))
380
  vapor_pressure = humidity / 100 * saturation_pressure
381
- humidity_ratio = 0.62198 * vapor_pressure / (pressure - vapor_pressure)
382
 
383
  fig = go.Figure()
 
 
384
  fig.add_trace(go.Scatter(
385
  x=dry_bulb,
386
- y=humidity_ratio * 1000, # g/kg
387
  mode='markers',
388
- marker=dict(size=5, opacity=0.5),
389
  name='Hourly Conditions'
390
  ))
391
 
392
- # ASHRAE 55 comfort zone (simplified: 20-26°C, 30-60% RH)
393
- comfort_x = [20, 26, 26, 20, 20]
394
- comfort_y = [30, 30, 60, 60, 30]
395
- comfort_hr = 0.62198 * (np.array(comfort_y) / 100 * 6.1078 * 10 ** (7.5 * np.array([23, 23, 23, 23, 23]) / (np.array([23, 23, 23, 23, 23]) + 237.3))) / (pressure - np.array(comfort_y) / 100 * 6.1078 * 10 ** (7.5 * np.array([23, 23, 23, 23, 23]) / (np.array([23, 23, 23, 23, 23]) + 237.3)))
 
396
  fig.add_trace(go.Scatter(
397
- x=comfort_x,
398
- y=comfort_hr * 1000,
399
  mode='lines',
400
  line=dict(color='green', width=2),
401
  fill='toself',
@@ -403,38 +401,58 @@ class ClimateData:
403
  name='ASHRAE 55 Comfort Zone'
404
  ))
405
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  fig.update_layout(
407
  title="Psychrometric Chart",
408
  xaxis_title="Dry-Bulb Temperature (°C)",
409
  yaxis_title="Humidity Ratio (g/kg dry air)",
410
- xaxis=dict(range=[-10, 50]),
411
- yaxis=dict(range=[0, 30]),
412
- showlegend=True
 
413
  )
414
  st.plotly_chart(fig, use_container_width=True)
415
-
416
- # HVAC strategy annotations
417
- outside_comfort = np.sum((dry_bulb < 20) | (dry_bulb > 26) | (humidity < 30) | (humidity > 60))
418
- st.markdown(f"**Hours outside comfort zone**: {outside_comfort} ({outside_comfort/8760*100:.1f}%)")
419
- if outside_comfort > 0:
420
- st.markdown("**HVAC Strategies**:")
421
- if np.any(dry_bulb < 20):
422
- st.markdown("- Heating required in colder periods.")
423
- if np.any(dry_bulb > 26):
424
- st.markdown("- Cooling required in warmer periods.")
425
- if np.any(humidity > 60):
426
- st.markdown("- Dehumidification may be needed in humid conditions.")
427
- if np.any(humidity < 30):
428
- st.markdown("- Humidification may be needed in dry conditions.")
429
 
430
  def plot_sun_shading_chart(self, location: ClimateLocation):
431
- """Plot sun path chart for solstices and equinox."""
432
  st.subheader("Sun Shading Chart")
433
 
434
  dates = [
435
- datetime(2025, 6, 21), # Summer solstice
436
- datetime(2025, 12, 21), # Winter solstice
437
- datetime(2025, 3, 20) # Spring equinox
438
  ]
439
  times = pd.date_range(start="2025-01-01 00:00", end="2025-01-01 23:00", freq='H')
440
  solar_data = []
@@ -453,13 +471,18 @@ class ClimateData:
453
  })
454
 
455
  fig = go.Figure()
456
- for data in solar_data:
 
 
 
457
  fig.add_trace(go.Scatterpolar(
458
  r=data['altitude'],
459
  theta=data['azimuth'],
460
- mode='lines',
461
- name=data['date'],
462
- line=dict(width=2)
 
 
463
  ))
464
 
465
  fig.update_layout(
@@ -478,16 +501,10 @@ class ClimateData:
478
  ticktext=["N", "E", "S", "W"]
479
  )
480
  ),
481
- showlegend=True
 
482
  )
483
  st.plotly_chart(fig, use_container_width=True)
484
-
485
- # Shading recommendations
486
- max_altitude = max([max(data['altitude']) for data in solar_data])
487
- st.markdown(f"**Maximum solar altitude**: {max_altitude:.1f}°")
488
- st.markdown("**Shading Recommendations**:")
489
- st.markdown(f"- Use overhangs or louvers angled to block sun above {max_altitude-10:.1f}° in summer.")
490
- st.markdown("- Allow low-angle winter sun for passive heating.")
491
 
492
  def plot_temperature_range(self, location: ClimateLocation, epw_data: pd.DataFrame):
493
  """Plot monthly temperature ranges with design conditions."""
@@ -544,12 +561,13 @@ class ClimateData:
544
  yaxis_title="Temperature (°C)",
545
  xaxis=dict(tickmode='array', tickvals=list(range(1, 13)), ticktext=month_names),
546
  legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
547
- showlegend=True
 
548
  )
549
  st.plotly_chart(fig, use_container_width=True)
550
 
551
  def plot_wind_rose(self, epw_data: pd.DataFrame):
552
- """Plot wind rose diagram."""
553
  st.subheader("Wind Rose")
554
 
555
  wind_speed = pd.to_numeric(epw_data[21], errors='coerce').values
@@ -558,10 +576,11 @@ class ClimateData:
558
  wind_speed = wind_speed[valid_mask]
559
  wind_direction = wind_direction[valid_mask]
560
 
561
- # Bin data
562
- speed_bins = [0, 2, 4, 6, 8, 10, np.inf]
563
- direction_bins = np.linspace(0, 360, 17)[:-1]
564
- speed_labels = ['0-2', '2-4', '4-6', '6-8', '8-10', '10+']
 
565
 
566
  hist = np.histogram2d(
567
  wind_direction, wind_speed,
@@ -571,16 +590,15 @@ class ClimateData:
571
  hist = hist * 100 # Convert to percentage
572
 
573
  fig = go.Figure()
 
 
574
  for i, speed_label in enumerate(speed_labels):
575
  fig.add_trace(go.Barpolar(
576
  r=hist[:, i],
577
  theta=direction_bins,
578
- width=22.5,
579
  name=speed_label,
580
- marker=dict(
581
- colorscale='Viridis',
582
- showscale=True
583
- ),
584
  opacity=0.8
585
  ))
586
 
@@ -595,11 +613,12 @@ class ClimateData:
595
  angularaxis=dict(
596
  direction="clockwise",
597
  rotation=90,
598
- tickvals=[0, 90, 180, 270],
599
- ticktext=["N", "E", "S", "W"]
600
  )
601
  ),
602
- showlegend=True
 
603
  )
604
  st.plotly_chart(fig, use_container_width=True)
605
 
@@ -622,5 +641,5 @@ class ClimateData:
622
 
623
  if __name__ == "__main__":
624
  climate_data = ClimateData()
625
- session_state = {"building_info": {"country": "Iceland", "city": "Reykjavik"}, "page": "Climate Data"}
626
  climate_data.display_climate_input(session_state)
 
4
 
5
  Author: Dr Majed Abuseif
6
  Date: May 2025
7
+ Version: 2.1.0
8
  """
9
 
10
  from typing import Dict, List, Any, Optional
 
18
  from io import StringIO
19
  import pvlib
20
  from datetime import datetime, timedelta
 
21
 
22
  # Define paths
23
  DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
262
 
263
  location = ClimateLocation(
264
  epw_file=epw_data,
265
+ id=f"{country[:1].upper()}{city[:3].upper()}",
266
  country=country,
267
  state_province=state_province,
268
  city=city,
 
311
  st.button("Continue to Building Components", on_click=lambda: setattr(session_state, "page", "Building Components"))
312
  else:
313
  st.button("Continue to Building Components", disabled=True)
 
 
 
 
314
 
315
  def display_design_conditions(self, location: ClimateLocation):
316
  """Display design conditions for HVAC calculations using Markdown."""
 
360
  return "8"
361
 
362
  def plot_psychrometric_chart(self, location: ClimateLocation, epw_data: pd.DataFrame):
363
+ """Plot psychrometric chart with ASHRAE 55 comfort zone and psychrometric lines."""
364
  st.subheader("Psychrometric Chart")
365
 
366
  dry_bulb = pd.to_numeric(epw_data[6], errors='coerce').values
 
373
  pressure = location.pressure / 1000 # kPa
374
  saturation_pressure = 6.1078 * 10 ** (7.5 * dry_bulb / (dry_bulb + 237.3))
375
  vapor_pressure = humidity / 100 * saturation_pressure
376
+ humidity_ratio = 0.62198 * vapor_pressure / (pressure - vapor_pressure) * 1000 # Convert to g/kg
377
 
378
  fig = go.Figure()
379
+
380
+ # Hourly data points
381
  fig.add_trace(go.Scatter(
382
  x=dry_bulb,
383
+ y=humidity_ratio,
384
  mode='markers',
385
+ marker=dict(size=5, opacity=0.5, color='blue'),
386
  name='Hourly Conditions'
387
  ))
388
 
389
+ # ASHRAE 55 comfort zone (simplified: 20-26°C, adjusted for humidity ratio)
390
+ comfort_db = [20, 26, 26, 20, 20]
391
+ comfort_rh = [30, 30, 60, 60, 30]
392
+ comfort_vp = np.array(comfort_rh) / 100 * 6.1078 * 10 ** (7.5 * np.array(comfort_db) / (np.array(comfort_db) + 237.3))
393
+ comfort_hr = 0.62198 * comfort_vp / (pressure - comfort_vp) * 1000
394
  fig.add_trace(go.Scatter(
395
+ x=comfort_db,
396
+ y=comfort_hr,
397
  mode='lines',
398
  line=dict(color='green', width=2),
399
  fill='toself',
 
401
  name='ASHRAE 55 Comfort Zone'
402
  ))
403
 
404
+ # Constant humidity ratio lines (inspired by Climate Consultant)
405
+ for hr in [5, 10, 15]: # g/kg
406
+ db_range = np.linspace(0, 40, 100)
407
+ vp = (hr / 1000 * pressure) / (0.62198 + hr / 1000)
408
+ rh = vp / (6.1078 * 10 ** (7.5 * db_range / (db_range + 237.3))) * 100
409
+ hr_line = np.full_like(db_range, hr)
410
+ fig.add_trace(go.Scatter(
411
+ x=db_range,
412
+ y=hr_line,
413
+ mode='lines',
414
+ line=dict(color='gray', width=1, dash='dash'),
415
+ name=f'{hr} g/kg',
416
+ showlegend=True
417
+ ))
418
+
419
+ # Constant wet-bulb temperature lines
420
+ wet_bulb_temps = [10, 15, 20]
421
+ for wbt in wet_bulb_temps:
422
+ db_range = np.linspace(0, 40, 100)
423
+ rh_range = np.linspace(5, 95, 100)
424
+ wb_values = self.calculate_wet_bulb(db_range, rh_range)
425
+ vp = rh_range / 100 * (6.1078 * 10 ** (7.5 * db_range / (db_range + 237.3)))
426
+ hr_values = 0.62198 * vp / (pressure - vp) * 1000
427
+ mask = (wb_values >= wbt - 0.5) & (wb_values <= wbt + 0.5)
428
+ if np.any(mask):
429
+ fig.add_trace(go.Scatter(
430
+ x=db_range[mask],
431
+ y=hr_values[mask],
432
+ mode='lines',
433
+ line=dict(color='purple', width=1, dash='dot'),
434
+ name=f'Wet-Bulb {wbt}°C',
435
+ showlegend=True
436
+ ))
437
+
438
  fig.update_layout(
439
  title="Psychrometric Chart",
440
  xaxis_title="Dry-Bulb Temperature (°C)",
441
  yaxis_title="Humidity Ratio (g/kg dry air)",
442
+ xaxis=dict(range=[-5, 40]), # Adjusted for Geelong (3.1°C to 33.0°C)
443
+ yaxis=dict(range=[0, 25]), # Adjusted for typical humidity ratios
444
+ showlegend=True,
445
+ template='plotly_white'
446
  )
447
  st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
 
449
  def plot_sun_shading_chart(self, location: ClimateLocation):
450
+ """Plot sun path chart for summer and winter solstices, inspired by Climate Consultant."""
451
  st.subheader("Sun Shading Chart")
452
 
453
  dates = [
454
+ datetime(2025, 6, 21), # Winter solstice (Southern Hemisphere)
455
+ datetime(2025, 12, 21) # Summer solstice (Southern Hemisphere)
 
456
  ]
457
  times = pd.date_range(start="2025-01-01 00:00", end="2025-01-01 23:00", freq='H')
458
  solar_data = []
 
471
  })
472
 
473
  fig = go.Figure()
474
+ colors = ['orange', 'blue'] # Summer = orange, Winter = blue
475
+ labels = ['Summer Solstice (Dec 21)', 'Winter Solstice (Jun 21)']
476
+
477
+ for i, data in enumerate(solar_data):
478
  fig.add_trace(go.Scatterpolar(
479
  r=data['altitude'],
480
  theta=data['azimuth'],
481
+ mode='lines+markers',
482
+ name=labels[i],
483
+ line=dict(color=colors[i], width=2),
484
+ marker=dict(size=6, color=colors[i]),
485
+ opacity=0.8
486
  ))
487
 
488
  fig.update_layout(
 
501
  ticktext=["N", "E", "S", "W"]
502
  )
503
  ),
504
+ showlegend=True,
505
+ template='plotly_white'
506
  )
507
  st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
508
 
509
  def plot_temperature_range(self, location: ClimateLocation, epw_data: pd.DataFrame):
510
  """Plot monthly temperature ranges with design conditions."""
 
561
  yaxis_title="Temperature (°C)",
562
  xaxis=dict(tickmode='array', tickvals=list(range(1, 13)), ticktext=month_names),
563
  legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
564
+ showlegend=True,
565
+ template='plotly_white'
566
  )
567
  st.plotly_chart(fig, use_container_width=True)
568
 
569
  def plot_wind_rose(self, epw_data: pd.DataFrame):
570
+ """Plot wind rose diagram with improved clarity, inspired by Climate Consultant."""
571
  st.subheader("Wind Rose")
572
 
573
  wind_speed = pd.to_numeric(epw_data[21], errors='coerce').values
 
576
  wind_speed = wind_speed[valid_mask]
577
  wind_direction = wind_direction[valid_mask]
578
 
579
+ # Bin data with 8 directions and tailored speed bins (based on Geelong’s mean wind speed of 4.0 m/s)
580
+ speed_bins = [0, 2, 4, 6, 8, np.inf]
581
+ direction_bins = np.linspace(0, 360, 9)[:-1]
582
+ speed_labels = ['0-2 m/s', '2-4 m/s', '4-6 m/s', '6-8 m/s', '8+ m/s']
583
+ direction_labels = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
584
 
585
  hist = np.histogram2d(
586
  wind_direction, wind_speed,
 
590
  hist = hist * 100 # Convert to percentage
591
 
592
  fig = go.Figure()
593
+ colors = ['#E6F0FF', '#B3D1FF', '#80B2FF', '#4D94FF', '#1A75FF'] # Light to dark blue gradient
594
+
595
  for i, speed_label in enumerate(speed_labels):
596
  fig.add_trace(go.Barpolar(
597
  r=hist[:, i],
598
  theta=direction_bins,
599
+ width=45,
600
  name=speed_label,
601
+ marker=dict(color=colors[i]),
 
 
 
602
  opacity=0.8
603
  ))
604
 
 
613
  angularaxis=dict(
614
  direction="clockwise",
615
  rotation=90,
616
+ tickvals=direction_bins,
617
+ ticktext=direction_labels
618
  )
619
  ),
620
+ showlegend=True,
621
+ template='plotly_white'
622
  )
623
  st.plotly_chart(fig, use_container_width=True)
624
 
 
641
 
642
  if __name__ == "__main__":
643
  climate_data = ClimateData()
644
+ session_state = {"building_info": {"country": "Australia", "city": "Geelong"}, "page": "Climate Data"}
645
  climate_data.display_climate_input(session_state)