Spaces:
Sleeping
Sleeping
Upload climate_data.py
Browse files- 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
|
| 1081 |
st.subheader("Psychrometric Chart")
|
| 1082 |
-
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
|
| 1086 |
-
|
| 1087 |
-
|
| 1088 |
-
|
| 1089 |
-
|
| 1090 |
-
|
| 1091 |
-
|
| 1092 |
-
|
| 1093 |
-
|
| 1094 |
-
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
|
| 1132 |
-
|
| 1133 |
-
|
| 1134 |
-
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
-
|
| 1138 |
-
|
| 1139 |
-
|
| 1140 |
-
|
| 1141 |
-
|
| 1142 |
-
|
| 1143 |
-
|
| 1144 |
-
|
| 1145 |
-
|
| 1146 |
-
|
| 1147 |
-
|
| 1148 |
-
|
| 1149 |
-
|
| 1150 |
-
|
| 1151 |
-
|
| 1152 |
-
|
| 1153 |
-
|
| 1154 |
-
|
| 1155 |
-
|
| 1156 |
-
|
| 1157 |
-
|
| 1158 |
-
|
| 1159 |
-
|
| 1160 |
-
|
| 1161 |
-
|
| 1162 |
-
|
| 1163 |
-
|
| 1164 |
-
|
| 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."""
|