// Calculation method public void Calculate ( double NDir // normal direct irradiance [W/m2] , double HDif // horizontal diffuse irradiance [W/m2] , double NExtra // normal extraterrestrial irradiance [W/m2] , double SunZenith // zenith angle of sun [radians] , double SunAzimuth // azimuth angle of sun [radians] , double AirMass // air mass [#] , int MonthNum // The month number of the time stamp [1->12] ) { // Calculate direct horizontal if direct normal is provided double HDir = NDir * Math.Cos(SunZenith); // call Perez et al. algorithm or Hay algorithm switch (itsTiltAlgorithm) { case TiltAlgorithm.PEREZ: TGlo = GetTiltCompIrradPerez(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, AirMass, MonthNum); break; case TiltAlgorithm.HAY: TGlo = GetTiltCompIrradHay(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, MonthNum); break; default: itsTiltAlgorithm = TiltAlgorithm.HAY; break; } // Compute the incidence angle from the Tilt class IncidenceAngle = Tilt.GetIncidenceAngle(SunZenith, SunAzimuth, itsSurfaceSlope, itsSurfaceAzimuth); }
// 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); } } } }
// Calculation method public void Calculate ( double NDir // normal direct irradiance [W/m2] , double HDif // horizontal diffuse irradiance [W/m2] , double NExtra // normal extraterrestrial irradiance [W/m2] , double SunZenith // zenith angle of sun [radians] , double SunAzimuth // azimuth angle of sun [radians] , double AirMass // air mass [#] , int MonthNum // The month number of the time stamp [1->12] , double MeasAlbedo // albedo read from climate file, if available (NaN otherwise) ) { // Calculate albedo // Read from climate file if (ReadFarmSettings.GetAttribute("Albedo", "Frequency", ErrLevel.WARNING) == "From Climate File") { Albedo = MeasAlbedo; } // Otherwise, read from monthly array else { Albedo = itsMonthlyAlbedo[MonthNum]; } // Calculate direct horizontal if direct normal is provided double HDir = NDir * Math.Cos(SunZenith); // call Perez et al. algorithm or Hay algorithm switch (itsTiltAlgorithm) { case TiltAlgorithm.PEREZ: TGlo = GetTiltCompIrradPerez(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, AirMass, MonthNum); break; case TiltAlgorithm.HAY: TGlo = GetTiltCompIrradHay(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, MonthNum); break; default: itsTiltAlgorithm = TiltAlgorithm.HAY; break; } // Compute the incidence angle from the Tilt class IncidenceAngle = Tilt.GetIncidenceAngle(SunZenith, SunAzimuth, itsSurfaceSlope, itsSurfaceAzimuth); }
// De-transposition method to the be used if the meter and panel tilt do not match void PyranoDetranspose(SimMeteo SimMet) { if (pyranoTilter.NoPyranoAnglesDefined) { SimTracker.Calculate(SimSun.Zenith, SimSun.Azimuth, SimMet.Year, SimMet.DayOfYear); pyranoTilter.itsSurfaceAzimuth = SimTracker.SurfAzimuth; pyranoTilter.itsSurfaceSlope = SimTracker.SurfSlope; pyranoTilter.IncidenceAngle = SimTracker.IncidenceAngle; } // Lower bound of bisection double HGloLo = 0; // Higher bound of bisection double HGloHi = SimSun.NExtra; // Calculating the Incidence Angle for the current setup double cosInc = Tilt.GetCosIncidenceAngle(SimSun.Zenith, SimSun.Azimuth, pyranoTilter.itsSurfaceSlope, pyranoTilter.itsSurfaceAzimuth); // Trivial case if (SimMet.TGlo <= 0) { SimSplitter.Calculate(SimSun.Zenith, 0, NExtra: SimSun.NExtra); pyranoTilter.Calculate(SimSplitter.NDir, SimSplitter.HDif, SimSun.NExtra, SimSun.Zenith, SimSun.Azimuth, SimSun.AirMass, SimMet.MonthOfYear, SimMet.Albedo); } else if ((SimSun.Zenith > 87.5 * Util.DTOR) || (cosInc <= Math.Cos(87.5 * Util.DTOR))) { SimMet.HGlo = SimMet.TGlo / ((1 + Math.Cos(pyranoTilter.itsSurfaceSlope)) / 2 + pyranoTilter.itsMonthlyAlbedo[SimMet.MonthOfYear] * (1 - Math.Cos(pyranoTilter.itsSurfaceSlope)) / 2); // Forcing the horizontal irradiance to be composed entirely of diffuse irradiance SimSplitter.HGlo = SimMet.HGlo; SimSplitter.HDif = SimMet.HGlo; SimSplitter.NDir = 0; SimSplitter.HDir = 0; //SimSplitter.Calculate(SimSun.Zenith, HGlo, NExtra: SimSun.NExtra); pyranoTilter.Calculate(SimSplitter.NDir, SimSplitter.HDif, SimSun.NExtra, SimSun.Zenith, SimSun.Azimuth, SimSun.AirMass, SimMet.MonthOfYear, SimMet.Albedo); } // Otherwise, bisection loop else { // Bisection loop while (Math.Abs(HGloHi - HGloLo) > 0.01) { // Use the central value between the domain to start the bisection, and then solve for TGlo, double HGloAv = (HGloLo + HGloHi) / 2; SimSplitter.Calculate(SimSun.Zenith, _HGlo: HGloAv, NExtra: SimSun.NExtra); pyranoTilter.Calculate(SimSplitter.NDir, SimSplitter.HDif, SimSun.NExtra, SimSun.Zenith, SimSun.Azimuth, SimSun.AirMass, SimMet.MonthOfYear, SimMet.Albedo); double TGloAv = pyranoTilter.TGlo; // Compare the TGloAv calculated from the Horizontal guess to the acutal TGlo and change the bounds for analysis // comparing the TGloAv and TGlo if (TGloAv < SimMet.TGlo) { HGloLo = HGloAv; } else { HGloHi = HGloAv; } } } SimMet.HGlo = SimSplitter.HGlo; // This value of the horizontal global should now be transposed to the tilt value from the array. Transpose(SimMet); }
// Calculate the tracker slope, azimuth and incidence angle using public void Calculate(double SunZenith, double SunAzimuth, int Year, int DayOfYear) { switch (itsTrackMode) { case TrackMode.SAXT: // Surface stays parallel to the ground. if (itsTrackerSlope == 0.0) { // For east-west tracking, the absolute value of the sun-azimuth is checked against the tracker azimuth // This is from Duffie and Beckman Page 22. if (itsTrackerAzimuth == Math.PI / 2 || itsTrackerAzimuth == -Math.PI / 2) { // If the user inputs a minimum tilt less than 0, the tracker is able to face the non-dominant direction, so the surface azimuth will change based on the sun azimuth. // However, if the minimum tilt is greater than zero, the tracker can only face the dominant direction. if (itsMinTilt <= 0) { // Math.Abs is used so that the surface azimuth is set to 0 degrees if the sun azimuth is between -90 and 90, and set to 180 degrees if the sun azimuth is between -180 and -90 or between 90 and 180 if (Math.Abs(SunAzimuth) >= Math.Abs(itsTrackerAzimuth)) { SurfAzimuth = Math.PI; } else { SurfAzimuth = 0; } } else { SurfAzimuth = itsTrackerAzimuth - Math.PI / 2; } } else if (itsTrackerAzimuth == 0) { // For north-south tracking, the sign of the sun-azimuth is checked against the tracker azimuth // This is from Duffie and Beckman Page 22. if (SunAzimuth >= itsTrackerAzimuth) { SurfAzimuth = Math.PI / 2; } else { SurfAzimuth = -Math.PI / 2; } } // Surface slope calculated from eq. 31 of reference guide SurfSlope = Math.Atan2(Math.Sin(SunZenith) * Math.Cos(SurfAzimuth - SunAzimuth), Math.Cos(SunZenith)); // If the shadow is greater than the Pitch and backtracking is selected if (useBackTracking) { if (itsTrackerBW / (Math.Cos(SurfSlope)) > itsTrackerPitch) { // NB: From Lorenzo, Narvarte, and Munoz AngleCorrection = Math.Acos((itsTrackerPitch * (Math.Cos(SurfSlope))) / itsTrackerBW); SurfSlope = SurfSlope - AngleCorrection; } } // Adjusting limits for elevation tracking, so if positive min tilt, the tracker operates within limits properly if (itsTrackerAzimuth == Math.PI / 2 || itsTrackerAzimuth == -Math.PI / 2) { if (itsMinTilt <= 0) { if (Math.Abs(SunAzimuth) <= itsTrackerAzimuth) { SurfSlope = Math.Min(itsMaxTilt, SurfSlope); } else if (Math.Abs(SunAzimuth) > itsTrackerAzimuth) { SurfSlope = Math.Min(Math.Abs(itsMinTilt), SurfSlope); } } else if (itsMinTilt > 0) { SurfSlope = Math.Min(SurfSlope, itsMaxTilt); SurfSlope = Math.Max(SurfSlope, itsMinTilt); } } else if (itsTrackerAzimuth == 0) { SurfSlope = Math.Min(itsMaxTilt, SurfSlope); } } else { // Tilt and Roll double aux = Tilt.GetCosIncidenceAngle(SunZenith, SunAzimuth, itsTrackerSlope, itsTrackerAzimuth); // Equation (7) from Marion and Dobos RotAngle = Math.Atan2((Math.Sin(SunZenith) * Math.Sin(SunAzimuth - itsTrackerAzimuth)), aux); //NB: enforcing rotation limits on tracker RotAngle = Math.Min(itsMaxRotationAngle, RotAngle); RotAngle = Math.Max(itsMinRotationAngle, RotAngle); // Slope from equation (1) in Marion and Dobos SurfSlope = Math.Acos(Math.Cos(RotAngle) * Math.Cos(itsTrackerSlope)); // Surface Azimuth from NREL paper if (SurfSlope != 0) { // Equation (3) in Marion and Dobos if ((-Math.PI <= RotAngle) && (RotAngle < -Math.PI / 2)) { SurfAzimuth = itsTrackerAzimuth - Math.Asin(Math.Sin(RotAngle) / Math.Sin(SurfSlope)) - Math.PI; } // Equation (4) in Marion and Dobos else if ((Math.PI / 2 < RotAngle) && (RotAngle <= Math.PI)) { SurfAzimuth = itsTrackerAzimuth - Math.Asin(Math.Sin(RotAngle) / Math.Sin(SurfSlope)) + Math.PI; } // Equation (2) in Marion and Dobos else { SurfAzimuth = itsTrackerAzimuth + Math.Asin(Math.Sin(RotAngle) / Math.Sin(SurfSlope)); } } //NB: 360 degree correction to put Surface Azimuth into the correct quadrant, see Note 1 if (SurfAzimuth > Math.PI) { SurfAzimuth -= (Math.PI) * 2; } else if (SurfAzimuth < -Math.PI) { SurfAzimuth += (Math.PI) * 2; } } break; // Two Axis Tracking case TrackMode.TAXT: // Defining the surface slope SurfSlope = SunZenith; SurfSlope = Math.Max(itsMinTilt, SurfSlope); SurfSlope = Math.Min(itsMaxTilt, SurfSlope); // Defining the surface azimuth SurfAzimuth = SunAzimuth; // Changes the reference frame to be with respect to the reference azimuth if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } // Enforcing the rotation limits with respect to the reference azimuth SurfAzimuth = Math.Max(itsMinAzimuth, SurfAzimuth); SurfAzimuth = Math.Min(itsMaxAzimuth, SurfAzimuth); // Moving the surface azimuth back into the azimuth variable convention if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } break; // Azimuth Vertical Axis Tracking case TrackMode.AVAT: // Slope is constant. // Defining the surface azimuth SurfAzimuth = SunAzimuth; // Changes the reference frame to be with respect to the reference azimuth if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } // Enforcing the rotation limits with respect to the reference azimuth SurfAzimuth = Math.Max(itsMinAzimuth, SurfAzimuth); SurfAzimuth = Math.Min(itsMaxAzimuth, SurfAzimuth); // Moving the surface azimuth back into the azimuth variable convention if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } break; // Fixed Tilt with Seasonal Adjustment // determining if the current timestamp is in the summer or winter season and setting SurfSlope accordingly case TrackMode.FTSA: // SummerDate and WinterDate must be recalculated if year changes due to possible leap year if (previousYear != Year) { SummerDate = new DateTime(Year, itsSummerMonth, itsSummerDay); WinterDate = new DateTime(Year, itsWinterMonth, itsWinterDay); } previousYear = Year; // Winter date is before summer date in calender year if (SummerDate.DayOfYear - WinterDate.DayOfYear > 0) { if (DayOfYear >= WinterDate.DayOfYear && DayOfYear < SummerDate.DayOfYear) { SurfSlope = itsPlaneTiltWinter; } else { SurfSlope = itsPlaneTiltSummer; } } // Summer date is before winter date in calender year else { if (DayOfYear >= SummerDate.DayOfYear && DayOfYear < WinterDate.DayOfYear) { SurfSlope = itsPlaneTiltSummer; } else { SurfSlope = itsPlaneTiltWinter; } } break; case TrackMode.NOAT: break; // Throw error to user if there is an issue with the tracker. default: ErrorLogger.Log("Tracking Parameters were incorrectly defined. Please check your input file.", ErrLevel.FATAL); break; } IncidenceAngle = Tilt.GetIncidenceAngle(SunZenith, SunAzimuth, SurfSlope, SurfAzimuth); }
// 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; } } } } }
// Calculation for inverter output power, using efficiency curve public void Calculate ( int DayOfYear // Day of year (1-365) , double Hour // Hour of day, in decimal format (11.75 = 11:45 a.m.) ) { double itsSLatR = Utilities.ConvertDtoR(itsSLat); double itsSLongR = Utilities.ConvertDtoR(itsSLong); double itsMLongR = Utilities.ConvertDtoR(itsMLong); try { if (DayOfYear < 1 || DayOfYear > 365 || Hour < 0 || Hour > 24) { throw new CASSYSException("Sun.Calculate: Invalid time stamp for sun position calculation"); } } catch (CASSYSException cs) { ErrorLogger.Log(cs, ErrLevel.FATAL); } // Compute declination and normal extraterrestrial Irradiance if day has changed // Compute Sunrise and Sunset hour angles if (DayOfYear != itsCurrentDayOfYear) { itsCurrentDayOfYear = DayOfYear; itsCurrentDecl = Astro.GetDeclination(itsCurrentDayOfYear); itsCurrentNExtra = Astro.GetNormExtra(itsCurrentDayOfYear); // Variables used to hold the apparent/true sunset and sunrise hour angles double appSunRiseHA; // Hour angle for Sunrise [radians] double appSunsetHA; // Hour angle for Sunset [radians] double trueSunsetHA; // True Sunset Hour angle [radians] // Invoking the Tilt method to get the values Tilt.CalcApparentSunsetHourAngle(itsSLatR, itsCurrentDecl, itsSurfaceSlope, Azimuth, out appSunRiseHA, out appSunsetHA, out trueSunsetHA); // Assigning to the output values AppSunriseHour = Math.Abs(appSunRiseHA) * Util.HAtoR; AppSunsetHour = Util.NoonHour + appSunsetHA * Util.HAtoR; TrueSunSetHour = Util.NoonHour + trueSunsetHA * Util.HAtoR; TrueSunRiseHour = TrueSunSetHour - Astro.GetDayLength(itsSLatR, itsCurrentDecl); // If using local standard time then modify the sunrise and sunset to match the local time stamp. if (itsLSTFlag) { TrueSunSetHour -= Astro.GetATmsST(DayOfYear, itsSLongR, itsMLongR) / 60; // Going from solar to local time TrueSunRiseHour = TrueSunSetHour - Astro.GetDayLength(itsSLatR, itsCurrentDecl); } } // Compute hour angle double SolarTime = Hour; if (itsLSTFlag) { SolarTime += Astro.GetATmsST(DayOfYear, itsSLongR, itsMLongR) / 60; // Going from local to solar time } double HourAngle = Astro.GetHourAngle(SolarTime); // Compute azimuth and zenith angles Astro.CalcSunPositionHourAngle(itsCurrentDecl, HourAngle, itsSLatR, out Zenith, out Azimuth); // Compute normal extraterrestrial Irradiance NExtra = itsCurrentNExtra; // Compute air mass AirMass = Astro.GetAirMass(Zenith); }