mabuseif commited on
Commit
a76c414
·
verified ·
1 Parent(s): 11fa5f7

Update data/climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +93 -86
data/climate_data.py CHANGED
@@ -81,12 +81,13 @@ class ClimateLocation:
81
  pressure: float # Mean atmospheric pressure (Pa)
82
  direct_normal_irradiance: float # Mean DNI (W/m²)
83
  diffuse_horizontal_irradiance: float # Mean DHI (W/m²)
 
84
  hourly_data: List[Dict] # Hourly data for integration with main.py
85
  typical_extreme_periods: Dict[str, Dict] # Typical/extreme periods (summer/winter)
86
  ground_temperatures: Dict[str, List[float]] # Monthly ground temperatures by depth
87
- solstice_zenith_angles: Dict[str, float] # Summer and winter solstice zenith angles
88
- _solar_cache: Dict[str, Dict[str, float]] # Cache for solar angles
89
-
90
  def __init__(self, epw_file: pd.DataFrame, typical_extreme_periods: Dict, ground_temperatures: Dict, albedo: float = 0.2, **kwargs):
91
  """Initialize ClimateLocation with EPW file data and header information."""
92
  self.id = kwargs.get("id")
@@ -97,10 +98,11 @@ class ClimateLocation:
97
  self.longitude = kwargs.get("longitude")
98
  self.elevation = kwargs.get("elevation")
99
  self.time_zone = kwargs.get("time_zone")
 
100
  self.typical_extreme_periods = typical_extreme_periods
101
  self.ground_temperatures = ground_temperatures
102
  self._solar_cache = {}
103
- self.albedo = albedo # Default albedo from ASHRAE Fundamentals, Chapter 14, Table 4
104
 
105
  # Extract columns from EPW data
106
  months = pd.to_numeric(epw_file[1], errors='coerce').values
@@ -120,9 +122,9 @@ class ClimateLocation:
120
  if (wind_speed > 15).any():
121
  logger.warning(f"High wind speeds detected: {wind_speed[wind_speed > 15].tolist()}")
122
 
123
- # Filter irradiance outliers
124
- direct_normal_irradiance = direct_normal_irradiance[direct_normal_irradiance <= 1200]
125
- diffuse_horizontal_irradiance = diffuse_horizontal_irradiance[diffuse_horizontal_irradiance <= 1200]
126
  if (direct_normal_irradiance > 1000).any():
127
  logger.warning(f"High DNI values detected: {direct_normal_irradiance[direct_normal_irradiance > 1000].tolist()}")
128
  if (diffuse_horizontal_irradiance > 600).any():
@@ -163,26 +165,11 @@ class ClimateLocation:
163
  # Assign climate zone
164
  self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
165
 
166
- # Calculate solstice zenith angles
167
- self.solstice_zenith_angles = self._calculate_solstice_zenith_angles()
168
-
169
  # Store hourly data with enhanced fields
170
  self.hourly_data = []
171
  for i in range(len(months)):
172
  if np.isnan(months[i]) or np.isnan(days[i]) or np.isnan(hours[i]) or np.isnan(dry_bulb[i]):
173
  continue # Skip records with missing critical fields
174
- # Calculate solar time and angles
175
- timestamp = datetime(2025, int(months[i]), int(days[i]), int(hours[i]) - 1) # Hour is 1-24, adjust to 0-23
176
- solar_time = self._calculate_solar_time(timestamp)
177
- solar_angles = self._calculate_solar_angles(timestamp)
178
-
179
- # Handle DNI and DHI: set to 0 if missing or negative
180
- dni = float(direct_normal_irradiance[i]) if not np.isnan(direct_normal_irradiance[i]) and direct_normal_irradiance[i] >= 0 else 0.0
181
- dhi = float(diffuse_horizontal_irradiance[i]) if not np.isnan(diffuse_horizontal_irradiance[i]) and diffuse_horizontal_irradiance[i] >= 0 else 0.0
182
-
183
- # Calculate ground-reflected radiation
184
- ground_reflected = self.albedo * (dni * np.cos(np.radians(solar_angles['zenith'])) + dhi)
185
-
186
  record = {
187
  "month": int(months[i]),
188
  "day": int(days[i]),
@@ -191,14 +178,10 @@ class ClimateLocation:
191
  "relative_humidity": float(humidity[i]) if not np.isnan(humidity[i]) else 0.0,
192
  "atmospheric_pressure": float(pressure[i]) if not np.isnan(pressure[i]) else self.pressure,
193
  "global_horizontal_radiation": float(global_radiation[i]) if not np.isnan(global_radiation[i]) else 0.0,
194
- "direct_normal_irradiance": dni,
195
- "diffuse_horizontal_irradiance": dhi,
196
  "wind_speed": float(wind_speed[i]) if not np.isnan(wind_speed[i]) else 0.0,
197
- "wind_direction": float(wind_direction[i]) if not np.isnan(wind_direction[i]) else 0.0,
198
- "solar_time": float(solar_time),
199
- "solar_zenith": float(solar_angles['zenith']),
200
- "solar_azimuth": float(solar_angles['azimuth']),
201
- "ground_reflected_radiation": float(ground_reflected)
202
  }
203
  self.hourly_data.append(record)
204
 
@@ -206,27 +189,24 @@ class ClimateLocation:
206
  st.warning(f"Hourly data has {len(self.hourly_data)} records instead of 8760. Some records may have been excluded due to missing data.")
207
 
208
  def _calculate_solar_time(self, timestamp: datetime) -> float:
209
- """Calculate solar time from local time, adjusting for longitude and time zone (ASHRAE Fundamentals, Chapter 14, Section 14.7)."""
210
- # Standard meridian for the time zone
211
- standard_meridian = self.time_zone * 15 # Time zone offset in degrees (15° per hour)
212
- # Longitude correction (4 minutes per degree)
213
- longitude_correction = (self.longitude - standard_meridian) * 4 / 60 # in hours
214
- # Equation of time (simplified approximation, could use pvlib for precision)
215
  day_of_year = timestamp.timetuple().tm_yday
216
  B = (day_of_year - 1) * 360 / 365.242
217
- E = 229.18 * (0.000075 + 0.001868 * np.cos(np.radians(B)) - 0.032077 * np.sin(np.radians(B)) -
218
- 0.014615 * np.cos(np.radians(2 * B)) - 0.04089 * np.sin(np.radians(2 * B))) / 60 # in hours
219
- # Local standard time in hours
 
 
 
 
220
  local_std_time = timestamp.hour + timestamp.minute / 60
221
- # Solar time
222
  solar_time = local_std_time + longitude_correction + E
223
- # Normalize to 0-24 hours
224
- solar_time = solar_time % 24
225
- return solar_time
226
 
227
  def _calculate_solar_angles(self, timestamp: datetime) -> Dict[str, float]:
228
- """Calculate solar zenith and azimuth angles, caching results (ASHRAE Fundamentals, Chapter 14, Section 14.7)."""
229
- # Create cache key
230
  cache_key = hashlib.md5(
231
  f"{timestamp.strftime('%Y-%m-%d_%H')}_{self.latitude}_{self.longitude}_{self.time_zone}".encode()
232
  ).hexdigest()
@@ -234,40 +214,59 @@ class ClimateLocation:
234
  if cache_key in self._solar_cache:
235
  return self._solar_cache[cache_key]
236
 
237
- # Use pvlib for solar position
238
  solpos = pvlib.solarposition.get_solarposition(
239
  time=timestamp,
240
  latitude=self.latitude,
241
  longitude=self.longitude,
242
  altitude=self.elevation
243
  )
244
- zenith = solpos['zenith'].iloc[0]
245
- azimuth = solpos['azimuth'].iloc[0]
246
-
247
- # Cache results
248
- self._solar_cache[cache_key] = {
249
- 'zenith': zenith,
250
- 'azimuth': azimuth
251
  }
252
- return self._solar_cache[cache_key]
 
253
 
254
  def _calculate_solstice_zenith_angles(self) -> Dict[str, float]:
255
- """Calculate solar zenith angles for summer and winter solstices at noon."""
256
- dates = [
257
- datetime(2025, 12, 21, 12), # Summer solstice (Southern Hemisphere)
258
- datetime(2025, 6, 21, 12) # Winter solstice (Southern Hemisphere)
259
- ]
260
- solstice_zeniths = {}
261
- for date in dates:
262
  solpos = pvlib.solarposition.get_solarposition(
263
  time=date,
264
  latitude=self.latitude,
265
  longitude=self.longitude,
266
  altitude=self.elevation
267
  )
268
- key = 'summer' if date.month == 12 else 'winter'
269
- solstice_zeniths[key] = round(float(solpos['zenith'].iloc[0]), 1)
270
- return solstice_zeniths
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  def to_dict(self) -> Dict[str, Any]:
273
  """Convert the climate location to a dictionary."""
@@ -343,8 +342,8 @@ class ClimateData:
343
  "climate_zone", "heating_degree_days", "cooling_degree_days",
344
  "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
345
  "summer_daily_range", "wind_speed", "pressure",
346
- "direct_normal_irradiance", "diffuse_horizontal_irradiance", "hourly_data",
347
- "albedo", "solstice_zenith_angles"
348
  ]
349
 
350
  for field in required_fields:
@@ -391,6 +390,13 @@ class ClimateData:
391
  if not (0 <= data["albedo"] <= 1):
392
  st.error(f"Validation failed: Albedo {data['albedo']} outside range")
393
  return False
 
 
 
 
 
 
 
394
  if not data["hourly_data"] or len(data["hourly_data"]) < 8700:
395
  st.error(f"Validation failed: Hourly data has {len(data['hourly_data'])} records, expected ~8760")
396
  return False
@@ -428,16 +434,17 @@ class ClimateData:
428
  if not (0 <= record["wind_direction"] <= 360):
429
  st.error(f"Validation failed: Wind direction {record['wind_direction']} outside range")
430
  return False
431
- if not (0 <= record["solar_time"] < 24):
 
432
  st.error(f"Validation failed: Solar time {record['solar_time']} outside range")
433
  return False
434
- if not (0 <= record["solar_zenith"] <= 180):
435
  st.error(f"Validation failed: Solar zenith {record['solar_zenith']} outside range")
436
  return False
437
- if not (0 <= record["solar_azimuth"] <= 360):
438
  st.error(f"Validation failed: Solar azimuth {record['solar_azimuth']} outside range")
439
  return False
440
- if not (0 <= record["ground_reflected_radiation"] <= 1200):
441
  st.error(f"Validation failed: Ground reflected radiation {record['ground_reflected_radiation']} outside range")
442
  return False
443
 
@@ -460,14 +467,6 @@ class ClimateData:
460
  st.error(f"Validation failed: Invalid ground temperatures for depth {depth}")
461
  return False
462
 
463
- # Validate solstice zenith angles
464
- if "solstice_zenith_angles" not in data or not all(key in data["solstice_zenith_angles"] for key in ["summer", "winter"]):
465
- st.error("Validation failed: Missing or incomplete solstice zenith angles")
466
- return False
467
- if not (0 <= data["solstice_zenith_angles"]["summer"] <= 180 and 0 <= data["solstice_zenith_angles"]["winter"] <= 180):
468
- st.error("Validation failed: Invalid solstice zenith angles")
469
- return False
470
-
471
  return True
472
 
473
  @staticmethod
@@ -560,7 +559,7 @@ class ClimateData:
560
  period_name = parts[2 + i*4]
561
  period_type = parts[3 + i*4]
562
  start_date = parts[4 + i*4].strip()
563
- end_date = bits[5 + i*4].strip()
564
  if period_name in [
565
  "Summer - Week Nearest Max Temperature For Period",
566
  "Summer - Week Nearest Average Temperature For Period",
@@ -632,7 +631,7 @@ class ClimateData:
632
  epw_file=epw_data,
633
  typical_extreme_periods=typical_extreme_periods,
634
  ground_temperatures=ground_temperatures,
635
- albedo=albedo, # Pass albedo from main
636
  id=f"{country[:1].upper()}{city[:3].upper()}",
637
  country=country,
638
  state_province=state_province,
@@ -676,7 +675,7 @@ class ClimateData:
676
  epw_file=epw_data,
677
  typical_extreme_periods=climate_data_dict["typical_extreme_periods"],
678
  ground_temperatures=climate_data_dict["ground_temperatures"],
679
- albedo=climate_data_dict["albedo"],
680
  id=climate_data_dict["id"],
681
  country=climate_data_dict["country"],
682
  state_province=climate_data_dict["state_province"],
@@ -686,12 +685,20 @@ class ClimateData:
686
  elevation=climate_data_dict["elevation"],
687
  time_zone=climate_data_dict["time_zone"]
688
  )
689
- # Override hourly_data to ensure consistency
690
  location.hourly_data = climate_data_dict["hourly_data"]
691
- location.solstice_zenith_angles = climate_data_dict["solstice_zenith_angles"]
692
  self.add_location(location)
693
  st.info("Displaying previously extracted climate data.")
694
 
 
 
 
 
 
 
 
 
695
  # Display tabs if location and epw_data are available
696
  if location and epw_data is not None:
697
  tab1, tab2, tab3, tab4, tab5 = st.tabs([
@@ -1107,7 +1114,7 @@ class ClimateData:
1107
  epw_file=epw_data,
1108
  typical_extreme_periods=loc_dict["typical_extreme_periods"],
1109
  ground_temperatures=loc_dict["ground_temperatures"],
1110
- albedo=loc_dict["albedo"],
1111
  id=loc_dict["id"],
1112
  country=loc_dict["country"],
1113
  state_province=loc_dict["state_province"],
@@ -1117,12 +1124,12 @@ class ClimateData:
1117
  elevation=loc_dict["elevation"],
1118
  time_zone=loc_dict["time_zone"]
1119
  )
1120
- location.hourly_data = loc_dict["hourly_data"] # Restore all hourly data including solar and radiation fields
1121
- location.solstice_zenith_angles = loc_dict["solstice_zenith_angles"]
1122
  climate_data.add_location(location)
1123
  return climate_data
1124
 
1125
  if __name__ == "__main__":
1126
  climate_data = ClimateData()
1127
  session_state = {"building_info": {"country": "Australia", "city": "Geelong"}, "page": "Climate Data"}
1128
- climate_data.display_climate_input(session_state, albedo=0.2) # Default albedo
 
81
  pressure: float # Mean atmospheric pressure (Pa)
82
  direct_normal_irradiance: float # Mean DNI (W/m²)
83
  diffuse_horizontal_irradiance: float # Mean DHI (W/m²)
84
+ albedo: float # Surface reflectivity (0-1)
85
  hourly_data: List[Dict] # Hourly data for integration with main.py
86
  typical_extreme_periods: Dict[str, Dict] # Typical/extreme periods (summer/winter)
87
  ground_temperatures: Dict[str, List[float]] # Monthly ground temperatures by depth
88
+ solstice_zenith_angles: Dict[str, float] # Zenith angles for summer/winter solstices
89
+ _solar_cache: Dict[str, Dict[str, float]] # Cache for solar angle calculations
90
+
91
  def __init__(self, epw_file: pd.DataFrame, typical_extreme_periods: Dict, ground_temperatures: Dict, albedo: float = 0.2, **kwargs):
92
  """Initialize ClimateLocation with EPW file data and header information."""
93
  self.id = kwargs.get("id")
 
98
  self.longitude = kwargs.get("longitude")
99
  self.elevation = kwargs.get("elevation")
100
  self.time_zone = kwargs.get("time_zone")
101
+ self.albedo = albedo
102
  self.typical_extreme_periods = typical_extreme_periods
103
  self.ground_temperatures = ground_temperatures
104
  self._solar_cache = {}
105
+ self.solstice_zenith_angles = self._calculate_solstice_zenith_angles()
106
 
107
  # Extract columns from EPW data
108
  months = pd.to_numeric(epw_file[1], errors='coerce').values
 
122
  if (wind_speed > 15).any():
123
  logger.warning(f"High wind speeds detected: {wind_speed[wind_speed > 15].tolist()}")
124
 
125
+ # Filter irradiance outliers and handle negative/missing values
126
+ direct_normal_irradiance = np.where((np.isnan(direct_normal_irradiance) | (direct_normal_irradiance < 0)), 0.0, direct_normal_irradiance)
127
+ diffuse_horizontal_irradiance = np.where((np.isnan(diffuse_horizontal_irradiance) | (diffuse_horizontal_irradiance < 0)), 0.0, diffuse_horizontal_irradiance)
128
  if (direct_normal_irradiance > 1000).any():
129
  logger.warning(f"High DNI values detected: {direct_normal_irradiance[direct_normal_irradiance > 1000].tolist()}")
130
  if (diffuse_horizontal_irradiance > 600).any():
 
165
  # Assign climate zone
166
  self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
167
 
 
 
 
168
  # Store hourly data with enhanced fields
169
  self.hourly_data = []
170
  for i in range(len(months)):
171
  if np.isnan(months[i]) or np.isnan(days[i]) or np.isnan(hours[i]) or np.isnan(dry_bulb[i]):
172
  continue # Skip records with missing critical fields
 
 
 
 
 
 
 
 
 
 
 
 
173
  record = {
174
  "month": int(months[i]),
175
  "day": int(days[i]),
 
178
  "relative_humidity": float(humidity[i]) if not np.isnan(humidity[i]) else 0.0,
179
  "atmospheric_pressure": float(pressure[i]) if not np.isnan(pressure[i]) else self.pressure,
180
  "global_horizontal_radiation": float(global_radiation[i]) if not np.isnan(global_radiation[i]) else 0.0,
181
+ "direct_normal_irradiance": float(direct_normal_irradiance[i]),
182
+ "diffuse_horizontal_irradiance": float(diffuse_horizontal_irradiance[i]),
183
  "wind_speed": float(wind_speed[i]) if not np.isnan(wind_speed[i]) else 0.0,
184
+ "wind_direction": float(wind_direction[i]) if not np.isnan(wind_direction[i]) else 0.0
 
 
 
 
185
  }
186
  self.hourly_data.append(record)
187
 
 
189
  st.warning(f"Hourly data has {len(self.hourly_data)} records instead of 8760. Some records may have been excluded due to missing data.")
190
 
191
  def _calculate_solar_time(self, timestamp: datetime) -> float:
192
+ """Calculate solar time using ASHRAE Fundamentals, Chapter 14, Section 14.7."""
193
+ standard_meridian = self.time_zone * 15
194
+ longitude_correction = (self.longitude - standard_meridian) * 4 / 60
 
 
 
195
  day_of_year = timestamp.timetuple().tm_yday
196
  B = (day_of_year - 1) * 360 / 365.242
197
+ E = 229.18 * (
198
+ 0.000075 +
199
+ 0.001868 * np.cos(np.radians(B)) -
200
+ 0.032077 * np.sin(np.radians(B)) -
201
+ 0.014615 * np.cos(np.radians(2 * B)) -
202
+ 0.04089 * np.sin(np.radians(2 * B))
203
+ ) / 60
204
  local_std_time = timestamp.hour + timestamp.minute / 60
 
205
  solar_time = local_std_time + longitude_correction + E
206
+ return solar_time % 24
 
 
207
 
208
  def _calculate_solar_angles(self, timestamp: datetime) -> Dict[str, float]:
209
+ """Compute solar zenith and azimuth angles with caching."""
 
210
  cache_key = hashlib.md5(
211
  f"{timestamp.strftime('%Y-%m-%d_%H')}_{self.latitude}_{self.longitude}_{self.time_zone}".encode()
212
  ).hexdigest()
 
214
  if cache_key in self._solar_cache:
215
  return self._solar_cache[cache_key]
216
 
 
217
  solpos = pvlib.solarposition.get_solarposition(
218
  time=timestamp,
219
  latitude=self.latitude,
220
  longitude=self.longitude,
221
  altitude=self.elevation
222
  )
223
+ result = {
224
+ 'zenith': float(solpos['zenith'].iloc[0]),
225
+ 'azimuth': float(solpos['azimuth'].iloc[0])
 
 
 
 
226
  }
227
+ self._solar_cache[cache_key] = result
228
+ return result
229
 
230
  def _calculate_solstice_zenith_angles(self) -> Dict[str, float]:
231
+ """Compute zenith angles for summer and winter solstices at noon."""
232
+ solstices = {
233
+ 'summer': datetime(2025, 12, 21, 12, 0),
234
+ 'winter': datetime(2025, 6, 21, 12, 0)
235
+ }
236
+ result = {}
237
+ for season, date in solstices.items():
238
  solpos = pvlib.solarposition.get_solarposition(
239
  time=date,
240
  latitude=self.latitude,
241
  longitude=self.longitude,
242
  altitude=self.elevation
243
  )
244
+ result[season] = round(float(solpos['zenith'].iloc[0]), 1)
245
+ return result
246
+
247
+ def calculate_ground_reflection(self):
248
+ """Calculate solar time, angles, and ground-reflected radiation for hourly data."""
249
+ for i, record in enumerate(self.hourly_data):
250
+ # Create timestamp (adjust hour from 1-24 to 0-23)
251
+ try:
252
+ timestamp = datetime(2025, record['month'], record['day'], record['hour'] - 1)
253
+ except ValueError:
254
+ logger.warning(f"Invalid date in hourly data at index {i}: {record['month']}/{record['day']} {record['hour']}")
255
+ continue
256
+
257
+ # Calculate solar time
258
+ record['solar_time'] = round(self._calculate_solar_time(timestamp), 2)
259
+
260
+ # Calculate solar angles
261
+ solar_angles = self._calculate_solar_angles(timestamp)
262
+ record['solar_zenith'] = round(solar_angles['zenith'], 1)
263
+ record['solar_azimuth'] = round(solar_angles['azimuth'], 1)
264
+
265
+ # Calculate ground-reflected radiation
266
+ dni = record['direct_normal_irradiance']
267
+ dhi = record['diffuse_horizontal_irradiance']
268
+ ground_reflected = self.albedo * (dni * np.cos(np.radians(solar_angles['zenith'])) + dhi)
269
+ record['ground_reflected_radiation'] = round(max(0.0, ground_reflected), 1)
270
 
271
  def to_dict(self) -> Dict[str, Any]:
272
  """Convert the climate location to a dictionary."""
 
342
  "climate_zone", "heating_degree_days", "cooling_degree_days",
343
  "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
344
  "summer_daily_range", "wind_speed", "pressure",
345
+ "direct_normal_irradiance", "diffuse_horizontal_irradiance", "albedo",
346
+ "hourly_data", "solstice_zenith_angles"
347
  ]
348
 
349
  for field in required_fields:
 
390
  if not (0 <= data["albedo"] <= 1):
391
  st.error(f"Validation failed: Albedo {data['albedo']} outside range")
392
  return False
393
+ if not all(k in data["solstice_zenith_angles"] for k in ["summer", "winter"]):
394
+ st.error("Validation failed: Missing summer or winter solstice zenith angles")
395
+ return False
396
+ if not all(0 <= data["solstice_zenith_angles"][k] <= 180 for k in ["summer", "winter"]):
397
+ st.error("Validation failed: Solstice zenith angles outside range 0-180")
398
+ return False
399
+
400
  if not data["hourly_data"] or len(data["hourly_data"]) < 8700:
401
  st.error(f"Validation failed: Hourly data has {len(data['hourly_data'])} records, expected ~8760")
402
  return False
 
434
  if not (0 <= record["wind_direction"] <= 360):
435
  st.error(f"Validation failed: Wind direction {record['wind_direction']} outside range")
436
  return False
437
+ # Validate new hourly fields if present
438
+ if "solar_time" in record and not (0 <= record["solar_time"] < 24):
439
  st.error(f"Validation failed: Solar time {record['solar_time']} outside range")
440
  return False
441
+ if "solar_zenith" in record and not (0 <= record["solar_zenith"] <= 180):
442
  st.error(f"Validation failed: Solar zenith {record['solar_zenith']} outside range")
443
  return False
444
+ if "solar_azimuth" in record and not (0 <= record["solar_azimuth"] <= 360):
445
  st.error(f"Validation failed: Solar azimuth {record['solar_azimuth']} outside range")
446
  return False
447
+ if "ground_reflected_radiation" in record and not (0 <= record["ground_reflected_radiation"] <= 1200):
448
  st.error(f"Validation failed: Ground reflected radiation {record['ground_reflected_radiation']} outside range")
449
  return False
450
 
 
467
  st.error(f"Validation failed: Invalid ground temperatures for depth {depth}")
468
  return False
469
 
 
 
 
 
 
 
 
 
470
  return True
471
 
472
  @staticmethod
 
559
  period_name = parts[2 + i*4]
560
  period_type = parts[3 + i*4]
561
  start_date = parts[4 + i*4].strip()
562
+ end_date = parts[5 + i*4].strip()
563
  if period_name in [
564
  "Summer - Week Nearest Max Temperature For Period",
565
  "Summer - Week Nearest Average Temperature For Period",
 
631
  epw_file=epw_data,
632
  typical_extreme_periods=typical_extreme_periods,
633
  ground_temperatures=ground_temperatures,
634
+ albedo=albedo,
635
  id=f"{country[:1].upper()}{city[:3].upper()}",
636
  country=country,
637
  state_province=state_province,
 
675
  epw_file=epw_data,
676
  typical_extreme_periods=climate_data_dict["typical_extreme_periods"],
677
  ground_temperatures=climate_data_dict["ground_temperatures"],
678
+ albedo=climate_data_dict.get("albedo", albedo),
679
  id=climate_data_dict["id"],
680
  country=climate_data_dict["country"],
681
  state_province=climate_data_dict["state_province"],
 
685
  elevation=climate_data_dict["elevation"],
686
  time_zone=climate_data_dict["time_zone"]
687
  )
688
+ # Override hourly_data and solstice_zenith_angles to ensure consistency
689
  location.hourly_data = climate_data_dict["hourly_data"]
690
+ location.solstice_zenith_angles = climate_data_dict.get("solstice_zenith_angles", location._calculate_solstice_zenith_angles())
691
  self.add_location(location)
692
  st.info("Displaying previously extracted climate data.")
693
 
694
+ # Add button for calculating ground reflection radiation
695
+ if location and epw_data is not None:
696
+ if st.button("Calculate Ground Reflection Radiation"):
697
+ with st.spinner("Calculating solar time, angles, and ground-reflected radiation... Please wait."):
698
+ location.calculate_ground_reflection()
699
+ session_state["climate_data"] = location.to_dict()
700
+ st.success("Ground reflection calculations completed!")
701
+
702
  # Display tabs if location and epw_data are available
703
  if location and epw_data is not None:
704
  tab1, tab2, tab3, tab4, tab5 = st.tabs([
 
1114
  epw_file=epw_data,
1115
  typical_extreme_periods=loc_dict["typical_extreme_periods"],
1116
  ground_temperatures=loc_dict["ground_temperatures"],
1117
+ albedo=loc_dict.get("albedo", 0.2),
1118
  id=loc_dict["id"],
1119
  country=loc_dict["country"],
1120
  state_province=loc_dict["state_province"],
 
1124
  elevation=loc_dict["elevation"],
1125
  time_zone=loc_dict["time_zone"]
1126
  )
1127
+ location.hourly_data = loc_dict["hourly_data"] # Ensure consistency
1128
+ location.solstice_zenith_angles = loc_dict.get("solstice_zenith_angles", location._calculate_solstice_zenith_angles())
1129
  climate_data.add_location(location)
1130
  return climate_data
1131
 
1132
  if __name__ == "__main__":
1133
  climate_data = ClimateData()
1134
  session_state = {"building_info": {"country": "Australia", "city": "Geelong"}, "page": "Climate Data"}
1135
+ climate_data.display_climate_input(session_state, albedo=0.2)