mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
Compare commits
6
Commits
55eaa871e9
...
9d117a6069
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d117a6069 | ||
|
|
606cf1be68 | ||
|
|
5a6fc19ab5 | ||
|
|
9258d680fc | ||
|
|
8189be699e | ||
|
|
fa2e4b929a |
+68
-83
@@ -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,301 +16,288 @@ 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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇳🇿",
|
||||||
refs_globally_unique=True
|
refs_globally_unique=True,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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",
|
SIG(
|
||||||
|
name="BOTA",
|
||||||
comment_names=[],
|
comment_names=[],
|
||||||
description="Beaches on the Air",
|
description="Beaches on the Air",
|
||||||
icon="fa-umbrella-beach"
|
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇬🇧",
|
||||||
refs_globally_unique=False
|
refs_globally_unique=False,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇮🇪",
|
||||||
refs_globally_unique=False
|
refs_globally_unique=False,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇪🇸",
|
||||||
refs_globally_unique=False
|
refs_globally_unique=False,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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.
|
||||||
ref_regex=r"([DE]|FEA)[\- ]\d{4}(\.\d)?",
|
ref_regex=r"([DE]|FEA)[\- ]\d{4}(\.\d)?",
|
||||||
icon="fa-house-flood-water",
|
icon="fa-house-flood-water",
|
||||||
region_flag="🇪🇸",
|
region_flag="🇪🇸",
|
||||||
refs_globally_unique=True
|
refs_globally_unique=True,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇮🇹",
|
||||||
refs_globally_unique=True
|
refs_globally_unique=True,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇩🇪",
|
||||||
refs_globally_unique=False
|
refs_globally_unique=False,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🇵🇱",
|
||||||
refs_globally_unique=False
|
refs_globally_unique=False,
|
||||||
),
|
),
|
||||||
SIG(
|
SIG(
|
||||||
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="🏴☠️",
|
||||||
refs_globally_unique=True
|
refs_globally_unique=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 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_TYPES = ["CW", "PHONE", "DATA"]
|
|
||||||
|
|
||||||
# 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",
|
|
||||||
"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),
|
||||||
@@ -325,7 +313,7 @@ BANDS = [
|
|||||||
Band(name="12m", start_freq=24890000, end_freq=24990000, is_ham_hf=True),
|
Band(name="12m", start_freq=24890000, end_freq=24990000, is_ham_hf=True),
|
||||||
Band(name="11m", start_freq=26965000, end_freq=27405000),
|
Band(name="11m", start_freq=26965000, end_freq=27405000),
|
||||||
Band(name="10m", start_freq=28000000, end_freq=29700000, is_ham_hf=True),
|
Band(name="10m", start_freq=28000000, end_freq=29700000, is_ham_hf=True),
|
||||||
Band(name="6m", start_freq=50000000, end_freq=54000000),
|
Band(name="6m", start_freq=50000000, end_freq=54000000, is_ham_hf=True),
|
||||||
Band(name="5m", start_freq=56000000, end_freq=60500000),
|
Band(name="5m", start_freq=56000000, end_freq=60500000),
|
||||||
Band(name="4m", start_freq=70000000, end_freq=70500000),
|
Band(name="4m", start_freq=70000000, end_freq=70500000),
|
||||||
Band(name="2m", start_freq=144000000, end_freq=148000000),
|
Band(name="2m", start_freq=144000000, end_freq=148000000),
|
||||||
@@ -341,9 +329,6 @@ BANDS = [
|
|||||||
]
|
]
|
||||||
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0)
|
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0)
|
||||||
|
|
||||||
# Continents
|
|
||||||
CONTINENTS = ["EU", "NA", "SA", "AS", "AF", "OC", "AN"]
|
|
||||||
|
|
||||||
# Propagation modes used in VHF/UHF DX cluster comments, e.g. "JN61ES<ES>JM56XT". I don't think there's an official list
|
# Propagation modes used in VHF/UHF DX cluster comments, e.g. "JN61ES<ES>JM56XT". I don't think there's an official list
|
||||||
# of these anywhere, but here are some I've seen or seen reference to
|
# of these anywhere, but here are some I've seen or seen reference to
|
||||||
PROPAGATION_MODES = {
|
PROPAGATION_MODES = {
|
||||||
|
|||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class Continent(str, Enum):
|
||||||
|
EU = "EU"
|
||||||
|
NA = "NA"
|
||||||
|
SA = "SA"
|
||||||
|
AS = "AS"
|
||||||
|
AF = "AF"
|
||||||
|
OC = "OC"
|
||||||
|
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"
|
||||||
|
DATA = "DATA"
|
||||||
|
UNKNOWN = "UNKNOWN"
|
||||||
|
|
||||||
|
|
||||||
|
class ModeSource(str, Enum):
|
||||||
|
"""Where the mode data came from in a spot."""
|
||||||
|
|
||||||
|
SPOT = "SPOT"
|
||||||
|
COMMENT = "COMMENT"
|
||||||
|
BANDPLAN = "BANDPLAN"
|
||||||
|
NONE = "NONE"
|
||||||
|
|
||||||
|
|
||||||
|
class LocationSourceForSpot(str, Enum):
|
||||||
|
"""Where the location data came from in a spot."""
|
||||||
|
|
||||||
|
SPOT = "SPOT"
|
||||||
|
SIG_REF_LOOKUP = "SIG REF LOOKUP"
|
||||||
|
GRID = "GRID"
|
||||||
|
HOME_QTH = "HOME QTH"
|
||||||
|
DXCC = "DXCC"
|
||||||
|
NONE = "NONE"
|
||||||
|
|
||||||
|
|
||||||
|
class LocationSourceForCallsign(str, Enum):
|
||||||
|
"""Where the location data came from in a callsign data object."""
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
+30
-28
@@ -5,17 +5,10 @@ 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 data.callsign import Callsign
|
from core.enums import MODE_ALIASES, Continent, Mode, ModeType
|
||||||
|
from data.callsign import Callsign, LocationSourceForCallsign
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -27,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 "CW"
|
return ModeType.UNKNOWN
|
||||||
elif mode.upper() in PHONE_MODES:
|
|
||||||
return "PHONE"
|
try:
|
||||||
elif mode.upper() in DATA_MODES:
|
mode = Mode(mode.upper())
|
||||||
return "DATA"
|
if mode.is_cw:
|
||||||
else:
|
return ModeType.CW
|
||||||
|
if mode.is_phone:
|
||||||
|
return ModeType.PHONE
|
||||||
|
return ModeType.DATA
|
||||||
|
|
||||||
|
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):
|
||||||
@@ -116,7 +118,7 @@ def get_callsign_object_from_pyhamtools_callinfo(callsign, callinfo):
|
|||||||
|
|
||||||
country = data.get("country", None)
|
country = data.get("country", None)
|
||||||
dxcc_id = data.get("adif", None)
|
dxcc_id = data.get("adif", None)
|
||||||
continent = data.get("continent", None)
|
continent = Continent(data.get("continent", None))
|
||||||
cq_zone = data.get("cqz", None)
|
cq_zone = data.get("cqz", None)
|
||||||
itu_zone = data.get("ituz", None)
|
itu_zone = data.get("ituz", None)
|
||||||
lat = float(data["latitude"]) if "latitude" in data else None
|
lat = float(data["latitude"]) if "latitude" in data else None
|
||||||
@@ -136,7 +138,7 @@ def get_callsign_object_from_pyhamtools_callinfo(callsign, callinfo):
|
|||||||
latitude=lat,
|
latitude=lat,
|
||||||
longitude=lon,
|
longitude=lon,
|
||||||
grid=grid,
|
grid=grid,
|
||||||
location_source="DXCC",
|
location_source=LocationSourceForCallsign.DXCC,
|
||||||
)
|
)
|
||||||
|
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
|
|||||||
+3
-2
@@ -7,6 +7,7 @@ from datetime import datetime, timedelta
|
|||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from core.call_lookup_helper import get_call_info
|
from core.call_lookup_helper import get_call_info
|
||||||
|
from core.enums import Continent
|
||||||
from core.sig_lookup_helper import populate_missing_sig_ref_info
|
from core.sig_lookup_helper import populate_missing_sig_ref_info
|
||||||
from core.utils import get_flag_for_dxcc
|
from core.utils import get_flag_for_dxcc
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ class Alert:
|
|||||||
# Country flag of the DX operator
|
# Country flag of the DX operator
|
||||||
dx_flag: str | None = None
|
dx_flag: str | None = None
|
||||||
# Continent of the DX operator
|
# Continent of the DX operator
|
||||||
dx_continent: str | None = None
|
dx_continent: Continent | None = None
|
||||||
# DXCC ID of the DX operator
|
# DXCC ID of the DX operator
|
||||||
dx_dxcc_id: int | None = None
|
dx_dxcc_id: int | None = None
|
||||||
# CQ zone of the DX operator
|
# CQ zone of the DX operator
|
||||||
@@ -92,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 = 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:
|
||||||
|
|||||||
+5
-3
@@ -1,5 +1,7 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from core.enums import Continent, LocationSourceForCallsign
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Callsign:
|
class Callsign:
|
||||||
@@ -28,15 +30,15 @@ class Callsign:
|
|||||||
# Country in which the callsign indicates they are operating
|
# Country in which the callsign indicates they are operating
|
||||||
country: str | None = None
|
country: str | None = None
|
||||||
# Continent in which the callsign indicates they are operating
|
# Continent in which the callsign indicates they are operating
|
||||||
continent: str | None = None
|
continent: Continent | None = None
|
||||||
# DXCC ID in which the callsign indicates they are operating
|
# DXCC ID in which the callsign indicates they are operating
|
||||||
dxcc_id: int | None = None
|
dxcc_id: int | None = None
|
||||||
# CQ zone in which the callsign indicates they are operating
|
# CQ zone in which the callsign indicates they are operating
|
||||||
cq_zone: int | None = None
|
cq_zone: int | None = None
|
||||||
# ITU zone in which the callsign indicates they are operating
|
# ITU zone in which the callsign indicates they are operating
|
||||||
itu_zone: int | None = None
|
itu_zone: int | None = None
|
||||||
# Location source. This can be "HOME QTH" or "DXCC" depending on which provider gave us a location
|
# Location source
|
||||||
location_source: str | None = None
|
location_source: LocationSourceForCallsign = LocationSourceForCallsign.NONE
|
||||||
|
|
||||||
def fully_populated(self):
|
def fully_populated(self):
|
||||||
"""Utility method to indicate that the callsign data is fully populated. Multiple providers can return data for
|
"""Utility method to indicate that the callsign data is fully populated. Multiple providers can return data for
|
||||||
|
|||||||
+9
-5
@@ -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
@@ -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.
|
||||||
|
|||||||
+33
-33
@@ -11,7 +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, 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 (
|
||||||
@@ -52,7 +53,7 @@ class Spot:
|
|||||||
# Country flag of the DX operator
|
# Country flag of the DX operator
|
||||||
dx_flag: str | None = None
|
dx_flag: str | None = None
|
||||||
# Continent of the DX operator
|
# Continent of the DX operator
|
||||||
dx_continent: str | None = None
|
dx_continent: Continent | None = None
|
||||||
# DXCC ID of the DX operator
|
# DXCC ID of the DX operator
|
||||||
dx_dxcc_id: int | None = None
|
dx_dxcc_id: int | None = None
|
||||||
# CQ zone of the DX operator
|
# CQ zone of the DX operator
|
||||||
@@ -68,9 +69,8 @@ class Spot:
|
|||||||
# lookup
|
# lookup
|
||||||
dx_latitude: float | None = None
|
dx_latitude: float | None = None
|
||||||
dx_longitude: float | None = None
|
dx_longitude: float | None = None
|
||||||
# DX Location source. Indicates how accurate the location might be. Values: "SPOT", "GRID", "SIG REF LOOKUP",
|
# DX Location source. Indicates how accurate the location might be.
|
||||||
# "HOME QTH", "DXCC", "NONE"
|
dx_location_source: LocationSourceForSpot = LocationSourceForSpot.NONE
|
||||||
dx_location_source: str = "NONE"
|
|
||||||
# DX Location good. Indicates that the software thinks the location data is good enough to plot on a map. This is
|
# DX Location good. Indicates that the software thinks the location data is good enough to plot on a map. This is
|
||||||
# true if the location source is "SPOT", "SIG REF LOOKUP" or "GRID", or if the location source is "HOME QTH" and the
|
# true if the location source is "SPOT", "SIG REF LOOKUP" or "GRID", or if the location source is "HOME QTH" and the
|
||||||
# DX callsign doesn't have a suffix like /P.
|
# DX callsign doesn't have a suffix like /P.
|
||||||
@@ -85,7 +85,7 @@ class Spot:
|
|||||||
# Country flag of the spotter
|
# Country flag of the spotter
|
||||||
de_flag: str | None = None
|
de_flag: str | None = None
|
||||||
# Continent of the spotter
|
# Continent of the spotter
|
||||||
de_continent: str | None = None
|
de_continent: Continent | None = None
|
||||||
# DXCC ID of the spotter
|
# DXCC ID of the spotter
|
||||||
de_dxcc_id: int | None = None
|
de_dxcc_id: int | None = None
|
||||||
# If this is an APRS/Packet/etc spot, what SSID was the spotter/receiver using?
|
# If this is an APRS/Packet/etc spot, what SSID was the spotter/receiver using?
|
||||||
@@ -103,11 +103,11 @@ 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". One of "CW", "PHONE" or "DIGI".
|
# Inferred mode "family".
|
||||||
mode_type: str | None = None
|
mode_type: ModeType = ModeType.UNKNOWN
|
||||||
# Source of the mode information. "SPOT", "COMMENT", "BANDPLAN" or "NONE"
|
# Source of the mode information.
|
||||||
mode_source: str = "NONE"
|
mode_source: ModeSource = ModeSource.NONE
|
||||||
# Frequency, in Hz
|
# Frequency, in Hz
|
||||||
freq: float | None = None
|
freq: float | None = None
|
||||||
# Band, defined by the frequency, e.g. "40m" or "70cm"
|
# Band, defined by the frequency, e.g. "40m" or "70cm"
|
||||||
@@ -185,7 +185,7 @@ class Spot:
|
|||||||
if self.dx_call and not self.dx_country:
|
if self.dx_call and not self.dx_country:
|
||||||
self.dx_country = dx_call_info.country
|
self.dx_country = dx_call_info.country
|
||||||
if self.dx_call and not self.dx_continent:
|
if self.dx_call and not self.dx_continent:
|
||||||
self.dx_continent = dx_call_info.continent
|
self.dx_continent = Continent(dx_call_info.continent)
|
||||||
if self.dx_call and not self.dx_dxcc_id:
|
if self.dx_call and not self.dx_dxcc_id:
|
||||||
self.dx_dxcc_id = dx_call_info.dxcc_id
|
self.dx_dxcc_id = dx_call_info.dxcc_id
|
||||||
if self.dx_dxcc_id and not self.dx_flag:
|
if self.dx_dxcc_id and not self.dx_flag:
|
||||||
@@ -223,7 +223,7 @@ class Spot:
|
|||||||
if not self.de_country:
|
if not self.de_country:
|
||||||
self.de_country = de_call_info.country
|
self.de_country = de_call_info.country
|
||||||
if not self.de_continent:
|
if not self.de_continent:
|
||||||
self.de_continent = de_call_info.continent
|
self.de_continent = Continent(de_call_info.continent)
|
||||||
if not self.de_dxcc_id:
|
if not self.de_dxcc_id:
|
||||||
self.de_dxcc_id = de_call_info.dxcc_id
|
self.de_dxcc_id = de_call_info.dxcc_id
|
||||||
if self.de_dxcc_id and not self.de_flag:
|
if self.de_dxcc_id and not self.de_flag:
|
||||||
@@ -239,26 +239,26 @@ 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_source = "SPOT"
|
self.mode = Mode.UNKNOWN
|
||||||
if self.comment and not self.mode:
|
if self.mode != Mode.UNKNOWN:
|
||||||
|
self.mode_source = ModeSource.SPOT
|
||||||
|
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 = "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 = "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
|
||||||
if self.dx_latitude or self.dx_grid:
|
if self.dx_latitude or self.dx_grid:
|
||||||
self.dx_location_source = "SPOT"
|
self.dx_location_source = LocationSourceForSpot.SPOT
|
||||||
|
|
||||||
# Set the top-level "SIG" if it is missing but we have at least one SIG ref.
|
# Set the top-level "SIG" if it is missing but we have at least one SIG ref.
|
||||||
if not self.sig and self.sig_refs and len(self.sig_refs) > 0:
|
if not self.sig and self.sig_refs and len(self.sig_refs) > 0:
|
||||||
@@ -324,9 +324,9 @@ class Spot:
|
|||||||
self.dx_latitude = sig_ref.latitude
|
self.dx_latitude = sig_ref.latitude
|
||||||
self.dx_longitude = sig_ref.longitude
|
self.dx_longitude = sig_ref.longitude
|
||||||
if self.sig == "WAB" or self.sig == "WAI" or self.sig == "Tiles":
|
if self.sig == "WAB" or self.sig == "WAI" or self.sig == "Tiles":
|
||||||
self.dx_location_source = "GRID"
|
self.dx_location_source = LocationSourceForSpot.GRID
|
||||||
else:
|
else:
|
||||||
self.dx_location_source = "SIG REF LOOKUP"
|
self.dx_location_source = LocationSourceForSpot.SIG_REF_LOOKUP
|
||||||
|
|
||||||
# If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG
|
# If the spot itself doesn't have a SIG yet, but we have at least one SIG reference, take that reference's SIG
|
||||||
# and apply it to the whole spot.
|
# and apply it to the whole spot.
|
||||||
@@ -347,7 +347,7 @@ class Spot:
|
|||||||
self.de_grid = grid_mode_grid_match.group(1).upper()
|
self.de_grid = grid_mode_grid_match.group(1).upper()
|
||||||
if not self.dx_grid:
|
if not self.dx_grid:
|
||||||
self.dx_grid = grid_mode_grid_match.group(4).upper()
|
self.dx_grid = grid_mode_grid_match.group(4).upper()
|
||||||
self.dx_location_source = "GRID"
|
self.dx_location_source = LocationSourceForSpot.GRID
|
||||||
|
|
||||||
# And extract propagation mode (group 2 for <...>, group 3 for (...)):
|
# And extract propagation mode (group 2 for <...>, group 3 for (...)):
|
||||||
mode_tag = (grid_mode_grid_match.group(2) or grid_mode_grid_match.group(3) or "").upper()
|
mode_tag = (grid_mode_grid_match.group(2) or grid_mode_grid_match.group(3) or "").upper()
|
||||||
@@ -368,7 +368,7 @@ class Spot:
|
|||||||
# regex matches, so extract grids:
|
# regex matches, so extract grids:
|
||||||
if not self.dx_grid:
|
if not self.dx_grid:
|
||||||
self.dx_grid = grid_mode_grid_match.group(1).upper()
|
self.dx_grid = grid_mode_grid_match.group(1).upper()
|
||||||
self.dx_location_source = "GRID"
|
self.dx_location_source = LocationSourceForSpot.GRID
|
||||||
if not self.de_grid:
|
if not self.de_grid:
|
||||||
self.de_grid = grid_mode_grid_match.group(2).upper()
|
self.de_grid = grid_mode_grid_match.group(2).upper()
|
||||||
|
|
||||||
@@ -440,10 +440,10 @@ class Spot:
|
|||||||
self.dx_latitude
|
self.dx_latitude
|
||||||
and self.dx_longitude
|
and self.dx_longitude
|
||||||
and (
|
and (
|
||||||
self.dx_location_source == "SPOT"
|
self.dx_location_source == LocationSourceForSpot.SPOT
|
||||||
or self.dx_location_source == "SIG REF LOOKUP"
|
or self.dx_location_source == LocationSourceForSpot.SIG_REF_LOOKUP
|
||||||
or self.dx_location_source == "GRID"
|
or self.dx_location_source == LocationSourceForSpot.GRID
|
||||||
or (self.dx_location_source == "HOME QTH" and "/" not in (self.dx_call or ""))
|
or (self.dx_location_source == LocationSourceForSpot.HOME_QTH and "/" not in (self.dx_call or ""))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+12
-11
@@ -23,15 +23,16 @@ You can replace `#main` with any other branch or tag reference, for example `#1.
|
|||||||
|
|
||||||
Save the file. You will still need to create a copy of `config-example.yml` and name it `config.yml`, though with the
|
Save the file. You will still need to create a copy of `config-example.yml` and name it `config.yml`, though with the
|
||||||
Docker setup nothing has actually been downloaded yet, so you will have to copy the example from the repository some
|
Docker setup nothing has actually been downloaded yet, so you will have to copy the example from the repository some
|
||||||
other way, e.g. [from the repo in a web browser](https://git.ianrenton.com/ian/spothole/src/branch/main/config-example.yml).
|
other way,
|
||||||
|
e.g. [from the repo in a web browser](https://git.ianrenton.com/ian/spothole/src/branch/main/config-example.yml).
|
||||||
|
|
||||||
With that in place, run `docker compose up` and you should be good to go. To detach, press `d` or run the command with
|
With that in place, run `docker compose up` and you should be good to go. To detach, press `d` or run the command with
|
||||||
the `-d` flag.
|
the `-d` flag.
|
||||||
|
|
||||||
### nginx Reverse Proxy with Docker
|
### nginx Reverse Proxy with Docker
|
||||||
|
|
||||||
In a containerised setup, it's typical to run an nginx reverse proxy in one container, alongside certbot for renewal
|
In a containerised setup, it's typical to run an nginx reverse proxy in one container, alongside certbot for renewal of
|
||||||
of HTTPS certificates, and then applications like Spothole in a separate container. In this case, there are a couple of
|
HTTPS certificates, and then applications like Spothole in a separate container. In this case, there are a couple of
|
||||||
variations of the docker compose file above, and the nginx reverse proxy configuration covered [here](./nginx.md), that
|
variations of the docker compose file above, and the nginx reverse proxy configuration covered [here](./nginx.md), that
|
||||||
you will want to make.
|
you will want to make.
|
||||||
|
|
||||||
@@ -60,8 +61,8 @@ networks:
|
|||||||
```
|
```
|
||||||
|
|
||||||
In your nginx site configuration, you'll want to refer to the Spothole container directly, and drop the block that
|
In your nginx site configuration, you'll want to refer to the Spothole container directly, and drop the block that
|
||||||
allows nginx to access static files directly, as these will be inaccessible in another container. So you may end up
|
allows nginx to access static files directly, as these will be inaccessible in another container. So you may end up with
|
||||||
with something like:
|
something like:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
@@ -146,13 +147,13 @@ server {
|
|||||||
```
|
```
|
||||||
|
|
||||||
If desired, you could even change the port on which Spothole runs from 8080 to a plain 80, in which case your
|
If desired, you could even change the port on which Spothole runs from 8080 to a plain 80, in which case your
|
||||||
`proxy_pass` statements could drop the `:8080` suffix. Since Spothole is in a container, it can serve HTTP on port 80
|
`proxy_pass` statements could drop the `:8080` suffix. Since Spothole is in a container, it can serve HTTP on port 80 if
|
||||||
if desired, because it doesn't conflict with the host system.
|
desired, because it doesn't conflict with the host system.
|
||||||
|
|
||||||
### Restoring the static files bypass
|
### Restoring the static files bypass
|
||||||
|
|
||||||
If you would still like to bypass Spothole's web server for the static files, and serve them with nginx, you can
|
If you would still like to bypass Spothole's web server for the static files, and serve them with nginx, you can do. The
|
||||||
do. The easiest way is to run another nginx container to serve the files, so your Spothole `compose.yaml` becomes:
|
easiest way is to run another nginx container to serve the files, so your Spothole `compose.yaml` becomes:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -183,8 +184,8 @@ networks:
|
|||||||
external: true
|
external: true
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can re-add the block that handles the `/static` path in your nginx reverse proxy config, but this time
|
Then you can re-add the block that handles the `/static` path in your nginx reverse proxy config, but this time point it
|
||||||
point it at the new container rather than at a filesystem path:
|
at the new container rather than at a filesystem path:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
# Load static assets from the spothole-static-nginx container
|
# Load static assets from the spothole-static-nginx container
|
||||||
|
|||||||
+2
-2
@@ -48,8 +48,8 @@ To navigate your way around the source code, this list may help.
|
|||||||
### Extending the server
|
### Extending the server
|
||||||
|
|
||||||
Spothole is designed to be easily extensible. If you want to write your own spot provider, for example, simply add a
|
Spothole is designed to be easily extensible. If you want to write your own spot provider, for example, simply add a
|
||||||
module to the `providers.spot` package containing your class. (Currently, in order to be loaded correctly, the module (
|
module to the `providers.spot` package containing your class. (Currently, in order to be loaded correctly, the module
|
||||||
file) name should be the same as the class name, but lower case.)
|
(file) name should be the same as the class name, but lower case.)
|
||||||
|
|
||||||
Your class should extend "SpotProvider"; if it operates by polling an HTTP Server on a timer, it can instead extend "
|
Your class should extend "SpotProvider"; if it operates by polling an HTTP Server on a timer, it can instead extend "
|
||||||
HTTPSpotProvider" where some of the work is done for you.
|
HTTPSpotProvider" where some of the work is done for you.
|
||||||
|
|||||||
+10
-15
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
Web servers generally serve their pages from port 80. However, it's best not to serve Spothole's web interface directly
|
Web servers generally serve their pages from port 80. However, it's best not to serve Spothole's web interface directly
|
||||||
on port 80, as that requires root privileges on a Linux system. It also and prevents us using HTTPS to serve a secure
|
on port 80, as that requires root privileges on a Linux system. It also and prevents us using HTTPS to serve a secure
|
||||||
site, since Spothole itself doesn't directly support acting as an HTTPS server. The normal solution to this is to use
|
site, since Spothole itself doesn't directly support acting as an HTTPS server. The normal solution to this is to use a
|
||||||
a "reverse proxy" setup, where a general web server handles HTTP and HTTP requests (to port 80 & 443 respectively), then
|
"reverse proxy" setup, where a general web server handles HTTP and HTTP requests (to port 80 & 443 respectively), then
|
||||||
passes on the request to the back-end application (in this case Spothole). nginx is a common choice for this general web
|
passes on the request to the back-end application (in this case Spothole). nginx is a common choice for this general web
|
||||||
server.
|
server.
|
||||||
|
|
||||||
@@ -89,19 +89,14 @@ server {
|
|||||||
```
|
```
|
||||||
|
|
||||||
One further change you might want to make to the file above is the `add_header Access-Control-Allow-Origin` statements.
|
One further change you might want to make to the file above is the `add_header Access-Control-Allow-Origin` statements.
|
||||||
These are what's used on
|
These are what's used on my own Spothole server to make sure that other third-party web-based software can get the data
|
||||||
my own Spothole server to make sure that other third-party web-based software can get the data from my instance, and
|
from my instance, and applies to any endpoint underneath `/api`. If you want *your* Spothole instance to be set up the
|
||||||
applies to any endpoint underneath `/api`. If you want
|
same way, so that others can write software in JavaScript that can access it, leave this intact. But if you want your
|
||||||
*your* Spothole instance to be set up the same way, so that others can write software in JavaScript that can access it,
|
Spothole instance to only be usable by scripts running on the web server you write, you can remove these lines. (Note
|
||||||
leave this intact. But if you want your Spothole instance to only be usable by scripts running on the web server you
|
that this doesn't stop other people writing *non-web-based* software that accesses your Spothole API—the
|
||||||
write,
|
enforcement of cross-origin headers only happens within the user's browser. If you need to lock your instance down so
|
||||||
you can remove these lines. (Note that this doesn't stop other people writing *non-web-based* software that accesses
|
that no-one else can access it with *any* software, that's an aspect of nginx or firewall config that you will need to
|
||||||
your
|
find help with elsewhere.)
|
||||||
Spothole API—the enforcement of cross-origin headers only happens within the user's browser. If you need to lock
|
|
||||||
your
|
|
||||||
instance down so that no-one else can access it with *any* software, that's an aspect of nginx or firewall config that
|
|
||||||
you will need
|
|
||||||
to find help with elsewhere.)
|
|
||||||
|
|
||||||
Now, make a symbolic link to enable the site:
|
Now, make a symbolic link to enable the site:
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ from requests_cache import CachedSession
|
|||||||
from core.config import SERVER_OWNER_CALLSIGN
|
from core.config import SERVER_OWNER_CALLSIGN
|
||||||
from core.constants import HTTP_HEADERS, SOFTWARE_VERSION
|
from core.constants import HTTP_HEADERS, SOFTWARE_VERSION
|
||||||
from core.data_store import CACHE_DIR, DATA_STORE
|
from core.data_store import CACHE_DIR, DATA_STORE
|
||||||
|
from core.enums import Continent
|
||||||
from core.url_data_cache import URLDataCache
|
from core.url_data_cache import URLDataCache
|
||||||
from data.callsign import Callsign
|
from data.callsign import Callsign, LocationSourceForCallsign
|
||||||
from providers.callsigndata.api_query_callsign_data_provider import (
|
from providers.callsigndata.api_query_callsign_data_provider import (
|
||||||
APIQueryCallsignDataProvider,
|
APIQueryCallsignDataProvider,
|
||||||
)
|
)
|
||||||
@@ -142,12 +143,12 @@ class HamQTH(APIQueryCallsignDataProvider):
|
|||||||
name=data.get("nick", None),
|
name=data.get("nick", None),
|
||||||
qth=data.get("qth", None),
|
qth=data.get("qth", None),
|
||||||
country=data.get("country", None),
|
country=data.get("country", None),
|
||||||
continent=data.get("continent", None),
|
continent=Continent(data.get("continent", None)),
|
||||||
latitude=lat,
|
latitude=lat,
|
||||||
longitude=lon,
|
longitude=lon,
|
||||||
grid=grid,
|
grid=grid,
|
||||||
dxcc_id=int(data["adif"]) if "adif" in data else None,
|
dxcc_id=int(data["adif"]) if "adif" in data else None,
|
||||||
cq_zone=int(data["cq"]) if "cq" in data else None,
|
cq_zone=int(data["cq"]) if "cq" in data else None,
|
||||||
itu_zone=int(data["itu"]) if "itu" in data else None,
|
itu_zone=int(data["itu"]) if "itu" in data else None,
|
||||||
location_source="HOME QTH",
|
location_source=LocationSourceForCallsign.HOME_QTH,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ from requests_cache import CachedSession
|
|||||||
|
|
||||||
from core.constants import HTTP_HEADERS
|
from core.constants import HTTP_HEADERS
|
||||||
from core.data_store import CACHE_DIR, DATA_STORE
|
from core.data_store import CACHE_DIR, DATA_STORE
|
||||||
|
from core.enums import Continent, LocationSourceForCallsign
|
||||||
from core.url_data_cache import URLDataCache
|
from core.url_data_cache import URLDataCache
|
||||||
from data.callsign import Callsign
|
from data.callsign import Callsign
|
||||||
from providers.callsigndata.api_query_callsign_data_provider import (
|
from providers.callsigndata.api_query_callsign_data_provider import APIQueryCallsignDataProvider
|
||||||
APIQueryCallsignDataProvider,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -170,12 +169,12 @@ class QRZ(APIQueryCallsignDataProvider):
|
|||||||
name=name,
|
name=name,
|
||||||
qth=data.get("addr2", None),
|
qth=data.get("addr2", None),
|
||||||
country=data.get("country", None),
|
country=data.get("country", None),
|
||||||
continent=data.get("continent", None),
|
continent=Continent(data.get("continent", None)),
|
||||||
latitude=lat,
|
latitude=lat,
|
||||||
longitude=lon,
|
longitude=lon,
|
||||||
grid=grid,
|
grid=grid,
|
||||||
dxcc_id=int(data["adif"]) if "adif" in data else None,
|
dxcc_id=int(data["adif"]) if "adif" in data else None,
|
||||||
cq_zone=int(data["cqzone"]) if "cqzone" in data else None,
|
cq_zone=int(data["cqzone"]) if "cqzone" in data else None,
|
||||||
itu_zone=int(data["ituzone"]) if "ituzone" in data else None,
|
itu_zone=int(data["ituzone"]) if "ituzone" in data else None,
|
||||||
location_source="HOME QTH",
|
location_source=LocationSourceForCallsign.HOME_QTH,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,14 +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(
|
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=ref_name, ref_type=SIGRefType.BUILDING))
|
||||||
SIGRef(
|
|
||||||
sig=self.SIG,
|
|
||||||
id=ref_id,
|
|
||||||
name=ref_name,
|
|
||||||
ref_type="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,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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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],
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"]),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"])
|
||||||
|
|||||||
@@ -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
@@ -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(
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -86,6 +87,7 @@ class ParksNPeaks(HTTPSpotProvider):
|
|||||||
"POTA",
|
"POTA",
|
||||||
"SOTA",
|
"SOTA",
|
||||||
"WWFF",
|
"WWFF",
|
||||||
|
"HEMA",
|
||||||
"SIOTA",
|
"SIOTA",
|
||||||
"ZLOTA",
|
"ZLOTA",
|
||||||
"KRMNPA",
|
"KRMNPA",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -29,8 +30,8 @@ class POTA(HTTPSpotProvider):
|
|||||||
source_id=source_spot["spotId"],
|
source_id=source_spot["spotId"],
|
||||||
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,
|
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")
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
@@ -4,6 +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, 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
|
||||||
@@ -55,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.
|
||||||
@@ -67,13 +68,14 @@ 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(),
|
||||||
dx_grid=source_spot["maidenhead_grid"],
|
dx_grid=source_spot["maidenhead_grid"],
|
||||||
dx_latitude=source_spot["latitude"],
|
dx_latitude=source_spot["latitude"],
|
||||||
dx_longitude=source_spot["longitude"],
|
dx_longitude=source_spot["longitude"],
|
||||||
dx_location_source="GRID",
|
dx_location_source=LocationSourceForSpot.GRID,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -76,7 +77,9 @@ class WOTA(HTTPSpotProvider):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(f"Could not parse WOTA spot description: {source_spot.description}")
|
logger.warning(f"Could not parse WOTA spot description: {source_spot.description}")
|
||||||
|
|
||||||
time = datetime.strptime(source_spot.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(pytz.UTC)
|
time = datetime.strptime(source_spot.pub_date.content, self.RSS_DATE_TIME_FORMAT).astimezone(
|
||||||
|
pytz.UTC
|
||||||
|
)
|
||||||
|
|
||||||
# Convert to our spot format
|
# Convert to our spot format
|
||||||
spot = Spot(
|
spot = Spot(
|
||||||
@@ -85,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(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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,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=[
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import tornado
|
|||||||
from tornado import httputil
|
from tornado import httputil
|
||||||
from tornado.web import Application
|
from tornado.web import Application
|
||||||
|
|
||||||
|
from core.constants import BANDS
|
||||||
|
from core.enums import Continent
|
||||||
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
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONTINENTS = ["EU", "NA", "SA", "AS", "AF", "OC", "AN"]
|
CONTINENTS = [c.value for c in Continent]
|
||||||
BANDS = ["160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"]
|
HF_BANDS = [b.name for b in BANDS if b.is_ham_hf]
|
||||||
CONTINENTS_SET = frozenset(CONTINENTS)
|
|
||||||
BANDS_SET = frozenset(BANDS)
|
|
||||||
|
|
||||||
|
|
||||||
class APIDxStatsHandler(tornado.web.RequestHandler):
|
class APIDxStatsHandler(tornado.web.RequestHandler):
|
||||||
@@ -53,15 +53,11 @@ class APIDxStatsHandler(tornado.web.RequestHandler):
|
|||||||
continue
|
continue
|
||||||
if not spot.time or spot.time < one_hour_ago:
|
if not spot.time or spot.time < one_hour_ago:
|
||||||
continue
|
continue
|
||||||
if (
|
if spot.de_continent in CONTINENTS and spot.dx_continent in CONTINENTS and spot.band in HF_BANDS:
|
||||||
spot.de_continent in CONTINENTS_SET
|
|
||||||
and spot.dx_continent in CONTINENTS_SET
|
|
||||||
and spot.band in BANDS_SET
|
|
||||||
):
|
|
||||||
counts[spot.de_continent, spot.dx_continent, spot.band] += 1
|
counts[spot.de_continent, spot.dx_continent, spot.band] += 1
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
de: {dx: {band: counts[de, dx, band] for band in BANDS} for dx in CONTINENTS} for de in CONTINENTS
|
de: {dx: {band: counts[de, dx, band] for band in HF_BANDS} for dx in CONTINENTS} for de in CONTINENTS
|
||||||
}
|
}
|
||||||
|
|
||||||
self.write(json.dumps(result))
|
self.write(json.dumps(result))
|
||||||
|
|||||||
@@ -8,14 +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 (
|
from core.constants import BANDS, PROPAGATION_MODES, SIGS
|
||||||
ALL_MODES,
|
from core.enums import Continent, Mode, ModeType
|
||||||
BANDS,
|
|
||||||
CONTINENTS,
|
|
||||||
MODE_TYPES,
|
|
||||||
PROPAGATION_MODES,
|
|
||||||
SIGS,
|
|
||||||
)
|
|
||||||
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
|
||||||
|
|
||||||
@@ -89,14 +83,14 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
|||||||
|
|
||||||
options = {
|
options = {
|
||||||
"bands": BANDS,
|
"bands": BANDS,
|
||||||
"modes": ALL_MODES,
|
"modes": [m.value for m in Mode],
|
||||||
"mode_types": MODE_TYPES,
|
"mode_types": [t.value for t in ModeType],
|
||||||
"sigs": SIGS,
|
"sigs": SIGS,
|
||||||
"spot_providers": spot_providers,
|
"spot_providers": spot_providers,
|
||||||
"spot_providers_enabled_by_default": spot_providers_enabled_by_default,
|
"spot_providers_enabled_by_default": spot_providers_enabled_by_default,
|
||||||
"alert_providers": alert_providers,
|
"alert_providers": alert_providers,
|
||||||
"callsign_data_providers": callsign_data_providers,
|
"callsign_data_providers": callsign_data_providers,
|
||||||
"continents": CONTINENTS,
|
"continents": [c.value for c in Continent],
|
||||||
"propagation_modes": list(PROPAGATION_MODES.values()),
|
"propagation_modes": list(PROPAGATION_MODES.values()),
|
||||||
"max_spot_age": MAX_SPOT_AGE,
|
"max_spot_age": MAX_SPOT_AGE,
|
||||||
"spot_allowed": ALLOW_SPOTTING,
|
"spot_allowed": ALLOW_SPOTTING,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import tornado
|
|||||||
from tornado.httpclient import AsyncHTTPClient
|
from tornado.httpclient import AsyncHTTPClient
|
||||||
from tornado.httputil import HTTPHeaders
|
from tornado.httputil import HTTPHeaders
|
||||||
|
|
||||||
|
|
||||||
_LEGACY_PARAM_TO_HEADER_MAP = {
|
_LEGACY_PARAM_TO_HEADER_MAP = {
|
||||||
"qrz_username": "X-QRZ-Username",
|
"qrz_username": "X-QRZ-Username",
|
||||||
"qrz_password": "X-QRZ-Password",
|
"qrz_password": "X-QRZ-Password",
|
||||||
@@ -12,6 +11,7 @@ _LEGACY_PARAM_TO_HEADER_MAP = {
|
|||||||
"hamqth_session_id": "X-HamQTH-Session-ID",
|
"hamqth_session_id": "X-HamQTH-Session-ID",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class V1RedirectHandler(tornado.web.RequestHandler):
|
class V1RedirectHandler(tornado.web.RequestHandler):
|
||||||
"""Transparently proxies requests from the old API to the new one,
|
"""Transparently proxies requests from the old API to the new one,
|
||||||
returning whatever the v2 endpoint returns, for endpoints with no breaking changes."""
|
returning whatever the v2 endpoint returns, for endpoints with no breaking changes."""
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def _handle_legacy_params(handler):
|
|||||||
if value:
|
if value:
|
||||||
handler.request.headers[header] = value
|
handler.request.headers[header] = value
|
||||||
|
|
||||||
|
|
||||||
class V1APISpotsHandler(APISpotsHandler):
|
class V1APISpotsHandler(APISpotsHandler):
|
||||||
"""API request handler for /api/v1/spots (GET). Included in early Spothole v2 for backwards compatibility."""
|
"""API request handler for /api/v1/spots (GET). Included in early Spothole v2 for backwards compatibility."""
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from core.constants import SOFTWARE_VERSION
|
|||||||
from core.data_providers import DATA_PROVIDERS
|
from core.data_providers import DATA_PROVIDERS
|
||||||
from core.data_store import DATA_STORE
|
from core.data_store import DATA_STORE
|
||||||
from core.status_reporter import StatusReporter
|
from core.status_reporter import StatusReporter
|
||||||
from core.utils import infer_mode_from_comment
|
|
||||||
from server.webserver import WEB_SERVER
|
from server.webserver import WEB_SERVER
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -19,7 +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 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
|
||||||
|
|
||||||
@@ -866,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"
|
||||||
@@ -873,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:
|
||||||
@@ -937,6 +970,7 @@ components:
|
|||||||
- FSK
|
- FSK
|
||||||
- PKT
|
- PKT
|
||||||
- MSK144
|
- MSK144
|
||||||
|
- UNKNOWN
|
||||||
example: SSB
|
example: SSB
|
||||||
|
|
||||||
ModeType:
|
ModeType:
|
||||||
@@ -945,6 +979,7 @@ components:
|
|||||||
- CW
|
- CW
|
||||||
- PHONE
|
- PHONE
|
||||||
- DATA
|
- DATA
|
||||||
|
- UNKNOWN
|
||||||
example: CW
|
example: CW
|
||||||
|
|
||||||
ModeSource:
|
ModeSource:
|
||||||
@@ -981,7 +1016,7 @@ components:
|
|||||||
- NONE
|
- NONE
|
||||||
example: SPOT
|
example: SPOT
|
||||||
|
|
||||||
LocationSourceForAlert:
|
LocationSourceForCallsign:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- "HOME QTH"
|
- "HOME QTH"
|
||||||
@@ -1004,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
|
||||||
@@ -1491,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: >
|
||||||
@@ -2197,7 +2237,7 @@ components:
|
|||||||
Where we got the location (grid/latitude/longitude) from. Unlike a spot where we might
|
Where we got the location (grid/latitude/longitude) from. Unlike a spot where we might
|
||||||
have a summit position or WAB square, here the only options are an online QTH lookup, or
|
have a summit position or WAB square, here the only options are an online QTH lookup, or
|
||||||
a location based purely on DXCC, or nothing.
|
a location based purely on DXCC, or nothing.
|
||||||
$ref: "#/components/schemas/LocationSourceForAlert"
|
$ref: "#/components/schemas/LocationSourceForCallsign"
|
||||||
|
|
||||||
GridLookup:
|
GridLookup:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@@ -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>`);
|
||||||
|
|||||||
@@ -179,12 +179,14 @@
|
|||||||
filters. They are also stored in your browser's local storage, so that your preferences are remembered between
|
filters. They are also stored in your browser's local storage, so that your preferences are remembered between
|
||||||
sessions.</p>
|
sessions.</p>
|
||||||
<p>The data you provide can optionally include your login credentials for QRZ.com and HamQTH. You can provide these
|
<p>The data you provide can optionally include your login credentials for QRZ.com and HamQTH. You can provide these
|
||||||
in the "Your Data" menu of most pages. If you do, Spothole will augment the data it produces with lookups from these
|
in the "Your Data" menu of most pages. If you do, Spothole will augment the data it produces with lookups from
|
||||||
|
these
|
||||||
services, which can for example provide more accurate markers on the map tab, and operator names when you mouse
|
services, which can for example provide more accurate markers on the map tab, and operator names when you mouse
|
||||||
over a DX callsign. Spothole will still work fine if you don't provide these. The values you enter are sent to
|
over a DX callsign. Spothole will still work fine if you don't provide these. The values you enter are sent to
|
||||||
Spothole via HTTPS so are protected in transit, though of course you do have to trust Spothole with this
|
Spothole via HTTPS so are protected in transit, though of course you do have to trust Spothole with this
|
||||||
sensitive data in order to use this feature.</p>
|
sensitive data in order to use this feature.</p>
|
||||||
<p>Any data you send as part of a query, such as your QRZ or HamQTH credentials, is used only for the lifetime of that
|
<p>Any data you send as part of a query, such as your QRZ or HamQTH credentials, is used only for the lifetime of
|
||||||
|
that
|
||||||
query and is not saved anywhere apart from your own device.</p>
|
query and is not saved anywhere apart from your own device.</p>
|
||||||
<p>Spothole uses no trackers, no ads, and no cookies.</p>
|
<p>Spothole uses no trackers, no ads, and no cookies.</p>
|
||||||
{% if len(web_ui_options["support_button_html"]) > 0 %}
|
{% if len(web_ui_options["support_button_html"]) > 0 %}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div id="add-spot-intro-box" class="permanently-dismissible-box mt-3">
|
<div id="add-spot-intro-box" class="permanently-dismissible-box mt-3">
|
||||||
<div class="alert alert-primary alert-dismissible fade show" role="alert"> <!-- TODO Remove when feature available -->
|
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||||
|
<!-- TODO Remove when feature available -->
|
||||||
<i class="fa-solid fa-circle-info"></i> <strong>Adding spots to Spothole</strong><br/>This page is implemented
|
<i class="fa-solid fa-circle-info"></i> <strong>Adding spots to Spothole</strong><br/>This page is implemented
|
||||||
as a proof of concept for adding spots to the Spothole system. Currently, spots added in this way are only
|
as a proof of concept for adding spots to the Spothole system. Currently, spots added in this way are only
|
||||||
visible within Spothole and are not sent "upstream" to DX clusters or xOTA spotting sites. The functionality
|
visible within Spothole and are not sent "upstream" to DX clusters or xOTA spotting sites. The functionality
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/add-spot.js?v=1788209684"></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>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/alerts.js?v=1788209684"></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>
|
||||||
|
|||||||
@@ -76,8 +76,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/spotsbandsandmap.js?v=1788209684"></script>
|
<script src="/static/js/spotsbandsandmap.js?v=1788338285"></script>
|
||||||
<script src="/static/js/bands.js?v=1788209684"></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>
|
||||||
|
|||||||
+7
-6
@@ -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=1788209684" 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">
|
||||||
@@ -11,14 +11,15 @@
|
|||||||
<script src="/static/vendor/js/tinycolor2-1.6.0.min.js"></script>
|
<script src="/static/vendor/js/tinycolor2-1.6.0.min.js"></script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { fetchEventSource } from '/static/vendor/js/fetch-event-source-2.0.1/index.js';
|
import {fetchEventSource} from '/static/vendor/js/fetch-event-source-2.0.1/index.js';
|
||||||
|
|
||||||
window.fetchEventSource = fetchEventSource;
|
window.fetchEventSource = fetchEventSource;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/static/js/utils.js?v=1788209684"></script>
|
<script src="/static/js/utils.js?v=1788338285"></script>
|
||||||
<script src="/static/js/ui-ham.js?v=1788209684"></script>
|
<script src="/static/js/ui-ham.js?v=1788338285"></script>
|
||||||
<script src="/static/js/geo.js?v=1788209684"></script>
|
<script src="/static/js/geo.js?v=1788338285"></script>
|
||||||
<script src="/static/js/common.js?v=1788209684"></script>
|
<script src="/static/js/common.js?v=1788338285"></script>
|
||||||
{% end %}
|
{% end %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -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=1788209684"></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
@@ -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=1788209683"></script>
|
<script src="/static/js/spotsbandsandmap.js?v=1788338286"></script>
|
||||||
<script src="/static/js/map.js?v=1788209683"></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>
|
||||||
|
|||||||
@@ -113,8 +113,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/spotsbandsandmap.js?v=1788209683"></script>
|
<script src="/static/js/spotsbandsandmap.js?v=1788338285"></script>
|
||||||
<script src="/static/js/spots.js?v=1788209683"></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>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/status.js?v=1788209684"></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");
|
||||||
|
|||||||
Reference in New Issue
Block a user