Remove latest observations display

This commit is contained in:
mrkmntal 2026-04-07 18:25:27 -04:00
commit 0090e8f30e
8 changed files with 2 additions and 314 deletions

View file

@ -79,7 +79,6 @@ const mjsSources = [
'server/scripts/modules/extendedforecast.mjs',
'server/scripts/modules/hourly.mjs',
'server/scripts/modules/hourly-graph.mjs',
'server/scripts/modules/latestobservations.mjs',
'server/scripts/modules/localforecast.mjs',
'server/scripts/modules/radar.mjs',
'server/scripts/modules/regionalforecast.mjs',

View file

@ -1,215 +0,0 @@
// current weather conditions display
import { distance as calcDistance, directionToNSEW } from './utils/calc.mjs';
import { safeJson, safePromiseAll } from './utils/fetch.mjs';
import STATUS from './status.mjs';
import { locationCleanup } from './utils/string.mjs';
import { temperature, windSpeed } from './utils/units.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import augmentObservationWithMetar from './utils/metar.mjs';
import settings from './settings.mjs';
import { debugFlag } from './utils/debug.mjs';
import { enhanceObservationWithMapClick } from './utils/mapclick.mjs';
class LatestObservations extends WeatherDisplay {
constructor(navId, elemId) {
super(navId, elemId, 'Latest Observations', true);
// constants
this.MaximumRegionalStations = 7;
}
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
if (!this.weatherParameters?.supportsNoaaDisplays) {
this.data = [];
this.timing.totalScreens = 0;
this.setStatus(STATUS.loaded);
return;
}
this.timing.totalScreens = 1;
// latest observations does a silent refresh but will not fall back to previously fetched data
// this is intentional because up to 30 stations are available to pull data from
// calculate distance to each station
const stationsByDistance = Object.values(StationInfo).map((station) => {
const distance = calcDistance(station.lat, station.lon, this.weatherParameters.latitude, this.weatherParameters.longitude);
return { ...station, distance };
});
// sort the stations by distance
const sortedStations = stationsByDistance.sort((a, b) => a.distance - b.distance);
// try up to 30 regional stations
const regionalStations = sortedStations.slice(0, 30);
// Fetch stations sequentially in batches to avoid unnecessary API calls.
// We start with the 7 closest stations and only fetch more if some fail,
// stopping as soon as we have 7 valid stations with data.
const actualConditions = [];
let lastStation = Math.min(regionalStations.length, 7);
let firstStation = 0;
while (actualConditions.length < 7 && (lastStation) <= regionalStations.length) {
// Sequential fetching is intentional here - we want to try closest stations first
// and only fetch additional batches if needed, rather than hitting all 30 stations at once
// eslint-disable-next-line no-await-in-loop
const someStations = await this.getStations(regionalStations.slice(firstStation, lastStation));
actualConditions.push(...someStations);
// update counters
firstStation += lastStation;
lastStation = Math.min(regionalStations.length + 1, firstStation + 7 - actualConditions.length);
}
// cut down to the maximum of 7
this.data = actualConditions.slice(0, this.MaximumRegionalStations);
// test for at least one station
if (this.data.length === 0) {
this.setStatus(STATUS.noData);
return;
}
this.setStatus(STATUS.loaded);
}
// This is a class method because it needs access to the instance's `stillWaiting` method
async getStations(stations) {
// Use centralized safe Promise handling to avoid unhandled AbortError rejections
const stationData = await safePromiseAll(stations.map(async (station) => {
try {
const data = await safeJson(`https://api.weather.gov/stations/${station.id}/observations/latest`, {
retryCount: 1,
stillWaiting: () => this.stillWaiting(),
});
if (!data) {
if (debugFlag('verbose-failures')) {
console.log(`Failed to get Latest Observations for station ${station.id}`);
}
return false;
}
// Enhance observation data with METAR parsing for missing fields
const originalData = { ...data.properties };
data.properties = augmentObservationWithMetar(data.properties);
const metarFields = [
{ name: 'temperature', check: (orig, metar) => orig.temperature.value === null && metar.temperature.value !== null },
{ name: 'windSpeed', check: (orig, metar) => orig.windSpeed.value === null && metar.windSpeed.value !== null },
{ name: 'windDirection', check: (orig, metar) => orig.windDirection.value === null && metar.windDirection.value !== null },
];
const augmentedData = data.properties;
const metarReplacements = metarFields.filter((field) => field.check(originalData, augmentedData)).map((field) => field.name);
if (debugFlag('latestobservations') && metarReplacements.length > 0) {
console.log(`Latest Observations for station ${station.id} were augmented with METAR data for ${metarReplacements.join(', ')}`);
}
// test data quality
const requiredFields = [
{ name: 'temperature', check: (props) => props.temperature?.value === null },
{ name: 'windSpeed', check: (props) => props.windSpeed?.value === null },
{ name: 'windDirection', check: (props) => props.windDirection?.value === null },
{ name: 'textDescription', check: (props) => props.textDescription === null || props.textDescription === '' },
];
// Use enhanced observation with MapClick fallback
const enhancedResult = await enhanceObservationWithMapClick(data.properties, {
requiredFields,
stationId: station.id,
stillWaiting: () => this.stillWaiting(),
debugContext: 'latestobservations',
});
data.properties = enhancedResult.data;
const { missingFields } = enhancedResult;
// Check final data quality
if (missingFields.length > 0) {
if (debugFlag('latestobservations')) {
console.log(`Latest Observations for station ${station.id} is missing fields: ${missingFields.join(', ')}`);
}
return false;
}
// format the return values
return {
...data.properties,
StationId: station.id,
city: station.city,
};
} catch (error) {
console.error(`Unexpected error getting latest observations for station ${station.id}: ${error.message}`);
return false;
}
}));
// filter false (no data or other error)
return stationData.filter((d) => d);
}
async drawCanvas() {
super.drawCanvas();
const conditions = this.data;
// sort array by station name
const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1));
if (settings.units.value === 'us') {
this.elem.querySelector('.column-headers .temp.english').classList.add('show');
this.elem.querySelector('.column-headers .temp.metric').classList.remove('show');
} else {
this.elem.querySelector('.column-headers .temp.english').classList.remove('show');
this.elem.querySelector('.column-headers .temp.metric').classList.add('show');
}
// get unit converters
const windConverter = windSpeed();
const temperatureConverter = temperature();
const lines = sortedConditions.map((condition) => {
const windDirection = directionToNSEW(condition.windDirection.value);
const Temperature = temperatureConverter(condition.temperature.value);
const WindSpeed = windConverter(condition.windSpeed.value);
const fill = {
location: locationCleanup(condition.city).substr(0, 14),
temp: Temperature,
weather: shortenCurrentConditions(condition.textDescription).substr(0, 9),
};
if (WindSpeed > 0) {
fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString();
} else if (WindSpeed === 'NA') {
fill.wind = 'NA';
} else {
fill.wind = 'Calm';
}
return this.fillTemplate('observation-row', fill);
});
const linesContainer = this.elem.querySelector('.observation-lines');
linesContainer.innerHTML = '';
linesContainer.append(...lines);
this.finishDraw();
}
}
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
registerDisplay(new LatestObservations(2, 'latest-observations'));

View file

@ -1,72 +0,0 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
.weather-display .latest-observations {
&.main {
overflow-y: hidden;
.column-headers {
height: 20px;
position: absolute;
width: 100%;
}
.column-headers {
top: 0px;
div {
display: inline-block;
font-family: 'Star4000 Small';
font-size: 24pt;
position: absolute;
top: -14px;
@include u.text-shadow();
}
.temp {
// hidden initially for english/metric switching
display: none;
&.show {
display: inline-block;
}
}
}
.temp {
left: 230px;
}
.weather {
left: 280px;
}
.wind {
left: 430px;
}
.observation-lines {
min-height: 338px;
padding-top: 10px;
.observation-row {
font-family: 'Star4000';
font-size: 24pt;
@include u.text-shadow();
position: relative;
height: 40px;
>div {
position: absolute;
top: 8px;
}
.wind {
white-space: pre;
text-align: right;
}
}
}
}
}

View file

@ -5,7 +5,6 @@
@use 'hourly';
@use 'hourly-graph';
@use 'travel';
@use 'latest-observations';
@use 'local-forecast';
@use 'progress';
@use 'radar';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -57,7 +57,6 @@
<script type="module" src="scripts/modules/extendedforecast.mjs"></script>
<script type="module" src="scripts/modules/hourly-graph.mjs"></script>
<script type="module" src="scripts/modules/hourly.mjs"></script>
<script type="module" src="scripts/modules/latestobservations.mjs"></script>
<script type="module" src="scripts/modules/localforecast.mjs"></script>
<script type="module" src="scripts/modules/radar.mjs"></script>
<script type="module" src="scripts/modules/regionalforecast.mjs"></script>
@ -118,9 +117,6 @@
<div id="local-forecast-html" class="weather-display">
<%- include('partials/local-forecast.ejs') %>
</div>
<div id="latest-observations-html" class="weather-display">
<%- include('partials/latest-observations.ejs') %>
</div>
<div id="regional-forecast-html" class="weather-display">
<%- include('partials/regional-forecast.ejs') %>
</div>

View file

@ -1,19 +0,0 @@
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true, hasTime: true }) %>
<div class="main has-scroll latest-observations has-box">
<div class="container">
<div class="column-headers">
<div class="temp english">&deg;F</div>
<div class="temp metric">&deg;C</div>
<div class="weather">Weather</div>
<div class="wind">Wind</div>
</div>
<div class="observation-lines">
<div class="observation-row template">
<div class="location"></div>
<div class="temp"></div>
<div class="weather"></div>
<div class="wind"></div>
</div>
</div>
</div>
</div>