mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 09:09:30 -07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66a161762e | ||
|
|
707b08ee1a | ||
|
|
7900e59aab | ||
|
|
9b422dd697 | ||
|
|
e4ce0b6cc6 | ||
|
|
b0e5018179 | ||
|
|
6422589b5c |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,3 +12,4 @@ Please do not report issues with api.weather.gov being down. It's a new service
|
|||||||
Please include:
|
Please include:
|
||||||
* Web browser and OS
|
* Web browser and OS
|
||||||
* Headend Information text block from the very bottom of the web page
|
* Headend Information text block from the very bottom of the web page
|
||||||
|
* How you're running Weatherstar (Node, Dockerfile, Dockerfile.server, etc.)
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.5",
|
"version": "6.2.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.5",
|
"version": "6.2.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "6.2.5",
|
"version": "6.2.8",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from './utils/units.mjs';
|
} from './utils/units.mjs';
|
||||||
import { debugFlag } from './utils/debug.mjs';
|
import { debugFlag } from './utils/debug.mjs';
|
||||||
import { isDataStale, enhanceObservationWithMapClick } from './utils/mapclick.mjs';
|
import { isDataStale, enhanceObservationWithMapClick } from './utils/mapclick.mjs';
|
||||||
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
|
|
||||||
// some stations prefixed do not provide all the necessary data
|
// some stations prefixed do not provide all the necessary data
|
||||||
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
|
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
|
||||||
@@ -49,7 +50,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
candidateObservation = await safeJson(`${station.id}/observations`, {
|
candidateObservation = await safeJson(`${station.id}/observations`, {
|
||||||
data: {
|
data: {
|
||||||
limit: 2, // we need the two most recent observations to calculate pressure direction
|
limit: 5, // we need the two most recent observations to calculate pressure direction, and to back fill any missing data
|
||||||
},
|
},
|
||||||
retryCount: 3,
|
retryCount: 3,
|
||||||
stillWaiting: () => this.stillWaiting(),
|
stillWaiting: () => this.stillWaiting(),
|
||||||
@@ -231,7 +232,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
this.setAutoReload();
|
this.setAutoReload();
|
||||||
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.data) resolve(this.data);
|
if (this.data) resolve({ data: this.data, parameters: this.weatherParameters });
|
||||||
// data not available, put it into the data callback queue
|
// data not available, put it into the data callback queue
|
||||||
this.getDataCallbacks.push(() => resolve(this.data));
|
this.getDataCallbacks.push(() => resolve(this.data));
|
||||||
});
|
});
|
||||||
@@ -266,7 +267,7 @@ const parseData = (data) => {
|
|||||||
const kilometersConverter = distanceKilometers();
|
const kilometersConverter = distanceKilometers();
|
||||||
const pressureConverter = pressure();
|
const pressureConverter = pressure();
|
||||||
|
|
||||||
const observations = data.features[0].properties;
|
const observations = backfill(data.features);
|
||||||
// values from api are provided in metric
|
// values from api are provided in metric
|
||||||
data.observations = observations;
|
data.observations = observations;
|
||||||
data.Temperature = temperatureConverter(observations.temperature.value);
|
data.Temperature = temperatureConverter(observations.temperature.value);
|
||||||
@@ -306,6 +307,46 @@ const parseData = (data) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// default to the latest data in the provided observations, but use older data if something is missing
|
||||||
|
const backfill = (data) => {
|
||||||
|
// make easy to use timestamps
|
||||||
|
const sortedData = data.map((observation) => {
|
||||||
|
observation.timestamp = DateTime.fromISO(observation.properties.timestamp);
|
||||||
|
return observation;
|
||||||
|
});
|
||||||
|
|
||||||
|
// sort by timestamp with [0] being the earliest
|
||||||
|
sortedData.sort((a, b) => b.timestamp - a.timestamp);
|
||||||
|
|
||||||
|
// create the result data
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
// backfill each property
|
||||||
|
Object.keys(sortedData[0].properties).forEach((key) => {
|
||||||
|
// qualify the key (must have value)
|
||||||
|
if (Object.hasOwn(sortedData[0].properties[key], 'value')) {
|
||||||
|
// backfill this property
|
||||||
|
result[key] = backfillProperty(sortedData, key);
|
||||||
|
} else {
|
||||||
|
// use the property as is
|
||||||
|
result[key] = sortedData[0].properties[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// return the property with a value closest to the [0] index
|
||||||
|
// reduce returns the first non-null value in the array
|
||||||
|
const backfillProperty = (data, key) => data.reduce(
|
||||||
|
(prev, cur) => {
|
||||||
|
const curValue = cur.properties?.[key]?.value;
|
||||||
|
if (prev.value === null && curValue !== null && curValue !== undefined) return cur.properties[key];
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
{ value: null }, // null is the default provided by the api
|
||||||
|
);
|
||||||
|
|
||||||
const display = new CurrentWeather(1, 'current-weather');
|
const display = new CurrentWeather(1, 'current-weather');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const incrementInterval = (force) => {
|
|||||||
|
|
||||||
const drawScreen = async () => {
|
const drawScreen = async () => {
|
||||||
// get the conditions
|
// get the conditions
|
||||||
const data = await getCurrentWeather();
|
const { data, parameters } = await getCurrentWeather();
|
||||||
|
|
||||||
// create a data object (empty if no valid current weather conditions)
|
// create a data object (empty if no valid current weather conditions)
|
||||||
const scrollData = data || {};
|
const scrollData = data || {};
|
||||||
@@ -100,7 +100,7 @@ const drawScreen = async () => {
|
|||||||
// if we have no current weather and no hazards, there's nothing to display
|
// if we have no current weather and no hazards, there's nothing to display
|
||||||
if (!data && (!scrollData.hazards || scrollData.hazards.length === 0)) return;
|
if (!data && (!scrollData.hazards || scrollData.hazards.length === 0)) return;
|
||||||
|
|
||||||
const thisScreen = workingScreens[screenIndex](scrollData);
|
const thisScreen = workingScreens[screenIndex](scrollData, parameters);
|
||||||
|
|
||||||
// update classes on the scroll area
|
// update classes on the scroll area
|
||||||
mainScroll.classList.forEach((cls) => { if (cls !== 'scroll') mainScroll.classList.remove(cls); });
|
mainScroll.classList.forEach((cls) => { if (cls !== 'scroll') mainScroll.classList.remove(cls); });
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import STATUS from './status.mjs';
|
import STATUS from './status.mjs';
|
||||||
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
|
||||||
import { safeJson } from './utils/fetch.mjs';
|
import { safeJson } from './utils/fetch.mjs';
|
||||||
import { temperature as temperatureUnit, distanceKilometers } from './utils/units.mjs';
|
import { temperature as temperatureUnit, windSpeed as windUnit } from './utils/units.mjs';
|
||||||
import { getHourlyIcon } from './icons.mjs';
|
import { getHourlyIcon } from './icons.mjs';
|
||||||
import { directionToNSEW } from './utils/calc.mjs';
|
import { directionToNSEW } from './utils/calc.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
@@ -191,7 +191,7 @@ class Hourly extends WeatherDisplay {
|
|||||||
const parseForecast = async (data) => {
|
const parseForecast = async (data) => {
|
||||||
// get unit converters
|
// get unit converters
|
||||||
const temperatureConverter = temperatureUnit();
|
const temperatureConverter = temperatureUnit();
|
||||||
const distanceConverter = distanceKilometers();
|
const windConverter = windUnit();
|
||||||
|
|
||||||
// parse data
|
// parse data
|
||||||
const temperature = expand(data.temperature.values);
|
const temperature = expand(data.temperature.values);
|
||||||
@@ -210,8 +210,8 @@ const parseForecast = async (data) => {
|
|||||||
temperature: temperatureConverter(temperature[idx]),
|
temperature: temperatureConverter(temperature[idx]),
|
||||||
temperatureUnit: temperatureConverter.units,
|
temperatureUnit: temperatureConverter.units,
|
||||||
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
||||||
windSpeed: distanceConverter(windSpeed[idx]),
|
windSpeed: windConverter(windSpeed[idx]),
|
||||||
windUnit: distanceConverter.units,
|
windUnit: windConverter.units,
|
||||||
windDirection: directionToNSEW(windDirection[idx]),
|
windDirection: directionToNSEW(windDirection[idx]),
|
||||||
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
||||||
skyCover: skyCover[idx],
|
skyCover: skyCover[idx],
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const getWeather = async (latLon, haveDataCallback) => {
|
|||||||
weatherParameters.forecast = point.properties.forecast;
|
weatherParameters.forecast = point.properties.forecast;
|
||||||
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
||||||
weatherParameters.stations = stations.features;
|
weatherParameters.stations = stations.features;
|
||||||
|
weatherParameters.relativeLocation = point.properties.relativeLocation.properties;
|
||||||
|
|
||||||
// update the main process for display purposes
|
// update the main process for display purposes
|
||||||
populateWeatherParameters(weatherParameters, point.properties);
|
populateWeatherParameters(weatherParameters, point.properties);
|
||||||
|
|||||||
Reference in New Issue
Block a user