Spaces:
Sleeping
Sleeping
Upload solar_calculations.py
Browse files- data/solar_calculations.py +145 -0
data/solar_calculations.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from typing import List, Dict, Any
|
| 3 |
+
import math
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import streamlit as st
|
| 6 |
+
|
| 7 |
+
# Initialize session_state
|
| 8 |
+
if "climate_data" not in st.session_state:
|
| 9 |
+
st.session_state["climate_data"] = {
|
| 10 |
+
"latitude": 0.0,
|
| 11 |
+
"longitude": 0.0,
|
| 12 |
+
"timezone": 0.0
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
class SolarCalculations:
|
| 16 |
+
"""Class for performing solar radiation and angle calculations based on ASHRAE methodologies."""
|
| 17 |
+
|
| 18 |
+
@staticmethod
|
| 19 |
+
def day_of_year(month: int, day: int, year: int) -> int:
|
| 20 |
+
"""Calculate day of the year (n) from month, day, and year, accounting for leap years."""
|
| 21 |
+
days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
| 22 |
+
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
|
| 23 |
+
days_in_month[1] = 29
|
| 24 |
+
return sum(days_in_month[:month-1]) + day
|
| 25 |
+
|
| 26 |
+
@staticmethod
|
| 27 |
+
def equation_of_time(n: int) -> float:
|
| 28 |
+
"""Calculate Equation of Time (EOT) in minutes using Spencer's formula."""
|
| 29 |
+
B = (n - 1) * 360 / 365
|
| 30 |
+
B_rad = math.radians(B)
|
| 31 |
+
EOT = 229.2 * (0.000075 + 0.001868 * math.cos(B_rad) - 0.032077 * math.sin(B_rad) -
|
| 32 |
+
0.014615 * math.cos(2 * B_rad) - 0.04089 * math.sin(2 * B_rad))
|
| 33 |
+
return EOT
|
| 34 |
+
|
| 35 |
+
@staticmethod
|
| 36 |
+
def calculate_solar_parameters(
|
| 37 |
+
hourly_data: List[Dict[str, Any]],
|
| 38 |
+
latitude: float,
|
| 39 |
+
longitude: float,
|
| 40 |
+
timezone: float,
|
| 41 |
+
ground_reflectivity: float,
|
| 42 |
+
surface_tilt: float,
|
| 43 |
+
year: int = 2025
|
| 44 |
+
) -> List[Dict[str, Any]]:
|
| 45 |
+
"""Calculate solar angles and ground-reflected radiation for hourly data with GHI > 0."""
|
| 46 |
+
# Display input parameters in Streamlit UI
|
| 47 |
+
st.write("### Input Parameters")
|
| 48 |
+
st.write(f"- **Latitude**: {latitude}°")
|
| 49 |
+
st.write(f"- **Longitude**: {longitude}°")
|
| 50 |
+
st.write(f"- **Timezone**: {timezone} hours")
|
| 51 |
+
st.write(f"- **Ground Reflectivity**: {ground_reflectivity}")
|
| 52 |
+
st.write(f"- **Surface Tilt**: {surface_tilt}°")
|
| 53 |
+
st.write(f"- **Year**: {year}")
|
| 54 |
+
st.write("") # Add spacing
|
| 55 |
+
|
| 56 |
+
results = []
|
| 57 |
+
lambda_std = 15 * timezone # Standard meridian longitude (°)
|
| 58 |
+
first_hour = True
|
| 59 |
+
|
| 60 |
+
for record in hourly_data:
|
| 61 |
+
if record["global_horizontal_radiation"] <= 0:
|
| 62 |
+
continue # Skip hours with no solar radiation
|
| 63 |
+
|
| 64 |
+
# Step 1: Extract data
|
| 65 |
+
month = record["month"]
|
| 66 |
+
day = record["day"]
|
| 67 |
+
hour = record["hour"]
|
| 68 |
+
ghi = record["global_horizontal_radiation"]
|
| 69 |
+
dni = record["direct_normal_radiation"]
|
| 70 |
+
dhi = record["diffuse_horizontal_radiation"]
|
| 71 |
+
|
| 72 |
+
if first_hour:
|
| 73 |
+
st.write(f"### Calculations for First Hour (Month: {month}, Day: {day}, Hour: {hour})")
|
| 74 |
+
st.write(f"- **Global Horizontal Radiation (GHI)**: {ghi} W/m²")
|
| 75 |
+
st.write(f"- **Direct Normal Radiation (DNI)**: {dni} W/m²")
|
| 76 |
+
st.write(f"- **Diffuse Horizontal Radiation (DHI)**: {dhi} W/m²")
|
| 77 |
+
|
| 78 |
+
# Step 2: Local Solar Time (LST) with Equation of Time
|
| 79 |
+
n = SolarCalculations.day_of_year(month, day, year)
|
| 80 |
+
if first_hour:
|
| 81 |
+
st.write(f"- **Day of Year (n)**: {n}")
|
| 82 |
+
|
| 83 |
+
EOT = SolarCalculations.equation_of_time(n)
|
| 84 |
+
if first_hour:
|
| 85 |
+
st.write(f"- **Equation of Time (EOT)**: {EOT:.2f} minutes")
|
| 86 |
+
|
| 87 |
+
standard_time = hour - 1 + 0.5 # Convert to decimal, assume mid-hour
|
| 88 |
+
LST = standard_time + (4 * (lambda_std - longitude) + EOT)/60
|
| 89 |
+
if first_hour:
|
| 90 |
+
st.write(f"- **Local Solar Time (LST)**: {LST:.2f} hours")
|
| 91 |
+
|
| 92 |
+
# Step 3: Solar Declination (δ)
|
| 93 |
+
delta = 23.45 * math.sin(math.radians(360 / 365 * (284 + n)))
|
| 94 |
+
if first_hour:
|
| 95 |
+
st.write(f"- **Solar Declination (δ)**: {delta:.2f}°")
|
| 96 |
+
|
| 97 |
+
# Step 4: Hour Angle (HRA)
|
| 98 |
+
hra = 15 * (LST - 12)
|
| 99 |
+
if first_hour:
|
| 100 |
+
st.write(f"- **Hour Angle (HRA)**: {hra:.2f}°")
|
| 101 |
+
|
| 102 |
+
# Step 5: Solar Altitude (α) and Azimuth (Az)
|
| 103 |
+
phi = math.radians(latitude)
|
| 104 |
+
delta_rad = math.radians(delta)
|
| 105 |
+
hra_rad = math.radians(hra)
|
| 106 |
+
|
| 107 |
+
sin_alpha = math.sin(phi) * math.sin(delta_rad) + math.cos(phi) * math.cos(delta_rad) * math.cos(hra_rad)
|
| 108 |
+
alpha = math.degrees(math.asin(sin_alpha))
|
| 109 |
+
if first_hour:
|
| 110 |
+
st.write(f"- **Solar Altitude (α)**: {alpha:.2f}°")
|
| 111 |
+
|
| 112 |
+
if abs(math.cos(math.radians(alpha))) < 0.01:
|
| 113 |
+
azimuth = 0 # North at sunrise/sunset
|
| 114 |
+
else:
|
| 115 |
+
sin_az = math.cos(delta_rad) * math.sin(hra_rad) / math.cos(math.radians(alpha))
|
| 116 |
+
cos_az = (sin_alpha * math.sin(phi) - math.sin(delta_rad)) / (math.cos(math.radians(alpha)) * math.cos(phi))
|
| 117 |
+
azimuth = math.degrees(math.atan2(sin_az, cos_az))
|
| 118 |
+
if hra > 0: # Afternoon
|
| 119 |
+
azimuth = 360 - azimuth if azimuth > 0 else -azimuth
|
| 120 |
+
if first_hour:
|
| 121 |
+
st.write(f"- **Solar Azimuth (Az)**: {azimuth:.2f}°")
|
| 122 |
+
|
| 123 |
+
# Step 6: Ground-Reflected Radiation (I_gr)
|
| 124 |
+
view_factor = (1 - math.cos(math.radians(surface_tilt))) / 2
|
| 125 |
+
ground_reflected = ground_reflectivity * ghi * view_factor
|
| 126 |
+
if first_hour:
|
| 127 |
+
st.write(f"- **Ground-Reflected Radiation (I_gr)**: {ground_reflected:.2f} W/m²")
|
| 128 |
+
st.write("") # Add spacing
|
| 129 |
+
first_hour = False
|
| 130 |
+
|
| 131 |
+
# Store results
|
| 132 |
+
result = {
|
| 133 |
+
"month": month,
|
| 134 |
+
"day": day,
|
| 135 |
+
"hour": hour,
|
| 136 |
+
"declination": round(delta, 2),
|
| 137 |
+
"LST": round(LST, 2),
|
| 138 |
+
"HRA": round(hra, 2),
|
| 139 |
+
"altitude": round(alpha, 2),
|
| 140 |
+
"azimuth": round(azimuth, 2),
|
| 141 |
+
"ground_reflected": round(ground_reflected, 2)
|
| 142 |
+
}
|
| 143 |
+
results.append(result)
|
| 144 |
+
|
| 145 |
+
return results
|