class static code cleanup
This commit is contained in:
parent
0c8db4f38e
commit
3f5cd4ca70
|
@ -133,7 +133,7 @@ class Almanac extends WeatherDisplay {
|
||||||
|
|
||||||
fill.date = date;
|
fill.date = date;
|
||||||
fill.type = MoonPhase.phase;
|
fill.type = MoonPhase.phase;
|
||||||
fill.icon = { type: 'img', src: Almanac.imageName(MoonPhase.Phase) };
|
fill.icon = { type: 'img', src: imageName(MoonPhase.Phase) };
|
||||||
|
|
||||||
return this.fillTemplate('day', fill);
|
return this.fillTemplate('day', fill);
|
||||||
});
|
});
|
||||||
|
@ -145,20 +145,6 @@ class Almanac extends WeatherDisplay {
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
static imageName(type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'Full':
|
|
||||||
return 'images/2/Full-Moon.gif';
|
|
||||||
case 'Last':
|
|
||||||
return 'images/2/Last-Quarter.gif';
|
|
||||||
case 'New':
|
|
||||||
return 'images/2/New-Moon.gif';
|
|
||||||
case 'First':
|
|
||||||
default:
|
|
||||||
return 'images/2/First-Quarter.gif';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sun and moon data available outside this class
|
// make sun and moon data available outside this class
|
||||||
// promise allows for data to be requested before it is available
|
// promise allows for data to be requested before it is available
|
||||||
async getSun() {
|
async getSun() {
|
||||||
|
@ -170,6 +156,20 @@ class Almanac extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imageName = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'Full':
|
||||||
|
return 'images/2/Full-Moon.gif';
|
||||||
|
case 'Last':
|
||||||
|
return 'images/2/Last-Quarter.gif';
|
||||||
|
case 'New':
|
||||||
|
return 'images/2/New-Moon.gif';
|
||||||
|
case 'First':
|
||||||
|
default:
|
||||||
|
return 'images/2/First-Quarter.gif';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
const display = new Almanac(8, 'almanac');
|
const display = new Almanac(8, 'almanac');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
|
@ -128,7 +128,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
|
|
||||||
let Conditions = data.observations.textDescription;
|
let Conditions = data.observations.textDescription;
|
||||||
if (Conditions.length > 15) {
|
if (Conditions.length > 15) {
|
||||||
Conditions = CurrentWeather.shortConditions(Conditions);
|
Conditions = shortConditions(Conditions);
|
||||||
}
|
}
|
||||||
fill.condition = Conditions;
|
fill.condition = Conditions;
|
||||||
|
|
||||||
|
@ -170,8 +170,9 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
this.getDataCallbacks.push(() => resolve(this.parseData()));
|
this.getDataCallbacks.push(() => resolve(this.parseData()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static shortConditions(_condition) {
|
const shortConditions = (_condition) => {
|
||||||
let condition = _condition;
|
let condition = _condition;
|
||||||
condition = condition.replace(/Light/g, 'L');
|
condition = condition.replace(/Light/g, 'L');
|
||||||
condition = condition.replace(/Heavy/g, 'H');
|
condition = condition.replace(/Heavy/g, 'H');
|
||||||
|
@ -188,8 +189,8 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
condition = condition.replace(/L Snow Fog/g, 'L Snw/Fog');
|
condition = condition.replace(/L Snow Fog/g, 'L Snw/Fog');
|
||||||
condition = condition.replace(/ with /g, '/');
|
condition = condition.replace(/ with /g, '/');
|
||||||
return condition;
|
return condition;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
const display = new CurrentWeather(0, 'current-weather');
|
const display = new CurrentWeather(0, 'current-weather');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
|
|
|
@ -36,13 +36,48 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// we only get here if there was no error above
|
// we only get here if there was no error above
|
||||||
this.data = ExtendedForecast.parse(forecast.properties.periods);
|
this.data = parse(forecast.properties.periods);
|
||||||
this.screenIndex = 0;
|
this.screenIndex = 0;
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the api provides the forecast in 12 hour increments, flatten to day increments with high and low temperatures
|
async drawCanvas() {
|
||||||
static parse(fullForecast) {
|
super.drawCanvas();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// create each day template
|
||||||
|
const days = forecast.map((Day) => {
|
||||||
|
const fill = {};
|
||||||
|
fill.date = Day.dayName;
|
||||||
|
|
||||||
|
const { low } = Day;
|
||||||
|
if (low !== undefined) {
|
||||||
|
fill['value-lo'] = Math.round(low);
|
||||||
|
}
|
||||||
|
const { high } = Day;
|
||||||
|
fill['value-hi'] = Math.round(high);
|
||||||
|
fill.condition = Day.text;
|
||||||
|
|
||||||
|
// draw the icon
|
||||||
|
fill.icon = { type: 'img', src: Day.icon };
|
||||||
|
|
||||||
|
// return the filled template
|
||||||
|
return this.fillTemplate('day', fill);
|
||||||
|
});
|
||||||
|
|
||||||
|
// empty and update the container
|
||||||
|
const dayContainer = this.elem.querySelector('.day-container');
|
||||||
|
dayContainer.innerHTML = '';
|
||||||
|
dayContainer.append(...days);
|
||||||
|
this.finishDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the api provides the forecast in 12 hour increments, flatten to day increments with high and low temperatures
|
||||||
|
const parse = (fullForecast) => {
|
||||||
// create a list of days starting with today
|
// 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];
|
||||||
|
|
||||||
|
@ -65,7 +100,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
const fDay = forecast[destIndex];
|
const fDay = forecast[destIndex];
|
||||||
// high temperature will always be last in the source array so it will overwrite the low values assigned below
|
// high temperature will always be last in the source array so it will overwrite the low values assigned below
|
||||||
fDay.icon = getWeatherIconFromIconLink(period.icon);
|
fDay.icon = getWeatherIconFromIconLink(period.icon);
|
||||||
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
|
fDay.text = shortenExtendedForecastText(period.shortForecast);
|
||||||
fDay.dayName = dates[destIndex];
|
fDay.dayName = dates[destIndex];
|
||||||
|
|
||||||
// preload the icon
|
// preload the icon
|
||||||
|
@ -82,9 +117,9 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
});
|
});
|
||||||
|
|
||||||
return forecast;
|
return forecast;
|
||||||
}
|
};
|
||||||
|
|
||||||
static shortenExtendedForecastText(long) {
|
const shortenExtendedForecastText = (long) => {
|
||||||
const regexList = [
|
const regexList = [
|
||||||
[/ and /ig, ' '],
|
[/ and /ig, ' '],
|
||||||
[/Slight /ig, ''],
|
[/Slight /ig, ''],
|
||||||
|
@ -123,42 +158,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
async drawCanvas() {
|
|
||||||
super.drawCanvas();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// create each day template
|
|
||||||
const days = forecast.map((Day) => {
|
|
||||||
const fill = {};
|
|
||||||
fill.date = Day.dayName;
|
|
||||||
|
|
||||||
const { low } = Day;
|
|
||||||
if (low !== undefined) {
|
|
||||||
fill['value-lo'] = Math.round(low);
|
|
||||||
}
|
|
||||||
const { high } = Day;
|
|
||||||
fill['value-hi'] = Math.round(high);
|
|
||||||
fill.condition = Day.text;
|
|
||||||
|
|
||||||
// draw the icon
|
|
||||||
fill.icon = { type: 'img', src: Day.icon };
|
|
||||||
|
|
||||||
// return the filled template
|
|
||||||
return this.fillTemplate('day', fill);
|
|
||||||
});
|
|
||||||
|
|
||||||
// empty and update the container
|
|
||||||
const dayContainer = this.elem.querySelector('.day-container');
|
|
||||||
dayContainer.innerHTML = '';
|
|
||||||
dayContainer.append(...days);
|
|
||||||
this.finishDraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new ExtendedForecast(7, 'extended-forecast'));
|
registerDisplay(new ExtendedForecast(7, 'extended-forecast'));
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Hourly extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data = await Hourly.parseForecast(forecast.properties);
|
this.data = await parseForecast(forecast.properties);
|
||||||
this.getDataCallback();
|
this.getDataCallback();
|
||||||
if (!superResponse) return;
|
if (!superResponse) return;
|
||||||
|
|
||||||
|
@ -51,66 +51,6 @@ class Hourly extends WeatherDisplay {
|
||||||
this.drawLongCanvas();
|
this.drawLongCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract specific values from forecast and format as an array
|
|
||||||
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 Hourly.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
|
|
||||||
|
|
||||||
return temperature.map((val, idx) => ({
|
|
||||||
temperature: celsiusToFahrenheit(temperature[idx]),
|
|
||||||
apparentTemperature: celsiusToFahrenheit(apparentTemperature[idx]),
|
|
||||||
windSpeed: kilometersToMiles(windSpeed[idx]),
|
|
||||||
windDirection: directionToNSEW(windDirection[idx]),
|
|
||||||
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
|
||||||
skyCover: skyCover[idx],
|
|
||||||
icon: icons[idx],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// given forecast paramaters determine a suitable icon
|
|
||||||
static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
|
|
||||||
const startOfHour = DateTime.local().startOf('hour');
|
|
||||||
const sunTimes = (await getSun()).sun;
|
|
||||||
const overnight = Interval.fromDateTimes(DateTime.fromJSDate(sunTimes[0].sunset), DateTime.fromJSDate(sunTimes[1].sunrise));
|
|
||||||
const tomorrowOvernight = DateTime.fromJSDate(sunTimes[1].sunset);
|
|
||||||
return skyCover.map((val, idx) => {
|
|
||||||
const hour = startOfHour.plus({ hours: idx });
|
|
||||||
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
|
||||||
return 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
|
|
||||||
static expand(data) {
|
|
||||||
const startOfHour = DateTime.utc().startOf('hour').toMillis();
|
|
||||||
const result = []; // resulting expanded values
|
|
||||||
data.forEach((item) => {
|
|
||||||
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
|
|
||||||
const duration = 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 {
|
|
||||||
// test for timestamp greater than now
|
|
||||||
if (startTime >= startOfHour && result.length < 24) {
|
|
||||||
result.push(item.value); // push data array
|
|
||||||
} // timestamp is after now
|
|
||||||
// increment start time by 1 hour
|
|
||||||
startTime += 3600000;
|
|
||||||
} while (startTime < endTime && result.length < 24);
|
|
||||||
}); // for each value
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async drawLongCanvas() {
|
async drawLongCanvas() {
|
||||||
// get the list element and populate
|
// get the list element and populate
|
||||||
const list = this.elem.querySelector('.hourly-lines');
|
const list = this.elem.querySelector('.hourly-lines');
|
||||||
|
@ -178,19 +118,6 @@ class Hourly extends WeatherDisplay {
|
||||||
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTravelCitiesDayName(cities) {
|
|
||||||
// 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 });
|
|
||||||
// return the day
|
|
||||||
return day.toLocaleString({ weekday: 'long' });
|
|
||||||
}
|
|
||||||
return dayName;
|
|
||||||
}, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// make data available outside this class
|
// make data available outside this class
|
||||||
// promise allows for data to be requested before it is available
|
// promise allows for data to be requested before it is available
|
||||||
async getCurrentData() {
|
async getCurrentData() {
|
||||||
|
@ -202,6 +129,66 @@ class Hourly extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extract specific values from forecast and format as an array
|
||||||
|
const parseForecast = async (data) => {
|
||||||
|
const temperature = expand(data.temperature.values);
|
||||||
|
const apparentTemperature = expand(data.apparentTemperature.values);
|
||||||
|
const windSpeed = expand(data.windSpeed.values);
|
||||||
|
const windDirection = expand(data.windDirection.values);
|
||||||
|
const skyCover = expand(data.skyCover.values); // cloud icon
|
||||||
|
const weather = expand(data.weather.values); // fog icon
|
||||||
|
const iceAccumulation = expand(data.iceAccumulation.values); // ice icon
|
||||||
|
const probabilityOfPrecipitation = expand(data.probabilityOfPrecipitation.values); // rain icon
|
||||||
|
const snowfallAmount = expand(data.snowfallAmount.values); // snow icon
|
||||||
|
|
||||||
|
const icons = await determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
|
||||||
|
|
||||||
|
return temperature.map((val, idx) => ({
|
||||||
|
temperature: celsiusToFahrenheit(temperature[idx]),
|
||||||
|
apparentTemperature: celsiusToFahrenheit(apparentTemperature[idx]),
|
||||||
|
windSpeed: kilometersToMiles(windSpeed[idx]),
|
||||||
|
windDirection: directionToNSEW(windDirection[idx]),
|
||||||
|
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
||||||
|
skyCover: skyCover[idx],
|
||||||
|
icon: icons[idx],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// given forecast paramaters determine a suitable icon
|
||||||
|
const determineIcon = async (skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) => {
|
||||||
|
const startOfHour = DateTime.local().startOf('hour');
|
||||||
|
const sunTimes = (await getSun()).sun;
|
||||||
|
const overnight = Interval.fromDateTimes(DateTime.fromJSDate(sunTimes[0].sunset), DateTime.fromJSDate(sunTimes[1].sunrise));
|
||||||
|
const tomorrowOvernight = DateTime.fromJSDate(sunTimes[1].sunset);
|
||||||
|
return skyCover.map((val, idx) => {
|
||||||
|
const hour = startOfHour.plus({ hours: idx });
|
||||||
|
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
||||||
|
return 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
|
||||||
|
const expand = (data) => {
|
||||||
|
const startOfHour = DateTime.utc().startOf('hour').toMillis();
|
||||||
|
const result = []; // resulting expanded values
|
||||||
|
data.forEach((item) => {
|
||||||
|
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
|
||||||
|
const duration = 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 {
|
||||||
|
// test for timestamp greater than now
|
||||||
|
if (startTime >= startOfHour && result.length < 24) {
|
||||||
|
result.push(item.value); // push data array
|
||||||
|
} // timestamp is after now
|
||||||
|
// increment start time by 1 hour
|
||||||
|
startTime += 3600000;
|
||||||
|
} while (startTime < endTime && result.length < 24);
|
||||||
|
}); // for each value
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
const display = new Hourly(2, 'hourly', false);
|
const display = new Hourly(2, 'hourly', false);
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
|
@ -82,7 +82,7 @@ class LatestObservations extends WeatherDisplay {
|
||||||
const fill = {};
|
const fill = {};
|
||||||
fill.location = locationCleanup(condition.city).substr(0, 14);
|
fill.location = locationCleanup(condition.city).substr(0, 14);
|
||||||
fill.temp = Temperature;
|
fill.temp = Temperature;
|
||||||
fill.weather = LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9);
|
fill.weather = shortenCurrentConditions(condition.textDescription).substr(0, 9);
|
||||||
if (WindSpeed > 0) {
|
if (WindSpeed > 0) {
|
||||||
fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString();
|
fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString();
|
||||||
} else if (WindSpeed === 'NA') {
|
} else if (WindSpeed === 'NA') {
|
||||||
|
@ -100,8 +100,8 @@ class LatestObservations extends WeatherDisplay {
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
static shortenCurrentConditions(_condition) {
|
const shortenCurrentConditions = (_condition) => {
|
||||||
let condition = _condition;
|
let condition = _condition;
|
||||||
condition = condition.replace(/Light/, 'L');
|
condition = condition.replace(/Light/, 'L');
|
||||||
condition = condition.replace(/Heavy/, 'H');
|
condition = condition.replace(/Heavy/, 'H');
|
||||||
|
@ -118,7 +118,6 @@ class LatestObservations extends WeatherDisplay {
|
||||||
condition = condition.replace(/L Snow Fog/, 'L Snw/Fog');
|
condition = condition.replace(/L Snow Fog/, 'L Snw/Fog');
|
||||||
condition = condition.replace(/ with /, '/');
|
condition = condition.replace(/ with /, '/');
|
||||||
return condition;
|
return condition;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new LatestObservations(1, 'latest-observations'));
|
registerDisplay(new LatestObservations(1, 'latest-observations'));
|
||||||
|
|
|
@ -25,7 +25,7 @@ class LocalForecast extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// parse raw data
|
// parse raw data
|
||||||
const conditions = LocalForecast.parse(rawData);
|
const conditions = parse(rawData);
|
||||||
|
|
||||||
// read each text
|
// read each text
|
||||||
this.screenTexts = conditions.map((condition) => {
|
this.screenTexts = conditions.map((condition) => {
|
||||||
|
@ -80,17 +80,14 @@ class LocalForecast extends WeatherDisplay {
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// format the forecast
|
// format the forecast
|
||||||
static parse(forecast) {
|
// only use the first 6 lines
|
||||||
// only use the first 6 lines
|
const parse = (forecast) => forecast.properties.periods.slice(0, 6).map((text) => ({
|
||||||
return forecast.properties.periods.slice(0, 6).map((text) => ({
|
|
||||||
// format day and text
|
// format day and text
|
||||||
DayName: text.name.toUpperCase(),
|
DayName: text.name.toUpperCase(),
|
||||||
Text: text.detailedForecast,
|
Text: text.detailedForecast,
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new LocalForecast(6, 'local-forecast'));
|
registerDisplay(new LocalForecast(6, 'local-forecast'));
|
||||||
|
|
185
server/scripts/modules/radar-utils.mjs
Normal file
185
server/scripts/modules/radar-utils.mjs
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
const getXYFromLatitudeLongitudeMap = (pos, offsetX, offsetY) => {
|
||||||
|
let y = 0;
|
||||||
|
let x = 0;
|
||||||
|
const imgHeight = 3200;
|
||||||
|
const imgWidth = 5100;
|
||||||
|
|
||||||
|
y = (51.75 - pos.latitude) * 55.2;
|
||||||
|
// center map
|
||||||
|
y -= offsetY;
|
||||||
|
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (y > (imgHeight - (offsetY * 2))) {
|
||||||
|
y = imgHeight - (offsetY * 2);
|
||||||
|
} else if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = ((-130.37 - pos.longitude) * 41.775) * -1;
|
||||||
|
// center map
|
||||||
|
x -= offsetX;
|
||||||
|
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (x > (imgWidth - (offsetX * 2))) {
|
||||||
|
x = imgWidth - (offsetX * 2);
|
||||||
|
} else if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: x * 2, y: y * 2 };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getXYFromLatitudeLongitudeDoppler = (pos, offsetX, offsetY) => {
|
||||||
|
let y = 0;
|
||||||
|
let x = 0;
|
||||||
|
const imgHeight = 6000;
|
||||||
|
const imgWidth = 2800;
|
||||||
|
|
||||||
|
y = (51 - pos.latitude) * 61.4481;
|
||||||
|
// center map
|
||||||
|
y -= offsetY;
|
||||||
|
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (y > (imgHeight - (offsetY * 2))) {
|
||||||
|
y = imgHeight - (offsetY * 2);
|
||||||
|
} else if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = ((-129.138 - pos.longitude) * 42.1768) * -1;
|
||||||
|
// center map
|
||||||
|
x -= offsetX;
|
||||||
|
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (x > (imgWidth - (offsetX * 2))) {
|
||||||
|
x = imgWidth - (offsetX * 2);
|
||||||
|
} else if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: x * 2, y: y * 2 };
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDopplerRadarImageNoise = (RadarContext) => {
|
||||||
|
const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
|
||||||
|
|
||||||
|
// examine every pixel,
|
||||||
|
// change any old rgb to the new-rgb
|
||||||
|
for (let i = 0; i < RadarImageData.data.length; i += 4) {
|
||||||
|
// i + 0 = red
|
||||||
|
// i + 1 = green
|
||||||
|
// i + 2 = blue
|
||||||
|
// i + 3 = alpha (0 = transparent, 255 = opaque)
|
||||||
|
let R = RadarImageData.data[i];
|
||||||
|
let G = RadarImageData.data[i + 1];
|
||||||
|
let B = RadarImageData.data[i + 2];
|
||||||
|
let A = RadarImageData.data[i + 3];
|
||||||
|
|
||||||
|
// is this pixel the old rgb?
|
||||||
|
if ((R === 0 && G === 0 && B === 0)
|
||||||
|
|| (R === 0 && G === 236 && B === 236)
|
||||||
|
|| (R === 1 && G === 160 && B === 246)
|
||||||
|
|| (R === 0 && G === 0 && B === 246)) {
|
||||||
|
// change to your new rgb
|
||||||
|
|
||||||
|
// Transparent
|
||||||
|
R = 0;
|
||||||
|
G = 0;
|
||||||
|
B = 0;
|
||||||
|
A = 0;
|
||||||
|
} else if ((R === 0 && G === 255 && B === 0)) {
|
||||||
|
// Light Green 1
|
||||||
|
R = 49;
|
||||||
|
G = 210;
|
||||||
|
B = 22;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 0 && G === 200 && B === 0)) {
|
||||||
|
// Light Green 2
|
||||||
|
R = 0;
|
||||||
|
G = 142;
|
||||||
|
B = 0;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 0 && G === 144 && B === 0)) {
|
||||||
|
// Dark Green 1
|
||||||
|
R = 20;
|
||||||
|
G = 90;
|
||||||
|
B = 15;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 255 && G === 255 && B === 0)) {
|
||||||
|
// Dark Green 2
|
||||||
|
R = 10;
|
||||||
|
G = 40;
|
||||||
|
B = 10;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 231 && G === 192 && B === 0)) {
|
||||||
|
// Yellow
|
||||||
|
R = 196;
|
||||||
|
G = 179;
|
||||||
|
B = 70;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 255 && G === 144 && B === 0)) {
|
||||||
|
// Orange
|
||||||
|
R = 190;
|
||||||
|
G = 72;
|
||||||
|
B = 19;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 214 && G === 0 && B === 0)
|
||||||
|
|| (R === 255 && G === 0 && B === 0)) {
|
||||||
|
// Red
|
||||||
|
R = 171;
|
||||||
|
G = 14;
|
||||||
|
B = 14;
|
||||||
|
A = 255;
|
||||||
|
} else if ((R === 192 && G === 0 && B === 0)
|
||||||
|
|| (R === 255 && G === 0 && B === 255)) {
|
||||||
|
// Brown
|
||||||
|
R = 115;
|
||||||
|
G = 31;
|
||||||
|
B = 4;
|
||||||
|
A = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
RadarImageData.data[i] = R;
|
||||||
|
RadarImageData.data[i + 1] = G;
|
||||||
|
RadarImageData.data[i + 2] = B;
|
||||||
|
RadarImageData.data[i + 3] = A;
|
||||||
|
}
|
||||||
|
|
||||||
|
RadarContext.putImageData(RadarImageData, 0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const 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 (let i = 0; i < radarImageData.data.length; i += 4) {
|
||||||
|
// i + 0 = red
|
||||||
|
// i + 1 = green
|
||||||
|
// i + 2 = blue
|
||||||
|
// i + 3 = alpha (0 = transparent, 255 = opaque)
|
||||||
|
|
||||||
|
// is this pixel the old rgb?
|
||||||
|
if ((mapImageData.data[i] < 116 && mapImageData.data[i + 1] < 116 && mapImageData.data[i + 2] < 116)) {
|
||||||
|
// change to your new rgb
|
||||||
|
|
||||||
|
// Transparent
|
||||||
|
radarImageData.data[i] = 0;
|
||||||
|
radarImageData.data[i + 1] = 0;
|
||||||
|
radarImageData.data[i + 2] = 0;
|
||||||
|
radarImageData.data[i + 3] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
radarContext.putImageData(radarImageData, 0, 0);
|
||||||
|
|
||||||
|
mapContext.drawImage(radarContext.canvas, 0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getXYFromLatitudeLongitudeDoppler,
|
||||||
|
getXYFromLatitudeLongitudeMap,
|
||||||
|
removeDopplerRadarImageNoise,
|
||||||
|
mergeDopplerRadarImage,
|
||||||
|
};
|
|
@ -6,6 +6,7 @@ import { text } from './utils/fetch.mjs';
|
||||||
import { rewriteUrl } from './utils/cors.mjs';
|
import { rewriteUrl } from './utils/cors.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
import { registerDisplay } from './navigation.mjs';
|
import { registerDisplay } from './navigation.mjs';
|
||||||
|
import * as utils from './radar-utils.mjs';
|
||||||
|
|
||||||
class Radar extends WeatherDisplay {
|
class Radar extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
|
@ -109,7 +110,7 @@ class Radar extends WeatherDisplay {
|
||||||
const height = 1600;
|
const height = 1600;
|
||||||
offsetX *= 2;
|
offsetX *= 2;
|
||||||
offsetY *= 2;
|
offsetY *= 2;
|
||||||
const sourceXY = Radar.getXYFromLatitudeLongitudeMap(weatherParameters, offsetX, offsetY);
|
const sourceXY = utils.getXYFromLatitudeLongitudeMap(weatherParameters, offsetX, offsetY);
|
||||||
|
|
||||||
// create working context for manipulation
|
// create working context for manipulation
|
||||||
const workingCanvas = document.createElement('canvas');
|
const workingCanvas = document.createElement('canvas');
|
||||||
|
@ -121,7 +122,7 @@ class Radar extends WeatherDisplay {
|
||||||
// calculate radar offsets
|
// calculate radar offsets
|
||||||
const radarOffsetX = 120;
|
const radarOffsetX = 120;
|
||||||
const radarOffsetY = 70;
|
const radarOffsetY = 70;
|
||||||
const radarSourceXY = Radar.getXYFromLatitudeLongitudeDoppler(weatherParameters, offsetX, offsetY);
|
const radarSourceXY = utils.getXYFromLatitudeLongitudeDoppler(weatherParameters, offsetX, offsetY);
|
||||||
const radarSourceX = radarSourceXY.x / 2;
|
const radarSourceX = radarSourceXY.x / 2;
|
||||||
const radarSourceY = radarSourceXY.y / 2;
|
const radarSourceY = radarSourceXY.y / 2;
|
||||||
|
|
||||||
|
@ -179,10 +180,10 @@ class Radar extends WeatherDisplay {
|
||||||
cropContext.imageSmoothingEnabled = false;
|
cropContext.imageSmoothingEnabled = false;
|
||||||
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
|
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
|
||||||
// clean the image
|
// clean the image
|
||||||
Radar.removeDopplerRadarImageNoise(cropContext);
|
utils.removeDopplerRadarImageNoise(cropContext);
|
||||||
|
|
||||||
// merge the radar and map
|
// merge the radar and map
|
||||||
Radar.mergeDopplerRadarImage(context, cropContext);
|
utils.mergeDopplerRadarImage(context, cropContext);
|
||||||
|
|
||||||
const elem = this.fillTemplate('frame', { map: { type: 'img', src: canvas.toDataURL() } });
|
const elem = this.fillTemplate('frame', { map: { type: 'img', src: canvas.toDataURL() } });
|
||||||
|
|
||||||
|
@ -218,185 +219,6 @@ class Radar extends WeatherDisplay {
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getXYFromLatitudeLongitudeMap(pos, offsetX, offsetY) {
|
|
||||||
let y = 0;
|
|
||||||
let x = 0;
|
|
||||||
const imgHeight = 3200;
|
|
||||||
const imgWidth = 5100;
|
|
||||||
|
|
||||||
y = (51.75 - pos.latitude) * 55.2;
|
|
||||||
// center map
|
|
||||||
y -= offsetY;
|
|
||||||
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (y > (imgHeight - (offsetY * 2))) {
|
|
||||||
y = imgHeight - (offsetY * 2);
|
|
||||||
} else if (y < 0) {
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = ((-130.37 - pos.longitude) * 41.775) * -1;
|
|
||||||
// center map
|
|
||||||
x -= offsetX;
|
|
||||||
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (x > (imgWidth - (offsetX * 2))) {
|
|
||||||
x = imgWidth - (offsetX * 2);
|
|
||||||
} else if (x < 0) {
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x: x * 2, y: y * 2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getXYFromLatitudeLongitudeDoppler(pos, offsetX, offsetY) {
|
|
||||||
let y = 0;
|
|
||||||
let x = 0;
|
|
||||||
const imgHeight = 6000;
|
|
||||||
const imgWidth = 2800;
|
|
||||||
|
|
||||||
y = (51 - pos.latitude) * 61.4481;
|
|
||||||
// center map
|
|
||||||
y -= offsetY;
|
|
||||||
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (y > (imgHeight - (offsetY * 2))) {
|
|
||||||
y = imgHeight - (offsetY * 2);
|
|
||||||
} else if (y < 0) {
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = ((-129.138 - pos.longitude) * 42.1768) * -1;
|
|
||||||
// center map
|
|
||||||
x -= offsetX;
|
|
||||||
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (x > (imgWidth - (offsetX * 2))) {
|
|
||||||
x = imgWidth - (offsetX * 2);
|
|
||||||
} else if (x < 0) {
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x: x * 2, y: y * 2 };
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeDopplerRadarImageNoise(RadarContext) {
|
|
||||||
const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
|
|
||||||
|
|
||||||
// examine every pixel,
|
|
||||||
// change any old rgb to the new-rgb
|
|
||||||
for (let i = 0; i < RadarImageData.data.length; i += 4) {
|
|
||||||
// i + 0 = red
|
|
||||||
// i + 1 = green
|
|
||||||
// i + 2 = blue
|
|
||||||
// i + 3 = alpha (0 = transparent, 255 = opaque)
|
|
||||||
let R = RadarImageData.data[i];
|
|
||||||
let G = RadarImageData.data[i + 1];
|
|
||||||
let B = RadarImageData.data[i + 2];
|
|
||||||
let A = RadarImageData.data[i + 3];
|
|
||||||
|
|
||||||
// is this pixel the old rgb?
|
|
||||||
if ((R === 0 && G === 0 && B === 0)
|
|
||||||
|| (R === 0 && G === 236 && B === 236)
|
|
||||||
|| (R === 1 && G === 160 && B === 246)
|
|
||||||
|| (R === 0 && G === 0 && B === 246)) {
|
|
||||||
// change to your new rgb
|
|
||||||
|
|
||||||
// Transparent
|
|
||||||
R = 0;
|
|
||||||
G = 0;
|
|
||||||
B = 0;
|
|
||||||
A = 0;
|
|
||||||
} else if ((R === 0 && G === 255 && B === 0)) {
|
|
||||||
// Light Green 1
|
|
||||||
R = 49;
|
|
||||||
G = 210;
|
|
||||||
B = 22;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 0 && G === 200 && B === 0)) {
|
|
||||||
// Light Green 2
|
|
||||||
R = 0;
|
|
||||||
G = 142;
|
|
||||||
B = 0;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 0 && G === 144 && B === 0)) {
|
|
||||||
// Dark Green 1
|
|
||||||
R = 20;
|
|
||||||
G = 90;
|
|
||||||
B = 15;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 255 && G === 255 && B === 0)) {
|
|
||||||
// Dark Green 2
|
|
||||||
R = 10;
|
|
||||||
G = 40;
|
|
||||||
B = 10;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 231 && G === 192 && B === 0)) {
|
|
||||||
// Yellow
|
|
||||||
R = 196;
|
|
||||||
G = 179;
|
|
||||||
B = 70;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 255 && G === 144 && B === 0)) {
|
|
||||||
// Orange
|
|
||||||
R = 190;
|
|
||||||
G = 72;
|
|
||||||
B = 19;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 214 && G === 0 && B === 0)
|
|
||||||
|| (R === 255 && G === 0 && B === 0)) {
|
|
||||||
// Red
|
|
||||||
R = 171;
|
|
||||||
G = 14;
|
|
||||||
B = 14;
|
|
||||||
A = 255;
|
|
||||||
} else if ((R === 192 && G === 0 && B === 0)
|
|
||||||
|| (R === 255 && G === 0 && B === 255)) {
|
|
||||||
// Brown
|
|
||||||
R = 115;
|
|
||||||
G = 31;
|
|
||||||
B = 4;
|
|
||||||
A = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadarImageData.data[i] = R;
|
|
||||||
RadarImageData.data[i + 1] = G;
|
|
||||||
RadarImageData.data[i + 2] = B;
|
|
||||||
RadarImageData.data[i + 3] = A;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadarContext.putImageData(RadarImageData, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (let i = 0; i < radarImageData.data.length; i += 4) {
|
|
||||||
// i + 0 = red
|
|
||||||
// i + 1 = green
|
|
||||||
// i + 2 = blue
|
|
||||||
// i + 3 = alpha (0 = transparent, 255 = opaque)
|
|
||||||
|
|
||||||
// is this pixel the old rgb?
|
|
||||||
if ((mapImageData.data[i] < 116 && mapImageData.data[i + 1] < 116 && mapImageData.data[i + 2] < 116)) {
|
|
||||||
// change to your new rgb
|
|
||||||
|
|
||||||
// Transparent
|
|
||||||
radarImageData.data[i] = 0;
|
|
||||||
radarImageData.data[i + 1] = 0;
|
|
||||||
radarImageData.data[i + 2] = 0;
|
|
||||||
radarImageData.data[i + 3] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
radarContext.putImageData(radarImageData, 0, 0);
|
|
||||||
|
|
||||||
mapContext.drawImage(radarContext.canvas, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
|
|
205
server/scripts/modules/regionalforecast-utils.mjs
Normal file
205
server/scripts/modules/regionalforecast-utils.mjs
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
import { getWeatherRegionalIconFromIconLink } from './icons.mjs';
|
||||||
|
import { preloadImg } from './utils/image.mjs';
|
||||||
|
import { json } from './utils/fetch.mjs';
|
||||||
|
|
||||||
|
const buildForecast = (forecast, city, cityXY) => ({
|
||||||
|
daytime: forecast.isDaytime,
|
||||||
|
temperature: forecast.temperature || 0,
|
||||||
|
name: formatCity(city.city),
|
||||||
|
icon: forecast.icon,
|
||||||
|
x: cityXY.x,
|
||||||
|
y: cityXY.y,
|
||||||
|
time: forecast.startTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRegionalObservation = async (point, city) => {
|
||||||
|
try {
|
||||||
|
// get stations
|
||||||
|
const stations = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/stations`);
|
||||||
|
|
||||||
|
// get the first station
|
||||||
|
const station = stations.features[0].id;
|
||||||
|
// get the observation data
|
||||||
|
const observation = await json(`${station}/observations/latest`);
|
||||||
|
// preload the image
|
||||||
|
if (!observation.properties.icon) return false;
|
||||||
|
preloadImg(getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
||||||
|
// return the observation
|
||||||
|
return observation.properties;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Unable to get regional observations for ${city.Name ?? city.city}`);
|
||||||
|
console.error(e.status, e.responseJSON);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// utility latitude/pixel conversions
|
||||||
|
const getXYFromLatitudeLongitude = (Latitude, Longitude, OffsetX, OffsetY, state) => {
|
||||||
|
if (state === 'AK') return getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
||||||
|
if (state === 'HI') return getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
||||||
|
let y = 0;
|
||||||
|
let x = 0;
|
||||||
|
const ImgHeight = 1600;
|
||||||
|
const ImgWidth = 2550;
|
||||||
|
|
||||||
|
y = (50.5 - Latitude) * 55.2;
|
||||||
|
y -= OffsetY; // Centers map.
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (y > (ImgHeight - (OffsetY * 2))) {
|
||||||
|
y = ImgHeight - (OffsetY * 2);
|
||||||
|
} else if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = ((-127.5 - Longitude) * 41.775) * -1;
|
||||||
|
x -= OffsetX; // Centers map.
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (x > (ImgWidth - (OffsetX * 2))) {
|
||||||
|
x = ImgWidth - (OffsetX * 2);
|
||||||
|
} else if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getXYFromLatitudeLongitudeAK = (Latitude, Longitude, OffsetX, OffsetY) => {
|
||||||
|
let y = 0;
|
||||||
|
let x = 0;
|
||||||
|
const ImgHeight = 1142;
|
||||||
|
const ImgWidth = 1200;
|
||||||
|
|
||||||
|
y = (73.0 - Latitude) * 56;
|
||||||
|
y -= OffsetY; // Centers map.
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (y > (ImgHeight - (OffsetY * 2))) {
|
||||||
|
y = ImgHeight - (OffsetY * 2);
|
||||||
|
} else if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = ((-175.0 - Longitude) * 25.0) * -1;
|
||||||
|
x -= OffsetX; // Centers map.
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (x > (ImgWidth - (OffsetX * 2))) {
|
||||||
|
x = ImgWidth - (OffsetX * 2);
|
||||||
|
} else if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getXYFromLatitudeLongitudeHI = (Latitude, Longitude, OffsetX, OffsetY) => {
|
||||||
|
let y = 0;
|
||||||
|
let x = 0;
|
||||||
|
const ImgHeight = 571;
|
||||||
|
const ImgWidth = 600;
|
||||||
|
|
||||||
|
y = (25 - Latitude) * 55.2;
|
||||||
|
y -= OffsetY; // Centers map.
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (y > (ImgHeight - (OffsetY * 2))) {
|
||||||
|
y = ImgHeight - (OffsetY * 2);
|
||||||
|
} else if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = ((-164.5 - Longitude) * 41.775) * -1;
|
||||||
|
x -= OffsetX; // Centers map.
|
||||||
|
// Do not allow the map to exceed the max/min coordinates.
|
||||||
|
if (x > (ImgWidth - (OffsetX * 2))) {
|
||||||
|
x = ImgWidth - (OffsetX * 2);
|
||||||
|
} else if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMinMaxLatitudeLongitude = (X, Y, OffsetX, OffsetY, state) => {
|
||||||
|
if (state === 'AK') return getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
||||||
|
if (state === 'HI') return 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getXYForCity = (City, MaxLatitude, MinLongitude, state) => {
|
||||||
|
if (state === 'AK') getXYForCityAK(City, MaxLatitude, MinLongitude);
|
||||||
|
if (state === 'HI') getXYForCityHI(City, MaxLatitude, MinLongitude);
|
||||||
|
let x = (City.lon - MinLongitude) * 57;
|
||||||
|
let y = (MaxLatitude - City.lat) * 70;
|
||||||
|
|
||||||
|
if (y < 30) y = 30;
|
||||||
|
if (y > 282) y = 282;
|
||||||
|
|
||||||
|
if (x < 40) x = 40;
|
||||||
|
if (x > 580) x = 580;
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getXYForCityAK = (City, MaxLatitude, MinLongitude) => {
|
||||||
|
let x = (City.lon - MinLongitude) * 37;
|
||||||
|
let y = (MaxLatitude - City.lat) * 70;
|
||||||
|
|
||||||
|
if (y < 30) y = 30;
|
||||||
|
if (y > 282) y = 282;
|
||||||
|
|
||||||
|
if (x < 40) x = 40;
|
||||||
|
if (x > 580) x = 580;
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getXYForCityHI = (City, MaxLatitude, MinLongitude) => {
|
||||||
|
let x = (City.lon - MinLongitude) * 57;
|
||||||
|
let y = (MaxLatitude - City.lat) * 70;
|
||||||
|
|
||||||
|
if (y < 30) y = 30;
|
||||||
|
if (y > 282) y = 282;
|
||||||
|
|
||||||
|
if (x < 40) x = 40;
|
||||||
|
if (x > 580) x = 580;
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
// to fit on the map, remove anything after punctuation and then limit to 15 characters
|
||||||
|
const formatCity = (city) => city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
||||||
|
|
||||||
|
export {
|
||||||
|
buildForecast,
|
||||||
|
getRegionalObservation,
|
||||||
|
getXYFromLatitudeLongitude,
|
||||||
|
getMinMaxLatitudeLongitude,
|
||||||
|
getXYForCity,
|
||||||
|
formatCity,
|
||||||
|
};
|
|
@ -10,6 +10,7 @@ import { preloadImg } from './utils/image.mjs';
|
||||||
import { DateTime } from '../vendor/auto/luxon.mjs';
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
import { registerDisplay } from './navigation.mjs';
|
import { registerDisplay } from './navigation.mjs';
|
||||||
|
import * as utils from './regionalforecast-utils.mjs';
|
||||||
|
|
||||||
class RegionalForecast extends WeatherDisplay {
|
class RegionalForecast extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
|
@ -38,10 +39,10 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
y: 117,
|
y: 117,
|
||||||
};
|
};
|
||||||
// get user's location in x/y
|
// get user's location in x/y
|
||||||
const sourceXY = RegionalForecast.getXYFromLatitudeLongitude(weatherParameters.latitude, weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state);
|
const sourceXY = utils.getXYFromLatitudeLongitude(weatherParameters.latitude, weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state);
|
||||||
|
|
||||||
// get latitude and longitude limits
|
// get latitude and longitude limits
|
||||||
const minMaxLatLon = RegionalForecast.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, weatherParameters.state);
|
const minMaxLatLon = utils.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, weatherParameters.state);
|
||||||
|
|
||||||
// get a target distance
|
// get a target distance
|
||||||
let targetDistance = 2.5;
|
let targetDistance = 2.5;
|
||||||
|
@ -75,12 +76,12 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
if (!city.point) throw new Error('No pre-loaded point');
|
if (!city.point) throw new Error('No pre-loaded point');
|
||||||
|
|
||||||
// start off the observation task
|
// start off the observation task
|
||||||
const observationPromise = RegionalForecast.getRegionalObservation(city.point, city);
|
const observationPromise = utils.getRegionalObservation(city.point, city);
|
||||||
|
|
||||||
const forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
const forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
||||||
|
|
||||||
// get XY on map for city
|
// get XY on map for city
|
||||||
const cityXY = RegionalForecast.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
const cityXY = utils.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
||||||
|
|
||||||
// wait for the regional observation if it's not done yet
|
// wait for the regional observation if it's not done yet
|
||||||
const observation = await observationPromise;
|
const observation = await observationPromise;
|
||||||
|
@ -88,7 +89,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
const regionalObservation = {
|
const regionalObservation = {
|
||||||
daytime: !!observation.icon.match(/\/day\//),
|
daytime: !!observation.icon.match(/\/day\//),
|
||||||
temperature: celsiusToFahrenheit(observation.temperature.value),
|
temperature: celsiusToFahrenheit(observation.temperature.value),
|
||||||
name: RegionalForecast.formatCity(city.city),
|
name: utils.formatCity(city.city),
|
||||||
icon: observation.icon,
|
icon: observation.icon,
|
||||||
x: cityXY.x,
|
x: cityXY.x,
|
||||||
y: cityXY.y,
|
y: cityXY.y,
|
||||||
|
@ -104,8 +105,8 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// always skip the first forecast index because it's what's going on right now
|
// always skip the first forecast index because it's what's going on right now
|
||||||
return [
|
return [
|
||||||
regionalObservation,
|
regionalObservation,
|
||||||
RegionalForecast.buildForecast(forecast.properties.periods[1], city, cityXY),
|
utils.buildForecast(forecast.properties.periods[1], city, cityXY),
|
||||||
RegionalForecast.buildForecast(forecast.properties.periods[2], city, cityXY),
|
utils.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||||
];
|
];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`No regional forecast data for '${city.name ?? city.city}'`);
|
console.log(`No regional forecast data for '${city.name ?? city.city}'`);
|
||||||
|
@ -133,203 +134,6 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildForecast(forecast, city, cityXY) {
|
|
||||||
return {
|
|
||||||
daytime: forecast.isDaytime,
|
|
||||||
temperature: forecast.temperature || 0,
|
|
||||||
name: RegionalForecast.formatCity(city.city),
|
|
||||||
icon: forecast.icon,
|
|
||||||
x: cityXY.x,
|
|
||||||
y: cityXY.y,
|
|
||||||
time: forecast.startTime,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getRegionalObservation(point, city) {
|
|
||||||
try {
|
|
||||||
// get stations
|
|
||||||
const stations = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/stations`);
|
|
||||||
|
|
||||||
// get the first station
|
|
||||||
const station = stations.features[0].id;
|
|
||||||
// get the observation data
|
|
||||||
const observation = await json(`${station}/observations/latest`);
|
|
||||||
// preload the image
|
|
||||||
if (!observation.properties.icon) return false;
|
|
||||||
preloadImg(getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
|
||||||
// return the observation
|
|
||||||
return observation.properties;
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Unable to get regional observations for ${city.Name ?? city.city}`);
|
|
||||||
console.error(e.status, e.responseJSON);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility latitude/pixel conversions
|
|
||||||
static getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
|
||||||
if (state === 'AK') return RegionalForecast.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
|
||||||
if (state === 'HI') return RegionalForecast.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
|
||||||
let y = 0;
|
|
||||||
let x = 0;
|
|
||||||
const ImgHeight = 1600;
|
|
||||||
const ImgWidth = 2550;
|
|
||||||
|
|
||||||
y = (50.5 - Latitude) * 55.2;
|
|
||||||
y -= OffsetY; // Centers map.
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (y > (ImgHeight - (OffsetY * 2))) {
|
|
||||||
y = ImgHeight - (OffsetY * 2);
|
|
||||||
} else if (y < 0) {
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = ((-127.5 - Longitude) * 41.775) * -1;
|
|
||||||
x -= OffsetX; // Centers map.
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (x > (ImgWidth - (OffsetX * 2))) {
|
|
||||||
x = ImgWidth - (OffsetX * 2);
|
|
||||||
} else if (x < 0) {
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY) {
|
|
||||||
let y = 0;
|
|
||||||
let x = 0;
|
|
||||||
const ImgHeight = 1142;
|
|
||||||
const ImgWidth = 1200;
|
|
||||||
|
|
||||||
y = (73.0 - Latitude) * 56;
|
|
||||||
y -= OffsetY; // Centers map.
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (y > (ImgHeight - (OffsetY * 2))) {
|
|
||||||
y = ImgHeight - (OffsetY * 2);
|
|
||||||
} else if (y < 0) {
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = ((-175.0 - Longitude) * 25.0) * -1;
|
|
||||||
x -= OffsetX; // Centers map.
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (x > (ImgWidth - (OffsetX * 2))) {
|
|
||||||
x = ImgWidth - (OffsetX * 2);
|
|
||||||
} else if (x < 0) {
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
|
||||||
let y = 0;
|
|
||||||
let x = 0;
|
|
||||||
const ImgHeight = 571;
|
|
||||||
const ImgWidth = 600;
|
|
||||||
|
|
||||||
y = (25 - Latitude) * 55.2;
|
|
||||||
y -= OffsetY; // Centers map.
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (y > (ImgHeight - (OffsetY * 2))) {
|
|
||||||
y = ImgHeight - (OffsetY * 2);
|
|
||||||
} else if (y < 0) {
|
|
||||||
y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = ((-164.5 - Longitude) * 41.775) * -1;
|
|
||||||
x -= OffsetX; // Centers map.
|
|
||||||
// Do not allow the map to exceed the max/min coordinates.
|
|
||||||
if (x > (ImgWidth - (OffsetX * 2))) {
|
|
||||||
x = ImgWidth - (OffsetX * 2);
|
|
||||||
} else if (x < 0) {
|
|
||||||
x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
|
||||||
if (state === 'AK') return RegionalForecast.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
|
||||||
if (state === 'HI') return RegionalForecast.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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
|
||||||
if (state === 'AK') RegionalForecast.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
|
||||||
if (state === 'HI') RegionalForecast.getXYForCityHI(City, MaxLatitude, MinLongitude);
|
|
||||||
let x = (City.lon - MinLongitude) * 57;
|
|
||||||
let y = (MaxLatitude - City.lat) * 70;
|
|
||||||
|
|
||||||
if (y < 30) y = 30;
|
|
||||||
if (y > 282) y = 282;
|
|
||||||
|
|
||||||
if (x < 40) x = 40;
|
|
||||||
if (x > 580) x = 580;
|
|
||||||
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getXYForCityAK(City, MaxLatitude, MinLongitude) {
|
|
||||||
let x = (City.lon - MinLongitude) * 37;
|
|
||||||
let y = (MaxLatitude - City.lat) * 70;
|
|
||||||
|
|
||||||
if (y < 30) y = 30;
|
|
||||||
if (y > 282) y = 282;
|
|
||||||
|
|
||||||
if (x < 40) x = 40;
|
|
||||||
if (x > 580) x = 580;
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
static getXYForCityHI(City, MaxLatitude, MinLongitude) {
|
|
||||||
let x = (City.lon - MinLongitude) * 57;
|
|
||||||
let y = (MaxLatitude - City.lat) * 70;
|
|
||||||
|
|
||||||
if (y < 30) y = 30;
|
|
||||||
if (y > 282) y = 282;
|
|
||||||
|
|
||||||
if (x < 40) x = 40;
|
|
||||||
if (x > 580) x = 580;
|
|
||||||
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
// to fit on the map, remove anything after punctuation and then limit to 15 characters
|
|
||||||
static formatCity(city) {
|
|
||||||
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawCanvas() {
|
drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
// break up data into useful values
|
// break up data into useful values
|
||||||
|
|
|
@ -112,7 +112,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
// set up variables
|
// set up variables
|
||||||
const cities = this.data;
|
const cities = this.data;
|
||||||
|
|
||||||
this.elem.querySelector('.header .title.dual .bottom').innerHTML = `For ${TravelForecast.getTravelCitiesDayName(cities)}`;
|
this.elem.querySelector('.header .title.dual .bottom').innerHTML = `For ${getTravelCitiesDayName(cities)}`;
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
@ -140,9 +140,14 @@ class TravelForecast extends WeatherDisplay {
|
||||||
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTravelCitiesDayName(cities) {
|
// necessary to get the lastest long canvas when scrolling
|
||||||
// effectively returns early on the first found date
|
getLongCanvas() {
|
||||||
return cities.reduce((dayName, city) => {
|
return this.longCanvas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// effectively returns early on the first found date
|
||||||
|
const getTravelCitiesDayName = (cities) => cities.reduce((dayName, city) => {
|
||||||
if (city && dayName === '') {
|
if (city && dayName === '') {
|
||||||
// today or tomorrow
|
// today or tomorrow
|
||||||
const day = DateTime.local().plus({ days: (city.today) ? 0 : 1 });
|
const day = DateTime.local().plus({ days: (city.today) ? 0 : 1 });
|
||||||
|
@ -150,14 +155,8 @@ class TravelForecast extends WeatherDisplay {
|
||||||
return day.toLocaleString({ weekday: 'long' });
|
return day.toLocaleString({ weekday: 'long' });
|
||||||
}
|
}
|
||||||
return dayName;
|
return dayName;
|
||||||
}, '');
|
}, '')
|
||||||
}
|
;
|
||||||
|
|
||||||
// necessary to get the lastest long canvas when scrolling
|
|
||||||
getLongCanvas() {
|
|
||||||
return this.longCanvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register display, not active by default
|
// register display, not active by default
|
||||||
registerDisplay(new TravelForecast(4, 'travel', false));
|
registerDisplay(new TravelForecast(4, 'travel', false));
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -73,7 +73,10 @@ button {
|
||||||
.autocomplete-suggestions {
|
.autocomplete-suggestions {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
/*overflow: auto;*/
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-suggestion {
|
.autocomplete-suggestion {
|
||||||
|
|
Loading…
Reference in a new issue