mabuseif commited on
Commit
2b900dd
·
verified ·
1 Parent(s): 7ecdf77

Upload climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +84 -153
data/climate_data.py CHANGED
@@ -21,11 +21,6 @@ from datetime import datetime, timedelta
21
  import re
22
  import logging
23
  from os.path import join as os_join
24
- import psychrolib
25
- from psychrochart import PsychroChart
26
- import matplotlib.pyplot as plt
27
- from io import BytesIO
28
- import base64
29
 
30
  # Set up logging
31
  logging.basicConfig(level=logging.INFO)
@@ -1077,155 +1072,91 @@ class ClimateData:
1077
  return "8"
1078
 
1079
  def plot_psychrometric_chart(self, location: ClimateLocation, epw_data: pd.DataFrame):
1080
- """Plot psychrometric chart with ASHRAE 55 comfort zone and psychrometric lines using psychrochart."""
1081
  st.subheader("Psychrometric Chart")
1082
-
1083
- try:
1084
- # Initialize psychrolib for SI units
1085
- psychrolib.SetUnitSystem(psychrolib.SI)
1086
-
1087
- # Extract data from EPW
1088
- dry_bulb = pd.to_numeric(epw_data[6], errors='coerce').values
1089
- humidity = pd.to_numeric(epw_data[8], errors='coerce').values
1090
- valid_mask = ~np.isnan(dry_bulb) & ~np.isnan(humidity)
1091
- dry_bulb = dry_bulb[valid_mask]
1092
- humidity = humidity[valid_mask]
1093
- pressure = location.pressure # Pa
1094
-
1095
- if len(dry_bulb) == 0:
1096
- st.error("No valid data points for psychrometric chart after filtering NaN values.")
1097
- logger.error("Empty dry_bulb or humidity arrays after filtering.")
1098
- return
1099
-
1100
- # Calculate humidity ratio (g/kg dry air) with error handling
1101
- humidity_ratio = np.zeros_like(dry_bulb)
1102
- for i, (t, rh) in enumerate(zip(dry_bulb, humidity)):
1103
- try:
1104
- hr = psychrolib.GetHumRatioFromRelHum(t, rh / 100, pressure) * 1000
1105
- humidity_ratio[i] = hr if not np.isnan(hr) else 0.0
1106
- except Exception as e:
1107
- logger.warning(f"Error calculating humidity ratio for index {i}: {str(e)}")
1108
- humidity_ratio[i] = 0.0
1109
-
1110
- valid_hr_mask = (humidity_ratio > 0) & ~np.isnan(humidity_ratio)
1111
- dry_bulb = dry_bulb[valid_hr_mask]
1112
- humidity_ratio = humidity_ratio[valid_hr_mask]
1113
-
1114
- if len(dry_bulb) == 0:
1115
- st.error("No valid humidity ratio data points after calculation.")
1116
- logger.error("Empty humidity_ratio array after calculation.")
1117
- return
1118
-
1119
- # Define axis limits based on data
1120
- temp_min = float(np.min(dry_bulb)) - 5 if len(dry_bulb) > 0 else -10
1121
- temp_max = float(np.max(dry_bulb)) + 5 if len(dry_bulb) > 0 else 40
1122
- hr_max = float(np.max(humidity_ratio)) + 5 if len(humidity_ratio) > 0 else 30
1123
-
1124
- # Define custom style for psychrochart
1125
- custom_style = {
1126
- "figure": {
1127
- "x_label": "Dry-Bulb Temperature (°C)",
1128
- "y_label": "Humidity Ratio (g/kg dry air)",
1129
- "x_axis": {"color": [0.0, 0.0, 0.0], "linewidth": 1.5, "linestyle": "-"},
1130
- "x_axis_labels": {"color": [0.0, 0.0, 0.0], "fontsize": 8},
1131
- "x_axis_ticks": {"direction": "out", "color": [0.0, 0.0, 0.0]},
1132
- "y_axis": {"color": [0.0, 0.0, 0.0], "linewidth": 1.5, "linestyle": "-"},
1133
- "y_axis_labels": {"color": [0.0, 0.0, 0.0], "fontsize": 8},
1134
- "y_axis_ticks": {"direction": "out", "color": [0.0, 0.0, 0.0]},
1135
- "partial_axis": False,
1136
- "position": [0.025, 0.075, 0.925, 0.875]
1137
- },
1138
- "limits": {
1139
- "range_temp_c": [max(-10, temp_min), min(40, temp_max)],
1140
- "range_humidity_g_kg": [0, max(30, hr_max)],
1141
- "altitude_m": location.elevation,
1142
- "step_temp": 1.0
1143
- },
1144
- "saturation": {"color": [0.0, 0.0, 0.0], "linewidth": 2, "linestyle": "-"},
1145
- "constant_rh": {"color": [0.8, 0.0, 0.0], "linewidth": 1, "linestyle": "-"},
1146
- "constant_v": {"color": [0.0, 0.4, 0.4], "linewidth": 0.5, "linestyle": "-"},
1147
- "constant_h": {"color": [1.0, 0.4, 0.0], "linewidth": 0.75, "linestyle": "-"},
1148
- "constant_wet_temp": {"color": [0.2, 0.8, 0.2], "linewidth": 1, "linestyle": "--"},
1149
- "constant_dry_temp": {"color": [0.0, 0.0, 0.0], "linewidth": 0.25, "linestyle": "-"},
1150
- "constant_humidity": {"color": [0.0, 0.0, 0.0], "linewidth": 0.25, "linestyle": "-"},
1151
- "chart_params": {
1152
- "with_constant_rh": True,
1153
- "constant_rh_curves": [20, 30, 40, 50, 60, 70, 80, 90],
1154
- "constant_rh_labels": [20, 40, 60, 80],
1155
- "with_constant_v": False,
1156
- "constant_v_step": 0.01,
1157
- "range_vol_m3_kg": [0.78, 0.96],
1158
- "with_constant_h": True,
1159
- "constant_h_step": 10,
1160
- "constant_h_labels": [0],
1161
- "range_h": [10, 130],
1162
- "with_constant_wet_temp": True,
1163
- "constant_wet_temp_step": 1,
1164
- "range_wet_temp": [-10, 35],
1165
- "constant_wet_temp_labels": [0, 5, 10, 15, 20, 25, 30],
1166
- "with_constant_dry_temp": True,
1167
- "constant_temp_step": 5,
1168
- "with_constant_humidity": True,
1169
- "constant_humid_step": 2,
1170
- "with_zones": True
1171
- }
1172
- }
1173
-
1174
- # Create matplotlib figure
1175
- fig, ax = plt.subplots(figsize=(7, 3))
1176
- chart = PsychroChart(custom_style) # Pass custom_style as positional argument
1177
- chart.plot(ax)
1178
-
1179
- # Add ASHRAE 55 comfort zones
1180
- zones_conf = {
1181
- "zones": [
1182
- {
1183
- "zone_type": "dbt-rh",
1184
- "style": {
1185
- "edgecolor": [0.0, 0.8, 0.0, 0.8],
1186
- "facecolor": [0.0, 1.0, 0.0, 0.2],
1187
- "linewidth": 2,
1188
- "linestyle": "--"
1189
- },
1190
- "points_x": [20, 26],
1191
- "points_y": [30, 60],
1192
- "label": "ASHRAE 55 Comfort Zone"
1193
- }
1194
- ]
1195
- }
1196
- chart.append_zones(zones_conf)
1197
-
1198
- # Subsample points to improve performance (every 10th point)
1199
- subsample_idx = np.arange(0, len(dry_bulb), 10)
1200
- dry_bulb_sub = dry_bulb[subsample_idx]
1201
- humidity_ratio_sub = humidity_ratio[subsample_idx]
1202
-
1203
- # Plot hourly data points
1204
- points = {
1205
- f"hour_{i}": {
1206
- "label": "",
1207
- "style": {"color": [0.0, 0.0, 1.0, 0.5], "marker": ".", "markersize": 3},
1208
- "xy": (db, hr)
1209
- }
1210
- for i, (db, hr) in enumerate(zip(dry_bulb_sub, humidity_ratio_sub))
1211
- }
1212
- chart.plot_points_dbt_rh(points, connectors=[])
1213
-
1214
- # Add legend
1215
- chart.plot_legend(markerscale=0.7, frameon=False, fontsize=8, labelspacing=1.2)
1216
-
1217
- # Save plot to PNG for faster rendering
1218
- buffer = BytesIO()
1219
- plt.savefig(buffer, format="png", dpi=100, bbox_inches="tight")
1220
- buffer.seek(0)
1221
- plt.close(fig)
1222
-
1223
- # Display in Streamlit using st.image
1224
- st.image(buffer, use_column_width="auto", caption="Psychrometric Chart")
1225
-
1226
- except Exception as e:
1227
- st.error(f"Error rendering psychrometric chart: {str(e)}")
1228
- logger.error(f"Psychrometric chart error: {str(e)}", exc_info=True)
1229
 
1230
  def plot_sun_shading_chart(self, location: ClimateLocation):
1231
  """Plot sun path chart for summer and winter solstices, inspired by Climate Consultant."""
 
21
  import re
22
  import logging
23
  from os.path import join as os_join
 
 
 
 
 
24
 
25
  # Set up logging
26
  logging.basicConfig(level=logging.INFO)
 
1072
  return "8"
1073
 
1074
  def plot_psychrometric_chart(self, location: ClimateLocation, epw_data: pd.DataFrame):
1075
+ """Plot psychrometric chart with ASHRAE 55 comfort zone and psychrometric lines."""
1076
  st.subheader("Psychrometric Chart")
1077
+
1078
+ dry_bulb = pd.to_numeric(epw_data[6], errors='coerce').values
1079
+ humidity = pd.to_numeric(epw_data[8], errors='coerce').values
1080
+ valid_mask = ~np.isnan(dry_bulb) & ~np.isnan(humidity)
1081
+ dry_bulb = dry_bulb[valid_mask]
1082
+ humidity = humidity[valid_mask]
1083
+
1084
+ # Calculate humidity ratio (kg/kg dry air)
1085
+ pressure = location.pressure / 1000 # kPa
1086
+ saturation_pressure = 6.1078 * 10 ** (7.5 * dry_bulb / (dry_bulb + 237.3))
1087
+ vapor_pressure = humidity / 100 * saturation_pressure
1088
+ humidity_ratio = 0.62198 * vapor_pressure / (pressure - vapor_pressure) * 1000 # Convert to g/kg
1089
+
1090
+ fig = go.Figure()
1091
+
1092
+ # Hourly data points
1093
+ fig.add_trace(go.Scatter(
1094
+ x=dry_bulb,
1095
+ y=humidity_ratio,
1096
+ mode='markers',
1097
+ marker=dict(size=5, opacity=0.5, color='blue'),
1098
+ name='Hourly Conditions'
1099
+ ))
1100
+
1101
+ # ASHRAE 55 comfort zone
1102
+ comfort_db = [20, 26, 26, 20, 20]
1103
+ comfort_rh = [30, 30, 60, 60, 30]
1104
+ comfort_vp = np.array(comfort_rh) / 100 * 6.1078 * 10 ** (7.5 * np.array(comfort_db) / (np.array(comfort_db) + 237.3))
1105
+ comfort_hr = 0.62198 * comfort_vp / (pressure - comfort_vp) * 1000
1106
+ fig.add_trace(go.Scatter(
1107
+ x=comfort_db,
1108
+ y=comfort_hr,
1109
+ mode='lines',
1110
+ line=dict(color='green', width=2),
1111
+ fill='toself',
1112
+ fillcolor='rgba(0, 255, 0, 0.2)',
1113
+ name='ASHRAE 55 Comfort Zone'
1114
+ ))
1115
+
1116
+ # Constant humidity ratio lines
1117
+ for hr in [5, 10, 15]:
1118
+ db_range = np.linspace(0, 40, 100)
1119
+ vp = (hr / 1000 * pressure) / (0.62198 + hr / 1000)
1120
+ rh = vp / (6.1078 * 10 ** (7.5 * db_range / (db_range + 237.3))) * 100
1121
+ hr_line = np.full_like(db_range, hr)
1122
+ fig.add_trace(go.Scatter(
1123
+ x=db_range,
1124
+ y=hr_line,
1125
+ mode='lines',
1126
+ line=dict(color='gray', width=1, dash='dash'),
1127
+ name=f'{hr} g/kg',
1128
+ showlegend=True
1129
+ ))
1130
+
1131
+ # Constant wet-bulb temperature lines
1132
+ wet_bulb_temps = [10, 15, 20]
1133
+ for wbt in wet_bulb_temps:
1134
+ db_range = np.linspace(0, 40, 100)
1135
+ rh_range = np.linspace(5, 95, 100)
1136
+ wb_values = self.calculate_wet_bulb(db_range, rh_range)
1137
+ vp = rh_range / 100 * (6.1078 * 10 ** (7.5 * db_range / (db_range + 237.3)))
1138
+ hr_values = 0.62198 * vp / (pressure - vp) * 1000
1139
+ mask = (wb_values >= wbt - 0.5) & (wb_values <= wbt + 0.5)
1140
+ if np.any(mask):
1141
+ fig.add_trace(go.Scatter(
1142
+ x=db_range[mask],
1143
+ y=hr_values[mask],
1144
+ mode='lines',
1145
+ line=dict(color='purple', width=1, dash='dot'),
1146
+ name=f'Wet-Bulb {wbt}°C',
1147
+ showlegend=True
1148
+ ))
1149
+
1150
+ fig.update_layout(
1151
+ title="Psychrometric Chart",
1152
+ xaxis_title="Dry-Bulb Temperature (°C)",
1153
+ yaxis_title="Humidity Ratio (g/kg dry air)",
1154
+ xaxis=dict(range=[-5, 40]),
1155
+ yaxis=dict(range=[0, 25]),
1156
+ showlegend=True,
1157
+ template='plotly_white'
1158
+ )
1159
+ st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1160
 
1161
  def plot_sun_shading_chart(self, location: ClimateLocation):
1162
  """Plot sun path chart for summer and winter solstices, inspired by Climate Consultant."""