function sun = sun_position(time, location)
% sun = sun_position(time, location)
%
% This function compute the sun position (zenith and azimuth angle at the observer
% location) as a function of the observer local time and position. 
%
% It is an implementation of the algorithm presented by Reda et Andreas in:
%   Reda, I., Andreas, A. (2003) Solar position algorithm for solar
%   radiation application. National Renewable Energy Laboratory (NREL)
%   Technical report NREL/TP-560-34302. 
% This document is avalaible at www.osti.gov/bridge
%
% This algorithm is based on numerical approximation of the exact equations.
% The authors of the original paper state that this algorithm should be
% precise at +/- 0.0003 degrees. I have compared it to NOAA solar table
% (http://www.srrb.noaa.gov/highlights/sunrise/azel.html) and to USNO solar
% table (http://aa.usno.navy.mil/data/docs/AltAz.html) and found very good
% correspondance (up to the precision of those tables), except for large
% zenith angle, where the refraction by the atmosphere is significant 
% (difference of about 1 degree). Note that in this code the correction 
% for refraction in the atmosphere as been implemented for a temperature 
% of 10C (283 kelvins) and a pressure of 1010 mbar. See the subfunction 
% «sun_topocentric_zenith_angle_calculation» for a possible modification 
% to explicitely model the effect of temperature and pressure as describe
% in Reda & Andreas (2003). 
%
% Input parameters:
%   time: a structure that specify the time when the sun position is
%   calculated. 
%       time.year: year. Valid for [-2000, 6000]
%       time.month: month [1-12]
%       time.day: calendar day [1-31]
%       time.hour: local hour [0-23]
%       time.min: minute [0-59]
%       time.sec: second [0-59]
%       time.UTC: offset hour from UTC. Local time = Greenwich time + time.UTC
%   This input can also be passed using the Matlab time format ('dd-mmm-yyyy HH:MM:SS'). 
%   In that case, the time has to be specified as UTC time (time.UTC = 0)
%
%   location: a structure that specify the location of the observer
%       location.latitude: latitude (in degrees, north of equator is
%       positive)
%       location.longitude: longitude (in degrees, positive for east of
%       Greenwich)
%       location.altitude: altitude above mean sea level (in meters) 
% 
% Output parameters
%   sun: a structure with the calculated sun position
%       sun.zenith = zenith angle in degrees (angle from the vertical)
%       sun.azimuth = azimuth angle in degrees, eastward from the north. 
% Only the sun zenith and azimuth angles are returned as output, but a lot
% of other parameters are calculated that could also extracted as output of
% this function. 
%
% Exemple of use
%
% location.longitude = -105.1786; 
% location.latitude = 39.742476; 
% location.altitude = 1830.14;
% time.year = 2003;
% time.month = 10;
% time.day = 17;  
% time.hour = 12;
% time.min = 30;
% time.sec = 30;
% time.UTC = -7;
%
% sun = sun_position(time, location);
%
% sun = 
% 
%      zenith: 50.1080438859849
%      azimuth: 194.341174010338
%
% History
%   09/03/2004  Original creation by Vincent Roy (vincent.roy@drdc-rddc.gc.ca)
%   10/03/2004  Fixed a bug in julian_calculation subfunction (was
%               incorrect for year 1582 only), Vincent Roy
%   18/03/2004  Correction to the header (help display) only. No changes to
%               the code. (changed the «elevation» field in «location» structure
%               information to «altitude»), Vincent Roy
%   13/04/2004  Following a suggestion from Jody Klymak (jklymak@ucsd.edu),
%               allowed the 'time' input to be passed as a Matlab time string. 
%   22/08/2005  Following a bug report from Bruce Bowler
%               (bbowler@bigelow.org), modified the julian_calculation function. Bug
%               was 'MATLAB has allowed structure assignment  to a non-empty non-structure 
%               to overwrite the previous value.  This behavior will continue in this release, 
%               but will be an error in a  future version of MATLAB.  For advice on how to 
%               write code that  will both avoid this warning and work in future versions of 
%               MATLAB,  see R14SP2 Release Notes'. Script should now be
%               compliant with futher release of Matlab...


% 1. Calculate the Julian Day, and Century. Julian Ephemeris day, century
% and millenium are calculated using a mean delta_t of 33.184 seconds.  
julian = julian_calculation(time);

% 2. Calculate the Earth heliocentric longitude, latitude, and radius
% vector (L, B, and R)
earth_heliocentric_position = earth_heliocentric_position_calculation(julian);

% 3. Calculate the geocentric longitude and latitude
sun_geocentric_position = sun_geocentric_position_calculation(earth_heliocentric_position);

% 4. Calculate the nutation in longitude and obliquity (in degrees). 
nutation = nutation_calculation(julian);

% 5. Calculate the true obliquity of the ecliptic (in degrees). 
true_obliquity = true_obliquity_calculation(julian, nutation);

% 6. Calculate the aberration correction (in degrees)
aberration_correction = abberation_correction_calculation(earth_heliocentric_position);

% 7. Calculate the apparent sun longitude in degrees)
apparent_sun_longitude = apparent_sun_longitude_calculation(sun_geocentric_position, nutation, aberration_correction);

% 8. Calculate the apparent sideral time at Greenwich (in degrees)
apparent_stime_at_greenwich = apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity);

% 9. Calculate the sun rigth ascension (in degrees)
sun_rigth_ascension = sun_rigth_ascension_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position);

% 10. Calculate the geocentric sun declination (in degrees). Positive or
% negative if the sun is north or south of the celestial equator. 
sun_geocentric_declination = sun_geocentric_declination_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position);

% 11. Calculate the observer local hour angle (in degrees, westward from south).
observer_local_hour = observer_local_hour_calculation(apparent_stime_at_greenwich, location, sun_rigth_ascension);

% 12. Calculate the topocentric sun position (rigth ascension, declination and
% rigth ascension parallax in degrees)
topocentric_sun_position = topocentric_sun_position_calculate(earth_heliocentric_position, location, observer_local_hour, sun_rigth_ascension, sun_geocentric_declination);

% 13. Calculate the topocentric local hour angle (in degrees)
topocentric_local_hour = topocentric_local_hour_calculate(observer_local_hour, topocentric_sun_position);

% 14. Calculate the topocentric zenith and azimuth angle (in degrees)
sun = sun_topocentric_zenith_angle_calculate(location, topocentric_sun_position, topocentric_local_hour);





%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Subfunction definitions %
%%%%%%%%%%%%%%%%%%%%%%%%%%%




function julian = julian_calculation(t_input)
% This function compute the julian day and julian century from the local
% time and timezone information. Ephemeris are calculated with a delta_t=0
% seconds. 

% If time input is a Matlab time string, extract the information from
% this string and create the structure as defined in the main header of
% this script.
if ~isstruct(t_input)
    tt = datevec(t_input);
    time.UTC=0;
    time.year = tt(1);
    time.month = tt(2);
    time.day = tt(3);
    time.hour = tt(4);
    time.min = tt(5);
    time.sec = tt(6);
else
    time = t_input;
end

if(time.month == 1 | time.month == 2)
    Y = time.year - 1;
    M = time.month + 12;
else
    Y = time.year;
    M = time.month; 
end
ut_time = ((time.hour - time.UTC)/24) + (time.min/(60*24)) + (time.sec/(60*60*24)); % time of day in UT time. 
D = time.day + ut_time; % Day of month in decimal time, ex. 2sd day of month at 12:30:30UT, D=2.521180556


% In 1582, the gregorian calendar was adopted
if(time.year == 1582)
    if(time.month == 10)
        if(time.day <= 4) % The Julian calendar ended on October 4, 1582
            B = 0;    
        elseif(time.day >= 15) % The Gregorian calendar started on October 15, 1582
            A = floor(Y/100);
            B = 2 - A + floor(A/4);    
        else
            disp('This date never existed!. Date automatically set to October 4, 1582');
            time.month = 10;
            time.day = 4; 
            B = 0;
        end
    elseif(time.month<10) % Julian calendar 
        B = 0;
    else % Gregorian calendar
        A = floor(Y/100);
        B = 2 - A + floor(A/4);
    end
    
elseif(time.year<1582) % Julian calendar
    B = 0;
else
    A = floor(Y/100); % Gregorian calendar
    B = 2 - A + floor(A/4);
end

julian.day = floor(365.25*(Y+4716)) + floor(30.6001*(M+1)) + D + B - 1524.5;

delta_t = 0; % 33.184;
julian.ephemeris_day = julian.day + (delta_t/86400);

julian.century = (julian.day - 2451545) / 36525; 

julian.ephemeris_century = (julian.ephemeris_day - 2451545) / 36525;

julian.ephemeris_millenium = julian.ephemeris_century / 10; 


function earth_heliocentric_position = earth_heliocentric_position_calculation(julian)
% This function compute the earth position relative to the sun, using
% tabulated values. 

% Tabulated values for the longitude calculation
% L terms  from the original code. 
 L0_terms = [175347046.0 0 0  
 3341656.0 4.6692568 6283.07585  
 34894.0 4.6261 12566.1517  
 3497.0 2.7441 5753.3849  
 3418.0 2.8289 3.5231  
 3136.0 3.6277 77713.7715  
 2676.0 4.4181 7860.4194  
 2343.0 6.1352 3930.2097  
 1324.0 0.7425 11506.7698  
 1273.0 2.0371 529.691  
 1199.0 1.1096 1577.3435  
 990 5.233 5884.927  
 902 2.045 26.298
 857 3.508 398.149  
 780 1.179 5223.694  
 753 2.533 5507.553  
 505 4.583 18849.228  
 492 4.205 775.523  
 357 2.92 0.067  
 317 5.849 11790.629  
 284 1.899 796.298  
 271 0.315 10977.079  
 243 0.345 5486.778  
 206 4.806 2544.314  
 205 1.869 5573.143  
 202 2.4458 6069.777  
 156 0.833 213.299  
 132 3.411 2942.463  
 126 1.083 20.775  
 115 0.645 0.98  
 103 0.636 4694.003  
 102 0.976 15720.839  
 102 4.267 7.114  
 99 6.21 2146.17  
 98 0.68 155.42  
 86 5.98 161000.69  
 85 1.3 6275.96  
 85 3.67 71430.7  
 80 1.81 17260.15  
 79 3.04 12036.46  
 71 1.76 5088.63  
 74 3.5 3154.69  
 74 4.68 801.82  
 70 0.83 9437.76  
 62 3.98 8827.39  
 61 1.82 7084.9  
 57 2.78 6286.6  
 56 4.39 14143.5  
 56 3.47 6279.55  
 52 0.19 12139.55  
 52 1.33 1748.02  
 51 0.28 5856.48  
 49 0.49 1194.45  
 41 5.37 8429.24  
 41 2.4 19651.05  
 39 6.17 10447.39  
 37 6.04 10213.29  
 37 2.57 1059.38  
 36 1.71 2352.87  
 36 1.78 6812.77  
 33 0.59 17789.85  
 30 0.44 83996.85  
 30 2.74 1349.87  
 25 3.16 4690.48];
 
L1_terms = [628331966747.0 0 0  
 206059.0 2.678235 6283.07585  
 4303.0 2.6351 12566.1517  
 425.0 1.59 3.523  
 119.0 5.796 26.298  
 109.0 2.966 1577.344  
 93 2.59 18849.23  
 72 1.14 529.69  
 68 1.87 398.15  
 67 4.41 5507.55  
 59 2.89 5223.69  
 56 2.17 155.42  
 45 0.4 796.3  
 36 0.47 775.52  
 29 2.65 7.11  
 21 5.34 0.98  
 19 1.85 5486.78  
 19 4.97 213.3  
 17 2.99 6275.96  
 16 0.03 2544.31  
 16 1.43 2146.17  
 15 1.21 10977.08
 12 2.83 1748.02  
 12 3.26 5088.63  
 12 5.27 1194.45  
 12 2.08 4694  
 11 0.77 553.57  
 10 1.3 3286.6  
 10 4.24 1349.87  
 9 2.7 242.73  
 9 5.64 951.72  
 8 5.3 2352.87  
 6 2.65 9437.76  
 6 4.67 4690.48]; 
  
L2_terms = [52919.0 0 0  
 8720.0 1.0721 6283.0758  
 309.0 0.867 12566.152  
 27 0.05 3.52  
 16 5.19 26.3  
 16 3.68 155.42  
 10 0.76 18849.23  
 9 2.06 77713.77  
 7 0.83 775.52  
 5 4.66 1577.34  
 4 1.03 7.11  
 4 3.44 5573.14  
 3 5.14 796.3  
 3 6.05 5507.55  
 3 1.19 242.73  
 3 6.12 529.69  
 3 0.31 398.15  
 3 2.28 553.57  
 2 4.38 5223.69  
 2 3.75 0.98];

L3_terms =[289.0 5.844 6283.076  
 35 0 0  
 17 5.49 12566.15  
 3 5.2 155.42  
 1 4.72 3.52  
 1 5.3 18849.23  
 1 5.97 242.73]; 
 
L4_terms = [114.0 3.142 0  
 8 4.13 6283.08  
 1 3.84 12566.15];

L5_terms = [1 3.14 0]; 

A0 = L0_terms(:,1);
B0 = L0_terms(:,2);
C0 = L0_terms(:,3);

A1 = L1_terms(:,1);
B1 = L1_terms(:,2);
C1 = L1_terms(:,3);

A2 = L2_terms(:,1);
B2 = L2_terms(:,2);
C2 = L2_terms(:,3);

A3 = L3_terms(:,1);
B3 = L3_terms(:,2);
C3 = L3_terms(:,3);

A4 = L4_terms(:,1);
B4 = L4_terms(:,2);
C4 = L4_terms(:,3);

A5 = L5_terms(:,1);
B5 = L5_terms(:,2);
C5 = L5_terms(:,3);

JME = julian.ephemeris_millenium;

% Compute the Earth Heliochentric longitude from the tabulated values. 
L0 = sum(A0 .* cos(B0 + (C0 * JME)));
L1 = sum(A1 .* cos(B1 + (C1 * JME)));
L2 = sum(A2 .* cos(B2 + (C2 * JME)));
L3 = sum(A3 .* cos(B3 + (C3 * JME)));
L4 = sum(A4 .* cos(B4 + (C4 * JME)));
L5 = A5 .* cos(B5 + (C5 * JME));


earth_heliocentric_position.longitude = (L0 + (L1 * JME) + (L2 * JME^2) + (L3 * JME^3) + (L4 * JME^4) + (L5 * JME^5)) / 1e8; 
% Convert the longitude to degrees. 
earth_heliocentric_position.longitude = earth_heliocentric_position.longitude * 180/pi;
% Limit the range to [0,360[;
earth_heliocentric_position.longitude = set_to_range(earth_heliocentric_position.longitude, 0, 360);

% Tabulated values for the earth heliocentric latitude. 
% B terms  from the original code. 
 B0_terms = [280.0 3.199 84334.662  
 102.0 5.422 5507.553  
 80 3.88 5223.69  
 44 3.7 2352.87  
 32 4 1577.34]; 
  
B1_terms = [9 3.9 5507.55  
 6 1.73 5223.69]; 

A0 = B0_terms(:,1);
B0 = B0_terms(:,2);
C0 = B0_terms(:,3);

A1 = B1_terms(:,1);
B1 = B1_terms(:,2);
C1 = B1_terms(:,3);

L0 = sum(A0 .* cos(B0 + (C0 * JME)));
L1 = sum(A1 .* cos(B1 + (C1 * JME)));

earth_heliocentric_position.latitude = (L0 + (L1 * JME)) / 1e8; 
% Convert the latitude to degrees. 
earth_heliocentric_position.latitude = earth_heliocentric_position.latitude * 180/pi;
% Limit the range to [0,360];
earth_heliocentric_position.latitude = set_to_range(earth_heliocentric_position.latitude, 0, 360);

% Tabulated values for radius vector. 
% R terms from the original code
R0_terms = [ 100013989.0 0 0  
 1670700.0 3.0984635 6283.07585  
 13956.0 3.05525 12566.1517   
 3084.0 5.1985 77713.7715  
 1628.0 1.1739 5753.3849  
 1576.0 2.8469 7860.4194  
 925.0 5.453 11506.77  
 542.0 4.564 3930.21  
 472.0 3.661 5884.927  
 346.0 0.964 5507.553  
 329.0 5.9 5223.694  
 307.0 0.299 5573.143  
 243.0 4.273 11790.629  
 212.0 5.847 1577.344  
 186.0 5.022 10977.079  
 175.0 3.012 18849.228  
 110.0 5.055 5486.778  
 98 0.89 6069.78  
 86 5.69 15720.84  
 86 1.27 161000.69  
 85 0.27 17260.15  
 63 0.92 529.69  
 57 2.01 83996.85  
 56 5.24 71430.7  
 49 3.25 2544.31  
 47 2.58 775.52  
 45 5.54 9437.76  
 43 6.01 6275.96  
 39 5.36 4694  
 38 2.39 8827.39  
 37 0.83 19651.05  
 37 4.9 12139.55  
 36 1.67 12036.46  
 35 1.84 2942.46  
 33 0.24 7084.9  
 32 0.18 5088.63  
 32 1.78 398.15  
 28 1.21 6286.6  
 28 1.9 6279.55  
 26 4.59 10447.39];
   
R1_terms = [ 103019.0 1.10749 6283.07585  
 1721.0 1.0644 12566.1517  
 702.0 3.142 0  
 32 1.02 18849.23  
 31 2.84 5507.55  
 25 1.32 5223.69  
 18 1.42 1577.34  
 10 5.91 10977.08  
 9 1.42 6275.96  
 9 0.27 5486.78];
  
R2_terms = [4359.0 5.7846 6283.0758  
 124.0 5.579 12566.152  
 12 3.14 0  
 9 3.63 77713.77  
 6 1.87 5573.14  
 3 5.47 18849];
 
R3_terms = [145.0 4.273 6283.076  
 7 3.92 12566.15];

R4_terms = [4 2.56 6283.08];


A0 = R0_terms(:,1);
B0 = R0_terms(:,2);
C0 = R0_terms(:,3);

A1 = R1_terms(:,1);
B1 = R1_terms(:,2);
C1 = R1_terms(:,3);

A2 = R2_terms(:,1);
B2 = R2_terms(:,2);
C2 = R2_terms(:,3);

A3 = R3_terms(:,1);
B3 = R3_terms(:,2);
C3 = R3_terms(:,3);

A4 = R4_terms(:,1);
B4 = R4_terms(:,2);
C4 = R4_terms(:,3);

% Compute the Earth heliocentric radius vector
L0 = sum(A0 .* cos(B0 + (C0 * JME)));
L1 = sum(A1 .* cos(B1 + (C1 * JME)));
L2 = sum(A2 .* cos(B2 + (C2 * JME)));
L3 = sum(A3 .* cos(B3 + (C3 * JME)));
L4 = A4 .* cos(B4 + (C4 * JME));

% Units are in AU
earth_heliocentric_position.radius = (L0 + (L1 * JME) + (L2 * JME^2) + (L3 * JME^3) + (L4 * JME^4)) / 1e8; 





function sun_geocentric_position = sun_geocentric_position_calculation(earth_heliocentric_position)
% This function compute the sun position relative to the earth. 

sun_geocentric_position.longitude = earth_heliocentric_position.longitude + 180;
% Limit the range to [0,360];
sun_geocentric_position.longitude = set_to_range(sun_geocentric_position.longitude, 0, 360);

sun_geocentric_position.latitude = -earth_heliocentric_position.latitude;
% Limit the range to [0,360]
sun_geocentric_position.latitude = set_to_range(sun_geocentric_position.latitude, 0, 360);


function nutation = nutation_calculation(julian)
% This function compute the nutation in longtitude and in obliquity, in
% degrees. 

% All Xi are in degrees. 
JCE = julian.ephemeris_century;

% 1. Mean elongation of the moon from the sun 
p = [(1/189474) -0.0019142 445267.11148 297.85036];
% X0 = polyval(p, JCE);
X0 = p(1) * JCE^3 + p(2) * JCE^2 + p(3) * JCE + p(4); % This is faster than polyval...

% 2. Mean anomaly of the sun (earth)
p = [-(1/300000) -0.0001603 35999.05034 357.52772];
% X1 = polyval(p, JCE);
X1 = p(1) * JCE^3 + p(2) * JCE^2 + p(3) * JCE + p(4); 

% 3. Mean anomaly of the moon
p = [(1/56250) 0.0086972 477198.867398 134.96298];
% X2 = polyval(p, JCE);
X2 = p(1) * JCE^3 + p(2) * JCE^2 + p(3) * JCE + p(4); 

% 4. Moon argument of latitude
p = [(1/327270) -0.0036825 483202.017538 93.27191];
% X3 = polyval(p, JCE);
X3 = p(1) * JCE^3 + p(2) * JCE^2 + p(3) * JCE + p(4); 

% 5. Longitude of the ascending node of the moon's mean orbit on the
% ecliptic, measured from the mean equinox of the date
p = [(1/450000) 0.0020708 -1934.136261 125.04452];
% X4 = polyval(p, JCE);
X4 = p(1) * JCE^3 + p(2) * JCE^2 + p(3) * JCE + p(4); 

% Y tabulated terms from the original code
Y_terms =  [0 0 0 0 1  
 -2 0 0 2 2  
 0 0 0 2 2  
 0 0 0 0 2  
 0 1 0 0 0  
 0 0 1 0 0  
 -2 1 0 2 2  
 0 0 0 2 1  
 0 0 1 2 2  
 -2 -1 0 2 2  
 -2 0 1 0 0  
 -2 0 0 2 1  
 0 0 -1 2 2  
 2 0 0 0 0  
 0 0 1 0 1  
 2 0 -1 2 2  
 0 0 -1 0 1  
 0 0 1 2 1  
 -2 0 2 0 0  
 0 0 -2 2 1  
 2 0 0 2 2  
 0 0 2 2 2  
 0 0 2 0 0  
 -2 0 1 2 2  
 0 0 0 2 0  
 -2 0 0 2 0  
 0 0 -1 2 1  
 0 2 0 0 0  
 2 0 -1 0 1  
 -2 2 0 2 2  
 0 1 0 0 1  
 -2 0 1 0 1  
 0 -1 0 0 1  
 0 0 2 -2 0  
 2 0 -1 2 1  
 2 0 1 2 2  
 0 1 0 2 2  
 -2 1 1 0 0  
 0 -1 0 2 2  
 2 0 0 2 1  
 2 0 1 0 0  
 -2 0 2 2 2  
 -2 0 1 2 1  
 2 0 -2 0 1  
 2 0 0 0 1  
 0 -1 1 0 0  
 -2 -1 0 2 1  
 -2 0 0 0 1  
 0 0 2 2 1  
 -2 0 2 0 1  
 -2 1 0 2 1  
 0 0 1 -2 0  
 -1 0 1 0 0  
 -2 1 0 0 0  
 1 0 0 0 0  
 0 0 1 2 0  
 0 0 -2 2 2  
 -1 -1 1 0 0  
 0 1 1 0 0  
 0 -1 1 2 2  
 2 -1 -1 2 2  
 0 0 3 2 2  
 2 -1 0 2 2];

nutation_terms = [ -171996 -174.2 92025 8.9  
 -13187 -1.6 5736 -3.1  
 -2274 -0.2 977 -0.5  
 2062 0.2 -895 0.5  
 1426 -3.4 54 -0.1  
 712 0.1 -7 0  
 -517 1.2 224 -0.6  
 -386 -0.4 200 0  
 -301 0 129 -0.1  
 217 -0.5 -95 0.3  
 -158 0 0 0  
 129 0.1 -70 0  
 123 0 -53 0  
 63 0 0 0  
 63 0.1 -33 0  
 -59 0 26 0  
 -58 -0.1 32 0  
 -51 0 27 0  
 48 0 0 0  
 46 0 -24 0  
 -38 0 16 0  
 -31 0 13 0  
 29 0 0 0  
 29 0 -12 0  
 26 0 0 0  
 -22 0 0 0  
 21 0 -10 0  
 17 -0.1 0 0  
 16 0 -8 0  
 -16 0.1 7 0  
 -15 0 9 0  
 -13 0 7 0  
 -12 0 6 0  
 11 0 0 0  
 -10 0 5 0  
 -8 0 3 0  
 7 0 -3 0  
 -7 0 0 0  
 -7 0 3 0  
 -7 0 3 0  
 6 0 0 0  
 6 0 -3 0  
 6 0 -3 0  
 -6 0 3 0  
 -6 0 3 0  
 5 0 0 0  
 -5 0 3 0  
 -5 0 3 0  
 -5 0 3 0  
 4 0 0 0  
 4 0 0 0  
 4 0 0 0  
 -4 0 0 0  
 -4 0 0 0  
 -4 0 0 0  
 3 0 0 0  
 -3 0 0 0  
 -3 0 0 0  
 -3 0 0 0  
 -3 0 0 0  
 -3 0 0 0  
 -3 0 0 0  
 -3 0 0 0];

% Using the tabulated values, compute the delta_longitude and
% delta_obliquity. 
Xi = [X0
    X1
    X2
    X3
    X4];

tabulated_argument = (Y_terms * Xi) * pi/180;

delta_longitude = ((nutation_terms(:,1) + (nutation_terms(:,2) * JCE))) .* sin(tabulated_argument);
delta_obliquity = ((nutation_terms(:,3) + (nutation_terms(:,4) * JCE))) .* cos(tabulated_argument);

% Nutation in longitude
nutation.longitude = sum(delta_longitude) / 36000000;

% Nutation in obliquity
nutation.obliquity = sum(delta_obliquity) / 36000000;



function true_obliquity = true_obliquity_calculation(julian, nutation)
% This function compute the true obliquity of the ecliptic. 


p = [2.45 5.79 27.87 7.12 -39.05 -249.67 -51.38 1999.25 -1.55 -4680.93 84381.448];
% mean_obliquity = polyval(p, julian.ephemeris_millenium/10);

U = julian.ephemeris_millenium/10;
mean_obliquity = p(1)*U^10 + p(2)*U^9 + p(3)*U^8 + p(4)*U^7 + p(5)*U^6 + p(6)*U^5 + p(7)*U^4 + p(8)*U^3 + p(9)*U^2 + p(10)*U + p(11);


true_obliquity = (mean_obliquity/3600) + nutation.obliquity;


function aberration_correction = abberation_correction_calculation(earth_heliocentric_position)
% This function compute the aberration_correction, as a function of the
% earth-sun distance. 

aberration_correction = -20.4898/(3600*earth_heliocentric_position.radius);


function apparent_sun_longitude = apparent_sun_longitude_calculation(sun_geocentric_position, nutation, aberration_correction)
% This function compute the sun apparent longitude

apparent_sun_longitude = sun_geocentric_position.longitude + nutation.longitude + aberration_correction;


function apparent_stime_at_greenwich = apparent_stime_at_greenwich_calculation(julian, nutation, true_obliquity)
% This function compute the apparent sideral time at Greenwich. 

JD = julian.day;
JC = julian.century;

% Mean sideral time, in degrees
mean_stime = 280.46061837 + (360.98564736629*(JD-2451545)) + (0.000387933*JC^2) - (JC^3/38710000);

% Limit the range to [0-360];
mean_stime = set_to_range(mean_stime, 0, 360);

apparent_stime_at_greenwich = mean_stime + (nutation.longitude * cos(true_obliquity * pi/180));


function sun_rigth_ascension = sun_rigth_ascension_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position)
% This function compute the sun rigth ascension. 

argument_numerator = (sin(apparent_sun_longitude * pi/180) * cos(true_obliquity * pi/180)) - ...
    (tan(sun_geocentric_position.latitude * pi/180) * sin(true_obliquity * pi/180));
argument_denominator = cos(apparent_sun_longitude * pi/180);

sun_rigth_ascension = atan2(argument_numerator, argument_denominator) * 180/pi;
% Limit the range to [0,360];
sun_rigth_ascension = set_to_range(sun_rigth_ascension, 0, 360);


function sun_geocentric_declination = sun_geocentric_declination_calculation(apparent_sun_longitude, true_obliquity, sun_geocentric_position)

argument = (sin(sun_geocentric_position.latitude * pi/180) * cos(true_obliquity * pi/180)) + ...
    (cos(sun_geocentric_position.latitude * pi/180) * sin(true_obliquity * pi/180) * sin(apparent_sun_longitude * pi/180));

sun_geocentric_declination = asin(argument) * 180/pi;

function observer_local_hour = observer_local_hour_calculation(apparent_stime_at_greenwich, location, sun_rigth_ascension)

observer_local_hour = apparent_stime_at_greenwich + location.longitude - sun_rigth_ascension;
% Set the range to [0-360]
observer_local_hour = set_to_range(observer_local_hour, 0, 360);


function topocentric_sun_position = topocentric_sun_position_calculate(earth_heliocentric_position, location, observer_local_hour, sun_rigth_ascension, sun_geocentric_declination)
% This function compute the sun position (rigth ascension and declination)
% with respect to the observer local position at the Earth surface. 

% Equatorial horizontal parallax of the sun in degrees
eq_horizontal_parallax = 8.794 / (3600 * earth_heliocentric_position.radius);

% Term u, used in the following calculations (in radians)
u = atan(0.99664719 * tan(location.latitude * pi/180));

% Term x, used in the following calculations
x = cos(u) + ((location.altitude/6378140) * cos(location.latitude * pi/180));

% Term y, used in the following calculations
y = (0.99664719 * sin(u)) + ((location.altitude/6378140) * sin(location.latitude * pi/180));

% Parallax in the sun rigth ascension (in radians)
nominator = -x * sin(eq_horizontal_parallax * pi/180) * sin(observer_local_hour * pi/180);
denominator = cos(sun_geocentric_declination * pi/180) - (x * sin(eq_horizontal_parallax * pi/180) * cos(observer_local_hour * pi/180));
sun_rigth_ascension_parallax = atan2(nominator, denominator);
% Conversion to degrees. 
topocentric_sun_position.rigth_ascension_parallax = sun_rigth_ascension_parallax * 180/pi;

% Topocentric sun rigth ascension (in degrees)
topocentric_sun_position.rigth_ascension = sun_rigth_ascension + (sun_rigth_ascension_parallax * 180/pi);

% Topocentric sun declination (in degrees)
nominator = (sin(sun_geocentric_declination * pi/180) - (y*sin(eq_horizontal_parallax * pi/180))) * cos(sun_rigth_ascension_parallax);
denominator = cos(sun_geocentric_declination * pi/180) - (y*sin(eq_horizontal_parallax * pi/180)) * cos(observer_local_hour * pi/180);
topocentric_sun_position.declination = atan2(nominator, denominator) * 180/pi;


function topocentric_local_hour = topocentric_local_hour_calculate(observer_local_hour, topocentric_sun_position)
% This function compute the topocentric local jour angle in degrees

topocentric_local_hour = observer_local_hour - topocentric_sun_position.rigth_ascension_parallax;


function sun = sun_topocentric_zenith_angle_calculate(location, topocentric_sun_position, topocentric_local_hour)
% This function compute the sun zenith angle, taking into account the
% atmospheric refraction. A default temperature of 283K and a
% default pressure of 1010 mbar are used. 

% Topocentric elevation, without atmospheric refraction
argument = (sin(location.latitude * pi/180) * sin(topocentric_sun_position.declination * pi/180)) + ...
    (cos(location.latitude * pi/180) * cos(topocentric_sun_position.declination * pi/180) * cos(topocentric_local_hour * pi/180));
true_elevation = asin(argument) * 180/pi;

% Atmospheric refraction correction (in degrees)
argument = true_elevation + (10.3/(true_elevation + 5.11));
refraction_corr = 1.02 / (60 * tan(argument * pi/180));

% For exact pressure and temperature correction, use this, 
% with P the pressure in mbar amd T the temperature in Kelvins:
% refraction_corr = (P/1010) * (283/T) * 1.02 / (60 * tan(argument * pi/180));

% Apparent elevation
apparent_elevation = true_elevation + refraction_corr;

sun.zenith = 90 - apparent_elevation;

% Topocentric azimuth angle. The +180 conversion is to pass from astronomer
% notation (westward from south) to navigation notation (eastward from
% north);
nominator = sin(topocentric_local_hour * pi/180);
denominator = (cos(topocentric_local_hour * pi/180) * sin(location.latitude * pi/180)) - ...
    (tan(topocentric_sun_position.declination * pi/180) * cos(location.latitude * pi/180));
sun.azimuth = (atan2(nominator, denominator) * 180/pi) + 180;
% Set the range to [0-360]
sun.azimuth = set_to_range(sun.azimuth, 0, 360);


% This function make sure the variable is in the specified range. 
function var = set_to_range(var, min_interval, max_interval)


% if(var>0)
%     var = var - max_interval * floor(var/max_interval);
% else
%     var = var - max_interval * ceil(var/max_interval);
% end
% 
% if(var<min_interval)
%     var = var + max_interval;
% end

var = var - max_interval * floor(var/max_interval);

if(var<min_interval)
    var = var + max_interval;
end
