Split out event type SIGs, and do other enum conversions. Closes #138

This commit is contained in:
Ian Renton
2026-09-02 09:38:05 +01:00
parent 606cf1be68
commit 9d117a6069
54 changed files with 363 additions and 155 deletions
+41 -44
View File
@@ -1,4 +1,5 @@
from core.config import SERVER_OWNER_CALLSIGN from core.config import SERVER_OWNER_CALLSIGN
from core.enums import SIGType
from data.band import Band from data.band import Band
from data.sig import SIG from data.sig import SIG
@@ -15,6 +16,7 @@ SIGS = [
name="POTA", name="POTA",
comment_names=["POTA"], comment_names=["POTA"],
description="Parks on the Air", description="Parks on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST", ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
icon="fa-tree", icon="fa-tree",
refs_globally_unique=False, refs_globally_unique=False,
@@ -23,6 +25,7 @@ SIGS = [
name="SOTA", name="SOTA",
comment_names=["SOTA"], comment_names=["SOTA"],
description="Summits on the Air", description="Summits on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}", ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-mountain-sun", icon="fa-mountain-sun",
refs_globally_unique=False, refs_globally_unique=False,
@@ -31,6 +34,7 @@ SIGS = [
name="WWFF", name="WWFF",
comment_names=["WWFF"], comment_names=["WWFF"],
description="World Wide Flora & Fauna", description="World Wide Flora & Fauna",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}", ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
icon="fa-seedling", icon="fa-seedling",
refs_globally_unique=True, refs_globally_unique=True,
@@ -39,6 +43,7 @@ SIGS = [
name="GMA", name="GMA",
comment_names=["GMA"], comment_names=["GMA"],
description="Global Mountain Activity", description="Global Mountain Activity",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}", ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
icon="fa-person-hiking", icon="fa-person-hiking",
refs_globally_unique=False, refs_globally_unique=False,
@@ -47,6 +52,7 @@ SIGS = [
name="WWBOTA", name="WWBOTA",
comment_names=["WWBOTA", "BOTA"], comment_names=["WWBOTA", "BOTA"],
description="Worldwide Bunkers on the Air", description="Worldwide Bunkers on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}", ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}",
icon="fa-radiation", icon="fa-radiation",
refs_globally_unique=True, refs_globally_unique=True,
@@ -55,6 +61,7 @@ SIGS = [
name="HEMA", name="HEMA",
comment_names=["HEMA"], comment_names=["HEMA"],
description="HuMPs Excluding Marilyns Award", description="HuMPs Excluding Marilyns Award",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}", ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
icon="fa-mound", icon="fa-mound",
refs_globally_unique=False, refs_globally_unique=False,
@@ -63,6 +70,7 @@ SIGS = [
name="IOTA", name="IOTA",
comment_names=["IOTA"], comment_names=["IOTA"],
description="Islands on the Air", description="Islands on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2}\-\d{3}", ref_regex=r"[A-Z]{2}\-\d{3}",
icon="fa-book-atlas", icon="fa-book-atlas",
refs_globally_unique=False, refs_globally_unique=False,
@@ -71,6 +79,7 @@ SIGS = [
name="GMA Islands", name="GMA Islands",
comment_names=[], comment_names=[],
description="Global Mountain Activity - Islands", description="Global Mountain Activity - Islands",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"(([A-Z]{2}\-\d{3})|([A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}))", ref_regex=r"(([A-Z]{2}\-\d{3})|([A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}))",
icon="fa-person-hiking", icon="fa-person-hiking",
refs_globally_unique=False, refs_globally_unique=False,
@@ -79,6 +88,7 @@ SIGS = [
name="ARLHS", name="ARLHS",
comment_names=["ARLHS"], comment_names=["ARLHS"],
description="Amateur Radio Lighthouse Society", description="Amateur Radio Lighthouse Society",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{3}[\- ]\d{3,4}", ref_regex=r"[A-Z]{3}[\- ]\d{3,4}",
icon="fa-house-flood-water", icon="fa-house-flood-water",
refs_globally_unique=False, refs_globally_unique=False,
@@ -87,6 +97,7 @@ SIGS = [
name="ILLW", name="ILLW",
comment_names=["ILLW"], comment_names=["ILLW"],
description="International Lighthouse & Lightship Weekend", description="International Lighthouse & Lightship Weekend",
sig_type=SIGType.EVENT,
ref_regex=r"[A-Z]{2}\d{4}", ref_regex=r"[A-Z]{2}\d{4}",
icon="fa-house-flood-water", icon="fa-house-flood-water",
refs_globally_unique=False, refs_globally_unique=False,
@@ -95,6 +106,7 @@ SIGS = [
name="MOTA", name="MOTA",
comment_names=["MOTA"], comment_names=["MOTA"],
description="Mills on the Air", description="Mills on the Air",
sig_type=SIGType.EVENT,
ref_regex=r"X\d{4,6}", ref_regex=r"X\d{4,6}",
icon="fa-fan", icon="fa-fan",
refs_globally_unique=True, refs_globally_unique=True,
@@ -103,6 +115,7 @@ SIGS = [
name="SIOTA", name="SIOTA",
comment_names=["SIOTA"], comment_names=["SIOTA"],
description="Silos on the Air", description="Silos on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d", ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
icon="fa-wheat-awn", icon="fa-wheat-awn",
refs_globally_unique=False, refs_globally_unique=False,
@@ -111,6 +124,7 @@ SIGS = [
name="WCA", name="WCA",
comment_names=["WCA"], comment_names=["WCA"],
description="World Castles Award", description="World Castles Award",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z0-9]{1,3}\-\d{5}", ref_regex=r"[A-Z0-9]{1,3}\-\d{5}",
icon="fa-chess-rook", icon="fa-chess-rook",
refs_globally_unique=False, refs_globally_unique=False,
@@ -119,6 +133,7 @@ SIGS = [
name="ZLOTA", name="ZLOTA",
comment_names=["ZLOTA"], comment_names=["ZLOTA"],
description="New Zealand on the Air", description="New Zealand on the Air",
sig_type=SIGType.REGIONAL,
ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}", ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}",
icon="fa-kiwi-bird", icon="fa-kiwi-bird",
region_flag="🇳🇿", region_flag="🇳🇿",
@@ -128,32 +143,45 @@ SIGS = [
name="WOTA", name="WOTA",
comment_names=["WOTA"], comment_names=["WOTA"],
description="Wainwrights on the Air", description="Wainwrights on the Air",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{3}-[0-9]{2}", ref_regex=r"[A-Z]{3}-[0-9]{2}",
icon="fa-w", icon="fa-w",
region_flag="🇬🇧", region_flag="🇬🇧",
refs_globally_unique=False, refs_globally_unique=False,
), ),
SIG(name="BOTA", comment_names=[], description="Beaches on the Air", icon="fa-umbrella-beach"), SIG(
name="BOTA",
comment_names=[],
description="Beaches on the Air",
sig_type=SIGType.WORLDWIDE,
icon="fa-umbrella-beach",
refs_globally_unique=False,
),
SIG( SIG(
name="KRMNPA", name="KRMNPA",
comment_names=["KRMNPA"], comment_names=["KRMNPA"],
description="Keith Roget Memorial National Parks Award", description="Keith Roget Memorial National Parks Award",
sig_type=SIGType.REGIONAL,
ref_regex=r"VKFF\-\d{4}", ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania", icon="fa-earth-oceania",
region_flag="🇦🇺", region_flag="🇦🇺",
refs_globally_unique=False,
), ),
SIG( SIG(
name="SANPCPA", name="SANPCPA",
comment_names=["SANPCPA"], comment_names=["SANPCPA"],
description="South Australian National Parks and Conservation Parks Award", description="South Australian National Parks and Conservation Parks Award",
sig_type=SIGType.REGIONAL,
ref_regex=r"VKFF\-\d{4}", ref_regex=r"VKFF\-\d{4}",
icon="fa-earth-oceania", icon="fa-earth-oceania",
region_flag="🇦🇺", region_flag="🇦🇺",
refs_globally_unique=False,
), ),
SIG( SIG(
name="LLOTA", name="LLOTA",
comment_names=["LLOTA"], comment_names=["LLOTA"],
description="Lagos y Lagunas on the Air", description="Lagos y Lagunas on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"LL[A-Z]{2}\-\d{4}", ref_regex=r"LL[A-Z]{2}\-\d{4}",
icon="fa-water", icon="fa-water",
refs_globally_unique=True, refs_globally_unique=True,
@@ -162,6 +190,7 @@ SIGS = [
name="Towers", name="Towers",
comment_names=["TOTA"], comment_names=["TOTA"],
description="Towers on the Air", description="Towers on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Z]{2,3}R\-\d{4}", ref_regex=r"[A-Z]{2,3}R\-\d{4}",
icon="fa-tower-observation", icon="fa-tower-observation",
refs_globally_unique=False, refs_globally_unique=False,
@@ -170,6 +199,7 @@ SIGS = [
name="Tiles", name="Tiles",
comment_names=[], comment_names=[],
description="Tiles on the Air", description="Tiles on the Air",
sig_type=SIGType.WORLDWIDE,
ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}", ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}",
icon="fa-square", icon="fa-square",
refs_globally_unique=False, refs_globally_unique=False,
@@ -178,6 +208,7 @@ SIGS = [
name="WAB", name="WAB",
comment_names=["WAB"], comment_names=["WAB"],
description="Worked All Britain", description="Worked All Britain",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{1,2}[0-9]{2}", ref_regex=r"[A-Z]{1,2}[0-9]{2}",
icon="fa-table-cells-large", icon="fa-table-cells-large",
region_flag="🇬🇧", region_flag="🇬🇧",
@@ -187,6 +218,7 @@ SIGS = [
name="WAI", name="WAI",
comment_names=["WAI"], comment_names=["WAI"],
description="Worked All Ireland", description="Worked All Ireland",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z][0-9]{2}", ref_regex=r"[A-Z][0-9]{2}",
icon="fa-table-cells-large", icon="fa-table-cells-large",
region_flag="🇮🇪", region_flag="🇮🇪",
@@ -196,6 +228,7 @@ SIGS = [
name="DME", name="DME",
comment_names=["DME"], comment_names=["DME"],
description="Diploma Municipios de España", description="Diploma Municipios de España",
sig_type=SIGType.REGIONAL,
ref_regex=r"\d{4,5}", ref_regex=r"\d{4,5}",
icon="fa-building", icon="fa-building",
region_flag="🇪🇸", region_flag="🇪🇸",
@@ -205,6 +238,7 @@ SIGS = [
name="FEA", name="FEA",
comment_names=["FEA"], comment_names=["FEA"],
description="Diploma Faros de España", description="Diploma Faros de España",
sig_type=SIGType.REGIONAL,
# FEA references are technically [DE]\-\d{4}(\.\d)? but spotters always seem to miss out the D- or E- # FEA references are technically [DE]\-\d{4}(\.\d)? but spotters always seem to miss out the D- or E-
# prefix and just use FEA-1234 or FEA 1234, so allow for that. The FEA sigref data provider adds both # prefix and just use FEA-1234 or FEA 1234, so allow for that. The FEA sigref data provider adds both
# forms to the database. # forms to the database.
@@ -217,6 +251,7 @@ SIGS = [
name="DTMBA", name="DTMBA",
comment_names=["DTMBA"], comment_names=["DTMBA"],
description="Diploma Teatri Musei e Belle Arti", description="Diploma Teatri Musei e Belle Arti",
sig_type=SIGType.REGIONAL,
ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}", ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}",
icon="fa-masks-theater", icon="fa-masks-theater",
region_flag="🇮🇹", region_flag="🇮🇹",
@@ -226,13 +261,16 @@ SIGS = [
name="BIWOTA", name="BIWOTA",
comment_names=["BIWOTA"], comment_names=["BIWOTA"],
description="British Inland Waterways on the Air", description="British Inland Waterways on the Air",
sig_type=SIGType.EVENT,
icon="fa-ship", icon="fa-ship",
region_flag="🇬🇧", region_flag="🇬🇧",
refs_globally_unique=False,
), ),
SIG( SIG(
name="COTA", name="COTA",
comment_names=["COTA"], comment_names=["COTA"],
description="Castles on the Air", description="Castles on the Air",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{3}\-[0-9]{3,5}", ref_regex=r"[A-Z]{3}\-[0-9]{3,5}",
icon="fa-chess-rook", icon="fa-chess-rook",
region_flag="🇩🇪", region_flag="🇩🇪",
@@ -242,6 +280,7 @@ SIGS = [
name="PGA", name="PGA",
comment_names=["PGA"], comment_names=["PGA"],
description="Polish Gmina Award", description="Polish Gmina Award",
sig_type=SIGType.REGIONAL,
ref_regex=r"[A-Z]{2}[0-9]{2}", ref_regex=r"[A-Z]{2}[0-9]{2}",
icon="fa-g", icon="fa-g",
region_flag="🇵🇱", region_flag="🇵🇱",
@@ -251,6 +290,7 @@ SIGS = [
name="Toilets", name="Toilets",
comment_names=[], comment_names=[],
description="Toilets on the Air", description="Toilets on the Air",
sig_type=SIGType.EVENT,
ref_regex=r"T\-[0-9]{2}", ref_regex=r"T\-[0-9]{2}",
icon="fa-toilet", icon="fa-toilet",
region_flag="🏴‍☠️", region_flag="🏴‍☠️",
@@ -258,49 +298,6 @@ SIGS = [
), ),
] ]
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
CW_MODES = ["CW"]
PHONE_MODES = ["PHONE", "SSB", "AM", "FM", "DV"]
DATA_MODES = [
"DATA",
"FT8",
"FT4",
"RTTY",
"SSTV",
"JS8",
"HELL",
"PSK",
"FSK",
"OLIVIA",
"PKT",
"MSK144",
]
ALL_MODES = CW_MODES + PHONE_MODES + DATA_MODES
# Mode aliases. Sometimes we get spots with a mode described in a different way that is effectively the same as a mode
# we already know, or we want to normalise things for consistency. The lookup table for this is here. Incoming spots
# that match a key in this table will be converted to the corresponding value, so only the modes above will actually be
# present in the spots.
MODE_ALIASES = {
"USB": "SSB",
"LSB": "SSB",
"DIGITALVOICE": "DV",
"DIGITALVOI": "DV",
"DMR": "DV",
"DSTAR": "DV",
"C4FM": "DV",
"FUSION": "DV",
"M17": "DV",
"RTT": "RTTY",
"BPSK": "PSK",
"PSK31": "PSK",
"BPSK31": "PSK",
"MFSK": "FSK",
"MFSK32": "FSK",
"DIGI": "DATA",
"DIGITAL": "DATA",
}
# Band definitions # Band definitions
BANDS = [ BANDS = [
Band(name="2200m", start_freq=135700, end_freq=137800), Band(name="2200m", start_freq=135700, end_freq=137800),
+102
View File
@@ -11,6 +11,52 @@ class Continent(str, Enum):
AN = "AN" AN = "AN"
class Mode(str, Enum):
CW = "CW"
PHONE = "PHONE"
SSB = "SSB"
AM = "AM"
FM = "FM"
DV = "DV"
DATA = "DATA"
FT8 = "FT8"
FT4 = "FT4"
RTTY = "RTTY"
SSTV = "SSTV"
JS8 = "JS8"
HELL = "HELL"
PSK = "PSK"
FSK = "FSK"
OLIVIA = "OLIVIA"
PKT = "PKT"
MSK144 = "MSK144"
UNKNOWN = "UNKNOWN"
@property
def is_cw(self) -> bool:
return self == Mode.CW
@property
def is_phone(self) -> bool:
return self in {Mode.PHONE, Mode.SSB, Mode.AM, Mode.FM, Mode.DV}
@property
def is_data(self) -> bool:
return not (self.is_cw or self.is_phone or self == Mode.UNKNOWN)
@staticmethod
def from_name(name):
"""Convert a string to an enum mode using the alias table."""
try:
return Mode(name.upper())
except ValueError:
try:
return Mode(MODE_ALIASES[name.upper()])
except ValueError:
return Mode.UNKNOWN
class ModeType(str, Enum): class ModeType(str, Enum):
PHONE = "PHONE" PHONE = "PHONE"
CW = "CW" CW = "CW"
@@ -44,3 +90,59 @@ class LocationSourceForCallsign(str, Enum):
HOME_QTH = "HOME QTH" HOME_QTH = "HOME QTH"
DXCC = "DXCC" DXCC = "DXCC"
NONE = "NONE" NONE = "NONE"
class SIGRefType(str, Enum):
"""Type of a Special Interest Group reference."""
PARK = "PARK"
SUMMIT = "SUMMIT"
CASTLE = "CASTLE"
LIGHTHOUSE = "LIGHTHOUSE"
ISLAND = "ISLAND"
BUNKER = "BUNKER"
MILL = "MILL"
SILO = "SILO"
BEACH = "BEACH"
LAKE = "LAKE"
TOWER = "TOWER"
WATERWAY = "WATERWAY"
TOWN = "TOWN"
BUILDING = "BUILDING"
REGION = "REGION"
GRID = "GRID"
TOILET = "TOILET"
UNKNOWN = "UNKNOWN"
class SIGType(str, Enum):
"""Type of a Special Interest Group. Used to group them in the web UI."""
WORLDWIDE = "WORLDWIDE"
REGIONAL = "REGIONAL"
EVENT = "EVENT"
# Mode aliases. Sometimes we get spots with a mode described in a different way that is effectively the same as a mode
# we already know, or we want to normalise things for consistency. The lookup table for this is here. Incoming spots
# that match a key in this table will be converted to the corresponding value, so only the modes above will actually be
# present in the spots.
MODE_ALIASES = {
"USB": "SSB",
"LSB": "SSB",
"DIGITALVOICE": "DV",
"DIGITALVOI": "DV",
"DMR": "DV",
"DSTAR": "DV",
"C4FM": "DV",
"FUSION": "DV",
"M17": "DV",
"RTT": "RTTY",
"BPSK": "PSK",
"PSK31": "PSK",
"BPSK31": "PSK",
"MFSK": "FSK",
"MFSK32": "FSK",
"DIGI": "DATA",
"DIGITAL": "DATA",
}
+24 -23
View File
@@ -5,17 +5,9 @@ import simplejson
from pyhamtools.frequency import freq_to_band from pyhamtools.frequency import freq_to_band
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.constants import ( from core.constants import BANDS, UNKNOWN_BAND
ALL_MODES,
BANDS,
CW_MODES,
DATA_MODES,
MODE_ALIASES,
PHONE_MODES,
UNKNOWN_BAND,
)
from core.data_store import DATA_STORE from core.data_store import DATA_STORE
from core.enums import Continent, ModeType from core.enums import MODE_ALIASES, Continent, Mode, ModeType
from data.callsign import Callsign, LocationSourceForCallsign from data.callsign import Callsign, LocationSourceForCallsign
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,31 +20,40 @@ def safe_json_dumps(obj):
return simplejson.dumps(obj, ensure_ascii=False, ignore_nan=True, default=lambda o: o.__dict__) return simplejson.dumps(obj, ensure_ascii=False, ignore_nan=True, default=lambda o: o.__dict__)
def infer_mode_from_comment(comment): def infer_mode_from_comment(comment: str) -> Mode:
"""Infer a mode from the comment""" """Infer a mode from the comment"""
for mode in ALL_MODES: if not comment:
return Mode.UNKNOWN
for mode in Mode:
if re.search(r"(^|\W)" + mode + r"($|\W)", comment, re.IGNORECASE): if re.search(r"(^|\W)" + mode + r"($|\W)", comment, re.IGNORECASE):
return mode return mode
for mode in MODE_ALIASES: for alias in MODE_ALIASES:
if re.search(r"(^|\W)" + mode + r"($|\W)", comment, re.IGNORECASE): if re.search(r"(^|\W)" + alias + r"($|\W)", comment, re.IGNORECASE):
return MODE_ALIASES[mode] return Mode(MODE_ALIASES[alias])
return None
return Mode.UNKNOWN
def infer_mode_type_from_mode(mode): def infer_mode_type_from_mode(mode: str) -> ModeType:
"""Infer a "mode family" from a mode.""" """Infer a "mode family" from a mode ."""
if mode.upper() in CW_MODES: if not mode:
return ModeType.UNKNOWN
try:
mode = Mode(mode.upper())
if mode.is_cw:
return ModeType.CW return ModeType.CW
elif mode.upper() in PHONE_MODES: if mode.is_phone:
return ModeType.PHONE return ModeType.PHONE
elif mode.upper() in DATA_MODES:
return ModeType.DATA return ModeType.DATA
else:
except ValueError:
if mode.upper() != "OTHER" and mode != "?": if mode.upper() != "OTHER" and mode != "?":
logger.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.") logger.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.")
return None return ModeType.UNKNOWN
def infer_band_from_freq(freq): def infer_band_from_freq(freq):
+1 -1
View File
@@ -93,7 +93,7 @@ class Alert:
if self.dx_calls and self.dx_calls[0] and not self.dx_country: if self.dx_calls and self.dx_calls[0] and not self.dx_country:
self.dx_country = call_info.country self.dx_country = call_info.country
if self.dx_calls and self.dx_calls[0] and not self.dx_continent: if self.dx_calls and self.dx_calls[0] and not self.dx_continent:
self.dx_continent = Continent(call_info.continent) self.dx_continent = Continent(call_info.continent) if call_info.continent else None
if self.dx_calls and self.dx_calls[0] and not self.dx_cq_zone: if self.dx_calls and self.dx_calls[0] and not self.dx_cq_zone:
self.dx_cq_zone = call_info.cq_zone self.dx_cq_zone = call_info.cq_zone
if self.dx_calls and self.dx_calls[0] and not self.dx_itu_zone: if self.dx_calls and self.dx_calls[0] and not self.dx_itu_zone:
+9 -5
View File
@@ -1,5 +1,7 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from core.enums import SIGType
@dataclass @dataclass
class SIG: class SIG:
@@ -15,16 +17,18 @@ class SIG:
name: str name: str
# Description, e.g. "Towers on the Air" # Description, e.g. "Towers on the Air"
description: str description: str
# SIG names as they might appear in cluster spot comments, e.g. ["TOTA"] # Type, either Worldwide, Regional or Event. Used for sorting in the web UI.
comment_names: list[str] = field(default_factory=list) sig_type: SIGType
# Regex matcher for references, e.g. for POTA r"[A-Z]{2}\-\d+".
ref_regex: str | None = None
# Identifies that the SIG's reference ID structure defined by its regex is unique across all programmes and # Identifies that the SIG's reference ID structure defined by its regex is unique across all programmes and
# anything else we expect a user to put in a spot comment, and therefore we can pull references out of # anything else we expect a user to put in a spot comment, and therefore we can pull references out of
# spot comments without also needing to see the SIG name first. For example, "OHFF-1234" or "B/G-1234" are # spot comments without also needing to see the SIG name first. For example, "OHFF-1234" or "B/G-1234" are
# obviously WWFF and WWBOTA, nothing else looks like those. But "SZ09" could be WAB or Tiles, "GB1234" could # obviously WWFF and WWBOTA, nothing else looks like those. But "SZ09" could be WAB or Tiles, "GB1234" could
# conceivably be POTA or ILLW, etc. # conceivably be POTA or ILLW, etc.
refs_globally_unique: bool = False refs_globally_unique: bool
# SIG names as they might appear in cluster spot comments, e.g. ["TOTA"]
comment_names: list[str] = field(default_factory=list)
# Regex matcher for references, e.g. for POTA r"[A-Z]{2}\-\d+".
ref_regex: str | None = None
# Icon to use in the UI when referencing this SIG. Chosen from the Font Awesome set. # Icon to use in the UI when referencing this SIG. Chosen from the Font Awesome set.
icon: str | None = None icon: str | None = None
# Emoji flag for the country or region where this SIG is relevant, if any. If None, this implies the SIG is in # Emoji flag for the country or region where this SIG is relevant, if any. If None, this implies the SIG is in
+3 -1
View File
@@ -1,5 +1,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from core.enums import SIGRefType
@dataclass @dataclass
class SIGRef: class SIGRef:
@@ -13,7 +15,7 @@ class SIGRef:
# 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.
ref_type: str | None = None ref_type: SIGRefType = SIGRefType.UNKNOWN
# URL to look up more information about the reference, if known. # URL to look up more information about the reference, if known.
url: str | None = None url: str | None = None
# Latitude of the reference, in degrees, if known. # Latitude of the reference, in degrees, if known.
+11 -11
View File
@@ -11,8 +11,8 @@ from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from core.call_lookup_helper import get_call_info from core.call_lookup_helper import get_call_info
from core.config import MAX_SPOT_AGE from core.config import MAX_SPOT_AGE
from core.constants import MODE_ALIASES, PROPAGATION_MODES, SIGS from core.constants import PROPAGATION_MODES, SIGS
from core.enums import Continent, LocationSourceForSpot, ModeSource, ModeType from core.enums import Continent, LocationSourceForSpot, Mode, ModeSource, ModeType
from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone
from core.sig_lookup_helper import populate_missing_sig_ref_info from core.sig_lookup_helper import populate_missing_sig_ref_info
from core.sig_utils import ( from core.sig_utils import (
@@ -103,7 +103,7 @@ class Spot:
# General QSO info # General QSO info
# Reported mode, such as SSB, PHONE, CW, FT8... # Reported mode, such as SSB, PHONE, CW, FT8...
mode: str | None = None mode: Mode = Mode.UNKNOWN
# Inferred mode "family". # Inferred mode "family".
mode_type: ModeType = ModeType.UNKNOWN mode_type: ModeType = ModeType.UNKNOWN
# Source of the mode information. # Source of the mode information.
@@ -239,21 +239,21 @@ class Spot:
self.band = band.name self.band = band.name
# Mode from comments or bandplan # Mode from comments or bandplan
if self.mode: if not self.mode:
self.mode = Mode.UNKNOWN
if self.mode != Mode.UNKNOWN:
self.mode_source = ModeSource.SPOT self.mode_source = ModeSource.SPOT
if self.comment and not self.mode: if self.comment and self.mode == Mode.UNKNOWN:
self.mode = infer_mode_from_comment(self.comment) self.mode = infer_mode_from_comment(self.comment)
self.mode_source = ModeSource.COMMENT self.mode_source = ModeSource.COMMENT
if self.freq and not self.mode: if self.freq and self.mode == Mode.UNKNOWN:
self.mode = infer_mode_from_frequency(self.freq) self.mode = infer_mode_from_frequency(self.freq)
self.mode_source = ModeSource.BANDPLAN self.mode_source = ModeSource.BANDPLAN
# Normalise mode if necessary.
if self.mode in MODE_ALIASES:
self.mode = MODE_ALIASES[self.mode]
# Mode type from mode # Mode type from mode
if self.mode and not self.mode_type: if not self.mode_type:
self.mode_type = ModeType.UNKNOWN
if self.mode != Mode.UNKNOWN and self.mode_type == ModeType.UNKNOWN:
self.mode_type = infer_mode_type_from_mode(self.mode) self.mode_type = infer_mode_type_from_mode(self.mode)
# If we have a latitude or grid at this point, it can only have been provided by the spot itself # If we have a latitude or grid at this point, it can only have been provided by the spot itself
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -27,7 +28,7 @@ class ARLHS(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("Name", None), name=row.get("Name", None),
ref_type="Lighthouse", ref_type=SIGRefType.LIGHTHOUSE,
url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None, latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None, longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None,
+2 -1
View File
@@ -2,6 +2,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider
@@ -32,7 +33,7 @@ class COTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=name, name=name,
ref_type="Castle", ref_type=SIGRefType.CASTLE,
grid=grid, grid=grid,
latitude=lat, latitude=lat,
longitude=lon, longitude=lon,
+2 -1
View File
@@ -3,6 +3,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.local_file_sig_ref_data_provider import ( from providers.sigrefdata.local_file_sig_ref_data_provider import (
LocalFileSIGRefDataProvider, LocalFileSIGRefDataProvider,
@@ -37,7 +38,7 @@ class DME(LocalFileSIGRefDataProvider):
ref = SIGRef( ref = SIGRef(
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
ref_type="Town", ref_type=SIGRefType.TOWN,
name=f"{row['NOMBRE_ACTUAL']}, {row['PROVINCIA']}", name=f"{row['NOMBRE_ACTUAL']}, {row['PROVINCIA']}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
+2 -1
View File
@@ -1,5 +1,6 @@
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider
@@ -20,7 +21,7 @@ class DTMBA(FileDownloadSIGRefDataProvider):
split = row.split(";") split = row.split(";")
ref_id = split[0] ref_id = split[0]
ref_name = split[1] ref_name = split[1]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=ref_name, ref_type="Building")) new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=ref_name, ref_type=SIGRefType.BUILDING))
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of # Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
# the data in this case # the data in this case
+3 -2
View File
@@ -3,6 +3,7 @@ from time import sleep
import pdfplumber import pdfplumber
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider
@@ -40,8 +41,8 @@ class FEA(FileDownloadSIGRefDataProvider):
# prefix and just use FEA-1234 or FEA 1234, so we add both copies to the database. # prefix and just use FEA-1234 or FEA 1234, so we add both copies to the database.
ref_id_1 = row[0].strip() ref_id_1 = row[0].strip()
ref_id_2 = ref_id_1.replace("D-", "FEA-").replace("E-", "FEA-") ref_id_2 = ref_id_1.replace("D-", "FEA-").replace("E-", "FEA-")
new_data.append(SIGRef(sig=self.SIG, id=ref_id_1, name=row[1].strip(), ref_type="Lighthouse")) new_data.append(SIGRef(sig=self.SIG, id=ref_id_1, name=row[1].strip(), ref_type=SIGRefType.LIGHTHOUSE))
new_data.append(SIGRef(sig=self.SIG, id=ref_id_2, name=row[1].strip(), ref_type="Lighthouse")) new_data.append(SIGRef(sig=self.SIG, id=ref_id_2, name=row[1].strip(), ref_type=SIGRefType.LIGHTHOUSE))
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of # Bail out if a stop has been requested, i.e. the program is shutting down - no need to parse the rest of
# the data in this case # the data in this case
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class GMA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("Name", None), name=row.get("Name", None),
ref_type="Summit", ref_type=SIGRefType.SUMMIT,
url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None, latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None, longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None,
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -27,7 +28,7 @@ class ILLW(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("Name", None), name=row.get("Name", None),
ref_type="Lighthouse", ref_type=SIGRefType.LIGHTHOUSE,
url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None, latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None, longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None,
+2 -1
View File
@@ -3,6 +3,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -44,7 +45,7 @@ class IOTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=ref["name"], name=ref["name"],
ref_type="Island", ref_type=SIGRefType.ISLAND,
grid=grid, grid=grid,
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
+2 -1
View File
@@ -2,6 +2,7 @@ from time import sleep
from pyhamtools.locator import locator_to_latlong from pyhamtools.locator import locator_to_latlong
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -32,7 +33,7 @@ class LLOTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=str(ref["name"]), name=str(ref["name"]),
ref_type="Lake", ref_type=SIGRefType.LAKE,
url=f"https://llota.app/list/ref/{ref_id}", url=f"https://llota.app/list/ref/{ref_id}",
grid=grid, grid=grid,
latitude=ll[0], latitude=ll[0],
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class MOTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("Name", None), name=row.get("Name", None),
ref_type="Mill", ref_type=SIGRefType.MILL,
url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None, latitude=float(row["Latitude"]) if "Latitude" in row and row["Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None, longitude=float(row["Longitude"]) if "Longitude" in row and row["Longitude"] != "" else None,
+2 -1
View File
@@ -2,6 +2,7 @@ from time import sleep
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider
@@ -41,7 +42,7 @@ class PGA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=name, name=name,
ref_type="Region", ref_type=SIGRefType.REGION,
) )
) )
@@ -4,6 +4,7 @@ from time import sleep
from fastkml import kml from fastkml import kml
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -43,7 +44,7 @@ class ParksNPeaksKMLSIGRefDataProvider(FileDownloadSIGRefDataProvider):
sig=self.sig_name, sig=self.sig_name,
id=ref_id, id=ref_id,
name=placemark.name, name=placemark.name,
ref_type="Park", ref_type=SIGRefType.PARK,
url=f"https://parksnpeaks.org/getPark.php?actPark={ref_id}", url=f"https://parksnpeaks.org/getPark.php?actPark={ref_id}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class POTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("name", None), name=row.get("name", None),
ref_type="Park", ref_type=SIGRefType.PARK,
url=f"https://pota.app/#/park/{ref_id}", url=f"https://pota.app/#/park/{ref_id}",
grid=row.get("grid", None), grid=row.get("grid", None),
latitude=float(row["latitude"]) if "latitude" in row and row["latitude"] != "" else None, latitude=float(row["latitude"]) if "latitude" in row and row["latitude"] != "" else None,
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class SIOTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("NAME", None), name=row.get("NAME", None),
ref_type="Silo", ref_type=SIGRefType.SILO,
grid=row.get("LOCATOR", None), grid=row.get("LOCATOR", None),
latitude=float(row["LAT"]) if "LAT" in row else None, latitude=float(row["LAT"]) if "LAT" in row else None,
longitude=float(row["LNG"]) if "LNG" in row else None, longitude=float(row["LNG"]) if "LNG" in row else None,
+2 -1
View File
@@ -3,6 +3,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -30,7 +31,7 @@ class SOTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("SummitName", None), name=row.get("SummitName", None),
ref_type="Summit", ref_type=SIGRefType.SUMMIT,
url=f"https://www.sotadata.org.uk/en/summit/{ref_id}", url=f"https://www.sotadata.org.uk/en/summit/{ref_id}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
+2 -1
View File
@@ -1,5 +1,6 @@
import csv import csv
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.local_file_sig_ref_data_provider import ( from providers.sigrefdata.local_file_sig_ref_data_provider import (
LocalFileSIGRefDataProvider, LocalFileSIGRefDataProvider,
@@ -26,7 +27,7 @@ class Toilets(LocalFileSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=row["ref"], id=row["ref"],
name=row["ref"], name=row["ref"],
ref_type="Toilet", ref_type=SIGRefType.TOILET,
latitude=float(row["lat"]), latitude=float(row["lat"]),
longitude=float(row["lon"]), longitude=float(row["lon"]),
) )
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class Towers(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("Nazev", None), name=row.get("Nazev", None),
ref_type="Tower", ref_type=SIGRefType.TOWER,
url=f"https://wwtota.com/seznam/karta_rozhledny.php?ref={ref_id}", url=f"https://wwtota.com/seznam/karta_rozhledny.php?ref={ref_id}",
grid=row["Lokator"] if "Lokator" in row and row["Lokator"] != "" else None, grid=row["Lokator"] if "Lokator" in row and row["Lokator"] != "" else None,
latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None, latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None,
+2 -1
View File
@@ -4,6 +4,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -45,7 +46,7 @@ class WCA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("CLEAN NAME", None), name=row.get("CLEAN NAME", None),
ref_type="Castle", ref_type=SIGRefType.CASTLE,
url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
+2 -1
View File
@@ -1,5 +1,6 @@
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -33,7 +34,7 @@ class WOTA(FileDownloadSIGRefDataProvider):
id=ref_id, id=ref_id,
name=feature["properties"]["title"], name=feature["properties"]["title"],
url=url, url=url,
ref_type="Summit", ref_type=SIGRefType.SUMMIT,
grid=feature["properties"]["qthLocator"], grid=feature["properties"]["qthLocator"],
latitude=feature["geometry"]["coordinates"][1], latitude=feature["geometry"]["coordinates"][1],
longitude=feature["geometry"]["coordinates"][0], longitude=feature["geometry"]["coordinates"][0],
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class WWBOTA(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("Name", None), name=row.get("Name", None),
ref_type="Bunker", ref_type=SIGRefType.BUNKER,
url=f"https://bunkerwiki.org/?s={ref_id}" if ref_id.startswith("B/G") else None, url=f"https://bunkerwiki.org/?s={ref_id}" if ref_id.startswith("B/G") else None,
grid=row["Locator"] if "Locator" in row and row["Locator"] != "" else None, grid=row["Locator"] if "Locator" in row and row["Locator"] != "" else None,
latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None, latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None,
+2 -1
View File
@@ -1,6 +1,7 @@
import csv import csv
from time import sleep from time import sleep
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,7 +27,7 @@ class WWFF(FileDownloadSIGRefDataProvider):
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=row.get("name", None), name=row.get("name", None),
ref_type="Park", ref_type=SIGRefType.PARK,
url=f"https://wwff.co/directory/?showRef={ref_id}", url=f"https://wwff.co/directory/?showRef={ref_id}",
grid=row["iaruLocator"] if "iaruLocator" in row and row["iaruLocator"] != "-" else None, grid=row["iaruLocator"] if "iaruLocator" in row and row["iaruLocator"] != "-" else None,
latitude=float(row["latitude"]) latitude=float(row["latitude"])
+6 -1
View File
@@ -2,6 +2,7 @@ from time import sleep
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import ( from providers.sigrefdata.file_download_sig_ref_data_provider import (
FileDownloadSIGRefDataProvider, FileDownloadSIGRefDataProvider,
@@ -26,12 +27,16 @@ class ZLOTA(FileDownloadSIGRefDataProvider):
ref_id = ref["code"] ref_id = ref["code"]
latitude = ref["latitude"] latitude = ref["latitude"]
longitude = ref["longitude"] longitude = ref["longitude"]
try:
ref_type = SIGRefType(ref["asset_type"].title().upper())
except ValueError:
ref_type = SIGRefType.UNKNOWN
new_ref = SIGRef( new_ref = SIGRef(
sig=self.SIG, sig=self.SIG,
id=ref_id, id=ref_id,
name=ref["name"], name=ref["name"],
ref_type=ref["asset_type"].title(), ref_type=ref_type,
url=f"https://ontheair.nz/assets/{ref_id.replace('/', '_')}", url=f"https://ontheair.nz/assets/{ref_id.replace('/', '_')}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
+11 -1
View File
@@ -4,6 +4,7 @@ from datetime import datetime
import pytz import pytz
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import Mode, SIGRefType
from core.url_data_cache import URLDataCache from core.url_data_cache import URLDataCache
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
@@ -65,7 +66,9 @@ class GMA(HTTPSpotProvider):
if (source_spot["QRG"] != "" and source_spot["QRG"] != "QRT") if (source_spot["QRG"] != "" and source_spot["QRG"] != "QRT")
else None, else None,
# Filter out some weird mode strings # Filter out some weird mode strings
mode=source_spot["MODE"].upper() if "<>" not in source_spot["MODE"] else None, mode=Mode.from_name(source_spot["MODE"].upper())
if "<>" not in source_spot["MODE"]
else Mode.UNKNOWN,
comment=source_spot["TEXT"], comment=source_spot["TEXT"],
sig_refs=[ sig_refs=[
SIGRef( SIGRef(
@@ -113,24 +116,31 @@ class GMA(HTTPSpotProvider):
match ref_info["reftype"]: match ref_info["reftype"]:
case "Summit": case "Summit":
spot.sig_refs[0].sig = "GMA" spot.sig_refs[0].sig = "GMA"
spot.sig_refs[0].ref_type = SIGRefType.SUMMIT
spot.sig = "GMA" spot.sig = "GMA"
case "IOTA Island": case "IOTA Island":
spot.sig_refs[0].sig = "IOTA" spot.sig_refs[0].sig = "IOTA"
spot.sig_refs[0].ref_type = SIGRefType.ISLAND
spot.sig = "IOTA" spot.sig = "IOTA"
case "GMA Island": case "GMA Island":
spot.sig_refs[0].sig = "GMA Islands" spot.sig_refs[0].sig = "GMA Islands"
spot.sig_refs[0].ref_type = SIGRefType.ISLAND
spot.sig = "GMA Islands" spot.sig = "GMA Islands"
case "Lighthouse (ILLW)": case "Lighthouse (ILLW)":
spot.sig_refs[0].sig = "ILLW" spot.sig_refs[0].sig = "ILLW"
spot.sig_refs[0].ref_type = SIGRefType.LIGHTHOUSE
spot.sig = "ILLW" spot.sig = "ILLW"
case "Lighthouse (ARLHS)": case "Lighthouse (ARLHS)":
spot.sig_refs[0].sig = "ARLHS" spot.sig_refs[0].sig = "ARLHS"
spot.sig_refs[0].ref_type = SIGRefType.LIGHTHOUSE
spot.sig = "ARLHS" spot.sig = "ARLHS"
case "Castle": case "Castle":
spot.sig_refs[0].sig = "WCA" spot.sig_refs[0].sig = "WCA"
spot.sig_refs[0].ref_type = SIGRefType.CASTLE
spot.sig = "WCA" spot.sig = "WCA"
case "Mill": case "Mill":
spot.sig_refs[0].sig = "MOTA" spot.sig_refs[0].sig = "MOTA"
spot.sig_refs[0].ref_type = SIGRefType.MILL
spot.sig = "MOTA" spot.sig = "MOTA"
case _: case _:
logger.warning( logger.warning(
+3 -1
View File
@@ -7,6 +7,7 @@ import requests
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -59,7 +60,7 @@ class HEMA(HTTPSpotProvider):
dx_call=spot_items[2].upper(), dx_call=spot_items[2].upper(),
de_call=spotter_comment_match.group(1).upper(), de_call=spotter_comment_match.group(1).upper(),
freq=float(freq_mode_match.group(1)) * 1000000, freq=float(freq_mode_match.group(1)) * 1000000,
mode=freq_mode_match.group(2).upper(), mode=Mode.from_name(freq_mode_match.group(2).upper()),
comment=spotter_comment_match.group(2), comment=spotter_comment_match.group(2),
sig="HEMA", sig="HEMA",
sig_refs=[ sig_refs=[
@@ -69,6 +70,7 @@ class HEMA(HTTPSpotProvider):
name=spot_items[4], name=spot_items[4],
latitude=float(spot_items[7]), latitude=float(spot_items[7]),
longitude=float(spot_items[8]), longitude=float(spot_items[8]),
ref_type=SIGRefType.SUMMIT,
) )
], ],
time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M") time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M")
+3 -1
View File
@@ -1,5 +1,6 @@
from datetime import datetime from datetime import datetime
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -31,7 +32,7 @@ class LLOTA(HTTPSpotProvider):
dx_call=source_spot["callsign"].upper(), dx_call=source_spot["callsign"].upper(),
de_call=spotter.upper() if spotter else None, de_call=spotter.upper() if spotter else None,
freq=float(source_spot["frequency"]) * 1000000, freq=float(source_spot["frequency"]) * 1000000,
mode=source_spot["mode"].upper(), mode=Mode.from_name(source_spot["mode"].upper()),
comment=comment, comment=comment,
sig="LLOTA", sig="LLOTA",
sig_refs=[ sig_refs=[
@@ -39,6 +40,7 @@ class LLOTA(HTTPSpotProvider):
id=source_spot["reference"], id=source_spot["reference"],
sig="LLOTA", sig="LLOTA",
name=source_spot["reference_name"], name=source_spot["reference_name"],
ref_type=SIGRefType.LAKE,
) )
], ],
time=datetime.fromisoformat(source_spot["updated_at"].replace("Z", "+00:00")).timestamp(), time=datetime.fromisoformat(source_spot["updated_at"].replace("Z", "+00:00")).timestamp(),
+2 -1
View File
@@ -6,6 +6,7 @@ import pytz
import requests import requests
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import Mode
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -51,7 +52,7 @@ class ParksNPeaks(HTTPSpotProvider):
if (source_spot["actFreq"] != "") if (source_spot["actFreq"] != "")
else None, else None,
# Seen PNP spots with empty frequency, and with comma-separated thousands digits # Seen PNP spots with empty frequency, and with comma-separated thousands digits
mode=source_spot["actMode"].upper(), mode=Mode.from_name(source_spot["actMode"].upper()),
comment=source_spot["actComments"], comment=source_spot["actComments"],
time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S") time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S")
.replace(tzinfo=pytz.UTC) .replace(tzinfo=pytz.UTC)
+3 -1
View File
@@ -4,6 +4,7 @@ import pytz
import requests import requests
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -30,7 +31,7 @@ class POTA(HTTPSpotProvider):
dx_call=source_spot["activator"].upper(), dx_call=source_spot["activator"].upper(),
de_call=source_spot["spotter"].upper(), de_call=source_spot["spotter"].upper(),
freq=float(source_spot["frequency"]) * 1000 if source_spot["frequency"] != "INVALID" else None, freq=float(source_spot["frequency"]) * 1000 if source_spot["frequency"] != "INVALID" else None,
mode=source_spot["mode"].upper(), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["comments"], comment=source_spot["comments"],
sig="POTA", sig="POTA",
sig_refs=[ sig_refs=[
@@ -40,6 +41,7 @@ class POTA(HTTPSpotProvider):
name=source_spot["name"], name=source_spot["name"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
ref_type=SIGRefType.PARK
) )
], ],
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S") time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S")
+3 -1
View File
@@ -5,6 +5,7 @@ import requests
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -53,7 +54,7 @@ class SOTA(HTTPSpotProvider):
if (source_spot["frequency"] is not None) if (source_spot["frequency"] is not None)
else None, else None,
# Seen SOTA spots with no frequency! # Seen SOTA spots with no frequency!
mode=source_spot["mode"].upper(), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["comments"], comment=source_spot["comments"],
sig="SOTA", sig="SOTA",
sig_refs=[ sig_refs=[
@@ -64,6 +65,7 @@ class SOTA(HTTPSpotProvider):
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
activation_score=source_spot["points"], activation_score=source_spot["points"],
ref_type=SIGRefType.SUMMIT,
) )
], ],
dx_latitude=source_spot["latitude"], dx_latitude=source_spot["latitude"],
+3 -2
View File
@@ -4,7 +4,7 @@ from datetime import datetime
import requests import requests
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.enums import LocationSourceForSpot from core.enums import LocationSourceForSpot, Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -56,7 +56,7 @@ class Tiles(HTTPSpotProvider):
# No separate spotter callsign, assume all spots are self-spots # No separate spotter callsign, assume all spots are self-spots
de_call=source_spot["call_sign"].upper(), de_call=source_spot["call_sign"].upper(),
freq=freq, freq=freq,
mode=source_spot["mode"].upper(), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["notes"], comment=source_spot["notes"],
sig="Tiles", sig="Tiles",
# Tiles spots can include POTA & SOTA references, but ignore those on the basis that we will get them separately from the POTA/SOTA providers anyway. # Tiles spots can include POTA & SOTA references, but ignore those on the basis that we will get them separately from the POTA/SOTA providers anyway.
@@ -68,6 +68,7 @@ class Tiles(HTTPSpotProvider):
name=source_spot["maidenhead_grid"], name=source_spot["maidenhead_grid"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
ref_type=SIGRefType.GRID
) )
], ],
time=datetime.fromisoformat(source_spot["created_at"].replace("Z", "+00:00")).timestamp(), time=datetime.fromisoformat(source_spot["created_at"].replace("Z", "+00:00")).timestamp(),
+7 -4
View File
@@ -1,6 +1,9 @@
import json import json
from datetime import datetime from datetime import datetime
import pytz
from core.enums import SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -32,10 +35,10 @@ class Towers(HTTPSpotProvider):
freq=likely_freq, freq=likely_freq,
comment=source_spot["comment"], comment=source_spot["comment"],
sig="Towers", sig="Towers",
sig_refs=[SIGRef(id=source_spot["ref"], sig="Towers")], sig_refs=[SIGRef(id=source_spot["ref"], sig="Towers", ref_type=SIGRefType.TOWER)],
time=datetime.strptime( time=datetime.strptime(response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M")
response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M" .replace(tzinfo=pytz.utc)
).timestamp(), .timestamp(),
) )
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do # Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
+2 -1
View File
@@ -3,6 +3,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import Mode
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -77,7 +78,7 @@ class UKPacketNet(HTTPSpotProvider):
dx_call=heard["callsign"].upper(), dx_call=heard["callsign"].upper(),
de_call=node["callsign"].upper(), de_call=node["callsign"].upper(),
freq=freq, freq=freq,
mode="PKT", mode=Mode.PKT,
comment=comment, comment=comment,
time=datetime.strptime(heard["lastHeard"], "%Y-%m-%d %H:%M:%S") time=datetime.strptime(heard["lastHeard"], "%Y-%m-%d %H:%M:%S")
.replace(tzinfo=pytz.UTC) .replace(tzinfo=pytz.UTC)
+3 -2
View File
@@ -8,6 +8,7 @@ import pytz
from rss_parser import Parser from rss_parser import Parser
from rss_parser.models.rss import RSS from rss_parser.models.rss import RSS
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -87,10 +88,10 @@ class WOTA(HTTPSpotProvider):
dx_call=dx_call, dx_call=dx_call,
de_call=spotter, de_call=spotter,
freq=freq_hz, freq=freq_hz,
mode=mode, mode=Mode.from_name(mode),
comment=comment, comment=comment,
sig="WOTA", sig="WOTA",
sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name)] if ref else [], sig_refs=[SIGRef(id=ref, sig="WOTA", name=ref_name, ref_type=SIGRefType.SUMMIT)] if ref else [],
time=time.timestamp(), time=time.timestamp(),
) )
+3 -1
View File
@@ -1,6 +1,7 @@
import json import json
from datetime import datetime from datetime import datetime
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.sse_spot_provider import SSESpotProvider from providers.spot.sse_spot_provider import SSESpotProvider
@@ -26,6 +27,7 @@ class WWBOTA(SSESpotProvider):
name=ref["name"], name=ref["name"],
latitude=ref["lat"], latitude=ref["lat"],
longitude=ref["long"], longitude=ref["long"],
ref_type=SIGRefType.BUNKER
) )
refs.append(sigref) refs.append(sigref)
@@ -34,7 +36,7 @@ class WWBOTA(SSESpotProvider):
dx_call=source_spot["call"].upper(), dx_call=source_spot["call"].upper(),
de_call=source_spot["spotter"].upper(), de_call=source_spot["spotter"].upper(),
freq=float(source_spot["freq"]) * 1000000, freq=float(source_spot["freq"]) * 1000000,
mode=source_spot["mode"].upper() if "mode" in source_spot else None, mode=Mode.from_name(source_spot["mode"].upper()) if "mode" in source_spot else Mode.UNKNOWN,
comment=source_spot["comment"], comment=source_spot["comment"],
sig="WWBOTA", sig="WWBOTA",
sig_refs=refs, sig_refs=refs,
+3 -1
View File
@@ -2,6 +2,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import Mode, SIGRefType
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -27,7 +28,7 @@ class WWFF(HTTPSpotProvider):
dx_call=source_spot["activator"].upper(), dx_call=source_spot["activator"].upper(),
de_call=source_spot["spotter"].upper(), de_call=source_spot["spotter"].upper(),
freq=float(source_spot["frequency_khz"]) * 1000, freq=float(source_spot["frequency_khz"]) * 1000,
mode=source_spot["mode"].upper(), mode=Mode.from_name(source_spot["mode"].upper()),
comment=source_spot["remarks"], comment=source_spot["remarks"],
sig="WWFF", sig="WWFF",
sig_refs=[ sig_refs=[
@@ -37,6 +38,7 @@ class WWFF(HTTPSpotProvider):
name=source_spot["reference_name"], name=source_spot["reference_name"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"], longitude=source_spot["longitude"],
ref_type=SIGRefType.PARK
) )
], ],
time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(), time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(),
+2 -1
View File
@@ -3,6 +3,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import Mode
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.websocket_spot_provider import WebsocketSpotProvider from providers.spot.websocket_spot_provider import WebsocketSpotProvider
@@ -34,7 +35,7 @@ class XOTA(WebsocketSpotProvider):
source_id=source_spot["id"], source_id=source_spot["id"],
dx_call=source_spot["stationCallSign"].upper(), dx_call=source_spot["stationCallSign"].upper(),
freq=float(source_spot["freq"]) * 1000, freq=float(source_spot["freq"]) * 1000,
mode=source_spot["mode"].upper(), mode=Mode.from_name(source_spot["mode"].upper()),
sig=self.SIG, sig=self.SIG,
sig_refs=[ sig_refs=[
SIGRef( SIGRef(
+2 -1
View File
@@ -2,6 +2,7 @@ from datetime import datetime
import pytz import pytz
from core.enums import Mode
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from providers.spot.http_spot_provider import HTTPSpotProvider from providers.spot.http_spot_provider import HTTPSpotProvider
@@ -32,7 +33,7 @@ class ZLOTA(HTTPSpotProvider):
dx_call=source_spot["activator"].upper(), dx_call=source_spot["activator"].upper(),
de_call=source_spot["spotter"].upper(), de_call=source_spot["spotter"].upper(),
freq=freq_hz, freq=freq_hz,
mode=source_spot["mode"].upper().strip(), mode=Mode.from_name(source_spot["mode"].upper().strip()),
comment=source_spot["comments"], comment=source_spot["comments"],
sig="ZLOTA", sig="ZLOTA",
sig_refs=[ sig_refs=[
+3 -3
View File
@@ -8,8 +8,8 @@ from tornado import httputil
from tornado.web import Application from tornado.web import Application
from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE
from core.constants import ALL_MODES, BANDS, PROPAGATION_MODES, SIGS from core.constants import BANDS, PROPAGATION_MODES, SIGS
from core.enums import Continent, ModeType from core.enums import Continent, Mode, ModeType
from core.prometheus_metrics_handler import api_requests_counter from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps from core.utils import safe_json_dumps
@@ -83,7 +83,7 @@ class APIOptionsHandler(tornado.web.RequestHandler):
options = { options = {
"bands": BANDS, "bands": BANDS,
"modes": ALL_MODES, "modes": [m.value for m in Mode],
"mode_types": [t.value for t in ModeType], "mode_types": [t.value for t in ModeType],
"sigs": SIGS, "sigs": SIGS,
"spot_providers": spot_providers, "spot_providers": spot_providers,
+42 -4
View File
@@ -19,8 +19,9 @@ info:
* Added DTMBA, FEA, BIWOTA, COTA & PGA SIGs * Added DTMBA, FEA, BIWOTA, COTA & PGA SIGs
* Removed the distinction between LSB & USB (both will now show as SSB) and between the various digital voice modes, which will now show as DV. * Removed the distinction between LSB & USB (both will now show as SSB) and between the various digital voice modes, which will now show as DV.
* Added `icon`, `region_flag` and `refs_globally_unique` to SIG information * Added `sig_type`, `icon`, `region_flag` and `refs_globally_unique` to SIG information
* Unknown mode types now return "UNKNOWN" not null * Unknown modes and mode types now return "UNKNOWN" not null
* SIG reference types (e.g. "Park") are now capitalised to match other enums
### 2.0 ### 2.0
@@ -867,6 +868,14 @@ components:
- Toilets - Toilets
example: POTA example: POTA
SIGType:
type: string
enum:
- WORLDWIDE
- REGIONAL
- EVENT
example: WORLDWIDE
SIGNameIncludingNoSIG: SIGNameIncludingNoSIG:
oneOf: oneOf:
- $ref: "#/components/schemas/SIGName" - $ref: "#/components/schemas/SIGName"
@@ -874,6 +883,29 @@ components:
enum: [ NO_SIG ] enum: [ NO_SIG ]
example: POTA example: POTA
SIGRefType:
type: string
enum:
- PARK
- SUMMIT
- CASTLE
- LIGHTHOUSE
- ISLAND
- BUNKER
- MILL
- SILO
- BEACH
- LAKE
- TOWER
- WATERWAY
- TOWN
- BUILDING
- REGION
- GRID
- TOILET
- UNKNOWN
example: PARK
Continent: Continent:
type: string type: string
enum: enum:
@@ -938,6 +970,7 @@ components:
- FSK - FSK
- PKT - PKT
- MSK144 - MSK144
- UNKNOWN
example: SSB example: SSB
ModeType: ModeType:
@@ -1006,9 +1039,8 @@ components:
description: SIG reference name description: SIG reference name
example: Null Country Park example: Null Country Park
ref_type: ref_type:
type: string
description: SIG reference type description: SIG reference type
example: Park $ref: "#/components/schemas/SIGRefType"
url: url:
type: string type: string
description: SIG reference URL, which the user can look up for more information description: SIG reference URL, which the user can look up for more information
@@ -1493,6 +1525,12 @@ components:
type: string type: string
description: The full name of the SIG description: The full name of the SIG
example: Parks on the Air example: Parks on the Air
sig_type:
type: boolean
description: >
Whether the SIG is worldwide, regional, or for a specific event. Generally for Spothole's own internal use,
clients probably won't need this. Used to group them in the web UI.
$ref: "#/components/schemas/SIGType"
comment_names: comment_names:
type: array type: array
description: > description: >
+11 -4
View File
@@ -42,18 +42,25 @@ function setHamHFBandToggles() {
function generateSIGsMultiToggleFilterCard(sig_options) { function generateSIGsMultiToggleFilterCard(sig_options) {
const $grid1 = $('<div class="row row-cols-2 g-1 mb-1">'); const $grid1 = $('<div class="row row-cols-2 g-1 mb-1">');
$("#sig-options").append('<strong>Worldwide</strong>'); $("#sig-options").append('<strong>Worldwide</strong>');
sig_options.filter(o => o["region_flag"] == null).forEach(o => { sig_options.filter(o => o["sig_type"] === "WORLDWIDE").forEach(o => {
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, ""); const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid1.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']}</label></div></div>`); $grid1.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
}); });
$("#sig-options").append($grid1); $("#sig-options").append($grid1);
const $grid2 = $('<div class="row row-cols-2 g-1 mb-1">'); const $grid2 = $('<div class="row row-cols-2 g-1 mb-1">');
$("#sig-options").append('<strong>Regional</strong>'); $("#sig-options").append('<strong>Regional</strong>');
sig_options.filter(o => o["region_flag"] != null).forEach(o => { sig_options.filter(o => o["sig_type"] === "REGIONAL").forEach(o => {
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, ""); const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid2.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${o['region_flag']}</label></div></div>`); $grid2.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
}); });
$("#sig-options").append($grid2); $("#sig-options").append($grid2);
const $grid3 = $('<div class="row row-cols-2 g-1 mb-1">');
$("#sig-options").append('<strong>Event</strong>');
sig_options.filter(o => o["sig_type"] === "EVENT").forEach(o => {
const domSafeName = o["name"].replace(/^[^A-Za-z0-9]+|[^\w]+/gi, "");
$grid3.append(`<div class="col"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-${domSafeName}" value="${o['name']}" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-${domSafeName}" for="filter-button-sig-${domSafeName}" title="${o['description']}"><i class="fa-solid ${o['icon']}"></i> ${o['name']} ${(o["region_flag"] != null) ? o['region_flag'] : ''}</label></div></div>`);
});
$("#sig-options").append($grid3);
$("#sig-options").append('<strong>Other</strong>'); $("#sig-options").append('<strong>Other</strong>');
// Bonus "NO_SIG" / "General DX" option // Bonus "NO_SIG" / "General DX" option
$("#sig-options").append(`<div class="w-100"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-NO_SIG" value="NO_SIG" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-NO_SIG" for="filter-button-sig-NO_SIG"><i class="fa-solid fa-tower-cell"></i> General DX</label></div></div>`); $("#sig-options").append(`<div class="w-100"><div class="form-check"><input type="checkbox" class="form-check-input filter-button-sig storeable-checkbox" id="filter-button-sig-NO_SIG" value="NO_SIG" autocomplete="off" onClick="filtersUpdated()" checked><label class="form-check-label" id="filter-button-label-sig-NO_SIG" for="filter-button-sig-NO_SIG"><i class="fa-solid fa-tower-cell"></i> General DX</label></div></div>`);
+1 -1
View File
@@ -77,7 +77,7 @@
</div> </div>
<script src="/static/js/add-spot.js?v=1788334633"></script> <script src="/static/js/add-spot.js?v=1788338285"></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
@@ -84,7 +84,7 @@
</div> </div>
<script src="/static/js/alerts.js?v=1788334633"></script> <script src="/static/js/alerts.js?v=1788338286"></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=1788334633"></script> <script src="/static/js/spotsbandsandmap.js?v=1788338285"></script>
<script src="/static/js/bands.js?v=1788334633"></script> <script src="/static/js/bands.js?v=1788338285"></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=1788334633" type="text/css"> <link rel="stylesheet" href="/static/css/style.css?v=1788338285" 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=1788334633"></script> <script src="/static/js/utils.js?v=1788338285"></script>
<script src="/static/js/ui-ham.js?v=1788334633"></script> <script src="/static/js/ui-ham.js?v=1788338285"></script>
<script src="/static/js/geo.js?v=1788334633"></script> <script src="/static/js/geo.js?v=1788338285"></script>
<script src="/static/js/common.js?v=1788334633"></script> <script src="/static/js/common.js?v=1788338285"></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=1788334633"></script> <script src="/static/js/conditions.js?v=1788338285"></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=1788334633"></script> <script src="/static/js/spotsbandsandmap.js?v=1788338286"></script>
<script src="/static/js/map.js?v=1788334633"></script> <script src="/static/js/map.js?v=1788338286"></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=1788334633"></script> <script src="/static/js/spotsbandsandmap.js?v=1788338285"></script>
<script src="/static/js/spots.js?v=1788334633"></script> <script src="/static/js/spots.js?v=1788338285"></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=1788334633"></script> <script src="/static/js/status.js?v=1788338285"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");