class static code cleanup
This commit is contained in:
parent
0c8db4f38e
commit
f633631532
|
@ -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,26 +170,27 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
this.getDataCallbacks.push(() => resolve(this.parseData()));
|
this.getDataCallbacks.push(() => resolve(this.parseData()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static shortConditions(_condition) {
|
|
||||||
let condition = _condition;
|
|
||||||
condition = condition.replace(/Light/g, 'L');
|
|
||||||
condition = condition.replace(/Heavy/g, 'H');
|
|
||||||
condition = condition.replace(/Partly/g, 'P');
|
|
||||||
condition = condition.replace(/Mostly/g, 'M');
|
|
||||||
condition = condition.replace(/Few/g, 'F');
|
|
||||||
condition = condition.replace(/Thunderstorm/g, 'T\'storm');
|
|
||||||
condition = condition.replace(/ in /g, '');
|
|
||||||
condition = condition.replace(/Vicinity/g, '');
|
|
||||||
condition = condition.replace(/ and /g, ' ');
|
|
||||||
condition = condition.replace(/Freezing Rain/g, 'Frz Rn');
|
|
||||||
condition = condition.replace(/Freezing/g, 'Frz');
|
|
||||||
condition = condition.replace(/Unknown Precip/g, '');
|
|
||||||
condition = condition.replace(/L Snow Fog/g, 'L Snw/Fog');
|
|
||||||
condition = condition.replace(/ with /g, '/');
|
|
||||||
return condition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shortConditions = (_condition) => {
|
||||||
|
let condition = _condition;
|
||||||
|
condition = condition.replace(/Light/g, 'L');
|
||||||
|
condition = condition.replace(/Heavy/g, 'H');
|
||||||
|
condition = condition.replace(/Partly/g, 'P');
|
||||||
|
condition = condition.replace(/Mostly/g, 'M');
|
||||||
|
condition = condition.replace(/Few/g, 'F');
|
||||||
|
condition = condition.replace(/Thunderstorm/g, 'T\'storm');
|
||||||
|
condition = condition.replace(/ in /g, '');
|
||||||
|
condition = condition.replace(/Vicinity/g, '');
|
||||||
|
condition = condition.replace(/ and /g, ' ');
|
||||||
|
condition = condition.replace(/Freezing Rain/g, 'Frz Rn');
|
||||||
|
condition = condition.replace(/Freezing/g, 'Frz');
|
||||||
|
condition = condition.replace(/Unknown Precip/g, '');
|
||||||
|
condition = condition.replace(/L Snow Fog/g, 'L Snw/Fog');
|
||||||
|
condition = condition.replace(/ with /g, '/');
|
||||||
|
return condition;
|
||||||
|
};
|
||||||
|
|
||||||
const display = new CurrentWeather(0, 'current-weather');
|
const display = new CurrentWeather(0, 'current-weather');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
|
|
|
@ -36,95 +36,11 @@ 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
|
|
||||||
static parse(fullForecast) {
|
|
||||||
// create a list of days starting with today
|
|
||||||
const Days = [0, 1, 2, 3, 4, 5, 6];
|
|
||||||
|
|
||||||
const dates = Days.map((shift) => {
|
|
||||||
const date = 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) => {
|
|
||||||
// create the destination object if necessary
|
|
||||||
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 = getWeatherIconFromIconLink(period.icon);
|
|
||||||
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
|
|
||||||
fDay.dayName = dates[destIndex];
|
|
||||||
|
|
||||||
// preload the icon
|
|
||||||
preloadImg(fDay.icon);
|
|
||||||
|
|
||||||
if (period.isDaytime) {
|
|
||||||
// day time is the high temperature
|
|
||||||
fDay.high = period.temperature;
|
|
||||||
destIndex += 1;
|
|
||||||
} else {
|
|
||||||
// low temperature
|
|
||||||
fDay.low = period.temperature;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return forecast;
|
|
||||||
}
|
|
||||||
|
|
||||||
static shortenExtendedForecastText(long) {
|
|
||||||
const regexList = [
|
|
||||||
[/ and /ig, ' '],
|
|
||||||
[/Slight /ig, ''],
|
|
||||||
[/Chance /ig, ''],
|
|
||||||
[/Very /ig, ''],
|
|
||||||
[/Patchy /ig, ''],
|
|
||||||
[/Areas /ig, ''],
|
|
||||||
[/Dense /ig, ''],
|
|
||||||
[/Thunderstorm/g, 'T\'Storm'],
|
|
||||||
];
|
|
||||||
// run all regexes
|
|
||||||
const short = regexList.reduce((working, [regex, replace]) => working.replace(regex, replace), long);
|
|
||||||
|
|
||||||
let conditions = short.split(' ');
|
|
||||||
if (short.indexOf('then') !== -1) {
|
|
||||||
conditions = short.split(' then ');
|
|
||||||
conditions = conditions[1].split(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
let short1 = conditions[0].substr(0, 10);
|
|
||||||
let short2 = '';
|
|
||||||
if (conditions[1]) {
|
|
||||||
if (!short1.endsWith('.')) {
|
|
||||||
short2 = conditions[1].substr(0, 10);
|
|
||||||
} else {
|
|
||||||
short1 = short1.replace(/\./, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (short2 === 'Blowing') {
|
|
||||||
short2 = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = short1;
|
|
||||||
if (short2 !== '') {
|
|
||||||
result += ` ${short2}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
|
|
||||||
|
@ -160,5 +76,89 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const Days = [0, 1, 2, 3, 4, 5, 6];
|
||||||
|
|
||||||
|
const dates = Days.map((shift) => {
|
||||||
|
const date = 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) => {
|
||||||
|
// create the destination object if necessary
|
||||||
|
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 = getWeatherIconFromIconLink(period.icon);
|
||||||
|
fDay.text = shortenExtendedForecastText(period.shortForecast);
|
||||||
|
fDay.dayName = dates[destIndex];
|
||||||
|
|
||||||
|
// preload the icon
|
||||||
|
preloadImg(fDay.icon);
|
||||||
|
|
||||||
|
if (period.isDaytime) {
|
||||||
|
// day time is the high temperature
|
||||||
|
fDay.high = period.temperature;
|
||||||
|
destIndex += 1;
|
||||||
|
} else {
|
||||||
|
// low temperature
|
||||||
|
fDay.low = period.temperature;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return forecast;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortenExtendedForecastText = (long) => {
|
||||||
|
const regexList = [
|
||||||
|
[/ and /ig, ' '],
|
||||||
|
[/Slight /ig, ''],
|
||||||
|
[/Chance /ig, ''],
|
||||||
|
[/Very /ig, ''],
|
||||||
|
[/Patchy /ig, ''],
|
||||||
|
[/Areas /ig, ''],
|
||||||
|
[/Dense /ig, ''],
|
||||||
|
[/Thunderstorm/g, 'T\'Storm'],
|
||||||
|
];
|
||||||
|
// run all regexes
|
||||||
|
const short = regexList.reduce((working, [regex, replace]) => working.replace(regex, replace), long);
|
||||||
|
|
||||||
|
let conditions = short.split(' ');
|
||||||
|
if (short.indexOf('then') !== -1) {
|
||||||
|
conditions = short.split(' then ');
|
||||||
|
conditions = conditions[1].split(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
let short1 = conditions[0].substr(0, 10);
|
||||||
|
let short2 = '';
|
||||||
|
if (conditions[1]) {
|
||||||
|
if (!short1.endsWith('.')) {
|
||||||
|
short2 = conditions[1].substr(0, 10);
|
||||||
|
} else {
|
||||||
|
short1 = short1.replace(/\./, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (short2 === 'Blowing') {
|
||||||
|
short2 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = short1;
|
||||||
|
if (short2 !== '') {
|
||||||
|
result += ` ${short2}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
// 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,25 +100,24 @@ class LatestObservations extends WeatherDisplay {
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
static shortenCurrentConditions(_condition) {
|
|
||||||
let condition = _condition;
|
|
||||||
condition = condition.replace(/Light/, 'L');
|
|
||||||
condition = condition.replace(/Heavy/, 'H');
|
|
||||||
condition = condition.replace(/Partly/, 'P');
|
|
||||||
condition = condition.replace(/Mostly/, 'M');
|
|
||||||
condition = condition.replace(/Few/, 'F');
|
|
||||||
condition = condition.replace(/Thunderstorm/, 'T\'storm');
|
|
||||||
condition = condition.replace(/ in /, '');
|
|
||||||
condition = condition.replace(/Vicinity/, '');
|
|
||||||
condition = condition.replace(/ and /, ' ');
|
|
||||||
condition = condition.replace(/Freezing Rain/, 'Frz Rn');
|
|
||||||
condition = condition.replace(/Freezing/, 'Frz');
|
|
||||||
condition = condition.replace(/Unknown Precip/, '');
|
|
||||||
condition = condition.replace(/L Snow Fog/, 'L Snw/Fog');
|
|
||||||
condition = condition.replace(/ with /, '/');
|
|
||||||
return condition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const shortenCurrentConditions = (_condition) => {
|
||||||
|
let condition = _condition;
|
||||||
|
condition = condition.replace(/Light/, 'L');
|
||||||
|
condition = condition.replace(/Heavy/, 'H');
|
||||||
|
condition = condition.replace(/Partly/, 'P');
|
||||||
|
condition = condition.replace(/Mostly/, 'M');
|
||||||
|
condition = condition.replace(/Few/, 'F');
|
||||||
|
condition = condition.replace(/Thunderstorm/, 'T\'storm');
|
||||||
|
condition = condition.replace(/ in /, '');
|
||||||
|
condition = condition.replace(/Vicinity/, '');
|
||||||
|
condition = condition.replace(/ and /, ' ');
|
||||||
|
condition = condition.replace(/Freezing Rain/, 'Frz Rn');
|
||||||
|
condition = condition.replace(/Freezing/, 'Frz');
|
||||||
|
condition = condition.replace(/Unknown Precip/, '');
|
||||||
|
condition = condition.replace(/L Snow Fog/, 'L Snw/Fog');
|
||||||
|
condition = condition.replace(/ with /, '/');
|
||||||
|
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
|
|
||||||
static parse(forecast) {
|
|
||||||
// only use the first 6 lines
|
|
||||||
return forecast.properties.periods.slice(0, 6).map((text) => ({
|
|
||||||
// format day and text
|
|
||||||
DayName: text.name.toUpperCase(),
|
|
||||||
Text: text.detailedForecast,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// format the forecast
|
||||||
|
// only use the first 6 lines
|
||||||
|
const parse = (forecast) => forecast.properties.periods.slice(0, 6).map((text) => ({
|
||||||
|
// format day and text
|
||||||
|
DayName: text.name.toUpperCase(),
|
||||||
|
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,24 +140,22 @@ class TravelForecast 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;
|
|
||||||
}, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// necessary to get the lastest long canvas when scrolling
|
// necessary to get the lastest long canvas when scrolling
|
||||||
getLongCanvas() {
|
getLongCanvas() {
|
||||||
return this.longCanvas;
|
return this.longCanvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// effectively returns early on the first found date
|
||||||
|
const getTravelCitiesDayName = (cities) => 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;
|
||||||
|
}, '');
|
||||||
|
|
||||||
// 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