This commit is contained in:
Ian Renton
2025-10-02 12:17:42 +01:00
parent 99d60cced0
commit 09082b5d62
16 changed files with 39 additions and 7 deletions

View File

@@ -31,3 +31,11 @@ If you're extending the `HTTPProvider` class, you will need to provide a URI to
When constructing spots, use the comments in the Spot class and the existing implementations as an example. All parameters are optional, but you will at least want to provide a `time` (which must be timezone-aware) and a `dx_call`. When constructing spots, use the comments in the Spot class and the existing implementations as an example. All parameters are optional, but you will at least want to provide a `time` (which must be timezone-aware) and a `dx_call`.
Finally, simply add the appropriate config to the `providers` section of `config.yml`, and your provider should be instantiated on startup. Finally, simply add the appropriate config to the `providers` section of `config.yml`, and your provider should be instantiated on startup.
### Third Party Libraries
The project contains a self-hosted copy of Font Awesome's free library, in the `/fa/` directory. This is subject to Font Awesome's licence and is not covered by the overall licence declared in the `LICENSE` file. This approach was taken in preference to using their hosted kits due to the popularity of this project exceeding the page view limit for their free hosted offering.
TODO JS & Python libs...
This project would not have been possible without these libraries, so many thanks to their developers.

View File

@@ -73,6 +73,8 @@ class Spot:
sig_refs_names: list = None sig_refs_names: list = None
# Activation score. SOTA only # Activation score. SOTA only
activation_score: int = None activation_score: int = None
# 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"
# Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country # Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country
grid: str = None grid: str = None
# Latitude & longitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup # Latitude & longitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup

View File

@@ -47,6 +47,7 @@ class APRSIS(Provider):
comment=data["comment"] if "comment" in data else None, comment=data["comment"] if "comment" in data else None,
latitude=data["latitude"] if "latitude" in data else None, latitude=data["latitude"] if "latitude" in data else None,
longitude=data["longitude"] if "longitude" in data else None, longitude=data["longitude"] if "longitude" in data else None,
icon="tower-cell",
time=datetime.now(pytz.UTC)) # APRS-IS spots are live so we can assume spot time is "now" time=datetime.now(pytz.UTC)) # APRS-IS spots are live so we can assume spot time is "now"
# Add to our list # Add to our list

View File

@@ -69,6 +69,7 @@ class DXCluster(Provider):
de_call=match.group(1), de_call=match.group(1),
freq=float(match.group(2)), freq=float(match.group(2)),
comment=match.group(4).strip(), comment=match.group(4).strip(),
icon="desktop",
time=spot_datetime) time=spot_datetime)
# Add to our list # Add to our list

View File

@@ -32,6 +32,7 @@ class GMA(HTTPProvider):
comment=source_spot["TEXT"], comment=source_spot["TEXT"],
sig_refs=[source_spot["REF"]], sig_refs=[source_spot["REF"]],
sig_refs_names=[source_spot["NAME"]], sig_refs_names=[source_spot["NAME"]],
icon="person-hiking",
time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(tzinfo=pytz.UTC), time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(tzinfo=pytz.UTC),
latitude=float(source_spot["LAT"]) if (source_spot["LAT"] != "") else None, # Seen GMA spots with no lat/lon latitude=float(source_spot["LAT"]) if (source_spot["LAT"] != "") else None, # Seen GMA spots with no lat/lon
longitude=float(source_spot["LON"]) if (source_spot["LON"] != "") else None) longitude=float(source_spot["LON"]) if (source_spot["LON"] != "") else None)

View File

@@ -54,6 +54,7 @@ class HEMA(HTTPProvider):
sig="HEMA", sig="HEMA",
sig_refs=[spot_items[3].upper()], sig_refs=[spot_items[3].upper()],
sig_refs_names=[spot_items[4]], sig_refs_names=[spot_items[4]],
icon="person-hiking",
time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M").replace(tzinfo=pytz.UTC), time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M").replace(tzinfo=pytz.UTC),
latitude=float(spot_items[7]), latitude=float(spot_items[7]),
longitude=float(spot_items[8])) longitude=float(spot_items[8]))

View File

@@ -29,6 +29,7 @@ class ParksNPeaks(HTTPProvider):
comment=source_spot["actComments"], comment=source_spot["actComments"],
sig=source_spot["actClass"], sig=source_spot["actClass"],
sig_refs=[source_spot["actSiteID"]], sig_refs=[source_spot["actSiteID"]],
icon="question", # todo determine from actClass
time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC)) time=datetime.strptime(source_spot["actTime"], "%Y-%m-%d %H:%M:%S").replace(tzinfo=pytz.UTC))
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. # If this is POTA, SOTA or WWFF data we already have it through other means, so ignore.

View File

@@ -29,6 +29,7 @@ class POTA(HTTPProvider):
sig="POTA", sig="POTA",
sig_refs=[source_spot["reference"]], sig_refs=[source_spot["reference"]],
sig_refs_names=[source_spot["name"]], sig_refs_names=[source_spot["name"]],
icon="tree",
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=pytz.UTC), time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=pytz.UTC),
grid=source_spot["grid6"], grid=source_spot["grid6"],
latitude=source_spot["latitude"], latitude=source_spot["latitude"],

View File

@@ -70,6 +70,7 @@ class RBN(Provider):
de_call=match.group(1), de_call=match.group(1),
freq=float(match.group(2)), freq=float(match.group(2)),
comment=match.group(4).strip(), comment=match.group(4).strip(),
icon="tower-cell",
time=spot_datetime) time=spot_datetime)
# Add to our list # Add to our list

View File

@@ -48,6 +48,7 @@ class SOTA(HTTPProvider):
sig="SOTA", sig="SOTA",
sig_refs=[source_spot["summitCode"]], sig_refs=[source_spot["summitCode"]],
sig_refs_names=[source_spot["summitName"]], sig_refs_names=[source_spot["summitName"]],
icon="mountain-sun",
time=datetime.fromisoformat(source_spot["timeStamp"]), time=datetime.fromisoformat(source_spot["timeStamp"]),
activation_score=source_spot["points"]) activation_score=source_spot["points"])

View File

@@ -32,6 +32,7 @@ class WWBOTA(HTTPProvider):
sig="WWBOTA", sig="WWBOTA",
sig_refs=refs, sig_refs=refs,
sig_refs_names=ref_names, sig_refs_names=ref_names,
icon="radiation",
time=datetime.fromisoformat(source_spot["time"]), time=datetime.fromisoformat(source_spot["time"]),
# WWBOTA spots can contain multiple references for bunkers being activated simultaneously. For # 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. # now, we will just pick the first one to use as our grid, latitude and longitude.

View File

@@ -29,6 +29,7 @@ class WWFF(HTTPProvider):
sig="WWFF", sig="WWFF",
sig_refs=[source_spot["reference"]], sig_refs=[source_spot["reference"]],
sig_refs_names=[source_spot["reference_name"]], sig_refs_names=[source_spot["reference_name"]],
icon="seedling",
time=datetime.fromtimestamp(source_spot["spot_time"]).replace(tzinfo=pytz.UTC), time=datetime.fromtimestamp(source_spot["spot_time"]).replace(tzinfo=pytz.UTC),
latitude=source_spot["latitude"], latitude=source_spot["latitude"],
longitude=source_spot["longitude"]) longitude=source_spot["longitude"])

View File

@@ -6,15 +6,17 @@
<title>(S)pothole</title> <title>(S)pothole</title>
<link rel="stylesheet" href="css/style.css" type="text/css"> <link rel="stylesheet" href="/css/style.css" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous"> integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
<link href="/fa/css/solid.min.css" rel="stylesheet" />
<link rel="icon" type="image/png" href="img/icon-512.png"> <link rel="icon" type="image/png" href="/img/icon-512.png">
<link rel="alternate icon" type="image/png" href="img/icon-192.png"> <link rel="alternate icon" type="image/png" href="/img/icon-192.png">
<link rel="alternate icon" type="image/png" href="img/icon-32.png"> <link rel="alternate icon" type="image/png" href="/img/icon-32.png">
<link rel="alternate icon" type="image/png" href="img/icon-16.png"> <link rel="alternate icon" type="image/png" href="/img/icon-16.png">
<link rel="alternate icon" type="image/x-icon" href="img/favicon.ico"> <link rel="alternate icon" type="image/x-icon" href="/img/favicon.ico">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>

View File

@@ -493,6 +493,10 @@ components:
type: integer type: integer
description: Activation score. SOTA only description: Activation score. SOTA only
example: 0 example: 0
icon:
type: string
descripton: 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
grid: grid:
type: string type: string
description: Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country description: Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country

View File

@@ -7,4 +7,10 @@
max-width: 60em; max-width: 60em;
margin: 0 auto; margin: 0 auto;
} }
}
span.icon-wrapper {
display: inline-block;
width: 1.5em;
text-align: center;
} }

View File

@@ -36,7 +36,7 @@ function loadSpots() {
$tr.append(`<td>${row["freq"]}</td>`); $tr.append(`<td>${row["freq"]}</td>`);
$tr.append(`<td>${row["mode"]}</td>`); $tr.append(`<td>${row["mode"]}</td>`);
$tr.append('<td>' + escapeHtml(`${row["comment"]}`) + '</td>'); $tr.append('<td>' + escapeHtml(`${row["comment"]}`) + '</td>');
$tr.append(`<td>${row["source"]}</td>`); $tr.append(`<td><span class='icon-wrapper'><i class='fa-solid fa-${row["icon"]}'></i></span> ${row["source"]}</td>`);
$tr.append(`<td>${row["de_flag"]}&nbsp;${row["de_call"]}</td>`); $tr.append(`<td>${row["de_flag"]}&nbsp;${row["de_call"]}</td>`);
table.find('tbody').append($tr); table.find('tbody').append($tr);
}); });