ZLOTA support + misc changes

This commit is contained in:
Ian Renton
2025-10-09 21:25:01 +01:00
parent 60bb640074
commit a866d41aa7
6 changed files with 54 additions and 16 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ __pycache__
/gma_ref_info_cache.sqlite
/config.yml
/siota_data_cache.sqlite
/zlota_data_cache.sqlite

View File

@@ -1,5 +1,4 @@
import logging
from datetime import datetime
from diskcache import Cache
from pyhamtools import LookupLib, Callinfo
@@ -48,35 +47,45 @@ def infer_band_from_freq(freq):
# Infer a country name from a callsign
def infer_country_from_callsign(call):
try:
return CALL_INFO_BASIC.get_country_name(call)
# Get base callsign, assuming this will be the longest of any /-separated sections.
base_call = max(call.split("/"), key=len)
return CALL_INFO_BASIC.get_country_name(base_call)
except KeyError as e:
return None
# Infer a DXCC ID from a callsign
def infer_dxcc_id_from_callsign(call):
try:
return CALL_INFO_BASIC.get_adif_id(call)
# Get base callsign, assuming this will be the longest of any /-separated sections.
base_call = max(call.split("/"), key=len)
return CALL_INFO_BASIC.get_adif_id(base_call)
except KeyError as e:
return None
# Infer a continent shortcode from a callsign
def infer_continent_from_callsign(call):
try:
return CALL_INFO_BASIC.get_continent(call)
# Get base callsign, assuming this will be the longest of any /-separated sections.
base_call = max(call.split("/"), key=len)
return CALL_INFO_BASIC.get_continent(base_call)
except KeyError as e:
return None
# Infer a CQ zone from a callsign
def infer_cq_zone_from_callsign(call):
try:
return CALL_INFO_BASIC.get_cqz(call)
# Get base callsign, assuming this will be the longest of any /-separated sections.
base_call = max(call.split("/"), key=len)
return CALL_INFO_BASIC.get_cqz(base_call)
except KeyError as e:
return None
# Infer a ITU zone from a callsign
def infer_itu_zone_from_callsign(call):
try:
return CALL_INFO_BASIC.get_ituz(call)
# Get base callsign, assuming this will be the longest of any /-separated sections.
base_call = max(call.split("/"), key=len)
return CALL_INFO_BASIC.get_ituz(base_call)
except KeyError as e:
return None

View File

@@ -3,7 +3,6 @@ import logging
from datetime import datetime, timedelta
import pytz
import requests
from requests_cache import CachedSession
from data.spot import Spot
@@ -14,9 +13,12 @@ from spotproviders.http_spot_provider import HTTPSpotProvider
class ParksNPeaks(HTTPSpotProvider):
POLL_INTERVAL_SEC = 120
SPOTS_URL = "https://www.parksnpeaks.org/api/ALL"
SIOTA_CSV_URL = "https://www.silosontheair.com/data/silos.csv"
SIOTA_CSV_CACHE_TIME_DAYS = 30
SIOTA_CSV_CACHE = CachedSession("siota_data_cache", expire_after=timedelta(days=SIOTA_CSV_CACHE_TIME_DAYS))
SIOTA_LIST_URL = "https://www.silosontheair.com/data/silos.csv"
SIOTA_LIST_CACHE_TIME_DAYS = 30
SIOTA_LIST_CACHE = CachedSession("siota_data_cache", expire_after=timedelta(days=SIOTA_LIST_CACHE_TIME_DAYS))
ZLOTA_LIST_URL = "https://ontheair.nz/assets/assets.json"
ZLOTA_LIST_CACHE_TIME_DAYS = 30
ZLOTA_LIST_CACHE = CachedSession("zlota_data_cache", expire_after=timedelta(days=ZLOTA_LIST_CACHE_TIME_DAYS))
def __init__(self, provider_config):
super().__init__(provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
@@ -43,6 +45,8 @@ class ParksNPeaks(HTTPSpotProvider):
# 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 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 = ""
@@ -54,16 +58,27 @@ class ParksNPeaks(HTTPSpotProvider):
# SiOTA lat/lon/grid lookup
if spot.sig == "SiOTA":
siota_csv_data = self.SIOTA_CSV_CACHE.get(self.SIOTA_CSV_URL, headers=self.HTTP_HEADERS)
siota_csv_data = self.SIOTA_LIST_CACHE.get(self.SIOTA_LIST_URL, headers=self.HTTP_HEADERS)
siota_dr = csv.DictReader(siota_csv_data.content.decode().splitlines())
for row in siota_dr:
if row["SILO_CODE"] == spot.sig_refs[0]:
spot.dx_country = row["COUNTRY"]
spot.latitude = float(row["LAT"])
spot.longitude = float(row["LON"])
spot.grid = row["LOCATOR"]
break
# ZLOTA name/lat/lon lookup
if spot.sig == "ZLOTA":
zlota_data = self.ZLOTA_LIST_CACHE.get(self.ZLOTA_LIST_URL, headers=self.HTTP_HEADERS).json()
for asset in zlota_data:
if asset["code"] == spot.sig_refs[0]:
spot.sig_refs_names = [asset["name"]]
spot.latitude = asset["y"]
spot.longitude = asset["x"]
# Junk the "DE call", PNP always returns "ZLOTA" as the spotter for ZLOTA spots
spot.de_call = None
break
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to
# the spot list.
if spot.sig not in ["POTA", "SOTA", "WWFF"]:

View File

@@ -7,7 +7,7 @@
</div>
<div class="col-auto">
<p class="d-inline-flex gap-1">
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button">Filters</button>
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button"><i class="fa-solid fa-filter"></i> Filters</button>
</p>
</div>
</div>

View File

@@ -22,7 +22,7 @@
</div>
<div class="col-auto">
<p class="d-inline-flex gap-1">
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button">Filters</button>
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button"><i class="fa-solid fa-filter"></i> Filters</button>
</p>
</div>
</div>

View File

@@ -111,6 +111,12 @@ function updateTable() {
sig_refs = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`).join(", ");
}
// Format sig_refs title
var sig_refs_title_string = "";
if (s["sig_refs_names"]) {
sig_refs_title_string = " title=\"" + s["sig_refs_names"].join(", ") + "\"";
}
// Format DE flag
var de_flag = "<i class='fa-solid fa-circle-question'></i>";
if (s["de_flag"] && s["de_flag"] != null && s["de_flag"] != "") {
@@ -123,6 +129,13 @@ function updateTable() {
de_country = "Unknown or not a country";
}
// Format de call
var de_call = s["de_call"];
if (de_call == null) {
de_call = "";
de_flag = "";
}
// CSS doesn't like classes with decimal points in, so we need to replace that in the same way as when we originally
// queried the options endpoint and set our CSS.
var cssFormattedBandName = s['band'] ? s['band'].replace('.', 'p') : "unknown";
@@ -135,8 +148,8 @@ function updateTable() {
$tr.append(`<td class='nowrap'>${mode_string}</td>`);
$tr.append(`<td class='hideonmobile'>${commentText}</td>`);
$tr.append(`<td class='nowrap hideonmobile'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${sigSourceText}</td>`);
$tr.append(`<td class='hideonmobile'>${sig_refs}</td>`);
$tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${s["de_call"]}</td>`);
$tr.append(`<td class='hideonmobile'><span ${sig_refs_title_string}>${sig_refs}</span></td>`);
$tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${de_call}</td>`);
table.find('tbody').append($tr);
// Second row for mobile view only, containing source, ref & comment