mirror of https://github.com/espruino/BangleApps
225 lines
6.3 KiB
JavaScript
225 lines
6.3 KiB
JavaScript
// daylight world map clock
|
|
// equirectangular projected map and approximated daylight graph
|
|
|
|
// load font for timezone, weekday and day in month
|
|
require("FontDennis8").add(Graphics);
|
|
|
|
const W = g.getWidth();
|
|
const H = g.getHeight();
|
|
|
|
const TZOFFSET = new Date().getTimezoneOffset();
|
|
|
|
const UTCSTRING = ((TZOFFSET > 0 ? "-" : "+")
|
|
+ ("0" + Math.floor(Math.abs(TZOFFSET) / 60)).slice(-2))
|
|
+ (TZOFFSET % 60 ? Math.abs(TZOFFSET) % 60 : "");
|
|
|
|
function getMap() {
|
|
return {
|
|
width: 176, height: 88, bpp: 1,
|
|
transparent: 1,
|
|
buffer: require("heatshrink").decompress(atob("/4A/AA0Av+Ag4UQwBhDn//1//8///AUI3MAhAUBgIQBh4LC/kfCg34rmAngVD/1/CYICBA4IAF8EOwF/+AVCAAXj//AA4PjDQIVDgkQj/4gBtEx+EGgXwCoJ8Bv+8geQgIVE4P/553Egf/nwFCgUE4H8gBqB/0AhLxHggFE+E8gJoBDIIAI5wFE4F8h/4v5FBABA2BAAUf7n+VYXgoAVNn/Dv+fCoPACo8MEQPHHAUf4DuB//58FgCgsHeoWfMgUDConw4AVFh/wXIRDDwBWC8jfBFY3xaAa5DYYXkKw8D+YVDHAcXAwKuIgIUDSIIJCsYVKeAIVHj5fGNogVHgN/AwPyEgPhCokZCo40D8E0wcwTYhsECoY0D8H2hEACocBCoqnCKwQVB/nICokJ+4VL/RGBQQkdw4VESQTwCDgIVBNgkeEQaSEQQReC4QrEhwUECoUECooAFVwoABgF+CoY+DAYZAFAAOgv4VGoFgCpXwGIoABkEHDQUvCo9zD4YVE4EIgIUGCoNnZwYVCiEP8E8hYVH/kHII0Qj/wvkP94WH4IVGhE/MQMH54VH+IVGKYIJBgfnCo/98IVFcYP5/9HMYbdGn7FFv/4/9vCpH/4DmC4AVCD4P/n4VKUoXgCwQ2Cz42CCpX//BtCCoMeCpJTBZgcAgYFCjElCpA7BEIQVBZoeYp4sICoIQCIIJzC/+Mp+DCpJSC/kAj4KC5/f4GfK5AVIeYPgNpIVEIIf/6f/v6ZHPwYVG//7V5BtDCoMOEof+jYVH8AVFhgLD/EZCo6UBCokYBYa2BCp04G4oVJNAX+gF4XYqDHCoKqCCoIrDAoL9DCowfCB4N9CorMDCooPEfowVMB4IVPeAQABwIVPeAQABw4LEg/ANo/wTAQAI8E//YVS+F//IIGGg4AFCo7OHAAf+v/jCowqM//HAwvhCpuPOwwVNAAwrOAA3xCqhtOAH4AfW4wAN/0/A4sP//AgFygYVH/V/AwlwgE8gAACDYIAF9ArC+uACAUgCocAHIn8k/gj4FBCgYAGBoXwgEYDof+ChMAJ4PmAwcBDgIUKgANBJIkZ/0cCpYrBIAIADzkwChQ5B/tgBAh7FNpANMAGg="))
|
|
};
|
|
}
|
|
|
|
const YOFFSET = H - getMap().height;
|
|
|
|
// map offset in degree
|
|
// -180 to 180 / default: 0
|
|
function getLongitudeOffset() {
|
|
return require("Storage").readJSON("dwm-clock.json", 1) || {"lon": 0};
|
|
}
|
|
|
|
function drawMap() {
|
|
g.setBgColor(0, 0, 0);
|
|
|
|
// does not flip on it's own, but there is a draw function after that does
|
|
g.drawImages([{
|
|
x: -lonOffset * W / 360,
|
|
y: YOFFSET,
|
|
image: getMap(),
|
|
scale: 1,
|
|
rotate: 0,
|
|
center: false,
|
|
repeat: true,
|
|
nobounds: false
|
|
}], {
|
|
x: 0,
|
|
y: YOFFSET,
|
|
width: getMap().width,
|
|
height: getMap().height
|
|
});
|
|
}
|
|
|
|
function drawDaylightMap() {
|
|
// number of xy points, < 40 looks very skewed around solstice
|
|
const STEPS = 40;
|
|
const YFACTOR = getMap().height / 2;
|
|
const YOFF = H / 2 + YFACTOR;
|
|
var graph = [];
|
|
|
|
// progress of day, float 0 to 1
|
|
var dayOffset = (now.getHours() + (now.getMinutes() + TZOFFSET) / 60) / 24;
|
|
|
|
// sun position modifier
|
|
var sunPosMod;
|
|
|
|
var solarNoon = require("suncalc").getTimes(now, 0, 0, 0).solarNoon;
|
|
|
|
var altitude = require("suncalc").getPosition(solarNoon, 0, 0).altitude;
|
|
|
|
// this is trial and error. no thought went into this
|
|
sunPosMod = Math.pow(altitude - 0.08, 8);
|
|
|
|
// switch sign on equinox
|
|
// this is an approximation
|
|
if (require("suncalc").getPosition(solarNoon, 0, 0).azimuth < -1) {
|
|
sunPosMod = -sunPosMod;
|
|
}
|
|
|
|
for (var x = 0; x < (STEPS + 1) / STEPS; x += 1 / STEPS) {
|
|
// this is an approximation instead of projecting a circle onto a sphere
|
|
// y = arctan(sin(x) * n)
|
|
var y = Math.atan(Math.sin(2 * Math.PI * x + dayOffset * 2 * Math.PI
|
|
// user defined map offset fixed offset
|
|
// v v
|
|
+ 2 * Math.PI * lonOffset / 360 - Math.PI / 2) * sunPosMod)
|
|
* (2 / Math.PI);
|
|
// ^
|
|
// factor keeps y <= 1
|
|
|
|
graph.push(x * W, y * YFACTOR + YOFF);
|
|
}
|
|
|
|
// day area, yellow
|
|
g.setColor(0.8, 0.8, 0.3);
|
|
g.fillRect(0, YOFFSET, W, H);
|
|
|
|
// night area, blue
|
|
g.setColor(0, 0, 0.5);
|
|
// switch on equinox
|
|
if (sunPosMod < 0) {
|
|
g.fillPoly([0, H - 1].concat(graph, W - 1, H - 1));
|
|
} else {
|
|
g.fillPoly([0, YOFFSET].concat(graph, W, YOFFSET));
|
|
}
|
|
|
|
drawMap();
|
|
|
|
// day-night line, white
|
|
g.setColor(1, 1, 1);
|
|
g.drawPoly(graph, false);
|
|
}
|
|
|
|
function drawClock() {
|
|
// clock area
|
|
g.clearRect(0, YOFFSET, W, 24);
|
|
|
|
// clock text
|
|
g.setColor(1, 1, 1);
|
|
g.setFontAlign(0, -1);
|
|
g.setFont("Vector", 58);
|
|
// with the vector font this leaves 26px above the text
|
|
g.drawString(require("locale").time(now, 1), W / 2, 24 - 2);
|
|
|
|
|
|
// timezone text
|
|
g.setFontAlign(-1, 1);
|
|
g.setFont("6x8", 2);
|
|
g.drawString("UTC" + UTCSTRING, 3, YOFFSET);
|
|
|
|
|
|
// day text
|
|
g.setFontAlign(1, 1);
|
|
g.setFont("Dennis8", 2);
|
|
g.drawString(require("locale").dow(now, 1) + " " + now.getDate(),
|
|
W - 1, YOFFSET);
|
|
}
|
|
|
|
function renderScreen() {
|
|
now = new Date();
|
|
|
|
drawClock();
|
|
drawDaylightMap();
|
|
}
|
|
|
|
function renderAndQueue() {
|
|
timeoutID = setTimeout(renderAndQueue, 60000 - (Date.now() % 60000));
|
|
renderScreen();
|
|
}
|
|
|
|
g.reset().clearRect(Bangle.appRect);
|
|
|
|
Bangle.setUI("clock");
|
|
|
|
Bangle.loadWidgets();
|
|
Bangle.drawWidgets();
|
|
|
|
g.setBgColor(0, 0, 0);
|
|
|
|
var now = new Date();
|
|
|
|
// map offsets
|
|
var defLonOffset = getLongitudeOffset().lon;
|
|
var lonOffset = defLonOffset;
|
|
|
|
var timeoutID;
|
|
var timeoutIDTouch;
|
|
|
|
Bangle.on('drag', function(touch) {
|
|
|
|
if (timeoutIDTouch) {
|
|
clearTimeout(timeoutIDTouch);
|
|
}
|
|
|
|
// return after not touching for 5 seconds
|
|
timeoutIDTouch = setTimeout(renderAndQueue, 5 * 1000);
|
|
|
|
// touch map
|
|
if (touch.y >= YOFFSET) {
|
|
lonOffset -= touch.dx * 360 / W;
|
|
|
|
// wrap map offset
|
|
if (lonOffset < -180) {
|
|
lonOffset += 360;
|
|
} else if (lonOffset >= 180) {
|
|
lonOffset -= 360;
|
|
}
|
|
|
|
// snap to 0° longitude
|
|
if (lonOffset > -5 && lonOffset < 5) {
|
|
lonOffset = 0;
|
|
}
|
|
|
|
lonOffset = Math.round(lonOffset);
|
|
|
|
// clock area
|
|
g.clearRect(0, YOFFSET, W, 24);
|
|
|
|
// text
|
|
g.setColor(1, 1, 1);
|
|
g.setFontAlign(0, -1);
|
|
g.setFont("Dennis8", 2);
|
|
// could not get ° (degree sign) to render
|
|
g.drawString("select lon offset\n< tap: save\nreset: tap >\n"
|
|
+ lonOffset + " degree", W / 2, 24);
|
|
|
|
drawDaylightMap();
|
|
|
|
// touch clock, left side, save offset
|
|
} else if (touch.x < W / 2) {
|
|
if (defLonOffset != lonOffset) {
|
|
require("Storage").writeJSON("dwm-clock.json", {"lon": lonOffset});
|
|
defLonOffset = lonOffset;
|
|
}
|
|
|
|
renderScreen();
|
|
|
|
// touch clock, right side, reset offset
|
|
} else {
|
|
lonOffset = defLonOffset;
|
|
renderScreen();
|
|
}
|
|
});
|
|
|
|
renderAndQueue();
|