From d655354d05d680907c35791f22ea41e362f9074f Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Sat, 16 May 2026 11:26:23 +0100 Subject: [PATCH] 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. --- solarconditionsproviders/giroionosonde.py | 10 ++++++++++ templates/about.html | 2 +- templates/add_spot.html | 4 ++-- templates/alerts.html | 4 ++-- templates/bands.html | 6 +++--- templates/base.html | 8 ++++---- templates/conditions.html | 8 ++++---- templates/map.html | 6 +++--- templates/spots.html | 6 +++--- templates/status.html | 4 ++-- webassets/apidocs/openapi.yml | 9 ++++----- webassets/js/conditions.js | 18 ++++++++++++++++-- 12 files changed, 54 insertions(+), 31 deletions(-) diff --git a/solarconditionsproviders/giroionosonde.py b/solarconditionsproviders/giroionosonde.py index b15c54d..4f5b6c6 100644 --- a/solarconditionsproviders/giroionosonde.py +++ b/solarconditionsproviders/giroionosonde.py @@ -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) diff --git a/templates/about.html b/templates/about.html index 165ed88..493ff96 100644 --- a/templates/about.html +++ b/templates/about.html @@ -69,7 +69,7 @@

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.

- + {% end %} \ No newline at end of file diff --git a/templates/add_spot.html b/templates/add_spot.html index 95964dd..d427bd2 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -69,8 +69,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/alerts.html b/templates/alerts.html index 076c652..1e6a312 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -70,8 +70,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/bands.html b/templates/bands.html index c56e993..6c3db4d 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -76,9 +76,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 5a57e3e..f43b23e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -24,7 +24,7 @@ Spothole - + @@ -52,9 +52,9 @@ integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn" crossorigin="anonymous"> - - - + + + diff --git a/templates/conditions.html b/templates/conditions.html index 69814ec..35493cf 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -176,7 +176,7 @@ {% if has_giro_ionosonde %}
- Critical & Maximum Usable Frequencies + Ionosonde Data
@@ -186,7 +186,7 @@
- +
@@ -249,8 +249,8 @@ - - + + diff --git a/templates/map.html b/templates/map.html index d7800fc..affb7fd 100644 --- a/templates/map.html +++ b/templates/map.html @@ -94,9 +94,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/spots.html b/templates/spots.html index 8af4444..8c26a2b 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -104,9 +104,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/status.html b/templates/status.html index e2fc24e..86257bd 100644 --- a/templates/status.html +++ b/templates/status.html @@ -59,8 +59,8 @@ - - + + diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index 2603407..9dd75cf 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -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: diff --git a/webassets/js/conditions.js b/webassets/js/conditions.js index 948c6ec..d5dab24 100644 --- a/webassets/js/conditions.js +++ b/webassets/js/conditions.js @@ -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('
No data available for this station.
'); + $('#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) + ? '
Data is more than 12 hours old!
' + : ''; $('#ionosonde-latest').html( '
' + '
Latest values as of ' + latestTimeStr + '
' + '
foF2: ' + (latestFof2 !== null ? latestFof2.toFixed(2) + ' MHz' : 'N/A') + '
' + '
MUF (3000 km): ' + (latestMuf !== null ? latestMuf.toFixed(2) + ' MHz' : 'N/A') + '
' + '
' + + staleWarning + '' ); @@ -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