Task #9: Here Comes/Goes the Sun

This isn’t an entry, per se, but it would make any of the entries more fun, because you could make them animate in sync with the actual sun!

I started with sunrise-sunset/index.js at b7b0cd9712257c1dea1f47b0390471c2ca6d966d · udivankin/sunrise-sunset · GitHub (the version before it was ported to TypeScript) and PBscriptified it, rolled a bunch of degrees/radians calculations directly into the code, and added a cache and fade in/out utility.

It seems to calculate the correct sunrise/sunset for my location (hardcoded in this example, be sure to change it to your lat/long!), more or less the same as Sunrise and Sunset Calculator. I have a small doubt about the +1 I had to add to the hour. I expect that’s a DST artifact and I’m happy to report that DST is soon going away forever where I live. :smiley:

Sunrise/Sunset calculator code
function pmod(a, b) {
  result = a % b

  return result < 0
    ? result + b
    : result
}

function getDayOfYear(Year, Month, Day) {
  // check for leap year
  mdays = [31,
    ( (((Year % 4) == 0) && (Year % 100 != 0)) || (Year % 400 == 0) ) ? 29 : 28,
  31,30,31,30,31,31,30,31,30,31]

  yday = Day

  for(m = 0; m < Month; m++) {
      yday += mdays[m]
  }

  return yday + 1 // ? +1 because it's 0-based? I dunno.
}

DEFAULT_ZENITH = 90.8333
DEGREES_PER_HOUR = 15

// Return fractional hour of sunrise or sunset
function calculate(latitude, longitude, isSunrise, zenith, Year, Month, Day) {
  var dayOfYear = getDayOfYear(Year, Month, Day)
  var hoursFromMeridian = longitude / DEGREES_PER_HOUR
  var approxTimeOfEventInDays = isSunrise
    ? dayOfYear + ((6 - hoursFromMeridian) / 24)
    : dayOfYear + ((18 - hoursFromMeridian) / 24)

  var sunMeanAnomaly = (0.9856 * approxTimeOfEventInDays) - 3.289
  var sunTrueLongitude = pmod(sunMeanAnomaly + (1.916 * sin(sunMeanAnomaly * PI / 180)) + (0.020 * sin(sunMeanAnomaly * PI / 90)) + 282.634, 360)
  var ascension = 0.91764 * tan(sunTrueLongitude * PI / 180)

  var rightAscension = atan(ascension) * 180 / PI
  rightAscension = pmod(rightAscension, 360)

  var lQuadrant = floor(sunTrueLongitude / 90) * 90
  var raQuadrant = floor(rightAscension / 90) * 90
  rightAscension = rightAscension + (lQuadrant - raQuadrant)
  rightAscension /= DEGREES_PER_HOUR

  var sinDec = 0.39782 * sin(sunTrueLongitude * PI / 180)
  var cosDec = cos(asin(sinDec))
  var cosLocalHourAngle = ((cos(zenith * PI / 180)) - (sinDec * (sin(latitude * PI / 180)))) / (cosDec * cos(latitude * PI / 180))

  var localHourAngle = isSunrise
    ? 360 - (acos(cosLocalHourAngle) * (180 / PI))
    : acos(cosLocalHourAngle) * (180 / PI)

  var localHour = localHourAngle / DEGREES_PER_HOUR
  var localMeanTime = localHour + rightAscension - (0.06571 * approxTimeOfEventInDays) - 6.622
  var hour = pmod(localMeanTime - (longitude / DEGREES_PER_HOUR), 24) + 1 // ? +1 not sure why, maybe DST?

  return hour
}

// sunrise calc-and-cache
sr_cache = [-1,-1,-1,-1]

function getSunrise(latitude, longitude) {
  if (sr_cache[0] != clockYear() || sr_cache[1] != clockMonth() || sr_cache[2] != clockDay()) {
    sr_cache[0] = clockYear()
    sr_cache[1] = clockMonth()
    sr_cache[2] = clockDay()
    sr_cache[3] = calculate(latitude, longitude, true, DEFAULT_ZENITH, sr_cache[0], sr_cache[1], sr_cache[2])
  }
  return sr_cache[3]
}

// sunset calc-and-cache
ss_cache = [-1,-1,-1,-1]

function getSunset(latitude, longitude) {
  if (ss_cache[0] != clockYear() || ss_cache[1] != clockMonth() || ss_cache[2] != clockDay()) {
    ss_cache[0] = clockYear()
    ss_cache[1] = clockMonth()
    ss_cache[2] = clockDay()
    ss_cache[3] = calculate(latitude, longitude, false, DEFAULT_ZENITH, ss_cache[0], ss_cache[1], ss_cache[2])
  }
  return ss_cache[3]
}

// given a location, return a value which is 1 between sunrise and sunset
// but fades to 0 over trans (fractional) hours just outside of sr/ss
function getPower(latitude, longitude, trans) {
  var now = clockHour() + (clockMinute() / 60) + (clockSecond() / 3600)
  var sr = getSunrise(latitude, longitude)
  var ss = getSunset(latitude, longitude)

  if ( (now > sr) && (now < ss) ) {
    return 1
  }

  sr = sr - now
  if ( (sr > 0) && (sr < trans) ) {
    return (1 - sr/trans)
  }

  ss = now - ss
  if ( (ss > 0) && (ss < trans) ) {
    return ss/trans
  }

  return 0
}

// simple demonstration - use vars watch to see the results.
export var srh, srm, ssh, ssm, power

export function beforeRender(delta)  {
    var lat = 52.339
    var long = 4.9592
    sr = getSunrise(lat, long)
    srh = floor(sr)
    srm = 60 * (sr % 1)
    ss = getSunset(lat, long)
    ssh = floor(ss)
    ssm = 60 * (ss % 1)

    power = getPower(lat, long, 0.25) // 0.25h = 15m
}

export function render(index) {
    rgb(power,power,power)
}
2 Likes