lune
Version:
Calculate the phases of the moon
366 lines (314 loc) • 10.3 kB
JavaScript
/**
* This library calculates the current phase of the moon
* as well as finds the dates of the recent moon phases.
*
* Ported from python version found here:
* https://bazaar.launchpad.net/~keturn/py-moon-phase/trunk/annotate/head:/moon.py
*
* Author: Ryan Seys (https://github.com/ryanseys)
*/
'use strict'
const julian = require('./julian')
// Phases of the moon & precision
const NEW = 0
const FIRST = 1
const FULL = 2
const LAST = 3
const PHASE_MASK = 3
// Astronomical Constants
// JDN stands for Julian Day Number
// Angles here are in degrees
// 1980 January 0.0 in JDN
// XXX: DateTime(1980).jdn yields 2444239.5 -- which one is right?
// XXX: even though 2444239.5 is correct for the 1 Jan 1980, 2444238.5 gives
// better accuracy results... possibly somebody chose all of the below
// constants based on the wrong epoch?
const EPOCH = 2444238.5
// Ecliptic longitude of the Sun at epoch 1980.0
const ECLIPTIC_LONGITUDE_EPOCH = 278.833540
// Ecliptic longitude of the Sun at perigee
const ECLIPTIC_LONGITUDE_PERIGEE = 282.596403
// Eccentricity of Earth's orbit
const ECCENTRICITY = 0.016718
// Semi-major axis of Earth's orbit, in kilometers
const SUN_SMAXIS = 1.49585e8
// Sun's angular size, in degrees, at semi-major axis distance
const SUN_ANGULAR_SIZE_SMAXIS = 0.533128
// Elements of the Moon's orbit, epoch 1980.0
// Moon's mean longitude at the epoch
const MOON_MEAN_LONGITUDE_EPOCH = 64.975464
// Mean longitude of the perigee at the epoch
const MOON_MEAN_PERIGEE_EPOCH = 349.383063
// Eccentricity of the Moon's orbit
const MOON_ECCENTRICITY = 0.054900
// Semi-major axis of the Moon's orbit, in kilometers
const MOON_SMAXIS = 384401.0
// MOON_SMAXIS premultiplied by the angular size of the Moon from the Earth
const MOON_ANGULAR_SIZE_SMAXIS = MOON_SMAXIS * 0.5181
// Synodic month (new Moon to new Moon), in days
const SYNODIC_MONTH = 29.53058868
function fixangle (a) {
return a - 360.0 * Math.floor(a / 360.0)
}
/**
* Convert degrees to radians
* @param {Number} d Angle in degrees
* @return {Number} Angle in radians
*/
function torad (d) {
return (Math.PI / 180.0) * d
}
/**
* Convert radians to degrees
* @param {Number} r Angle in radians
* @return {Number} Angle in degrees
*/
function todeg (r) {
return (180.0 / Math.PI) * r
}
function dsin (d) {
return Math.sin(torad(d))
}
function dcos (d) {
return Math.cos(torad(d))
}
/**
* Solve the equation of Kepler.
*/
function kepler (m, ecc) {
const epsilon = 1e-6
m = torad(m)
let e = m
while (1) {
const delta = e - ecc * Math.sin(e) - m
e -= delta / (1.0 - ecc * Math.cos(e))
if (Math.abs(delta) <= epsilon) {
break
}
}
return e
}
/**
* Finds the phase information for specific date.
* @param {Date} phase_date Date to get phase information of.
* @return {Object} Phase data
*/
function phase (phase_date) {
if (!phase_date) {
phase_date = new Date()
}
phase_date = julian.fromDate(phase_date)
const day = phase_date - EPOCH
// calculate sun position
const sun_mean_anomaly =
(360.0 / 365.2422) * day +
(ECLIPTIC_LONGITUDE_EPOCH - ECLIPTIC_LONGITUDE_PERIGEE)
const sun_true_anomaly =
2 * todeg(Math.atan(
Math.sqrt((1.0 + ECCENTRICITY) / (1.0 - ECCENTRICITY)) *
Math.tan(0.5 * kepler(sun_mean_anomaly, ECCENTRICITY))
))
const sun_ecliptic_longitude =
ECLIPTIC_LONGITUDE_PERIGEE + sun_true_anomaly
const sun_orbital_distance_factor =
(1 + ECCENTRICITY * dcos(sun_true_anomaly)) /
(1 - ECCENTRICITY * ECCENTRICITY)
// calculate moon position
const moon_mean_longitude =
MOON_MEAN_LONGITUDE_EPOCH + 13.1763966 * day
const moon_mean_anomaly =
moon_mean_longitude - 0.1114041 * day - MOON_MEAN_PERIGEE_EPOCH
const moon_evection =
1.2739 * dsin(
2 * (moon_mean_longitude - sun_ecliptic_longitude) - moon_mean_anomaly
)
const moon_annual_equation =
0.1858 * dsin(sun_mean_anomaly)
// XXX: what is the proper name for this value?
const moon_mp =
moon_mean_anomaly +
moon_evection -
moon_annual_equation -
0.37 * dsin(sun_mean_anomaly)
const moon_equation_center_correction =
6.2886 * dsin(moon_mp)
const moon_corrected_longitude =
moon_mean_longitude +
moon_evection +
moon_equation_center_correction -
moon_annual_equation +
0.214 * dsin(2.0 * moon_mp)
const moon_age =
fixangle(
moon_corrected_longitude -
sun_ecliptic_longitude +
0.6583 * dsin(
2 * (moon_corrected_longitude - sun_ecliptic_longitude)
)
)
const moon_distance =
(MOON_SMAXIS * (1.0 - MOON_ECCENTRICITY * MOON_ECCENTRICITY)) /
(1.0 + MOON_ECCENTRICITY * dcos(moon_mp + moon_equation_center_correction))
return {
phase: (1.0 / 360.0) * moon_age,
illuminated: 0.5 * (1.0 - dcos(moon_age)),
age: (SYNODIC_MONTH / 360.0) * moon_age,
distance: moon_distance,
angular_diameter: MOON_ANGULAR_SIZE_SMAXIS / moon_distance,
sun_distance: SUN_SMAXIS / sun_orbital_distance_factor,
sun_angular_diameter: SUN_ANGULAR_SIZE_SMAXIS * sun_orbital_distance_factor
}
}
/**
* Calculates time of the mean new Moon for a given base date.
* This argument K to this function is the precomputed synodic month
* index, given by:
* K = (year - 1900) * 12.3685
* where year is expressed as a year and fractional year.
* @param {Date} sdate Start date
* @param {[type]} k [description]
* @return {[type]} [description]
*/
function meanphase (sdate, k) {
// Time in Julian centuries from 1900 January 12 noon UTC
const delta_t = (sdate - -2208945600000.0) / 86400000.0
const t = delta_t / 36525
return 2415020.75933 +
SYNODIC_MONTH * k +
(0.0001178 - 0.000000155 * t) * t * t +
0.00033 * dsin(166.56 + (132.87 - 0.009173 * t) * t)
}
/**
* Given a K value used to determine the mean phase of the new moon, and a
* phase selector (0, 1, 2, 3), obtain the true, corrected phase time.
* @param {[type]} k [description]
* @param {[type]} tphase [description]
* @return {[type]} [description]
*/
function truephase (k, tphase) {
// restrict tphase to (0, 1, 2, 3)
tphase = tphase & PHASE_MASK
// add phase to new moon time
k = k + 0.25 * tphase
// Time in Julian centuries from 1900 January 0.5
const t = (1.0 / 1236.85) * k
// Mean time of phase
let pt = 2415020.75933 +
SYNODIC_MONTH * k +
(0.0001178 - 0.000000155 * t) * t * t +
0.00033 * dsin(166.56 + (132.87 - 0.009173 * t) * t)
// Sun's mean anomaly
const m = 359.2242 + 29.10535608 * k - (0.0000333 - 0.00000347 * t) * t * t
// Moon's mean anomaly
const mprime = 306.0253 + 385.81691806 * k + (0.0107306 + 0.00001236 * t) * t * t
// Moon's argument of latitude
const f = 21.2964 + 390.67050646 * k - (0.0016528 - 0.00000239 * t) * t * t
// use different correction equations depending on the phase being sought
switch (tphase) {
// new and full moon use one correction
case NEW:
case FULL:
pt += (0.1734 - 0.000393 * t) * dsin(m) +
0.0021 * dsin(2 * m) -
0.4068 * dsin(mprime) +
0.0161 * dsin(2 * mprime) -
0.0004 * dsin(3 * mprime) +
0.0104 * dsin(2 * f) -
0.0051 * dsin(m + mprime) -
0.0074 * dsin(m - mprime) +
0.0004 * dsin(2 * f + m) -
0.0004 * dsin(2 * f - m) -
0.0006 * dsin(2 * f + mprime) +
0.0010 * dsin(2 * f - mprime) +
0.0005 * dsin(m + 2 * mprime)
break
// first and last quarter moon use a different correction
case FIRST:
case LAST:
pt += (0.1721 - 0.0004 * t) * dsin(m) +
0.0021 * dsin(2 * m) -
0.6280 * dsin(mprime) +
0.0089 * dsin(2 * mprime) -
0.0004 * dsin(3 * mprime) +
0.0079 * dsin(2 * f) -
0.0119 * dsin(m + mprime) -
0.0047 * dsin(m - mprime) +
0.0003 * dsin(2 * f + m) -
0.0004 * dsin(2 * f - m) -
0.0006 * dsin(2 * f + mprime) +
0.0021 * dsin(2 * f - mprime) +
0.0003 * dsin(m + 2 * mprime) +
0.0004 * dsin(m - 2 * mprime) -
0.0003 * dsin(2 * m + mprime)
// the sign of the last term depends on whether we're looking for a first
// or last quarter moon!
const sign = (tphase < FULL) ? +1 : -1
pt += sign * (0.0028 - 0.0004 * dcos(m) + 0.0003 * dcos(mprime))
break
}
return julian.toDate(pt)
}
/**
* Find time of phases of the moon which surround the current date.
* Five phases are found, starting and ending with the new moons
* which bound the current lunation.
* @param {Date} sdate Date to start hunting from (defaults to current date)
* @return {Object} Object containing recent past and future phases
*/
function phase_hunt (sdate) {
if (!sdate) {
sdate = new Date()
}
let adate = new Date(sdate.getTime() - (45 * 86400000)) // 45 days prior
let k1 = Math.floor(12.3685 * (adate.getFullYear() + (1.0 / 12.0) * adate.getMonth() - 1900))
let nt1 = meanphase(adate.getTime(), k1)
sdate = julian.fromDate(sdate)
adate = nt1 + SYNODIC_MONTH
let k2 = k1 + 1
let nt2 = meanphase(adate, k2)
while (nt1 > sdate || sdate >= nt2) {
adate += SYNODIC_MONTH
k1++
k2++
nt1 = nt2
nt2 = meanphase(adate, k2)
}
return {
new_date: truephase(k1, NEW),
q1_date: truephase(k1, FIRST),
full_date: truephase(k1, FULL),
q3_date: truephase(k1, LAST),
nextnew_date: truephase(k2, NEW)
}
}
function phase_range (start, end, phase) {
start = start.getTime()
end = end.getTime()
let t = start - 45 * 86400000
let k
{
const d = new Date(t)
k = Math.floor(12.3685 * (d.getFullYear() + (1.0 / 12.0) * d.getMonth() - 1900))
}
let date = truephase(k, phase)
// skip every phase before starting date
while (date.getTime() < start) {
k++
date = truephase(k, phase)
}
// add every phase before (or on!) ending date to a list, and return it
const list = []
while (date.getTime() <= end) {
list.push(date)
k++
date = truephase(k, phase)
}
return list
}
exports.PHASE_NEW = NEW
exports.PHASE_FIRST = FIRST
exports.PHASE_FULL = FULL
exports.PHASE_LAST = LAST
exports.phase = phase
exports.phase_hunt = phase_hunt
exports.phase_range = phase_range