SANPCPA support. Closes #120

This commit is contained in:
Ian Renton
2026-08-08 13:45:02 +01:00
parent 605b172bb3
commit 43763bf76f
11 changed files with 95 additions and 17 deletions
+2 -1
View File
@@ -19,7 +19,8 @@ Supported data sources include DX Clusters, the Reverse Beacon Network (RBN), th
SOTA, WWFF, GMA, WWBOTA, HEMA, Parks 'n' Peaks, ZLOTA, WOTA, BOTA, LLOTA, WWTOTA, Tiles on the Air, the UK Packet
Repeater Network, NG3K, and any site based on the xOTA software by nischu.
Additional Special Interest Groups (SIGs) without their own specific data source include WAB, WAI and DME.
Additional Special Interest Groups (SIGs) without their own specific data source include KRMNPA, SANPCPA, WAB, WAI and
DME.
![Screenshot](/images/screenshot2.png)
+6
View File
@@ -246,6 +246,12 @@ sig-ref-data-providers:
- class: "DME"
enabled: true
- class: "KRMNPA"
enabled: true
- class: "SANPCPA"
enabled: true
- class: "Toilets"
enabled: true
+2 -9
View File
@@ -27,13 +27,6 @@ def get_sig_ref_info(sig, ref_id):
if sig.upper() == "DME":
ref_id = ref_id.zfill(5)
# KRMNPA & SANPCPA fudge. These don't have their own reference system, they just use VKFF references, so pretend
# the sig is WWFF and carry on
if sig.upper() == "KRMNPA" or sig.upper() == "SANPCPA":
sig = "WWFF"
### SKIPS ###
#
# If the SIG is HEMA, we have no current lookup for this so just skip the lookup here.
if sig.upper() == "HEMA":
return sig_ref
@@ -75,8 +68,8 @@ def get_sig_ref_info(sig, ref_id):
### ACTUAL LOOKUP ###
#
# OK, this is something we have to look up. Now check to see if our data store contains SIG ref information for
# this SIG. If so, check for the reference data and use that.
# OK, this is something we have to look up. Now check to see if our data store contains reference data and use
# that.
key = sig + ":" + ref_id
lookup_data = DATA_STORE.sigrefs.get(key) if key in DATA_STORE.sigrefs else None
if lookup_data:
+1 -1
View File
@@ -50,7 +50,7 @@ class ParksNPeaks(HTTPAlertProvider):
is_dxpedition=False)
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if sig and sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "LLOTA", "QRP"]:
if sig and sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA", "QRP"]:
logging.warning("PNP alert found with sig " + 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
+12
View File
@@ -0,0 +1,12 @@
from providers.sigrefdata.pnp_kml_sig_ref_data_provider import ParksNPeaksKMLSIGRefDataProvider
class KRMNPA(ParksNPeaksKMLSIGRefDataProvider):
"""SIG ref data provider for the Keith Roget Memorrial National Parks Award (KRMNPA)."""
POLL_INTERVAL_DAYS = 365
SIG = "KRMNPA"
DATA_URL = "https://parksnpeaks.org/getPOI.php?poiClass=KRMNPA&poiFormat=4"
def __init__(self, provider_config):
super().__init__(self.SIG, provider_config, self.DATA_URL, self.POLL_INTERVAL_DAYS)
@@ -0,0 +1,50 @@
import re
from fastkml import kml
from pyhamtools.locator import latlong_to_locator
from data.sig_ref import SIGRef
from providers.sigrefdata.file_download_sig_ref_data_provider import FileDownloadSIGRefDataProvider
class ParksNPeaksKMLSIGRefDataProvider(FileDownloadSIGRefDataProvider):
"""Base class for SIG ref data providers that use parksnpeaks.org KML POI feeds and have references that use the
VKFF refs rather than their own system (i.e. KRMNPA and SANPCPA)."""
REF_PATTERN = re.compile(r"VKFF-\d+")
def __init__(self, sig_name, provider_config, url, poll_interval):
""" Set up the provider, note poll_interval is in *days*."""
super().__init__(sig_name, provider_config, url, poll_interval)
def _http_response_to_data(self, http_response):
new_data = []
k = kml.KML.from_string(http_response.content)
for document in k.features:
for folder in document.features:
for placemark in folder.features:
description = placemark.description or ""
match = self.REF_PATTERN.search(description)
if not match:
# No VKFF reference found in this placemark - skip it (e.g. non-park waypoints)
continue
ref_id = match.group(0)
longitude, latitude = placemark.geometry.x, placemark.geometry.y
ref = SIGRef(sig=self.sig_name, id=ref_id, name=placemark.name,
url="https://parksnpeaks.org/getPark.php?actPark=" + ref_id,
latitude=latitude,
longitude=longitude)
if latitude and longitude:
ref.grid = latlong_to_locator(latitude, longitude, 6)
new_data.append(ref)
# 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
if self._stop_event.is_set():
return new_data
return new_data
+12
View File
@@ -0,0 +1,12 @@
from providers.sigrefdata.pnp_kml_sig_ref_data_provider import ParksNPeaksKMLSIGRefDataProvider
class SANPCPA(ParksNPeaksKMLSIGRefDataProvider):
"""SIG ref data provider for the South Australia National Parks and Conservation Parks Award (SANPCPA)."""
POLL_INTERVAL_DAYS = 365
SIG = "SANPCPA"
DATA_URL = "https://parksnpeaks.org/getPOI.php?poiClass=SANPCPA&poiFormat=4"
def __init__(self, provider_config):
super().__init__(self.SIG, provider_config, self.DATA_URL, self.POLL_INTERVAL_DAYS)
+2 -2
View File
@@ -18,7 +18,7 @@ class ParksNPeaks(HTTPSpotProvider):
SPOTS_URL = "https://www.parksnpeaks.org/api/ALL"
SUBMIT_URL = "https://www.parksnpeaks.org/api/SPOT/"
SIOTA_LIST_URL = "https://www.silosontheair.com/data/silos.csv"
SUBMITTABLE_SIGS = ["POTA", "SOTA", "WWFF", "HEMA", "WOTA", "ZLOTA", "SIOTA", "KRMNPA"]
SUBMITTABLE_SIGS = ["POTA", "SOTA", "WWFF", "HEMA", "WOTA", "ZLOTA", "SIOTA", "KRMNPA", "SANPCPA"]
def __init__(self, provider_config):
super().__init__("ParksNPeaks", provider_config, self.SPOTS_URL, self.POLL_INTERVAL_SEC)
@@ -61,7 +61,7 @@ class ParksNPeaks(HTTPSpotProvider):
sig_refs[0].name = source_spot["actLocation"]
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "LLOTA"]:
if sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA"]:
logging.warning("PNP spot found with sig " + sig + ", developer needs to add support for this!")
# Add new spot to the list
+2 -1
View File
@@ -18,4 +18,5 @@ tornado~=6.4.2
tornado_eventsource~=3.0.0
geopandas~=0.13.2
simplejson~=4.1.1
cachetools~=7.1.6
cachetools~=7.1.6
fastkml~=1.4.0
+2
View File
@@ -357,6 +357,7 @@ const SIG_ICONS = {
"WOTA": "fa-w",
"BOTA": "fa-umbrella-beach",
"KRMNPA": "fa-earth-oceania",
"SANPCPA": "fa-earth-oceania",
"LLOTA": "fa-water",
"Towers": "fa-tower-observation",
"WAB": "fa-table-cells-large",
@@ -383,6 +384,7 @@ const SIG_NAMES = {
"WOTA": "Wainwrights on the Air",
"BOTA": "Beaches on the Air",
"KRMNPA": "Keith Roget Memorial National Parks Award",
"SANPCPA": "South Australia National Parks and Conservation Parks Award",
"LLOTA": "Lagos y Lagunas on the Air",
"WWTOTA": "Towers on the Air",
"WAB": "Worked All Britain",
+4 -3
View File
@@ -100,9 +100,10 @@
Bunkers on the Air (WWBOTA), HuMPs Excluding Marilyns Award (HEMA), Islands on the Air (IOTA), Mills on the Air
(MOTA), the Amateur Radio Lighthouse Socirty (ARLHS), International Lighthouse Lightship Weekend (ILLW), Silos
on the Air (SIOTA), World Castles Award (WCA), New Zealand on the Air (ZLOTA), Keith Roget Memorial National
Parks Award (KRMNPA), Wainwrights on the Air (WOTA), Beaches on the Air (BOTA), Lagos y Lagunas On the Air
(LLOTA), Towers on the Air (WWTOTA), Tiles on the Air, Worked All Britain (WAB), Worked All Ireland (WAI), el
Diploma Municipios de España (DME) and Toilets on the Air (TOTA).</p>
Parks Award (KRMNPA), South Australia National Parks and Conservation Parks Award (SANPCPA), Wainwrights on the
Air (WOTA), Beaches on the Air (BOTA), Lagos y Lagunas On the Air (LLOTA), Towers on the Air (WWTOTA), Tiles on
the Air, Worked All Britain (WAB), Worked All Ireland (WAI), el Diploma Municipios de España (DME) and Toilets
on the Air (TOTA).</p>
<p>As of the time of writing in June 2026, I think Spothole captures essentially all outdoor radio programmes
that have a defined reference list, and almost certainly those that have a spotting/alerting API. If you know of
one I've missed, please let me know!</p>