mirror of https://github.com/espruino/BangleApps
376 lines
9.4 KiB
JavaScript
376 lines
9.4 KiB
JavaScript
// banglejs app made by pancake
|
|
// sunrise/sunset script by Matt Kane from https://github.com/Triggertrap/sun-js
|
|
|
|
const LOCATION_FILE = 'mylocation.json';
|
|
|
|
Bangle.setUI('clock');
|
|
Bangle.loadWidgets();
|
|
// requires the myLocation app
|
|
function loadLocation () {
|
|
try {
|
|
return require('Storage').readJSON(LOCATION_FILE, 1);
|
|
} catch (e) {
|
|
return { lat: 41.38, lon: 2.168 };
|
|
}
|
|
}
|
|
const latlon = loadLocation() || {};
|
|
const lat = latlon.lat || 41.38;
|
|
const lon = latlon.lon || 2.168;
|
|
|
|
/**
|
|
* Sunrise/sunset script. By Matt Kane.
|
|
*
|
|
* Based loosely and indirectly on Kevin Boone's SunTimes Java implementation
|
|
* of the US Naval Observatory's algorithm.
|
|
*
|
|
* Copyright © 2012 Triggertrap Ltd. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
|
|
* Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
|
* details.
|
|
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA,
|
|
* or connect to: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
|
*/
|
|
|
|
Date.prototype.sunrise = function (latitude, longitude, zenith) {
|
|
return this.sunriseSet(latitude, longitude, true, zenith);
|
|
};
|
|
|
|
Date.prototype.sunset = function (latitude, longitude, zenith) {
|
|
return this.sunriseSet(latitude, longitude, false, zenith);
|
|
};
|
|
|
|
Date.prototype.sunriseSet = function (latitude, longitude, sunrise, zenith) {
|
|
if (!zenith) {
|
|
zenith = 90.8333;
|
|
}
|
|
|
|
const hoursFromMeridian = longitude / Date.DEGREES_PER_HOUR;
|
|
const dayOfYear = this.getDayOfYear();
|
|
let approxTimeOfEventInDays;
|
|
let sunMeanAnomaly;
|
|
let sunTrueLongitude;
|
|
let ascension;
|
|
let rightAscension;
|
|
let lQuadrant;
|
|
let raQuadrant;
|
|
let sinDec;
|
|
let cosDec;
|
|
let localHourAngle;
|
|
let localHour;
|
|
let localMeanTime;
|
|
let time;
|
|
|
|
if (sunrise) {
|
|
approxTimeOfEventInDays = dayOfYear + ((6 - hoursFromMeridian) / 24);
|
|
} else {
|
|
approxTimeOfEventInDays = dayOfYear + ((18.0 - hoursFromMeridian) / 24);
|
|
}
|
|
|
|
sunMeanAnomaly = (0.9856 * approxTimeOfEventInDays) - 3.289;
|
|
|
|
sunTrueLongitude = sunMeanAnomaly + (1.916 * Math.sinDeg(sunMeanAnomaly)) + (0.020 * Math.sinDeg(2 * sunMeanAnomaly)) + 282.634;
|
|
sunTrueLongitude = Math.mod(sunTrueLongitude, 360);
|
|
|
|
ascension = 0.91764 * Math.tanDeg(sunTrueLongitude);
|
|
rightAscension = 360 / (2 * Math.PI) * Math.atan(ascension);
|
|
rightAscension = Math.mod(rightAscension, 360);
|
|
|
|
lQuadrant = Math.floor(sunTrueLongitude / 90) * 90;
|
|
raQuadrant = Math.floor(rightAscension / 90) * 90;
|
|
rightAscension = rightAscension + (lQuadrant - raQuadrant);
|
|
rightAscension /= Date.DEGREES_PER_HOUR;
|
|
|
|
sinDec = 0.39782 * Math.sinDeg(sunTrueLongitude);
|
|
cosDec = Math.cosDeg(Math.asinDeg(sinDec));
|
|
cosLocalHourAngle = ((Math.cosDeg(zenith)) - (sinDec * (Math.sinDeg(latitude)))) / (cosDec * (Math.cosDeg(latitude)));
|
|
|
|
localHourAngle = Math.acosDeg(cosLocalHourAngle);
|
|
|
|
if (sunrise) {
|
|
localHourAngle = 360 - localHourAngle;
|
|
}
|
|
|
|
localHour = localHourAngle / Date.DEGREES_PER_HOUR;
|
|
|
|
localMeanTime = localHour + rightAscension - (0.06571 * approxTimeOfEventInDays) - 6.622;
|
|
|
|
time = localMeanTime - (longitude / Date.DEGREES_PER_HOUR);
|
|
time = Math.mod(time, 24);
|
|
|
|
const midnight = new Date(0);
|
|
// midnight.setUTCFullYear(this.getUTCFullYear());
|
|
// midnight.setUTCMonth(this.getUTCMonth());
|
|
// midnight.setUTCDate(this.getUTCDate());
|
|
|
|
const milli = midnight.getTime() + (time * 60 * 60 * 1000);
|
|
|
|
return new Date(milli);
|
|
};
|
|
|
|
Date.DEGREES_PER_HOUR = 360 / 24;
|
|
|
|
// Utility functions
|
|
|
|
Date.prototype.getDayOfYear = function () {
|
|
const onejan = new Date(this.getFullYear(), 0, 1);
|
|
return Math.ceil((this - onejan) / 86400000);
|
|
};
|
|
|
|
Math.degToRad = function (num) {
|
|
return num * Math.PI / 180;
|
|
};
|
|
|
|
Math.radToDeg = function (radians) {
|
|
return radians * 180.0 / Math.PI;
|
|
};
|
|
|
|
Math.sinDeg = function (deg) {
|
|
return Math.sin(deg * 2.0 * Math.PI / 360.0);
|
|
};
|
|
|
|
Math.acosDeg = function (x) {
|
|
return Math.acos(x) * 360.0 / (2 * Math.PI);
|
|
};
|
|
|
|
Math.asinDeg = function (x) {
|
|
return Math.asin(x) * 360.0 / (2 * Math.PI);
|
|
};
|
|
|
|
Math.tanDeg = function (deg) {
|
|
return Math.tan(deg * 2.0 * Math.PI / 360.0);
|
|
};
|
|
|
|
Math.cosDeg = function (deg) {
|
|
return Math.cos(deg * 2.0 * Math.PI / 360.0);
|
|
};
|
|
|
|
Math.mod = function (a, b) {
|
|
let result = a % b;
|
|
if (result < 0) {
|
|
result += b;
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const sunrise = new Date().sunrise(lat, lon);
|
|
const sunset = new Date().sunset(lat, lon);
|
|
|
|
const w = g.getWidth();
|
|
const h = g.getHeight();
|
|
const oy = h / 1.7;
|
|
|
|
let sunRiseX = 0;
|
|
let sunSetX = 0;
|
|
const sinStep = 13;
|
|
|
|
let pos = 0;
|
|
let realTime = true;
|
|
const r = 10;
|
|
|
|
let frames = 0; // amount of pending frames to render (0 if none)
|
|
// set to 1 because pos 0 is displayed as 0-1:59
|
|
let curPos = 1; // x position of the sun
|
|
let realPos = 0; // x position of the sun depending on currentime
|
|
|
|
|
|
function formatAsTime (hour, minute) {
|
|
return '' + ((hour < 10) ? '0' : '') + (0 | hour) +
|
|
':' + ((minute < 10) ? '0' : '') + (0 | minute);
|
|
}
|
|
|
|
function drawSinuses () {
|
|
let x = 0;
|
|
|
|
g.setColor(1, 1, 1);
|
|
let y = ypos(x);
|
|
while (x < w) {
|
|
y2 = ypos(x + sinStep);
|
|
g.drawLine(x, y, x + sinStep, y2);
|
|
y = y2;
|
|
x += sinStep; // no need to draw all steps
|
|
}
|
|
|
|
// sea level line
|
|
const sl0 = seaLevel(sunrise.getHours());
|
|
const sl1 = seaLevel(sunset.getHours());
|
|
sunRiseX = xfromTime(sunrise.getHours() + sunrise.getMinutes() / 60);
|
|
sunSetX = xfromTime(sunset.getHours() + sunset.getMinutes() / 60);
|
|
g.setColor(0, 0.5, 1);
|
|
g.drawLine(0, sl0, w, sl1);
|
|
g.drawLine(0, sl0 + 1, w, sl1 + 1);
|
|
/*
|
|
g.setColor(0, 0, 1);
|
|
g.drawLine(0, sl0 + 1, w, sl1 + 1);
|
|
g.setColor(0, 0, 0.5);
|
|
g.drawLine(0, sl0 + 2, w, sl1 + 2);
|
|
*/
|
|
}
|
|
|
|
function drawTimes () {
|
|
g.setColor(1, 1, 1);
|
|
g.setFont('6x8', 2);
|
|
g.drawString(formatAsTime(sunrise.getHours(), sunrise.getMinutes()), 10, h - 20);
|
|
g.drawString(formatAsTime(sunset.getHours(), sunset.getMinutes()), w - 60, h - 20);
|
|
}
|
|
|
|
function drawGlow () {
|
|
const now = new Date();
|
|
if (frames < 1 && realTime) {
|
|
pos = xfromTime(now.getHours() + now.getMinutes() / 60);
|
|
}
|
|
const x = pos;
|
|
const y = ypos(x);
|
|
|
|
g.setColor(0.2, 0.2, 0);
|
|
// wide glow
|
|
if (x > sunRiseX && x < sunSetX) {
|
|
g.fillCircle(x, y, r + 20);
|
|
g.setColor(0.5, 0.5, 0);
|
|
}
|
|
// smol glow
|
|
g.fillCircle(x, y, r + 8);
|
|
}
|
|
|
|
function seaLevel (hour) {
|
|
// hour goes from 0 to 24
|
|
// to get the X we divide the screen in 24
|
|
return ypos(xfromTime(hour));
|
|
}
|
|
|
|
function ypos (x) {
|
|
// offset, resulting in zenith being at the correct time
|
|
return oy + (32 * Math.sin(((x + sunRiseX - 12) / w) * 6.28 ));
|
|
}
|
|
|
|
function xfromTime (t) {
|
|
return (w / 24) * t;
|
|
}
|
|
|
|
function drawBall () {
|
|
const now = new Date();
|
|
if (frames < 1 && realTime) {
|
|
pos = xfromTime(now.getHours() + now.getMinutes() / 60);
|
|
}
|
|
const x = pos;
|
|
const y = ypos(x);
|
|
|
|
// glow
|
|
if (x > sunRiseX && x < sunSetX) {
|
|
g.setColor(1, 1, 1);
|
|
} else {
|
|
g.setColor(0.5, 0.5, 0);
|
|
}
|
|
g.fillCircle(x, y, r);
|
|
g.setColor(1, 1, 0);
|
|
g.drawCircle(x, y, r);
|
|
}
|
|
function drawClock () {
|
|
const now = new Date();
|
|
|
|
let hours = 0.0;
|
|
let mins = 0.0;
|
|
if (realTime) {
|
|
hours = now.getHours();
|
|
mins = now.getMinutes();
|
|
} else {
|
|
hours = 24 * (pos / w);
|
|
const nexth = 24 * 60 * (pos / w);
|
|
mins = 59 - ((24 * 60) - nexth) % 60;
|
|
|
|
// this prevents the displayed time to jump from 11:50 to 12:59 to 12:07
|
|
if (mins == 59) {
|
|
hours--;
|
|
}
|
|
}
|
|
|
|
g.setFont('Vector', 30);
|
|
g.setColor(realTime, 1, 1);
|
|
g.drawString(formatAsTime(hours, mins), w / 1.9, 32);
|
|
// day-month
|
|
if (realTime) {
|
|
const mo = now.getMonth() + 1;
|
|
const da = now.getDate();
|
|
g.setFont('6x8', 2);
|
|
g.drawString('' + da + '/' + mo, 5, 30);
|
|
}
|
|
}
|
|
|
|
function renderScreen () {
|
|
const now = new Date();
|
|
g.setColor(0, 0, 0);
|
|
g.fillRect(0, 30, w, h);
|
|
realPos = xfromTime(now.getHours() + now.getMinutes() / 60);
|
|
g.setFontAlign(-1, -1, 0);
|
|
|
|
Bangle.drawWidgets();
|
|
|
|
drawGlow();
|
|
drawSinuses();
|
|
drawTimes();
|
|
drawClock();
|
|
drawBall();
|
|
}
|
|
|
|
Bangle.on('drag', function (tap, top) {
|
|
if (tap.y < h / 3) {
|
|
curPos = pos;
|
|
initialAnimation();
|
|
} else {
|
|
pos = tap.x;
|
|
realTime = false;
|
|
}
|
|
renderScreen();
|
|
});
|
|
|
|
Bangle.on('lock', () => {
|
|
// TODO: render animation here
|
|
realTime = Bangle.isLocked();
|
|
renderScreen();
|
|
});
|
|
|
|
function initialAnimationFrame () {
|
|
if (frames > 0) {
|
|
let distance = (realPos - curPos) / frames;
|
|
pos = curPos;
|
|
curPos += distance;
|
|
renderScreen();
|
|
frames--;
|
|
setTimeout(initialAnimationFrame, 50);
|
|
} else {
|
|
realTime = true;
|
|
renderScreen();
|
|
}
|
|
}
|
|
|
|
function initialAnimation () {
|
|
const now = new Date();
|
|
realPos = xfromTime(now.getHours() + now.getMinutes() / 60);
|
|
const distance = Math.abs(realPos - pos);
|
|
frames = distance / 16;
|
|
realTime = false;
|
|
initialAnimationFrame();
|
|
}
|
|
|
|
function renderAndQueue() {
|
|
setTimeout(renderAndQueue, 60000 - (Date.now() % 60000));
|
|
renderScreen();
|
|
}
|
|
|
|
function main () {
|
|
sunRiseX = xfromTime(sunrise.getHours() + sunrise.getMinutes() / 60);
|
|
sunSetX = xfromTime(sunset.getHours() + sunset.getMinutes() / 60);
|
|
|
|
g.setBgColor(0, 0, 0);
|
|
g.clear();
|
|
renderAndQueue();
|
|
initialAnimation();
|
|
}
|
|
|
|
main();
|