// display sun and moon data /* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, SunCalc, luxon */ // eslint-disable-next-line no-unused-vars class Almanac extends WeatherDisplay { constructor(navId,elemId,weatherParameters) { super(navId,elemId); // pre-load background image (returns promise) this.backgroundImage = utils.image.load('images/BackGround1_1.png'); // load all images in parallel (returns promises) this.moonImages = [ utils.image.load('images/2/Full-Moon.gif'), utils.image.load('images/2/Last-Quarter.gif'), utils.image.load('images/2/New-Moon.gif'), utils.image.load('images/2/First-Quarter.gif'), ]; this.backgroundImage = utils.image.load('images/BackGround3_1.png'); // get the data this.getData(weatherParameters); } getData(weatherParameters) { super.getData(); const {DateTime} = luxon; const sun = [ SunCalc.getTimes(new Date(), weatherParameters.latitude, weatherParameters.longitude), SunCalc.getTimes(DateTime.local().plus({days:1}).toJSDate(), weatherParameters.latitude, weatherParameters.longitude), ]; // brute force the moon phases by scanning the next 30 days const moon = []; // start with yesterday let moonDate = DateTime.local().minus({days:1}); let phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase; let iterations = 0; do { // get yesterday's moon info const lastPhase = phase; // calculate new values moonDate = moonDate.plus({days:1}); phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase; // check for 4 cases if (lastPhase < 0.25 && phase >= 0.25) moon.push(this.getMoonTransition(0.25, 'First', moonDate)); if (lastPhase < 0.50 && phase >= 0.50) moon.push(this.getMoonTransition(0.50, 'Full', moonDate)); if (lastPhase < 0.75 && phase >= 0.75) moon.push(this.getMoonTransition(0.75, 'Last', moonDate)); if (lastPhase > phase) moon.push(this.getMoonTransition(0.00, 'New', moonDate)); // stop after 30 days or 4 moon phases iterations++; } while (iterations <= 30 && moon.length < 4); // store the data this.data = { sun, moon, }; // draw the canvas this.drawCanvas(); } // get moon transition from one phase to the next by drilling down by hours, minutes and seconds getMoonTransition(threshold, phaseName, start, iteration = 0) { let moonDate = start; let phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase; let iterations = 0; const step = { hours: iteration === 0 ? -1:0, minutes: iteration === 1 ? 1:0, seconds: iteration === 2 ? -1:0, milliseconds: iteration === 3 ? 1:0, }; // increasing test let test = (lastPhase,phase,threshold) => lastPhase < threshold && phase >= threshold; // decreasing test if (iteration%2===0) test = (lastPhase,phase,threshold) => lastPhase > threshold && phase <= threshold; do { // store last phase const lastPhase = phase; // calculate new phase after step moonDate = moonDate.plus(step); phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase; // wrap phases > 0.9 to -0.1 for ease of detection if (phase > 0.9) phase -= 1.0; // compare if (test(lastPhase, phase, threshold)) { // last iteration is three, return value if (iteration >= 3) break; // iterate recursively return this.getMoonTransition(threshold, phaseName, moonDate, iteration+1); } iterations++; } while (iterations < 1000); return {phase: phaseName, date: moonDate}; } async drawCanvas() { super.drawCanvas(); const info = this.data; const {DateTime} = luxon; // extract moon images const [FullMoonImage, LastMoonImage, NewMoonImage, FirstMoonImage] = await Promise.all(this.moonImages); this.context.drawImage(await this.backgroundImage, 0, 0); draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2); draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90); draw.horizontalGradientSingle(this.context, 0, 90, 640, 190, draw.sideColor1, draw.sideColor2); draw.titleText(this.context, 'Almanac', 'Astronomical'); const Today = DateTime.local(); const Tomorrow = Today.plus({days: 1}); draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 320, 120, Today.toLocaleString({weekday: 'long'}), 2, 'center'); draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 500, 120, Tomorrow.toLocaleString({weekday: 'long'}), 2, 'center'); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 150, 'Sunrise:', 2); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 270, 150, DateTime.fromJSDate(info.sun[0].sunrise).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 450, 150, DateTime.fromJSDate(info.sun[1].sunrise).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 180, ' Sunset:', 2); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 270, 180, DateTime.fromJSDate(info.sun[0].sunset).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 450, 180, DateTime.fromJSDate(info.sun[1].sunset).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2); draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 70, 220, 'Moon Data:', 2); info.moon.forEach((MoonPhase, Index) => { const date = MoonPhase.date.toLocaleString({month: 'short', day: 'numeric'}); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+Index*130, 260, MoonPhase.phase, 2, 'center'); draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+Index*130, 390, date, 2, 'center'); const image = (() => { switch (MoonPhase.phase) { case 'Full': return FullMoonImage; case 'Last': return LastMoonImage; case 'New': return NewMoonImage; case 'First': default: return FirstMoonImage; } })(); this.context.drawImage(image, 75+Index*130, 270); }); this.finishDraw(); this.setStatus(STATUS.loaded); } }