Show a warning instead of an empty canvas if the ionosonde station has no data, and also show a warning if we have data but it's old.

This commit is contained in:
Ian Renton
2026-05-16 11:26:23 +01:00
parent a7a45190cb
commit d655354d05
12 changed files with 54 additions and 31 deletions

View File

@@ -35,6 +35,16 @@ class GIROIonosonde(SolarConditionsProvider):
stations.append({"ursi": row[0].strip(), "name": row[1].strip()})
return stations
def setup(self, solar_conditions, solar_conditions_cache):
"""Prepopulate the ionosonde_data map with known URSI and station names, so that the API exposes this structure
even before we actually have any data in it."""
super().setup(solar_conditions, solar_conditions_cache)
self.update_data({"ionosonde_data": {
s["ursi"]: {"ursi": s["ursi"], "name": s["name"], "fof2": None, "muf": None}
for s in self._stations
}})
def start(self):
logging.info(f"Set up query of GIRO ionosonde data API every {POLL_INTERVAL} seconds.")
self._thread = Thread(target=self._run, daemon=True)

View File

@@ -69,7 +69,7 @@
<p>This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.</p>
</div>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %}

View File

@@ -69,8 +69,8 @@
</div>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/add-spot.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/add-spot.js?v=1778927183"></script>
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %}

View File

@@ -70,8 +70,8 @@
</div>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/alerts.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/alerts.js?v=1778927183"></script>
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %}

View File

@@ -76,9 +76,9 @@
<script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/spotsbandsandmap.js?v=1778925881"></script>
<script src="/js/bands.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/spotsbandsandmap.js?v=1778927183"></script>
<script src="/js/bands.js?v=1778927183"></script>
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %}

View File

@@ -24,7 +24,7 @@
<title>Spothole</title>
<link rel="stylesheet" href="/css/style.css?v=1778925881" type="text/css">
<link rel="stylesheet" href="/css/style.css?v=1778927183" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
@@ -52,9 +52,9 @@
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
crossorigin="anonymous"></script>
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1778925881"></script>
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1778925881"></script>
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1778925881"></script>
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1778927183"></script>
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1778927183"></script>
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1778927183"></script>
</head>
<body>

View File

@@ -176,7 +176,7 @@
{% if has_giro_ionosonde %}
<div class="card mt-5">
<div class="card-header">
Critical & Maximum Usable Frequencies
Ionosonde Data
</div>
<div class="card-body">
<div class="mb-3">
@@ -186,7 +186,7 @@
</select>
</div>
<div id="ionosonde-latest" class="mb-3"></div>
<canvas id="ionosonde-chart" class="mt-3 mb-3 d-none d-md-block"></canvas>
<canvas id="ionosonde-chart" class="mt-3 mb-3 hideonmobile"></canvas>
<div class="form-text mt-2">Data from the <a href="https://lgdc.uml.edu/">Lowell GIRO Data Center</a>.</div>
</div>
</div>
@@ -249,8 +249,8 @@
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/conditions.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/conditions.js?v=1778927183"></script>
<script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script>

View File

@@ -94,9 +94,9 @@
<script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/spotsbandsandmap.js?v=1778925881"></script>
<script src="/js/map.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/spotsbandsandmap.js?v=1778927183"></script>
<script src="/js/map.js?v=1778927183"></script>
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %}

View File

@@ -104,9 +104,9 @@
<script>
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
</script>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/spotsbandsandmap.js?v=1778925881"></script>
<script src="/js/spots.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/spotsbandsandmap.js?v=1778927183"></script>
<script src="/js/spots.js?v=1778927183"></script>
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
{% end %}

View File

@@ -59,8 +59,8 @@
</div>
</div>
<script src="/js/common.js?v=1778925881"></script>
<script src="/js/status.js?v=1778925881"></script>
<script src="/js/common.js?v=1778927183"></script>
<script src="/js/status.js?v=1778927183"></script>
<script>
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
</script>

View File

@@ -1691,9 +1691,8 @@ components:
type: object
nullable: true
description: >
Ionosonde measurements from the GIRO Data Center, keyed by URSI station code. Only
stations for which data was successfully retrieved are included. Null if the
GIROIonosonde provider has not yet completed its first poll or if this data source is disabled.
Ionosonde measurements from the GIRO Data Center, keyed by URSI station code. All known stations are
included, but not all of them may contain data.
additionalProperties:
$ref: '#/components/schemas/IonosondeStation'
@@ -1712,7 +1711,7 @@ components:
fof2:
type: object
nullable: true
description: F2 layer critical frequency (foF2) measurements in MHz, keyed by UNIX timestamp (UTC seconds since epoch) of each measurement.
description: F2 layer critical frequency (foF2) measurements in MHz, keyed by UNIX timestamp (UTC seconds since epoch) of each measurement. Can be null if there is no data.
additionalProperties:
type: number
example:
@@ -1721,7 +1720,7 @@ components:
muf:
type: object
nullable: true
description: Maximum Usable Frequency (MUF) for a 3000 km path in MHz, keyed by UNIX timestamp (UTC seconds since epoch) of each measurement.
description: Maximum Usable Frequency (MUF) for a 3000 km path in MHz, keyed by UNIX timestamp (UTC seconds since epoch) of each measurement. Can be null if there is no data.
additionalProperties:
type: number
example:

View File

@@ -380,7 +380,9 @@ function populateIonosondeDropdown(data) {
// Render the foF2/MUF data and line chart for the currently selected station
function renderIonosondeData() {
// First make sure that we have some data, that a station entry is selected in the drop-down box, and that the
// data contains an entry for that station. If not, bail out at this point.
// data contains an entry for that station. If not, this represents an odd state (over and above just "no data for
// this station", so bail out at this point. The user will have to reselect something from the list, or wait until
// the API is behaving itself again.
if (!ionosondeData) return;
const ursi = $('#ionosonde-station').val();
if (!ursi) return;
@@ -406,7 +408,12 @@ function renderIonosondeData() {
const fof2Entries = toSeries(station.fof2);
const mufEntries = toSeries(station.muf);
const allTs = [...fof2Entries, ...mufEntries].map(e => e.ts);
if (allTs.length === 0) return;
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-chart').hide();
if (ionosondeChart) { ionosondeChart.destroy(); ionosondeChart = null; }
return;
}
// Populate latest values summary (visible on all screen sizes)
const latestFof2 = fof2Entries.length ? fof2Entries[fof2Entries.length - 1].val : null;
@@ -418,12 +425,16 @@ function renderIonosondeData() {
const latestDate = moment.utc(maxTs * 1000);
latestTimeStr = latestDate.format('DD MMM YYYY HH:mm [UTC]') + ' (' + latestDate.fromNow() + ')';
}
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 border-bottom align-items-center me-0">' +
'<div class="col-12 col-md-6 py-2 text-muted">Latest values as of ' + latestTimeStr + '</div>' +
'<div class="col-12 col-md-2 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>'
);
@@ -564,6 +575,9 @@ function renderIonosondeData() {
},
plugins: [bandLinesPlugin],
});
// Chart canvas is normally hidden until we get here with some definitely good data. Now we have that so show it
$('#ionosonde-chart').show();
}
// Called when the ionosonde station select changes