mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-23 21:25:12 +00:00
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:
@@ -35,6 +35,16 @@ class GIROIonosonde(SolarConditionsProvider):
|
|||||||
stations.append({"ursi": row[0].strip(), "name": row[1].strip()})
|
stations.append({"ursi": row[0].strip(), "name": row[1].strip()})
|
||||||
return stations
|
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):
|
def start(self):
|
||||||
logging.info(f"Set up query of GIRO ionosonde data API every {POLL_INTERVAL} seconds.")
|
logging.info(f"Set up query of GIRO ionosonde data API every {POLL_INTERVAL} seconds.")
|
||||||
self._thread = Thread(target=self._run, daemon=True)
|
self._thread = Thread(target=self._run, daemon=True)
|
||||||
|
|||||||
@@ -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>
|
<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>
|
</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>
|
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1778925881"></script>
|
<script src="/js/common.js?v=1778927183"></script>
|
||||||
<script src="/js/add-spot.js?v=1778925881"></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>
|
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -70,8 +70,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1778925881"></script>
|
<script src="/js/common.js?v=1778927183"></script>
|
||||||
<script src="/js/alerts.js?v=1778925881"></script>
|
<script src="/js/alerts.js?v=1778927183"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -76,9 +76,9 @@
|
|||||||
<script>
|
<script>
|
||||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/common.js?v=1778925881"></script>
|
<script src="/js/common.js?v=1778927183"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=1778925881"></script>
|
<script src="/js/spotsbandsandmap.js?v=1778927183"></script>
|
||||||
<script src="/js/bands.js?v=1778925881"></script>
|
<script src="/js/bands.js?v=1778927183"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<title>Spothole</title>
|
<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"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||||
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
|
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
|
||||||
@@ -52,9 +52,9 @@
|
|||||||
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
|
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script src="https://misc.ianrenton.com/jsutils/utils.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=1778925881"></script>
|
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1778927183"></script>
|
||||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1778925881"></script>
|
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1778927183"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
{% if has_giro_ionosonde %}
|
{% if has_giro_ionosonde %}
|
||||||
<div class="card mt-5">
|
<div class="card mt-5">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Critical & Maximum Usable Frequencies
|
Ionosonde Data
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="ionosonde-latest" class="mb-3"></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 class="form-text mt-2">Data from the <a href="https://lgdc.uml.edu/">Lowell GIRO Data Center</a>.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,8 +249,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
|
<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/common.js?v=1778927183"></script>
|
||||||
<script src="/js/conditions.js?v=1778925881"></script>
|
<script src="/js/conditions.js?v=1778927183"></script>
|
||||||
<script>$(document).ready(function () {
|
<script>$(document).ready(function () {
|
||||||
$("#nav-link-conditions").addClass("active");
|
$("#nav-link-conditions").addClass("active");
|
||||||
}); <!-- highlight active page in nav --></script>
|
}); <!-- highlight active page in nav --></script>
|
||||||
|
|||||||
@@ -94,9 +94,9 @@
|
|||||||
<script>
|
<script>
|
||||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/common.js?v=1778925881"></script>
|
<script src="/js/common.js?v=1778927183"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=1778925881"></script>
|
<script src="/js/spotsbandsandmap.js?v=1778927183"></script>
|
||||||
<script src="/js/map.js?v=1778925881"></script>
|
<script src="/js/map.js?v=1778927183"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -104,9 +104,9 @@
|
|||||||
<script>
|
<script>
|
||||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/common.js?v=1778925881"></script>
|
<script src="/js/common.js?v=1778927183"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=1778925881"></script>
|
<script src="/js/spotsbandsandmap.js?v=1778927183"></script>
|
||||||
<script src="/js/spots.js?v=1778925881"></script>
|
<script src="/js/spots.js?v=1778927183"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1778925881"></script>
|
<script src="/js/common.js?v=1778927183"></script>
|
||||||
<script src="/js/status.js?v=1778925881"></script>
|
<script src="/js/status.js?v=1778927183"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
|
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1691,9 +1691,8 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
nullable: true
|
nullable: true
|
||||||
description: >
|
description: >
|
||||||
Ionosonde measurements from the GIRO Data Center, keyed by URSI station code. Only
|
Ionosonde measurements from the GIRO Data Center, keyed by URSI station code. All known stations are
|
||||||
stations for which data was successfully retrieved are included. Null if the
|
included, but not all of them may contain data.
|
||||||
GIROIonosonde provider has not yet completed its first poll or if this data source is disabled.
|
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
$ref: '#/components/schemas/IonosondeStation'
|
$ref: '#/components/schemas/IonosondeStation'
|
||||||
|
|
||||||
@@ -1712,7 +1711,7 @@ components:
|
|||||||
fof2:
|
fof2:
|
||||||
type: object
|
type: object
|
||||||
nullable: true
|
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:
|
additionalProperties:
|
||||||
type: number
|
type: number
|
||||||
example:
|
example:
|
||||||
@@ -1721,7 +1720,7 @@ components:
|
|||||||
muf:
|
muf:
|
||||||
type: object
|
type: object
|
||||||
nullable: true
|
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:
|
additionalProperties:
|
||||||
type: number
|
type: number
|
||||||
example:
|
example:
|
||||||
|
|||||||
@@ -380,7 +380,9 @@ function populateIonosondeDropdown(data) {
|
|||||||
// Render the foF2/MUF data and line chart for the currently selected station
|
// Render the foF2/MUF data and line chart for the currently selected station
|
||||||
function renderIonosondeData() {
|
function renderIonosondeData() {
|
||||||
// First make sure that we have some data, that a station entry is selected in the drop-down box, and that the
|
// 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;
|
if (!ionosondeData) return;
|
||||||
const ursi = $('#ionosonde-station').val();
|
const ursi = $('#ionosonde-station').val();
|
||||||
if (!ursi) return;
|
if (!ursi) return;
|
||||||
@@ -406,7 +408,12 @@ function renderIonosondeData() {
|
|||||||
const fof2Entries = toSeries(station.fof2);
|
const fof2Entries = toSeries(station.fof2);
|
||||||
const mufEntries = toSeries(station.muf);
|
const mufEntries = toSeries(station.muf);
|
||||||
const allTs = [...fof2Entries, ...mufEntries].map(e => e.ts);
|
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)
|
// Populate latest values summary (visible on all screen sizes)
|
||||||
const latestFof2 = fof2Entries.length ? fof2Entries[fof2Entries.length - 1].val : null;
|
const latestFof2 = fof2Entries.length ? fof2Entries[fof2Entries.length - 1].val : null;
|
||||||
@@ -418,12 +425,16 @@ function renderIonosondeData() {
|
|||||||
const latestDate = moment.utc(maxTs * 1000);
|
const latestDate = moment.utc(maxTs * 1000);
|
||||||
latestTimeStr = latestDate.format('DD MMM YYYY HH:mm [UTC]') + ' (' + latestDate.fromNow() + ')';
|
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(
|
$('#ionosonde-latest').html(
|
||||||
'<div class="row border-bottom align-items-center me-0">' +
|
'<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-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-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 class="col-12 col-md-4 py-2">MUF (3000 km): <strong>' + (latestMuf !== null ? latestMuf.toFixed(2) + ' MHz' : 'N/A') + '</strong></div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
staleWarning +
|
||||||
'</div>'
|
'</div>'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -564,6 +575,9 @@ function renderIonosondeData() {
|
|||||||
},
|
},
|
||||||
plugins: [bandLinesPlugin],
|
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
|
// Called when the ionosonde station select changes
|
||||||
|
|||||||
Reference in New Issue
Block a user