diff --git a/core/constants.py b/core/constants.py index 2bcfe8f..d2669a4 100644 --- a/core/constants.py +++ b/core/constants.py @@ -1,4 +1,5 @@ from core.config import SERVER_OWNER_CALLSIGN +from core.enums import SIGType from data.band import Band from data.sig import SIG @@ -15,6 +16,7 @@ SIGS = [ name="POTA", comment_names=["POTA"], description="Parks on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST", icon="fa-tree", refs_globally_unique=False, @@ -23,6 +25,7 @@ SIGS = [ name="SOTA", comment_names=["SOTA"], description="Summits on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}", icon="fa-mountain-sun", refs_globally_unique=False, @@ -31,6 +34,7 @@ SIGS = [ name="WWFF", comment_names=["WWFF"], description="World Wide Flora & Fauna", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}", icon="fa-seedling", refs_globally_unique=True, @@ -39,6 +43,7 @@ SIGS = [ name="GMA", comment_names=["GMA"], description="Global Mountain Activity", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}", icon="fa-person-hiking", refs_globally_unique=False, @@ -47,6 +52,7 @@ SIGS = [ name="WWBOTA", comment_names=["WWBOTA", "BOTA"], description="Worldwide Bunkers on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}", icon="fa-radiation", refs_globally_unique=True, @@ -55,6 +61,7 @@ SIGS = [ name="HEMA", comment_names=["HEMA"], description="HuMPs Excluding Marilyns Award", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}", icon="fa-mound", refs_globally_unique=False, @@ -63,6 +70,7 @@ SIGS = [ name="IOTA", comment_names=["IOTA"], description="Islands on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z]{2}\-\d{3}", icon="fa-book-atlas", refs_globally_unique=False, @@ -71,6 +79,7 @@ SIGS = [ name="GMA Islands", comment_names=[], 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}))", icon="fa-person-hiking", refs_globally_unique=False, @@ -79,6 +88,7 @@ SIGS = [ name="ARLHS", comment_names=["ARLHS"], description="Amateur Radio Lighthouse Society", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z]{3}[\- ]\d{3,4}", icon="fa-house-flood-water", refs_globally_unique=False, @@ -87,6 +97,7 @@ SIGS = [ name="ILLW", comment_names=["ILLW"], description="International Lighthouse & Lightship Weekend", + sig_type=SIGType.EVENT, ref_regex=r"[A-Z]{2}\d{4}", icon="fa-house-flood-water", refs_globally_unique=False, @@ -95,6 +106,7 @@ SIGS = [ name="MOTA", comment_names=["MOTA"], description="Mills on the Air", + sig_type=SIGType.EVENT, ref_regex=r"X\d{4,6}", icon="fa-fan", refs_globally_unique=True, @@ -103,6 +115,7 @@ SIGS = [ name="SIOTA", comment_names=["SIOTA"], description="Silos on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d", icon="fa-wheat-awn", refs_globally_unique=False, @@ -111,6 +124,7 @@ SIGS = [ name="WCA", comment_names=["WCA"], description="World Castles Award", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z0-9]{1,3}\-\d{5}", icon="fa-chess-rook", refs_globally_unique=False, @@ -119,6 +133,7 @@ SIGS = [ name="ZLOTA", comment_names=["ZLOTA"], description="New Zealand on the Air", + sig_type=SIGType.REGIONAL, ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}", icon="fa-kiwi-bird", region_flag="🇳🇿", @@ -128,32 +143,45 @@ SIGS = [ name="WOTA", comment_names=["WOTA"], description="Wainwrights on the Air", + sig_type=SIGType.REGIONAL, ref_regex=r"[A-Z]{3}-[0-9]{2}", icon="fa-w", region_flag="🇬🇧", 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( name="KRMNPA", comment_names=["KRMNPA"], description="Keith Roget Memorial National Parks Award", + sig_type=SIGType.REGIONAL, ref_regex=r"VKFF\-\d{4}", icon="fa-earth-oceania", region_flag="🇦🇺", + refs_globally_unique=False, ), SIG( name="SANPCPA", comment_names=["SANPCPA"], description="South Australian National Parks and Conservation Parks Award", + sig_type=SIGType.REGIONAL, ref_regex=r"VKFF\-\d{4}", icon="fa-earth-oceania", region_flag="🇦🇺", + refs_globally_unique=False, ), SIG( name="LLOTA", comment_names=["LLOTA"], description="Lagos y Lagunas on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"LL[A-Z]{2}\-\d{4}", icon="fa-water", refs_globally_unique=True, @@ -162,6 +190,7 @@ SIGS = [ name="Towers", comment_names=["TOTA"], description="Towers on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Z]{2,3}R\-\d{4}", icon="fa-tower-observation", refs_globally_unique=False, @@ -170,6 +199,7 @@ SIGS = [ name="Tiles", comment_names=[], description="Tiles on the Air", + sig_type=SIGType.WORLDWIDE, ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}", icon="fa-square", refs_globally_unique=False, @@ -178,6 +208,7 @@ SIGS = [ name="WAB", comment_names=["WAB"], description="Worked All Britain", + sig_type=SIGType.REGIONAL, ref_regex=r"[A-Z]{1,2}[0-9]{2}", icon="fa-table-cells-large", region_flag="🇬🇧", @@ -187,6 +218,7 @@ SIGS = [ name="WAI", comment_names=["WAI"], description="Worked All Ireland", + sig_type=SIGType.REGIONAL, ref_regex=r"[A-Z][0-9]{2}", icon="fa-table-cells-large", region_flag="🇮🇪", @@ -196,6 +228,7 @@ SIGS = [ name="DME", comment_names=["DME"], description="Diploma Municipios de España", + sig_type=SIGType.REGIONAL, ref_regex=r"\d{4,5}", icon="fa-building", region_flag="🇪🇸", @@ -205,6 +238,7 @@ SIGS = [ name="FEA", comment_names=["FEA"], 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- # prefix and just use FEA-1234 or FEA 1234, so allow for that. The FEA sigref data provider adds both # forms to the database. @@ -217,6 +251,7 @@ SIGS = [ name="DTMBA", comment_names=["DTMBA"], description="Diploma Teatri Musei e Belle Arti", + sig_type=SIGType.REGIONAL, ref_regex=r"I-?[0-9]{3,4}\s?[A-Z]{2}", icon="fa-masks-theater", region_flag="🇮🇹", @@ -226,13 +261,16 @@ SIGS = [ name="BIWOTA", comment_names=["BIWOTA"], description="British Inland Waterways on the Air", + sig_type=SIGType.EVENT, icon="fa-ship", region_flag="🇬🇧", + refs_globally_unique=False, ), SIG( name="COTA", comment_names=["COTA"], description="Castles on the Air", + sig_type=SIGType.REGIONAL, ref_regex=r"[A-Z]{3}\-[0-9]{3,5}", icon="fa-chess-rook", region_flag="🇩🇪", @@ -242,6 +280,7 @@ SIGS = [ name="PGA", comment_names=["PGA"], description="Polish Gmina Award", + sig_type=SIGType.REGIONAL, ref_regex=r"[A-Z]{2}[0-9]{2}", icon="fa-g", region_flag="🇵🇱", @@ -251,6 +290,7 @@ SIGS = [ name="Toilets", comment_names=[], description="Toilets on the Air", + sig_type=SIGType.EVENT, ref_regex=r"T\-[0-9]{2}", icon="fa-toilet", 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 BANDS = [ Band(name="2200m", start_freq=135700, end_freq=137800), diff --git a/core/enums.py b/core/enums.py index ca31870..17b4016 100644 --- a/core/enums.py +++ b/core/enums.py @@ -11,6 +11,52 @@ class Continent(str, Enum): 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): PHONE = "PHONE" CW = "CW" @@ -44,3 +90,59 @@ class LocationSourceForCallsign(str, Enum): HOME_QTH = "HOME QTH" DXCC = "DXCC" 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", +} diff --git a/core/utils.py b/core/utils.py index ecbc61a..204b251 100644 --- a/core/utils.py +++ b/core/utils.py @@ -5,17 +5,9 @@ import simplejson from pyhamtools.frequency import freq_to_band from pyhamtools.locator import latlong_to_locator -from core.constants import ( - ALL_MODES, - BANDS, - CW_MODES, - DATA_MODES, - MODE_ALIASES, - PHONE_MODES, - UNKNOWN_BAND, -) +from core.constants import BANDS, UNKNOWN_BAND 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 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__) -def infer_mode_from_comment(comment): +def infer_mode_from_comment(comment: str) -> Mode: """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): return mode - for mode in MODE_ALIASES: - if re.search(r"(^|\W)" + mode + r"($|\W)", comment, re.IGNORECASE): - return MODE_ALIASES[mode] - return None + for alias in MODE_ALIASES: + if re.search(r"(^|\W)" + alias + r"($|\W)", comment, re.IGNORECASE): + return Mode(MODE_ALIASES[alias]) + + return Mode.UNKNOWN -def infer_mode_type_from_mode(mode): - """Infer a "mode family" from a mode.""" +def infer_mode_type_from_mode(mode: str) -> ModeType: + """Infer a "mode family" from a mode .""" - if mode.upper() in CW_MODES: - return ModeType.CW - elif mode.upper() in PHONE_MODES: - return ModeType.PHONE - elif mode.upper() in DATA_MODES: + if not mode: + return ModeType.UNKNOWN + + try: + mode = Mode(mode.upper()) + if mode.is_cw: + return ModeType.CW + if mode.is_phone: + return ModeType.PHONE return ModeType.DATA - else: + + except ValueError: if mode.upper() != "OTHER" and mode != "?": logger.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.") - return None + return ModeType.UNKNOWN def infer_band_from_freq(freq): diff --git a/data/alert.py b/data/alert.py index f73efcc..ecdb0b0 100644 --- a/data/alert.py +++ b/data/alert.py @@ -93,7 +93,7 @@ class Alert: if self.dx_calls and self.dx_calls[0] and not self.dx_country: self.dx_country = call_info.country 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: self.dx_cq_zone = call_info.cq_zone if self.dx_calls and self.dx_calls[0] and not self.dx_itu_zone: diff --git a/data/sig.py b/data/sig.py index 2da10f1..a69603a 100644 --- a/data/sig.py +++ b/data/sig.py @@ -1,5 +1,7 @@ from dataclasses import dataclass, field +from core.enums import SIGType + @dataclass class SIG: @@ -15,16 +17,18 @@ class SIG: name: str # Description, e.g. "Towers on the Air" description: str - # 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 + # Type, either Worldwide, Regional or Event. Used for sorting in the web UI. + sig_type: SIGType # 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 # 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 # 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: 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 diff --git a/data/sig_ref.py b/data/sig_ref.py index 26ea482..8896b6c 100644 --- a/data/sig_ref.py +++ b/data/sig_ref.py @@ -1,5 +1,7 @@ from dataclasses import dataclass +from core.enums import SIGRefType + @dataclass class SIGRef: @@ -13,7 +15,7 @@ class SIGRef: # Name of the reference, e.g. "Null Country Park", if known. name: str | None = None # 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: str | None = None # Latitude of the reference, in degrees, if known. diff --git a/data/spot.py b/data/spot.py index b101d38..173c556 100644 --- a/data/spot.py +++ b/data/spot.py @@ -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.config import MAX_SPOT_AGE -from core.constants import MODE_ALIASES, PROPAGATION_MODES, SIGS -from core.enums import Continent, LocationSourceForSpot, ModeSource, ModeType +from core.constants import PROPAGATION_MODES, SIGS +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.sig_lookup_helper import populate_missing_sig_ref_info from core.sig_utils import ( @@ -103,7 +103,7 @@ class Spot: # General QSO info # Reported mode, such as SSB, PHONE, CW, FT8... - mode: str | None = None + mode: Mode = Mode.UNKNOWN # Inferred mode "family". mode_type: ModeType = ModeType.UNKNOWN # Source of the mode information. @@ -239,21 +239,21 @@ class Spot: self.band = band.name # 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 - 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_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_source = ModeSource.BANDPLAN - # Normalise mode if necessary. - if self.mode in MODE_ALIASES: - self.mode = MODE_ALIASES[self.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) # If we have a latitude or grid at this point, it can only have been provided by the spot itself diff --git a/providers/sigrefdata/arlhs.py b/providers/sigrefdata/arlhs.py index 1a060c6..972a018 100644 --- a/providers/sigrefdata/arlhs.py +++ b/providers/sigrefdata/arlhs.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -27,7 +28,7 @@ class ARLHS(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("Name", None), - ref_type="Lighthouse", + ref_type=SIGRefType.LIGHTHOUSE, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", 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, diff --git a/providers/sigrefdata/cota.py b/providers/sigrefdata/cota.py index d9c0043..37c0ecc 100644 --- a/providers/sigrefdata/cota.py +++ b/providers/sigrefdata/cota.py @@ -2,6 +2,7 @@ from time import sleep from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider @@ -32,7 +33,7 @@ class COTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=name, - ref_type="Castle", + ref_type=SIGRefType.CASTLE, grid=grid, latitude=lat, longitude=lon, diff --git a/providers/sigrefdata/dme.py b/providers/sigrefdata/dme.py index 1eee1bd..c46b1bc 100644 --- a/providers/sigrefdata/dme.py +++ b/providers/sigrefdata/dme.py @@ -3,6 +3,7 @@ from time import sleep from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.local_file_sig_ref_data_provider import ( LocalFileSIGRefDataProvider, @@ -37,7 +38,7 @@ class DME(LocalFileSIGRefDataProvider): ref = SIGRef( sig=self.SIG, id=ref_id, - ref_type="Town", + ref_type=SIGRefType.TOWN, name=f"{row['NOMBRE_ACTUAL']}, {row['PROVINCIA']}", latitude=latitude, longitude=longitude, diff --git a/providers/sigrefdata/dtmba.py b/providers/sigrefdata/dtmba.py index d20d52a..8e52ba9 100644 --- a/providers/sigrefdata/dtmba.py +++ b/providers/sigrefdata/dtmba.py @@ -1,5 +1,6 @@ from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider @@ -20,7 +21,7 @@ class DTMBA(FileDownloadSIGRefDataProvider): split = row.split(";") ref_id = split[0] 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 # the data in this case diff --git a/providers/sigrefdata/fea.py b/providers/sigrefdata/fea.py index 2e28e43..bdf82c3 100644 --- a/providers/sigrefdata/fea.py +++ b/providers/sigrefdata/fea.py @@ -3,6 +3,7 @@ from time import sleep import pdfplumber +from core.enums import SIGRefType from data.sig_ref import SIGRef 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. ref_id_1 = row[0].strip() 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_2, 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=SIGRefType.LIGHTHOUSE)) # 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 diff --git a/providers/sigrefdata/gma.py b/providers/sigrefdata/gma.py index 4d7f3e6..36ce616 100644 --- a/providers/sigrefdata/gma.py +++ b/providers/sigrefdata/gma.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class GMA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("Name", None), - ref_type="Summit", + ref_type=SIGRefType.SUMMIT, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", 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, diff --git a/providers/sigrefdata/illw.py b/providers/sigrefdata/illw.py index 6c77d35..a5b6a91 100644 --- a/providers/sigrefdata/illw.py +++ b/providers/sigrefdata/illw.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -27,7 +28,7 @@ class ILLW(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("Name", None), - ref_type="Lighthouse", + ref_type=SIGRefType.LIGHTHOUSE, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", 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, diff --git a/providers/sigrefdata/iota.py b/providers/sigrefdata/iota.py index 222df24..744b057 100644 --- a/providers/sigrefdata/iota.py +++ b/providers/sigrefdata/iota.py @@ -3,6 +3,7 @@ from time import sleep from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -44,7 +45,7 @@ class IOTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=ref["name"], - ref_type="Island", + ref_type=SIGRefType.ISLAND, grid=grid, latitude=latitude, longitude=longitude, diff --git a/providers/sigrefdata/llota.py b/providers/sigrefdata/llota.py index 6af80b3..208b34c 100644 --- a/providers/sigrefdata/llota.py +++ b/providers/sigrefdata/llota.py @@ -2,6 +2,7 @@ from time import sleep from pyhamtools.locator import locator_to_latlong +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -32,7 +33,7 @@ class LLOTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=str(ref["name"]), - ref_type="Lake", + ref_type=SIGRefType.LAKE, url=f"https://llota.app/list/ref/{ref_id}", grid=grid, latitude=ll[0], diff --git a/providers/sigrefdata/mota.py b/providers/sigrefdata/mota.py index eb88f40..aaa2a16 100644 --- a/providers/sigrefdata/mota.py +++ b/providers/sigrefdata/mota.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class MOTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("Name", None), - ref_type="Mill", + ref_type=SIGRefType.MILL, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", 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, diff --git a/providers/sigrefdata/pga.py b/providers/sigrefdata/pga.py index d20bb04..2c20c67 100644 --- a/providers/sigrefdata/pga.py +++ b/providers/sigrefdata/pga.py @@ -2,6 +2,7 @@ from time import sleep from bs4 import BeautifulSoup +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider @@ -41,7 +42,7 @@ class PGA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=name, - ref_type="Region", + ref_type=SIGRefType.REGION, ) ) diff --git a/providers/sigrefdata/pnp_kml_sig_ref_data_provider.py b/providers/sigrefdata/pnp_kml_sig_ref_data_provider.py index a53a177..d13868b 100644 --- a/providers/sigrefdata/pnp_kml_sig_ref_data_provider.py +++ b/providers/sigrefdata/pnp_kml_sig_ref_data_provider.py @@ -4,6 +4,7 @@ from time import sleep from fastkml import kml from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -43,7 +44,7 @@ class ParksNPeaksKMLSIGRefDataProvider(FileDownloadSIGRefDataProvider): sig=self.sig_name, id=ref_id, name=placemark.name, - ref_type="Park", + ref_type=SIGRefType.PARK, url=f"https://parksnpeaks.org/getPark.php?actPark={ref_id}", latitude=latitude, longitude=longitude, diff --git a/providers/sigrefdata/pota.py b/providers/sigrefdata/pota.py index b639ecb..7b6db68 100644 --- a/providers/sigrefdata/pota.py +++ b/providers/sigrefdata/pota.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class POTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("name", None), - ref_type="Park", + ref_type=SIGRefType.PARK, url=f"https://pota.app/#/park/{ref_id}", grid=row.get("grid", None), latitude=float(row["latitude"]) if "latitude" in row and row["latitude"] != "" else None, diff --git a/providers/sigrefdata/siota.py b/providers/sigrefdata/siota.py index fc042d0..91f863a 100644 --- a/providers/sigrefdata/siota.py +++ b/providers/sigrefdata/siota.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class SIOTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("NAME", None), - ref_type="Silo", + ref_type=SIGRefType.SILO, grid=row.get("LOCATOR", None), latitude=float(row["LAT"]) if "LAT" in row else None, longitude=float(row["LNG"]) if "LNG" in row else None, diff --git a/providers/sigrefdata/sota.py b/providers/sigrefdata/sota.py index 60b1a9d..07462ba 100644 --- a/providers/sigrefdata/sota.py +++ b/providers/sigrefdata/sota.py @@ -3,6 +3,7 @@ from time import sleep from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -30,7 +31,7 @@ class SOTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("SummitName", None), - ref_type="Summit", + ref_type=SIGRefType.SUMMIT, url=f"https://www.sotadata.org.uk/en/summit/{ref_id}", latitude=latitude, longitude=longitude, diff --git a/providers/sigrefdata/toilets.py b/providers/sigrefdata/toilets.py index 5326b89..ca98bce 100644 --- a/providers/sigrefdata/toilets.py +++ b/providers/sigrefdata/toilets.py @@ -1,5 +1,6 @@ import csv +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.local_file_sig_ref_data_provider import ( LocalFileSIGRefDataProvider, @@ -26,7 +27,7 @@ class Toilets(LocalFileSIGRefDataProvider): sig=self.SIG, id=row["ref"], name=row["ref"], - ref_type="Toilet", + ref_type=SIGRefType.TOILET, latitude=float(row["lat"]), longitude=float(row["lon"]), ) diff --git a/providers/sigrefdata/towers.py b/providers/sigrefdata/towers.py index 226da38..8449ca7 100644 --- a/providers/sigrefdata/towers.py +++ b/providers/sigrefdata/towers.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class Towers(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("Nazev", None), - ref_type="Tower", + ref_type=SIGRefType.TOWER, url=f"https://wwtota.com/seznam/karta_rozhledny.php?ref={ref_id}", 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, diff --git a/providers/sigrefdata/wca.py b/providers/sigrefdata/wca.py index 4f41737..778481c 100644 --- a/providers/sigrefdata/wca.py +++ b/providers/sigrefdata/wca.py @@ -4,6 +4,7 @@ from time import sleep from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -45,7 +46,7 @@ class WCA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("CLEAN NAME", None), - ref_type="Castle", + ref_type=SIGRefType.CASTLE, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}", latitude=latitude, longitude=longitude, diff --git a/providers/sigrefdata/wota.py b/providers/sigrefdata/wota.py index e20fdf2..10e476c 100644 --- a/providers/sigrefdata/wota.py +++ b/providers/sigrefdata/wota.py @@ -1,5 +1,6 @@ from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -33,7 +34,7 @@ class WOTA(FileDownloadSIGRefDataProvider): id=ref_id, name=feature["properties"]["title"], url=url, - ref_type="Summit", + ref_type=SIGRefType.SUMMIT, grid=feature["properties"]["qthLocator"], latitude=feature["geometry"]["coordinates"][1], longitude=feature["geometry"]["coordinates"][0], diff --git a/providers/sigrefdata/wwbota.py b/providers/sigrefdata/wwbota.py index ae6fa21..94e43be 100644 --- a/providers/sigrefdata/wwbota.py +++ b/providers/sigrefdata/wwbota.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class WWBOTA(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, 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, 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, diff --git a/providers/sigrefdata/wwff.py b/providers/sigrefdata/wwff.py index 86f3652..768de70 100644 --- a/providers/sigrefdata/wwff.py +++ b/providers/sigrefdata/wwff.py @@ -1,6 +1,7 @@ import csv from time import sleep +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,7 +27,7 @@ class WWFF(FileDownloadSIGRefDataProvider): sig=self.SIG, id=ref_id, name=row.get("name", None), - ref_type="Park", + ref_type=SIGRefType.PARK, url=f"https://wwff.co/directory/?showRef={ref_id}", grid=row["iaruLocator"] if "iaruLocator" in row and row["iaruLocator"] != "-" else None, latitude=float(row["latitude"]) diff --git a/providers/sigrefdata/zlota.py b/providers/sigrefdata/zlota.py index 5d68be0..8cc1958 100644 --- a/providers/sigrefdata/zlota.py +++ b/providers/sigrefdata/zlota.py @@ -2,6 +2,7 @@ from time import sleep from pyhamtools.locator import latlong_to_locator +from core.enums import SIGRefType from data.sig_ref import SIGRef from providers.sigrefdata.file_download_sig_ref_data_provider import ( FileDownloadSIGRefDataProvider, @@ -26,12 +27,16 @@ class ZLOTA(FileDownloadSIGRefDataProvider): ref_id = ref["code"] latitude = ref["latitude"] longitude = ref["longitude"] + try: + ref_type = SIGRefType(ref["asset_type"].title().upper()) + except ValueError: + ref_type = SIGRefType.UNKNOWN new_ref = SIGRef( sig=self.SIG, id=ref_id, name=ref["name"], - ref_type=ref["asset_type"].title(), + ref_type=ref_type, url=f"https://ontheair.nz/assets/{ref_id.replace('/', '_')}", latitude=latitude, longitude=longitude, diff --git a/providers/spot/gma.py b/providers/spot/gma.py index 3ebe4c5..44983bf 100644 --- a/providers/spot/gma.py +++ b/providers/spot/gma.py @@ -4,6 +4,7 @@ from datetime import datetime import pytz from core.constants import HTTP_HEADERS +from core.enums import Mode, SIGRefType from core.url_data_cache import URLDataCache from data.sig_ref import SIGRef from data.spot import Spot @@ -65,7 +66,9 @@ class GMA(HTTPSpotProvider): if (source_spot["QRG"] != "" and source_spot["QRG"] != "QRT") else None, # 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"], sig_refs=[ SIGRef( @@ -113,24 +116,31 @@ class GMA(HTTPSpotProvider): match ref_info["reftype"]: case "Summit": spot.sig_refs[0].sig = "GMA" + spot.sig_refs[0].ref_type = SIGRefType.SUMMIT spot.sig = "GMA" case "IOTA Island": spot.sig_refs[0].sig = "IOTA" + spot.sig_refs[0].ref_type = SIGRefType.ISLAND spot.sig = "IOTA" case "GMA Island": spot.sig_refs[0].sig = "GMA Islands" + spot.sig_refs[0].ref_type = SIGRefType.ISLAND spot.sig = "GMA Islands" case "Lighthouse (ILLW)": spot.sig_refs[0].sig = "ILLW" + spot.sig_refs[0].ref_type = SIGRefType.LIGHTHOUSE spot.sig = "ILLW" case "Lighthouse (ARLHS)": spot.sig_refs[0].sig = "ARLHS" + spot.sig_refs[0].ref_type = SIGRefType.LIGHTHOUSE spot.sig = "ARLHS" case "Castle": spot.sig_refs[0].sig = "WCA" + spot.sig_refs[0].ref_type = SIGRefType.CASTLE spot.sig = "WCA" case "Mill": spot.sig_refs[0].sig = "MOTA" + spot.sig_refs[0].ref_type = SIGRefType.MILL spot.sig = "MOTA" case _: logger.warning( diff --git a/providers/spot/hema.py b/providers/spot/hema.py index 26f983b..6fed404 100644 --- a/providers/spot/hema.py +++ b/providers/spot/hema.py @@ -7,6 +7,7 @@ import requests from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout from core.constants import HTTP_HEADERS +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -59,7 +60,7 @@ class HEMA(HTTPSpotProvider): dx_call=spot_items[2].upper(), de_call=spotter_comment_match.group(1).upper(), 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), sig="HEMA", sig_refs=[ @@ -69,6 +70,7 @@ class HEMA(HTTPSpotProvider): name=spot_items[4], latitude=float(spot_items[7]), longitude=float(spot_items[8]), + ref_type=SIGRefType.SUMMIT, ) ], time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M") diff --git a/providers/spot/llota.py b/providers/spot/llota.py index fed4062..738ff28 100644 --- a/providers/spot/llota.py +++ b/providers/spot/llota.py @@ -1,5 +1,6 @@ from datetime import datetime +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -31,7 +32,7 @@ class LLOTA(HTTPSpotProvider): dx_call=source_spot["callsign"].upper(), de_call=spotter.upper() if spotter else None, freq=float(source_spot["frequency"]) * 1000000, - mode=source_spot["mode"].upper(), + mode=Mode.from_name(source_spot["mode"].upper()), comment=comment, sig="LLOTA", sig_refs=[ @@ -39,6 +40,7 @@ class LLOTA(HTTPSpotProvider): id=source_spot["reference"], sig="LLOTA", name=source_spot["reference_name"], + ref_type=SIGRefType.LAKE, ) ], time=datetime.fromisoformat(source_spot["updated_at"].replace("Z", "+00:00")).timestamp(), diff --git a/providers/spot/parksnpeaks.py b/providers/spot/parksnpeaks.py index 22d72d6..2426142 100644 --- a/providers/spot/parksnpeaks.py +++ b/providers/spot/parksnpeaks.py @@ -6,6 +6,7 @@ import pytz import requests from core.constants import HTTP_HEADERS +from core.enums import Mode from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -51,7 +52,7 @@ class ParksNPeaks(HTTPSpotProvider): if (source_spot["actFreq"] != "") else None, # 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"], time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S") .replace(tzinfo=pytz.UTC) diff --git a/providers/spot/pota.py b/providers/spot/pota.py index b84ec2a..92faabe 100644 --- a/providers/spot/pota.py +++ b/providers/spot/pota.py @@ -4,6 +4,7 @@ import pytz import requests from core.constants import HTTP_HEADERS +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -30,7 +31,7 @@ class POTA(HTTPSpotProvider): dx_call=source_spot["activator"].upper(), de_call=source_spot["spotter"].upper(), 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"], sig="POTA", sig_refs=[ @@ -40,6 +41,7 @@ class POTA(HTTPSpotProvider): name=source_spot["name"], latitude=source_spot["latitude"], longitude=source_spot["longitude"], + ref_type=SIGRefType.PARK ) ], time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S") diff --git a/providers/spot/sota.py b/providers/spot/sota.py index e21f8c8..c2f153f 100644 --- a/providers/spot/sota.py +++ b/providers/spot/sota.py @@ -5,6 +5,7 @@ import requests from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout from core.constants import HTTP_HEADERS +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -53,7 +54,7 @@ class SOTA(HTTPSpotProvider): if (source_spot["frequency"] is not None) else None, # Seen SOTA spots with no frequency! - mode=source_spot["mode"].upper(), + mode=Mode.from_name(source_spot["mode"].upper()), comment=source_spot["comments"], sig="SOTA", sig_refs=[ @@ -64,6 +65,7 @@ class SOTA(HTTPSpotProvider): latitude=source_spot["latitude"], longitude=source_spot["longitude"], activation_score=source_spot["points"], + ref_type=SIGRefType.SUMMIT, ) ], dx_latitude=source_spot["latitude"], diff --git a/providers/spot/tiles.py b/providers/spot/tiles.py index 7d8c524..2af03fa 100644 --- a/providers/spot/tiles.py +++ b/providers/spot/tiles.py @@ -4,7 +4,7 @@ from datetime import datetime import requests 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.spot import Spot 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 de_call=source_spot["call_sign"].upper(), freq=freq, - mode=source_spot["mode"].upper(), + mode=Mode.from_name(source_spot["mode"].upper()), comment=source_spot["notes"], 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. @@ -68,6 +68,7 @@ class Tiles(HTTPSpotProvider): name=source_spot["maidenhead_grid"], latitude=source_spot["latitude"], longitude=source_spot["longitude"], + ref_type=SIGRefType.GRID ) ], time=datetime.fromisoformat(source_spot["created_at"].replace("Z", "+00:00")).timestamp(), diff --git a/providers/spot/towers.py b/providers/spot/towers.py index b48ff62..995410f 100644 --- a/providers/spot/towers.py +++ b/providers/spot/towers.py @@ -1,6 +1,9 @@ import json from datetime import datetime +import pytz + +from core.enums import SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -32,10 +35,10 @@ class Towers(HTTPSpotProvider): freq=likely_freq, comment=source_spot["comment"], sig="Towers", - sig_refs=[SIGRef(id=source_spot["ref"], sig="Towers")], - time=datetime.strptime( - response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M" - ).timestamp(), + sig_refs=[SIGRef(id=source_spot["ref"], sig="Towers", ref_type=SIGRefType.TOWER)], + time=datetime.strptime(response_json["updated"][:10] + source_spot["time"], "%Y-%m-%d%H:%M") + .replace(tzinfo=pytz.utc) + .timestamp(), ) # Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do diff --git a/providers/spot/ukpacketnet.py b/providers/spot/ukpacketnet.py index 89cb09c..05d28c1 100644 --- a/providers/spot/ukpacketnet.py +++ b/providers/spot/ukpacketnet.py @@ -3,6 +3,7 @@ from datetime import datetime import pytz +from core.enums import Mode from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -77,7 +78,7 @@ class UKPacketNet(HTTPSpotProvider): dx_call=heard["callsign"].upper(), de_call=node["callsign"].upper(), freq=freq, - mode="PKT", + mode=Mode.PKT, comment=comment, time=datetime.strptime(heard["lastHeard"], "%Y-%m-%d %H:%M:%S") .replace(tzinfo=pytz.UTC) diff --git a/providers/spot/wota.py b/providers/spot/wota.py index 87ce14c..ceb08ac 100644 --- a/providers/spot/wota.py +++ b/providers/spot/wota.py @@ -8,6 +8,7 @@ import pytz from rss_parser import Parser from rss_parser.models.rss import RSS +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -87,10 +88,10 @@ class WOTA(HTTPSpotProvider): dx_call=dx_call, de_call=spotter, freq=freq_hz, - mode=mode, + mode=Mode.from_name(mode), comment=comment, 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(), ) diff --git a/providers/spot/wwbota.py b/providers/spot/wwbota.py index 253151a..dcb6ad2 100644 --- a/providers/spot/wwbota.py +++ b/providers/spot/wwbota.py @@ -1,6 +1,7 @@ import json from datetime import datetime +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.sse_spot_provider import SSESpotProvider @@ -26,6 +27,7 @@ class WWBOTA(SSESpotProvider): name=ref["name"], latitude=ref["lat"], longitude=ref["long"], + ref_type=SIGRefType.BUNKER ) refs.append(sigref) @@ -34,7 +36,7 @@ class WWBOTA(SSESpotProvider): dx_call=source_spot["call"].upper(), de_call=source_spot["spotter"].upper(), 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"], sig="WWBOTA", sig_refs=refs, diff --git a/providers/spot/wwff.py b/providers/spot/wwff.py index 1eb303b..6623ec1 100644 --- a/providers/spot/wwff.py +++ b/providers/spot/wwff.py @@ -2,6 +2,7 @@ from datetime import datetime import pytz +from core.enums import Mode, SIGRefType from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -27,7 +28,7 @@ class WWFF(HTTPSpotProvider): dx_call=source_spot["activator"].upper(), de_call=source_spot["spotter"].upper(), freq=float(source_spot["frequency_khz"]) * 1000, - mode=source_spot["mode"].upper(), + mode=Mode.from_name(source_spot["mode"].upper()), comment=source_spot["remarks"], sig="WWFF", sig_refs=[ @@ -37,6 +38,7 @@ class WWFF(HTTPSpotProvider): name=source_spot["reference_name"], latitude=source_spot["latitude"], longitude=source_spot["longitude"], + ref_type=SIGRefType.PARK ) ], time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(), diff --git a/providers/spot/xota.py b/providers/spot/xota.py index 122d298..a1c1ca4 100644 --- a/providers/spot/xota.py +++ b/providers/spot/xota.py @@ -3,6 +3,7 @@ from datetime import datetime import pytz +from core.enums import Mode from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.websocket_spot_provider import WebsocketSpotProvider @@ -34,7 +35,7 @@ class XOTA(WebsocketSpotProvider): source_id=source_spot["id"], dx_call=source_spot["stationCallSign"].upper(), freq=float(source_spot["freq"]) * 1000, - mode=source_spot["mode"].upper(), + mode=Mode.from_name(source_spot["mode"].upper()), sig=self.SIG, sig_refs=[ SIGRef( diff --git a/providers/spot/zlota.py b/providers/spot/zlota.py index d3a274d..0439750 100644 --- a/providers/spot/zlota.py +++ b/providers/spot/zlota.py @@ -2,6 +2,7 @@ from datetime import datetime import pytz +from core.enums import Mode from data.sig_ref import SIGRef from data.spot import Spot from providers.spot.http_spot_provider import HTTPSpotProvider @@ -32,7 +33,7 @@ class ZLOTA(HTTPSpotProvider): dx_call=source_spot["activator"].upper(), de_call=source_spot["spotter"].upper(), freq=freq_hz, - mode=source_spot["mode"].upper().strip(), + mode=Mode.from_name(source_spot["mode"].upper().strip()), comment=source_spot["comments"], sig="ZLOTA", sig_refs=[ diff --git a/server/handlers/api/options.py b/server/handlers/api/options.py index 6aa9928..4f9f7be 100644 --- a/server/handlers/api/options.py +++ b/server/handlers/api/options.py @@ -8,8 +8,8 @@ from tornado import httputil from tornado.web import Application from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE -from core.constants import ALL_MODES, BANDS, PROPAGATION_MODES, SIGS -from core.enums import Continent, ModeType +from core.constants import BANDS, PROPAGATION_MODES, SIGS +from core.enums import Continent, Mode, ModeType from core.prometheus_metrics_handler import api_requests_counter from core.utils import safe_json_dumps @@ -83,7 +83,7 @@ class APIOptionsHandler(tornado.web.RequestHandler): options = { "bands": BANDS, - "modes": ALL_MODES, + "modes": [m.value for m in Mode], "mode_types": [t.value for t in ModeType], "sigs": SIGS, "spot_providers": spot_providers, diff --git a/static/apidocs/openapi.yml b/static/apidocs/openapi.yml index 5ec9a10..a0313ea 100644 --- a/static/apidocs/openapi.yml +++ b/static/apidocs/openapi.yml @@ -19,8 +19,9 @@ info: * 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. - * Added `icon`, `region_flag` and `refs_globally_unique` to SIG information - * Unknown mode types now return "UNKNOWN" not null + * Added `sig_type`, `icon`, `region_flag` and `refs_globally_unique` to SIG information + * 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 @@ -867,6 +868,14 @@ components: - Toilets example: POTA + SIGType: + type: string + enum: + - WORLDWIDE + - REGIONAL + - EVENT + example: WORLDWIDE + SIGNameIncludingNoSIG: oneOf: - $ref: "#/components/schemas/SIGName" @@ -874,6 +883,29 @@ components: enum: [ NO_SIG ] 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: type: string enum: @@ -938,6 +970,7 @@ components: - FSK - PKT - MSK144 + - UNKNOWN example: SSB ModeType: @@ -1006,9 +1039,8 @@ components: description: SIG reference name example: Null Country Park ref_type: - type: string description: SIG reference type - example: Park + $ref: "#/components/schemas/SIGRefType" url: type: string description: SIG reference URL, which the user can look up for more information @@ -1493,6 +1525,12 @@ components: type: string description: The full name of the SIG 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: type: array description: > diff --git a/static/js/spotsbandsandmap.js b/static/js/spotsbandsandmap.js index e761ab7..ee66a06 100644 --- a/static/js/spotsbandsandmap.js +++ b/static/js/spotsbandsandmap.js @@ -42,18 +42,25 @@ function setHamHFBandToggles() { function generateSIGsMultiToggleFilterCard(sig_options) { const $grid1 = $('