sun.frink


// This is a library of calculations for calculating astronomical positions.
// Most algorithms are based on Jean Meeus book _Astronomical Algorithms_

// Sign conventions 
// It is *highly* recommended that you use these constants instead of
// assuming a sign convention.  These are the conventions used in Meeus and
// many other astronomical calculations, which often differ than sign
// conventions used in geoinformatics.
North := +1
South := -1
West := +1
East := -1


// Turn a latitude measurement into the appropriate letter or word.  (e.g.
// "N" or "S"
// Pass in the latitude and an optional flag to produce the letter
// or word.  Default is letter only.
latitudeName[lat, short=true] :=
{
   if lat >= 0 degrees
      short ? "N" : "North"
   else
      short ? "S" : "South"
}

// Turn a longitude measurement into the appropriate letter or word.  (e.g.
// "N" or "S"
// Pass in the longitude and an optional flag to produce the letter
// or word.  Default is letter only.
longitudeName[long, short=true] :=
{
   if long >= 0 degrees
      short ? "W" : "West"
   else
      short ? "E" : "East"
}

// Returns the value of omega from Chapter 25 of Jean Meeus.
meeusOmega[date] := 
{
   T = meeusT[date]
   return (125.04 - 1934.136 T) degree
}


// Returns the apparent longitude of the sun (lambda) (low accuracy)
// from Meeus, Chapter 25
sunApparentLongitude[date] :=
{
   T = meeusT[date]

   omega = meeusOmega[date]
//   println["omega: " + (omega mod (360 degree) -> "degrees")];

   return sunTrueLongitude[date] - 0.00569 degree - 0.00478 degree sin[omega]
}


// Returns geometric mean longitude L0 of the sun, referred to the mean equinox
// of the date.  Equation 25.2 in Jean Meeus.
sunGeometricMeanLongitude[date] := 
{
   T = meeusT[date]
   return (280.46646 + 36000.76983 T + 0.0003032 T^2) degree
}


// Returns true longitude (sunSymbol) of the sun for a specified date.
// From Chapter 25 in Jean Meeus.
sunTrueLongitude[date] := 
{
   return sunGeometricMeanLongitude[date] + sunCenter[date]
}


// Returns the mean anomaly M of the sun for a given date.
// Equation 25.3 in Jean Meeus.
sunMeanAnomaly[date] := 
{
   T = meeusT[date]
   return ((357.52911 + 35999.05029 T - 0.0001537 T^2) degree) mod (360 degree)
}


// Returns the true anomaly nu of the sun for a given date.
// From Chapter 25 of Meeus.
sunTrueAnomaly[date] := 
{
   T = meeusT[date]
   return sunMeanAnomaly[date] + sunCenter[date]
}


// Equation for center of the Sun C from Chapter 25 of Meeus.
sunCenter[date] :=
{
   T = meeusT[date]
   M = sunMeanAnomaly[date]

   return ((1.914602 - 0.004817 T - 0.000014 T^2) degree) sin[M] + 
          ((0.019993 - 0.000101 T) degree) sin[2 M] +
          (0.000289 degree) sin[3 M]
}


// Returns the eccentricity of the earth's orbit for a given date.
// Equation 25.4 in Meeus.
earthEccentricity[date] :=
{
   T = meeusT[date]
   return 0.016708634 - 0.000042037 T - 0.0000001267 T^2
}


// Distance between center of Sun and Earth
sunDistance[date] :=
{
   eccentricity = earthEccentricity[date]
   trueAnomaly = sunTrueAnomaly[date]
   return 1.000001018 au ( 1 - eccentricity^2 ) / (1 + eccentricity cos[trueAnomaly])
}

// Calculates the apparent sun radius angle, corrected for distance, but not
// corrected for refraction.  It gives a true arcsin geometric interpretation.
sunRadiusAngle[date] := arcsin[sunradius / (sunradius + sunDistance[date])]

// This returns the number of Julian centuries since JDE 2451545 as a 
// dimensionless number.
// Equation 25.1 in Jean Meeus
meeusT[date] := (JDE[date] - 2451545 days) / juliancentury


// Mean obliquity of the ecliptic epsilon0 from Jean Meeus, eq. 22.3.  This 
// is a high accuracy version of the function that is valid for a period
// of 10000 years before or after J2000.0
// It is NOT corrected for nutation.
// In Meeus, this symbol is referred to as epsilon_0
meanObliquityOfEcliptic[date] :=
{
   T = meeusT[date]
   U = T/100

   return 23 degree + 26 arcmin + 21.448 arcsec +
      (-4680.93 U   - 1.55 U^2 + 1999.25 U^3 - 51.38 U^4 - 249.67 U^5 +
       -39.05 U^6 + 7.12 U^7 +   27.87 U^8 +  5.79 U^9 +   2.45 U^10) arcsec
}


// True obliquity of the ecliptic corrected for nutation.  This is called
// epsilon in Meeus.
trueObliquityOfEcliptic[date] :=
{
   epsilon0 = meanObliquityOfEcliptic[date]
   
   [deltapsi, deltaepsilon] = highAccuracyNutation[date]
   return epsilon0 + deltaepsilon
}


// Mean obliquity of the ecliptic epsilon0 from Jean Meeus, eq. 22.3.  This 
// is a high accuracy version of the function that is only valid for a period
// of 10000 years before or after J2000.0
// In Meeus, this symbol is referred to as epsilon_0
narrowValidityMeanObliquityOfEcliptic[date] :=
{
   T = meeusT[date]

   return 23 degree + 26 arcmin + 21.448 arcsec +
        (-46.8150 T - 0.00059 T^2 + 0.001813 T^3) arcsec
}


// Returns the apparent right ascension and declination of the sun for a
// given date.  It implements the corrected versions of equation 25.6
// and 25.7 in Meeus.
// Returns [ra, decl]
sunApparentRADecl[date] :=
{
   epsilon = trueObliquityOfEcliptic[date]
   
   epsilonprime = epsilon + 0.00256 degree cos[meeusOmega[date]]
   sunLong = sunApparentLongitude[date]

   ssl = sin[sunLong]
   ra = arctan[cos[epsilonprime] ssl, cos[sunLong]]
   decl = arcsin[sin[epsilonprime] ssl]
   return [ra, decl]
}



// Calculate mean Sidereal time theta_0 at Greenwich for any instant as an
// angle.
// Equation 12.4 in Meeus.
meanGreenwichSiderealAngle[date] :=
{
   T = meeusT[date]

   return ((280.46061837 + 360.98564736629 (JD[date]/day - 2451545) +
           0.000387933 T^2 - T^3/38710000) degree) mod circle
}


// Calculate apparent Sidereal time as an angle at Greenwich for any instant.
// From Chapter 12 of Meeus.
apparentGreenwichSiderealAngle[date] :=
{
   deltapsi = highAccuracyNutation[date]@0
   trueObliquity = trueObliquityOfEcliptic[date]
   correction = deltapsi cos[trueObliquity]
   return (meanGreenwichSiderealAngle[date] + correction) mod circle
}

// Calculate apparent local sidereal time as an angle.
apparentLocalSiderealAngle[date, longitude] :=
{
   angle = apparentGreenwichSiderealAngle[date]
   return angle - longitude
}


// Calculate the hour angle H of a body with right ascension ra.
// See chapter 13 in Meeus.
hourAngle[date, longitude, ra] :=
{
   return apparentLocalSiderealAngle[date, longitude] - ra
}

// Returns the parallactic angle q for a given body from a point [lat, long]
// on the earth of a body at right ascension ra
// and declination decl.
// See equation 14.1 in Meeus.
parallacticAngle[date, lat, long, ra, decl] :=
{
   H = hourAngle[date, long, ra]
   return arctan[sin[H], tan[lat] cos[decl] - sin[decl] cos[H]]
}

// Calculate the approximate date of the equinoxes and solstices
// (to medium accuracy.)  The value should be correct within a minute;
// see Table 27.D in Meeus.
// This is from chapter 27 of Meeus.

// This function is called by the equinox and solstice methods below.
equinoxDate[JDE0] :=
{
   T = (JDE0 - 2451545.0) / 36525
   W = (35999.373 T - 2.47) degrees
   deltaLambda = 1 + 0.0334 cos[W] + 0.0007 cos[2 W]

   // These values are from Table 27.C
   S = 485 cos[(324.96 +   1934.136 T) degrees] +
       203 cos[(337.23 +  32964.467 T) degrees] +
       199 cos[(342.08 +     20.186 T) degrees] +
       182 cos[( 27.85 + 445267.112 T) degrees] +
       156 cos[( 73.14 +  45036.886 T) degrees] +
       136 cos[(171.52 +  22518.443 T) degrees] +
        77 cos[(222.54 +  65928.934 T) degrees] +
        74 cos[(296.72 +   3034.906 T) degrees] +
        70 cos[(243.58 +   9037.513 T) degrees] +
        58 cos[(119.81 +  33718.147 T) degrees] +
        52 cos[(297.17 +    150.678 T) degrees] +
        50 cos[( 21.02 +   2281.226 T) degrees] +
        45 cos[(247.54 +  29929.562 T) degrees] +
        44 cos[(325.15 +  31555.956 T) degrees] +
        29 cos[( 60.93 +   4443.417 T) degrees] +
        18 cos[(155.12 +  67555.328 T) degrees] +
        17 cos[(288.79 +   4562.452 T) degrees] +
        16 cos[(198.04 +  62894.029 T) degrees] +
        14 cos[(199.76 +  31436.921 T) degrees] +
        12 cos[( 95.39 +  14577.848 T) degrees] +
        12 cos[(287.11 +  31931.756 T) degrees] +
        12 cos[(320.81 +  34777.259 T) degrees] +
         9 cos[(227.73 +   1222.114 T) degrees] +
         8 cos[( 15.45 +  16859.074 T) degrees]

   ss = JDE0 + 0.00001 S/deltaLambda
   return JDE[ss]
}
   

// Spring Equinox for the years +1000 to +3000, Table 27.B in Meeus.
// The year passed in must be an integer!
springEquinox[year] :=
{
   Y = (year - 2000)/1000
   JDE0 = 2451623.80984 + 365242.37404 Y + 0.05169 Y^2 - 0.00411 Y^3 -
           0.00057 Y^4
   return equinoxDate[JDE0]
}

// Summer Solstice for the years +1000 to +3000, Table 27.B in Meeus.
// The year passed in must be an integer!
summerSolstice[year] :=
{
   Y = (year - 2000)/1000
   JDE0 = 2451716.56767 + 365241.62603 Y + 0.00325 Y^2 + 0.00888 Y^3 -
           0.00030 Y^4
   return equinoxDate[JDE0]
}

// Autumn Equinox for the years +1000 to +3000, Table 27.B in Meeus.
// The year passed in must be an integer!
autumnEquinox[year] :=
{
   Y = (year - 2000)/1000
   JDE0 = 2451810.21715 + 365242.01767 Y - 0.11575 Y^2 + 0.00337 Y^3 +
           0.00078 Y^4
   return equinoxDate[JDE0]
}

// Winter Solstice for the years +1000 to +3000, Table 27.B in Meeus.
// The year passed in must be an integer!
winterSolstice[year] :=
{
   Y = (year - 2000)/1000
   JDE0 = 2451900.05952 + 365242.74049 Y - 0.06223 Y^2 - 0.00823 Y^3 +
           0.00032 Y^4
   return equinoxDate[JDE0]
}


// Calculate Nutation in longitude (delta psi) and
// obliquity (delta epsilon) to low accuracy.  
// Returns [deltapsi, deltaepsilon]
// From chapter 22 in Meeus.
lowAccuracyNutation[date] :=
{
   T = meeusT[date]
   omega = (125.04452 - 1934.136261 T) degree

   // Mean longitude of the sun
   L = (280.4665 + 36000.7698 T) degree

   // Mean longitude of the moon
   Lmoon = (218.3165 + 481267.8813 T) degree

   deltapsi = (-17.20 sin[omega] - 1.32 sin[2 L] - 0.23 sin[2 Lmoon] +
               0.21 sin[2 omega] ) arcsec

   deltaepsilon = (9.20 cos[omega] + 0.57 cos[2 L] + 0.10 cos[2 Lmoon] -
                   0.09 cos[2 omega] ) arcsec

   return [deltapsi, deltaepsilon]
}


// Calculate Nutation in longitude (delta psi) and
// obliquity (delta epsilon) to high
// Returns [deltapsi, deltaepsilon]
// From chapter 22 in Meeus.
highAccuracyNutation[date] :=
{
   T = meeusT[date]
//   println["T: $T"]

   // Mean elongation of the moon from the sun:
   D = (297.85036 + 445267.111480 T - 0.0019142 T^2 + T^3/189474) degree 
//   println["D: " + (D->"degree")]

   // Mean anomaly of the Sun:
   M = (357.52772 + 35999.050340 T - 0.0001603 T^2 - T^3/300000) degree
//   println["M: " + (M->"degree")]

   // Mean anomaly of the Moon:
   MP = (134.96298 + 477198.867398 T + 0.0086972 T^2 + T^3/56250) degree
//   println["MP: " + (MP->"degree")]


   // Moon's argument of Latitude:
   F = (93.27191 + 483202.017538 T - 0.0036825 T^2 + T^3/327270) degree
//   println["F: " + (F->"degree")]


   // Longitude of the ascending node of the moon's mean orbit on the ecliptic
   // measured from the mean equinox of the date.
   Omega = (125.04452 - 1934.136261 T + 0.0020708 T^2 + T^3/450000) degree
//   println["Omega: " + (Omega->"degree")]

   // These lines generated by iau1980.frink and pasted in here.
   deltapsi = 0.0001 arcsec ( + (-171996 + -174.2 T) sin[ + Omega] + (-13187 + -1.6 T) sin[ -2 D + 2 F + 2 Omega] + (-2274 + -0.2 T) sin[ + 2 F + 2 Omega] + (2062 + 0.2 T) sin[ + 2 Omega] + (1426 + -3.4 T) sin[ + M] + (712 + 0.1 T) sin[ + MP] + (-517 + 1.2 T) sin[ -2 D + M + 2 F + 2 Omega] + (-386 + -0.4 T) sin[ + 2 F + Omega] + (-301) sin[ + MP + 2 F + 2 Omega] + (217 + -0.5 T) sin[ -2 D -1 M + 2 F + 2 Omega] + (-158) sin[ -2 D + MP] + (129 + 0.1 T) sin[ -2 D + 2 F + Omega] + (123) sin[ -1 MP + 2 F + 2 Omega] + (63) sin[ + 2 D] + (63 + 0.1 T) sin[ + MP + Omega] + (-59) sin[ + 2 D -1 MP + 2 F + 2 Omega] + (-58 + -0.1 T) sin[ -1 MP + Omega] + (-51) sin[ + MP + 2 F + Omega] + (48) sin[ -2 D + 2 MP] + (46) sin[ -2 MP + 2 F + Omega] + (-38) sin[ + 2 D + 2 F + 2 Omega] + (-31) sin[ + 2 MP + 2 F + 2 Omega] + (29) sin[ + 2 MP] + (29) sin[ -2 D + MP + 2 F + 2 Omega] + (26) sin[ + 2 F] + (-22) sin[ -2 D + 2 F] + (21) sin[ -1 MP + 2 F + Omega] + (17 + -0.1 T) sin[ + 2 M] + (16) sin[ + 2 D -1 MP + Omega] + (-16 + 0.1 T) sin[ -2 D + 2 M + 2 F + 2 Omega] + (-15) sin[ + M + Omega] + (-13) sin[ -2 D + MP + Omega] + (-12) sin[ -1 M + Omega] + (11) sin[ + 2 MP -2 F] + (-10) sin[ + 2 D -1 MP + 2 F + Omega] + (-8) sin[ + 2 D + MP + 2 F + 2 Omega] + (7) sin[ + M + 2 F + 2 Omega] + (-7) sin[ -2 D + M + MP] + (-7) sin[ -1 M + 2 F + 2 Omega] + (-8) sin[ + 2 D + 2 F + Omega] + (6) sin[ + 2 D + MP] + (6) sin[ -2 D + 2 MP + 2 F + 2 Omega] + (6) sin[ -2 D + MP + 2 F + Omega] + (-6) sin[ + 2 D -2 MP + Omega] + (-6) sin[ + 2 D + Omega] + (5) sin[ -1 M + MP] + (-5) sin[ -2 D -1 M + 2 F + Omega] + (-5) sin[ -2 D + Omega] + (-5) sin[ + 2 MP + 2 F + Omega] + (4) sin[ -2 D + 2 MP + Omega] + (4) sin[ -2 D + M + 2 F + Omega] + (4) sin[ + MP -2 F] + (-4) sin[ -1 D + MP] + (-4) sin[ -2 D + M] + (-4) sin[ + D] + (3) sin[ + MP + 2 F] + (-3) sin[ -2 MP + 2 F + 2 Omega] + (-3) sin[ -1 D -1 M + MP] + (-3) sin[ + M + MP] + (-3) sin[ -1 M + MP + 2 F + 2 Omega] + (-3) sin[ + 2 D -1 M -1 MP + 2 F + 2 Omega] + (-3) sin[ + 3 MP + 2 F + 2 Omega] + (-3) sin[ + 2 D -1 M + 2 F + 2 Omega])

   deltaepsilon = 0.0001 arcsec ( + (92025 + 8.9 T) cos[ + Omega] + (5736 + -3.1 T) cos[ -2 D + 2 F + 2 Omega] + (977 + -0.5 T) cos[ + 2 F + 2 Omega] + (-895 + 0.5 T) cos[ + 2 Omega] + (54 + -0.1 T) cos[ + M] + (-7) cos[ + MP] + (224 + -0.6 T) cos[ -2 D + M + 2 F + 2 Omega] + (200) cos[ + 2 F + Omega] + (129 + -0.1 T) cos[ + MP + 2 F + 2 Omega] + (-95 + 0.3 T) cos[ -2 D -1 M + 2 F + 2 Omega] + (-70) cos[ -2 D + 2 F + Omega] + (-53) cos[ -1 MP + 2 F + 2 Omega] + (-33) cos[ + MP + Omega] + (26) cos[ + 2 D -1 MP + 2 F + 2 Omega] + (32) cos[ -1 MP + Omega] + (27) cos[ + MP + 2 F + Omega] + (-24) cos[ -2 MP + 2 F + Omega] + (16) cos[ + 2 D + 2 F + 2 Omega] + (13) cos[ + 2 MP + 2 F + 2 Omega] + (-12) cos[ -2 D + MP + 2 F + 2 Omega] + (-10) cos[ -1 MP + 2 F + Omega] + (-8) cos[ + 2 D -1 MP + Omega] + (7) cos[ -2 D + 2 M + 2 F + 2 Omega] + (9) cos[ + M + Omega] + (7) cos[ -2 D + MP + Omega] + (6) cos[ -1 M + Omega] + (5) cos[ + 2 D -1 MP + 2 F + Omega] + (3) cos[ + 2 D + MP + 2 F + 2 Omega] + (-3) cos[ + M + 2 F + 2 Omega] + (3) cos[ -1 M + 2 F + 2 Omega] + (3) cos[ + 2 D + 2 F + Omega] + (-3) cos[ -2 D + 2 MP + 2 F + 2 Omega] + (-3) cos[ -2 D + MP + 2 F + Omega] + (3) cos[ + 2 D -2 MP + Omega] + (3) cos[ + 2 D + Omega] + (3) cos[ -2 D -1 M + 2 F + Omega] + (3) cos[ -2 D + Omega] + (3) cos[ + 2 MP + 2 F + Omega])

   return[deltapsi, deltaepsilon]
}


// Calculate the right ascension and declination of an object given ecliptical
// coordinates.  This is an implementation of eq. 13.3 and 13.4 in Meeus.
//
// arguments:
//  [lambda, beta, epsilon]
//
// returns:
//  [ra, decl]
eclipticalToRADecl[lambda, beta, epsilon] :=
{
   alpha = arctan[sin[lambda] cos[epsilon] - tan[beta] sin[epsilon], cos[lambda]]

   delta = arcsin[sin[beta] cos[epsilon] + cos[beta] sin[epsilon] sin[lambda]]

   return [alpha, delta]
}


// Calculate the azimuth and altitude of an object.
// Arguments:
//  [date, objectRA, objectDecl, lat, long]
//
// date: time of observation
// objectRA:    Right Ascension of object (can be angle or time)
// objectDecl:  Declination of object
// lat:         Latitude of observer (North is positive)
// long:        Longitude of observer (West is positive)
//
// Returns:
//    [azimuth, altitude] as angles.  Azimuth is calculated as angle west from
//                                    south.  To get compass bearing, use
//                                    (azimuth + 180 degrees) mod circle
// 
// Implements equations 13.5 and 13.6 from Meeus.
// NOTE: This is not corrected for refraction nor parallax!  These are
// geocentric coordinates, that is, relative to the center of the earth.
// Due to parallax, the altitude figure may be significantly in error for
// nearby bodies like the moon.  To get the corrected altitudes, use the
// refractedAzimuthAltitude function below.
airlessAzimuthAltitude[date, objectRA, objectDecl, lat, long] :=
{
   // We want all of this in angles... correct if necessary.
   if (objectRA conforms hour)
      objectRA = objectRA (circle/day)

   H = hourAngle[date, long, objectRA]

   azimuth = arctan[sin[H], cos[H] sin[lat] - tan[objectDecl] cos[lat]]
   altitude = arcsin[sin[lat] sin[objectDecl] + cos[lat] cos[objectDecl] cos[H]]

   return [azimuth, altitude]
}


// Calculate the apparent azimuth and altitude of an object with atmospheric
// refraction and parallax corrections.
// Arguments:
//  [date, objectRA, objectDecl, lat, long, distance, temp, pressure]
//
// date: time of observation
// objectRA:    Right Ascension of object (can be angle or time)
// objectDecl:  Declination of object
// lat:         Latitude of observer (North is positive)
// long:        Longitude of observer (West is positive)
// distance:    Distance to object (undef means don't correct for parallax)
// temp:        Temperature of atmosphere
// pressure:    Atmospheric pressure
//
// Returns:
//    [azimuth, altitude] as angles.  Azimuth is calculated as angle west from
//                                    south.  To get compass bearing, use
//                                    (azimuth + 180 degrees) mod circle
// 
// Implements equations 13.5 and 13.6 from Meeus.
//
refractedAzimuthAltitude[date, objectRA, objectDecl, lat, long, distance=undef, temp=283K, pressure=1010 millibars] :=
{
   [azimuth, altitude] = airlessAzimuthAltitude[date, objectRA, objectDecl, lat, long]

   // Correct for parallax is distance is defined.
   if distance != undef
      altitude = altitude - parallaxAngleAlt[distance, altitude]

//   println["Parallax angle is " + (parallaxAngleAlt[distance, altitude] -> "degrees")]

   corrAltitude = altitude + refractionAngle[altitude, temp, pressure]
   return[azimuth, corrAltitude]
}


// Calculate angle of refraction given the altitude in airless coordinates.
// This is equation 16.4 in Meeus, although the equation in the book
// is explained ridiculously poorly, and I had to dig through old 
// Sky & Telescope program listings to figure out how to apply the equation.
// This is the angle to be *added* to the airless altitude to get the
// refracted altitude.
refractionAngle[altitude, temp = 283 K, pressure = 1010 millibars] :=
{
   // h5 is altitude in degrees
   h5 = altitude/degree  
   v5 = (h5 + 10.3/(h5+5.11)) degree
   pressCorr = pressure/(1010 millibars) (283 K)/temp
   R = pressCorr 1.02 arcmin / tan[v5]

   return R
}


// Returns the apparent azimuth and altitude for the sun, adjusted for
// nutation, but not adjusted for refraction.
// 
// Returns:
//   [azimuth, altitude]  Azimuth is calculated as angle west from
//                        south.  To get compass bearing, use
//                        (azimuth + 180 degrees) mod circle
airlessSunAzimuthAltitude[date, lat, long] :=
{
   [sunRA, sunDecl] = sunApparentRADecl[date]

   return airlessAzimuthAltitude[date, sunRA, sunDecl, lat, long]
}


// Returns the apparent azimuth and altitude for the sun, adjusted for
// nutation, atmospheric refraction, and parallax.
// 
// Returns:
//   [azimuth, altitude] as angles.  Azimuth is calculated as angle west from
//                                   south.  To get compass bearing, use
//                                   (azimuth + 180 degrees) mod circle
refractedSunAzimuthAltitude[date, lat, long, temp = 283K, pressure = 1010 millibars] :=
{
   [sunRA, sunDecl] = sunApparentRADecl[date]

   return refractedAzimuthAltitude[date, sunRA, sunDecl, lat, long, sunDistance[date], temp, pressure]
}


// Secant method solver to find when the sun is at a given altitude to
// an observer at the specified latitude and longitude on the Earth.
// It takes a first guess at date1 and adds an hour to that to find
// its two starting points.  Thus, to find sunrise, give it a guess
// of sometime around 6 AM local time and a desired altitude of 0 degrees,
// or about -16 arcmin to account for the time that the sun's upper limb
// is just crosses the horizon.  To find sunset, give it a guess
// around 6 PM local time.
// 
// This currently doesn't handle the case where the sun doesn't cross through
// the specified azimuth on that date and may give unpredictable results in
// this case or in the case when the initial guess is far off.
sunSecantAltitude[date1, lat, long, desiredAltitude, temperature = 283 K,
                  pressure=1010 millibars ] :=
{
   date2 = date1 + 1 hour
   [azimuth1, altitude1] = refractedSunAzimuthAltitude[date1, lat, long, temperature, pressure]
   [azimuth2, altitude2] = refractedSunAzimuthAltitude[date2, lat, long, temperature, pressure]

   altitude1 = altitude1 - desiredAltitude
   altitude2 = altitude2 - desiredAltitude

//   println["Altitude 1: $altitude1"]
//   println["Altitude 2: $altitude2"]

   while (true)
   {
      correction = (altitude1 * (date1 - date2)) / (altitude1-altitude2)

      date = date1 - correction
//      println[date -> ### HH:mm:ss.SSS ###]
      if abs[correction] < 1 second
         return date 

      date2 = date1
      date1 = date
      altitude2 = altitude1

      [azimuth1, altitude1] = refractedSunAzimuthAltitude[date, lat, long, temperature, pressure]
      altitude1 = altitude1 - desiredAltitude
   }
}

// Secant method solver to find when the sun is at a given altitude to
// an observer at the specified latitude and longitude on the Earth.
// This version does *not* correct for refraction.  To correct for refraction,
// use the function sunSecantAltitude[] instead.
// It takes a first guess at date1 and adds an hour to that to find
// its two starting points.  Thus, to find sunrise, give it a guess
// of sometime around 6 AM local time and a desired altitude of 0 degrees,
// or about -16 arcmin to account for the time that the sun's upper limb
// is just crosses the horizon.  To find sunset, give it a guess
// around 6 PM local time.
// 
// This currently doesn't handle the case where the sun doesn't cross through
// the specified azimuth on that date and may give unpredictable results in
// this case or in the case when the initial guess is far off.
sunSecantAirlessAltitude[date1, lat, long, desiredAltitude ] :=
{
   date2 = date1 + 1 hour
   [azimuth1, altitude1] = airlessSunAzimuthAltitude[date1, lat, long]
   [azimuth2, altitude2] = airlessSunAzimuthAltitude[date2, lat, long]

   altitude1 = altitude1 - desiredAltitude
   altitude2 = altitude2 - desiredAltitude

//   println["Altitude 1: $altitude1"]
//   println["Altitude 2: $altitude2"]

   while (true)
   {
      correction = (altitude1 * (date1 - date2)) / (altitude1-altitude2)

      date = date1 - correction
//      println[date -> ### HH:mm:ss.SSS ###]
      if abs[correction] < 1 second
         return date 

      date2 = date1
      date1 = date
      altitude2 = altitude1

      [azimuth1, altitude1] = airlessSunAzimuthAltitude[date, lat, long]
      altitude1 = altitude1 - desiredAltitude
   }
}


// Secant method solver to find when the sun is at a given azimuth to
// an observer at the specified latitude and longitude on the Earth.
// It takes a first guess at date1 and adds an hour to that to find
// its two starting points.  Thus, to find sunrise, give it a guess
// of sometime around 6 AM local time and a desired altitude of 0 degrees,
// or about -16 arcmin to account for the time that the sun's upper limb
// is just crosses the horizon.  To find sunset, give it a guess
// around 6 PM local time.
//
// This function corrects for refraction.
// 
// This currently doesn't handle the case where the sun doesn't pass through
// the desired azimuth on that date and may give unpredictable results in
// this case or the case where the initial guess is far off.
//
// desiredAzimuth should be specified in the coordinate system specified by
// Meeus, which is angle west of south.  To convert from normal true compass
// bearings, use   (trueAzimuth + 180 degrees) mod circle
sunSecantAzimuth[date1, lat, long, desiredAzimuth, temperature = 283 K, 
                pressure=1010 millibars ] :=
{
   date2 = date1 + 5 minutes
   [azimuth1, altitude1] = refractedSunAzimuthAltitude[date1, lat, long, temperature, pressure]
   [azimuth2, altitude2] = refractedSunAzimuthAltitude[date2, lat, long, temperature, pressure]

   azimuth1 = azimuth1 - desiredAzimuth
   azimuth2 = azimuth2 - desiredAzimuth

   while (true)
   {
      // Don't overcorrect... if we're correcting more than half a phase
      // from the initial guess, then go back a phase.
      if azimuth1 > 180 degrees
         azimuth1 = azimuth1 - circle
      if azimuth1 < -180 degrees
         azimuth1 = azimuth1 + circle
      if azimuth2 > 180 degrees
         azimuth2 = azimuth2 - circle
      if azimuth2 < -180 degrees
         azimuth2 = azimuth2 + circle

      // did we wrap around the circle?
      if abs[azimuth1 - azimuth2] > 180 degrees
         if (azimuth1 < azimuth2)
            azimuth2 = azimuth2 - circle
         else
            azimuth1 = azimuth1 - circle

//      println["Azimuth 1: $azimuth1"]
//      println["Azimuth 2: $azimuth2"]

      correction = (azimuth1 * (date1 - date2)) / (azimuth1-azimuth2)

//      println["Correction: $correction"]

      date = date1 - correction
//      println[date]
      if abs[correction] < 1 second
         return date 

      date2 = date1
      date1 = date
      azimuth2 = azimuth1

      [azimuth1, altitude1] = refractedSunAzimuthAltitude[date, lat, long, temperature, pressure]
      azimuth1 = azimuth1 - desiredAzimuth
   }
}


// Secant method solver to find when the moon is at a given altitude to
// an observer at the specified latitude and longitude on the Earth.
// It takes a first guess at date1 and adds an hour to that to find
// its two starting points.
// 
// This currently doesn't handle the case where the moon doesn't cross through
// the specified altitude on that date, or if initial guess is very wrong,
// and may give unpredictable results.
moonSecantAltitude[date1, lat, long, desiredAltitude, temperature = 283 K, 
                   pressure=1010 millibars ] :=
{
   date2 = date1 + 1 hour
   [azimuth1, altitude1] = refractedMoonAzimuthAltitude[date1, lat, long, temperature, pressure]
   [azimuth2, altitude2] = refractedMoonAzimuthAltitude[date2, lat, long, temperature, pressure]

   altitude1 = altitude1 - desiredAltitude
   altitude2 = altitude2 - desiredAltitude

//   println["Altitude 1: $altitude1"]
//   println["Altitude 2: $altitude2"]

   while (true)
   {
      correction = (altitude1 * (date1 - date2)) / (altitude1-altitude2)

      if (correction > 24 hours)
         correction = 24 hours
      if (correction < -24 hours)
         correction = -24 hours

      date = date1 - correction
//      println[date -> ### HH:mm:ss.SSS ###]
//      println[date]
      if abs[correction] < 1 second
         return date 

      date2 = date1
      date1 = date
      altitude2 = altitude1

      [azimuth1, altitude1] = refractedMoonAzimuthAltitude[date, lat, long, temperature, pressure]
      altitude1 = altitude1 - desiredAltitude
   }
}


// Calculate the approximate mean time of various phases of the moon.
// This will give the approximate time that the specified phase
// will occur near the given date.   This is only accurate to within
// some number of hours.
//
// The phaseAdjust can be only one of 4 values:
//  0  for New Moon
// 1/4 for First Quarter
// 1/2 for Full Moon
// 3/4 for Last Quarter
//
// All other values give meaningless results!
// 
// This guess serves to supply a good starting guess to moonPhase
// which is much higher accuracy.
moonApproximatePhase[date, phaseAdjust] :=
{
   // This was found experimentally by Alan Eliasen to give a better initial
   // estimate that centers around the closest phase of the moon, with a
   // slight bias of about 4-5 days toward selecting the "next" event.
   d1 = date - phaseAdjust lunarmonth
   
   // Get approximate k, Meeus eq. 49.2
   k = round[(JDE[d1] - JDE[#2000-01-01#])/year * 12.3685] + phaseAdjust

   T = k / 1236.85

   approxDate = JDE[2451550.09766 + 29.530588861 k + 0.00015437 T^2 -
                                  0.000000150 T^3 + 0.00000000073 T^4]

   return approxDate
}


// This finds the excess of the apparent geocentric latitude of the moon
// over the apparent geocentric longitude of the sun.  It is used to find
// the phases of the moon to medium accuracy.  The accuracy of this is limited
// to the somewhat low accuracy calculation of sunApparentLongitude.
// This could be improved by using the high-accuracy VSOP87 theory
// implemented in planets.frink and Meeus p. 166
// See Meeus chapter 49.
moonExcessLongitude[date] := (moonApparentLongitude[date] - sunApparentLongitude[date]) mod circle


// Find high accuracy phases of the moon.
// The phaseAdjust can be only one of 4 values:
//  0  for New Moon
// 1/4 for First Quarter
// 1/2 for Full Moon
// 3/4 for Last Quarter
//
// All other values give meaningless results!
// 
// For simplicity, you should probably use one of the convenience functions:
// newMoon[date], fullMoon[date], etc.
//
// This function uses a secant method successive approximation to find the
// moment of the desired phase to high accuracy using the full-precision
// moon calculations.  The convergence limit is set at one second or less.
moonPhase[date, phaseAdjust] :=
{
   desiredAngle = phaseAdjust * circle
   date1 = moonApproximatePhase[date, phaseAdjust]
//   println[date1]
   date2 = date1 + 5 min
   
   excess1 = moonExcessLongitude[date1]
   excess2 = moonExcessLongitude[date2]

//   println["$excess1\t$excess2"]

//   println["Phase is " + excess1 / circle]

   // Don't overcorrect... if we're correcting more than half a phase forward
   // from the initial guess, then go back a phase.
   // This generally corrects the problem around the new moon where the angle
   // jumps from close to 360 degrees back to 0 degrees, and may prevent other
   // as-yet-undetected weird jumps.
   if (excess1 - desiredAngle > 180 degrees)
         excess1 = excess1 - circle

   if (excess2 - desiredAngle > 180 degrees)
        excess2 = excess2 - circle
   
   // did we wrap around the circle?
   if abs[excess1 - excess2] > 180 degrees
      if (excess1 < excess2)
         excess2 = excess2 - circle
      else
         excess1 = excess1 - circle

//   println["$excess1\t$excess2"]
   excess1 = excess1 - desiredAngle
   excess2 = excess2 - desiredAngle
//   println["$excess1\t$excess2"]

   while (true)
   {
      correction = (excess1 * (date1 - date2)) / (excess1-excess2)

      date = date1 - correction
//      println[date]
//      println["$excess1\t$excess2"]

      if abs[correction] < 1 second
         return date

      date2 = date1
      date1 = date
      excess2 = excess1

      excess1 = moonExcessLongitude[date1]
      excess1 = excess1 - desiredAngle

      // did we wrap around the circle?
      if abs[excess1 - excess2] > 180 degrees
         if (excess1 < excess2)
            excess2 = excess2 - circle
         else
            excess1 = excess1 - circle
   }

   return date
}


// Finds the date of the New Moon close to the specifed date.
newMoon[date] := moonPhase[date, 0]

// Finds the date of the First Quarter moon close to the specifed date.
firstQuarterMoon[date] := moonPhase[date, 1/4]

// Finds the date of the Full Moon close to the specifed date.
fullMoon[date] := moonPhase[date, 1/2]

// Finds the date of the Last Quarter moon close to the specifed date.
lastQuarterMoon[date] := moonPhase[date, 3/4]


// Secant method solver to find when the moon is at a given azimuth to
// an observer at the specified latitude and longitude on the Earth.
// 
// This currently doesn't handle the case where the moon doesn't pass through
// a certain azimuth on that date and may give unpredictable results in that
// case or the case where the initial guess is far off.
//
// desiredAzimuth should be specified in the coordinate system specified by
// Meeus, which is angle west of south.  To convert from normal true compass
// bearings, use   (trueAzimuth + 180 degrees) mod circle
moonSecantAzimuth[date1, lat, long, desiredAzimuth, temperature = 283 K,
                  pressure=1010 millibars ] :=
{
   date2 = date1 + 5 minutes
   [azimuth1, altitude1] = refractedMoonAzimuthAltitude[date1, lat, long, temperature, pressure]
   [azimuth2, altitude2] = refractedMoonAzimuthAltitude[date2, lat, long, temperature, pressure]

   azimuth1 = azimuth1 - desiredAzimuth
   azimuth2 = azimuth2 - desiredAzimuth

//   println["Azimuth 1: " + (azimuth1->"degrees")]
//   println["Azimuth 2: " + (azimuth2->"degrees")]   

   while (true)
   {
      // Don't overcorrect... if we're correcting more than half a phase
      // from the initial guess, then go back a phase.
      if azimuth1 > 180 degrees
         azimuth1 = azimuth1 - circle
      if azimuth1 < -180 degrees
         azimuth1 = azimuth1 + circle
      if azimuth2 > 180 degrees
         azimuth2 = azimuth2 - circle
      if azimuth2 < -180 degrees
         azimuth2 = azimuth2 + circle

      // did we wrap around the circle?
      if abs[azimuth1 - azimuth2] > 180 degrees
         if (azimuth1 < azimuth2)
            azimuth2 = azimuth2 - circle
         else
            azimuth1 = azimuth1 - circle
         
//      println["Diff is " + (azimuth1-azimuth2 -> "degrees")]
      correction = (azimuth1 * (date1 - date2)) / (azimuth1-azimuth2)

//      println["Correction: $correction"]

      date = date1 - correction
//      println[date]
      if abs[correction] < 1 second
         return date 

      date2 = date1
      date1 = date
      azimuth2 = azimuth1

      [azimuth1, altitude1] = refractedMoonAzimuthAltitude[date, lat, long, temperature, pressure]
      azimuth1 = azimuth1 - desiredAzimuth
   }
}


// Calculate the time when the upper limb of the sun just crosses the
// horizon.
sunrise[date, lat, long, temp = 283 K, pressure=1010 millibars ] := sunSecantAltitude[approxMidnight[date, long] + 5.5 hours,
                                      lat, long, -16 arcmin, temp, pressure]

// Calculate the time when the upper limb of the sun just crosses the
// horizon.
sunset[date, lat, long, temp = 283 K, pressure=1010 millibars ] := sunSecantAltitude[approxMidnight[date, long] + 17.5 hours,
                                      lat, long, -16 arcmin, temp, pressure]

civilTwilightBegin[date, lat, long] := sunSecantAirlessAltitude[approxMidnight[date, long] + 5 hour, lat, long, -6 degree]

civilTwilightEnd[date, lat, long] := sunSecantAirlessAltitude[approxMidnight[date, long] + 18 hour, lat, long, -6 degree]

nauticalTwilightBegin[date, lat, long] := sunSecantAirlessAltitude[approxMidnight[date, long] + 4.7 hour, lat, long, -12 degree]

nauticalTwilightEnd[date, lat, long] := sunSecantAirlessAltitude[approxMidnight[date, long] + 18.3 hour, lat, long, -12 degree]

astronomicalTwilightBegin[date, lat, long] := sunSecantAirlessAltitude[approxMidnight[date, long] + 4.3 hour, lat, long, -18 degree]

astronomicalTwilightEnd[date, lat, long] := sunSecantAirlessAltitude[approxMidnight[date, long] + 18.7 hour, lat, long, -18 degree]

// Get a time object representing the approximate midnight that begins the 
// the specified date at the specified longitude.  It is highly recommended
// that you use a date significantly after midnight to eliminate issues with
// the local meridian and daylight savings time. 
approxMidnight[date, longitude] := 
{
   d0 = #2003-01-01 UTC# + (longitude / (circle/day))
   r = d0 + floor[(date - d0) / day] day
//   println[r]
   return r
}       

// Besselian Date, as defined by http://maia.usno.navy.mil/
BesselianDate[date] := 2000 + (MJD[date]/day - 51544.03) / 365.2422

//
// Moon position calculations
//

// Moon mean longitude L', referred to the mean equinox of date, including the 
// constant term of the effect of the light-time.  This is equation 47.1
// in Meeus.
moonMeanLongitude[date] :=
{
   T = meeusT[date]
   return ((218.3164477 + 481267.88123421 T - 0.0015786 T^2 + T^3/538_841 -
          T^4/65_194_000) degree) mod circle
}

// Moon mean elongation as an angle D.  This is equation 47.2 in Meeus.
moonMeanElongation[date] :=
{
   T = meeusT[date]
   return ((297.8501921 + 445267.1114034 T - 0.0018819 T^2 + T^3/545_868 -
           T^4/113_065_000) degree) mod circle
}

// Calculate the mean anomaly of the sun, M.  This is equation 47.3 in Meeus.
// Note that these numbers are very slightly different than the sun anomaly
// function given by equation 25.3 and called sunMeanAnomaly above.
moonCalcSunMeanAnomaly[date] :=
{
   T = meeusT[date]
   return ((357.5291092 + 35999.0502909 T - 0.0001536 T^2 + 
           T^3/24_490_000) degree) mod circle
}

// Moon mean anomaly M'.  This is equation 47.4 in Meeus.
moonMeanAnomaly[date] :=
{
   T = meeusT[date]
   return ((134.9633964 + 477198.8675055 T + 0.0087414 T^2 + T^3/69_699 -
           T^4 / 14_712_000) degree) mod circle
}

// Moon's argument of latitude F, the mean distance of the Moon from its
// ascending node.  This is equation 47.5 in Meeus.
moonArgumentOfLatitude[date] :=
{
   T = meeusT[date]
   return ((93.2720950 + 483202.0175233 T - 0.0036539 T^2 - T^3 / 3_526_000 +
           T^4 / 863_310_000) degree) mod circle
}

// Moon correction term A1 (listed after equation 47.5 in Meeus)
// This is due to the action of Venus.
moonA1[date] := 
{
   T = meeusT[date]
   return ((119.75 + 131.849 T) degree) mod circle
}

// Moon correction term A2 (listed after equation 47.5 in Meeus)
// This is due to the action of Jupiter.
moonA2[date] := 
{
   T = meeusT[date]
   return ((53.09 + 479264.290 T) degree) mod circle
}

// Moon correction term A3 (listed after equation 47.5 in Meeus)
moonA3[date] := 
{
   T = meeusT[date]
   return ((313.45 + 481266.484 T) degree) mod circle
}

// Earth eccentricity E for Moon calculations.  This is equation 47.6 in 
// Meeus.  This is not the eccentricity given by eq. 25.4
moonCalcEarthEccentricity[date] :=
{
   T = meeusT[date]
   return 1 - 0.002516 T - 0.0000074 T^2
}

// Moon sum of L from Meeus, chapter 47.  This is with the extra additive
// terms listed on p. 342.
moonSumL[date] := 
{
   D = moonMeanElongation[date]
   M = moonCalcSunMeanAnomaly[date]
   MP = moonMeanAnomaly[date]
   F = moonArgumentOfLatitude[date]
   E = moonCalcEarthEccentricity[date]
   LP = moonMeanLongitude[date]

   sumL = ( + (6288774) sin[ + MP] + (1274027) sin[ + 2 D - MP] + (658314) sin[ + 2 D] + (213618) sin[ + 2 MP] + (-185116 E) sin[ + M] + (-114332) sin[ + 2 F] + (58793) sin[ + 2 D -2 MP] + (57066 E) sin[ + 2 D - M - MP] + (53322) sin[ + 2 D + MP] + (45758 E) sin[ + 2 D - M] + (-40923 E) sin[ + M - MP] + (-34720) sin[ + D] + (-30383 E) sin[ + M + MP] + (15327) sin[ + 2 D -2 F] + (-12528) sin[ + MP + 2 F] + (10980) sin[ + MP -2 F] + (10675) sin[ + 4 D - MP] + (10034) sin[ + 3 MP] + (8548) sin[ + 4 D -2 MP] + (-7888 E) sin[ + 2 D + M - MP] + (-6766 E) sin[ + 2 D + M] + (-5163) sin[ + D - MP] + (4987 E) sin[ + D + M] + (4036 E) sin[ + 2 D - M + MP] + (3994) sin[ + 2 D + 2 MP] + (3861) sin[ + 4 D] + (3665) sin[ + 2 D -3 MP] + (-2689 E) sin[ + M -2 MP] + (-2602) sin[ + 2 D - MP + 2 F] + (2390 E) sin[ + 2 D - M -2 MP] + (-2348) sin[ + D + MP] + (2236 E^2) sin[ + 2 D -2 M] + (-2120 E) sin[ + M + 2 MP] + (-2069 E^2) sin[ + 2 M] + (2048 E^2) sin[ + 2 D -2 M - MP] + (-1773) sin[ + 2 D + MP -2 F] + (-1595) sin[ + 2 D + 2 F] + (1215 E) sin[ + 4 D - M - MP] + (-1110) sin[ + 2 MP + 2 F] + (-892) sin[ + 3 D - MP] + (-810 E) sin[ + 2 D + M + MP] + (759 E) sin[ + 4 D - M -2 MP] + (-713 E^2) sin[ + 2 M - MP] + (-700 E^2) sin[ + 2 D + 2 M - MP] + (691 E) sin[ + 2 D + M -2 MP] + (596 E) sin[ + 2 D - M -2 F] + (549) sin[ + 4 D + MP] + (537) sin[ + 4 MP] + (520 E) sin[ + 4 D - M] + (-487) sin[ + D -2 MP] + (-399 E) sin[ + 2 D + M -2 F] + (-381) sin[ + 2 MP -2 F] + (351 E) sin[ + D + M + MP] + (-340) sin[ + 3 D -2 MP] + (330) sin[ + 4 D -3 MP] + (327 E) sin[ + 2 D - M + 2 MP] + (-323 E^2) sin[ + 2 M + MP] + (299 E) sin[ + D + M - MP] + (294) sin[ + 2 D + 3 MP])

   sumL = sumL + 3958 sin[moonA1[date]] + 
            1962 sin[LP - F] +
            318 sin[moonA2[date]]

   return sumL
}

// Moon longitude calculation from section 47 of Meeus.  This is not corrected
// for Earth's nutation.  For that, use moonApparentLongitude[date]
moonLongitude[date] :=
{
   return moonMeanLongitude[date] + (moonSumL[date]/1000000 degree)
}

// Returns the moon's apparent longitude, corrected for Earth's
// nutation.  It is not corrected for refraction.
moonApparentLongitude[date] :=
{
   [deltaPsi, deltaEpsilon] = highAccuracyNutation[date]
   return moonLongitude[date] + deltaPsi
}

// Moon distance calculation Delta from section 47 of Meeus.
moonDistance[date] :=
{
   D = moonMeanElongation[date]
   M = moonCalcSunMeanAnomaly[date]
   MP = moonMeanAnomaly[date]
   F = moonArgumentOfLatitude[date]
   E = moonCalcEarthEccentricity[date]
   LP = moonMeanLongitude[date]

   sumR = ( + (-20905355) cos[ + MP] + (-3699111) cos[ + 2 D - MP] + (-2955968) cos[ + 2 D] + (-569925) cos[ + 2 MP] + (48888 E) cos[ + M] + (-3149) cos[ + 2 F] + (246158) cos[ + 2 D -2 MP] + (-152138 E) cos[ + 2 D - M - MP] + (-170733) cos[ + 2 D + MP] + (-204586 E) cos[ + 2 D - M] + (-129620 E) cos[ + M - MP] + (108743) cos[ + D] + (104755 E) cos[ + M + MP] + (10321) cos[ + 2 D -2 F] + (79661) cos[ + MP -2 F] + (-34782) cos[ + 4 D - MP] + (-23210) cos[ + 3 MP] + (-21636) cos[ + 4 D -2 MP] + (24208 E) cos[ + 2 D + M - MP] + (30824 E) cos[ + 2 D + M] + (-8379) cos[ + D - MP] + (-16675 E) cos[ + D + M] + (-12831 E) cos[ + 2 D - M + MP] + (-10445) cos[ + 2 D + 2 MP] + (-11650) cos[ + 4 D] + (14403) cos[ + 2 D -3 MP] + (-7003 E) cos[ + M -2 MP] + (10056 E) cos[ + 2 D - M -2 MP] + (6322) cos[ + D + MP] + (-9884 E^2) cos[ + 2 D -2 M] + (5751 E) cos[ + M + 2 MP] + (-4950 E^2) cos[ + 2 D -2 M - MP] + (4130) cos[ + 2 D + MP -2 F] + (-3958 E) cos[ + 4 D - M - MP] + (3258) cos[ + 3 D - MP] + (2616 E) cos[ + 2 D + M + MP] + (-1897 E) cos[ + 4 D - M -2 MP] + (-2117 E^2) cos[ + 2 M - MP] + (2354 E^2) cos[ + 2 D + 2 M - MP] + (-1423) cos[ + 4 D + MP] + (-1117) cos[ + 4 MP] + (-1571 E) cos[ + 4 D - M] + (-1739) cos[ + D -2 MP] + (-4421) cos[ + 2 MP -2 F] + (1165 E^2) cos[ + 2 M + MP] + (8752) cos[ + 2 D - MP -2 F])

   return (385000560 + sumR) meter
}

// Moon latitude calculation beta from section 47 of Meeus.
moonLatitude[date] :=
{
   D = moonMeanElongation[date]
   M = moonCalcSunMeanAnomaly[date]
   MP = moonMeanAnomaly[date]
   F = moonArgumentOfLatitude[date]
   E = moonCalcEarthEccentricity[date]
   LP = moonMeanLongitude[date]
   A1 = moonA1[date]

   sumB = ( + (5128122) sin[ + F] + (280602) sin[ + MP + F] + (277693) sin[ + MP - F] + (173237) sin[ + 2 D - F] + (55413) sin[ + 2 D - MP + F] + (46271) sin[ + 2 D - MP - F] + (32573) sin[ + 2 D + F] + (17198) sin[ + 2 MP + F] + (9266) sin[ + 2 D + MP - F] + (8822) sin[ + 2 MP - F] + (8216 E) sin[ + 2 D - M - F] + (4324) sin[ + 2 D -2 MP - F] + (4200) sin[ + 2 D + MP + F] + (-3359 E) sin[ + 2 D + M - F] + (2463 E) sin[ + 2 D - M - MP + F] + (2211 E) sin[ + 2 D - M + F] + (2065 E) sin[ + 2 D - M - MP - F] + (-1870 E) sin[ + M - MP - F] + (1828) sin[ + 4 D - MP - F] + (-1794 E) sin[ + M + F] + (-1749) sin[ + 3 F] + (-1565 E) sin[ + M - MP + F] + (-1491) sin[ + D + F] + (-1475 E) sin[ + M + MP + F] + (-1410 E) sin[ + M + MP - F] + (-1344 E) sin[ + M - F] + (-1335) sin[ + D - F] + (1107) sin[ + 3 MP + F] + (1021) sin[ + 4 D - F] + (833) sin[ + 4 D - MP + F] + (777) sin[ + MP -3 F] + (671) sin[ + 4 D -2 MP + F] + (607) sin[ + 2 D -3 F] + (596) sin[ + 2 D + 2 MP - F] + (491 E) sin[ + 2 D - M + MP - F] + (-451) sin[ + 2 D -2 MP + F] + (439) sin[ + 3 MP - F] + (422) sin[ + 2 D + 2 MP + F] + (421) sin[ + 2 D -3 MP - F] + (-366 E) sin[ + 2 D + M - MP + F] + (-351 E) sin[ + 2 D + M + F] + (331) sin[ + 4 D + F] + (315 E) sin[ + 2 D - M + MP + F] + (302 E^2) sin[ + 2 D -2 M - F] + (-283) sin[ + MP + 3 F] + (-229 E) sin[ + 2 D + M + MP - F] + (223 E) sin[ + D + M - F] + (223 E) sin[ + D + M + F] + (-220 E) sin[ + M -2 MP - F] + (-220 E) sin[ + 2 D + M - MP - F] + (-185) sin[ + D + MP + F] + (181 E) sin[ + 2 D - M -2 MP - F] + (-177 E) sin[ + M + 2 MP + F] + (176) sin[ + 4 D -2 MP - F] + (166 E) sin[ + 4 D - M - MP - F] + (-164) sin[ + D + MP - F] + (132) sin[ + 4 D + MP - F] + (-119) sin[ + D - MP - F] + (115 E) sin[ + 4 D - M - F] + (107 E^2) sin[ + 2 D -2 M + F])

   sumB = sumB - 2235 sin[LP] + 382 sin[moonA3[date]] + 175 sin[A1 - F] +
          175 sin[A1 + F] + 127 sin[LP - MP] - 115 sin[LP + MP]

   return (sumB / 1000000) degree
}


// Calculates the apparent Right ascension and declination of the moon
// at a given date.
// returns [ra, decl]
moonApparentRADecl[date] :=
{
   epsilon = trueObliquityOfEcliptic[date]

   moonLong = moonApparentLongitude[date]
   moonLat = moonLatitude[date]
   se = sin[epsilon]
   ce = cos[epsilon]
   sm = sin[moonLong]
   ra = arctan[sm ce - tan[moonLat] se,
               cos[moonLong]]
   decl = arcsin[sin[moonLat] ce + cos[moonLat] sin[epsilon] sm]
   return[ra,decl]
}

// Returns the apparent azimuth and altitude for the moon, adjusted for
// nutation, but not adjusted for refraction.
// 
// Returns:
//   [azimuth, altitude]  Azimuth is calculated as angle west from
//                        south.  To get compass bearing, use
//                        (azimuth + 180 degrees) mod circle
airlessMoonAzimuthAltitude[date, lat, long] :=
{
   [moonRA, moonDecl] = moonApparentRADecl[date]

   return airlessAzimuthAltitude[date, moonRA, moonDecl, lat, long]
}

// Returns the apparent azimuth and altitude for the moon, adjusted for
// nutation, atmospheric refraction, and parallax.
// 
// Returns:
//   [azimuth, altitude] as angles.  Azimuth is calculated as angle west from
//                                   south.  To get compass bearing, use
//                                   (azimuth + 180 degrees) mod circle
refractedMoonAzimuthAltitude[date, lat, long, temp = 283K, pressure=1010 millibars] :=
{
   [moonRA, moonDecl] = moonApparentRADecl[date]

   return refractedAzimuthAltitude[date, moonRA, moonDecl, lat, long, moonDistance[date], temp, pressure]
}

// Calculates the apparent moon radius angle, corrected for distance, but not
// corrected for refraction.  It gives a true arcsin geometric interpretation.
moonRadiusAngle[date] := arcsin[moonradius / (moonradius + moonDistance[date])]

// Calculates the geocentric elongation of the moon from the sun
// by Meeus equation 48.2 (second equation)
moonGeocentricElongationFromSun[date] := 
{
   arccos[cos[moonLatitude[date]] * cos[moonApparentLongitude[date] - sunTrueLongitude[date]]]
}

// Calculates the geocentric elongation of the moon from the sun
// by Meeus equation 48.2 (first equation)
moonGeocentricElongationFromSun2[date] := 
{
   [alpha0, delta0] = sunApparentRADecl[date]
   [alpha, delta] = moonApparentRADecl[date]
   
   return arccos[sin[delta0] sin[delta] +
                 cos[delta0] cos[delta] cos[alpha0 - alpha]]
}

// Phase angle of the moon as seen from earth, by equation 48.3 of Meeus
// This is constrained to be in the range 0-180 degrees as expected by the
// illuminated fraction calculations.
moonPhaseAngle[date] :=
{
   R = sunDistance[date]
   delta = moonDistance[date]
   phi = moonGeocentricElongationFromSun[date]

   return arctan[R sin[phi] / (delta - R cos[phi])] mod (180 degrees)
}

// Illuminated fraction of the moon as seen from earth.  This is found by
// equation 48.1 of Meeus.
moonIlluminatedFraction[date] := (1 + cos[moonPhaseAngle[date]]) / 2


// The position angle of the moon's bright limb, reckoned eastward from the
// North Point of the moon.  (That is, relative to the earth's North/South
// axis projected onto the moon's disc, *not* relative to the moon's axis.)
// This is equation 48.5 in Meeus.
moonPositionAngle[date] :=
{
   [alpha0, delta0] = sunApparentRADecl[date]
   [alpha, delta] = moonApparentRADecl[date]
   return arctan[cos[delta0] sin[alpha0 - alpha],
             sin[delta0] cos[delta] - cos[delta0] sin[delta] cos[alpha0-alpha]]
}


// The position angle of the moon's bright limb, reckoned "eastward" (?)
// from the zenith.  Note that this gives the direction *counterclockwise*
// from the zenith as seen from an observer standing on earth.  The "eastward"
// terminology is confusing.
// This is equation 48.5 in Meeus.
moonPositionAngleRelativeToZenith[date, lat, long] :=
{
   chi = moonPositionAngle[date]
   [ra, decl] = moonApparentRADecl[date]
   q = parallacticAngle[date, lat, long, ra, decl]
   return chi - q
}


// Returns a GeneralPath that represents the position of the moon.
//   arguments:
//     cx, cy: centerpoint of moon
//     radius: radius of moon
//     angle:  an angle, as obtained by one of the moonPositionAngle...
//             functions above.
//     illum:  the illuminated fraction of the moon as obtained from the
//             moonIlluminatedFraction function above.
//     filled: A boolean value indicating if the polygon is filled or not.
drawMoonPolygon[cx, cy, radius, angle, illumFraction, filled] :=
{
   // The shorter semidiameter of the inner ellipse.
   re = radius * (2 illumFraction - 1)

   ca = cos[angle]
   sa = sin[angle]

   if (filled)
      gp = new filledGeneralPath
   else
      gp = new GeneralPath

   // Draw outer limb
   gp.moveTo[radius ca + cx, -radius sa + cy]
   gp.circularArc[cx, cy, 180 degrees]

//   println["illumFraction is $illumFraction"]
//   println["re is $re"]
//   println["angle is " + (angle->degrees)]

   // Draw inner ellipse
   for theta = -90 degrees to 90 degrees step 2 degrees
   {
      x = re cos[theta]
      y = radius sin[theta]

      yp = x ca - y sa + cy
      xp = x sa + y ca + cx
      gp.lineTo[xp,yp]
   }

   gp.close[]
   
   return gp
}


// Draws a moon polygon relative to to the zenith.
// Params:
//   date:  The date to draw for
//   lat, long:  The latitude and longitude of the viewing location
//   cx, cy:  center coordinates to draw to
//   radius:  radius to draw moon
//   filled:  boolean flag describing whether to draw moon.
drawMoonPolygonRelativeToZenith[date, lat, long, cx, cy, radius, filled] :=
{
   return drawMoonPolygon[cx, cy, radius, moonPositionAngleRelativeToZenith[date, lat, long], moonIlluminatedFraction[date], filled]
}


// Parallax angle corrections (in azimuth and altitude) of an object as seen
// from earth.  This is needed because most of the other calculations here
// are geocentric, i.e., taken relative to the center of the earth.  The
// parallax becomes especially significant when taken to objects like the
// moon which are close to earth, and when looking at them close to the
// horizon.  Sigh.  I found this out the hard way.
// RETURNS:
//  altParallax
parallaxAngleAlt[distance, origAltitude] :=
{
   // TODO:  Add a term for altitude above the reference geoid
   
   // Horizontal parallax, called pi in Meeus
   // This is the equation *before* equation 40.1 in Meeus.
   // azParallax = arcsin[8.794 arcsec/(distance/au)]

   // The following possibly gives more accuracy and is more general
   // without the random unexplained factors of Meeus.
   azParallax = arcsin[earthradius_equatorial/distance]

   // TODO:  Correct this for non-sphericity of Earth.  For now, rho
   // is taken to be 1.
   altParallax = arcsin[sin[azParallax] cos[origAltitude]]

   return altParallax
}


// Returns the angular separation between two bodies given their right
// ascensions and declinations.
angularSeparation[ra1, decl1, ra2, decl2] :=
{
   return arccos[sin[decl1] sin[decl2] + cos[decl1] cos[decl2] cos[ra1-ra2]]
}


// Convenience function to obtain the angular separation between the sun
// and moon for a given time.
sunMoonAngularSeparation[date] :=
{
   [sunRA, sunDecl] = sunApparentRADecl[date]
   [moonRA, moonDecl] = moonApparentRADecl[date]
   return angularSeparation[sunRA, sunDecl, moonRA, moonDecl]
}


// Guess when the moon will be near the specified azimuth.
// The azimuth should be specified in real compass coordinates,
// *not* Meeus-like coordinates.  Thus, East should be 90 degrees, West 270
// degrees. This gives a very sloppy guess which should not be used
// directly, but rather as a seed to another routine, like moonrise[] or
// moonset[]
guessMoonAzimuth[date, lat, long, desiredAzimuth] :=
{
   [az, alt] = airlessMoonAzimuthAltitude[date, lat, long]
   realaz = (az + 180 deg)
//   println["Real azimuth is " + (realaz -> degrees)]
//   println["Altitude is " + (alt -> degrees)]
   azcorr = (realaz - desiredAzimuth) / (circle/ day)
   if (azcorr > 12 hours)
      azcorr = azcorr - day
   if (azcorr < -12 hours)
      azcorr = azcorr + day
//   println["Azimuth correction is " + (azcorr -> "hours")]
   timeguess = date - azcorr
//   println["Time guess is $timeguess"]
   return timeguess
}

// Calculate the time when the upper limb of the moon just crosses the
// horizon.
moonrise[date, lat, long, temp = 283 K, pressure=1010 millibars ] := moonSecantAltitude[guessMoonAzimuth[date, lat, long, 90 degrees],lat, long, -16 arcmin, temp, pressure]

// Calculate the time when the upper limb of the moon just crosses the
// horizon.
moonset[date, lat, long, temp = 283 K, pressure=1010 millibars ] := moonSecantAltitude[guessMoonAzimuth[date, lat, long, 270 degrees],lat, long, -16 arcmin, temp, pressure]


// Calculate the dip angle of the horizon.  This is a simplified calculation
// that just takes the height above the average sphere.
horizonDip[height] := arccos[earthradius / (earthradius+height)]


"sun.frink included Ok"


View or download sun.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 14704 days, 20 hours, 8 minutes ago.