Add EME SIG and do some refactoring

This commit is contained in:
Ian Renton
2026-09-11 08:33:00 +01:00
parent a4245ed5ce
commit 3bab3cb784
12 changed files with 57 additions and 47 deletions
+16 -8
View File
@@ -12,6 +12,22 @@ HAMQTH_PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}"
# Special Interest Groups # Special Interest Groups
SIGS = [ SIGS = [
SIG(
name="AMSAT",
comment_names=[],
description="Amateur Radio Satellites",
sig_type=SIGType.WORLDWIDE,
icon="fa-satellite",
refs_globally_unique=False,
),
SIG(
name="EME",
comment_names=[],
description="Moonbounce",
sig_type=SIGType.WORLDWIDE,
icon="fa-moon",
refs_globally_unique=False,
),
SIG( SIG(
name="POTA", name="POTA",
comment_names=["POTA"], comment_names=["POTA"],
@@ -359,14 +375,6 @@ SIGS = [
region_flag="🇵🇱", region_flag="🇵🇱",
refs_globally_unique=False, refs_globally_unique=False,
), ),
SIG(
name="AMSAT",
comment_names=[],
description="Amateur Radio Satellites",
sig_type=SIGType.WORLDWIDE,
icon="fa-satellite",
refs_globally_unique=False,
),
SIG( SIG(
name="Toilets", name="Toilets",
comment_names=[], comment_names=[],
+5 -5
View File
@@ -1,7 +1,7 @@
import hashlib import hashlib
import json import json
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass, field
from datetime import datetime, timedelta from datetime import datetime, timedelta
import pytz import pytz
@@ -64,7 +64,7 @@ class Alert:
# Special Interest Group (SIG), e.g. outdoor activity programme such as POTA # Special Interest Group (SIG), e.g. outdoor activity programme such as POTA
sig: str | None = None sig: str | None = None
# SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO # SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
sig_refs: list | None = None sig_refs: list = field(default_factory=list)
# Timing info # Timing info
@@ -128,13 +128,13 @@ class Alert:
# Fetch SIG data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a reference # Fetch SIG data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a reference
# in its initial call, we use this code to populate the rest of the data. This includes working out grid refs # in its initial call, we use this code to populate the rest of the data. This includes working out grid refs
# from WAB and WAI, which count as a SIG even though there's no real lookup, just maths # from WAB and WAI, which count as a SIG even though there's no real lookup, just maths
if self.sig_refs and len(self.sig_refs) > 0: if self.sig_refs:
for sig_ref in self.sig_refs: for sig_ref in self.sig_refs:
populate_missing_sig_ref_info(sig_ref) populate_missing_sig_ref_info(sig_ref)
# If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG # If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG
# and apply it to the whole spot. # and apply it to the whole spot.
if self.sig_refs and len(self.sig_refs) > 0 and self.sig_refs[0] and not self.sig: if self.sig_refs and self.sig_refs[0] and not self.sig:
self.sig = self.sig_refs[0].sig self.sig = self.sig_refs[0].sig
# Create an ID based on the source and source ID if possible, as these guaranee uniqueness. If there is no # Create an ID based on the source and source ID if possible, as these guaranee uniqueness. If there is no
@@ -160,7 +160,7 @@ class Alert:
self.icon = "fa-trophy" self.icon = "fa-trophy"
elif self.alert_type == AlertType.CONTEST: elif self.alert_type == AlertType.CONTEST:
self.icon = "fa-satellite" self.icon = "fa-satellite"
elif self.sig_refs and len(self.sig_refs) > 0 and self.sig_refs[0].icon: elif self.sig_refs and self.sig_refs[0].icon:
self.icon = self.sig_refs[0].icon self.icon = self.sig_refs[0].icon
except Exception: except Exception:
+2 -2
View File
@@ -8,10 +8,10 @@ class SIGRef:
"""Data class that defines a Special Interest Group "info" or reference. As well as the basic reference ID we include a """Data class that defines a Special Interest Group "info" or reference. As well as the basic reference ID we include a
name and a lookup URL.""" name and a lookup URL."""
# Reference ID, e.g. "GB-0001".
id: str
# SIG that this reference is in, e.g. "POTA". # SIG that this reference is in, e.g. "POTA".
sig: str sig: str
# Reference ID, e.g. "GB-0001".
id: str | None = None
# Name of the reference, e.g. "Null Country Park", if known. # Name of the reference, e.g. "Null Country Park", if known.
name: str | None = None name: str | None = None
# Type of the reference, e.g. "Park", if known. # Type of the reference, e.g. "Park", if known.
+15 -13
View File
@@ -2,7 +2,7 @@ import hashlib
import json import json
import logging import logging
import re import re
from dataclasses import dataclass from dataclasses import dataclass, field
from datetime import datetime, timedelta from datetime import datetime, timedelta
from math import isnan from math import isnan
@@ -124,7 +124,7 @@ class Spot:
# Special Interest Group (SIG), e.g. outdoor activity programme such as POTA # Special Interest Group (SIG), e.g. outdoor activity programme such as POTA
sig: str | None = None sig: str | None = None
# SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO # SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
sig_refs: list | None = None sig_refs: list = field(default_factory=list)
# Timing info # Timing info
@@ -262,12 +262,12 @@ class Spot:
self.dx_location_source = LocationSourceForSpot.SPOT self.dx_location_source = LocationSourceForSpot.SPOT
# Set the top-level "SIG" if it is missing but we have at least one SIG ref. # Set the top-level "SIG" if it is missing but we have at least one SIG ref.
if not self.sig and self.sig_refs and len(self.sig_refs) > 0: if not self.sig and self.sig_refs:
self.sig = self.sig_refs[0].sig.upper() self.sig = self.sig_refs[0].sig.upper()
# See if we already have a SIG reference, but the comment looks like it contains more for the same SIG. This # See if we already have a SIG reference, but the comment looks like it contains more for the same SIG. This
# should catch e.g. POTA comments like "2-fer: GB-0001 GB-0002". # should catch e.g. POTA comments like "2-fer: GB-0001 GB-0002".
if self.comment and self.sig_refs and len(self.sig_refs) > 0 and self.sig_refs[0].sig: if self.comment and self.sig_refs and self.sig_refs[0].sig:
sig = self.sig_refs[0].sig.upper() sig = self.sig_refs[0].sig.upper()
regex = get_ref_regex_for_sig(sig) regex = get_ref_regex_for_sig(sig)
if regex: if regex:
@@ -315,7 +315,7 @@ class Spot:
# Fetch SIG data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a reference # Fetch SIG data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a reference
# in its initial call, we use this code to populate the rest of the data. This includes working out grid refs # in its initial call, we use this code to populate the rest of the data. This includes working out grid refs
# from WAB and WAI, which count as a SIG even though there's no real lookup, just maths # from WAB and WAI, which count as a SIG even though there's no real lookup, just maths
if self.sig_refs and len(self.sig_refs) > 0: if self.sig_refs:
for sig_ref in self.sig_refs: for sig_ref in self.sig_refs:
sig_ref = populate_missing_sig_ref_info(sig_ref) sig_ref = populate_missing_sig_ref_info(sig_ref)
# If the spot itself doesn't have location yet, but the SIG ref does, extract it # If the spot itself doesn't have location yet, but the SIG ref does, extract it
@@ -331,7 +331,7 @@ class Spot:
# If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG # If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG
# and apply it to the whole spot. # and apply it to the whole spot.
if self.sig_refs and len(self.sig_refs) > 0 and not self.sig: if self.sig_refs and not self.sig:
self.sig = self.sig_refs[0].sig self.sig = self.sig_refs[0].sig
# Parse "de_grid<prop_mode>dx_grid" structures from the comment, e.g. "JN61ES(ES)JM56XT" or "JO02GQ<>KN17LG". # Parse "de_grid<prop_mode>dx_grid" structures from the comment, e.g. "JN61ES(ES)JM56XT" or "JO02GQ<>KN17LG".
@@ -359,11 +359,15 @@ class Spot:
self.propagation_mode = mode_tag self.propagation_mode = mode_tag
logger.info(f"Seen a new propagation mode tag not yet in the system: {mode_tag}") logger.info(f"Seen a new propagation mode tag not yet in the system: {mode_tag}")
# Tag satellite contacts as AMSAT SIG # Set SIGs based on propagation mode
if self.propagation_mode == "Satellite": if self.propagation_mode == "Satellite":
if not self.sig: if not self.sig:
self.sig = "AMSAT" self.sig = "AMSAT"
self.sig_refs.append(SIGRef(sig="AMSAT")) self.sig_refs.append(SIGRef(sig="AMSAT"))
if self.propagation_mode == "Earth-Moon-Earth":
if not self.sig:
self.sig = "EME"
self.sig_refs.append(SIGRef(sig="EME"))
# Parse "de_grid -> dx_grid" structures from the comment # Parse "de_grid -> dx_grid" structures from the comment
if self.comment: if self.comment:
@@ -421,7 +425,7 @@ class Spot:
# Determine a "QTH" string. If we have a SIG ref, pick the first one and turn it into a suitable string, # Determine a "QTH" string. If we have a SIG ref, pick the first one and turn it into a suitable string,
# otherwise see what they have set on an online lookup service. # otherwise see what they have set on an online lookup service.
if self.sig_refs and len(self.sig_refs) > 0: if self.sig_refs:
qth = self.sig_refs[0].id qth = self.sig_refs[0].id
if self.sig_refs[0].name: if self.sig_refs[0].name:
qth += f" {self.sig_refs[0].name}" qth += f" {self.sig_refs[0].name}"
@@ -468,7 +472,7 @@ class Spot:
# Icon for the spot should be the icon of the first SIG ref if present, otherwise a radio tower # Icon for the spot should be the icon of the first SIG ref if present, otherwise a radio tower
self.icon = "fa-tower-cell" self.icon = "fa-tower-cell"
if self.sig_refs and len(self.sig_refs) > 0 and self.sig_refs[0].icon: if self.sig_refs and self.sig_refs[0].icon:
self.icon = self.sig_refs[0].icon self.icon = self.sig_refs[0].icon
except Exception: except Exception:
@@ -482,16 +486,14 @@ class Spot:
def _append_sig_ref_if_missing(self, new_sig_ref): def _append_sig_ref_if_missing(self, new_sig_ref):
"""Append a sig_ref to the list, so long as it's not already there.""" """Append a sig_ref to the list, so long as it's not already there."""
sig_refs = self.sig_refs or []
self.sig_refs = sig_refs
new_sig_ref.id = new_sig_ref.id.strip().upper() new_sig_ref.id = new_sig_ref.id.strip().upper()
new_sig_ref.sig = new_sig_ref.sig.strip().upper() new_sig_ref.sig = new_sig_ref.sig.strip().upper()
if new_sig_ref.id == "": if new_sig_ref.id == "":
return return
for sig_ref in sig_refs: for sig_ref in self.sig_refs:
if sig_ref.id == new_sig_ref.id and sig_ref.sig == new_sig_ref.sig: if sig_ref.id == new_sig_ref.id and sig_ref.sig == new_sig_ref.sig:
return return
sig_refs.append(new_sig_ref) self.sig_refs.append(new_sig_ref)
def expired(self): def expired(self):
"""Decide if this spot has expired (in which case it should not be added to the system in the first place, and not """Decide if this spot has expired (in which case it should not be added to the system in the first place, and not
+1 -1
View File
@@ -77,7 +77,7 @@
</div> </div>
<script src="/static/js/add-spot.js?v=1789111115"></script> <script src="/static/js/add-spot.js?v=1789111981"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active"); $("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -83,7 +83,7 @@
</div> </div>
<script src="/static/js/alerts.js?v=1789111115"></script> <script src="/static/js/alerts.js?v=1789111981"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active"); $("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -76,8 +76,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1789111115"></script> <script src="/static/js/spotsbandsandmap.js?v=1789111981"></script>
<script src="/static/js/bands.js?v=1789111115"></script> <script src="/static/js/bands.js?v=1789111981"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-bands").addClass("active"); $("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+5 -5
View File
@@ -1,6 +1,6 @@
{% extends "skeleton.html" %} {% extends "skeleton.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/css/style.css?v=1789111115" type="text/css"> <link rel="stylesheet" href="/static/css/style.css?v=1789111980" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet"> <link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -16,10 +16,10 @@
window.fetchEventSource = fetchEventSource; window.fetchEventSource = fetchEventSource;
</script> </script>
<script src="/static/js/utils.js?v=1789111115"></script> <script src="/static/js/utils.js?v=1789111980"></script>
<script src="/static/js/ui-ham.js?v=1789111115"></script> <script src="/static/js/ui-ham.js?v=1789111980"></script>
<script src="/static/js/geo.js?v=1789111115"></script> <script src="/static/js/geo.js?v=1789111980"></script>
<script src="/static/js/common.js?v=1789111115"></script> <script src="/static/js/common.js?v=1789111980"></script>
{% end %} {% end %}
{% block body %} {% block body %}
<div class="container"> <div class="container">
+1 -1
View File
@@ -284,7 +284,7 @@
</div> </div>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script> <script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1789111115"></script> <script src="/static/js/conditions.js?v=1789111981"></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>
+2 -2
View File
@@ -113,8 +113,8 @@
const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}"; const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}";
</script> </script>
<script src="/static/js/spotsbandsandmap.js?v=1789111115"></script> <script src="/static/js/spotsbandsandmap.js?v=1789111981"></script>
<script src="/static/js/map.js?v=1789111115"></script> <script src="/static/js/map.js?v=1789111981"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-map").addClass("active"); $("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -113,8 +113,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1789111115"></script> <script src="/static/js/spotsbandsandmap.js?v=1789111980"></script>
<script src="/static/js/spots.js?v=1789111115"></script> <script src="/static/js/spots.js?v=1789111980"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-spots").addClass("active"); $("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
<script src="/static/js/status.js?v=1789111115"></script> <script src="/static/js/status.js?v=1789111981"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");