From 760077b0812a34a5fc8c6feed796486daad93390 Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Fri, 17 Oct 2025 12:22:16 +0100 Subject: [PATCH] Provide links for SIG refs --- data/spot.py | 2 ++ spotproviders/gma.py | 1 + spotproviders/pota.py | 1 + spotproviders/sota.py | 1 + spotproviders/wwbota.py | 7 +++++++ spotproviders/wwff.py | 1 + webassets/apidocs/openapi.yml | 6 ++++++ webassets/css/style.css | 15 ++++++++------- webassets/js/map.js | 21 +++++++++++++++------ webassets/js/spots.js | 10 ++++++++-- 10 files changed, 50 insertions(+), 15 deletions(-) diff --git a/data/spot.py b/data/spot.py index eae7187..07cc16a 100644 --- a/data/spot.py +++ b/data/spot.py @@ -102,6 +102,8 @@ class Spot: sig_refs: list = None # SIG reference names sig_refs_names: list = None + # SIG reference URLs + sig_refs_urls: list = None # Activation score. SOTA only activation_score: int = None diff --git a/spotproviders/gma.py b/spotproviders/gma.py index f306972..c744127 100644 --- a/spotproviders/gma.py +++ b/spotproviders/gma.py @@ -36,6 +36,7 @@ class GMA(HTTPSpotProvider): comment=source_spot["TEXT"], sig_refs=[source_spot["REF"]], sig_refs_names=[source_spot["NAME"]], + sig_refs_urls=["https://www.cqgma.org/zinfo.php?ref=" + source_spot["REF"]], time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace( tzinfo=pytz.UTC).timestamp(), dx_latitude=float(source_spot["LAT"]) if (source_spot["LAT"] and source_spot["LAT"] != "") else None, diff --git a/spotproviders/pota.py b/spotproviders/pota.py index ac2a90a..4a07c28 100644 --- a/spotproviders/pota.py +++ b/spotproviders/pota.py @@ -29,6 +29,7 @@ class POTA(HTTPSpotProvider): sig="POTA", 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"], diff --git a/spotproviders/sota.py b/spotproviders/sota.py index 104b75d..ae6573d 100644 --- a/spotproviders/sota.py +++ b/spotproviders/sota.py @@ -50,6 +50,7 @@ class SOTA(HTTPSpotProvider): sig="SOTA", 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"]) diff --git a/spotproviders/wwbota.py b/spotproviders/wwbota.py index 4906089..bf73da0 100644 --- a/spotproviders/wwbota.py +++ b/spotproviders/wwbota.py @@ -18,9 +18,16 @@ class WWBOTA(SSESpotProvider): # n-fer activations. refs = [] ref_names = [] + ref_urls = [] for ref in source_spot["references"]: refs.append(ref["reference"]) ref_names.append(ref["name"]) + # Bunkerbase URLs only work for UK bunkers, so only add a URL if we have a B/G prefix. In theory this could + # lead to array alignment mismatches if there was e.g. a B/F bunker followed by a B/G one, we'd end up with + # the B/G URL in index 0. But in practice there are no overlaps between B/G bunkers and any others, so an + # activation will either be entirely B/G or not B/G at all. + if ref["reference"].startswith("B/G"): + ref_urls.append("https://bunkerwiki.org/?s=" + ref["reference"]) spot = Spot(source=self.name, dx_call=source_spot["call"].upper(), diff --git a/spotproviders/wwff.py b/spotproviders/wwff.py index 4b089f7..60b8cb5 100644 --- a/spotproviders/wwff.py +++ b/spotproviders/wwff.py @@ -29,6 +29,7 @@ class WWFF(HTTPSpotProvider): sig="WWFF", 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"], diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index a14e8e6..7d8d910 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -680,6 +680,12 @@ components: type: string description: SIG reference names example: Null Country Park + sig_refs_urls: + type: array + items: + type: string + description: SIG reference URLs, which the user can look up for more information + example: "https://pota.app/#/park/GB-0001" activation_score: type: integer description: Activation score. SOTA only diff --git a/webassets/css/style.css b/webassets/css/style.css index b993e2c..b827ee3 100644 --- a/webassets/css/style.css +++ b/webassets/css/style.css @@ -92,10 +92,13 @@ span.icon-wrapper { } span.freq-mhz { + font-weight: bold; +} + +span.freq-mhz-pad { display: inline-block; min-width: 1.7em; text-align: right; - font-weight: bold; } span.freq-khz { @@ -117,6 +120,10 @@ a.dx-link { text-decoration: none; font-weight: bold; } +a.sig-ref-link { + color: var(--bs-emphasis-color); + text-decoration: none; +} /* QRT/faded styles */ tr.table-faded td { @@ -142,12 +149,6 @@ div#map { font-family: var(--bs-body-font-family) !important; } -a.leaflet-popup-callsign-link { - color: black; - font-weight: bold; - text-decoration: none; -} - /* GENERAL MOBILE SUPPORT */ diff --git a/webassets/js/map.js b/webassets/js/map.js index f8f8cae..41463c7 100644 --- a/webassets/js/map.js +++ b/webassets/js/map.js @@ -100,7 +100,13 @@ function getTooltipText(s) { // Format sig_refs var sig_refs = ""; - if (s["sig_refs"]) { + if (s["sig_refs"] && s["sig_refs_urls"] && s["sig_refs"].length == s["sig_refs_urls"].length) { + items = s["sig_refs"].map(s => `${s}`) + for (var i = 0; i < items.length; i++) { + items[i] = `${items[i]}` + } + sig_refs = items.join(", "); + } else if (s["sig_refs"]) { sig_refs = s["sig_refs"].map(s => `${s}`).join(", "); } @@ -108,10 +114,13 @@ function getTooltipText(s) { const shortCall = s["dx_call"].split("/").sort(function (a, b) { return b.length - a.length; })[0]; - ttt = `${dx_flag} ${s["dx_call"]}
`; + ttt = `${dx_flag} ${s["dx_call"]}
`; // Frequency & band - ttt += ` ${freq_string} (${s["band"]})`; + ttt += ` ${freq_string}`; + if (s["band"] != null) { + ttt += ` (${s["band"]})`; + } // Mode if (s["mode"] != null) { ttt += `     ${s["mode"]}`; @@ -119,14 +128,14 @@ function getTooltipText(s) { ttt += "
"; // Source / SIG / Ref - ttt += ` ${sigSourceText} ${sig_refs}
`; + ttt += ` ${sigSourceText} ${sig_refs}
`; // Time - ttt += ` ${moment.unix(s["time"]).fromNow()}`; + ttt += ` ${moment.unix(s["time"]).fromNow()}`; // Comment if (commentText.length > 0) { - ttt += `
${commentText}`; + ttt += `
${commentText}`; } return ttt; diff --git a/webassets/js/spots.js b/webassets/js/spots.js index 0e18287..4c5dabb 100644 --- a/webassets/js/spots.js +++ b/webassets/js/spots.js @@ -109,7 +109,7 @@ function updateTable() { var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0); var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0)); var hz_string = (hz > 0) ? hz.toFixed(0)[0] : ""; - var freq_string = `${mhz.toFixed(0)}${khz.toFixed(0).padStart(3, '0')}${hz_string}` + var freq_string = `${mhz.toFixed(0)}${khz.toFixed(0).padStart(3, '0')}${hz_string}` // Format the mode mode_string = s["mode"]; @@ -148,7 +148,13 @@ function updateTable() { // Format sig_refs var sig_refs = ""; - if (s["sig_refs"]) { + if (s["sig_refs"] && s["sig_refs_urls"] && s["sig_refs"].length == s["sig_refs_urls"].length) { + items = s["sig_refs"].map(s => `${s}`) + for (var i = 0; i < items.length; i++) { + items[i] = `${items[i]}` + } + sig_refs = items.join(", "); + } else if (s["sig_refs"]) { sig_refs = s["sig_refs"].map(s => `${s}`).join(", "); }