mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 16:59:25 +00:00
Compare commits
1 Commits
8c2ab61049
...
44-contain
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
885b832661 |
@@ -9,7 +9,7 @@ SOFTWARE_VERSION = "0.1"
|
|||||||
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + " " + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + " " + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
||||||
|
|
||||||
# Special Interest Groups
|
# Special Interest Groups
|
||||||
SIGS = ["POTA", "SOTA", "WWFF", "GMA", "WWBOTA", "HEMA", "MOTA", "ARLHS", "ILLW", "SiOTA", "WCA", "ZLOTA", "IOTA"]
|
SIGS = ["POTA", "SOTA", "WWFF", "GMA", "WWBOTA", "HEMA", "MOTA", "ARLHS", "SiOTA", "WCA"]
|
||||||
|
|
||||||
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
|
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
|
||||||
CW_MODES = ["CW"]
|
CW_MODES = ["CW"]
|
||||||
|
|||||||
10
data/spot.py
10
data/spot.py
@@ -2,7 +2,6 @@ import copy
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -103,8 +102,6 @@ class Spot:
|
|||||||
sig_refs: list = None
|
sig_refs: list = None
|
||||||
# SIG reference names
|
# SIG reference names
|
||||||
sig_refs_names: list = None
|
sig_refs_names: list = None
|
||||||
# SIG reference URLs
|
|
||||||
sig_refs_urls: list = None
|
|
||||||
# Activation score. SOTA only
|
# Activation score. SOTA only
|
||||||
activation_score: int = None
|
activation_score: int = None
|
||||||
|
|
||||||
@@ -235,13 +232,6 @@ class Spot:
|
|||||||
if self.comment and not self.qrt:
|
if self.comment and not self.qrt:
|
||||||
self.qrt = "QRT" in self.comment.upper()
|
self.qrt = "QRT" in self.comment.upper()
|
||||||
|
|
||||||
# Clean up comments
|
|
||||||
if self.comment:
|
|
||||||
comment = re.sub(r"\[.*]:", "", self.comment)
|
|
||||||
comment = re.sub(r"\[.*]", "", comment)
|
|
||||||
comment = re.sub(r"\"\"", "", comment)
|
|
||||||
self.comment = comment.strip()
|
|
||||||
|
|
||||||
# DX operator details lookup, using QRZ.com. This should be the last resort compared to taking the data from
|
# DX operator details lookup, using QRZ.com. This should be the last resort compared to taking the data from
|
||||||
# the actual spotting service, e.g. we don't want to accidentally use a user's QRZ.com home lat/lon instead of
|
# the actual spotting 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.
|
# the one from the park reference they're at.
|
||||||
|
|||||||
@@ -137,9 +137,6 @@ class WebServer:
|
|||||||
# in seconds UTC.
|
# in seconds UTC.
|
||||||
# We can also filter by source, sig, band, mode, dx_continent and de_continent. Each of these accepts a single
|
# We can also filter by source, sig, band, mode, dx_continent and de_continent. Each of these accepts a single
|
||||||
# value or a comma-separated list.
|
# value or a comma-separated list.
|
||||||
# We can filter by comments, accepting a single string, where the API will only return spots where the comment
|
|
||||||
# contains the provided value (case-insensitive).
|
|
||||||
# We can "de-dupe" spots, so only the latest spot will be sent for each callsign.
|
|
||||||
# We can provide a "limit" number as well. Spots are always returned newest-first; "limit" limits to only the
|
# We can provide a "limit" number as well. Spots are always returned newest-first; "limit" limits to only the
|
||||||
# most recent X spots.
|
# most recent X spots.
|
||||||
spot_ids = list(self.spots.iterkeys())
|
spot_ids = list(self.spots.iterkeys())
|
||||||
@@ -165,14 +162,8 @@ class WebServer:
|
|||||||
sources = query.get(k).split(",")
|
sources = query.get(k).split(",")
|
||||||
spots = [s for s in spots if s.source and s.source in sources]
|
spots = [s for s in spots if s.source and s.source in sources]
|
||||||
case "sig":
|
case "sig":
|
||||||
# If a list of sigs is provided, the spot must have a sig and it must match one of them
|
|
||||||
sigs = query.get(k).split(",")
|
sigs = query.get(k).split(",")
|
||||||
spots = [s for s in spots if s.sig and s.sig in sigs]
|
spots = [s for s in spots if s.sig and s.sig in sigs]
|
||||||
case "needs_sig":
|
|
||||||
# If true, a sig is required, regardless of what it is, it just can't be missing.
|
|
||||||
needs_sig = query.get(k).upper() == "TRUE"
|
|
||||||
if needs_sig:
|
|
||||||
spots = [s for s in spots if s.sig]
|
|
||||||
case "band":
|
case "band":
|
||||||
bands = query.get(k).split(",")
|
bands = query.get(k).split(",")
|
||||||
spots = [s for s in spots if s.band and s.band in bands]
|
spots = [s for s in spots if s.band and s.band in bands]
|
||||||
@@ -188,22 +179,6 @@ class WebServer:
|
|||||||
case "de_continent":
|
case "de_continent":
|
||||||
deconts = query.get(k).split(",")
|
deconts = query.get(k).split(",")
|
||||||
spots = [s for s in spots if s.de_continent and s.de_continent in deconts]
|
spots = [s for s in spots if s.de_continent and s.de_continent in deconts]
|
||||||
case "comment_includes":
|
|
||||||
comment_includes = query.get(k).strip()
|
|
||||||
spots = [s for s in spots if s.comment and comment_includes.upper() in s.comment.upper()]
|
|
||||||
case "dedupe":
|
|
||||||
# Ensure only the latest spot of each callsign is present in the list. This relies on the list being
|
|
||||||
# in reverse time order, so if any future change allows re-ordering the list, that should be done
|
|
||||||
# *after* this.
|
|
||||||
dedupe = query.get(k).upper() == "TRUE"
|
|
||||||
if dedupe:
|
|
||||||
spots_temp = []
|
|
||||||
already_seen = []
|
|
||||||
for s in spots:
|
|
||||||
if s.dx_call not in already_seen:
|
|
||||||
spots_temp.append(s)
|
|
||||||
already_seen.append(s.dx_call)
|
|
||||||
spots = spots_temp
|
|
||||||
# If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys.
|
# If we have a "limit" parameter, we apply that last, regardless of where it appeared in the list of keys.
|
||||||
if "limit" in query.keys():
|
if "limit" in query.keys():
|
||||||
spots = spots[:int(query.get("limit"))]
|
spots = spots[:int(query.get("limit"))]
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class GMA(HTTPSpotProvider):
|
|||||||
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"]],
|
||||||
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(
|
time=datetime.strptime(source_spot["DATE"] + source_spot["TIME"], "%Y%m%d%H%M").replace(
|
||||||
tzinfo=pytz.UTC).timestamp(),
|
tzinfo=pytz.UTC).timestamp(),
|
||||||
dx_latitude=float(source_spot["LAT"]) if (source_spot["LAT"] and source_spot["LAT"] != "") else None,
|
dx_latitude=float(source_spot["LAT"]) if (source_spot["LAT"] and source_spot["LAT"] != "") else None,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class POTA(HTTPSpotProvider):
|
|||||||
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"]],
|
||||||
sig_refs_urls=["https://pota.app/#/park/" + source_spot["reference"]],
|
|
||||||
icon="tree",
|
icon="tree",
|
||||||
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=pytz.UTC).timestamp(),
|
time=datetime.strptime(source_spot["spotTime"], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=pytz.UTC).timestamp(),
|
||||||
dx_grid=source_spot["grid6"],
|
dx_grid=source_spot["grid6"],
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ class SOTA(HTTPSpotProvider):
|
|||||||
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"]],
|
||||||
sig_refs_urls=["https://www.sotadata.org.uk/en/summit/" + source_spot["summitCode"]],
|
|
||||||
icon="mountain-sun",
|
icon="mountain-sun",
|
||||||
time=datetime.fromisoformat(source_spot["timeStamp"]).timestamp(),
|
time=datetime.fromisoformat(source_spot["timeStamp"]).timestamp(),
|
||||||
activation_score=source_spot["points"])
|
activation_score=source_spot["points"])
|
||||||
|
|||||||
@@ -18,16 +18,9 @@ class WWBOTA(SSESpotProvider):
|
|||||||
# n-fer activations.
|
# n-fer activations.
|
||||||
refs = []
|
refs = []
|
||||||
ref_names = []
|
ref_names = []
|
||||||
ref_urls = []
|
|
||||||
for ref in source_spot["references"]:
|
for ref in source_spot["references"]:
|
||||||
refs.append(ref["reference"])
|
refs.append(ref["reference"])
|
||||||
ref_names.append(ref["name"])
|
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,
|
spot = Spot(source=self.name,
|
||||||
dx_call=source_spot["call"].upper(),
|
dx_call=source_spot["call"].upper(),
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class WWFF(HTTPSpotProvider):
|
|||||||
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"]],
|
||||||
sig_refs_urls=["https://wwff.co/directory/?showRef=" + source_spot["reference"]],
|
|
||||||
icon="seedling",
|
icon="seedling",
|
||||||
time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(),
|
time=datetime.fromtimestamp(source_spot["spot_time"], tz=pytz.UTC).timestamp(),
|
||||||
dx_latitude=source_spot["latitude"],
|
dx_latitude=source_spot["latitude"],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
% rebase('webpage_base.tpl')
|
% rebase('webpage_base.tpl')
|
||||||
|
|
||||||
|
<div class="container main-container">
|
||||||
<div id="info-container" class="mt-4">
|
<div id="info-container" class="mt-4">
|
||||||
<h2 class="mt-4 mb-4">About Spothole</h2>
|
<h2 class="mt-4 mb-4">About Spothole</h2>
|
||||||
<p>Spothole is a utility to aggregate "spots" from amateur radio DX clusters and xOTA spotting sites, and provide an open JSON API as well as a website to browse the data.</p>
|
<p>Spothole is a utility to aggregate "spots" from amateur radio DX clusters and xOTA spotting sites, and provide an open JSON API as well as a website to browse the data.</p>
|
||||||
@@ -32,5 +33,6 @@
|
|||||||
<p>There are no trackers, no ads, and no cookies.</p>
|
<p>There are no trackers, no ads, and no cookies.</p>
|
||||||
<p>Spothole is open source, so you can audit <a href="https://git.ianrenton.com/ian/spothole">the code</a> if you like.</p>
|
<p>Spothole is open source, so you can audit <a href="https://git.ianrenton.com/ian/spothole">the code</a> if you like.</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
% rebase('webpage_base.tpl')
|
% rebase('webpage_base.tpl')
|
||||||
|
|
||||||
|
<div class="container main-container mobile-no-gutters">
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto me-auto pt-3">
|
<div class="col-auto me-auto pt-3">
|
||||||
@@ -156,6 +157,7 @@
|
|||||||
<div id="table-container"></div>
|
<div id="table-container"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js"></script>
|
<script src="/js/common.js"></script>
|
||||||
<script src="/js/alerts.js"></script>
|
<script src="/js/alerts.js"></script>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
% rebase('webpage_base.tpl')
|
% rebase('webpage_base.tpl')
|
||||||
|
|
||||||
|
<div class="container main-container">
|
||||||
<redoc spec-url="/apidocs/openapi.yml"></redoc>
|
<redoc spec-url="/apidocs/openapi.yml"></redoc>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
|
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-api").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-api").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
@@ -57,16 +57,17 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
|
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item ms-4"><a href="/" class="nav-link" id="nav-link-spots"><i class="fa-solid fa-tower-cell"></i> Spots</a></li>
|
<li class="nav-item ms-4"><a href="/" class="nav-link" id="nav-link-spots">Spots</a></li>
|
||||||
<li class="nav-item ms-4"><a href="/map" class="nav-link" id="nav-link-map"><i class="fa-solid fa-map"></i> Map</a></li>
|
<li class="nav-item ms-4"><a href="/map" class="nav-link" id="nav-link-map">Map</a></li>
|
||||||
<li class="nav-item ms-4"><a href="/alerts" class="nav-link" id="nav-link-alerts"><i class="fa-solid fa-bell"></i> Alerts</a></li>
|
<li class="nav-item ms-4"><a href="/alerts" class="nav-link" id="nav-link-alerts">Alerts</a></li>
|
||||||
<li class="nav-item ms-4"><a href="/status" class="nav-link" id="nav-link-status"><i class="fa-solid fa-chart-simple"></i> Status</a></li>
|
<li class="nav-item ms-4"><a href="/status" class="nav-link" id="nav-link-status">Status</a></li>
|
||||||
<li class="nav-item ms-4"><a href="/about" class="nav-link" id="nav-link-about"><i class="fa-solid fa-circle-info"></i> About</a></li>
|
<li class="nav-item ms-4"><a href="/about" class="nav-link" id="nav-link-about">About</a></li>
|
||||||
<li class="nav-item ms-4"><a href="/apidocs" class="nav-link" id="nav-link-api"><i class="fa-solid fa-gear"></i> API</a></li>
|
<li class="nav-item ms-4"><a href="/apidocs" class="nav-link" id="nav-link-api">API</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
<div class="hideonmobile hideonmap">
|
<div class="hideonmobile hideonmap">
|
||||||
<footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
|
<footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
|
||||||
<p class="col-md-4 mb-0 text-body-secondary">Made with love by <a href="https://ianrenton.com" class="text-body-secondary">Ian, MØTRT</a> and other contributors.</p>
|
<p class="col-md-4 mb-0 text-body-secondary">Made with love by <a href="https://ianrenton.com" class="text-body-secondary">Ian, MØTRT</a> and other contributors.</p>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
% rebase('webpage_base.tpl')
|
% rebase('webpage_base.tpl')
|
||||||
|
|
||||||
|
<div class="container main-container mobile-no-gutters">
|
||||||
<div id="map">
|
<div id="map">
|
||||||
<div class="mt-3 px-3" style="z-index: 1002; position: relative;">
|
<div class="mt-3 px-3" style="z-index: 1002; position: relative;">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -120,6 +121,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-extra-markers@1.2.2/dist/css/leaflet.extra-markers.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-extra-markers@1.2.2/dist/css/leaflet.extra-markers.min.css">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
% rebase('webpage_base.tpl')
|
% rebase('webpage_base.tpl')
|
||||||
|
|
||||||
|
<div class="container main-container mobile-no-gutters">
|
||||||
<div id="intro-box" class="mt-3">
|
<div id="intro-box" class="mt-3">
|
||||||
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||||
<i class="fa-solid fa-circle-info"></i> <strong>What is Spothole?</strong><br/>Spothole is an aggregator of amateur radio spots from DX clusters and outdoor activity programmes. It's free for anyone to use and includes an API that developers can build other applications on. For more information, check out the <a href="/about" class="alert-link">"About" page</a>. If that sounds like nonsense to you, you can visit <a href="/about#faq" class="alert-link">the FAQ section</a> to learn more.
|
<i class="fa-solid fa-circle-info"></i> <strong>What is Spothole?</strong><br/>Spothole is an aggregator of amateur radio spots from DX clusters and outdoor activity programmes. It's free for anyone to use and includes an API that developers can build other applications on. For more information, check out the <a href="/about" class="alert-link">"About" page</a>. If that sounds like nonsense to you, you can visit <a href="/about#faq" class="alert-link">the FAQ section</a> to learn more.
|
||||||
@@ -154,8 +155,8 @@
|
|||||||
<label class="form-check-label" for="tableShowBearing">Bearing</label>
|
<label class="form-check-label" for="tableShowBearing">Bearing</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowType" value="tableShowType" oninput="columnsUpdated();" checked>
|
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowSource" value="tableShowSource" oninput="columnsUpdated();" checked>
|
||||||
<label class="form-check-label" for="tableShowType">Type</label>
|
<label class="form-check-label" for="tableShowSource">Source</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowRef" value="tableShowRef" oninput="columnsUpdated();" checked>
|
<input class="form-check-input storeable-checkbox" type="checkbox" id="tableShowRef" value="tableShowRef" oninput="columnsUpdated();" checked>
|
||||||
@@ -236,6 +237,7 @@
|
|||||||
<div id="table-container"></div>
|
<div id="table-container"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js"></script>
|
<script src="/js/common.js"></script>
|
||||||
<script src="/js/spotandmap.js"></script>
|
<script src="/js/spotandmap.js"></script>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
% rebase('webpage_base.tpl')
|
% rebase('webpage_base.tpl')
|
||||||
|
|
||||||
|
<div class="container main-container">
|
||||||
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
|
<div id="status-container" class="row row-cols-1 row-cols-md-4 g-4 mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js"></script>
|
<script src="/js/common.js"></script>
|
||||||
<script src="/js/status.js"></script>
|
<script src="/js/status.js"></script>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ paths:
|
|||||||
- APRS-IS
|
- APRS-IS
|
||||||
- name: sig
|
- name: sig
|
||||||
in: query
|
in: query
|
||||||
description: "Limit the spots to only ones from one or more Special Interest Groups provided as an argument. To select more than one SIG, supply a comma-separated list."
|
description: "Limit the spots to only ones from one or more Special Interest Groups. To select more than one SIG, supply a comma-separated list."
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
@@ -76,20 +76,6 @@ paths:
|
|||||||
- WWBOTA
|
- WWBOTA
|
||||||
- GMA
|
- GMA
|
||||||
- HEMA
|
- HEMA
|
||||||
- WCA
|
|
||||||
- MOTA
|
|
||||||
- SiOTA
|
|
||||||
- ARLHS
|
|
||||||
- ILLW
|
|
||||||
- ZLOTA
|
|
||||||
- IOTA
|
|
||||||
- name: needs_sig
|
|
||||||
in: query
|
|
||||||
description: "Limit the spots to only ones from a Special Interest Grous such as POTA. Because supplying all known SIGs as a `sigs` parameter is unwieldy, and leaving `sigs` blank will also return spots with *no* SIG, this parameter can be set true to return only spots with a SIG, regardless of what it is, so long as it's not blank. This is what Field Spotter uses to exclude generic cluster spots and only retrieve xOTA things."
|
|
||||||
required: false
|
|
||||||
schema:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
- name: band
|
- name: band
|
||||||
in: query
|
in: query
|
||||||
description: "Limit the spots to only ones from one or more bands. To select more than one band, supply a comma-separated list."
|
description: "Limit the spots to only ones from one or more bands. To select more than one band, supply a comma-separated list."
|
||||||
@@ -182,19 +168,6 @@ paths:
|
|||||||
- AF
|
- AF
|
||||||
- OC
|
- OC
|
||||||
- AN
|
- AN
|
||||||
- name: dedupe
|
|
||||||
in: query
|
|
||||||
description: "\"De-duplicate\" the spots, returning only the latest spot for any given callsign."
|
|
||||||
required: false
|
|
||||||
schema:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
- name: comment_includes
|
|
||||||
in: query
|
|
||||||
description: "Return only spots where the comment includes the provided string (case-insensitive)."
|
|
||||||
required: false
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Success
|
description: Success
|
||||||
@@ -268,13 +241,6 @@ paths:
|
|||||||
- WWBOTA
|
- WWBOTA
|
||||||
- GMA
|
- GMA
|
||||||
- HEMA
|
- HEMA
|
||||||
- WCA
|
|
||||||
- MOTA
|
|
||||||
- SiOTA
|
|
||||||
- ARLHS
|
|
||||||
- ILLW
|
|
||||||
- ZLOTA
|
|
||||||
- IOTA
|
|
||||||
- name: dx_continent
|
- name: dx_continent
|
||||||
in: query
|
in: query
|
||||||
description: "Limit the alerts to only ones where the DX (the operator being spotted) is on the given continent(s). To select more than one continent, supply a comma-separated list."
|
description: "Limit the alerts to only ones where the DX (the operator being spotted) is on the given continent(s). To select more than one continent, supply a comma-separated list."
|
||||||
@@ -701,13 +667,6 @@ components:
|
|||||||
- WWBOTA
|
- WWBOTA
|
||||||
- GMA
|
- GMA
|
||||||
- HEMA
|
- HEMA
|
||||||
- WCA
|
|
||||||
- MOTA
|
|
||||||
- SiOTA
|
|
||||||
- ARLHS
|
|
||||||
- ILLW
|
|
||||||
- ZLOTA
|
|
||||||
- IOTA
|
|
||||||
example: POTA
|
example: POTA
|
||||||
sig_refs:
|
sig_refs:
|
||||||
type: array
|
type: array
|
||||||
@@ -721,12 +680,6 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: SIG reference names
|
description: SIG reference names
|
||||||
example: Null Country Park
|
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:
|
activation_score:
|
||||||
type: integer
|
type: integer
|
||||||
description: Activation score. SOTA only
|
description: Activation score. SOTA only
|
||||||
@@ -735,14 +688,6 @@ components:
|
|||||||
type: string
|
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.
|
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
|
example: tree
|
||||||
band_color:
|
|
||||||
type: string
|
|
||||||
descripton: 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.
|
|
||||||
example: #ff0000"
|
|
||||||
band_contrast_color:
|
|
||||||
type: string
|
|
||||||
descripton: Black or white, whichever best contrasts with "band_color".
|
|
||||||
example: "white"
|
|
||||||
qrt:
|
qrt:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
|
description: QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
|
||||||
@@ -861,13 +806,6 @@ components:
|
|||||||
- WWBOTA
|
- WWBOTA
|
||||||
- GMA
|
- GMA
|
||||||
- HEMA
|
- HEMA
|
||||||
- WCA
|
|
||||||
- MOTA
|
|
||||||
- SiOTA
|
|
||||||
- ARLHS
|
|
||||||
- ILLW
|
|
||||||
- ZLOTA
|
|
||||||
- IOTA
|
|
||||||
example: POTA
|
example: POTA
|
||||||
sig_refs:
|
sig_refs:
|
||||||
type: array
|
type: array
|
||||||
@@ -889,6 +827,14 @@ components:
|
|||||||
type: string
|
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.
|
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
|
example: tree
|
||||||
|
band_color:
|
||||||
|
type: string
|
||||||
|
descripton: 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.
|
||||||
|
example: #ff0000"
|
||||||
|
band_contrast_color:
|
||||||
|
type: string
|
||||||
|
descripton: Black or white, whichever best contrasts with "band_color".
|
||||||
|
example: "white"
|
||||||
source:
|
source:
|
||||||
type: string
|
type: string
|
||||||
description: Where we got the alert from.
|
description: Where we got the alert from.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
/* GENERAL PAGE LAYOUT */
|
/* GENERAL PAGE LAYOUT */
|
||||||
|
|
||||||
div.container {
|
div.main-container {
|
||||||
display:grid;
|
display:grid;
|
||||||
grid-template-rows:auto 1fr auto;
|
grid-template-rows:auto 1fr auto;
|
||||||
grid-template-columns:100%;
|
grid-template-columns:100%;
|
||||||
@@ -92,13 +92,10 @@ span.icon-wrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
span.freq-mhz {
|
span.freq-mhz {
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.freq-mhz-pad {
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 1.7em;
|
min-width: 1.7em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.freq-khz {
|
span.freq-khz {
|
||||||
@@ -120,10 +117,6 @@ a.dx-link {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
a.sig-ref-link {
|
|
||||||
color: var(--bs-emphasis-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* QRT/faded styles */
|
/* QRT/faded styles */
|
||||||
tr.table-faded td {
|
tr.table-faded td {
|
||||||
@@ -149,6 +142,12 @@ div#map {
|
|||||||
font-family: var(--bs-body-font-family) !important;
|
font-family: var(--bs-body-font-family) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.leaflet-popup-callsign-link {
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* GENERAL MOBILE SUPPORT */
|
/* GENERAL MOBILE SUPPORT */
|
||||||
|
|
||||||
@@ -156,9 +155,9 @@ div#map {
|
|||||||
.hideonmobile {
|
.hideonmobile {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
div#map, div#table-container {
|
.mobile-no-gutters {
|
||||||
margin-left: -1em;
|
padding-left: 0 !important;
|
||||||
margin-right: -1em;
|
padding-right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,13 +100,7 @@ function getTooltipText(s) {
|
|||||||
|
|
||||||
// Format sig_refs
|
// Format sig_refs
|
||||||
var sig_refs = "";
|
var sig_refs = "";
|
||||||
if (s["sig_refs"] && s["sig_refs_urls"] && s["sig_refs"].length == s["sig_refs_urls"].length) {
|
if (s["sig_refs"]) {
|
||||||
items = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`)
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
items[i] = `<a href='${s["sig_refs_urls"][i]}' target='_new' class='sig-ref-link'>${items[i]}</a>`
|
|
||||||
}
|
|
||||||
sig_refs = items.join(", ");
|
|
||||||
} else if (s["sig_refs"]) {
|
|
||||||
sig_refs = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`).join(", ");
|
sig_refs = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +108,10 @@ function getTooltipText(s) {
|
|||||||
const shortCall = s["dx_call"].split("/").sort(function (a, b) {
|
const shortCall = s["dx_call"].split("/").sort(function (a, b) {
|
||||||
return b.length - a.length;
|
return b.length - a.length;
|
||||||
})[0];
|
})[0];
|
||||||
ttt = `<span class='nowrap'><span class='icon-wrapper'>${dx_flag}</span> <a href='https://www.qrz.com/db/${shortCall}' target='_blank' class="dx-link">${s["dx_call"]}</a></span><br/>`;
|
ttt = `<span class='nowrap'>${dx_flag} <a href='https://www.qrz.com/db/${shortCall}' target='_blank' class="leaflet-popup-callsign-link">${s["dx_call"]}</a></span><br/>`;
|
||||||
|
|
||||||
// Frequency & band
|
// Frequency & band
|
||||||
ttt += `<span class='icon-wrapper'><i class='fa-solid fa-radio markerPopupIcon'></i></span> ${freq_string}`;
|
ttt += `<i class='fa-solid fa-walkie-talkie markerPopupIcon'></i> ${freq_string} (${s["band"]})`;
|
||||||
if (s["band"] != null) {
|
|
||||||
ttt += ` (${s["band"]})`;
|
|
||||||
}
|
|
||||||
// Mode
|
// Mode
|
||||||
if (s["mode"] != null) {
|
if (s["mode"] != null) {
|
||||||
ttt += ` <i class='fa-solid fa-wave-square markerPopupIcon'></i> ${s["mode"]}`;
|
ttt += ` <i class='fa-solid fa-wave-square markerPopupIcon'></i> ${s["mode"]}`;
|
||||||
@@ -128,14 +119,14 @@ function getTooltipText(s) {
|
|||||||
ttt += "<br/>";
|
ttt += "<br/>";
|
||||||
|
|
||||||
// Source / SIG / Ref
|
// Source / SIG / Ref
|
||||||
ttt += `<span class='nowrap'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${sigSourceText} ${sig_refs}</span><br/>`;
|
ttt += `<span class='nowrap'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i> ${sigSourceText} ${sig_refs}</span><br/>`;
|
||||||
|
|
||||||
// Time
|
// Time
|
||||||
ttt += `<span class='icon-wrapper'><i class='fa-solid fa-clock markerPopupIcon'></i></span> ${moment.unix(s["time"]).fromNow()}`;
|
ttt += `<i class='fa-solid fa-clock markerPopupIcon'></i> ${moment.unix(s["time"]).fromNow()}`;
|
||||||
|
|
||||||
// Comment
|
// Comment
|
||||||
if (commentText.length > 0) {
|
if (commentText.length > 0) {
|
||||||
ttt += `<br/><span class='icon-wrapper'><i class='fa-solid fa-comment markerPopupIcon'></i></span> ${commentText}`;
|
ttt += `<br/><i class='fa-solid fa-comment markerPopupIcon'></i> ${commentText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ttt;
|
return ttt;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function updateTable() {
|
|||||||
var showMode = $("#tableShowMode")[0].checked;
|
var showMode = $("#tableShowMode")[0].checked;
|
||||||
var showComment = $("#tableShowComment")[0].checked;
|
var showComment = $("#tableShowComment")[0].checked;
|
||||||
var showBearing = $("#tableShowBearing")[0].checked && userPos != null;
|
var showBearing = $("#tableShowBearing")[0].checked && userPos != null;
|
||||||
var showType = $("#tableShowType")[0].checked;
|
var showSource = $("#tableShowSource")[0].checked;
|
||||||
var showRef = $("#tableShowRef")[0].checked;
|
var showRef = $("#tableShowRef")[0].checked;
|
||||||
var showDE = $("#tableShowDE")[0].checked;
|
var showDE = $("#tableShowDE")[0].checked;
|
||||||
|
|
||||||
@@ -62,8 +62,8 @@ function updateTable() {
|
|||||||
if (showBearing) {
|
if (showBearing) {
|
||||||
table.find('thead tr').append(`<th class='hideonmobile'>Bearing</th>`);
|
table.find('thead tr').append(`<th class='hideonmobile'>Bearing</th>`);
|
||||||
}
|
}
|
||||||
if (showType) {
|
if (showSource) {
|
||||||
table.find('thead tr').append(`<th class='hideonmobile'>Type</th>`);
|
table.find('thead tr').append(`<th class='hideonmobile'>Source</th>`);
|
||||||
}
|
}
|
||||||
if (showRef) {
|
if (showRef) {
|
||||||
table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
|
table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
|
||||||
@@ -109,7 +109,7 @@ function updateTable() {
|
|||||||
var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
|
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 = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
|
||||||
var hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
|
var hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
|
||||||
var freq_string = `<span class='freq-mhz freq-mhz-pad'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz hideonmobile'>${hz_string}</span>`
|
var freq_string = `<span class='freq-mhz'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz hideonmobile'>${hz_string}</span>`
|
||||||
|
|
||||||
// Format the mode
|
// Format the mode
|
||||||
mode_string = s["mode"];
|
mode_string = s["mode"];
|
||||||
@@ -140,21 +140,15 @@ function updateTable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format "type" (Sig or fallback to source)
|
// Sig or fallback to source
|
||||||
var typeText = s["source"];
|
var sigSourceText = s["source"];
|
||||||
if (s["sig"]) {
|
if (s["sig"]) {
|
||||||
typeText = s["sig"];
|
sigSourceText = s["sig"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format sig_refs
|
// Format sig_refs
|
||||||
var sig_refs = "";
|
var sig_refs = "";
|
||||||
if (s["sig_refs"] && s["sig_refs_urls"] && s["sig_refs"].length == s["sig_refs_urls"].length) {
|
if (s["sig_refs"]) {
|
||||||
items = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`)
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
items[i] = `<a href='${s["sig_refs_urls"][i]}' target='_new' class='sig-ref-link'>${items[i]}</a>`
|
|
||||||
}
|
|
||||||
sig_refs = items.join(", ");
|
|
||||||
} else if (s["sig_refs"]) {
|
|
||||||
sig_refs = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`).join(", ");
|
sig_refs = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,8 +199,8 @@ function updateTable() {
|
|||||||
if (showBearing) {
|
if (showBearing) {
|
||||||
$tr.append(`<td class='nowrap hideonmobile'>${bearingText}</td>`);
|
$tr.append(`<td class='nowrap hideonmobile'>${bearingText}</td>`);
|
||||||
}
|
}
|
||||||
if (showType) {
|
if (showSource) {
|
||||||
$tr.append(`<td class='nowrap hideonmobile'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${typeText}</td>`);
|
$tr.append(`<td class='nowrap hideonmobile'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${sigSourceText}</td>`);
|
||||||
}
|
}
|
||||||
if (showRef) {
|
if (showRef) {
|
||||||
$tr.append(`<td class='hideonmobile'><span ${sig_refs_title_string}>${sig_refs}</span></td>`);
|
$tr.append(`<td class='hideonmobile'><span ${sig_refs_title_string}>${sig_refs}</span></td>`);
|
||||||
@@ -216,14 +210,14 @@ function updateTable() {
|
|||||||
}
|
}
|
||||||
table.find('tbody').append($tr);
|
table.find('tbody').append($tr);
|
||||||
|
|
||||||
// Second row for mobile view only, containing type, ref & comment
|
// Second row for mobile view only, containing source, ref & comment
|
||||||
$tr2 = $("<tr class='hidenotonmobile'>");
|
$tr2 = $("<tr class='hidenotonmobile'>");
|
||||||
if (s["qrt"] == true) {
|
if (s["qrt"] == true) {
|
||||||
$tr2.addClass("table-faded");
|
$tr2.addClass("table-faded");
|
||||||
}
|
}
|
||||||
$td2 = $("<td colspan='100'>");
|
$td2 = $("<td colspan='100'>");
|
||||||
if (showType) {
|
if (showSource) {
|
||||||
$td2.append(`<span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${typeText} `);
|
$td2.append(`<span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${sigSourceText} `);
|
||||||
}
|
}
|
||||||
if (showRef) {
|
if (showRef) {
|
||||||
$td2.append(`${sig_refs} `);
|
$td2.append(`${sig_refs} `);
|
||||||
|
|||||||
Reference in New Issue
Block a user