.stars { background: #000 url(‘https://media.vo.rs/s5.png') repeat top center; z-index: 0; }

class MoonService {

/**
 * @param a
 *
 * @return mixed
 */
static fixangle(a) {
    return (a - 360 * Math.floor(a / 360));
}

//  KEPLER  --   Solve the equation of Kepler.
/**
 * @param m
 * @param ecc
 *
 * @return float
 */
static kepler(m, ecc) {

    let epsilon = 0.000001;
    let e = m = MoonService.deg2rad(m);
    let delta = 0;

    do {
        delta = e - ecc * Math.sin(e) - m;
        e -= delta / (1 - ecc * Math.cos(e));
    } while (Math.abs(delta) > epsilon);

    return e;
}

static deg2rad(angle) {
    return angle * 0.017453292519943295 // (angle / 180) * Math.PI;
}

/*  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.

*/

/**
 * @return float
 */
get phaser() {
    return this.phase;
}

set phaser(x){
    this.phase = x;
}

/*  Given a K value used to determine the mean phase of
the new moon, and a phase selector (0.0, 0.25, 0.5,
0.75), obtain the true, corrected phase time.

*/

/**
 * @return float
 */
get illumination() {
    return this.illum;
}

/* 	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.
*/

/**
 * @return float
 */
get ager() {
    return this.age;
}

/*  Convert UNIX timestamp to astronomical Julian time (i.e. Julian date plus day fraction).  */

/**
 * @return float
 */
get distance() {
    return this.dist;
}

/**
 * @return float
 */
diameter() {
    return this.angdia;
}

/* functions for accessing results */

/**
 * @return float
 */
sundistance() {
    return this.sundist;
}

/**
 * @return float
 */
sundiameter() {
    return this.sunangdia;
}

/**
 * @return mixed
 */
new_moon() {
    return this.get_phase(0);
}

/**
 * @param $n
 *
 * @return mixed
 */
get_phase($n) {
    if (NULL === this.quarters) {
        this.phasehunt();
    }

    return this.quarters[$n];
}

phasehunt() {
    $sdate = MoonService.utctojulian(this.timestamp);
    $adate = $sdate - 45;
    $ats = this.timestamp - 86400 * 45;
    console.log($ats);

    $yy = MoonService.gmdate('Y', $ats);
    $mm = MoonService.gmdate('n', $ats);
    $k1 = Math.floor(($yy + (($mm - 1) * (1 / 12)) - 1900) * 12.3685);
    $adate = $nt1 = this.meanphase($adate, $k1);
    while (true) {
        $adate += this.synmonth;
        $k2 = $k1 + 1;
        $nt2 = this.meanphase($adate, $k2);
        // if nt2 is close to sdate, then mean phase isn't good enough, we have to be more accurate
        if (abs($nt2 - $sdate) < 0.5) {
            $nt2 = this.truephase($k2, 0.0);
        }
        if ($nt1 <= $sdate && $nt2 > $sdate) {
            break;
        }
        $nt1 = $nt2;
        $k1 = $k2;
    }
    // results in Julian dates
    $data = [
        this.truephase($k1, 0.0),
        this.truephase($k1, 0.25),
        this.truephase($k1, 0.5),
        this.truephase($k1, 0.75),
        this.truephase($k2, 0.0)
    ];
    this.quarters = [];

    $data.forEach(function (v, index) {
        this.quarters.push((v - 2440587.5) * 86400)
    });

}


static gmdate(format, timestamp) {
    console.log('FIX ME!!!')
    return 500;
}

/**
 * @param $ts
 *
 * @return float
 */
/**
 * @param $ts
 *
 * @return float
 */
static utctojulian($ts) {
    return $ts / 86400 + 2440587.5;
}

/**
 * @param sdate
 * @param k
 *
 * @return float
 */
meanphase(sdate, k) {
    // Time in Julian centuries from 1900 January 0.5
    let t = (sdate - 2415020.0) / 36525;
    let t2 = t * t;
    let t3 = t2 * t;
    let nt1 = 2415020.75933 + this.synmonth * k
        + 0.0001178 * t2
        - 0.000000155 * t3
        + 0.00033 * Math.sin(MoonService.deg2rad(166.56 + 132.87 * t - 0.009173 * t2));

    return $nt1;
}

/**
 * @param k
 * @param phase
 *
 * @return bool|float
 */
truephase(k, phase) {
    let apcor = false;
    k += $phase;                // Add phase to new moon time
    let t = k / 1236.85;            // Time in Julian centuries from 1900 January 0.5
    let t2 = t * t;                // Square for frequent use
    let t3 = t2 * t;                // Cube for frequent use
    let pt = 2415020.75933            // Mean time of phase
        + this.synmonth * k
        + 0.0001178 * t2
        - 0.000000155 * t3
        + 0.00033 * Math.sin(MoonService.deg2rad(166.56 + 132.87 * t - 0.009173 * t2));
    let m = 359.2242 + 29.10535608 * k - 0.0000333 * t2 - 0.00000347 * t3;            // Sun's mean anomaly
    let mprime = 306.0253 + 385.81691806 * k + 0.0107306 * t2 + 0.00001236 * t3;    // Moon's mean anomaly
    let f = 21.2964 + 390.67050646 * k - 0.0016528 * t2 - 0.00000239 * t3;            // Moon's argument of latitude
    if (phase < 0.01 || Math.abs(phase - 0.5) < 0.01) {
        // Corrections for New and Full Moon
        pt += (0.1734 - 0.000393 * t) * Math.sin(MoonService.deg2rad(m))
            + 0.0021 * Math.sin(MoonService.deg2rad(2 * m))
            - 0.4068 * Math.sin(MoonService.deg2rad(mprime))
            + 0.0161 * Math.sin(MoonService.deg2rad(2 * mprime))
            - 0.0004 * Math.sin(MoonService.deg2rad(3 * mprime))
            + 0.0104 * Math.sin(MoonService.deg2rad(2 * f))
            - 0.0051 * Math.sin(MoonService.deg2rad(m + mprime))
            - 0.0074 * Math.sin(MoonService.deg2rad(m - mprime))
            + 0.0004 * Math.sin(MoonService.deg2rad(2 * f + m))
            - 0.0004 * Math.sin(MoonService.deg2rad(2 * f - m))
            - 0.0006 * Math.sin(MoonService.deg2rad(2 * f + mprime))
            + 0.0010 * Math.sin(MoonService.deg2rad(2 * f - mprime))
            + 0.0005 * Math.sin(MoonService.deg2rad(m + 2 * mprime));
        apcor = true;
    } else {
        if (Math.abs(phase - 0.25) < 0.01 || Math.abs(phase - 0.75) < 0.01) {
            pt += (0.1721 - 0.0004 * t) * Math.sin(MoonService.deg2rad(m))
                + 0.0021 * Math.sin(MoonService.deg2rad(2 * m))
                - 0.6280 * Math.sin(MoonService.deg2rad(mprime))
                + 0.0089 * Math.sin(MoonService.deg2rad(2 * mprime))
                - 0.0004 * Math.sin(MoonService.deg2rad(3 * mprime))
                + 0.0079 * Math.sin(MoonService.deg2rad(2 * f))
                - 0.0119 * Math.sin(MoonService.deg2rad(m + mprime))
                - 0.0047 * Math.sin(MoonService.deg2rad(m - mprime))
                + 0.0003 * Math.sin(MoonService.deg2rad(2 * f + m))
                - 0.0004 * Math.sin(MoonService.deg2rad(2 * f - m))
                - 0.0006 * Math.sin(MoonService.deg2rad(2 * f + mprime))
                + 0.0021 * Math.sin(MoonService.deg2rad(2 * f - mprime))
                + 0.0003 * Math.sin(MoonService.deg2rad(m + 2 * mprime))
                + 0.0004 * Math.sin(MoonService.deg2rad(m - 2 * mprime))
                - 0.0003 * Math.sin(MoonService.deg2rad(2 * m + mprime));
            if (phase < 0.5)        // First quarter correction
            {
                pt += 0.0028 - 0.0004 * Math.cos(MoonService.deg2rad(m)) + 0.0003 * Math.cos(MoonService.deg2rad(mprime));
            } else    // Last quarter correction
            {
                pt += -0.0028 + 0.0004 * Math.cos(MoonService.deg2rad(m)) - 0.0003 * Math.cos(MoonService.deg2rad(mprime));
            }
            apcor = true;
        }
    }
    if (!apcor)    // function was called with an invalid phase selector
    {
        return false;
    }

    return pt;
}

/**
 * @return mixed
 */
first_quarter() {
    return this.get_phase(1);
}

/**
 * @return mixed
 */
full_moon() {
    return this.get_phase(2);
}

/**
 * @return mixed
 */
last_quarter() {
    return this.get_phase(3);
}

/**
 * @return mixed
 */
next_new_moon() {
    return this.get_phase(4);
}


constructor(pdate = null) {
    if (null === pdate) {
        pdate = Math.round((new Date()).getTime() / 1000);
    }

    //set stuff because ecmascript 6 is ... special...

    this.timestamp = 0;
    this.phase = 0;
    this.illum = 0;
    this.age = 0;
    this.dist = 0;
    this.angdia = 0;
    this.sundist = 0;
    this.sunangdia = 0;
    this.synmonth = 0;
    this.quarters = 0;


    /*  Astronomical constants  */
    this.epoch = 2444238.5;            // 1980 January 0.0
    /*  Constants defining the Sun's apparent orbit  */
    this.elonge = 278.833540;        // Ecliptic longitude of the Sun at epoch 1980.0
    this.elongp = 282.596403;        // Ecliptic longitude of the Sun at perigee
    this.eccent = 0.016718;            // Eccentricity of Earth's orbit
    this.sunsmax = 1.495985e8;        // Semi-major axis of Earth's orbit, km
    this.sunangsiz = 0.533128;        // Sun's angular size, degrees, at semi-major axis distance
    /*  Elements of the Moon's orbit, epoch 1980.0  */
    this.mmlong = 64.975464;        // Moon's mean longitude at the epoch
    this.mmlongp = 349.383063;        // Mean longitude of the perigee at the epoch
    this.mlnode = 151.950429;        // Mean longitude of the node at the epoch
    this.minc = 5.145396;            // Inclination of the Moon's orbit
    this.mecc = 0.054900;            // Eccentricity of the Moon's orbit
    this.mangsiz = 0.5181;            // Moon's angular size at distance a from Earth
    this.msmax = 384401;            // Semi-major axis of Moon's orbit in km
    this.mparallax = 0.9507;        // Parallax at distance a from Earth
    this.synmonth = 29.53058868;    // Synodic month (new Moon to new Moon)
    this.lunatbase = 2423436.0;        // Base date for E. W. Brown's numbered series of lunations (1923 January 16)
    /*  Properties of the Earth  */
    // $earthrad = 6378.16;				// Radius of Earth in kilometres
    // $PI = 3.14159265358979323846;	// Assume not near black hole
    this.timestamp = pdate;
    // pdate is coming in as a UNIX timestamp, so convert it to Julian
    pdate = pdate / 86400 + 2440587.5;
    /* Calculation of the Sun's position */
    this.day = pdate - this.epoch;                                // Date within epoch

    this.N = MoonService.fixangle((360 / 365.2422) * this.day);        // Mean anomaly of the Sun
    this.M = MoonService.fixangle(this.N + this.elonge - this.elongp);        // Convert from perigee coordinates to epoch 1980.0
    this.Ec = MoonService.kepler(this.M, this.eccent);                    // Solve equation of Kepler
    this.Ec = Math.sqrt((1 + this.eccent) / (1 - this.eccent)) * Math.tan(this.Ec / 2);
    this.Ec = 2 * MoonService.rad2deg(Math.atan(this.Ec));                        // True anomaly
    this.Lambdasun = MoonService.fixangle(this.Ec + this.elongp);        // Sun's geocentric ecliptic longitude
    this.F = ((1 + this.eccent * Math.cos(MoonService.deg2rad(this.Ec))) / (1 - this.eccent * this.eccent));    // Orbital distance factor
    this.SunDist = this.sunsmax / this.F;                            // Distance to Sun in km
    this.SunAng = this.F * this.sunangsiz;                            // Sun's angular size in degrees
    /* Calculation of the Moon's position */
    this.ml = MoonService.fixangle(13.1763966 * this.day + this.mmlong);                // Moon's mean longitude
    this.MM = MoonService.fixangle(this.ml - 0.1114041 * this.day - this.mmlongp);        // Moon's mean anomaly
    this.MN = MoonService.fixangle(this.mlnode - 0.0529539 * this.day);                // Moon's ascending node mean longitude
    this.Ev = 1.2739 * Math.sin(MoonService.deg2rad(2 * (this.ml - this.Lambdasun) - this.MM));        // Evection
    this.Ae = 0.1858 * Math.sin(MoonService.deg2rad(this.M));                                // Annual equation
    this.A3 = 0.37 * Math.sin(MoonService.deg2rad(this.M));                                    // Correction term
    this.MmP = this.MM + this.Ev - this.Ae - this.A3;                                    // Corrected anomaly
    this.mEc = 6.2886 * Math.sin(MoonService.deg2rad(this.MmP));                                // Correction for the equation of the centre
    this.A4 = 0.214 * Math.sin(MoonService.deg2rad(2 * this.MmP));                            // Another correction term
    this.lP = this.ml + this.Ev + this.mEc - this.Ae + this.A4;                                // Corrected longitude
    this.V = 0.6583 * Math.sin(MoonService.deg2rad(2 * (this.lP - this.Lambdasun)));                // Variation
    this.lPP = this.lP + this.V;                                                // True longitude
    this.NP = this.MN - 0.16 * Math.sin(MoonService.deg2rad(this.M));                            // Corrected longitude of the node
    this.y = Math.sin(MoonService.deg2rad(this.lPP - this.NP)) * Math.cos(MoonService.deg2rad(this.minc));            // Y inclination coordinate
    this.x = Math.cos(MoonService.deg2rad(this.lPP - this.NP));                                    // X inclination coordinate
    this.Lambdamoon = MoonService.rad2deg(Math.atan2(this.y, this.x)) + this.NP;                        // Ecliptic longitude
    this.BetaM = MoonService.rad2deg(Math.asin(Math.sin(MoonService.deg2rad(this.lPP - this.NP)) * Math.sin(MoonService.deg2rad(this.minc))));        // Ecliptic latitude
    /* Calculation of the phase of the Moon */
    this.MoonAge = this.lPP - this.Lambdasun;                                    // Age of the Moon in degrees
    this.MoonPhase = (1 - Math.cos(MoonService.deg2rad(this.MoonAge))) / 2;                    // Phase of the Moon
    // Distance of moon from the centre of the Earth
    this.MoonDist = (this.msmax * (1 - this.mecc * this.mecc)) / (1 + this.mecc * Math.cos(MoonService.deg2rad(this.MmP + this.mEc)));
    this.MoonDFrac = this.MoonDist / this.msmax;
    this.MoonAng = this.mangsiz / this.MoonDFrac;                                // Moon's angular diameter
    // $MoonPar = $mparallax / $MoonDFrac;							// Moon's parallax
    // store results
    this.phase = MoonService.fixangle(this.MoonAge) / 360;                    // Phase (0 to 1)
    this.illum = this.MoonPhase;                                        // Illuminated fraction (0 to 1)
    this.age = this.synmonth * this.phase;                            // Age of moon (days)
    this.dist = this.MoonDist;                                        // Distance (kilometres)
    this.angdia = this.MoonAng;                                        // Angular diameter (degrees)
    this.sundist = this.SunDist;                                        // Distance to Sun (kilometres)
    this.sunangdia = this.SunAng;
}

static rad2deg(angle) {
    return angle * 57.29577951308232 // angle / Math.PI * 180
}


static drawmoon(inPhase, inIsWaxing){
    let phase = inPhase;
    let isWaxing = inIsWaxing;
    let shadowColour = 'black';
    let lightColour = 'yellow';
    let outerColour = '';
    let innerColour = '';
    let diameter = 100;
    let earthshine = 0.1;
    let blur = 3;

    if (phase < 0.5) {
        outerColour = lightColour;
        innerColour = shadowColour;
        if (isWaxing) {
            phase *= -1;
        }
    } else {
        outerColour = shadowColour;
        innerColour = lightColour;
        phase = 1 - phase;
        if (!isWaxing) {
            phase *= -1;
        }
    }

    let semiPhase = phase * 2;

    let outerDiameter = diameter;

    let absPhase = Math.abs(semiPhase);

    let n = ((1 - absPhase) * outerDiameter / 2) | 0.01;

    if (n === 0){
        n ++;
    }

    let innerRadius = n / 2 + outerDiameter * outerDiameter / (8 * n);

    let innerDiameter = innerRadius * 2;
    let innerOffset = semiPhase > 0 ? (outerDiameter / 2 - n) : (-2 * innerRadius + outerDiameter / 2 + n);

    let outerCss = 'position:relative;';
    outerCss += 'height:' + outerDiameter + 'px;';
    outerCss += 'width:' + outerDiameter + 'px;';
    outerCss += 'border:1px solid black;';
    outerCss += 'background-color:' + outerColour +';';
    outerCss += 'border-radius:' + (outerDiameter/2) + 'px;';
    outerCss += 'overflow:hidden;';

    let blurredDiameter = innerDiameter - blur;
    let blurredOffset = innerOffset + blur/2;

    let innerCss  = 'position:relative;';
    innerCss += 'background-color:' + innerColour +';';
    innerCss += 'border-radius:'+ (blurredDiameter/2) + 'px;';
    innerCss += 'height:'+ blurredDiameter + 'px;';
    innerCss += 'width:' + blurredDiameter + 'px;';
    innerCss += 'left:'+blurredOffset+ 'px;';
    innerCss += 'top:'+ ((outerDiameter-blurredDiameter)/2) + 'px;';
    innerCss += 'box-shadow: 0px 0px ' + blur + 'px ' + blur + 'px ' + innerColour +';';
    innerCss += 'opacity:' + (1 - earthshine)+';';

    return '<div style="' + outerCss +'"><div style="' + innerCss + '"></div></div>';

}

}

/* * phase(): the terminator phase angle as a fraction of a full circle (i.e., 0 to 1). Both 0 and 1 correspond to a New Moon, and 0.5 corresponds to a Full Moon. illumination(): the illuminated fraction of the Moon (0 = New, 1 = Full). age(): the age of the Moon, in days. distance(): the distance of the Moon from the centre of the Earth (kilometres). diameter(): the angular diameter subtended by the Moon as seen by an observer at the centre of the Earth (radians). sundistance(): the distance to the Sun (kilometres). sundiameter(): the angular diameter subtended by the Sun as seen by an observer at the centre of the Earth (radians). new_moon(): the time of the last New Moon (UNIX timestamp). next_new_moon(): the time of the next New Moon (UNIX timestamp). full_moon(): the time of the Full Moon in the current lunar cycle (UNIX timestamp). first_quarter(): the time of the first quarter in the current lunar cycle (UNIX timestamp). last_quarter(): the time of the last quarter in the current lunar cycle (UNIX timestamp). */

/*

  • is {{($moonService->phase() < 0.5) ? ‘waxing’ : ‘waning’}} and is currently {{round(($moonService->illumination()*100),2)}} % illuminated.
  • is currently @include(‘convertible’,[‘unit’=>‘km’,’number’=>((floor($moonService->distance())))]) from earth centre.
  • will be new on {{\Carbon\Carbon::createFromTimestamp($moonService->next_new_moon())->format(’l, jS \of F ‘)}} .
  • will be full moon on on {{\Carbon\Carbon::createFromTimestamp($moonService->full_moon())->format(’l, jS \of F ‘)}} .
The sun -
  • is currently @include(‘convertible’,[‘unit’=>‘km’,’number’=>((floor($moonService->sundistance())))]) from earth centre.
  • */

    jQuery(function() { ms = new MoonService(); jQuery("#moondraw").html(MoonService.drawmoon(ms.illumination, (ms.phase < 0.5))); });