Short/long/closed display for each band calculated from latest data for each ionosonde station

This commit is contained in:
Ian Renton
2026-05-21 20:54:08 +01:00
parent c38be5b588
commit c939a5c1a1
15 changed files with 151 additions and 58 deletions

View File

@@ -15,7 +15,7 @@ info:
### 1.4
* `/solar` response now includes `ionosonde_data`, which contains ionosonde station measurements (foF2 and MUF) sourced from the GIRO Data Center.
* `/solar` response now includes `ionosonde_data`, which contains ionosonde station measurements (LUF, foF2 and MUF) sourced from the GIRO Data Center as well as implied band states.
### 1.3
@@ -1735,6 +1735,23 @@ components:
example:
"1747267201.0": 2.10
"1747267501.0": 2.05
band_states:
type: object
nullable: true
description: >
States of each HF amateur band, derived from the latest foF2, MUF and LUF values. Keyed by band name. Each
value is one of: "Closed" (band frequency is below LUF or above MUF), "Short" (band frequency is at or above
LUF and below foF2, so good for NVIS) or "Long" (band frequency is at or above foF2 and below MUF, so good
for DX). Null if foF2 or MUF data is not yet available.
additionalProperties:
type: string
enum: [Closed, Short, Long]
example:
"160m": "Closed"
"80m": "Short"
"40m": "Long"
"20m": "Long"
"10m": "Closed"
SolarConditionsProviderStatus:
type: object

View File

@@ -411,11 +411,15 @@ function renderIonosondeData() {
const lufEntries = toSeries(station.luf);
const allTs = [...fof2Entries, ...mufEntries, ...lufEntries].map(e => e.ts);
if (allTs.length === 0) {
$('#ionosonde-latest').html('<div class="alert alert-warning mt-2 mb-0 py-2">No data available for this station.</div>');
$('#ionosonde-no-data').show();
$('#ionosonde-data-rows').hide();
$('#ionosonde-band-state').hide();
$('#ionosonde-chart').hide();
if (ionosondeChart) { ionosondeChart.destroy(); ionosondeChart = null; }
return;
}
$('#ionosonde-no-data').hide();
$('#ionosonde-data-rows').show();
// Populate latest values summary (visible on all screen sizes)
const latestFof2 = fof2Entries.length ? fof2Entries[fof2Entries.length - 1].val : null;
@@ -423,25 +427,33 @@ function renderIonosondeData() {
const latestLuf = lufEntries.length ? lufEntries[lufEntries.length - 1].val : null;
const minTs = allTs.length ? Math.min(...allTs) : null;
const maxTs = allTs.length ? Math.max(...allTs) : null;
let latestTimeStr = '';
if (maxTs != null) {
const latestDate = moment.utc(maxTs * 1000);
latestTimeStr = latestDate.format('DD MMM YYYY HH:mm [UTC]') + ' (' + latestDate.fromNow() + ')';
$('#ionosonde-latest-time').text(latestDate.format('DD MMM YYYY HH:mm [UTC]') + ' (' + latestDate.fromNow() + ')');
}
$('#ionosonde-latest-luf').text(latestLuf !== null ? latestLuf.toFixed(2) + ' MHz' : 'N/A');
$('#ionosonde-latest-fof2').text(latestFof2 !== null ? latestFof2.toFixed(2) + ' MHz' : 'N/A');
$('#ionosonde-latest-muf').text(latestMuf !== null ? latestMuf.toFixed(2) + ' MHz' : 'N/A');
$('#ionosonde-stale-warning').toggle(maxTs !== null && (Date.now() / 1000 - maxTs) > 12 * 3600);
// Populate band state tables. There are actually two tables to populate, which is pretty janky, but allows us to
// display horizontally on desktop but flip it around to become a vertical list on mobile.
const bandStateClass = {'Closed': 'bg-danger-subtle', 'Short': 'bg-primary-subtle', 'Long': 'bg-success-subtle'};
const bandStates = station.band_states;
if (bandStates && Object.keys(bandStates).length > 0) {
const headRow = $('#ionosonde-band-state-head').empty();
const dataRow = $('#ionosonde-band-state-row').empty();
const vBody = $('#ionosonde-band-state-body').empty();
Object.entries(bandStates).forEach(([band, state]) => {
const cls = bandStateClass[state] || '';
headRow.append($('<th>').addClass('text-center').text(band));
dataRow.append($('<td>').addClass('text-center ' + cls).text(state));
vBody.append($('<tr>').append($('<td>').addClass('fw-bold').text(band)).append($('<td>').addClass(cls).text(state)));
});
$('#ionosonde-band-state').show();
} else {
$('#ionosonde-band-state').hide();
}
const staleWarning = (maxTs !== null && (Date.now() / 1000 - maxTs) > 12 * 3600)
? '<div class="alert alert-warning mt-2 mb-0 py-2">Data is more than 12 hours old!</div>'
: '';
$('#ionosonde-latest').html(
'<div class="row align-items-center me-0">' +
'<div class="col-12 py-2 text-muted">Latest values as of ' + latestTimeStr + '</div></div>' +
'<div class="row border-bottom align-items-center me-0">' +
'<div class="col-12 col-md-4 py-2">LUF: <strong>' + (latestLuf !== null ? latestLuf.toFixed(2) + ' MHz' : 'N/A') + '</strong></div>' +
'<div class="col-12 col-md-4 py-2">foF2: <strong>' + (latestFof2 !== null ? latestFof2.toFixed(2) + ' MHz' : 'N/A') + '</strong></div>' +
'<div class="col-12 col-md-4 py-2">MUF (3000 km): <strong>' + (latestMuf !== null ? latestMuf.toFixed(2) + ' MHz' : 'N/A') + '</strong></div>' +
'</div>' +
staleWarning +
'</div>'
);
if (ionosondeChart) {
ionosondeChart.destroy();