import numpy as np import pandas as pd import logging import math from tqdm import tqdm 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 panel_object = [] base_planes = [] 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_thickness, height=panel_length, base_plane=panel_plane, ) panel_object.append(panel) base_planes.append(panel_plane) panels = pd.DataFrame(columns=["panel", "base_plane"]) panels["panel"] = panel_object panels["base_plane"] = base_planes 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) z = math.sin(zenith_rad) * math.sin(azimuth_rad) y = math.cos(zenith_rad) return Vector3D(x, y, z)