change to airbnb eslint plugin

This commit is contained in:
Matt Walsh
2020-10-29 16:44:28 -05:00
parent b7967fca05
commit dd98daf0c2
31 changed files with 1179 additions and 790 deletions

View File

@@ -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);
});
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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,
};
})();
})();

View File

@@ -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,
};
})();
})();

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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;
}
}
}

View File

@@ -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,
}));
}
}
}

View File

@@ -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,
};
})();
})();

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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,
},
};
})();
})();

View File

@@ -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,
});
}
}
}