mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-16 00:29:34 -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:
|
||||
* Web browser and OS
|
||||
* 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",
|
||||
"version": "6.2.5",
|
||||
"version": "6.2.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ws4kp",
|
||||
"version": "6.2.5",
|
||||
"version": "6.2.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "6.2.5",
|
||||
"version": "6.2.8",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from './utils/units.mjs';
|
||||
import { debugFlag } from './utils/debug.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
|
||||
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
|
||||
candidateObservation = await safeJson(`${station.id}/observations`, {
|
||||
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,
|
||||
stillWaiting: () => this.stillWaiting(),
|
||||
@@ -231,7 +232,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||
this.setAutoReload();
|
||||
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
||||
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
|
||||
this.getDataCallbacks.push(() => resolve(this.data));
|
||||
});
|
||||
@@ -266,7 +267,7 @@ const parseData = (data) => {
|
||||
const kilometersConverter = distanceKilometers();
|
||||
const pressureConverter = pressure();
|
||||
|
||||
const observations = data.features[0].properties;
|
||||
const observations = backfill(data.features);
|
||||
// values from api are provided in metric
|
||||
data.observations = observations;
|
||||
data.Temperature = temperatureConverter(observations.temperature.value);
|
||||
@@ -306,6 +307,46 @@ const parseData = (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');
|
||||
registerDisplay(display);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ const incrementInterval = (force) => {
|
||||
|
||||
const drawScreen = async () => {
|
||||
// get the conditions
|
||||
const data = await getCurrentWeather();
|
||||
const { data, parameters } = await getCurrentWeather();
|
||||
|
||||
// create a data object (empty if no valid current weather conditions)
|
||||
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 (!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
|
||||
mainScroll.classList.forEach((cls) => { if (cls !== 'scroll') mainScroll.classList.remove(cls); });
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import STATUS from './status.mjs';
|
||||
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.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 { directionToNSEW } from './utils/calc.mjs';
|
||||
import WeatherDisplay from './weatherdisplay.mjs';
|
||||
@@ -191,7 +191,7 @@ class Hourly extends WeatherDisplay {
|
||||
const parseForecast = async (data) => {
|
||||
// get unit converters
|
||||
const temperatureConverter = temperatureUnit();
|
||||
const distanceConverter = distanceKilometers();
|
||||
const windConverter = windUnit();
|
||||
|
||||
// parse data
|
||||
const temperature = expand(data.temperature.values);
|
||||
@@ -210,8 +210,8 @@ const parseForecast = async (data) => {
|
||||
temperature: temperatureConverter(temperature[idx]),
|
||||
temperatureUnit: temperatureConverter.units,
|
||||
apparentTemperature: temperatureConverter(apparentTemperature[idx]),
|
||||
windSpeed: distanceConverter(windSpeed[idx]),
|
||||
windUnit: distanceConverter.units,
|
||||
windSpeed: windConverter(windSpeed[idx]),
|
||||
windUnit: windConverter.units,
|
||||
windDirection: directionToNSEW(windDirection[idx]),
|
||||
probabilityOfPrecipitation: probabilityOfPrecipitation[idx],
|
||||
skyCover: skyCover[idx],
|
||||
|
||||
@@ -109,6 +109,7 @@ const getWeather = async (latLon, haveDataCallback) => {
|
||||
weatherParameters.forecast = point.properties.forecast;
|
||||
weatherParameters.forecastGridData = point.properties.forecastGridData;
|
||||
weatherParameters.stations = stations.features;
|
||||
weatherParameters.relativeLocation = point.properties.relativeLocation.properties;
|
||||
|
||||
// update the main process for display purposes
|
||||
populateWeatherParameters(weatherParameters, point.properties);
|
||||
|
||||
Reference in New Issue
Block a user