lune.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /**
  2. * This library calculates the current phase of the moon
  3. * as well as finds the dates of the recent moon phases.
  4. *
  5. * Some functionality is ported from python version found here:
  6. * https://bazaar.launchpad.net/~keturn/py-moon-phase/trunk/annotate/head:/moon.py
  7. *
  8. * Some functionality is taken from Astronomical Algorithms, 2nd ed.
  9. *
  10. * Some functionality is taken from the US Naval Observatory, described here:
  11. * https://aa.usno.navy.mil/faq/docs/SunApprox.php
  12. *
  13. * Author: Ryan Seys (https://github.com/ryanseys)
  14. * Author: Jay LaPorte (https://github.com/ironwallaby)
  15. */
  16. 'use strict'
  17. const julian = require('./julian')
  18. // Phases of the moon & precision
  19. const NEW = 0
  20. const FIRST = 1
  21. const FULL = 2
  22. const LAST = 3
  23. const PHASE_MASK = 3
  24. // Astronomical Constants
  25. // Semi-major axis of Earth's orbit, in kilometers
  26. const SUN_SMAXIS = 1.49585e8
  27. // SUN_SMAXIS premultiplied by the angular size of the Sun from the Earth
  28. const SUN_ANGULAR_SIZE_SMAXIS = SUN_SMAXIS * 0.533128
  29. // Semi-major axis of the Moon's orbit, in kilometers
  30. const MOON_SMAXIS = 384401.0
  31. // MOON_SMAXIS premultiplied by the angular size of the Moon from the Earth
  32. const MOON_ANGULAR_SIZE_SMAXIS = MOON_SMAXIS * 0.5181
  33. // Synodic month (new Moon to new Moon), in days
  34. const SYNODIC_MONTH = 29.53058868
  35. /**
  36. * Convert degrees to radians
  37. * @param {Number} d Angle in degrees
  38. * @return {Number} Angle in radians
  39. */
  40. function torad (d) {
  41. return (Math.PI / 180.0) * d
  42. }
  43. /**
  44. * Convert radians to degrees
  45. * @param {Number} r Angle in radians
  46. * @return {Number} Angle in degrees
  47. */
  48. function dsin (d) {
  49. return Math.sin(torad(d))
  50. }
  51. function dcos (d) {
  52. return Math.cos(torad(d))
  53. }
  54. /**
  55. * Convert astronomical units to kilometers
  56. * @param {Number} au Distance in astronomical units
  57. * @return {Number} Distance in kilometers
  58. */
  59. function tokm (au) {
  60. return 149597870.700 * au
  61. }
  62. /**
  63. * Finds the phase information for specific date.
  64. * @param {Date} date Date to get phase information of.
  65. * @return {Object} Phase data
  66. */
  67. function phase (date) {
  68. if (!date) {
  69. date = new Date()
  70. }
  71. if (!(date instanceof Date)) {
  72. throw new TypeError('Invalid parameter')
  73. }
  74. if (Number.isNaN(date.getTime())) {
  75. throw new RangeError('Invalid Date')
  76. }
  77. // t is the time in "Julian centuries" of 36525 days starting from 2000-01-01
  78. // (Note the 0.3 is because the UNIX epoch is on 1970-01-01 and that's 3/10th
  79. // of a century before 2000-01-01, which I find cute :3 )
  80. const t = date.getTime() * (1 / 3155760000000) - 0.3
  81. // lunar mean elongation (Astronomical Algorithms, 2nd ed., p. 338)
  82. const d = 297.8501921 + t * (445267.1114034 + t * (-0.0018819 + t * ((1 / 545868) + t * (-1 / 113065000))))
  83. // solar mean anomaly (p. 338)
  84. const m = 357.5291092 + t * (35999.0502909 + t * (-0.0001536 + t * (1 / 24490000)))
  85. // lunar mean anomaly (p. 338)
  86. const n = 134.9633964 + t * (477198.8675055 + t * (0.0087414 + t * ((1 / 69699) + t * (-1 / 14712000))))
  87. // derive sines and cosines necessary for the below calculations
  88. const sind = dsin(d)
  89. const sinm = dsin(m)
  90. const sinn = dsin(n)
  91. const cosd = dcos(d)
  92. const cosm = dcos(m)
  93. const cosn = dcos(n)
  94. // use trigonometric identities to derive the remainder of the sines and
  95. // cosines we need. this reduces us from needing 14 sin/cos calls to only 7,
  96. // and makes the lunar distance and solar distance essentially free.
  97. // http://mathworld.wolfram.com/Double-AngleFormulas.html
  98. // http://mathworld.wolfram.com/TrigonometricAdditionFormulas.html
  99. const sin2d = 2 * sind * cosd // sin(2d)
  100. const sin2n = 2 * sinn * cosn // sin(2n)
  101. const cos2d = 2 * cosd * cosd - 1 // cos(2d)
  102. const cos2m = 2 * cosm * cosm - 1 // cos(2m)
  103. const cos2n = 2 * cosn * cosn - 1 // cos(2n)
  104. const sin2dn = sin2d * cosn - cos2d * sinn // sin(2d - n)
  105. const cos2dn = cos2d * cosn + sin2d * sinn // cos(2d - n)
  106. // lunar phase angle (p. 346)
  107. const i = 180 - d - 6.289 * sinn + 2.100 * sinm - 1.274 * sin2dn - 0.658 * sin2d - 0.214 * sin2n - 0.110 * sind
  108. // fractional illumination (p. 345)
  109. const illumination = dcos(i) * 0.5 + 0.5
  110. // fractional lunar phase
  111. let phase = 0.5 - i * (1 / 360)
  112. phase -= Math.floor(phase)
  113. // lunar distance (p. 339-342)
  114. // XXX: the book is not clear on how many terms to use for a given level of
  115. // accuracy! I used all the easy ones that we already have for free above,
  116. // but I imagine this is more than necessary...
  117. const moonDistance = 385000.56 - 20905.355 * cosn - 3699.111 * cos2dn - 2955.968 * cos2d - 569.925 * cos2n + 108.743 * cosd
  118. // solar distance
  119. // https://aa.usno.navy.mil/faq/docs/SunApprox.php
  120. const sunDistance = tokm(1.00014 - 0.01671 * cosm - 0.00014 * cos2m)
  121. return {
  122. phase: phase,
  123. illuminated: illumination,
  124. age: phase * SYNODIC_MONTH,
  125. distance: moonDistance,
  126. angular_diameter: MOON_ANGULAR_SIZE_SMAXIS / moonDistance,
  127. sun_distance: sunDistance,
  128. sun_angular_diameter: SUN_ANGULAR_SIZE_SMAXIS / sunDistance
  129. }
  130. }
  131. /**
  132. * Calculates time of the mean new Moon for a given base date.
  133. * This argument K to this function is the precomputed synodic month
  134. * index, given by:
  135. * K = (year - 1900) * 12.3685
  136. * where year is expressed as a year and fractional year.
  137. * @param {Date} sdate Start date
  138. * @param {[type]} k [description]
  139. * @return {[type]} [description]
  140. */
  141. function meanphase (sdate, k) {
  142. // Time in Julian centuries from 1900 January 12 noon UTC
  143. const delta = (sdate - -2208945600000.0) / 86400000.0
  144. const t = delta / 36525
  145. return 2415020.75933 +
  146. SYNODIC_MONTH * k +
  147. (0.0001178 - 0.000000155 * t) * t * t +
  148. 0.00033 * dsin(166.56 + (132.87 - 0.009173 * t) * t)
  149. }
  150. /**
  151. * Given a K value used to determine the mean phase of the new moon, and a
  152. * phase selector (0, 1, 2, 3), obtain the true, corrected phase time.
  153. * @param {[type]} k [description]
  154. * @param {[type]} tphase [description]
  155. * @return {[type]} [description]
  156. */
  157. function truephase (k, tphase) {
  158. // restrict tphase to (0, 1, 2, 3)
  159. tphase = tphase & PHASE_MASK
  160. // add phase to new moon time
  161. k = k + 0.25 * tphase
  162. // Time in Julian centuries from 1900 January 0.5
  163. const t = (1.0 / 1236.85) * k
  164. // Mean time of phase
  165. let pt = 2415020.75933 +
  166. SYNODIC_MONTH * k +
  167. (0.0001178 - 0.000000155 * t) * t * t +
  168. 0.00033 * dsin(166.56 + (132.87 - 0.009173 * t) * t)
  169. // Sun's mean anomaly
  170. const m = 359.2242 + 29.10535608 * k - (0.0000333 - 0.00000347 * t) * t * t
  171. // Moon's mean anomaly
  172. const mprime = 306.0253 + 385.81691806 * k + (0.0107306 + 0.00001236 * t) * t * t
  173. // Moon's argument of latitude
  174. const f = 21.2964 + 390.67050646 * k - (0.0016528 - 0.00000239 * t) * t * t
  175. // use different correction equations depending on the phase being sought
  176. switch (tphase) {
  177. // new and full moon use one correction
  178. case NEW:
  179. case FULL:
  180. pt += (0.1734 - 0.000393 * t) * dsin(m) +
  181. 0.0021 * dsin(2 * m) -
  182. 0.4068 * dsin(mprime) +
  183. 0.0161 * dsin(2 * mprime) -
  184. 0.0004 * dsin(3 * mprime) +
  185. 0.0104 * dsin(2 * f) -
  186. 0.0051 * dsin(m + mprime) -
  187. 0.0074 * dsin(m - mprime) +
  188. 0.0004 * dsin(2 * f + m) -
  189. 0.0004 * dsin(2 * f - m) -
  190. 0.0006 * dsin(2 * f + mprime) +
  191. 0.0010 * dsin(2 * f - mprime) +
  192. 0.0005 * dsin(m + 2 * mprime)
  193. break
  194. // first and last quarter moon use a different correction
  195. case FIRST:
  196. case LAST:
  197. pt += (0.1721 - 0.0004 * t) * dsin(m) +
  198. 0.0021 * dsin(2 * m) -
  199. 0.6280 * dsin(mprime) +
  200. 0.0089 * dsin(2 * mprime) -
  201. 0.0004 * dsin(3 * mprime) +
  202. 0.0079 * dsin(2 * f) -
  203. 0.0119 * dsin(m + mprime) -
  204. 0.0047 * dsin(m - mprime) +
  205. 0.0003 * dsin(2 * f + m) -
  206. 0.0004 * dsin(2 * f - m) -
  207. 0.0006 * dsin(2 * f + mprime) +
  208. 0.0021 * dsin(2 * f - mprime) +
  209. 0.0003 * dsin(m + 2 * mprime) +
  210. 0.0004 * dsin(m - 2 * mprime) -
  211. 0.0003 * dsin(2 * m + mprime)
  212. // the sign of the last term depends on whether we're looking for a first
  213. // or last quarter moon!
  214. const sign = (tphase < FULL) ? +1 : -1
  215. pt += sign * (0.0028 - 0.0004 * dcos(m) + 0.0003 * dcos(mprime))
  216. break
  217. }
  218. return julian.toDate(pt)
  219. }
  220. /**
  221. * Find time of phases of the moon which surround the current date.
  222. * Five phases are found, starting and ending with the new moons
  223. * which bound the current lunation.
  224. * @param {Date} sdate Date to start hunting from (defaults to current date)
  225. * @return {Object} Object containing recent past and future phases
  226. */
  227. function phaseHunt (sdate) {
  228. if (!sdate) {
  229. sdate = new Date()
  230. }
  231. if (!(sdate instanceof Date)) {
  232. throw new TypeError('Invalid parameter')
  233. }
  234. if (Number.isNaN(sdate.getTime())) {
  235. throw new RangeError('Invalid Date')
  236. }
  237. let adate = new Date(sdate.getTime() - (45 * 86400000)) // 45 days prior
  238. let k1 = Math.floor(12.3685 * (adate.getFullYear() + (1.0 / 12.0) * adate.getMonth() - 1900))
  239. let nt1 = meanphase(adate.getTime(), k1)
  240. sdate = julian.fromDate(sdate)
  241. adate = nt1 + SYNODIC_MONTH
  242. let k2 = k1 + 1
  243. let nt2 = meanphase(adate, k2)
  244. while (nt1 > sdate || sdate >= nt2) {
  245. adate += SYNODIC_MONTH
  246. k1++
  247. k2++
  248. nt1 = nt2
  249. nt2 = meanphase(adate, k2)
  250. }
  251. return {
  252. new_date: truephase(k1, NEW),
  253. q1_date: truephase(k1, FIRST),
  254. full_date: truephase(k1, FULL),
  255. q3_date: truephase(k1, LAST),
  256. nextnew_date: truephase(k2, NEW)
  257. }
  258. }
  259. function phaseRange (start, end, phase) {
  260. if (!(start instanceof Date)) {
  261. throw new TypeError('First argument must be a Date object.')
  262. }
  263. if (Number.isNaN(start.getTime())) {
  264. throw new RangeError('First argument not a valid date.')
  265. }
  266. if (!(end instanceof Date)) {
  267. throw new TypeError('Second argument must be a Date object.')
  268. }
  269. if (Number.isNaN(end.getTime())) {
  270. throw new RangeError('Second argument not a valid date.')
  271. }
  272. if (end - start < 0) {
  273. let temp = end
  274. end = start
  275. start = temp
  276. }
  277. start = start.getTime()
  278. end = end.getTime()
  279. let t = start - 45 * 86400000
  280. let k
  281. {
  282. const d = new Date(t)
  283. k = Math.floor(12.3685 * (d.getFullYear() + (1.0 / 12.0) * d.getMonth() - 1900))
  284. }
  285. let date = truephase(k, phase)
  286. // skip every phase before starting date
  287. while (date.getTime() < start) {
  288. k++
  289. date = truephase(k, phase)
  290. }
  291. // add every phase before (or on!) ending date to a list, and return it
  292. const list = []
  293. while (date.getTime() <= end) {
  294. list.push(date)
  295. k++
  296. date = truephase(k, phase)
  297. }
  298. return list
  299. }
  300. exports.PHASE_NEW = NEW
  301. exports.PHASE_FIRST = FIRST
  302. exports.PHASE_FULL = FULL
  303. exports.PHASE_LAST = LAST
  304. exports.phase = phase
  305. exports.phase_hunt = phaseHunt
  306. exports.phase_range = phaseRange