mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 00:59:29 -07:00
change to airbnb eslint plugin
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class Almanac extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId,'Almanac');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Almanac');
|
||||
|
||||
// pre-load background images (returns promises)
|
||||
this.backgroundImage0 = utils.image.load('images/BackGround3_1.png');
|
||||
@@ -20,12 +20,11 @@ class Almanac extends WeatherDisplay {
|
||||
];
|
||||
|
||||
this.timing.totalScreens = 2;
|
||||
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// get images for outlook
|
||||
const imagePromises = [
|
||||
@@ -34,15 +33,15 @@ class Almanac extends WeatherDisplay {
|
||||
];
|
||||
|
||||
// get sun/moon data
|
||||
const {sun, moon} = this.calcSunMoonData(weatherParameters);
|
||||
const { sun, moon } = this.calcSunMoonData(weatherParameters);
|
||||
|
||||
// process images for outlook
|
||||
const [outlookTemp, outlookPrecip] = await Promise.all(imagePromises);
|
||||
|
||||
const outlook = this.parseOutlooks(weatherParameters.latitude, weatherParameters.longitude, outlookTemp, outlookPrecip);
|
||||
const outlook = Almanac.parseOutlooks(weatherParameters.latitude, weatherParameters.longitude, outlookTemp, outlookPrecip);
|
||||
|
||||
// store the data
|
||||
this.data = {
|
||||
this.data = {
|
||||
sun,
|
||||
moon,
|
||||
outlook,
|
||||
@@ -52,28 +51,27 @@ class Almanac extends WeatherDisplay {
|
||||
|
||||
// share data
|
||||
this.getDataCallback();
|
||||
|
||||
}
|
||||
|
||||
calcSunMoonData(weatherParameters) {
|
||||
const {DateTime} = luxon;
|
||||
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),
|
||||
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 moonDate = DateTime.local().minus({ days: 1 });
|
||||
let { phase } = SunCalc.getMoonIllumination(moonDate.toJSDate());
|
||||
let iterations = 0;
|
||||
do {
|
||||
// get yesterday's moon info
|
||||
const lastPhase = phase;
|
||||
// calculate new values
|
||||
moonDate = moonDate.plus({days:1});
|
||||
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));
|
||||
@@ -82,7 +80,7 @@ class Almanac extends WeatherDisplay {
|
||||
if (lastPhase > phase) moon.push(this.getMoonTransition(0.00, 'New', moonDate));
|
||||
|
||||
// stop after 30 days or 4 moon phases
|
||||
iterations++;
|
||||
iterations += 1;
|
||||
} while (iterations <= 30 && moon.length < 4);
|
||||
|
||||
return {
|
||||
@@ -94,19 +92,19 @@ class Almanac extends WeatherDisplay {
|
||||
// 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 { phase } = SunCalc.getMoonIllumination(moonDate.toJSDate());
|
||||
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,
|
||||
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;
|
||||
let test = (lastPhase, testPhase) => lastPhase < threshold && testPhase >= threshold;
|
||||
// decreasing test
|
||||
if (iteration%2===0) test = (lastPhase,phase,threshold) => lastPhase > threshold && phase <= threshold;
|
||||
if (iteration % 2 === 0) test = (lastPhase, testPhase) => lastPhase > threshold && testPhase <= threshold;
|
||||
|
||||
do {
|
||||
// store last phase
|
||||
@@ -117,42 +115,42 @@ class Almanac extends WeatherDisplay {
|
||||
// wrap phases > 0.9 to -0.1 for ease of detection
|
||||
if (phase > 0.9) phase -= 1.0;
|
||||
// compare
|
||||
if (test(lastPhase, phase, threshold)) {
|
||||
if (test(lastPhase, phase)) {
|
||||
// last iteration is three, return value
|
||||
if (iteration >= 3) break;
|
||||
// iterate recursively
|
||||
return this.getMoonTransition(threshold, phaseName, moonDate, iteration+1);
|
||||
return this.getMoonTransition(threshold, phaseName, moonDate, iteration + 1);
|
||||
}
|
||||
iterations++;
|
||||
iterations += 1;
|
||||
} while (iterations < 1000);
|
||||
|
||||
return {phase: phaseName, date: moonDate};
|
||||
return { phase: phaseName, date: moonDate };
|
||||
}
|
||||
|
||||
// use the color of the pixel to determine the outlook
|
||||
parseOutlooks(lat, lon, temp, precip) {
|
||||
const {DateTime} = luxon;
|
||||
static parseOutlooks(lat, lon, temp, precip) {
|
||||
const { DateTime } = luxon;
|
||||
const month = DateTime.local();
|
||||
const thisMonth = month.toLocaleString({month: 'short'});
|
||||
const nextMonth = month.plus({months: 1}).toLocaleString({month: 'short'});
|
||||
const thisMonth = month.toLocaleString({ month: 'short' });
|
||||
const nextMonth = month.plus({ months: 1 }).toLocaleString({ month: 'short' });
|
||||
|
||||
// draw the images on the canvases
|
||||
const tempContext = utils.image.drawLocalCanvas(temp);
|
||||
const precipContext = utils.image.drawLocalCanvas(precip);
|
||||
|
||||
// get the color from each canvas
|
||||
const tempColor = this.getOutlookColor(lat, lon, tempContext);
|
||||
const precipColor = this.getOutlookColor(lat, lon, precipContext);
|
||||
const tempColor = Almanac.getOutlookColor(lat, lon, tempContext);
|
||||
const precipColor = Almanac.getOutlookColor(lat, lon, precipContext);
|
||||
|
||||
return {
|
||||
thisMonth,
|
||||
nextMonth,
|
||||
temperature: this.getOutlookTemperatureIndicator(tempColor),
|
||||
precipitation: this.getOutlookPrecipitationIndicator(precipColor),
|
||||
temperature: Almanac.getOutlookTemperatureIndicator(tempColor),
|
||||
precipitation: Almanac.getOutlookPrecipitationIndicator(precipColor),
|
||||
};
|
||||
}
|
||||
|
||||
getOutlookColor (lat, lon, context) {
|
||||
static getOutlookColor(lat, lon, context) {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
@@ -195,11 +193,11 @@ class Almanac extends WeatherDisplay {
|
||||
|
||||
// Determine if there is any "non-white" colors around the area.
|
||||
// Search a 16x16 region.
|
||||
for (let colorX = x - 8; colorX <= x + 8; colorX++) {
|
||||
for (let colorY = y - 8; colorY <= y + 8; colorY++) {
|
||||
const pixelColor = this.getPixelColor(context, colorX, colorY);
|
||||
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0) ||
|
||||
(pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
|
||||
for (let colorX = x - 8; colorX <= x + 8; colorX += 1) {
|
||||
for (let colorY = y - 8; colorY <= y + 8; colorY += 1) {
|
||||
const pixelColor = Almanac.getPixelColor(context, colorX, colorY);
|
||||
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0)
|
||||
|| (pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
|
||||
return pixelColor;
|
||||
}
|
||||
}
|
||||
@@ -209,7 +207,7 @@ class Almanac extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// get rgb values of a pixel
|
||||
getPixelColor (context, x, y) {
|
||||
static getPixelColor(context, x, y) {
|
||||
const pixelData = context.getImageData(x, y, 1, 1).data;
|
||||
return {
|
||||
r: pixelData[0],
|
||||
@@ -219,33 +217,31 @@ class Almanac extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// get temperature outlook from color
|
||||
getOutlookTemperatureIndicator(pixelColor) {
|
||||
static getOutlookTemperatureIndicator(pixelColor) {
|
||||
if (pixelColor.b > pixelColor.r) {
|
||||
return 'Below Normal';
|
||||
} else if (pixelColor.r > pixelColor.b) {
|
||||
} if (pixelColor.r > pixelColor.b) {
|
||||
return 'Above Normal';
|
||||
} else {
|
||||
return 'Normal';
|
||||
}
|
||||
return 'Normal';
|
||||
}
|
||||
|
||||
// get precipitation outlook from color
|
||||
getOutlookPrecipitationIndicator (pixelColor) {
|
||||
static getOutlookPrecipitationIndicator(pixelColor) {
|
||||
if (pixelColor.g > pixelColor.r) {
|
||||
return 'Above Normal';
|
||||
} else if (pixelColor.r > pixelColor.g) {
|
||||
} if (pixelColor.r > pixelColor.g) {
|
||||
return 'Below Normal';
|
||||
} else {
|
||||
return 'Normal';
|
||||
}
|
||||
return 'Normal';
|
||||
}
|
||||
|
||||
async drawCanvas() {
|
||||
super.drawCanvas();
|
||||
const info = this.data;
|
||||
const {DateTime} = luxon;
|
||||
const { DateTime } = luxon;
|
||||
const Today = DateTime.local();
|
||||
const Tomorrow = Today.plus({days: 1});
|
||||
const Tomorrow = Today.plus({ days: 1 });
|
||||
|
||||
// extract moon images
|
||||
const [FullMoonImage, LastMoonImage, NewMoonImage, FirstMoonImage] = await Promise.all(this.moonImages);
|
||||
@@ -261,8 +257,8 @@ class Almanac extends WeatherDisplay {
|
||||
|
||||
draw.titleText(this.context, 'Almanac', 'Astronomical');
|
||||
|
||||
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', '#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);
|
||||
@@ -274,12 +270,11 @@ class Almanac extends WeatherDisplay {
|
||||
|
||||
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'});
|
||||
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');
|
||||
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) {
|
||||
@@ -294,11 +289,11 @@ class Almanac extends WeatherDisplay {
|
||||
return FirstMoonImage;
|
||||
}
|
||||
})();
|
||||
this.context.drawImage(image, 75+Index*130, 270);
|
||||
this.context.drawImage(image, 75 + Index * 130, 270);
|
||||
});
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case 1: {
|
||||
this.context.drawImage(await this.backgroundImage1, 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);
|
||||
@@ -309,14 +304,15 @@ class Almanac extends WeatherDisplay {
|
||||
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 180, '30 Day Outlook', 2, 'center');
|
||||
|
||||
var DateRange = 'MID-' + info.outlook.thisMonth.toUpperCase() + ' TO MID-' + info.outlook.nextMonth.toUpperCase();
|
||||
const DateRange = `MID-${info.outlook.thisMonth.toUpperCase()} TO MID-${info.outlook.nextMonth.toUpperCase()}`;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 220, DateRange, 2, 'center');
|
||||
|
||||
var Temperature = info.outlook.temperature;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, 'Temperatures: ' + Temperature, 2);
|
||||
const Temperature = info.outlook.temperature;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, `Temperatures: ${Temperature}`, 2);
|
||||
|
||||
var Precipitation = info.outlook.precipitation;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, 'Precipitation: ' + Precipitation, 2);
|
||||
const Precipitation = info.outlook.precipitation;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, `Precipitation: ${Precipitation}`, 2);
|
||||
}
|
||||
}
|
||||
|
||||
this.finishDraw();
|
||||
@@ -331,4 +327,4 @@ class Almanac extends WeatherDisplay {
|
||||
this.getDataCallbacks.push(resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,28 @@
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class CurrentWeather extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId,'Current Conditions');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Current Conditions');
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// Load the observations
|
||||
let observations, station;
|
||||
let observations; let
|
||||
station;
|
||||
// station number counter
|
||||
let stationNum = 0;
|
||||
while (!observations && stationNum < weatherParameters.stations.length) {
|
||||
// get the station
|
||||
station = weatherParameters.stations[stationNum];
|
||||
stationNum++;
|
||||
stationNum += 1;
|
||||
try {
|
||||
// station observations
|
||||
observations = await utils.fetch.json(`${station.id}/observations`,{
|
||||
observations = await utils.fetch.json(`${station.id}/observations`, {
|
||||
cors: true,
|
||||
data: {
|
||||
limit: 2,
|
||||
@@ -31,9 +32,9 @@ class CurrentWeather extends WeatherDisplay {
|
||||
});
|
||||
|
||||
// test data quality
|
||||
if (observations.features[0].properties.temperature.value === null ||
|
||||
observations.features[0].properties.windSpeed.value === null ||
|
||||
observations.features[0].properties.textDescription === null) {
|
||||
if (observations.features[0].properties.temperature.value === null
|
||||
|| observations.features[0].properties.windSpeed.value === null
|
||||
|| observations.features[0].properties.textDescription === null) {
|
||||
observations = undefined;
|
||||
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
|
||||
}
|
||||
@@ -53,7 +54,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||
utils.image.preload(icons.getWeatherIconFromIconLink(observations.features[0].properties.icon));
|
||||
|
||||
// we only get here if there was no error above
|
||||
this.data = Object.assign({}, observations, {station: station});
|
||||
this.data = { ...observations, station };
|
||||
this.setStatus(STATUS.loaded);
|
||||
|
||||
this.getDataCallback();
|
||||
@@ -71,7 +72,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||
data.DewPoint = Math.round(observations.dewpoint.value);
|
||||
data.Ceiling = Math.round(observations.cloudLayers[0].base.value);
|
||||
data.CeilingUnit = 'm.';
|
||||
data.Visibility = Math.round(observations.visibility.value/1000);
|
||||
data.Visibility = Math.round(observations.visibility.value / 1000);
|
||||
data.VisibilityUnit = ' km.';
|
||||
data.WindSpeed = Math.round(observations.windSpeed.value);
|
||||
data.WindDirection = utils.calc.directionToNSEW(observations.windDirection.value);
|
||||
@@ -95,9 +96,9 @@ class CurrentWeather extends WeatherDisplay {
|
||||
data.Temperature = utils.units.celsiusToFahrenheit(data.Temperature);
|
||||
data.TemperatureUnit = 'F';
|
||||
data.DewPoint = utils.units.celsiusToFahrenheit(data.DewPoint);
|
||||
data.Ceiling = Math.round(utils.units.metersToFeet(data.Ceiling)/100)*100;
|
||||
data.Ceiling = Math.round(utils.units.metersToFeet(data.Ceiling) / 100) * 100;
|
||||
data.CeilingUnit = 'ft.';
|
||||
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value/1000);
|
||||
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value / 1000);
|
||||
data.VisibilityUnit = ' mi.';
|
||||
data.WindSpeed = utils.units.kphToMph(data.WindSpeed);
|
||||
data.WindUnit = 'MPH';
|
||||
@@ -109,7 +110,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||
return data;
|
||||
}
|
||||
|
||||
async drawCanvas () {
|
||||
async drawCanvas() {
|
||||
super.drawCanvas();
|
||||
// parse each time to deal with a change in units if necessary
|
||||
const data = this.parseData();
|
||||
@@ -131,14 +132,14 @@ class CurrentWeather extends WeatherDisplay {
|
||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 195, 170, Conditions, 2, 'center');
|
||||
|
||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 330, 'Wind:', 2);
|
||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 300, 330, data.WindDirection + ' ' + data.WindSpeed, 2, 'right');
|
||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 300, 330, `${data.WindDirection} ${data.WindSpeed}`, 2, 'right');
|
||||
|
||||
if (data.WindGust) draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 375, 'Gusts to ' + data.WindGust, 2);
|
||||
if (data.WindGust) draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 375, `Gusts to ${data.WindGust}`, 2);
|
||||
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFF00', 315, 120, this.data.station.properties.name.substr(0, 20), 2);
|
||||
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 165, 'Humidity:', 2);
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 165, data.Humidity + '%', 2, 'right');
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 165, `${data.Humidity}%`, 2, 'right');
|
||||
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 205, 'Dewpoint:', 2);
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 205, data.DewPoint + String.fromCharCode(176), 2, 'right');
|
||||
@@ -213,7 +214,8 @@ class CurrentWeather extends WeatherDisplay {
|
||||
});
|
||||
}
|
||||
|
||||
shortConditions(condition) {
|
||||
static shortConditions(_condition) {
|
||||
let condition = _condition;
|
||||
condition = condition.replace(/Light/g, 'L');
|
||||
condition = condition.replace(/Heavy/g, 'H');
|
||||
condition = condition.replace(/Partly/g, 'P');
|
||||
@@ -230,5 +232,4 @@ class CurrentWeather extends WeatherDisplay {
|
||||
condition = condition.replace(/ with /g, '/');
|
||||
return condition;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
/* globals draw, navigation */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@@ -10,7 +8,6 @@ const currentWeatherScroll = (() => {
|
||||
// local variables
|
||||
let context; // currently active context
|
||||
let blankDrawArea; // original state of context
|
||||
let station;
|
||||
let interval;
|
||||
let screenIndex = 0;
|
||||
|
||||
@@ -36,7 +33,6 @@ const currentWeatherScroll = (() => {
|
||||
|
||||
// draw the data
|
||||
drawScreen();
|
||||
|
||||
};
|
||||
|
||||
const stop = (reset) => {
|
||||
@@ -53,7 +49,7 @@ const currentWeatherScroll = (() => {
|
||||
|
||||
// increment interval, roll over
|
||||
const incrementInterval = () => {
|
||||
screenIndex = (screenIndex+1)%(screens.length);
|
||||
screenIndex = (screenIndex + 1) % (screens.length);
|
||||
// draw new text
|
||||
drawScreen();
|
||||
};
|
||||
@@ -74,7 +70,7 @@ const currentWeatherScroll = (() => {
|
||||
// the "screens" are stored in an array for easy addition and removal
|
||||
const screens = [
|
||||
// station name
|
||||
(data) => `Conditions at ${data.station.properties.name.substr(0,20)}`,
|
||||
(data) => `Conditions at ${data.station.properties.name.substr(0, 20)}`,
|
||||
|
||||
// temperature
|
||||
(data) => {
|
||||
@@ -108,7 +104,7 @@ const currentWeatherScroll = (() => {
|
||||
},
|
||||
|
||||
// visibility
|
||||
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling===0?'Unlimited':`${data.Ceiling} ${data.CeilingUnit}`}`,
|
||||
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling === 0 ? 'Unlimited' : `${data.Ceiling} ${data.CeilingUnit}`}`,
|
||||
];
|
||||
|
||||
// internal draw function with preset parameters
|
||||
@@ -121,4 +117,4 @@ const currentWeatherScroll = (() => {
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -46,17 +46,17 @@ const draw = (() => {
|
||||
}
|
||||
};
|
||||
|
||||
const text = (context, font, size, color, x, y, text, shadow = 0, align = 'start') => {
|
||||
const text = (context, font, size, color, x, y, myText, shadow = 0, align = 'start') => {
|
||||
context.textAlign = align;
|
||||
context.font = size + ` '${font}'`;
|
||||
context.font = `${size} '${font}'`;
|
||||
context.shadowColor = '#000000';
|
||||
context.shadowOffsetX = shadow;
|
||||
context.shadowOffsetY = shadow;
|
||||
context.strokeStyle = '#000000';
|
||||
context.lineWidth = 2;
|
||||
context.strokeText(text, x, y);
|
||||
context.strokeText(myText, x, y);
|
||||
context.fillStyle = color;
|
||||
context.fillText(text, x, y);
|
||||
context.fillText(myText, x, y);
|
||||
context.fillStyle = '';
|
||||
context.strokeStyle = '';
|
||||
context.shadowOffsetX = 0;
|
||||
@@ -98,4 +98,4 @@ const draw = (() => {
|
||||
sideColor1,
|
||||
sideColor2,
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class ExtendedForecast extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId,'Extended Forecast');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Extended Forecast');
|
||||
|
||||
// set timings
|
||||
this.timing.totalScreens = 2;
|
||||
@@ -15,17 +15,16 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
this.backgroundImage = utils.image.load('images/BackGround2_1.png');
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// request us or si units
|
||||
let units = 'us';
|
||||
if (navigation.units() === UNITS.metric) units = 'si';
|
||||
let forecast;
|
||||
try {
|
||||
forecast = await utils.fetch.json(weatherParameters.forecast,{
|
||||
forecast = await utils.fetch.json(weatherParameters.forecast, {
|
||||
data: {
|
||||
units,
|
||||
},
|
||||
@@ -37,32 +36,36 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
return;
|
||||
}
|
||||
// we only get here if there was no error above
|
||||
this.data = this.parseExtendedForecast(forecast.properties.periods);
|
||||
this.data = ExtendedForecast.parse(forecast.properties.periods);
|
||||
this.screenIndex = 0;
|
||||
this.setStatus(STATUS.loaded);
|
||||
}
|
||||
|
||||
// the api provides the forecast in 12 hour increments, flatten to day increments with high and low temperatures
|
||||
parseExtendedForecast(fullForecast) {
|
||||
static parse(fullForecast) {
|
||||
// create a list of days starting with today
|
||||
const _Days = [0, 1, 2, 3, 4, 5, 6];
|
||||
const Days = [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
const dates = _Days.map(shift => {
|
||||
const date = luxon.DateTime.local().startOf('day').plus({days:shift});
|
||||
return date.toLocaleString({weekday: 'short'});
|
||||
const dates = Days.map((shift) => {
|
||||
const date = luxon.DateTime.local().startOf('day').plus({ days: shift });
|
||||
return date.toLocaleString({ weekday: 'short' });
|
||||
});
|
||||
|
||||
// track the destination forecast index
|
||||
let destIndex = 0;
|
||||
const forecast = [];
|
||||
fullForecast.forEach(period => {
|
||||
fullForecast.forEach((period) => {
|
||||
// create the destination object if necessary
|
||||
if (!forecast[destIndex]) forecast.push({dayName:'', low: undefined, high: undefined, text: undefined, icon: undefined});
|
||||
if (!forecast[destIndex]) {
|
||||
forecast.push({
|
||||
dayName: '', low: undefined, high: undefined, text: undefined, icon: undefined,
|
||||
});
|
||||
}
|
||||
// get the object to modify/populate
|
||||
const fDay = forecast[destIndex];
|
||||
// high temperature will always be last in the source array so it will overwrite the low values assigned below
|
||||
fDay.icon = icons.getWeatherIconFromIconLink(period.icon);
|
||||
fDay.text = this.shortenExtendedForecastText(period.shortForecast);
|
||||
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
|
||||
fDay.dayName = dates[destIndex];
|
||||
|
||||
// preload the icon
|
||||
@@ -71,7 +74,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
if (period.isDaytime) {
|
||||
// day time is the high temperature
|
||||
fDay.high = period.temperature;
|
||||
destIndex++;
|
||||
destIndex += 1;
|
||||
} else {
|
||||
// low temperature
|
||||
fDay.low = period.temperature;
|
||||
@@ -81,7 +84,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
return forecast;
|
||||
}
|
||||
|
||||
shortenExtendedForecastText(long) {
|
||||
static shortenExtendedForecastText(long) {
|
||||
let short = long;
|
||||
short = short.replace(/ and /g, ' ');
|
||||
short = short.replace(/Slight /g, '');
|
||||
@@ -112,7 +115,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
}
|
||||
short = short1;
|
||||
if (short2 !== '') {
|
||||
short += ' ' + short2;
|
||||
short += ` ${short2}`;
|
||||
}
|
||||
|
||||
return [short, short1, short2];
|
||||
@@ -123,7 +126,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
|
||||
// determine bounds
|
||||
// grab the first three or second set of three array elements
|
||||
const forecast = this.data.slice(0+3*this.screenIndex, 3+this.screenIndex*3);
|
||||
const forecast = this.data.slice(0 + 3 * this.screenIndex, 3 + this.screenIndex * 3);
|
||||
|
||||
const backgroundImage = await this.backgroundImage;
|
||||
|
||||
@@ -138,27 +141,27 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
draw.titleText(this.context, 'Extended', 'Forecast');
|
||||
|
||||
await Promise.all(forecast.map(async (Day, Index) => {
|
||||
const offset = Index*195;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 100+offset, 135, Day.dayName.toUpperCase(), 2);
|
||||
draw.text(this.context, 'Star4000', '24pt', '#8080FF', 85+offset, 345, 'Lo', 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165+offset, 345, 'Hi', 2, 'center');
|
||||
let low = Day.low;
|
||||
const offset = Index * 195;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 100 + offset, 135, Day.dayName.toUpperCase(), 2);
|
||||
draw.text(this.context, 'Star4000', '24pt', '#8080FF', 85 + offset, 345, 'Lo', 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165 + offset, 345, 'Hi', 2, 'center');
|
||||
let { low } = Day;
|
||||
if (low !== undefined) {
|
||||
if (navigation.units() === UNITS.metric) low = utils.units.rahrenheitToCelsius(low);
|
||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 85+offset, 385, low, 2, 'center');
|
||||
if (navigation.units() === UNITS.metric) low = utils.units.fahrenheitToCelsius(low);
|
||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 85 + offset, 385, low, 2, 'center');
|
||||
}
|
||||
let high = Day.high;
|
||||
if (navigation.units() === UNITS.metric) high = utils.units.rahrenheitToCelsius(high);
|
||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 165+offset, 385, high, 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+offset, 270, Day.text[1], 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+offset, 310, Day.text[2], 2, 'center');
|
||||
let { high } = Day;
|
||||
if (navigation.units() === UNITS.metric) high = utils.units.fahrenheitToCelsius(high);
|
||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 165 + offset, 385, high, 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 270, Day.text[1], 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 310, Day.text[2], 2, 'center');
|
||||
|
||||
// draw the icon
|
||||
this.gifs.push(await utils.image.superGifAsync({
|
||||
src: Day.icon,
|
||||
auto_play: true,
|
||||
canvas: this.canvas,
|
||||
x: 70 + Index*195,
|
||||
x: 70 + Index * 195,
|
||||
y: 150,
|
||||
max_height: 75,
|
||||
}));
|
||||
@@ -166,4 +169,4 @@ class ExtendedForecast extends WeatherDisplay {
|
||||
|
||||
this.finishDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ class Hourly extends WeatherDisplay {
|
||||
this.timing.baseDelay = 20;
|
||||
// 24 hours = 6 pages
|
||||
const pages = 4; // first page is already displayed, last page doesn't happen
|
||||
const timingStep = this.hourHeight*4;
|
||||
this.timing.delay = [150+timingStep];
|
||||
const timingStep = this.hourHeight * 4;
|
||||
this.timing.delay = [150 + timingStep];
|
||||
// add additional pages
|
||||
for (let i = 0; i < pages; i++) this.timing.delay.push(timingStep);
|
||||
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||
// add the final 3 second delay
|
||||
this.timing.delay.push(150);
|
||||
}
|
||||
@@ -37,34 +37,36 @@ class Hourly extends WeatherDisplay {
|
||||
this.setStatus(STATUS.failed);
|
||||
}
|
||||
|
||||
this.data = await this.parseForecast(forecast.properties);
|
||||
this.data = await Hourly.parseForecast(forecast.properties);
|
||||
|
||||
this.setStatus(STATUS.loaded);
|
||||
this.drawLongCanvas();
|
||||
}
|
||||
|
||||
// extract specific values from forecast and format as an array
|
||||
async parseForecast(data) {
|
||||
const temperature = this.expand(data.temperature.values);
|
||||
const apparentTemperature = this.expand(data.apparentTemperature.values);
|
||||
const windSpeed = this.expand(data.windSpeed.values);
|
||||
const windDirection = this.expand(data.windDirection.values);
|
||||
const skyCover = this.expand(data.skyCover.values); // cloud icon
|
||||
const weather = this.expand(data.weather.values); // fog icon
|
||||
const iceAccumulation = this.expand(data.iceAccumulation.values); // ice icon
|
||||
const probabilityOfPrecipitation = this.expand(data.probabilityOfPrecipitation.values); // rain icon
|
||||
const snowfallAmount = this.expand(data.snowfallAmount.values); // snow icon
|
||||
static async parseForecast(data) {
|
||||
const temperature = Hourly.expand(data.temperature.values);
|
||||
const apparentTemperature = Hourly.expand(data.apparentTemperature.values);
|
||||
const windSpeed = Hourly.expand(data.windSpeed.values);
|
||||
const windDirection = Hourly.expand(data.windDirection.values);
|
||||
const skyCover = Hourly.expand(data.skyCover.values); // cloud icon
|
||||
const weather = Hourly.expand(data.weather.values); // fog icon
|
||||
const iceAccumulation = Hourly.expand(data.iceAccumulation.values); // ice icon
|
||||
const probabilityOfPrecipitation = Hourly.expand(data.probabilityOfPrecipitation.values); // rain icon
|
||||
const snowfallAmount = Hourly.expand(data.snowfallAmount.values); // snow icon
|
||||
|
||||
const icons = await this.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
|
||||
const icons = await Hourly.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
|
||||
|
||||
return temperature.map((val, idx) => {
|
||||
if (navigation.units === UNITS.metric) return {
|
||||
temperature: temperature[idx],
|
||||
apparentTemperature: apparentTemperature[idx],
|
||||
windSpeed: windSpeed[idx],
|
||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||
icon: icons[idx],
|
||||
};
|
||||
if (navigation.units === UNITS.metric) {
|
||||
return {
|
||||
temperature: temperature[idx],
|
||||
apparentTemperature: apparentTemperature[idx],
|
||||
windSpeed: windSpeed[idx],
|
||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||
icon: icons[idx],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
temperature: utils.units.celsiusToFahrenheit(temperature[idx]),
|
||||
@@ -73,30 +75,29 @@ class Hourly extends WeatherDisplay {
|
||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||
icon: icons[idx],
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// given forecast paramaters determine a suitable icon
|
||||
async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
|
||||
static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
|
||||
const startOfHour = luxon.DateTime.local().startOf('hour');
|
||||
const sunTimes = (await navigation.getSun()).sun;
|
||||
const overnight = luxon.Interval.fromDateTimes(luxon.DateTime.fromJSDate(sunTimes[0].sunset), luxon.DateTime.fromJSDate(sunTimes[1].sunrise));
|
||||
const tomorrowOvernight = luxon.DateTime.fromJSDate(sunTimes[1].sunset);
|
||||
return skyCover.map((val, idx) => {
|
||||
const hour = startOfHour.plus({hours: idx});
|
||||
const hour = startOfHour.plus({ hours: idx });
|
||||
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
||||
return icons.getHourlyIcon(skyCover[idx], weather[idx], iceAccumulation[idx], probabilityOfPrecipitation[idx], snowfallAmount[idx], windSpeed[idx], isNight);
|
||||
});
|
||||
}
|
||||
|
||||
// expand a set of values with durations to an hour-by-hour array
|
||||
expand(data) {
|
||||
static expand(data) {
|
||||
const startOfHour = luxon.DateTime.utc().startOf('hour').toMillis();
|
||||
const result = []; // resulting expanded values
|
||||
data.forEach(item => {
|
||||
data.forEach((item) => {
|
||||
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
|
||||
const duration = luxon.Duration.fromISO(item.validTime.substr(item.validTime.indexOf('/')+1)).shiftTo('milliseconds').values.milliseconds;
|
||||
const duration = luxon.Duration.fromISO(item.validTime.substr(item.validTime.indexOf('/') + 1)).shiftTo('milliseconds').values.milliseconds;
|
||||
const endTime = startTime + duration;
|
||||
// loop through duration at one hour intervals
|
||||
do {
|
||||
@@ -105,39 +106,39 @@ class Hourly extends WeatherDisplay {
|
||||
result.push(item.value); // push data array
|
||||
} // timestamp is after now
|
||||
// increment start time by 1 hour
|
||||
startTime = startTime + 3600000;
|
||||
startTime += 3600000;
|
||||
} while (startTime < endTime && result.length < 24);
|
||||
}); // for each value
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async drawLongCanvas () {
|
||||
async drawLongCanvas() {
|
||||
// create the "long" canvas if necessary
|
||||
if (!this.longCanvas) {
|
||||
this.longCanvas = document.createElement('canvas');
|
||||
this.longCanvas.width = 640;
|
||||
this.longCanvas.height = 24*this.hourHeight;
|
||||
this.longCanvas.height = 24 * this.hourHeight;
|
||||
this.longContext = this.longCanvas.getContext('2d');
|
||||
this.longCanvasGifs = [];
|
||||
}
|
||||
|
||||
// stop all gifs
|
||||
this.longCanvasGifs.forEach(gif => gif.pause());
|
||||
this.longCanvasGifs.forEach((gif) => gif.pause());
|
||||
// delete the gifs
|
||||
this.longCanvasGifs.length = 0;
|
||||
|
||||
// clean up existing gifs
|
||||
this.gifs.forEach(gif => gif.pause());
|
||||
this.gifs.forEach((gif) => gif.pause());
|
||||
// delete the gifs
|
||||
this.gifs.length = 0;
|
||||
|
||||
this.longContext.clearRect(0,0,this.longCanvas.width,this.longCanvas.height);
|
||||
this.longContext.clearRect(0, 0, this.longCanvas.width, this.longCanvas.height);
|
||||
|
||||
// draw the "long" canvas with all cities
|
||||
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, 24*this.hourHeight);
|
||||
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, 24 * this.hourHeight);
|
||||
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
for (let i = 0; i <= 4; i += 1) {
|
||||
const y = i * 346;
|
||||
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
||||
}
|
||||
@@ -146,14 +147,13 @@ class Hourly extends WeatherDisplay {
|
||||
|
||||
await Promise.all(this.data.map(async (data, index) => {
|
||||
// calculate base y value
|
||||
const y = 50+this.hourHeight*index;
|
||||
const y = 50 + this.hourHeight * index;
|
||||
|
||||
// hour
|
||||
const hour = startingHour.plus({hours: index});
|
||||
const formattedHour = hour.toLocaleString({weekday: 'short', hour: 'numeric'});
|
||||
const hour = startingHour.plus({ hours: index });
|
||||
const formattedHour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' });
|
||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, formattedHour, 2);
|
||||
|
||||
|
||||
// temperatures, convert to strings with no decimal
|
||||
const temperature = Math.round(data.temperature).toString().padStart(3);
|
||||
const feelsLike = Math.round(data.apparentTemperature).toString().padStart(3);
|
||||
@@ -177,8 +177,6 @@ class Hourly extends WeatherDisplay {
|
||||
y: y - 35,
|
||||
max_width: 47,
|
||||
}));
|
||||
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -221,7 +219,7 @@ class Hourly extends WeatherDisplay {
|
||||
const longCanvas = this.getLongCanvas();
|
||||
|
||||
// calculate scroll offset and don't go past end
|
||||
let offsetY = Math.min(longCanvas.height-289, (count-150));
|
||||
let offsetY = Math.min(longCanvas.height - 289, (count - 150));
|
||||
|
||||
// don't let offset go negative
|
||||
if (offsetY < 0) offsetY = 0;
|
||||
@@ -230,15 +228,15 @@ class Hourly extends WeatherDisplay {
|
||||
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
|
||||
}
|
||||
|
||||
getTravelCitiesDayName(cities) {
|
||||
const {DateTime} = luxon;
|
||||
static getTravelCitiesDayName(cities) {
|
||||
const { DateTime } = luxon;
|
||||
// effectively returns early on the first found date
|
||||
return cities.reduce((dayName, city) => {
|
||||
if (city && dayName === '') {
|
||||
// today or tomorrow
|
||||
const day = DateTime.local().plus({days: (city.today)?0:1});
|
||||
const day = DateTime.local().plus({ days: (city.today) ? 0 : 1 });
|
||||
// return the day
|
||||
return day.toLocaleString({weekday: 'long'});
|
||||
return day.toLocaleString({ weekday: 'long' });
|
||||
}
|
||||
return dayName;
|
||||
}, '');
|
||||
@@ -248,4 +246,4 @@ class Hourly extends WeatherDisplay {
|
||||
getLongCanvas() {
|
||||
return this.longCanvas;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
'use strict';
|
||||
/* spell-checker: disable */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const icons = (() => {
|
||||
|
||||
const getWeatherRegionalIconFromIconLink = (link, isNightTime) => {
|
||||
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
|
||||
// extract day or night if not provided
|
||||
if (isNightTime === undefined) isNightTime = link.indexOf('/night/') >=0;
|
||||
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
|
||||
// internal function to add path to returned icon
|
||||
const addPath = (icon) => `images/r/${icon}`;
|
||||
|
||||
@@ -16,12 +14,11 @@ const icons = (() => {
|
||||
// if a 'DualImage' is captured, adjust to just the j parameter
|
||||
if (conditionName === 'dualimage') {
|
||||
const match = link.match(/&j=(.*)&/);
|
||||
conditionName = match[1];
|
||||
[, conditionName] = match;
|
||||
}
|
||||
|
||||
|
||||
// find the icon
|
||||
switch (conditionName + (isNightTime?'-n':'')) {
|
||||
switch (conditionName + (isNightTime ? '-n' : '')) {
|
||||
case 'skc':
|
||||
case 'hot':
|
||||
case 'haze':
|
||||
@@ -132,11 +129,11 @@ const icons = (() => {
|
||||
}
|
||||
};
|
||||
|
||||
const getWeatherIconFromIconLink = function (link, isNightTime = false) {
|
||||
const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
||||
// internal function to add path to returned icon
|
||||
const addPath = (icon) => `images/${icon}`;
|
||||
// extract day or night if not provided
|
||||
if (isNightTime === undefined) isNightTime = link.indexOf('/night/') >=0;
|
||||
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
|
||||
|
||||
// grab everything after the last slash ending at any of these: ?&,
|
||||
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
||||
@@ -145,12 +142,11 @@ const icons = (() => {
|
||||
// if a 'DualImage' is captured, adjust to just the j parameter
|
||||
if (conditionName === 'dualimage') {
|
||||
const match = link.match(/&j=(.*)&/);
|
||||
conditionName = match[1];
|
||||
[, conditionName] = match;
|
||||
}
|
||||
|
||||
|
||||
// find the icon
|
||||
switch (conditionName + (isNightTime?'-n':'')) {
|
||||
switch (conditionName + (isNightTime ? '-n' : '')) {
|
||||
case 'skc':
|
||||
case 'hot':
|
||||
case 'haze':
|
||||
@@ -262,12 +258,11 @@ const icons = (() => {
|
||||
let thunder = false;
|
||||
let snow = false;
|
||||
let ice = false;
|
||||
let fog = false;
|
||||
let fog = false;
|
||||
let wind = false;
|
||||
|
||||
// test the phenomenon for various value if it is provided.
|
||||
weather.forEach(phenomenon => {
|
||||
console.log(phenomenon.weather);
|
||||
weather.forEach((phenomenon) => {
|
||||
if (!phenomenon.weather) return;
|
||||
if (phenomenon.weather.toLowerCase().includes('thunder')) thunder = true;
|
||||
if (phenomenon.weather.toLowerCase().includes('snow')) snow = true;
|
||||
@@ -284,7 +279,7 @@ const icons = (() => {
|
||||
}
|
||||
if ((snowfallAmount > 0 || snow) && thunder) return addPath('ThunderSnow.gif');
|
||||
if (snowfallAmount > 0 || snow) return addPath('Light-Snow.gif');
|
||||
if (thunder) return(addPath('Thunderstorm.gif'));
|
||||
if (thunder) return (addPath('Thunderstorm.gif'));
|
||||
if (probabilityOfPrecipitation > 70) return addPath('Rain-1992.gif');
|
||||
if (probabilityOfPrecipitation > 50) return addPath('Shower.gif');
|
||||
if (probabilityOfPrecipitation > 30) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// current weather conditions display
|
||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, _StationInfo */
|
||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, StationInfo */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class LatestObservations extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId,'Latest Observations');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Latest Observations');
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||
|
||||
@@ -12,44 +12,45 @@ class LatestObservations extends WeatherDisplay {
|
||||
this.MaximumRegionalStations = 7;
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// calculate distance to each station
|
||||
const stationsByDistance = Object.keys(_StationInfo).map(key => {
|
||||
const station = _StationInfo[key];
|
||||
const stationsByDistance = Object.keys(StationInfo).map((key) => {
|
||||
const station = StationInfo[key];
|
||||
const distance = utils.calc.distance(station.lat, station.lon, weatherParameters.latitude, weatherParameters.longitude);
|
||||
return Object.assign({}, station, {distance});
|
||||
return { ...station, distance };
|
||||
});
|
||||
|
||||
// sort the stations by distance
|
||||
const sortedStations = stationsByDistance.sort((a,b) => a.distance - b.distance);
|
||||
const sortedStations = stationsByDistance.sort((a, b) => a.distance - b.distance);
|
||||
// try up to 30 regional stations
|
||||
const regionalStations = sortedStations.slice(0,30);
|
||||
const regionalStations = sortedStations.slice(0, 30);
|
||||
|
||||
// get data for regional stations
|
||||
const allConditions = await Promise.all(regionalStations.map(async station => {
|
||||
const allConditions = await Promise.all(regionalStations.map(async (station) => {
|
||||
try {
|
||||
const data = await utils.fetch.json(`https://api.weather.gov/stations/${station.id}/observations/latest`);
|
||||
// test for temperature, weather and wind values present
|
||||
if (data.properties.temperature.value === null ||
|
||||
data.properties.textDescription === '' ||
|
||||
data.properties.windSpeed.value === null) return;
|
||||
if (data.properties.temperature.value === null
|
||||
|| data.properties.textDescription === ''
|
||||
|| data.properties.windSpeed.value === null) return false;
|
||||
// format the return values
|
||||
return Object.assign({}, data.properties, {
|
||||
return {
|
||||
...data.properties,
|
||||
StationId: station.id,
|
||||
city: station.city,
|
||||
});
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(`Unable to get latest observations for ${station.id}`);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
// remove and stations that did not return data
|
||||
const actualConditions = allConditions.filter(condition => condition);
|
||||
const actualConditions = allConditions.filter((condition) => condition);
|
||||
// cut down to the maximum of 7
|
||||
this.data = actualConditions.slice(0,this.MaximumRegionalStations);
|
||||
this.data = actualConditions.slice(0, this.MaximumRegionalStations);
|
||||
|
||||
// test for at least one station
|
||||
if (this.data.length < 1) {
|
||||
@@ -64,7 +65,7 @@ class LatestObservations extends WeatherDisplay {
|
||||
const conditions = this.data;
|
||||
|
||||
// sort array by station name
|
||||
const sortedConditions = conditions.sort((a,b) => ((a.Name < b.Name) ? -1 : ((a.Name > b.Name) ? 1 : 0)));
|
||||
const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1));
|
||||
|
||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
||||
@@ -75,9 +76,9 @@ class LatestObservations extends WeatherDisplay {
|
||||
draw.titleText(this.context, 'Latest', 'Observations');
|
||||
|
||||
if (navigation.units() === UNITS.english) {
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, String.fromCharCode(176) + 'F', 2);
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, `${String.fromCharCode(176)}F`, 2);
|
||||
} else {
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, String.fromCharCode(176) + 'C', 2);
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, `${String.fromCharCode(176)}C`, 2);
|
||||
}
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 345, 105, 'WEATHER', 2);
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 495, 105, 'WIND', 2);
|
||||
@@ -95,7 +96,7 @@ class LatestObservations extends WeatherDisplay {
|
||||
}
|
||||
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 65, y, condition.city.substr(0, 14), 2);
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 345, y, this.shortenCurrentConditions(condition.textDescription).substr(0, 9), 2);
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 345, y, LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9), 2);
|
||||
|
||||
if (WindSpeed > 0) {
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString(), 2);
|
||||
@@ -113,7 +114,8 @@ class LatestObservations extends WeatherDisplay {
|
||||
this.finishDraw();
|
||||
}
|
||||
|
||||
shortenCurrentConditions(condition) {
|
||||
static shortenCurrentConditions(_condition) {
|
||||
let condition = _condition;
|
||||
condition = condition.replace(/Light/, 'L');
|
||||
condition = condition.replace(/Heavy/, 'H');
|
||||
condition = condition.replace(/Partly/, 'P');
|
||||
@@ -130,4 +132,4 @@ class LatestObservations extends WeatherDisplay {
|
||||
condition = condition.replace(/ with /, '/');
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
// display text based local forecast
|
||||
|
||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation*/
|
||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class LocalForecast extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId, 'Local Forecast');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Forecast');
|
||||
|
||||
// set timings
|
||||
this.timing.baseDelay= 5000;
|
||||
this.timing.baseDelay = 5000;
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// get raw data
|
||||
const rawData = await this.getRawData(weatherParameters);
|
||||
@@ -27,7 +26,7 @@ class LocalForecast extends WeatherDisplay {
|
||||
return;
|
||||
}
|
||||
// parse raw data
|
||||
const conditions = this.parseLocalForecast(rawData);
|
||||
const conditions = LocalForecast.parse(rawData);
|
||||
|
||||
// split this forecast into the correct number of screens
|
||||
const maxRows = 7;
|
||||
@@ -36,9 +35,9 @@ class LocalForecast extends WeatherDisplay {
|
||||
this.screenTexts = [];
|
||||
|
||||
// read each text
|
||||
conditions.forEach(condition => {
|
||||
conditions.forEach((condition) => {
|
||||
// process the text
|
||||
let text = condition.DayName.toUpperCase() + '...';
|
||||
let text = `${condition.DayName.toUpperCase()}...`;
|
||||
let conditionText = condition.Text;
|
||||
if (navigation.units() === UNITS.metric) {
|
||||
conditionText = condition.TextC;
|
||||
@@ -52,28 +51,27 @@ class LocalForecast extends WeatherDisplay {
|
||||
const maxRowCount = maxRows;
|
||||
let rowCount = 0;
|
||||
|
||||
|
||||
// if (PrependAlert) {
|
||||
// ScreenText = LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1];
|
||||
// rowCount = ScreenText.split('\n').length - 1;
|
||||
// }
|
||||
|
||||
for (let i = 0; i <= lineCount - 1; i++) {
|
||||
if (lines[i] === '') continue;
|
||||
|
||||
if (rowCount > maxRowCount - 1) {
|
||||
for (let i = 0; i <= lineCount - 1; i += 1) {
|
||||
if (lines[i] !== '') {
|
||||
if (rowCount > maxRowCount - 1) {
|
||||
// if (PrependAlert) {
|
||||
// LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1] = ScreenText;
|
||||
// PrependAlert = false;
|
||||
// } else {
|
||||
this.screenTexts.push(ScreenText);
|
||||
// }
|
||||
ScreenText = '';
|
||||
rowCount = 0;
|
||||
}
|
||||
this.screenTexts.push(ScreenText);
|
||||
// }
|
||||
ScreenText = '';
|
||||
rowCount = 0;
|
||||
}
|
||||
|
||||
ScreenText += lines[i] + '\n';
|
||||
rowCount++;
|
||||
ScreenText += `${lines[i]}\n`;
|
||||
rowCount += 1;
|
||||
}
|
||||
}
|
||||
// if (PrependAlert) {
|
||||
// this.screenTexts[this.screenTexts.length - 1] = ScreenText;
|
||||
@@ -99,7 +97,6 @@ class LocalForecast extends WeatherDisplay {
|
||||
units,
|
||||
},
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`);
|
||||
console.error(e.status, e.responseJSON);
|
||||
@@ -125,18 +122,18 @@ class LocalForecast extends WeatherDisplay {
|
||||
draw.box(this.context, 'rgb(33, 40, 90)', 65, 105, 505, 280);
|
||||
// Draw the text.
|
||||
this.screenTexts[this.screenIndex].split('\n').forEach((text, index) => {
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 75, 140+40*index, text, 2);
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 75, 140 + 40 * index, text, 2);
|
||||
});
|
||||
this.finishDraw();
|
||||
}
|
||||
|
||||
// format the forecast
|
||||
parseLocalForecast (forecast) {
|
||||
static parse(forecast) {
|
||||
// only use the first 6 lines
|
||||
return forecast.properties.periods.slice(0,6).map(text => ({
|
||||
return forecast.properties.periods.slice(0, 6).map((text) => ({
|
||||
// format day and text
|
||||
DayName: text.name.toUpperCase(),
|
||||
Text: text.detailedForecast,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
// navigation handles progress, next/previous and initial load messages from the parent frame
|
||||
/* globals index, utils, _StationInfo, STATUS */
|
||||
/* globals index, utils, StationInfo, STATUS */
|
||||
/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac, Radar, Progress, Hourly */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -13,11 +12,11 @@ const UNITS = {
|
||||
};
|
||||
|
||||
const navigation = (() => {
|
||||
let weatherParameters = {};
|
||||
let displays = [];
|
||||
let currentUnits = UNITS.english;
|
||||
let playing = false;
|
||||
let progress;
|
||||
const weatherParameters = {};
|
||||
|
||||
// current conditions and sunrise/sunset are made available from the display below
|
||||
let currentWeather;
|
||||
@@ -46,11 +45,10 @@ const navigation = (() => {
|
||||
default:
|
||||
console.error(`Unknown event ${data.type}`);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const postMessage = (type, message = {}) => {
|
||||
index.message({type, message});
|
||||
const postMessage = (type, myMessage = {}) => {
|
||||
index.message({ type, message: myMessage });
|
||||
};
|
||||
|
||||
const getWeather = async (latLon) => {
|
||||
@@ -62,11 +60,11 @@ const navigation = (() => {
|
||||
|
||||
const StationId = stations.features[0].properties.stationIdentifier;
|
||||
|
||||
let city = point.properties.relativeLocation.properties.city;
|
||||
let { city } = point.properties.relativeLocation.properties;
|
||||
|
||||
if (StationId in _StationInfo) {
|
||||
city = _StationInfo[StationId].city;
|
||||
city = city.split('/')[0];
|
||||
if (StationId in StationInfo) {
|
||||
city = StationInfo[StationId].city;
|
||||
[city] = city.split('/');
|
||||
}
|
||||
|
||||
// populate the weather parameters
|
||||
@@ -89,13 +87,13 @@ const navigation = (() => {
|
||||
// draw the progress canvas and hide others
|
||||
hideAllCanvases();
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
progress = new Progress(-1,'progress');
|
||||
progress = new Progress(-1, 'progress');
|
||||
await progress.drawCanvas();
|
||||
progress.showCanvas();
|
||||
|
||||
// start loading canvases if necessary
|
||||
if (displays.length === 0) {
|
||||
currentWeather = new CurrentWeather(0,'currentWeather');
|
||||
currentWeather = new CurrentWeather(0, 'currentWeather');
|
||||
almanac = new Almanac(7, 'almanac');
|
||||
displays = [
|
||||
currentWeather,
|
||||
@@ -110,8 +108,7 @@ const navigation = (() => {
|
||||
];
|
||||
}
|
||||
// call for new data on each display
|
||||
displays.forEach(display => display.getData(weatherParameters));
|
||||
|
||||
displays.forEach((display) => display.getData(weatherParameters));
|
||||
};
|
||||
|
||||
// receive a status update from a module {id, value}
|
||||
@@ -130,12 +127,12 @@ const navigation = (() => {
|
||||
};
|
||||
|
||||
const countLoadedCanvases = () => displays.reduce((acc, display) => {
|
||||
if (display.status !== STATUS.loading) return acc+1;
|
||||
if (display.status !== STATUS.loading) return acc + 1;
|
||||
return acc;
|
||||
},0);
|
||||
}, 0);
|
||||
|
||||
const hideAllCanvases = () => {
|
||||
displays.forEach(display => display.hideCanvas());
|
||||
displays.forEach((display) => display.hideCanvas());
|
||||
};
|
||||
|
||||
const units = () => currentUnits;
|
||||
@@ -168,9 +165,9 @@ const navigation = (() => {
|
||||
};
|
||||
|
||||
// receive navigation messages from displays
|
||||
const displayNavMessage = (message) => {
|
||||
if (message.type === msg.response.previous) loadDisplay(-1);
|
||||
if (message.type === msg.response.next) loadDisplay(1);
|
||||
const displayNavMessage = (myMessage) => {
|
||||
if (myMessage.type === msg.response.previous) loadDisplay(-1);
|
||||
if (myMessage.type === msg.response.next) loadDisplay(1);
|
||||
};
|
||||
|
||||
// navigate to next or previous
|
||||
@@ -192,9 +189,9 @@ const navigation = (() => {
|
||||
const totalDisplays = displays.length;
|
||||
const curIdx = currentDisplayIndex();
|
||||
let idx;
|
||||
for (let i = 0; i < totalDisplays; i++) {
|
||||
for (let i = 0; i < totalDisplays; i += 1) {
|
||||
// convert form simple 0-10 to start at current display index +/-1 and wrap
|
||||
idx = utils.calc.wrap(curIdx+(i+1)*direction,totalDisplays);
|
||||
idx = utils.calc.wrap(curIdx + (i + 1) * direction, totalDisplays);
|
||||
if (displays[idx].status === STATUS.loaded) break;
|
||||
}
|
||||
const newDisplay = displays[idx];
|
||||
@@ -207,12 +204,10 @@ const navigation = (() => {
|
||||
|
||||
// get the current display index or value
|
||||
const currentDisplayIndex = () => {
|
||||
let index = displays.findIndex(display=>display.isActive());
|
||||
const index = displays.findIndex((display) => display.isActive());
|
||||
return index;
|
||||
};
|
||||
const currentDisplay = () => {
|
||||
return displays[currentDisplayIndex()];
|
||||
};
|
||||
const currentDisplay = () => displays[currentDisplayIndex()];
|
||||
|
||||
const setPlaying = (newValue) => {
|
||||
playing = newValue;
|
||||
@@ -279,4 +274,4 @@ const navigation = (() => {
|
||||
getCurrentWeather,
|
||||
getSun,
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class Progress extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId);
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
@@ -34,18 +34,17 @@ class Progress extends WeatherDisplay {
|
||||
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
||||
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.titleText(this.context, 'WeatherStar', '4000+ ' + this.version);
|
||||
draw.titleText(this.context, 'WeatherStar', `4000+ ${this.version}`);
|
||||
}
|
||||
|
||||
this.finishDraw();
|
||||
// if no displays provided just draw the backgrounds (above)
|
||||
if (!displays) return;
|
||||
displays.forEach((display, idx) => {
|
||||
const y = 120 + idx*29;
|
||||
const y = 120 + idx * 29;
|
||||
const dots = Array(120 - Math.floor(display.name.length * 2.5)).join('.');
|
||||
draw.text(this.context, 'Star4000 Extended', '19pt', '#ffffff', 70, y, display.name + dots, 2);
|
||||
|
||||
|
||||
let statusText;
|
||||
let statusColor;
|
||||
switch (display.status) {
|
||||
@@ -77,24 +76,21 @@ class Progress extends WeatherDisplay {
|
||||
// Erase any dots that spill into the status text.
|
||||
this.context.drawImage(backgroundImage, 475, y - 20, 165, 30, 475, y - 20, 165, 30);
|
||||
draw.text(this.context, 'Star4000 Extended', '19pt', statusColor, 565, y, statusText, 2, 'end');
|
||||
|
||||
});
|
||||
|
||||
|
||||
// calculate loaded percent
|
||||
const loadedPercent = (loadedCount/displays.length);
|
||||
const loadedPercent = (loadedCount / displays.length);
|
||||
|
||||
if (loadedPercent < 1.0) {
|
||||
// Draw a box for the progress.
|
||||
draw.box(this.context, '#000000', 51, 428, 534, 22);
|
||||
draw.box(this.context, '#ffffff', 53, 430, 530, 18);
|
||||
// update the progress gif
|
||||
draw.box(this.context, '#1d7fff', 55, 432, 526*loadedPercent, 14);
|
||||
draw.box(this.context, '#1d7fff', 55, 432, 526 * loadedPercent, 14);
|
||||
} else {
|
||||
// restore the background
|
||||
this.context.drawImage(backgroundImage, 51, 428, 534, 22, 51, 428, 534, 22);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
canvasClick(e) {
|
||||
@@ -106,13 +102,13 @@ class Progress extends WeatherDisplay {
|
||||
if (x < 440 || x > 570) return;
|
||||
|
||||
// stop playing
|
||||
navigation.message('navButton', stop);
|
||||
navigation.message('navButton');
|
||||
// use the y value to determine an index
|
||||
const index = Math.floor((y-100)/29);
|
||||
const index = Math.floor((y - 100) / 29);
|
||||
const display = navigation.getDisplay(index);
|
||||
if (display && display.status === STATUS.loaded) {
|
||||
display.showCanvas(navigation.msg.command.firstFrame);
|
||||
this.hideCanvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,42 +3,42 @@
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class Radar extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId,'Local Radar');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Radar');
|
||||
|
||||
// set max images
|
||||
this.dopplerRadarImageMax = 6;
|
||||
// update timing
|
||||
this.timing.baseDelay = 350;
|
||||
this.timing.delay = [
|
||||
{time: 4, si: 5},
|
||||
{time: 1, si: 0},
|
||||
{time: 1, si: 1},
|
||||
{time: 1, si: 2},
|
||||
{time: 1, si: 3},
|
||||
{time: 1, si: 4},
|
||||
{time: 4, si: 5},
|
||||
{time: 1, si: 0},
|
||||
{time: 1, si: 1},
|
||||
{time: 1, si: 2},
|
||||
{time: 1, si: 3},
|
||||
{time: 1, si: 4},
|
||||
{time: 4, si: 5},
|
||||
{time: 1, si: 0},
|
||||
{time: 1, si: 1},
|
||||
{time: 1, si: 2},
|
||||
{time: 1, si: 3},
|
||||
{time: 1, si: 4},
|
||||
{time: 12, si: 5},
|
||||
{ time: 4, si: 5 },
|
||||
{ time: 1, si: 0 },
|
||||
{ time: 1, si: 1 },
|
||||
{ time: 1, si: 2 },
|
||||
{ time: 1, si: 3 },
|
||||
{ time: 1, si: 4 },
|
||||
{ time: 4, si: 5 },
|
||||
{ time: 1, si: 0 },
|
||||
{ time: 1, si: 1 },
|
||||
{ time: 1, si: 2 },
|
||||
{ time: 1, si: 3 },
|
||||
{ time: 1, si: 4 },
|
||||
{ time: 4, si: 5 },
|
||||
{ time: 1, si: 0 },
|
||||
{ time: 1, si: 1 },
|
||||
{ time: 1, si: 2 },
|
||||
{ time: 1, si: 3 },
|
||||
{ time: 1, si: 4 },
|
||||
{ time: 12, si: 5 },
|
||||
];
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround4_1.png');
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// ALASKA ISN'T SUPPORTED!
|
||||
if (weatherParameters.state === 'AK') {
|
||||
@@ -47,7 +47,7 @@ class Radar extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// date and time parsing
|
||||
const {DateTime} = luxon;
|
||||
const { DateTime } = luxon;
|
||||
|
||||
// get the base map
|
||||
let src = 'images/4000RadarMap2.jpg';
|
||||
@@ -59,9 +59,9 @@ class Radar extends WeatherDisplay {
|
||||
let radarHtml;
|
||||
try {
|
||||
// get a list of available radars
|
||||
radarHtml = await utils.fetch.text(baseUrl, {cors: true});
|
||||
radarHtml = await utils.fetch.text(baseUrl, { cors: true });
|
||||
} catch (e) {
|
||||
console.log('Unable to get list of radars');
|
||||
console.error('Unable to get list of radars');
|
||||
console.error(e);
|
||||
this.setStatus(STATUS.failed);
|
||||
return;
|
||||
@@ -72,17 +72,17 @@ class Radar extends WeatherDisplay {
|
||||
const xmlDoc = parser.parseFromString(radarHtml, 'text/html');
|
||||
const anchors = xmlDoc.getElementsByTagName('a');
|
||||
const gifs = [];
|
||||
for (let idx in anchors) {
|
||||
gifs.push(anchors[idx].innerHTML);
|
||||
}
|
||||
Object.values(anchors).forEach((a) => {
|
||||
gifs.push(a.innerHTML);
|
||||
});
|
||||
|
||||
// filter for selected urls
|
||||
let filter = /Conus_\d/;
|
||||
if (weatherParameters.state === 'HI') filter = /hawaii_\d/;
|
||||
|
||||
// get the last few images
|
||||
const urlsFull = gifs.filter(gif => gif && gif.match(filter));
|
||||
const urls = urlsFull.slice(-(this.dopplerRadarImageMax-1));
|
||||
const urlsFull = gifs.filter((gif) => gif && gif.match(filter));
|
||||
const urls = urlsFull.slice(-(this.dopplerRadarImageMax - 1));
|
||||
|
||||
// add additional 'latest.gif'
|
||||
if (weatherParameters.state !== 'HI') urls.push('latest_radaronly.gif');
|
||||
@@ -97,13 +97,13 @@ class Radar extends WeatherDisplay {
|
||||
if (weatherParameters.state === 'HI') {
|
||||
width = 600;
|
||||
height = 571;
|
||||
sourceXY = this.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||
sourceXY = Radar.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||
} else {
|
||||
width = 2550;
|
||||
height = 1600;
|
||||
offsetX *= 2;
|
||||
offsetY *= 2;
|
||||
sourceXY = this.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||
sourceXY = Radar.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||
}
|
||||
|
||||
// create working context for manipulation
|
||||
@@ -116,7 +116,7 @@ class Radar extends WeatherDisplay {
|
||||
// calculate radar offsets
|
||||
let radarOffsetX = 117;
|
||||
let radarOffsetY = 60;
|
||||
let radarSourceXY = this.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||
let radarSourceXY = Radar.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||
let radarSourceX = radarSourceXY.x / 2;
|
||||
let radarSourceY = radarSourceXY.y / 2;
|
||||
|
||||
@@ -176,7 +176,7 @@ class Radar extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// get the base map
|
||||
context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, offsetX*2, offsetY*2, 0, 0, 640, 367);
|
||||
context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, 640, 367);
|
||||
|
||||
// crop the radar image
|
||||
const cropCanvas = document.createElement('canvas');
|
||||
@@ -186,10 +186,10 @@ class Radar extends WeatherDisplay {
|
||||
cropContext.imageSmoothingEnabled = false;
|
||||
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
|
||||
// clean the image
|
||||
this.removeDopplerRadarImageNoise(cropContext);
|
||||
Radar.removeDopplerRadarImageNoise(cropContext);
|
||||
|
||||
// merge the radar and map
|
||||
this.mergeDopplerRadarImage(context, cropContext);
|
||||
Radar.mergeDopplerRadarImage(context, cropContext);
|
||||
|
||||
return {
|
||||
canvas,
|
||||
@@ -199,9 +199,9 @@ class Radar extends WeatherDisplay {
|
||||
// set max length
|
||||
this.timing.totalScreens = radarInfo.length;
|
||||
// store the images
|
||||
this.data = radarInfo.map(radar=>radar.canvas);
|
||||
this.data = radarInfo.map((radar) => radar.canvas);
|
||||
|
||||
this.times = radarInfo.map(radar=>radar.time);
|
||||
this.times = radarInfo.map((radar) => radar.time);
|
||||
this.setStatus(STATUS.loaded);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ class Radar extends WeatherDisplay {
|
||||
super.drawCanvas();
|
||||
if (this.screenIndex === -1) return;
|
||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||
const {DateTime} = luxon;
|
||||
const { DateTime } = luxon;
|
||||
// Title
|
||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 60, 'Local', 2);
|
||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 95, 'Radar', 2);
|
||||
@@ -237,7 +237,7 @@ class Radar extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// utility latitude/pixel conversions
|
||||
getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
@@ -265,7 +265,7 @@ class Radar extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
||||
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
const ImgHeight = 571;
|
||||
@@ -292,7 +292,7 @@ class Radar extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getXYFromLatitudeLongitudeDoppler (Latitude, Longitude, OffsetX, OffsetY) {
|
||||
static getXYFromLatitudeLongitudeDoppler(Latitude, Longitude, OffsetX, OffsetY) {
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
const ImgHeight = 3200;
|
||||
@@ -319,7 +319,7 @@ class Radar extends WeatherDisplay {
|
||||
return { x: x * 2, y: y * 2 };
|
||||
}
|
||||
|
||||
removeDopplerRadarImageNoise (RadarContext) {
|
||||
static removeDopplerRadarImageNoise(RadarContext) {
|
||||
const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
|
||||
|
||||
// examine every pixel,
|
||||
@@ -329,7 +329,7 @@ class Radar extends WeatherDisplay {
|
||||
// i + 1 = green
|
||||
// i + 2 = blue
|
||||
// i + 3 = alpha (0 = transparent, 255 = opaque)
|
||||
let [R, G, B, A] = RadarImageData.data.slice(i,i+4);
|
||||
let [R, G, B, A] = RadarImageData.data.slice(i, i + 4);
|
||||
|
||||
// is this pixel the old rgb?
|
||||
if ((R === 1 && G === 159 && B === 244)
|
||||
@@ -402,13 +402,13 @@ class Radar extends WeatherDisplay {
|
||||
RadarContext.putImageData(RadarImageData, 0, 0);
|
||||
}
|
||||
|
||||
mergeDopplerRadarImage (mapContext, radarContext) {
|
||||
var mapImageData = mapContext.getImageData(0, 0, mapContext.canvas.width, mapContext.canvas.height);
|
||||
var radarImageData = radarContext.getImageData(0, 0, radarContext.canvas.width, radarContext.canvas.height);
|
||||
static mergeDopplerRadarImage(mapContext, radarContext) {
|
||||
const mapImageData = mapContext.getImageData(0, 0, mapContext.canvas.width, mapContext.canvas.height);
|
||||
const radarImageData = radarContext.getImageData(0, 0, radarContext.canvas.width, radarContext.canvas.height);
|
||||
|
||||
// examine every pixel,
|
||||
// change any old rgb to the new-rgb
|
||||
for (var i = 0; i < radarImageData.data.length; i += 4) {
|
||||
for (let i = 0; i < radarImageData.data.length; i += 4) {
|
||||
// i + 0 = red
|
||||
// i + 1 = green
|
||||
// i + 2 = blue
|
||||
@@ -430,4 +430,4 @@ class Radar extends WeatherDisplay {
|
||||
|
||||
mapContext.drawImage(radarContext.canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// regional forecast and observations
|
||||
// type 0 = observations, 1 = first forecast, 2 = second forecast
|
||||
|
||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation, luxon, _StationInfo, _RegionalCities */
|
||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation, luxon, StationInfo, RegionalCities */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class RegionalForecast extends WeatherDisplay {
|
||||
constructor(navId,elemId) {
|
||||
super(navId,elemId,'Regional Forecast');
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Regional Forecast');
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround5_1.png');
|
||||
@@ -15,9 +15,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||
this.timing.totalScreens = 3;
|
||||
}
|
||||
|
||||
async getData(weatherParameters) {
|
||||
super.getData(weatherParameters);
|
||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// pre-load the base map (returns promise)
|
||||
let src = 'images/Basemap2.png';
|
||||
@@ -44,35 +44,35 @@ class RegionalForecast extends WeatherDisplay {
|
||||
if (weatherParameters.state === 'HI') targetDistance = 1;
|
||||
|
||||
// make station info into an array
|
||||
const stationInfoArray = Object.keys(_StationInfo).map(key => Object.assign({}, _StationInfo[key], {targetDistance}));
|
||||
const stationInfoArray = Object.values(StationInfo).map((value) => ({ ...value, targetDistance }));
|
||||
// combine regional cities with station info for additional stations
|
||||
// stations are intentionally after cities to allow cities priority when drawing the map
|
||||
const combinedCities = [..._RegionalCities, ...stationInfoArray];
|
||||
const combinedCities = [...RegionalCities, ...stationInfoArray];
|
||||
|
||||
// Determine which cities are within the max/min latitude/longitude.
|
||||
const regionalCities = [];
|
||||
combinedCities.forEach(city => {
|
||||
if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat &&
|
||||
city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
|
||||
// default to 1 for cities loaded from _RegionalCities, use value calculate above for remaining stations
|
||||
const targetDistance = city.targetDistance || 1;
|
||||
combinedCities.forEach((city) => {
|
||||
if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat
|
||||
&& city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
|
||||
// default to 1 for cities loaded from RegionalCities, use value calculate above for remaining stations
|
||||
const targetDist = city.targetDistance || 1;
|
||||
// Only add the city as long as it isn't within set distance degree of any other city already in the array.
|
||||
const okToAddCity = regionalCities.reduce((acc, testCity) => {
|
||||
const distance = utils.calc.distance(city.lon, city.lat, testCity.lon, testCity.lat);
|
||||
return acc && distance >= targetDistance;
|
||||
return acc && distance >= targetDist;
|
||||
}, true);
|
||||
if (okToAddCity) regionalCities.push(city);
|
||||
}
|
||||
});
|
||||
|
||||
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
|
||||
const regionalForecastPromises = regionalCities.map(async city => {
|
||||
const regionalForecastPromises = regionalCities.map(async (city) => {
|
||||
try {
|
||||
// get the point first, then break down into forecast and observations
|
||||
const point = await utils.weather.getPoint(city.lat, city.lon);
|
||||
|
||||
// start off the observation task
|
||||
const observationPromise = this.getRegionalObservation(point, city);
|
||||
const observationPromise = RegionalForecast.getRegionalObservation(point, city);
|
||||
|
||||
const forecast = await utils.fetch.json(point.properties.forecast);
|
||||
|
||||
@@ -85,7 +85,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
const regionalObservation = {
|
||||
daytime: !!observation.icon.match(/\/day\//),
|
||||
temperature: utils.units.celsiusToFahrenheit(observation.temperature.value),
|
||||
name: this.formatCity(city.city),
|
||||
name: RegionalForecast.formatCity(city.city),
|
||||
icon: observation.icon,
|
||||
x: cityXY.x,
|
||||
y: cityXY.y,
|
||||
@@ -101,8 +101,8 @@ class RegionalForecast extends WeatherDisplay {
|
||||
// always skip the first forecast index because it's what's going on right now
|
||||
return [
|
||||
regionalObservation,
|
||||
this.buildForecast(forecast.properties.periods[1], city, cityXY),
|
||||
this.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||
RegionalForecast.buildForecast(forecast.properties.periods[1], city, cityXY),
|
||||
RegionalForecast.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||
];
|
||||
} catch (e) {
|
||||
console.log(`No regional forecast data for '${city.name}'`);
|
||||
@@ -114,7 +114,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
// wait for the forecasts
|
||||
const regionalDataAll = await Promise.all(regionalForecastPromises);
|
||||
// filter out any false (unavailable data)
|
||||
const regionalData = regionalDataAll.filter(data => data);
|
||||
const regionalData = regionalDataAll.filter((data) => data);
|
||||
|
||||
// test for data present
|
||||
if (regionalData.length === 0) {
|
||||
@@ -132,11 +132,11 @@ class RegionalForecast extends WeatherDisplay {
|
||||
this.setStatus(STATUS.loaded);
|
||||
}
|
||||
|
||||
buildForecast (forecast, city, cityXY) {
|
||||
static buildForecast(forecast, city, cityXY) {
|
||||
return {
|
||||
daytime: forecast.isDaytime,
|
||||
temperature: forecast.temperature||0,
|
||||
name: this.formatCity(city.city),
|
||||
temperature: forecast.temperature || 0,
|
||||
name: RegionalForecast.formatCity(city.city),
|
||||
icon: forecast.icon,
|
||||
x: cityXY.x,
|
||||
y: cityXY.y,
|
||||
@@ -144,7 +144,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
};
|
||||
}
|
||||
|
||||
async getRegionalObservation (point, city) {
|
||||
static async getRegionalObservation(point, city) {
|
||||
try {
|
||||
// get stations
|
||||
const stations = await utils.fetch.json(point.properties.observationStations);
|
||||
@@ -165,9 +165,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// utility latitude/pixel conversions
|
||||
getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(...arguments);
|
||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
|
||||
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
const ImgHeight = 1600;
|
||||
@@ -194,7 +194,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getXYFromLatitudeLongitudeAK (Latitude, Longitude, OffsetX, OffsetY) {
|
||||
static getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY) {
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
const ImgHeight = 1142;
|
||||
@@ -221,7 +221,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getXYFromLatitudeLongitudeHI (Latitude, Longitude, OffsetX, OffsetY) {
|
||||
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
const ImgHeight = 571;
|
||||
@@ -248,38 +248,44 @@ class RegionalForecast extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getMinMaxLatitudeLongitude (X, Y, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(...arguments);
|
||||
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(...arguments);
|
||||
getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
||||
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY);
|
||||
const maxLat = ((Y / 55.2) - 50.5) * -1;
|
||||
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 50.5) * -1;
|
||||
const minLon = (((X * -1) / 41.775) + 127.5) * -1;
|
||||
const maxLon = ((((X + (OffsetX * 2)) * -1) / 41.775) + 127.5) * -1;
|
||||
|
||||
return { minLat, maxLat, minLon, maxLon };
|
||||
return {
|
||||
minLat, maxLat, minLon, maxLon,
|
||||
};
|
||||
}
|
||||
|
||||
getMinMaxLatitudeLongitudeAK (X, Y, OffsetX, OffsetY) {
|
||||
static getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY) {
|
||||
const maxLat = ((Y / 56) - 73.0) * -1;
|
||||
const minLat = (((Y + (OffsetY * 2)) / 56) - 73.0) * -1;
|
||||
const minLon = (((X * -1) / 25) + 175.0) * -1;
|
||||
const maxLon = ((((X + (OffsetX * 2)) * -1) / 25) + 175.0) * -1;
|
||||
|
||||
return { minLat, maxLat, minLon, maxLon };
|
||||
return {
|
||||
minLat, maxLat, minLon, maxLon,
|
||||
};
|
||||
}
|
||||
|
||||
getMinMaxLatitudeLongitudeHI (X, Y, OffsetX, OffsetY) {
|
||||
static getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY) {
|
||||
const maxLat = ((Y / 55.2) - 25) * -1;
|
||||
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 25) * -1;
|
||||
const minLon = (((X * -1) / 41.775) + 164.5) * -1;
|
||||
const maxLon = ((((X + (OffsetX * 2)) * -1) / 41.775) + 164.5) * -1;
|
||||
|
||||
return { minLat, maxLat, minLon, maxLon };
|
||||
return {
|
||||
minLat, maxLat, minLon, maxLon,
|
||||
};
|
||||
}
|
||||
|
||||
getXYForCity (City, MaxLatitude, MinLongitude, state) {
|
||||
if (state === 'AK') this.getXYForCityAK(...arguments);
|
||||
if (state === 'HI') this.getXYForCityHI(...arguments);
|
||||
getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
||||
if (state === 'AK') this.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
||||
if (state === 'HI') this.getXYForCityHI(City, MaxLatitude, MinLongitude);
|
||||
let x = (City.lon - MinLongitude) * 57;
|
||||
let y = (MaxLatitude - City.lat) * 70;
|
||||
|
||||
@@ -292,7 +298,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getXYForCityAK (City, MaxLatitude, MinLongitude) {
|
||||
static getXYForCityAK(City, MaxLatitude, MinLongitude) {
|
||||
let x = (City.lon - MinLongitude) * 37;
|
||||
let y = (MaxLatitude - City.lat) * 70;
|
||||
|
||||
@@ -304,7 +310,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getXYForCityHI (City, MaxLatitude, MinLongitude) {
|
||||
static getXYForCityHI(City, MaxLatitude, MinLongitude) {
|
||||
let x = (City.lon - MinLongitude) * 57;
|
||||
let y = (MaxLatitude - City.lat) * 70;
|
||||
|
||||
@@ -318,19 +324,19 @@ class RegionalForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// to fit on the map, remove anything after punctuation and then limit to 15 characters
|
||||
formatCity(city) {
|
||||
return city.match(/[^-;/\\,]*/)[0].substr(0,12);
|
||||
static formatCity(city) {
|
||||
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
||||
}
|
||||
|
||||
async drawCanvas() {
|
||||
super.drawCanvas();
|
||||
// break up data into useful values
|
||||
const {regionalData: data, sourceXY, offsetXY} = this.data;
|
||||
const { regionalData: data, sourceXY, offsetXY } = this.data;
|
||||
|
||||
// fixed offset for all y values when drawing to the map
|
||||
const mapYOff = 90;
|
||||
|
||||
const {DateTime} = luxon;
|
||||
const { DateTime } = luxon;
|
||||
// draw the header graphics
|
||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
||||
@@ -340,21 +346,21 @@ class RegionalForecast extends WeatherDisplay {
|
||||
if (this.screenIndex === 0) {
|
||||
draw.titleText(this.context, 'Regional', 'Observations');
|
||||
} else {
|
||||
let forecastDate = DateTime.fromISO(data[0][this.screenIndex].time);
|
||||
const forecastDate = DateTime.fromISO(data[0][this.screenIndex].time);
|
||||
|
||||
// get the name of the day
|
||||
const dayName = forecastDate.toLocaleString({weekday: 'long'});
|
||||
const dayName = forecastDate.toLocaleString({ weekday: 'long' });
|
||||
// draw the title
|
||||
if (data[0][this.screenIndex].daytime) {
|
||||
draw.titleText(this.context, 'Forecast for', dayName);
|
||||
} else {
|
||||
draw.titleText(this.context, 'Forecast for', dayName + ' Night');
|
||||
draw.titleText(this.context, 'Forecast for', `${dayName} Night`);
|
||||
}
|
||||
}
|
||||
|
||||
// draw the map
|
||||
this.context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, (offsetXY.x * 2), (offsetXY.y * 2), 0, mapYOff, 640, 312);
|
||||
await Promise.all(data.map(async city => {
|
||||
await Promise.all(data.map(async (city) => {
|
||||
const period = city[this.screenIndex];
|
||||
// draw the icon if possible
|
||||
const icon = icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime);
|
||||
@@ -365,20 +371,19 @@ class RegionalForecast extends WeatherDisplay {
|
||||
auto_play: true,
|
||||
canvas: this.canvas,
|
||||
x: period.x,
|
||||
y: period.y - 15+mapYOff,
|
||||
y: period.y - 15 + mapYOff,
|
||||
}));
|
||||
}
|
||||
|
||||
// City Name
|
||||
draw.text(this.context, 'Star4000', '20px', '#ffffff', period.x - 40, period.y - 15+mapYOff, period.name, 2);
|
||||
draw.text(this.context, 'Star4000', '20px', '#ffffff', period.x - 40, period.y - 15 + mapYOff, period.name, 2);
|
||||
|
||||
// Temperature
|
||||
let temperature = period.temperature;
|
||||
let { temperature } = period;
|
||||
if (navigation.units() === UNITS.metric) temperature = Math.round(utils.units.fahrenheitToCelsius(temperature));
|
||||
draw.text(this.context, 'Star4000 Large Compressed', '28px', '#ffff00', period.x - (temperature.toString().length * 15), period.y + 20+mapYOff, temperature, 2);
|
||||
draw.text(this.context, 'Star4000 Large Compressed', '28px', '#ffff00', period.x - (temperature.toString().length * 15), period.y + 20 + mapYOff, temperature, 2);
|
||||
}));
|
||||
|
||||
this.finishDraw();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// travel forecast display
|
||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, icons, luxon, _TravelCities */
|
||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, icons, luxon, TravelCities */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class TravelForecast extends WeatherDisplay {
|
||||
@@ -15,15 +15,15 @@ class TravelForecast extends WeatherDisplay {
|
||||
// set up the timing
|
||||
this.timing.baseDelay = 20;
|
||||
// page sizes are 4 cities, calculate the number of pages necessary plus overflow
|
||||
const pagesFloat = _TravelCities.length/4;
|
||||
const pagesFloat = TravelCities.length / 4;
|
||||
const pages = Math.floor(pagesFloat) - 2; // first page is already displayed, last page doesn't happen
|
||||
const extra = pages%1;
|
||||
const timingStep = this.cityHeight*4;
|
||||
this.timing.delay = [150+timingStep];
|
||||
const extra = pages % 1;
|
||||
const timingStep = this.cityHeight * 4;
|
||||
this.timing.delay = [150 + timingStep];
|
||||
// add additional pages
|
||||
for (let i = 0; i < pages; i++) this.timing.delay.push(timingStep);
|
||||
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||
// add the extra (not exactly 4 pages portion)
|
||||
if (extra !== 0) this.timing.delay.push(Math.round(this.extra*this.cityHeight));
|
||||
if (extra !== 0) this.timing.delay.push(Math.round(this.extra * this.cityHeight));
|
||||
// add the final 3 second delay
|
||||
this.timing.delay.push(150);
|
||||
}
|
||||
@@ -31,25 +31,25 @@ class TravelForecast extends WeatherDisplay {
|
||||
async getData() {
|
||||
// super checks for enabled
|
||||
if (!super.getData()) return;
|
||||
const forecastPromises = _TravelCities.map(async city => {
|
||||
const forecastPromises = TravelCities.map(async (city) => {
|
||||
try {
|
||||
// get point then forecast
|
||||
const point = await utils.weather.getPoint(city.Latitude, city.Longitude);
|
||||
const forecast = await utils.fetch.json(point.properties.forecast);
|
||||
// determine today or tomorrow (shift periods by 1 if tomorrow)
|
||||
const todayShift = forecast.properties.periods[0].isDaytime? 0:1;
|
||||
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1;
|
||||
// return a pared-down forecast
|
||||
return {
|
||||
today: todayShift === 0,
|
||||
high: forecast.properties.periods[todayShift].temperature,
|
||||
low: forecast.properties.periods[todayShift+1].temperature,
|
||||
low: forecast.properties.periods[todayShift + 1].temperature,
|
||||
name: city.Name,
|
||||
icon: icons.getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(`GetTravelWeather for ${city.Name} failed`);
|
||||
console.error(e.status, e.responseJSON);
|
||||
return {name: city.Name};
|
||||
return { name: city.Name };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ class TravelForecast extends WeatherDisplay {
|
||||
this.data = forecasts;
|
||||
|
||||
// test for some data available in at least one forecast
|
||||
const hasData = this.data.reduce((acc,forecast) => acc || forecast.high, false);
|
||||
const hasData = this.data.reduce((acc, forecast) => acc || forecast.high, false);
|
||||
if (!hasData) {
|
||||
this.setStatus(STATUS.noData);
|
||||
return;
|
||||
@@ -68,7 +68,7 @@ class TravelForecast extends WeatherDisplay {
|
||||
this.drawLongCanvas();
|
||||
}
|
||||
|
||||
async drawLongCanvas () {
|
||||
async drawLongCanvas() {
|
||||
// create the "long" canvas if necessary
|
||||
if (!this.longCanvas) {
|
||||
this.longCanvas = document.createElement('canvas');
|
||||
@@ -79,7 +79,7 @@ class TravelForecast extends WeatherDisplay {
|
||||
}
|
||||
|
||||
// stop all gifs
|
||||
this.longCanvasGifs.forEach(gif => gif.pause());
|
||||
this.longCanvasGifs.forEach((gif) => gif.pause());
|
||||
// delete the gifs
|
||||
this.longCanvasGifs.length = 0;
|
||||
|
||||
@@ -87,23 +87,23 @@ class TravelForecast extends WeatherDisplay {
|
||||
const cities = this.data;
|
||||
|
||||
// clean up existing gifs
|
||||
this.gifs.forEach(gif => gif.pause());
|
||||
this.gifs.forEach((gif) => gif.pause());
|
||||
// delete the gifs
|
||||
this.gifs.length = 0;
|
||||
|
||||
this.longContext.clearRect(0,0,this.longCanvas.width,this.longCanvas.height);
|
||||
this.longContext.clearRect(0, 0, this.longCanvas.width, this.longCanvas.height);
|
||||
|
||||
// draw the "long" canvas with all cities
|
||||
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, _TravelCities.length*this.cityHeight);
|
||||
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, TravelCities.length * this.cityHeight);
|
||||
|
||||
for (let i = 0; i <= 4; i++) {
|
||||
for (let i = 0; i <= 4; i += 1) {
|
||||
const y = i * 346;
|
||||
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
||||
}
|
||||
|
||||
await Promise.all(cities.map(async (city, index) => {
|
||||
// calculate base y value
|
||||
const y = 50+this.cityHeight*index;
|
||||
const y = 50 + this.cityHeight * index;
|
||||
|
||||
// city name
|
||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, city.name, 2);
|
||||
@@ -111,7 +111,7 @@ class TravelForecast extends WeatherDisplay {
|
||||
// check for forecast data
|
||||
if (city.icon) {
|
||||
// get temperatures and convert if necessary
|
||||
let {low, high} = city;
|
||||
let { low, high } = city;
|
||||
|
||||
if (navigation.units() === UNITS.metric) {
|
||||
low = utils.units.fahrenheitToCelsius(low);
|
||||
@@ -140,7 +140,6 @@ class TravelForecast extends WeatherDisplay {
|
||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y - 18, 'NO TRAVEL', 2);
|
||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y, 'DATA AVAILABLE', 2);
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -157,7 +156,7 @@ class TravelForecast extends WeatherDisplay {
|
||||
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.titleText(this.context, 'Travel Forecast', 'For ' + this.getTravelCitiesDayName(cities));
|
||||
draw.titleText(this.context, 'Travel Forecast', `For ${TravelForecast.getTravelCitiesDayName(cities)}`);
|
||||
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 455, 105, 'LOW', 2);
|
||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 510, 105, 'HIGH', 2);
|
||||
@@ -185,7 +184,7 @@ class TravelForecast extends WeatherDisplay {
|
||||
const longCanvas = this.getLongCanvas();
|
||||
|
||||
// calculate scroll offset and don't go past end
|
||||
let offsetY = Math.min(longCanvas.height-289, (count-150));
|
||||
let offsetY = Math.min(longCanvas.height - 289, (count - 150));
|
||||
|
||||
// don't let offset go negative
|
||||
if (offsetY < 0) offsetY = 0;
|
||||
@@ -194,15 +193,15 @@ class TravelForecast extends WeatherDisplay {
|
||||
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
|
||||
}
|
||||
|
||||
getTravelCitiesDayName(cities) {
|
||||
const {DateTime} = luxon;
|
||||
static getTravelCitiesDayName(cities) {
|
||||
const { DateTime } = luxon;
|
||||
// effectively returns early on the first found date
|
||||
return cities.reduce((dayName, city) => {
|
||||
if (city && dayName === '') {
|
||||
// today or tomorrow
|
||||
const day = DateTime.local().plus({days: (city.today)?0:1});
|
||||
const day = DateTime.local().plus({ days: (city.today) ? 0 : 1 });
|
||||
// return the day
|
||||
return day.toLocaleString({weekday: 'long'});
|
||||
return day.toLocaleString({ weekday: 'long' });
|
||||
}
|
||||
return dayName;
|
||||
}, '');
|
||||
@@ -212,4 +211,4 @@ class TravelForecast extends WeatherDisplay {
|
||||
getLongCanvas() {
|
||||
return this.longCanvas;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
'use strict';
|
||||
// radar utilities
|
||||
|
||||
/* globals SuperGif */
|
||||
@@ -17,29 +16,25 @@ const utils = (() => {
|
||||
|
||||
// ****************************** load images *********************************
|
||||
// load an image from a blob or url
|
||||
const loadImg = (imgData, cors = false) => {
|
||||
return new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.onload = (e) => {
|
||||
resolve(e.target);
|
||||
};
|
||||
if (imgData instanceof Blob) {
|
||||
img.src = window.URL.createObjectURL(imgData);
|
||||
} else {
|
||||
let url = imgData;
|
||||
if (cors) url = rewriteUrl(imgData);
|
||||
img.src = url;
|
||||
}
|
||||
});
|
||||
};
|
||||
const loadImg = (imgData, cors = false) => new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = (e) => {
|
||||
resolve(e.target);
|
||||
};
|
||||
if (imgData instanceof Blob) {
|
||||
img.src = window.URL.createObjectURL(imgData);
|
||||
} else {
|
||||
let url = imgData;
|
||||
if (cors) url = rewriteUrl(imgData);
|
||||
img.src = url;
|
||||
}
|
||||
});
|
||||
|
||||
// async version of SuperGif
|
||||
const superGifAsync = (e) => {
|
||||
return new Promise(resolve => {
|
||||
const gif = new SuperGif(e);
|
||||
gif.load(() => resolve(gif));
|
||||
});
|
||||
};
|
||||
const superGifAsync = (e) => new Promise((resolve) => {
|
||||
const gif = new SuperGif(e);
|
||||
gif.load(() => resolve(gif));
|
||||
});
|
||||
|
||||
// preload an image
|
||||
// the goal is to get it in the browser's cache so it is available more quickly when the browser needs it
|
||||
@@ -65,24 +60,24 @@ const utils = (() => {
|
||||
context.imageSmoothingEnabled = false;
|
||||
|
||||
// draw the image
|
||||
context.drawImage(img, 0,0);
|
||||
context.drawImage(img, 0, 0);
|
||||
return context;
|
||||
};
|
||||
|
||||
// *********************************** unit conversions ***********************
|
||||
|
||||
Math.round2 = (value, decimals) => Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
|
||||
Math.round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`);
|
||||
|
||||
const mphToKph = (Mph) => Math.round(Mph * 1.60934);
|
||||
const kphToMph = (Kph) => Math.round(Kph / 1.60934);
|
||||
const celsiusToFahrenheit = (Celsius) => Math.round(Celsius * 9 / 5 + 32);
|
||||
const fahrenheitToCelsius = (Fahrenheit) => Math.round2(((Fahrenheit) - 32) * 5 / 9, 1);
|
||||
const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32);
|
||||
const fahrenheitToCelsius = (Fahrenheit) => Math.round2((((Fahrenheit) - 32) * 5) / 9, 1);
|
||||
const milesToKilometers = (Miles) => Math.round(Miles * 1.60934);
|
||||
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934);
|
||||
const feetToMeters = (Feet) => Math.round(Feet * 0.3048);
|
||||
const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
|
||||
const inchesToCentimeters = (Inches) => Math.round2(Inches * 2.54, 2);
|
||||
const pascalToInHg = (Pascal) => Math.round2(Pascal*0.0002953,2);
|
||||
const pascalToInHg = (Pascal) => Math.round2(Pascal * 0.0002953, 2);
|
||||
|
||||
// ***************************** calculations **********************************
|
||||
|
||||
@@ -125,7 +120,7 @@ const utils = (() => {
|
||||
const T = Temperature;
|
||||
const V = WindSpeed;
|
||||
|
||||
return Math.round(35.74 + (0.6215 * T) - (35.75 * Math.pow(V, 0.16)) + (0.4275 * T * Math.pow(V, 0.16)));
|
||||
return Math.round(35.74 + (0.6215 * T) - (35.75 * (V ** 0.16)) + (0.4275 * T * (V ** 0.16)));
|
||||
};
|
||||
|
||||
// wind direction
|
||||
@@ -135,20 +130,22 @@ const utils = (() => {
|
||||
return arr[(val % 16)];
|
||||
};
|
||||
|
||||
const distance = (x1 ,y1, x2, y2) => Math.sqrt((x2-=x1)*x2 + (y2-=y1)*y2);
|
||||
const distance = (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
||||
|
||||
// wrap a number to 0-m
|
||||
const wrap = (x,m) => (x%m + m)%m;
|
||||
const wrap = (x, m) => ((x % m) + m) % m;
|
||||
|
||||
// ********************************* strings *********************************************
|
||||
const wordWrap = (str, ...rest) => {
|
||||
let m = ((rest.length >= 1) ? rest[0] : 75);
|
||||
let b = ((rest.length >= 2) ? rest[1] : '\n');
|
||||
let c = ((rest.length >= 3) ? rest[2] : false);
|
||||
const wordWrap = (str = '', ...rest) => {
|
||||
const m = ((rest.length >= 1) ? rest[0] : 75);
|
||||
const b = ((rest.length >= 2) ? rest[1] : '\n');
|
||||
const c = ((rest.length >= 3) ? rest[2] : false);
|
||||
|
||||
let i, j, l, s, r;
|
||||
|
||||
str += '';
|
||||
let i;
|
||||
let j;
|
||||
let l;
|
||||
let s;
|
||||
let r;
|
||||
|
||||
if (m < 1) {
|
||||
return str;
|
||||
@@ -160,8 +157,8 @@ const utils = (() => {
|
||||
r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : '')) {
|
||||
j = c === 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1]
|
||||
? m
|
||||
: j.input.length - j[0].length || c === true && m ||
|
||||
j.input.length + (j = s.slice(m).match(/^\S*/))[0].length;
|
||||
: ((j.input.length - j[0].length || c === true) && m)
|
||||
|| j.input.length + (j = s.slice(m).match(/^\S*/))[0].length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +166,8 @@ const utils = (() => {
|
||||
};
|
||||
// ********************************* cors ********************************************
|
||||
// rewrite some urls for local server
|
||||
const rewriteUrl = (url) => {
|
||||
const rewriteUrl = (_url) => {
|
||||
let url = _url;
|
||||
url = url.replace('https://api.weather.gov/', window.location.href);
|
||||
url = url.replace('https://radar.weather.gov/', window.location.href);
|
||||
url = url.replace('https://www.cpc.ncep.noaa.gov/', window.location.href);
|
||||
@@ -182,14 +180,14 @@ const utils = (() => {
|
||||
const raw = (url, params) => fetchAsync(url, '', params);
|
||||
const blob = (url, params) => fetchAsync(url, 'blob', params);
|
||||
|
||||
const fetchAsync = async (_url, responseType, _params={}) => {
|
||||
// combine default and provided parametersutils
|
||||
const params = Object.assign({}, {
|
||||
const fetchAsync = async (_url, responseType, _params = {}) => {
|
||||
// combine default and provided parameters
|
||||
const params = {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
type: 'GET',
|
||||
},
|
||||
_params);
|
||||
..._params,
|
||||
};
|
||||
// build a url, including the rewrite for cors if necessary
|
||||
let corsUrl = _url;
|
||||
if (params.cors === true) corsUrl = rewriteUrl(_url);
|
||||
@@ -212,11 +210,11 @@ const utils = (() => {
|
||||
// return the requested response
|
||||
switch (responseType) {
|
||||
case 'json':
|
||||
return await response.json();
|
||||
return response.json();
|
||||
case 'text':
|
||||
return await response.text();
|
||||
return response.text();
|
||||
case 'blob':
|
||||
return await response.blob();
|
||||
return response.blob();
|
||||
default:
|
||||
return response;
|
||||
}
|
||||
@@ -266,4 +264,4 @@ const utils = (() => {
|
||||
blob,
|
||||
},
|
||||
};
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -19,7 +19,7 @@ class WeatherDisplay {
|
||||
this.gifs = [];
|
||||
this.data = undefined;
|
||||
this.loadingStatus = STATUS.loading;
|
||||
this.name = name?name:elemId;
|
||||
this.name = name ?? elemId;
|
||||
this.getDataCallbacks = [];
|
||||
|
||||
// default navigation timing
|
||||
@@ -59,7 +59,7 @@ class WeatherDisplay {
|
||||
// create a checkbox in the selected displays area
|
||||
const checkbox = document.createElement('template');
|
||||
checkbox.innerHTML = `<label for="${this.elemId}Enabled">
|
||||
<input type="checkbox" value="true" id="${this.elemId}Enabled" name="${this.elemId}Enabled"${this.enabled?' checked':''}/>
|
||||
<input type="checkbox" value="true" id="${this.elemId}Enabled" name="${this.elemId}Enabled"${this.enabled ? ' checked' : ''}/>
|
||||
${this.name}</label>`;
|
||||
checkbox.content.firstChild.addEventListener('change', (e) => this.checkboxChange(e));
|
||||
const availableDisplays = document.getElementById('enabledDisplays');
|
||||
@@ -99,7 +99,7 @@ class WeatherDisplay {
|
||||
|
||||
// create a canvas
|
||||
const canvas = document.createElement('template');
|
||||
canvas.innerHTML = `<canvas id='${elemId+'Canvas'}' width='${width}' height='${height}' style='display: none;' />`;
|
||||
canvas.innerHTML = `<canvas id='${`${elemId}Canvas`}' width='${width}' height='${height}' style='display: none;' />`;
|
||||
|
||||
// add to the page
|
||||
const container = document.getElementById('container');
|
||||
@@ -130,23 +130,22 @@ class WeatherDisplay {
|
||||
// return any data requested before it was available
|
||||
getDataCallback() {
|
||||
// call each callback
|
||||
this.getDataCallbacks.forEach(fxn => fxn(this.data));
|
||||
this.getDataCallbacks.forEach((fxn) => fxn(this.data));
|
||||
// clear the callbacks
|
||||
this.getDataCallbacks = [];
|
||||
}
|
||||
|
||||
drawCanvas() {
|
||||
// stop all gifs
|
||||
this.gifs.forEach(gif => gif.pause());
|
||||
this.gifs.forEach((gif) => gif.pause());
|
||||
// delete the gifs
|
||||
this.gifs.length = 0;
|
||||
// refresh the canvas
|
||||
this.canvas = document.getElementById(this.elemId+'Canvas');
|
||||
this.canvas = document.getElementById(`${this.elemId}Canvas`);
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
// clean up the first-run flag in screen index
|
||||
if (this.screenIndex < 0)
|
||||
this.screenIndex = 0;
|
||||
if (this.screenIndex < 0) this.screenIndex = 0;
|
||||
}
|
||||
|
||||
finishDraw() {
|
||||
@@ -155,7 +154,7 @@ class WeatherDisplay {
|
||||
let OkToDrawCurrentDateTime = true;
|
||||
let OkToDrawLogoImage = true;
|
||||
// let OkToDrawCustomScrollText = false;
|
||||
let bottom = undefined;
|
||||
let bottom;
|
||||
|
||||
// visibility tests
|
||||
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
|
||||
@@ -201,7 +200,7 @@ class WeatherDisplay {
|
||||
drawCurrentDateTime(bottom) {
|
||||
// only draw if canvas is active to conserve battery
|
||||
if (!this.isActive()) return;
|
||||
const {DateTime} = luxon;
|
||||
const { DateTime } = luxon;
|
||||
const font = 'Star4000 Small';
|
||||
const size = '24pt';
|
||||
const color = '#ffffff';
|
||||
@@ -226,10 +225,10 @@ class WeatherDisplay {
|
||||
// Get the current date and time.
|
||||
const now = DateTime.local();
|
||||
|
||||
//time = "11:35:08 PM";
|
||||
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11,' ');
|
||||
// time = "11:35:08 PM";
|
||||
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
|
||||
|
||||
let x,y;
|
||||
let x; let y;
|
||||
if (bottom) {
|
||||
x = 400;
|
||||
y = 402;
|
||||
@@ -241,9 +240,9 @@ class WeatherDisplay {
|
||||
x += 45;
|
||||
}
|
||||
|
||||
draw.text(this.context, font, size, color, x, y, time.toUpperCase(), shadow); //y += 20;
|
||||
draw.text(this.context, font, size, color, x, y, time.toUpperCase(), shadow); // y += 20;
|
||||
|
||||
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2,' ');
|
||||
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
|
||||
|
||||
if (bottom) {
|
||||
x = 55;
|
||||
@@ -255,7 +254,7 @@ class WeatherDisplay {
|
||||
draw.text(this.context, font, size, color, x, y, date.toUpperCase(), shadow);
|
||||
}
|
||||
|
||||
async drawNoaaImage () {
|
||||
async drawNoaaImage() {
|
||||
// load the image and store locally
|
||||
if (!this.drawNoaaImage.image) {
|
||||
this.drawNoaaImage.image = utils.image.load('images/noaa5.gif');
|
||||
@@ -265,7 +264,7 @@ class WeatherDisplay {
|
||||
this.context.drawImage(img, 356, 39);
|
||||
}
|
||||
|
||||
async drawLogoImage () {
|
||||
async drawLogoImage() {
|
||||
// load the image and store locally
|
||||
if (!this.drawLogoImage.image) {
|
||||
this.drawLogoImage.image = utils.image.load('images/Logo3.png');
|
||||
@@ -287,10 +286,9 @@ class WeatherDisplay {
|
||||
|
||||
// show the canvas
|
||||
this.canvas.style.display = 'block';
|
||||
|
||||
// stop if timing has been disabled
|
||||
if (!this.timing) return;
|
||||
return false;
|
||||
}
|
||||
|
||||
hideCanvas() {
|
||||
this.resetNavBaseCount();
|
||||
|
||||
@@ -299,7 +297,7 @@ class WeatherDisplay {
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return document.getElementById(this.elemId+'Canvas').offsetParent !== null;
|
||||
return document.getElementById(`${this.elemId}Canvas`).offsetParent !== null;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
@@ -318,7 +316,7 @@ class WeatherDisplay {
|
||||
// see if play is active and screen is active
|
||||
if (!navigation.isPlaying() || !this.isActive()) return;
|
||||
// increment the base count
|
||||
this.navBaseCount++;
|
||||
this.navBaseCount += 1;
|
||||
|
||||
// call base count change if available for this function
|
||||
if (this.baseCountChange) this.baseCountChange(this.navBaseCount);
|
||||
@@ -329,7 +327,7 @@ class WeatherDisplay {
|
||||
|
||||
async updateScreenFromBaseCount() {
|
||||
// get the next screen index
|
||||
let nextScreenIndex = this.screenIndexFromBaseCount();
|
||||
const nextScreenIndex = this.screenIndexFromBaseCount();
|
||||
|
||||
// special cases for first and last frame
|
||||
// must compare with false as nextScreenIndex could be 0 which is valid
|
||||
@@ -368,10 +366,10 @@ class WeatherDisplay {
|
||||
// if the delay is provided as a single value, expand it to a series of the same value
|
||||
let intermediateDelay = [];
|
||||
if (typeof this.timing.delay === 'number') {
|
||||
for (let i = 0; i < this.timing.totalScreens; i++) intermediateDelay.push(this.timing.delay);
|
||||
for (let i = 0; i < this.timing.totalScreens; i += 1) intermediateDelay.push(this.timing.delay);
|
||||
} else {
|
||||
// map just the delays to the intermediate block
|
||||
intermediateDelay = this.timing.delay.map(delay => {
|
||||
intermediateDelay = this.timing.delay.map((delay) => {
|
||||
if (typeof delay === 'object') return delay.time;
|
||||
return delay;
|
||||
});
|
||||
@@ -379,7 +377,7 @@ class WeatherDisplay {
|
||||
|
||||
// calculate the cumulative end point of each delay
|
||||
let sum = 0;
|
||||
this.timing.fullDelay = intermediateDelay.map(val => {
|
||||
this.timing.fullDelay = intermediateDelay.map((val) => {
|
||||
const calc = sum + val;
|
||||
sum += val;
|
||||
return calc;
|
||||
@@ -388,11 +386,11 @@ class WeatherDisplay {
|
||||
// generate a list of screen either sequentially if not provided in an object or from the object
|
||||
if (Array.isArray(this.timing.delay) && typeof this.timing.delay[0] === 'object') {
|
||||
// extract screen indexes from objects
|
||||
this.timing.screenIndexes = this.timing.delay.map(delay => delay.si);
|
||||
this.timing.screenIndexes = this.timing.delay.map((delay) => delay.si);
|
||||
} else {
|
||||
// generate sequential screen indexes
|
||||
this.timing.screenIndexes = [];
|
||||
for (let i = 0; i < this.timing.totalScreens; i++) this.timing.screenIndexes.push(i);
|
||||
for (let i = 0; i < this.timing.totalScreens; i += 1) this.timing.screenIndexes.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +401,7 @@ class WeatherDisplay {
|
||||
this.resetNavBaseCount();
|
||||
} else {
|
||||
// set the base count to the next available frame
|
||||
const newBaseCount = this.timing.fullDelay.find(delay => delay > this.navBaseCount);
|
||||
const newBaseCount = this.timing.fullDelay.find((delay) => delay > this.navBaseCount);
|
||||
this.navBaseCount = newBaseCount;
|
||||
}
|
||||
this.updateScreenFromBaseCount();
|
||||
@@ -413,13 +411,13 @@ class WeatherDisplay {
|
||||
navPrev(command) {
|
||||
// check for special 'last frame' command
|
||||
if (command === navigation.msg.command.lastFrame) {
|
||||
this.navBaseCount = this.timing.fullDelay[this.timing.totalScreens-1]-1;
|
||||
this.navBaseCount = this.timing.fullDelay[this.timing.totalScreens - 1] - 1;
|
||||
} else {
|
||||
// find the highest fullDelay that is less than the current base count
|
||||
const newBaseCount = this.timing.fullDelay.reduce((acc, delay) => {
|
||||
if (delay < this.navBaseCount) return delay;
|
||||
return acc;
|
||||
},0);
|
||||
}, 0);
|
||||
// if the new base count is zero then we're already at the first screen
|
||||
if (newBaseCount === 0 && this.navBaseCount === 0) {
|
||||
this.sendNavDisplayMessage(navigation.msg.response.previous);
|
||||
@@ -436,14 +434,14 @@ class WeatherDisplay {
|
||||
if (!this.timing) return 0;
|
||||
// find the first timing in the timing array that is greater than the base count
|
||||
if (this.timing && !this.timing.fullDelay) this.calcNavTiming();
|
||||
const timingIndex = this.timing.fullDelay.findIndex(delay => delay > this.navBaseCount);
|
||||
const timingIndex = this.timing.fullDelay.findIndex((delay) => delay > this.navBaseCount);
|
||||
if (timingIndex === -1) return false;
|
||||
return this.timing.screenIndexes[timingIndex];
|
||||
}
|
||||
|
||||
// start and stop base counter
|
||||
startNavCount() {
|
||||
if (!this.navInterval) this.navInterval = setInterval(()=>this.navBaseTime(), this.timing.baseDelay);
|
||||
if (!this.navInterval) this.navInterval = setInterval(() => this.navBaseTime(), this.timing.baseDelay);
|
||||
}
|
||||
|
||||
resetNavBaseCount() {
|
||||
@@ -463,4 +461,4 @@ class WeatherDisplay {
|
||||
type: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user