Add "de_" variants of grid/lat/lon #42

This commit is contained in:
Ian Renton
2025-10-16 20:58:40 +01:00
parent d02f0e1afc
commit 9594040ea4
12 changed files with 223 additions and 154 deletions

View File

@@ -159,7 +159,7 @@ DXCC_FLAGS = {
107: "\U0001F1EC\U0001F1F3", # GUINEA 107: "\U0001F1EC\U0001F1F3", # GUINEA
108: "\U0001F1E7\U0001F1F7", # BRAZIL 108: "\U0001F1E7\U0001F1F7", # BRAZIL
109: "\U0001F1EC\U0001F1FC", # GUINEA-BISSAU 109: "\U0001F1EC\U0001F1FC", # GUINEA-BISSAU
110: "", # HAWAII 110: "\U0001F1FA\U0001F1F8", # HAWAII
111: "\U0001F1ED\U0001F1F2", # HEARD ISLAND 111: "\U0001F1ED\U0001F1F2", # HEARD ISLAND
112: "\U0001F1E8\U0001F1F1", # CHILE 112: "\U0001F1E8\U0001F1F1", # CHILE
113: "", # IFNI 113: "", # IFNI

View File

@@ -10,29 +10,26 @@ from pyhamtools.locator import locator_to_latlong, latlong_to_locator
from core.constants import DXCC_FLAGS from core.constants import DXCC_FLAGS
from core.lookup_helper import lookup_helper from core.lookup_helper import lookup_helper
# Data class that defines a spot. # Data class that defines a spot.
@dataclass @dataclass
class Spot: class Spot:
# Unique identifier for the spot # Unique identifier for the spot
id: str = None id: str = None
# DX (spotted) operator info
# Callsign of the operator that has been spotted # Callsign of the operator that has been spotted
dx_call: str = None dx_call: str = None
# Callsign of the operator that has spotted them
de_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
# Country of the DX operator # Country of the DX operator
dx_country: str = None dx_country: str = None
# Country of the spotter
de_country: str = None
# Country flag of the DX operator # Country flag of the DX operator
dx_flag: str = None dx_flag: str = None
# Country flag of the spotter
de_flag: str = None
# Continent of the DX operator # Continent of the DX operator
dx_continent: str = None dx_continent: str = None
# Continent of the spotter
de_continent: str = None
# DXCC ID of the DX operator # DXCC ID of the DX operator
dx_dxcc_id: int = None dx_dxcc_id: int = None
# DXCC ID of the spotter # DXCC ID of the spotter
@@ -44,6 +41,42 @@ class Spot:
# If this is an APRS spot, what SSID was the DX operator using? # If this is an APRS spot, what SSID was the DX operator using?
# This is a string not an int for now, as I often see non-numeric ones somehow # This is a string not an int for now, as I often see non-numeric ones somehow
dx_aprs_ssid: str = None dx_aprs_ssid: str = None
# Maidenhead grid locator for the DX. This could be from a geographical reference e.g. POTA, or just from the
# country
dx_grid: str = None
# Latitude & longitude of the DX, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ
# lookup
dx_latitude: float = None
dx_longitude: float = None
# DX Location source. Indicates how accurate the location might be. Values: "SPOT", "QRZ, "DXCC", "NONE"
dx_location_source: str = "NONE"
# DX Location good. Indicates that the software thinks the location data is good enough to plot on a map.
dx_location_good: bool = False
# DE (Spotter) info
# Callsign of the spotter
de_call: str = None
# Country of the spotter
de_country: str = None
# Country flag of the spotter
de_flag: str = None
# Continent of the spotter
de_continent: str = None
# Maidenhead grid locator for the spotter. This is not going to be from a xOTA reference so it will likely just be
# a QRZ or DXCC lookup. If the spotter is also portable, this is probably wrong, but it's good enough for some
# simple mapping.
de_grid: str = None
# Latitude & longitude of the DX, in degrees. This is not going to be from a xOTA reference so it will likely just
# be a QRZ or DXCC lookup. If the spotter is also portable, this is probably wrong, but it's good enough for some
# simple mapping.
de_latitude: float = None
de_longitude: float = None
# General QSO info
# Reported mode, such as SSB, PHONE, CW, FT8... # Reported mode, such as SSB, PHONE, CW, FT8...
mode: str = None mode: str = None
# Inferred mode "family". One of "CW", "PHONE" or "DIGI". # Inferred mode "family". One of "CW", "PHONE" or "DIGI".
@@ -54,18 +87,14 @@ class Spot:
freq: float = None freq: float = None
# Band, defined by the frequency, e.g. "40m" or "70cm" # Band, defined by the frequency, e.g. "40m" or "70cm"
band: str = None band: str = None
# Time of the spot, UTC seconds since UNIX epoch
time: float = None
# Time of the spot, ISO 8601
time_iso: str = None
# Time that this software received the spot, UTC seconds since UNIX epoch. This is used with the "since_received"
# call to our API to receive all data that is new to us, even if by a quirk of the API it might be older than the
# list time the client polled the API.
received_time: float = None
# Time that this software received the spot, ISO 8601
received_time_iso: str = None
# Comment left by the spotter, if any # Comment left by the spotter, if any
comment: str = None comment: str = None
# QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
qrt: bool = False
# Special Interest Group info
# Special Interest Group (SIG), e.g. outdoor activity programme such as POTA # Special Interest Group (SIG), e.g. outdoor activity programme such as POTA
sig: str = None sig: str = None
# SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO # SIG references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO
@@ -76,17 +105,24 @@ class Spot:
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, 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" icon: str = "question"
# Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country
grid: str = None
# Latitude & longitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup # Timing info
latitude: float = None
longitude: float = None # Time of the spot, UTC seconds since UNIX epoch
# Location source. Indicates how accurate the location might be. Values: "SPOT", "QRZ, "DXCC", "NONE" time: float = None
location_source: str = "NONE" # Time of the spot, ISO 8601
# Location good. Indicates that the software thinks the location data is good enough to plot on a map. time_iso: str = None
location_good: bool = False # Time that this software received the spot, UTC seconds since UNIX epoch. This is used with the "since_received"
# QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments. # call to our API to receive all data that is new to us, even if by a quirk of the API it might be older than the
qrt: bool = False # list time the client polled the API.
received_time: float = None
# Time that this software received the spot, ISO 8601
received_time_iso: str = None
# Source info
# Where we got the spot from, e.g. "POTA", "Cluster"... # Where we got the spot from, e.g. "POTA", "Cluster"...
source: str = None source: str = None
# The ID the source gave it, if any. # The ID the source gave it, if any.
@@ -131,7 +167,9 @@ class Spot:
if self.de_call and "-" in self.de_call: if self.de_call and "-" in self.de_call:
self.de_call = self.de_call.split("-")[0] self.de_call = self.de_call.split("-")[0]
# Spotter country, continent, zones etc. from callsign # Spotter country, continent, zones etc. from callsign.
# DE of "RBNHOLE" and "SOTAMAT" are not things we can look up location for
if self.de_call != "RBNHOLE" and self.de_call != "SOTAMAT":
if self.de_call and not self.de_country: if self.de_call and not self.de_country:
self.de_country = lookup_helper.infer_country_from_callsign(self.de_call) self.de_country = lookup_helper.infer_country_from_callsign(self.de_call)
if self.de_call and not self.de_continent: if self.de_call and not self.de_continent:
@@ -165,15 +203,15 @@ class Spot:
if self.mode and not self.mode_type: if self.mode and not self.mode_type:
self.mode_type = lookup_helper.infer_mode_type_from_mode(self.mode) self.mode_type = lookup_helper.infer_mode_type_from_mode(self.mode)
# Grid to lat/lon and vice versa # DX Grid to lat/lon and vice versa
if self.grid and not self.latitude: if self.dx_grid and not self.dx_latitude:
ll = locator_to_latlong(self.grid) ll = locator_to_latlong(self.dx_grid)
self.latitude = ll[0] self.dx_latitude = ll[0]
self.longitude = ll[1] self.dx_longitude = ll[1]
if self.latitude and self.longitude and not self.grid: if self.dx_latitude and self.dx_longitude and not self.dx_grid:
self.grid = latlong_to_locator(self.latitude, self.longitude, 8) self.dx_grid = latlong_to_locator(self.dx_latitude, self.dx_longitude, 8)
if self.latitude: if self.dx_latitude:
self.location_source = "SPOT" self.dx_location_source = "SPOT"
# QRT comment detection # QRT comment detection
if self.comment and not self.qrt: if self.comment and not self.qrt:
@@ -184,26 +222,45 @@ class Spot:
# 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(self.dx_call)
if self.dx_call and not self.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_qrz(self.dx_call)
if latlon: if latlon:
self.latitude = latlon[0] self.dx_latitude = latlon[0]
self.longitude = latlon[1] self.dx_longitude = latlon[1]
self.grid = lookup_helper.infer_grid_from_callsign_qrz(self.dx_call) self.dx_grid = lookup_helper.infer_grid_from_callsign_qrz(self.dx_call)
self.location_source = "QRZ" self.dx_location_source = "QRZ"
# Last resort for getting a position, use the DXCC entity. # Last resort for getting a DX position, use the DXCC entity.
if self.dx_call and not self.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)
if latlon: if latlon:
self.latitude = latlon[0] self.dx_latitude = latlon[0]
self.longitude = latlon[1] self.dx_longitude = latlon[1]
self.grid = lookup_helper.infer_grid_from_callsign_dxcc(self.dx_call) self.dx_grid = lookup_helper.infer_grid_from_callsign_dxcc(self.dx_call)
self.location_source = "DXCC" self.dx_location_source = "DXCC"
# Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator # DX Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator
# is likely at home. # is likely at home.
self.location_good = self.location_source == "SPOT" or (self.location_source == "QRZ" and not "/" in self.dx_call) self.dx_location_good = self.dx_location_source == "SPOT" or (
self.dx_location_source == "QRZ" and not "/" in self.dx_call)
# DE of "RBNHOLE" and "SOTAMAT" are not things we can look up location for
if self.de_call != "RBNHOLE" and self.de_call != "SOTAMAT":
# DE operator position lookup, using QRZ.com.
if self.de_call and not self.de_latitude:
latlon = lookup_helper.infer_latlon_from_callsign_qrz(self.de_call)
if latlon:
self.de_latitude = latlon[0]
self.de_longitude = latlon[1]
self.de_grid = lookup_helper.infer_grid_from_callsign_qrz(self.de_call)
# Last resort for getting a DE position, use the DXCC entity.
if self.de_call and not self.de_latitude:
latlon = lookup_helper.infer_latlon_from_callsign_dxcc(self.de_call)
if latlon:
self.de_latitude = latlon[0]
self.de_longitude = latlon[1]
self.de_grid = lookup_helper.infer_grid_from_callsign_dxcc(self.de_call)
# 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

@@ -45,8 +45,8 @@ class APRSIS(SpotProvider):
dx_aprs_ssid=dx_aprs_ssid, dx_aprs_ssid=dx_aprs_ssid,
de_call=data["via"], de_call=data["via"],
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, dx_latitude=data["latitude"] if "latitude" in data else None,
longitude=data["longitude"] if "longitude" in data else None, dx_longitude=data["longitude"] if "longitude" in data else None,
icon="tower-cell", icon="tower-cell",
time=datetime.now(pytz.UTC).timestamp()) # APRS-IS spots are live so we can assume spot time is "now" time=datetime.now(pytz.UTC).timestamp()) # APRS-IS spots are live so we can assume spot time is "now"

View File

@@ -38,9 +38,9 @@ class GMA(HTTPSpotProvider):
sig_refs_names=[source_spot["NAME"]], sig_refs_names=[source_spot["NAME"]],
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(),
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,
# Seen GMA spots with no (or empty) lat/lon # Seen GMA spots with no (or empty) lat/lon
longitude=float(source_spot["LON"]) if (source_spot["LON"] and source_spot["LON"] != "") else None) dx_longitude=float(source_spot["LON"]) if (source_spot["LON"] and source_spot["LON"] != "") else None)
# GMA doesn't give what programme (SIG) the reference is for until we separately look it up. # GMA doesn't give what programme (SIG) the reference is for until we separately look it up.
ref_response = self.REF_INFO_CACHE.get(self.REF_INFO_URL_ROOT + source_spot["REF"], ref_response = self.REF_INFO_CACHE.get(self.REF_INFO_URL_ROOT + source_spot["REF"],

View File

@@ -56,8 +56,8 @@ class HEMA(HTTPSpotProvider):
sig_refs_names=[spot_items[4]], sig_refs_names=[spot_items[4]],
icon="mound", icon="mound",
time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M").replace(tzinfo=pytz.UTC).timestamp(), time=datetime.strptime(spot_items[0], "%d/%m/%Y %H:%M").replace(tzinfo=pytz.UTC).timestamp(),
latitude=float(spot_items[7]), dx_latitude=float(spot_items[7]),
longitude=float(spot_items[8])) dx_longitude=float(spot_items[8]))
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do # Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
# that for us. # that for us.

View File

@@ -63,9 +63,9 @@ class ParksNPeaks(HTTPSpotProvider):
siota_dr = csv.DictReader(siota_csv_data.content.decode().splitlines()) siota_dr = csv.DictReader(siota_csv_data.content.decode().splitlines())
for row in siota_dr: for row in siota_dr:
if row["SILO_CODE"] == spot.sig_refs[0]: if row["SILO_CODE"] == spot.sig_refs[0]:
spot.latitude = float(row["LAT"]) spot.dx_latitude = float(row["LAT"])
spot.longitude = float(row["LON"]) spot.dx_longitude = float(row["LON"])
spot.grid = row["LOCATOR"] spot.dx_grid = row["LOCATOR"]
break break
# ZLOTA name/lat/lon lookup # ZLOTA name/lat/lon lookup
@@ -74,8 +74,8 @@ class ParksNPeaks(HTTPSpotProvider):
for asset in zlota_data: for asset in zlota_data:
if asset["code"] == spot.sig_refs[0]: if asset["code"] == spot.sig_refs[0]:
spot.sig_refs_names = [asset["name"]] spot.sig_refs_names = [asset["name"]]
spot.latitude = asset["y"] spot.dx_latitude = asset["y"]
spot.longitude = asset["x"] spot.dx_longitude = asset["x"]
# Junk the "DE call", PNP always returns "ZLOTA" as the spotter for ZLOTA spots # Junk the "DE call", PNP always returns "ZLOTA" as the spotter for ZLOTA spots
spot.de_call = None spot.de_call = None
break break

View File

@@ -31,9 +31,9 @@ class POTA(HTTPSpotProvider):
sig_refs_names=[source_spot["name"]], sig_refs_names=[source_spot["name"]],
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(),
grid=source_spot["grid6"], dx_grid=source_spot["grid6"],
latitude=source_spot["latitude"], dx_latitude=source_spot["latitude"],
longitude=source_spot["longitude"]) dx_longitude=source_spot["longitude"])
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do # Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
# that for us. # that for us.

View File

@@ -58,9 +58,9 @@ class SOTA(HTTPSpotProvider):
try: try:
summit_response = self.SUMMIT_DATA_CACHE.get(self.SUMMIT_URL_ROOT + source_spot["summitCode"], headers=HTTP_HEADERS) summit_response = self.SUMMIT_DATA_CACHE.get(self.SUMMIT_URL_ROOT + source_spot["summitCode"], headers=HTTP_HEADERS)
summit_data = summit_response.json() summit_data = summit_response.json()
spot.grid = summit_data["locator"] spot.dx_grid = summit_data["locator"]
spot.latitude = summit_data["latitude"] spot.dx_latitude = summit_data["latitude"]
spot.longitude = summit_data["longitude"] spot.dx_longitude = summit_data["longitude"]
except Exception: except Exception:
logging.warn("Looking up summit " + source_spot["summitCode"] + " from the SOTA API failed. No summit data was available.") logging.warn("Looking up summit " + source_spot["summitCode"] + " from the SOTA API failed. No summit data was available.")

View File

@@ -35,9 +35,9 @@ class WWBOTA(SSESpotProvider):
time=datetime.fromisoformat(source_spot["time"]).timestamp(), time=datetime.fromisoformat(source_spot["time"]).timestamp(),
# 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.
grid=source_spot["references"][0]["locator"], dx_grid=source_spot["references"][0]["locator"],
latitude=source_spot["references"][0]["lat"], dx_latitude=source_spot["references"][0]["lat"],
longitude=source_spot["references"][0]["long"], dx_longitude=source_spot["references"][0]["long"],
qrt=source_spot["type"] == "QRT") qrt=source_spot["type"] == "QRT")
# WWBOTA does support a special "Test" spot type, we need to avoid adding that. # WWBOTA does support a special "Test" spot type, we need to avoid adding that.

View File

@@ -31,8 +31,8 @@ class WWFF(HTTPSpotProvider):
sig_refs_names=[source_spot["reference_name"]], sig_refs_names=[source_spot["reference_name"]],
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(),
latitude=source_spot["latitude"], dx_latitude=source_spot["latitude"],
longitude=source_spot["longitude"]) dx_longitude=source_spot["longitude"])
# Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do # Add to our list. Don't worry about de-duping, removing old spots etc. at this point; other code will do
# that for us. # that for us.

View File

@@ -461,10 +461,6 @@ components:
type: string type: string
description: Callsign of the operator that has been spotted description: Callsign of the operator that has been spotted
example: M0TRT example: M0TRT
de_call:
type: string
description: Callsign of the operator that has spotted them
example: M0TEST
dx_name: dx_name:
type: string type: string
description: Name of the operator that has been spotted description: Name of the operator that has been spotted
@@ -473,18 +469,10 @@ components:
type: string type: string
description: Country of the DX operator description: Country of the DX operator
example: United Kingdom example: United Kingdom
de_country:
type: string
description: Country of the spotter
example: United Kingdom
dx_flag: dx_flag:
type: string type: string
description: Country flag of the DX operator description: Country flag of the DX operator
example: "" example: ""
de_flag:
type: string
description: Country flag of the spotter
example: ""
dx_continent: dx_continent:
type: string type: string
description: Continent of the DX operator description: Continent of the DX operator
@@ -497,26 +485,10 @@ components:
- OC - OC
- AN - AN
example: EU example: EU
de_continent:
type: string
enum:
- EU
- NA
- SA
- AS
- AF
- OC
- AN
description: Continent of the spotter
example: EU
dx_dxcc_id: dx_dxcc_id:
type: integer type: integer
description: DXCC ID of the DX operator description: DXCC ID of the DX operator
example: 235 example: 235
de_dxcc_id:
type: integer
description: DXCC ID of the spotter
example: 235
dx_cq_zone: dx_cq_zone:
type: integer type: integer
description: CQ zone of the DX operator description: CQ zone of the DX operator
@@ -529,6 +501,71 @@ components:
type: string type: string
description: If this is an APRS spot, what SSID was the DX operator using? description: If this is an APRS spot, what SSID was the DX operator using?
example: "" example: ""
dx_grid:
type: string
description: Maidenhead grid locator for the DX spot. This could be from a geographical reference e.g. POTA, or just from the country
example: IO91aa
dx_latitude:
type: number
description: Latitude of the DX spot, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup
example: 51.2345
dx_longitude:
type: number
description: Longitude of the DX spot, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup
example: -1.2345
dx_location_source:
type: string
description: Where we got the DX location (grid/latitude/longitude) from. If this was from the spot itself, it's likely quite accurate, but if we had to fall back to QRZ lookup, or even a location based on the DXCC itself, it will be a lot less accurate.
enum:
- SPOT
- QRZ
- DXCC
- NONE
example: SPOT
dx_location_good:
type: boolean
description: Does the software think the location is good enough to put a marker on a map? This is true if the source is "SPOT", or alternatively if the source is "QRZ" and the callsign doesn't have a slash in it (i.e. operator likely at home).
example: true
de_call:
type: string
description: Callsign of the operator that has spotted them
example: M0TEST
de_country:
type: string
description: Country of the spotter
example: United Kingdom
de_flag:
type: string
description: Country flag of the spotter
example: ""
de_continent:
type: string
enum:
- EU
- NA
- SA
- AS
- AF
- OC
- AN
description: Continent of the spotter
example: EU
de_dxcc_id:
type: integer
description: DXCC ID of the spotter
example: 235
de_grid:
type: string
description: Maidenhead grid locator for the spotter. This is not going to be from a xOTA reference so it will likely just be a QRZ or DXCC lookup. If the spotter is also portable, this is probably wrong, but it's good enough for some simple mapping.
example: IO91aa
de_latitude:
type: number
description: Latitude of the spotter, in degrees. This is not going to be from a xOTA reference so it will likely just be a QRZ or DXCC lookup. If the spotter is also portable, this is probably wrong, but it's good enough for some simple mapping.
example: 51.2345
de_longitude:
type: number
description: Longitude of the DX spotspotter, in degrees. This is not going to be from a xOTA reference so it will likely just be a QRZ or DXCC lookup. If the spotter is also portable, this is probably wrong, but it's good enough for some simple mapping.
example: -1.2345
mode: mode:
type: string type: string
description: Reported mode. description: Reported mode.
@@ -651,31 +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
grid:
type: string
description: Maidenhead grid locator for the spot. This could be from a geographical reference e.g. POTA, or just from the country
example: IO91aa
latitude:
type: number
description: Latitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup
example: 51.2345
longitude:
type: number
description: Latitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup
example: -1.2345
location_source:
type: string
description: Where we got the location (grid/latitude/longitude) from. If this was from the spot itself, it's likely quite accurate, but if we had to fall back to QRZ lookup, or even a location based on the DXCC itself, it will be a lot less accurate.
enum:
- SPOT
- QRZ
- DXCC
- NONE
example: SPOT
location_good:
type: boolean
description: Does the software think the location is good enough to put a marker on a map? This is true if the source is "SPOT", or alternatively if the source is "QRZ" and the callsign doesn't have a slash in it (i.e. operator likely at home).
example: true
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.

View File

@@ -134,11 +134,11 @@ function updateTable() {
// Format bearing text // Format bearing text
var bearingText = "---<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service, and we could not determine one. A bearing to this DX is not available.'></i></span>"; var bearingText = "---<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service, and we could not determine one. A bearing to this DX is not available.'></i></span>";
if (userPos != null && s["latitude"] != null && s["longitude"] != null) { if (userPos != null && s["dx_latitude"] != null && s["dx_longitude"] != null) {
var bearing = calcBearing(userPos[0], userPos[1], s["latitude"], s["longitude"]); var bearing = calcBearing(userPos[0], userPos[1], s["dx_latitude"], s["dx_longitude"]);
bearingText = bearing.toFixed(0).padStart(3, '0') + "°"; bearingText = bearing.toFixed(0).padStart(3, '0') + "°";
if (s["location_good"] == null || s["location_good"] == false) { if (s["dx_location_good"] == null || s["dx_location_good"] == false) {
if (s["location_source"] == "QRZ") { if (s["dx_location_source"] == "QRZ") {
bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to a QRZ \"home\" location for a portable/mobile/alternative spot, so this bearing may not be accurate if the DX is close to you..'></i></span>"; bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to a QRZ \"home\" location for a portable/mobile/alternative spot, so this bearing may not be accurate if the DX is close to you..'></i></span>";
} else { } else {
bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to just using the centre of a DXCC entity, so this bearing may not be accurate if the DX is close to you.'></i></span>"; bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to just using the centre of a DXCC entity, so this bearing may not be accurate if the DX is close to you.'></i></span>";