216 lines
7.0 KiB
Python
216 lines
7.0 KiB
Python
import numpy as np
|
|
import pandas as pd
|
|
import logging
|
|
import math
|
|
|
|
from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D
|
|
from ladybug_geometry.geometry3d.plane import Plane
|
|
from ladybug_geometry.geometry3d.polyface import Polyface3D
|
|
|
|
import pvlib
|
|
|
|
from Utilities.Processes import calculate_no_of_panels
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def define_grid_layout(c):
|
|
# get number of panels required
|
|
no_of_panels = calculate_no_of_panels(
|
|
c["array"]["system_size"], c["panel"]["peak_power"]
|
|
)
|
|
|
|
# get maximum number of panels based on spacing and dimensions
|
|
max__panels_per_row = np.floor(
|
|
(
|
|
c["environment"]["roof"]["dimensions"]["width"]
|
|
- 2 * c["array"]["edge_setback"]
|
|
)
|
|
/ c["panel"]["dimensions"]["width"]
|
|
)
|
|
max_number_of_rows = np.floor(
|
|
(
|
|
c["environment"]["roof"]["dimensions"]["length"]
|
|
- 2 * c["array"]["edge_setback"]
|
|
)
|
|
/ (c["array"]["spacing"] + c["panel"]["dimensions"]["thickness"])
|
|
)
|
|
max_no_of_panels = max__panels_per_row * max_number_of_rows
|
|
logger.info(
|
|
f"Number of panels required: {no_of_panels}, Maximum panels possible: {max_no_of_panels}"
|
|
)
|
|
if no_of_panels > max_no_of_panels:
|
|
no_of_panels = max_no_of_panels
|
|
logger.warning(
|
|
f"Number of panels required exceeds maximum possible. Setting number of panels to {no_of_panels}."
|
|
)
|
|
else:
|
|
logger.info(
|
|
f"Number of panels required is within the maximum possible. Setting number of panels to {no_of_panels}."
|
|
)
|
|
|
|
# coordinate of panel determined by bottom left corner
|
|
# x - row wise position, y - column wise position, z - height
|
|
# first panel in row 1 is at (0, 0, 0)
|
|
# nth panel in row 1 is at ((n-1)*panel_width, 0, 0)
|
|
# first panel in nth row is at (0, (n-1)*(panel_thickness + spacing), 0)
|
|
|
|
# create matrices for x, y, z coordinates of panels
|
|
x = []
|
|
y = []
|
|
z = []
|
|
counter = 0
|
|
for j in range(int(max_number_of_rows)):
|
|
for i in range(int(max__panels_per_row)):
|
|
if counter < no_of_panels:
|
|
x.append(i * c["panel"]["dimensions"]["width"])
|
|
y.append(
|
|
j * (c["panel"]["dimensions"]["thickness"] + c["array"]["spacing"])
|
|
)
|
|
z.append(0)
|
|
counter += 1
|
|
else:
|
|
break
|
|
|
|
coordinates = pd.DataFrame(
|
|
{
|
|
"x": x,
|
|
"y": y,
|
|
"z": z,
|
|
}
|
|
)
|
|
return coordinates
|
|
|
|
|
|
def create_panels(coordinates, c):
|
|
panel_width = c["panel"]["dimensions"]["width"]
|
|
panel_length = c["panel"]["dimensions"]["length"]
|
|
panel_thickness = c["panel"]["dimensions"]["thickness"]
|
|
|
|
# if viewed from above, and assumming the roof is a rectangle, the
|
|
# global origin is at the bottom left corner of the roof
|
|
|
|
# For a vertical panel:
|
|
# - The vertical direction (panel height) is along the Z-axis.
|
|
y_axis = Vector3D(0, 1, 0) # points east, therefore front face is east facing
|
|
|
|
# - The horizontal direction along the panel's width.
|
|
# Here, we assume the width runs in the positive X-direction.
|
|
x_axis = Vector3D(1, 0, 0) # points north
|
|
|
|
panels = []
|
|
for index, row in coordinates.iterrows():
|
|
# Create the bottom-left corner of the panel
|
|
panel_origin = Point3D(row["x"], row["y"], row["z"])
|
|
|
|
# Create the plane for the panel
|
|
panel_plane = Plane(o=panel_origin, n=y_axis, x=x_axis)
|
|
|
|
# Create the panel geometry
|
|
panel = Polyface3D.from_box(
|
|
width=panel_width,
|
|
depth=panel_length,
|
|
height=panel_thickness,
|
|
base_plane=panel_plane,
|
|
)
|
|
|
|
panels.append(panel)
|
|
|
|
return panels
|
|
|
|
|
|
def get_solar_data(c):
|
|
logger.info(
|
|
f"Getting solar position data for {c['simulation_date_time']['start']} to {c['simulation_date_time']['end']}"
|
|
)
|
|
"""
|
|
Function to get solar position from PVLib
|
|
"""
|
|
latitude = c["environment"]["location"]["latitude"]
|
|
longitude = c["environment"]["location"]["longitude"]
|
|
tz = c["simulation_date_time"]["tz"]
|
|
|
|
times = pd.date_range(
|
|
c["simulation_date_time"]["start"],
|
|
c["simulation_date_time"]["end"],
|
|
freq="15min",
|
|
tz=tz,
|
|
)
|
|
|
|
# Get solar position data using PVLib
|
|
solar_positions = pvlib.solarposition.get_solarposition(times, latitude, longitude)
|
|
|
|
return solar_positions
|
|
|
|
|
|
def calculate_sun_vector(solar_zenith, solar_azimuth):
|
|
"""
|
|
Calculate the sun vector from solar zenith and azimuth angles.
|
|
Args:
|
|
solar_zenith (float): Solar zenith angle in degrees.
|
|
solar_azimuth (float): Solar azimuth angle in degrees.
|
|
|
|
Returns:
|
|
Vector3D: Sun vector as a 3D vector.
|
|
"""
|
|
# Convert angles from degrees to radians
|
|
zenith_rad = math.radians(solar_zenith)
|
|
azimuth_rad = math.radians(solar_azimuth)
|
|
|
|
# Calculate the sun vector components
|
|
x = math.sin(zenith_rad) * math.cos(azimuth_rad)
|
|
y = math.sin(zenith_rad) * math.sin(azimuth_rad)
|
|
z = math.cos(zenith_rad)
|
|
|
|
return Vector3D(x, y, z)
|
|
|
|
|
|
def compute_array_shading(panels, sun_vector, n_samples=25):
|
|
"""
|
|
Given a list of panel geometries (Polyface3D) and the sun vector,
|
|
compute the shading fraction for each panel and return the overall average shading.
|
|
|
|
Parameters:
|
|
panels: List of Polyface3D objects representing the PV panels.
|
|
sun_vector: Unit Vector3D in the direction of the sun.
|
|
n_samples: Number of sample points per panel.
|
|
|
|
Returns:
|
|
Dictionary mapping panel index to its shading fraction, and the overall average.
|
|
"""
|
|
shading_results = {}
|
|
for i, panel in enumerate(panels):
|
|
# Define obstacles as all other panels in the array
|
|
obstacles = [pan for j, pan in enumerate(panels) if j != i]
|
|
shading_frac = calculate_shading_fraction(
|
|
panel, sun_vector, obstacles, n_samples=n_samples
|
|
)
|
|
shading_results[i] = shading_frac
|
|
# Compute the overall average shading fraction across all panels:
|
|
overall_avg = np.mean(list(shading_results.values()))
|
|
return shading_results, overall_avg
|
|
|
|
|
|
def calculate_shading_fraction(c):
|
|
coordinates = define_grid_layout(c)
|
|
panels = create_panels(coordinates, c)
|
|
solar_positions = get_solar_data(c)
|
|
|
|
shading_fractions = []
|
|
for panel in panels:
|
|
shading_fraction = []
|
|
for index, row in solar_positions.iterrows():
|
|
# Get the solar position for the current time step
|
|
# in a sphere, azimuth is the angle in the x-y plane from the north
|
|
# and zenith is the angle from the vertical axis
|
|
solar_zenith = row["apparent_zenith"]
|
|
solar_azimuth = row["apparent_azimuth"]
|
|
sun_vector = calculate_sun_vector(solar_zenith, solar_azimuth)
|
|
|
|
# Calculate the shading fraction using the panel and solar position
|
|
shading_fraction.append(panel.shading_fraction(solar_zenith, solar_azimuth))
|
|
|
|
shading_fractions.append(shading_fraction)
|
|
|
|
return shading_fractions
|