mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-04-29 18:25:58 +00:00
Add descriptions for solar conditions #92
This commit is contained in:
@@ -1,6 +1,87 @@
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Lookup tables for derived text descriptions.
|
||||
# Each threshold-based table is a list of (min_value, description) pairs in descending order;
|
||||
# the first entry whose threshold the value meets or exceeds is used.
|
||||
|
||||
BLACKOUT_DESCRIPTIONS = {
|
||||
"X": "Extreme HF radio blackout on entire sunlit side",
|
||||
"M": "Wide area HF radio blackout on sunlit side",
|
||||
"C": "Occasional loss of HF communications on sunlit side",
|
||||
"B": "No significant radio blackout",
|
||||
"A": "No impact",
|
||||
}
|
||||
|
||||
PROTON_FLUX_DESCRIPTIONS = [
|
||||
(1000000, "Complete HF blackout in polar regions"),
|
||||
(100000, "Partial HF blackout in polar regions"),
|
||||
(10000, "Degraded HF propagation in polar regions"),
|
||||
(1000, "Small effect on HF propagation in polar regions"),
|
||||
(100, "Minor effect on HF propagation in polar regions"),
|
||||
(10, "Very minor effect on HF propagation in polar regions"),
|
||||
(0, "No impact"),
|
||||
]
|
||||
|
||||
SOLAR_STORM_SCALES = [
|
||||
(100000, 5),
|
||||
(10000, 4),
|
||||
(1000, 3),
|
||||
(100, 2),
|
||||
(10, 1),
|
||||
(0, 0),
|
||||
]
|
||||
|
||||
GEOMAG_STORM_DESCRIPTIONS = [
|
||||
(9, "Solar storm. Complete HF blackout, S30+ noise"),
|
||||
(8, "Solar storm. HF sporadic only, S20-30 noise"),
|
||||
(7, "Solar storm. HF intermittent, S9-20 noise"),
|
||||
(6, "Solar storm. HF fading at higher latitudes, S6-9 noise"),
|
||||
(5, "Solar storm. HF fading at higher latitudes, S4-6 noise"),
|
||||
(4, "Active. Minor HF fading at higher latitudes, S2-3 noise"),
|
||||
(3, "Unsettled. Minor HF fading at higher latitudes, S2-3 noise"),
|
||||
(2, "Inactive. No impact, S0-2 noise"),
|
||||
(1, "Quiet. No impact, S0-2 noise"),
|
||||
(0, "Quiet. No impact, S0-2 noise"),
|
||||
]
|
||||
|
||||
GEOMAG_STORM_SCALES = [
|
||||
(9, 5),
|
||||
(8, 4),
|
||||
(7, 3),
|
||||
(6, 2),
|
||||
(5, 1),
|
||||
(0, 0),
|
||||
]
|
||||
|
||||
BAND_CONDITIONS_DESCRIPTIONS = [
|
||||
(200, "Reliable conditions on all bands including 6m"),
|
||||
(150, "Excellent conditions on all bands up to 10m, occasional 6m openings"),
|
||||
(120, "Fair to good conditions on all bands up to 10m"),
|
||||
(90, "Fair conditions on bands up to 15m"),
|
||||
(70, "Poor to fair conditions on bands up to 20m"),
|
||||
(0, "Bands above 40m unusable"),
|
||||
]
|
||||
|
||||
ELECTRON_FLUX_DESCRIPTIONS = [
|
||||
(1000, "Partial to complete HF blackout in polar regions"),
|
||||
(100, "Degraded HF propagation in polar regions"),
|
||||
(10, "Minor impact on HF in polar regions"),
|
||||
(0, "No impact"),
|
||||
]
|
||||
|
||||
|
||||
def _lookup_by_threshold(value, table, default=None):
|
||||
"""Return the description from a threshold table for the given numeric value.
|
||||
The table is a list of (min_value, description) pairs in descending order."""
|
||||
|
||||
if value is None:
|
||||
return default
|
||||
for threshold, description in table:
|
||||
if value >= threshold:
|
||||
return description
|
||||
return default
|
||||
|
||||
|
||||
@dataclass
|
||||
class HFBandCondition:
|
||||
@@ -63,6 +144,36 @@ class SolarConditions:
|
||||
# VHF propagation phenomena
|
||||
vhf_conditions: list = None # list[VHFCondition]
|
||||
|
||||
# Derived values (populated by infer_descriptions())
|
||||
# HF radio blackout risk, derived from x_ray
|
||||
blackout_desc: str = None
|
||||
# Solar radiation storm level description, derived from proton_flux
|
||||
proton_flux_desc: str = None
|
||||
# Solar radiation storm scale number (S0–S5), derived from proton_flux
|
||||
solar_storm_scale: int = None
|
||||
# Geomagnetic storm level description, derived from k_index
|
||||
geomag_storm_desc: str = None
|
||||
# Geomagnetic storm scale number (G0–G5), derived from k_index
|
||||
geomag_storm_scale: int = None
|
||||
# Overall HF band conditions summary, derived from sfi
|
||||
band_conditions_desc: str = None
|
||||
# Electron flux level, derived from electron_flux
|
||||
electron_flux_desc: str = None
|
||||
|
||||
def infer_descriptions(self):
|
||||
"""Populate derived text description fields from the current numeric/raw field values."""
|
||||
|
||||
# blackout_desc: use the X-ray flux class letter (first character of x_ray)
|
||||
if self.x_ray and len(self.x_ray) > 0:
|
||||
self.blackout_desc = BLACKOUT_DESCRIPTIONS.get(self.x_ray[0].upper())
|
||||
|
||||
self.proton_flux_desc = _lookup_by_threshold(self.proton_flux, PROTON_FLUX_DESCRIPTIONS)
|
||||
self.solar_storm_scale = _lookup_by_threshold(self.proton_flux, SOLAR_STORM_SCALES)
|
||||
self.geomag_storm_desc = _lookup_by_threshold(self.k_index, GEOMAG_STORM_DESCRIPTIONS)
|
||||
self.geomag_storm_scale = _lookup_by_threshold(self.k_index, GEOMAG_STORM_SCALES)
|
||||
self.band_conditions_desc = _lookup_by_threshold(self.sfi, BAND_CONDITIONS_DESCRIPTIONS)
|
||||
self.electron_flux_desc = _lookup_by_threshold(self.electron_flux, ELECTRON_FLUX_DESCRIPTIONS)
|
||||
|
||||
def to_json(self):
|
||||
"""JSON serialise"""
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
self._stop_event = Event()
|
||||
|
||||
def start(self):
|
||||
logging.info("Set up query of " + self.name + " solar conditions API every " + str(self._poll_interval) + " seconds.")
|
||||
logging.info(
|
||||
"Set up query of " + self.name + " solar conditions API every " + str(self._poll_interval) + " seconds.")
|
||||
self._thread = Thread(target=self._run, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
@@ -39,10 +40,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
logging.debug("Polling " + self.name + " solar conditions API...")
|
||||
http_response = requests.get(self._url, headers=HTTP_HEADERS)
|
||||
new_data = self._http_response_to_solar_conditions(http_response)
|
||||
if new_data:
|
||||
for key, value in new_data.items():
|
||||
if hasattr(self._solar_conditions, key):
|
||||
setattr(self._solar_conditions, key, value)
|
||||
self.update_data(new_data)
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
|
||||
@@ -30,3 +30,12 @@ class SolarConditionsProvider:
|
||||
"""Stop any threads and prepare for application shutdown"""
|
||||
|
||||
raise NotImplementedError("Subclasses must implement this method")
|
||||
|
||||
def update_data(self, new_data):
|
||||
"""Update the solar conditions object with new data"""
|
||||
|
||||
if new_data:
|
||||
for key, value in new_data.items():
|
||||
if hasattr(self._solar_conditions, key):
|
||||
setattr(self._solar_conditions, key, value)
|
||||
self._solar_conditions.infer_descriptions()
|
||||
|
||||
@@ -67,7 +67,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=1774692270"></script>
|
||||
<script src="/js/common.js?v=1774694366"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -69,8 +69,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1774692270"></script>
|
||||
<script src="/js/add-spot.js?v=1774692270"></script>
|
||||
<script src="/js/common.js?v=1774694366"></script>
|
||||
<script src="/js/add-spot.js?v=1774694366"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -56,8 +56,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/common.js?v=1774692270"></script>
|
||||
<script src="/js/alerts.js?v=1774692270"></script>
|
||||
<script src="/js/common.js?v=1774694367"></script>
|
||||
<script src="/js/alerts.js?v=1774694367"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -62,9 +62,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1774692270"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1774692270"></script>
|
||||
<script src="/js/bands.js?v=1774692270"></script>
|
||||
<script src="/js/common.js?v=1774694366"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1774694366"></script>
|
||||
<script src="/js/bands.js?v=1774694366"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -46,10 +46,10 @@
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/cjs/tinycolor.min.js"></script>
|
||||
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1774692269"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=1774692269"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1774692269"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1774692269"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1774694366"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/storage.js?v=1774694366"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1774694366"></script>
|
||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1774694366"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
@@ -67,7 +67,7 @@
|
||||
<li class="nav-item ms-4"><a href="/" class="nav-link" id="nav-link-spots"><i class="fa-solid fa-tower-cell"></i> Spots</a></li>
|
||||
<li class="nav-item ms-4"><a href="/map" class="nav-link" id="nav-link-map"><i class="fa-solid fa-map"></i> Map</a></li>
|
||||
<li class="nav-item ms-4"><a href="/bands" class="nav-link" id="nav-link-bands"><i class="fa-solid fa-ruler-vertical"></i> Bands</a></li>
|
||||
<li class="nav-item ms-4"><a href="/alerts" class="nav-link" id="nav-link-alerts"><i class="fa-solid fa-bell"></i> Alerts</a></li>
|
||||
<li class="nav-item ms-4"><a href="/alerts" class="nav-link" id="nav-link-alerts"><i class="fa-solid fa-clock"></i> Upcoming</a></li>
|
||||
{% if allow_spotting %}
|
||||
<li class="nav-item ms-4"><a href="/add-spot" class="nav-link" id="nav-link-add-spot"><i class="fa-solid fa-comment"></i> Add Spot</a></li>
|
||||
{% end %}
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1774692270"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1774692270"></script>
|
||||
<script src="/js/map.js?v=1774692270"></script>
|
||||
<script src="/js/common.js?v=1774694367"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1774694367"></script>
|
||||
<script src="/js/map.js?v=1774694367"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -87,9 +87,9 @@
|
||||
<script>
|
||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||
</script>
|
||||
<script src="/js/common.js?v=1774692269"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1774692269"></script>
|
||||
<script src="/js/spots.js?v=1774692269"></script>
|
||||
<script src="/js/common.js?v=1774694366"></script>
|
||||
<script src="/js/spotsbandsandmap.js?v=1774694366"></script>
|
||||
<script src="/js/spots.js?v=1774694366"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
|
||||
|
||||
<script src="/js/common.js?v=1774692270"></script>
|
||||
<script src="/js/status.js?v=1774692270"></script>
|
||||
<script src="/js/common.js?v=1774694366"></script>
|
||||
<script src="/js/status.js?v=1774694366"></script>
|
||||
<script>$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||
|
||||
{% end %}
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="d-inline-flex gap-1">
|
||||
<button id="filters-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>
|
||||
<button id="conditions-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleConditionsPanel();"><i class="fa-solid fa-sun"></i><span class="hideonmobile"> Conditions</span></button>
|
||||
<button id="filters-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i><span class="hideonmobile"> Filters</span></button>
|
||||
<button id="display-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i><span class="hideonmobile"> Display</span></button>
|
||||
</div>
|
||||
@@ -1362,18 +1362,18 @@ components:
|
||||
properties:
|
||||
band:
|
||||
type: string
|
||||
description: Band group name as used by the data source, e.g. "80m-40m", "30m-20m", "17m-15m", "10m-6m".
|
||||
description: Band group, e.g. "80m-40m", "30m-20m", "17m-15m", "10m-6m". As provided by HamQSL.
|
||||
example: "80m-40m"
|
||||
time:
|
||||
type: string
|
||||
description: Time of day these conditions apply to.
|
||||
description: Time of day these conditions apply to. As provided by HamQSL.
|
||||
enum:
|
||||
- day
|
||||
- night
|
||||
example: day
|
||||
condition:
|
||||
type: string
|
||||
description: Propagation condition assessment.
|
||||
description: Propagation condition assessment. As provided by HamQSL.
|
||||
enum:
|
||||
- Good
|
||||
- Fair
|
||||
@@ -1386,12 +1386,12 @@ components:
|
||||
properties:
|
||||
phenomenon:
|
||||
type: string
|
||||
description: The name of the propagation phenomenon, e.g. "E-Skip", "Sporadic E".
|
||||
description: The name of the propagation phenomenon, e.g. "E-Skip", "vhf-aurora". As provided by HamQSL.
|
||||
example: "E-Skip"
|
||||
location:
|
||||
type: string
|
||||
description: The geographic region this condition applies to, e.g. "Europe", "N America".
|
||||
example: "Europe"
|
||||
description: The geographic region this condition applies to, e.g. "europe", "north_america", "northern_hemi". As provided by HamQSL.
|
||||
example: "europe"
|
||||
condition:
|
||||
type: string
|
||||
description: The current condition for this phenomenon and location.
|
||||
@@ -1407,66 +1407,98 @@ components:
|
||||
example: 1759579508
|
||||
sfi:
|
||||
type: integer
|
||||
description: Solar Flux Index (SFI). Higher values generally indicate better HF propagation.
|
||||
description: Solar Flux Index (SFI)
|
||||
example: 170
|
||||
a_index:
|
||||
type: integer
|
||||
description: A-index — daily geomagnetic activity index. Higher values indicate more disturbed conditions.
|
||||
description: Daily geomagnetic activity index
|
||||
example: 7
|
||||
k_index:
|
||||
type: integer
|
||||
description: K-index — 3-hour geomagnetic activity index, 0–9. Values of 5 or above indicate a geomagnetic storm.
|
||||
description: 3-hour geomagnetic activity index, 0–9
|
||||
example: 2
|
||||
x_ray:
|
||||
type: string
|
||||
description: Current X-ray flux class, e.g. "B2.3", "C1.0", "M5.0".
|
||||
description: Current X-ray flux class
|
||||
example: "B2.3"
|
||||
proton_flux:
|
||||
type: integer
|
||||
description: Proton flux level.
|
||||
description: Proton flux level
|
||||
example: 1
|
||||
electron_flux:
|
||||
type: integer
|
||||
description: Electron flux level.
|
||||
description: Electron flux level
|
||||
example: 631
|
||||
aurora:
|
||||
type: integer
|
||||
description: Aurora activity level.
|
||||
description: Aurora activity level
|
||||
example: 5
|
||||
aurora_latitude:
|
||||
type: number
|
||||
description: Latitude in degrees of the equatorward boundary of the aurora.
|
||||
description: Lowest latitude at which aurora should be visible
|
||||
example: 66.3
|
||||
sunspots:
|
||||
type: integer
|
||||
description: Current sunspot count.
|
||||
description: Sunspot count
|
||||
example: 87
|
||||
solar_wind:
|
||||
type: number
|
||||
description: Solar wind speed in km/s.
|
||||
description: Solar wind speed in km/s
|
||||
example: 356.6
|
||||
magnetic_field:
|
||||
type: number
|
||||
description: Interplanetary magnetic field (IMF) strength in nT.
|
||||
description: Interplanetary magnetic field strength in nT
|
||||
example: 2.5
|
||||
geomag_field:
|
||||
type: string
|
||||
description: Geomagnetic field condition summary.
|
||||
description: Geomagnetic field condition summary
|
||||
example: "Active"
|
||||
geomag_noise:
|
||||
type: string
|
||||
description: Geomagnetic background noise level on HF, using S-units.
|
||||
description: Geomagnetic background noise level on HF, in S-units
|
||||
example: "S0"
|
||||
hf_conditions:
|
||||
type: array
|
||||
description: HF propagation condition assessments by band group and time of day.
|
||||
description: HF propagation condition assessments by band group and time of day
|
||||
items:
|
||||
$ref: '#/components/schemas/HFBandCondition'
|
||||
vhf_conditions:
|
||||
type: array
|
||||
description: VHF propagation condition assessments by phenomenon and location.
|
||||
description: VHF propagation condition assessments by phenomenon and location
|
||||
items:
|
||||
$ref: '#/components/schemas/VHFCondition'
|
||||
blackout_desc:
|
||||
type: string
|
||||
description: HF radio blackout risk description, derived from the X-ray flux class.
|
||||
example: "No significant radio blackout"
|
||||
proton_flux_desc:
|
||||
type: string
|
||||
description: Solar radiation storm level description, derived from proton flux.
|
||||
example: "No solar radiation storm"
|
||||
solar_storm_scale:
|
||||
type: integer
|
||||
description: Solar radiation storm scale number (S0-S5), derived from proton flux. S0 = none, S5 = extreme.
|
||||
minimum: 0
|
||||
maximum: 5
|
||||
example: 0
|
||||
geomag_storm_desc:
|
||||
type: string
|
||||
description: Geomagnetic storm level description, derived from K-index.
|
||||
example: "Quiet"
|
||||
geomag_storm_scale:
|
||||
type: integer
|
||||
description: Geomagnetic storm scale number (G0-G5), derived from K-index. G0 = none, G5 = extreme.
|
||||
minimum: 0
|
||||
maximum: 5
|
||||
example: 0
|
||||
band_conditions_desc:
|
||||
type: string
|
||||
description: Overall HF band conditions summary, derived from Solar Flux Index.
|
||||
example: "Fair to good conditions on all bands up to 10m"
|
||||
electron_flux_desc:
|
||||
type: string
|
||||
description: Electron flux impact description, derived from electron flux level.
|
||||
example: "No impact"
|
||||
|
||||
SolarConditionsProviderStatus:
|
||||
type: object
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
.navbar-nav .nav-link.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
.navbar-nav .nav-link i {
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
/* In embedded mode, hide header/footer/settings. "#header div" is kind of janky but for some reason if we hide the
|
||||
whole of #header, the map vertical sizing breaks. */
|
||||
|
||||
Reference in New Issue
Block a user