mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Icon lookup for SIGs in preparation for #47. Improved ZLOTA spotter lookup.
This commit is contained in:
@@ -42,21 +42,9 @@ class ParksNPeaks(HTTPAlertProvider):
|
||||
start_time=start_time,
|
||||
is_dxpedition=False)
|
||||
|
||||
# PNP supports a bunch of programs which should have different icons
|
||||
if alert.sig == "SiOTA":
|
||||
alert.icon = "wheat-awn"
|
||||
elif alert.sig == "ZLOTA":
|
||||
alert.icon = "kiwi-bird"
|
||||
elif alert.sig == "KRMNPA":
|
||||
alert.icon = "earth-oceania"
|
||||
elif alert.sig in ["POTA", "SOTA", "WWFF"]:
|
||||
# Don't care about an icon as this will be rejected anyway, we have better data from POTA/SOTA/WWFF direct
|
||||
alert.icon = ""
|
||||
else:
|
||||
# Unknown programme we've never seen before
|
||||
logging.warn(
|
||||
"PNP alert found with sig " + alert.sig + ", developer needs to add support for this and set an icon!")
|
||||
alert.icon = "question"
|
||||
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
|
||||
if alert.sig not in ["POTA", "SOTA", "WWFF", "SiOTA", "ZLOTA", "KRMNPA"]:
|
||||
logging.warn("PNP alert found with sig " + alert.sig + ", developer needs to add support for this!")
|
||||
|
||||
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to
|
||||
# the alert list.
|
||||
|
||||
@@ -27,7 +27,6 @@ class POTA(HTTPAlertProvider):
|
||||
sig="POTA",
|
||||
sig_refs=[source_alert["reference"]],
|
||||
sig_refs_names=[source_alert["name"]],
|
||||
icon="tree",
|
||||
start_time=datetime.strptime(source_alert["startDate"] + source_alert["startTime"],
|
||||
"%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
end_time=datetime.strptime(source_alert["endDate"] + source_alert["endTime"],
|
||||
|
||||
@@ -28,7 +28,6 @@ class SOTA(HTTPAlertProvider):
|
||||
sig="SOTA",
|
||||
sig_refs=[source_alert["associationCode"] + "/" + source_alert["summitCode"]],
|
||||
sig_refs_names=[source_alert["summitDetails"]],
|
||||
icon="mountain-sun",
|
||||
start_time=datetime.strptime(source_alert["dateActivated"],
|
||||
"%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
is_dxpedition=False)
|
||||
|
||||
@@ -26,7 +26,6 @@ class WWFF(HTTPAlertProvider):
|
||||
comment=source_alert["remarks"],
|
||||
sig="WWFF",
|
||||
sig_refs=[source_alert["reference"]],
|
||||
icon="seedling",
|
||||
start_time=datetime.strptime(source_alert["utc_start"],
|
||||
"%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
end_time=datetime.strptime(source_alert["utc_end"],
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
from core.config import SERVER_OWNER_CALLSIGN
|
||||
from data.band import Band
|
||||
from data.sig import SIG
|
||||
|
||||
# General software
|
||||
SOFTWARE_NAME = "Spothole by M0TRT"
|
||||
SOFTWARE_VERSION = "0.1"
|
||||
|
||||
# HTTP headers used for spot providers that use HTTP
|
||||
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + " " + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
||||
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + ", v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
||||
|
||||
# Special Interest Groups
|
||||
SIGS = ["POTA", "SOTA", "WWFF", "GMA", "WWBOTA", "HEMA", "MOTA", "ARLHS", "ILLW", "SiOTA", "WCA", "ZLOTA", "IOTA", "KRMNPA"]
|
||||
SIGS = [
|
||||
SIG(name="POTA", description="Parks on the Air", icon="tree"),
|
||||
SIG(name="SOTA", description="Summits on the Air", icon="mountain-sun"),
|
||||
SIG(name="WWFF", description="World Wide Flora & Fauna", icon="seedling"),
|
||||
SIG(name="GMA", description="Global Mountain Activity", icon="person-hiking"),
|
||||
SIG(name="WWBOTA", description="Worldwide Bunkers on the Air", icon="radiation"),
|
||||
SIG(name="HEMA", description="HuMPs Excluding Marilyns Award", icon="mound"),
|
||||
SIG(name="IOTA", description="Islands on the Air", icon="umbrella-beach"),
|
||||
SIG(name="MOTA", description="Mills on the Air", icon="fan"),
|
||||
SIG(name="ARLHS", description="Amateur Radio Lighthouse Society", icon="tower-observation"),
|
||||
SIG(name="ILLW", description="International Lighthouse & Lightship Weekend", icon="tower-observation"),
|
||||
SIG(name="SiOTA", description="Silos on the Air", icon="wheat-awn"),
|
||||
SIG(name="WCA", description="World Castles Award", icon="chess-rook"),
|
||||
SIG(name="ZLOTA", description="New Zealand on the Air", icon="kiwi-bird"),
|
||||
SIG(name="KRMNPA", description="Keith Roget Memorial National Parks Award", icon="earth-oceania")
|
||||
]
|
||||
|
||||
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
|
||||
CW_MODES = ["CW"]
|
||||
|
||||
8
core/utility_functions.py
Normal file
8
core/utility_functions.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from core.constants import SIGS
|
||||
|
||||
# Utility function to get the icon for a named SIG. If no match is found, the "circle-question" icon will be returned.
|
||||
def get_icon_for_sig(sig):
|
||||
for s in SIGS:
|
||||
if s.name == sig:
|
||||
return s.icon
|
||||
return "circle-question"
|
||||
@@ -9,6 +9,7 @@ import pytz
|
||||
|
||||
from core.constants import DXCC_FLAGS
|
||||
from core.lookup_helper import lookup_helper
|
||||
from core.utility_functions import get_icon_for_sig
|
||||
|
||||
|
||||
# Data class that defines an alert.
|
||||
@@ -59,7 +60,7 @@ class Alert:
|
||||
# Activation score. SOTA only
|
||||
activation_score: int = None
|
||||
# Icon, from the Font Awesome set. This is fairly opinionated but is here to help the alerthole web UI and Field alertter. Does not include the "fa-" prefix.
|
||||
icon: str = "question"
|
||||
icon: str = None
|
||||
# Whether this alert is for a DXpedition, as opposed to e.g. an xOTA programme.
|
||||
is_dxpedition: bool = False
|
||||
# Where we got the alert from, e.g. "POTA", "SOTA"...
|
||||
@@ -100,6 +101,10 @@ class Alert:
|
||||
if self.dx_dxcc_id and self.dx_dxcc_id in DXCC_FLAGS and not self.dx_flag:
|
||||
self.dx_flag = DXCC_FLAGS[self.dx_dxcc_id]
|
||||
|
||||
# Icon from SIG
|
||||
if self.sig and not self.icon:
|
||||
self.icon = get_icon_for_sig(self.sig)
|
||||
|
||||
# DX operator details lookup, using QRZ.com. This should be the last resort compared to taking the data from
|
||||
# the actual alertting service, e.g. we don't want to accidentally use a user's QRZ.com home lat/lon instead of
|
||||
# the one from the park reference they're at.
|
||||
|
||||
12
data/sig.py
Normal file
12
data/sig.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Data class that defines a Special Interest Group.
|
||||
@dataclass
|
||||
class SIG:
|
||||
# SIG name, e.g. "POTA"
|
||||
name: str
|
||||
# Description, e.g. "Parks on the Air"
|
||||
description: str
|
||||
# Icon to use for it, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI
|
||||
# and Field Spotter. Does not include the "fa-" prefix.
|
||||
icon: str
|
||||
14
data/spot.py
14
data/spot.py
@@ -11,6 +11,7 @@ from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
||||
|
||||
from core.constants import DXCC_FLAGS
|
||||
from core.lookup_helper import lookup_helper
|
||||
from core.utility_functions import get_icon_for_sig
|
||||
|
||||
|
||||
# Data class that defines a spot.
|
||||
@@ -113,7 +114,7 @@ class Spot:
|
||||
|
||||
# Icon, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI and Field
|
||||
# Spotter. Does not include the "fa-" prefix.
|
||||
icon: str = "question"
|
||||
icon: str = None
|
||||
# Colour to represent this spot, if a client chooses to colour spots based on their frequency band, using PSK
|
||||
# Reporter's default colours. HTML colour e.g. hex. A contrast colour is also provided which will be black or white.
|
||||
band_color: str = None
|
||||
@@ -218,6 +219,10 @@ class Spot:
|
||||
if self.mode and not self.mode_type:
|
||||
self.mode_type = lookup_helper.infer_mode_type_from_mode(self.mode)
|
||||
|
||||
# Icon from SIG
|
||||
if self.sig and not self.icon:
|
||||
self.icon = get_icon_for_sig(self.sig)
|
||||
|
||||
# DX Grid to lat/lon and vice versa
|
||||
if self.dx_grid and not self.dx_latitude:
|
||||
ll = locator_to_latlong(self.dx_grid)
|
||||
@@ -237,7 +242,8 @@ class Spot:
|
||||
|
||||
# Clean up comments
|
||||
if self.comment:
|
||||
comment = re.sub(r"\[.*]:", "", self.comment)
|
||||
comment = re.sub(r"\(de [A-Za-z0-9]*\)", "", self.comment)
|
||||
comment = re.sub(r"\[.*]:", "", comment)
|
||||
comment = re.sub(r"\[.*]", "", comment)
|
||||
comment = re.sub(r"\"\"", "", comment)
|
||||
self.comment = comment.strip()
|
||||
@@ -269,8 +275,8 @@ class Spot:
|
||||
self.dx_location_good = self.dx_location_source == "SPOT" or (
|
||||
self.dx_location_source == "QRZ" and not "/" in self.dx_call)
|
||||
|
||||
# DE of "RBNHOLE" and "SOTAMAT" are not things we can look up location for
|
||||
if self.de_call != "RBNHOLE" and self.de_call != "SOTAMAT":
|
||||
# DE of "RBNHOLE", "SOTAMAT" and "ZLOTA" are not things we can look up location for
|
||||
if self.de_call != "RBNHOLE" and self.de_call != "SOTAMAT" and self.de_call != "ZLOTA":
|
||||
# DE operator position lookup, using QRZ.com.
|
||||
if self.de_call and not self.de_latitude:
|
||||
latlon = lookup_helper.infer_latlon_from_callsign_qrz(self.de_call)
|
||||
|
||||
@@ -75,6 +75,8 @@ class DXCluster(SpotProvider):
|
||||
icon="desktop",
|
||||
time=spot_datetime.timestamp())
|
||||
|
||||
# See if the comment looks like it contains a SIG (and optionally SIG reference)
|
||||
|
||||
# Add to our list
|
||||
self.submit(spot)
|
||||
|
||||
|
||||
@@ -57,27 +57,20 @@ class GMA(HTTPSpotProvider):
|
||||
match ref_info["reftype"]:
|
||||
case "Summit":
|
||||
spot.sig = "GMA"
|
||||
spot.icon = "mountain"
|
||||
case "IOTA Island":
|
||||
spot.sig = "IOTA"
|
||||
spot.icon = "umbrella-beach"
|
||||
case "Lighthouse (ILLW)":
|
||||
spot.sig = "ILLW"
|
||||
spot.icon = "tower-observation"
|
||||
case "Lighthouse (ARLHS)":
|
||||
spot.sig = "ARLHS"
|
||||
spot.icon = "tower-observation"
|
||||
case "Castle":
|
||||
spot.sig = "WCA/COTA"
|
||||
spot.icon = "chess-rook"
|
||||
spot.sig = "WCA"
|
||||
case "Mill":
|
||||
spot.sig = "MOTA"
|
||||
spot.icon = "fan"
|
||||
case _:
|
||||
logging.warn("GMA spot found with ref type " + ref_info[
|
||||
"reftype"] + ", developer needs to figure out an icon for this!")
|
||||
"reftype"] + ", developer needs to add support for this!")
|
||||
spot.sig = ref_info["reftype"]
|
||||
spot.icon = "person-hiking"
|
||||
|
||||
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
|
||||
# that for us.
|
||||
|
||||
@@ -54,7 +54,6 @@ class HEMA(HTTPSpotProvider):
|
||||
sig="HEMA",
|
||||
sig_refs=[spot_items[3].upper()],
|
||||
sig_refs_names=[spot_items[4]],
|
||||
icon="mound",
|
||||
time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
dx_latitude=float(spot_items[7]),
|
||||
dx_longitude=float(spot_items[8]))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import csv
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
@@ -32,7 +33,7 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
spot = Spot(source=self.name,
|
||||
source_id=source_spot["actID"],
|
||||
dx_call=source_spot["actCallsign"].upper(),
|
||||
de_call=source_spot["actSpoter"].upper(), # typo exists in API
|
||||
de_call=source_spot["actSpoter"].upper() if source_spot["actSpoter"] != "" else None, # typo exists in API
|
||||
freq=float(source_spot["actFreq"].replace(",", "")) * 1000000 if (
|
||||
source_spot["actFreq"] != "") else None,
|
||||
# Seen PNP spots with empty frequency, and with comma-separated thousands digits
|
||||
@@ -47,21 +48,14 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
if "actLocation" in source_spot and source_spot["actLocation"] != "":
|
||||
spot.sig_refs_names = [source_spot["actLocation"]]
|
||||
|
||||
# PNP supports a bunch of programs which should have different icons
|
||||
if spot.sig == "SiOTA":
|
||||
spot.icon = "wheat-awn"
|
||||
elif spot.sig == "ZLOTA":
|
||||
spot.icon = "kiwi-bird"
|
||||
elif spot.sig == "KRMNPA":
|
||||
spot.icon = "earth-oceania"
|
||||
elif spot.sig in ["POTA", "SOTA", "WWFF"]:
|
||||
# Don't care about an icon as this will be rejected anyway, we have better data from POTA/SOTA/WWFF direct
|
||||
spot.icon = ""
|
||||
else:
|
||||
# Unknown programme we've never seen before
|
||||
logging.warn(
|
||||
"PNP spot found with sig " + spot.sig + ", developer needs to add support for icon and grid/lat/lon lookup!")
|
||||
spot.icon = "question"
|
||||
# Extract a de_call if it's in the comment but not in the "actSpoter" field
|
||||
m = re.search(r"\(de ([A-Za-z0-9]*)\)", spot.comment)
|
||||
if (not spot.de_call or spot.de_call == "ZLOTA") and m is not None:
|
||||
spot.de_call = m.group(1)
|
||||
|
||||
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
|
||||
if spot.sig not in ["POTA", "SOTA", "WWFF", "SiOTA", "ZLOTA", "KRMNPA"]:
|
||||
logging.warn("PNP spot found with sig " + spot.sig + ", developer needs to add support for this!")
|
||||
|
||||
# SiOTA lat/lon/grid lookup
|
||||
if spot.sig == "SiOTA":
|
||||
@@ -82,8 +76,6 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
spot.sig_refs_names = [asset["name"]]
|
||||
spot.dx_latitude = asset["y"]
|
||||
spot.dx_longitude = asset["x"]
|
||||
# Junk the "DE call", PNP always returns "ZLOTA" as the spotter for ZLOTA spots
|
||||
spot.de_call = None
|
||||
break
|
||||
|
||||
# Note there is currently no support for KRMNPA location lookup, see issue #61.
|
||||
|
||||
@@ -30,7 +30,6 @@ class POTA(HTTPSpotProvider):
|
||||
sig_refs=[source_spot["reference"]],
|
||||
sig_refs_names=[source_spot["name"]],
|
||||
sig_refs_urls=["https://pota.app/#/park/" + source_spot["reference"]],
|
||||
icon="tree",
|
||||
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
dx_grid=source_spot["grid6"],
|
||||
dx_latitude=source_spot["latitude"],
|
||||
|
||||
@@ -51,7 +51,6 @@ class SOTA(HTTPSpotProvider):
|
||||
sig_refs=[source_spot["summitCode"]],
|
||||
sig_refs_names=[source_spot["summitName"]],
|
||||
sig_refs_urls=["https://www.sotadata.org.uk/en/summit/" + source_spot["summitCode"]],
|
||||
icon="mountain-sun",
|
||||
time=datetime.fromisoformat(source_spot["timeStamp"]).timestamp(),
|
||||
activation_score=source_spot["points"])
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ class WWBOTA(SSESpotProvider):
|
||||
sig="WWBOTA",
|
||||
sig_refs=refs,
|
||||
sig_refs_names=ref_names,
|
||||
icon="radiation",
|
||||
time=datetime.fromisoformat(source_spot["time"]).timestamp(),
|
||||
# WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For
|
||||
# now, we will just pick the first one to use as our grid, latitude and longitude.
|
||||
|
||||
@@ -30,7 +30,6 @@ class WWFF(HTTPSpotProvider):
|
||||
sig_refs=[source_spot["reference"]],
|
||||
sig_refs_names=[source_spot["reference_name"]],
|
||||
sig_refs_urls=["https://wwff.co/directory/?showRef=" + source_spot["reference"]],
|
||||
icon="seedling",
|
||||
time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(),
|
||||
dx_latitude=source_spot["latitude"],
|
||||
dx_longitude=source_spot["longitude"])
|
||||
|
||||
@@ -413,8 +413,7 @@ paths:
|
||||
type: array
|
||||
description: An array of all the supported Special Interest Groups.
|
||||
items:
|
||||
type: string
|
||||
example: "POTA"
|
||||
$ref: '#/components/schemas/SIG'
|
||||
sources:
|
||||
type: array
|
||||
description: An array of all the supported data sources.
|
||||
@@ -976,3 +975,19 @@ components:
|
||||
type: string
|
||||
description: Black or white, whichever provides the best contrast against the band colour.
|
||||
example: white
|
||||
|
||||
SIG:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The abbreviated name of the SIG
|
||||
example: POTA
|
||||
description:
|
||||
type: string
|
||||
description: The full name of the SIG
|
||||
example: Parks on the Air
|
||||
icon:
|
||||
type: string
|
||||
description: Icon, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI and Field Spotter. Does not include the "fa-" prefix.
|
||||
example: tree
|
||||
Reference in New Issue
Block a user