12 Commits

Author SHA1 Message Date
Ian Renton
422c917073 Docs tweak 2025-11-10 19:30:40 +00:00
Ian Renton
cad1f5cfdf Defensive coding fix 2025-11-10 19:03:12 +00:00
Ian Renton
78f8cd26f0 Possible emoji flag fix for Windows/Chrome 2025-11-10 19:01:25 +00:00
Ian Renton
d6cc2673dd Search input should have search type 2025-11-08 18:44:37 +00:00
Ian Renton
8f553a59f8 Doc tweaks 2025-11-08 18:23:11 +00:00
Ian Renton
f1841ca59e v1.0 release 2025-11-08 11:44:11 +00:00
Ian Renton
85e0a7354c Reject "AA00aa" grids and 0/0 latlons from online lookup 2025-11-03 20:14:41 +00:00
Ian Renton
2ccfa28119 Get "qth" friendly name from QRZ/clublog and return in the callsign lookup. Closes #77 2025-11-02 20:51:16 +00:00
Ian Renton
b313735e28 Add missing break statements 2025-11-02 20:38:30 +00:00
Ian Renton
bbaa3597f6 Implement WWFF reference lookup. Closes #76 2025-11-02 20:37:30 +00:00
Ian Renton
e61d7bedb4 Exception handling #74 2025-11-02 18:00:24 +00:00
Ian Renton
ebf07f352f Exception handling #74 2025-11-02 17:59:37 +00:00
12 changed files with 174 additions and 115 deletions

View File

@@ -4,7 +4,7 @@ from data.sig import SIG
# General software # General software
SOFTWARE_NAME = "Spothole by M0TRT" SOFTWARE_NAME = "Spothole by M0TRT"
SOFTWARE_VERSION = "0.1" SOFTWARE_VERSION = "1.0"
# HTTP headers used for spot providers that use HTTP # HTTP headers used for spot providers that use HTTP
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + ", v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"} HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + ", v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}

View File

@@ -301,7 +301,7 @@ class LookupHelper:
return ituz return ituz
# Infer an operator name from a callsign (requires QRZ.com/HamQTH) # Infer an operator name from a callsign (requires QRZ.com/HamQTH)
def infer_name_from_callsign(self, call): def infer_name_from_callsign_online_lookup(self, call):
data = self.get_qrz_data_for_callsign(call) data = self.get_qrz_data_for_callsign(call)
if data and "fname" in data: if data and "fname" in data:
name = data["fname"] name = data["fname"]
@@ -315,27 +315,40 @@ class LookupHelper:
return None return None
# Infer a latitude and longitude from a callsign (requires QRZ.com/HamQTH) # Infer a latitude and longitude from a callsign (requires QRZ.com/HamQTH)
def infer_latlon_from_callsign_qrz(self, call): # Coordinates that look default are rejected (apologies if your position really is 0,0, enjoy your voyage)
def infer_latlon_from_callsign_online_lookup(self, call):
data = self.get_qrz_data_for_callsign(call) data = self.get_qrz_data_for_callsign(call)
if data and "latitude" in data and "longitude" in data: if data and "latitude" in data and "longitude" in data and (data["latitude"] != 0 or data["longitude"] != 0):
return [data["latitude"], data["longitude"]] return [data["latitude"], data["longitude"]]
data = self.get_hamqth_data_for_callsign(call) data = self.get_hamqth_data_for_callsign(call)
if data and "latitude" in data and "longitude" in data: if data and "latitude" in data and "longitude" in data and (data["latitude"] != 0 or data["longitude"] != 0):
return [data["latitude"], data["longitude"]] return [data["latitude"], data["longitude"]]
else: else:
return None return None
# Infer a grid locator from a callsign (requires QRZ.com/HamQTH) # Infer a grid locator from a callsign (requires QRZ.com/HamQTH).
def infer_grid_from_callsign_qrz(self, call): # Grids that look default are rejected (apologies if your grid really is AA00aa, enjoy your research)
def infer_grid_from_callsign_online_lookup(self, call):
data = self.get_qrz_data_for_callsign(call) data = self.get_qrz_data_for_callsign(call)
if data and "locator" in data: if data and "locator" in data and data["locator"].upper() != "AA00" and data["locator"].upper() != "AA00AA" and data["locator"].upper() != "AA00AA00":
return data["locator"] return data["locator"]
data = self.get_hamqth_data_for_callsign(call) data = self.get_hamqth_data_for_callsign(call)
if data and "grid" in data: if data and "grid" in data and data["grid"].upper() != "AA00" and data["grid"].upper() != "AA00AA" and data["grid"].upper() != "AA00AA00":
return data["grid"] return data["grid"]
else: else:
return None return None
# Infer a textual QTH from a callsign (requires QRZ.com/HamQTH)
def infer_qth_from_callsign_online_lookup(self, call):
data = self.get_qrz_data_for_callsign(call)
if data and "addr2" in data:
return data["addr2"]
data = self.get_hamqth_data_for_callsign(call)
if data and "qth" in data:
return data["qth"]
else:
return None
# Infer a latitude and longitude from a callsign (using DXCC, probably very inaccurate) # Infer a latitude and longitude from a callsign (using DXCC, probably very inaccurate)
def infer_latlon_from_callsign_dxcc(self, call): def infer_latlon_from_callsign_dxcc(self, call):
try: try:

View File

@@ -1,4 +1,5 @@
import csv import csv
import logging
from pyhamtools.locator import latlong_to_locator from pyhamtools.locator import latlong_to_locator
@@ -28,6 +29,7 @@ def get_ref_regex_for_sig(sig):
# Note there is currently no support for KRMNPA location lookup, see issue #61. # Note there is currently no support for KRMNPA location lookup, see issue #61.
def get_sig_ref_info(sig, sig_ref_id): def get_sig_ref_info(sig, sig_ref_id):
sig_ref = SIGRef(id=sig_ref_id, sig=sig) sig_ref = SIGRef(id=sig_ref_id, sig=sig)
try:
if sig.upper() == "POTA": if sig.upper() == "POTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.pota.app/park/" + sig_ref_id, headers=HTTP_HEADERS).json() data = SEMI_STATIC_URL_DATA_CACHE.get("https://api.pota.app/park/" + sig_ref_id, headers=HTTP_HEADERS).json()
if data: if data:
@@ -67,7 +69,17 @@ def get_sig_ref_info(sig, sig_ref_id):
sig_ref.latitude = data["latitude"] if "latitude" in data else None sig_ref.latitude = data["latitude"] if "latitude" in data else None
sig_ref.longitude = data["longitude"] if "longitude" in data else None sig_ref.longitude = data["longitude"] if "longitude" in data else None
elif sig.upper() == "WWFF": elif sig.upper() == "WWFF":
wwff_csv_data = SEMI_STATIC_URL_DATA_CACHE.get("https://wwff.co/wwff-data/wwff_directory.csv",
headers=HTTP_HEADERS)
wwff_dr = csv.DictReader(wwff_csv_data.content.decode().splitlines())
for row in wwff_dr:
if row["reference"] == sig_ref_id:
sig_ref.name = row["name"] if "name" in row else None
sig_ref.url = "https://wwff.co/directory/?showRef=" + sig_ref_id sig_ref.url = "https://wwff.co/directory/?showRef=" + sig_ref_id
sig_ref.grid = row["iaruLocator"] if "iaruLocator" in row else None
sig_ref.latitude = float(row["latitude"]) if "latitude" in row else None
sig_ref.longitude = float(row["longitude"]) if "longitude" in row else None
break
elif sig.upper() == "SIOTA": elif sig.upper() == "SIOTA":
siota_csv_data = SEMI_STATIC_URL_DATA_CACHE.get("https://www.silosontheair.com/data/silos.csv", siota_csv_data = SEMI_STATIC_URL_DATA_CACHE.get("https://www.silosontheair.com/data/silos.csv",
headers=HTTP_HEADERS) headers=HTTP_HEADERS)
@@ -78,6 +90,7 @@ def get_sig_ref_info(sig, sig_ref_id):
sig_ref.grid = row["LOCATOR"] if "LOCATOR" in row else None sig_ref.grid = row["LOCATOR"] if "LOCATOR" in row else None
sig_ref.latitude = float(row["LAT"]) if "LAT" in row else None sig_ref.latitude = float(row["LAT"]) if "LAT" in row else None
sig_ref.longitude = float(row["LNG"]) if "LNG" in row else None sig_ref.longitude = float(row["LNG"]) if "LNG" in row else None
break
elif sig.upper() == "WOTA": elif sig.upper() == "WOTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://www.wota.org.uk/mapping/data/summits.json", data = SEMI_STATIC_URL_DATA_CACHE.get("https://www.wota.org.uk/mapping/data/summits.json",
headers=HTTP_HEADERS).json() headers=HTTP_HEADERS).json()
@@ -89,6 +102,7 @@ def get_sig_ref_info(sig, sig_ref_id):
sig_ref.grid = feature["properties"]["qthLocator"] sig_ref.grid = feature["properties"]["qthLocator"]
sig_ref.latitude = feature["geometry"]["coordinates"][1] sig_ref.latitude = feature["geometry"]["coordinates"][1]
sig_ref.longitude = feature["geometry"]["coordinates"][0] sig_ref.longitude = feature["geometry"]["coordinates"][0]
break
elif sig.upper() == "ZLOTA": elif sig.upper() == "ZLOTA":
data = SEMI_STATIC_URL_DATA_CACHE.get("https://ontheair.nz/assets/assets.json", headers=HTTP_HEADERS).json() data = SEMI_STATIC_URL_DATA_CACHE.get("https://ontheair.nz/assets/assets.json", headers=HTTP_HEADERS).json()
if data: if data:
@@ -99,6 +113,7 @@ def get_sig_ref_info(sig, sig_ref_id):
sig_ref.grid = latlong_to_locator(asset["y"], asset["x"], 6) sig_ref.grid = latlong_to_locator(asset["y"], asset["x"], 6)
sig_ref.latitude = asset["y"] sig_ref.latitude = asset["y"]
sig_ref.longitude = asset["x"] sig_ref.longitude = asset["x"]
break
elif sig.upper() == "BOTA": elif sig.upper() == "BOTA":
if not sig_ref.name: if not sig_ref.name:
sig_ref.name = sig_ref.id sig_ref.name = sig_ref.id
@@ -110,7 +125,8 @@ def get_sig_ref_info(sig, sig_ref_id):
sig_ref.grid = latlong_to_locator(ll[0], ll[1], 6) sig_ref.grid = latlong_to_locator(ll[0], ll[1], 6)
sig_ref.latitude = ll[0] sig_ref.latitude = ll[0]
sig_ref.longitude = ll[1] sig_ref.longitude = ll[1]
except:
logging.warn("Failed to look up sig_ref info for " + sig + " ref " + sig_ref_id + ".")
return sig_ref return sig_ref

View File

@@ -121,7 +121,7 @@ class Alert:
# the actual alertting service, e.g. we don't want to accidentally use a user's QRZ.com home lat/lon instead of # the actual alertting 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.
if self.dx_calls and not self.dx_names: if self.dx_calls and not self.dx_names:
self.dx_names = list(map(lambda c: lookup_helper.infer_name_from_callsign(c), self.dx_calls)) self.dx_names = list(map(lambda c: lookup_helper.infer_name_from_callsign_online_lookup(c), self.dx_calls))
# Always create an ID based on a hash of every parameter *except* received_time. This is used as the index # Always create an ID based on a hash of every parameter *except* received_time. This is used as the index
# to a map, which as a byproduct avoids us having multiple duplicate copies of the object that are identical # to a map, which as a byproduct avoids us having multiple duplicate copies of the object that are identical

View File

@@ -27,6 +27,9 @@ class Spot:
dx_call: str = None dx_call: str = None
# Name of the operator that has been spotted # Name of the operator that has been spotted
dx_name: str = None dx_name: str = None
# QTH of the operator that has been spotted. This could be from any SIG refs or could be from online lookup of their
# home QTH.
dx_qth: str = None
# Country of the DX operator # Country of the DX operator
dx_country: str = None dx_country: str = None
# Country flag of the DX operator # Country flag of the DX operator
@@ -313,15 +316,24 @@ class Spot:
# 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.
if self.dx_call and not self.dx_name: if self.dx_call and not self.dx_name:
self.dx_name = lookup_helper.infer_name_from_callsign(self.dx_call) self.dx_name = lookup_helper.infer_name_from_callsign_online_lookup(self.dx_call)
if self.dx_call and not self.dx_latitude: if self.dx_call and not self.dx_latitude:
latlon = lookup_helper.infer_latlon_from_callsign_qrz(self.dx_call) latlon = lookup_helper.infer_latlon_from_callsign_online_lookup(self.dx_call)
if latlon: if latlon:
self.dx_latitude = latlon[0] self.dx_latitude = latlon[0]
self.dx_longitude = latlon[1] self.dx_longitude = latlon[1]
self.dx_grid = lookup_helper.infer_grid_from_callsign_qrz(self.dx_call) self.dx_grid = lookup_helper.infer_grid_from_callsign_online_lookup(self.dx_call)
self.dx_location_source = "HOME QTH" self.dx_location_source = "HOME QTH"
# Determine a "QTH" string. If we have a SIG ref, pick the first one and turn it into a suitable stirng,
# otherwise see what they have set on an online lookup service.
if self.sig_refs and len(self.sig_refs) > 0:
self.dx_qth = self.sig_refs[0].id
if self.sig_refs[0].name:
self.dx_qth = self.dx_qth + " " + self.sig_refs[0].name
else:
self.dx_qth = lookup_helper.infer_qth_from_callsign_online_lookup(self.dx_call)
# Last resort for getting a DX position, use the DXCC entity. # Last resort for getting a DX position, use the DXCC entity.
if self.dx_call and not self.dx_latitude: if self.dx_call and not self.dx_latitude:
latlon = lookup_helper.infer_latlon_from_callsign_dxcc(self.dx_call) latlon = lookup_helper.infer_latlon_from_callsign_dxcc(self.dx_call)
@@ -341,11 +353,11 @@ class Spot:
if self.de_call and any(char.isdigit() for char in self.de_call) and not (self.de_call.startswith("T2") and self.source == "APRS-IS"): if self.de_call and any(char.isdigit() for char in self.de_call) and not (self.de_call.startswith("T2") and self.source == "APRS-IS"):
# DE operator position lookup, using QRZ.com. # DE operator position lookup, using QRZ.com.
if not self.de_latitude: if not self.de_latitude:
latlon = lookup_helper.infer_latlon_from_callsign_qrz(self.de_call) latlon = lookup_helper.infer_latlon_from_callsign_online_lookup(self.de_call)
if latlon: if latlon:
self.de_latitude = latlon[0] self.de_latitude = latlon[0]
self.de_longitude = latlon[1] self.de_longitude = latlon[1]
self.de_grid = lookup_helper.infer_grid_from_callsign_qrz(self.de_call) self.de_grid = lookup_helper.infer_grid_from_callsign_online_lookup(self.de_call)
# Last resort for getting a DE position, use the DXCC entity. # Last resort for getting a DE position, use the DXCC entity.
if not self.de_latitude: if not self.de_latitude:

View File

@@ -127,6 +127,7 @@ class WebServer:
return self.serve_api({ return self.serve_api({
"call": call, "call": call,
"name": fake_spot.dx_name, "name": fake_spot.dx_name,
"qth": fake_spot.dx_qth,
"country": fake_spot.dx_country, "country": fake_spot.dx_country,
"flag": fake_spot.dx_flag, "flag": fake_spot.dx_flag,
"continent": fake_spot.dx_continent, "continent": fake_spot.dx_continent,

View File

@@ -5,7 +5,6 @@ import pytz
from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE
from core.constants import HTTP_HEADERS from core.constants import HTTP_HEADERS
from core.sig_utils import get_icon_for_sig
from data.sig_ref import SIGRef from data.sig_ref import SIGRef
from data.spot import Spot from data.spot import Spot
from spotproviders.http_spot_provider import HTTPSpotProvider from spotproviders.http_spot_provider import HTTPSpotProvider
@@ -51,7 +50,7 @@ class GMA(HTTPSpotProvider):
# spots come through with reftype=POTA or reftype=WWFF. SOTA is harder to figure out because both SOTA # spots come through with reftype=POTA or reftype=WWFF. SOTA is harder to figure out because both SOTA
# and GMA summits come through with reftype=Summit, so we must check for the presence of a "sota" entry # and GMA summits come through with reftype=Summit, so we must check for the presence of a "sota" entry
# to determine if it's a SOTA summit. # to determine if it's a SOTA summit.
if ref_info["reftype"] not in ["POTA", "WWFF"] and (ref_info["reftype"] != "Summit" or ref_info["sota"] == ""): if "reftype" in ref_info and ref_info["reftype"] not in ["POTA", "WWFF"] and (ref_info["reftype"] != "Summit" or ref_info["sota"] == ""):
match ref_info["reftype"]: match ref_info["reftype"]:
case "Summit": case "Summit":
spot.sig_refs[0].sig = "GMA" spot.sig_refs[0].sig = "GMA"

View File

@@ -32,6 +32,8 @@
<p>To avoid putting too much load on the various servers that Spothole connects to, the Spothole server only polls them once every two minutes for spots, and once every hour for alerts. (Some sources, such as DX clusters, RBN, APRS-IS and WWBOTA use a non-polling mechanism, and their updates will therefore arrive more quickly.) Then if you are using the web interface, that has its own rate at which it reloads the data from Spothole, which is once a minute for spots or 30 minutes for alerts. So you could be waiting around three minutes to see a newly added spot, or 90 minutes to see a newly added alert.</p> <p>To avoid putting too much load on the various servers that Spothole connects to, the Spothole server only polls them once every two minutes for spots, and once every hour for alerts. (Some sources, such as DX clusters, RBN, APRS-IS and WWBOTA use a non-polling mechanism, and their updates will therefore arrive more quickly.) Then if you are using the web interface, that has its own rate at which it reloads the data from Spothole, which is once a minute for spots or 30 minutes for alerts. So you could be waiting around three minutes to see a newly added spot, or 90 minutes to see a newly added alert.</p>
<h4 class="mt-4">What licence does Spothole use?</h4> <h4 class="mt-4">What licence does Spothole use?</h4>
<p>Spothole's source code is licenced under the Public Domain. You can write a Spothole client, run your own server, modify it however you like, you can claim you wrote it and charge people £1000 for a copy, I don't really mind. (Please don't do the last one. But if you're using my code for something cool, it would be nice to hear from you!)</p> <p>Spothole's source code is licenced under the Public Domain. You can write a Spothole client, run your own server, modify it however you like, you can claim you wrote it and charge people £1000 for a copy, I don't really mind. (Please don't do the last one. But if you're using my code for something cool, it would be nice to hear from you!)</p>
<h2 class="mt-4">Data Accuracy</h2>
<p>Please note that the data coming out of Spothole is only as good as the data going in. People mis-hear and make typos when spotting callsigns all the time. There are also plenty of cases where Spothole's data, particularly location data, may be inaccurate. For example, there are POTA parks that span multiple US states, countries that span multiple CQ zones, portable operators with no requirement to sign /P, etc. If you are doing something where accuracy is important, such as contesting, you should not rely on Spothole's data to fill in any gaps in your log.</p>
<h2 id="privacy" class="mt-4">Privacy</h2> <h2 id="privacy" class="mt-4">Privacy</h2>
<p>Spothole collects no data about you, and there is no way to enter personally identifying information into the site apart from by spotting and alerting through Spothole or the various services it connects to. All spots and alerts are "timed out" and deleted from the system after a set interval, which by default is one hour for spots and one week for alerts.</p> <p>Spothole collects no data about you, and there is no way to enter personally identifying information into the site apart from by spotting and alerting through Spothole or the various services it connects to. All spots and alerts are "timed out" and deleted from the system after a set interval, which by default is one hour for spots and one week for alerts.</p>
<p>Settings you select from Spothole's menus are sent to the server, in order to provide the data with the requested filters. They are also stored in your browser's local storage, so that your preferences are remembered between sessions.</p> <p>Settings you select from Spothole's menus are sent to the server, in order to provide the data with the requested filters. They are also stored in your browser's local storage, so that your preferences are remembered between sessions.</p>

View File

@@ -30,6 +30,10 @@
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" /> <link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
<link href="/fa/css/solid.min.css" rel="stylesheet" /> <link href="/fa/css/solid.min.css" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap" 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="apple-touch-icon" href="img/icon-512-pwa.png"> <link rel="apple-touch-icon" href="img/icon-512-pwa.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">

View File

@@ -16,7 +16,7 @@
<p class="d-inline-flex gap-1"> <p class="d-inline-flex gap-1">
<span style="position: relative;"> <span style="position: relative;">
<i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 0px; top: 2px; padding: 10px; pointer-events: none;"></i> <i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 0px; top: 2px; padding: 10px; pointer-events: none;"></i>
<input id="filter-dx-call" type="text" class="form-control" oninput="filtersUpdated();" placeholder="Search for call"> <input id="filter-dx-call" type="search" class="form-control" oninput="filtersUpdated();" placeholder="Callsign">
</span> </span>
<button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button> <button id="filters-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleFiltersPanel();"><i class="fa-solid fa-filter"></i> Filters</button>
<button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button> <button id="display-button" type="button" class="btn btn-outline-primary" data-bs-toggle="button" onclick="toggleDisplayPanel();"><i class="fa-solid fa-desktop"></i> Display</button>

View File

@@ -5,6 +5,10 @@ info:
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. 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.
While there are other web-based interfaces to DX clusters, and sites that aggregate spots from various outdoor activity programmes for amateur radio, Spothole differentiates itself by supporting a large number of data sources, and by being "API first" rather than just providing a web front-end. This allows other software to be built on top of it. Spothole itself is also open source, Public Domain licenced code that anyone can take and modify. While there are other web-based interfaces to DX clusters, and sites that aggregate spots from various outdoor activity programmes for amateur radio, Spothole differentiates itself by supporting a large number of data sources, and by being "API first" rather than just providing a web front-end. This allows other software to be built on top of it. Spothole itself is also open source, Public Domain licenced code that anyone can take and modify.
The API calls described below allow third-party software to access data from Spothole, and receive data on spots and alerts in a consistent format regardless of the data sources used by Spothole itself. Utility calls are also provided for general data lookups.
Please note that the data coming out of Spothole is only as good as the data going in. People mis-hear and make typos when spotting callsigns all the time, and there are plenty of areas where Spothole's location data may be inaccurate. If you are doing something where accuracy is important, such as contesting, you should not rely on Spothole's data to fill in any gaps in your log.
contact: contact:
email: ian@ianrenton.com email: ian@ianrenton.com
license: license:
@@ -518,13 +522,17 @@ paths:
type: string type: string
description: Name of the operator description: Name of the operator
example: Ian example: Ian
qth:
type: string
description: QTH of the operator. This could be from any SIG refs or could be from online lookup of their home QTH.
example: Dorset
country: country:
type: string type: string
description: Country of the operator description: Country of the operator. Note that this is named "country" for commonality with other amateur radio tools, but in reality this is more of a "DXCC Name", as it includes many options which are not countries, just territories that DXCC uniquely identifies.
example: United Kingdom example: England
flag: flag:
type: string type: string
description: Country flag of the operator description: Country flag of the operator. This is limited to the range of emoji flags. For some DXCCs there may not be an official emoji flag, e.g. Northern Ireland, so the appearance may vary depending on your browser and operating system. Some small islands may also have no flag. Many DXCCs may also share a flag, e.g. mainland Spain, Balearic Islands, etc.
example: "" example: ""
continent: continent:
type: string type: string
@@ -745,13 +753,17 @@ components:
type: string type: string
description: Name of the operator that has been spotted description: Name of the operator that has been spotted
example: Ian example: Ian
dx_qth:
type: string
description: QTH of the operator that has been spotted. This could be from any SIG refs or could be from online lookup of their home QTH.
example: Dorset
dx_country: dx_country:
type: string type: string
description: Country of the DX operator description: Country of the operator. Note that this is named "country" for commonality with other amateur radio tools, but in reality this is more of a "DXCC Name", as it includes many options which are not countries, just territories that DXCC uniquely identifies.
example: United Kingdom example: England
dx_flag: dx_flag:
type: string type: string
description: Country flag of the DX operator description: Country flag of the DX operator. This is limited to the range of emoji flags. For some DXCCs there may not be an official emoji flag, e.g. Northern Ireland, so the appearance may vary depending on your browser and operating system. Some small islands may also have no flag. Many DXCCs may also share a flag, e.g. mainland Spain, Balearic Islands, etc.
example: "" example: ""
dx_continent: dx_continent:
type: string type: string
@@ -814,11 +826,11 @@ components:
example: M0TEST example: M0TEST
de_country: de_country:
type: string type: string
description: Country of the spotter description: Country of the operator. Note that this is named "country" for commonality with other amateur radio tools, but in reality this is more of a "DXCC Name", as it includes many options which are not countries, just territories that DXCC uniquely identifies.
example: United Kingdom example: England
de_flag: de_flag:
type: string type: string
description: Country flag of the spotter description: Country flag of the spotter. This is limited to the range of emoji flags. For some DXCCs there may not be an official emoji flag, e.g. Northern Ireland, so the appearance may vary depending on your browser and operating system. Some small islands may also have no flag. Many DXCCs may also share a flag, e.g. mainland Spain, Balearic Islands, etc.
example: "" example: ""
de_continent: de_continent:
type: string type: string
@@ -1038,11 +1050,11 @@ components:
example: Ian example: Ian
dx_country: dx_country:
type: string type: string
description: Country of the DX operator. This, and the subsequent fields, assume that all activators will be in the same country! description: Country of the DX operator. Country of the operator. Note that this is named "country" for commonality with other amateur radio tools, but in reality this is more of a "DXCC Name", as it includes many options which are not countries, just territories that DXCC uniquely identifies. This, and the subsequent fields, assume that all activators will be in the same country!
example: United Kingdom example: England
dx_flag: dx_flag:
type: string type: string
description: Country flag of the DX operator description: Country flag of the DX operator. This is limited to the range of emoji flags. For some DXCCs there may not be an official emoji flag, e.g. Northern Ireland, so the appearance may vary depending on your browser and operating system. Some small islands may also have no flag. Many DXCCs may also share a flag, e.g. mainland Spain, Balearic Islands, etc.
example: "" example: ""
dx_continent: dx_continent:
type: string type: string

View File

@@ -44,7 +44,7 @@ div.container {
/* SPOTS/ALERTS PAGES, SETTINGS/STATUS AREAS */ /* SPOTS/ALERTS PAGES, SETTINGS/STATUS AREAS */
input#filter-dx-call { input#filter-dx-call {
max-width: 10em; max-width: 12em;
margin-right: 1rem; margin-right: 1rem;
padding-left: 2em; padding-left: 2em;
} }
@@ -265,7 +265,7 @@ div.band-spot:hover span.band-spot-info {
} }
/* Filter/search DX Call field should be smaller on mobile */ /* Filter/search DX Call field should be smaller on mobile */
input#filter-dx-call { input#filter-dx-call {
max-width: 6em; max-width: 9em;
margin-right: 0; margin-right: 0;
} }
} }