// Computes the fraction of collectors arranged in rows that will be shaded on the back side at a particular sun position. public double GetBackShadedFraction ( double SunZenith // Zenith angle of sun [radians] , double SunAzimuth // Azimuth angle of sun [radians] , double CollectorTilt // Tilt of the module [radians] ) { if (SunZenith > Math.PI / 2) { BackProfileAng = 0; return(0); // No shading possible as Sun is set or not risen } else if (Math.Abs(SunAzimuth - itsCollAzimuth) < Math.PI / 2) { BackProfileAng = 0; return(0); // No partial back shading possible as Sun is in front of the collectors } else { // Compute profile angle, relative to back of module (see Ref 1.) BackProfileAng = Tilt.GetProfileAngle(SunZenith, SunAzimuth, itsCollAzimuth + Math.PI); // NB: Added small tolerance since shading limit angle and profile angle are found through different methods and could have a small difference if (BackSLA - BackProfileAng <= 0.000001) { return(0); // No shading possible as the light reaching the back of the panel is not limited by the proceeding row } else { // Computes the fraction of collectors arranged in rows that will be shaded on the back side at a particular sun position. double AC = Math.Sin(CollectorTilt) * itsCollBW / Math.Sin(BackSLA); double CAAp = Math.PI - CollectorTilt - BackSLA; double CApA = Math.PI - CAAp - (BackSLA - BackProfileAng); // Length of shaded section double AAp = AC * Math.Sin(BackSLA - BackProfileAng) / Math.Sin(CApA); // Using the Cell based shading model if (useCellBasedShading) { double cellShaded = AAp / (CellSize); // The number of cells shaded double SF = 1; // The resultant shading factor initialized to 1, modified later// Calculate the Shading fraction based on which cell numbers they are between for (int i = 1; i <= itsNumModTransverseStrings; i++) { if ((cellShaded > cellSetup[i - 1]) && (cellShaded < cellSetup[i])) { SF = Math.Min(((shadingPC[i - 1] + (shadingPC[i] * (cellShaded - cellSetup[i - 1])))), shadingPC[i]); } } return(SF); } else { // Return shaded fraction of the collector bandwidth return(AAp / itsCollBW); } } } }
// Divides the ground between two PV rows into n segments and determines direct beam shading (0 = not shaded, 1 = shaded) for each segment void CalcGroundShading ( double SunZenith // The zenith position of the sun with 0 being normal to the earth [radians] , double SunAzimuth // The azimuth position of the sun relative to 0 being true south. Positive if west, negative if east [radians] ) { // When sun is below horizon, set everything to shaded if (SunZenith > (Math.PI / 2)) { for (int i = 0; i < numGroundSegs; i++) { frontGroundSH[i] = 1; rearGroundSH[i] = 1; } } else { double h = Math.Sin(itsPanelTilt); // Vertical height of sloped PV panel [panel slope lengths] double b = Math.Cos(itsPanelTilt); // Horizontal distance from front of panel to back of panel [panel slope lengths] double FrontPA = Tilt.GetProfileAngle(SunZenith, SunAzimuth, itsPanelAzimuth); double Lh = h / Math.Tan(FrontPA); // Base of triangle formed by beam of sun and height of module top from bottom double Lc = itsClearance / Math.Tan(FrontPA); // Base of triangle formed by beam of sun and height of module bottom from ground double Lhc = (h + itsClearance) / Math.Tan(FrontPA); // Base of triangle formed by beam of sun and height of module top from ground double s1Start = 0; // Shading start position for first potential shading segment double s1End = 0; // Shading end position for first potential shading segment double s2Start = 0; // Shading start position for second potential shading segment double s2End = 0; // Shading end position for second potential shading segment double SStart = 0; // Shading start position for placeholder segment double SEnd = 0; // Shading start position for placeholder segment // Divide the row-to-row spacing into n intervals for calculating ground shade factors double delta = itsPitch / numGroundSegs; // Initialize horizontal dimension x to provide midpoint intervals double x = 0; if (itsRowType == RowType.SINGLE) { // Calculate front ground shading // Front side of PV module completely sunny, ground partially shaded if (Lh > 0.0) { s1Start = Lc; s1End = Lhc + b; } // Front side of PV module completely shaded, ground completely shaded else if (Lh < -(itsPitch + b)) { s1Start = -itsPitch; s1End = itsPitch; } // Shadow to front of row - either front or back might be shaded, depending on tilt and other factors else { // Sun hits front of module. Shadow cast by bottom of module extends further forward than shadow cast by top if (Lc < Lhc + b) { s1Start = Lc; s1End = Lhc + b; } // Sun hits back of module. Shadow cast by top of module extends further forward than shadow cast by bottom else { s1Start = Lhc + b; s1End = Lc; } } // Determine whether shaded or sunny for each n ground segments // TODO: improve accuracy (especially for n < 100) by setting 1 only if > 50% of segment is shaded for (int i = 0; i < numGroundSegs; i++) { // Offset x coordinate by -itsPitch because row ahead is being measured x = (i + 0.5) * delta - itsPitch; if (x >= s1Start && x < s1End) { // x within a shaded interval, so set to 1 to indicate shaded frontGroundSH[i] = 1; } else { // x not within a shaded interval, so set to 0 to indicate sunny frontGroundSH[i] = 0; } } // Calculate rear ground shading // Back side of PV module completely shaded, ground completely shaded if (Lh > itsPitch - b) { s1Start = 0.0; s1End = itsPitch; } // Shadow to front of row - either front or back might be shaded, depending on tilt and other factors else { // Sun hits front of module. Shadow cast by bottom of module extends further forward than shadow cast by top if (Lc < Lhc + b) { s1Start = Lc; s1End = Lhc + b; } // Sun hits back of module. Shadow cast by top of module extends further forward than shadow cast by bottom else { s1Start = Lhc + b; s1End = Lc; } } // Determine whether shaded or sunny for each n ground segments // TODO: improve accuracy (especially for n < 100) by setting 1 only if > 50% of segment is shaded for (int i = 0; i < numGroundSegs; i++) { x = (i + 0.5) * delta; if (x >= s1Start && x < s1End) { // x within a shaded interval, so set to 1 to indicate shaded rearGroundSH[i] = 1; } else { // x not within a shaded interval, so set to 0 to indicate sunny rearGroundSH[i] = 0; } } } else { // Calculate interior ground shading // Front side of PV module partially shaded, back completely shaded, ground completely shaded if (Lh > itsPitch - b) { s1Start = 0.0; s1End = itsPitch; } // Front side of PV module completely shaded, back partially shaded, ground completely shaded else if (Lh < -(itsPitch + b)) { s1Start = 0.0; s1End = itsPitch; } // Assume ground is partially shaded else { // Shadow to back of row - module front unshaded, back shaded if (Lhc >= 0.0) { SStart = Lc; SEnd = Lhc + b; // Put shadow in correct row-to-row space if needed while (SStart > itsPitch) { SStart -= itsPitch; SEnd -= itsPitch; } s1Start = SStart; s1End = SEnd; // Need to use two shade areas. Transpose the area that extends beyond itsPitch to the front of the row-to-row space if (s1End > itsPitch) { s1End = itsPitch; s2Start = 0.0; s2End = SEnd - itsPitch; if (s2End - s1Start > 0.000001) { ErrorLogger.Log("Unexpected shading coordinates encountered.", ErrLevel.FATAL); } } } // Shadow to front of row - either front or back might be shaded, depending on tilt and other factors else { // Sun hits front of module. Shadow cast by bottom of module extends further forward than shadow cast by top if (Lc < Lhc + b) { SStart = Lc; SEnd = Lhc + b; } // Sun hits back of module. Shadow cast by top of module extends further forward than shadow cast by bottom else { SStart = Lhc + b; SEnd = Lc; } // Put shadow in correct row-to-row space if needed while (SStart < 0.0) { SStart += itsPitch; SEnd += itsPitch; } s1Start = SStart; s1End = SEnd; // Need to use two shade areas. Transpose the area that extends beyond itsPitch to the front of the row-to-row space if (s1End > itsPitch) { s1End = itsPitch; s2Start = 0.0; s2End = SEnd - itsPitch; if (s2End - s1Start > 0.000001) { ErrorLogger.Log("Unexpected shading coordinates encountered.", ErrLevel.FATAL); } } } } // Determine whether shaded or sunny for each n ground segments // TODO: improve accuracy (especially for n < 100) by setting 1 only if > 50% of segment is shaded for (int i = 0; i < numGroundSegs; i++) { x = (i + 0.5) * delta; // Assume homogeneity to the front and rear of interior rows if ((x >= s1Start && x < s1End) || (x >= s2Start && x < s2End)) { // x within a shaded interval, so set to 1 to indicate shaded frontGroundSH[i] = 1; rearGroundSH[i] = 1; } else { // x not within a shaded interval, so set to 0 to indicate sunny frontGroundSH[i] = 0; rearGroundSH[i] = 0; } } } } }