diff --git a/core/utils.py b/core/utils.py index a352788..cc92cd8 100644 --- a/core/utils.py +++ b/core/utils.py @@ -2,6 +2,7 @@ import logging from datetime import datetime from pyhamtools import LookupLib, Callinfo +from pyhamtools.locator import latlong_to_locator from core.config import config from core.constants import BANDS, UNKNOWN_BAND, CW_MODES, PHONE_MODES, DATA_MODES, ALL_MODES @@ -105,21 +106,34 @@ def infer_name_from_callsign(call): return None # Infer a latitude and longitude from a callsign (requires QRZ.com) -def infer_latlon_from_callsign(call): +def infer_latlon_from_callsign_qrz(call): data = get_qrz_data_for_callsign(call) if data and "latitude" in data and "longitude" in data: - return [data["longitude"], data["longitude"]] + return [data["latitude"], data["longitude"]] else: return None # Infer a grid locator from a callsign (requires QRZ.com) -def infer_grid_from_callsign(call): +def infer_grid_from_callsign_qrz(call): data = get_qrz_data_for_callsign(call) if data and "locator" in data: return data["locator"] else: return None +# Infer a latitude and longitude from a callsign (using DXCC, probably very inaccurate) +def infer_latlon_from_callsign_dxcc(call): + data = CALL_INFO_BASIC.get_lat_long(call) + if data and "latitude" in data and "longitude" in data: + return [data["latitude"], data["longitude"]] + else: + return None + +# Infer a grid locator from a callsign (using DXCC, probably very inaccurate) +def infer_grid_from_callsign_dxcc(call): + latlon = infer_latlon_from_callsign_dxcc(call) + return latlong_to_locator(latlon[0], latlon[1], 8) + # Convert objects to serialisable things. Used by JSON serialiser as a default when it encounters unserializable things. # Converts datetimes to ISO. diff --git a/data/spot.py b/data/spot.py index f51d852..50f3761 100644 --- a/data/spot.py +++ b/data/spot.py @@ -8,7 +8,8 @@ from pyhamtools.locator import locator_to_latlong, latlong_to_locator from core.constants import DXCC_FLAGS from core.utils import infer_mode_family_from_mode, infer_band_from_freq, infer_continent_from_callsign, \ infer_country_from_callsign, infer_cq_zone_from_callsign, infer_itu_zone_from_callsign, infer_dxcc_id_from_callsign, \ - infer_mode_from_comment, infer_name_from_callsign, infer_latlon_from_callsign, infer_grid_from_callsign + infer_mode_from_comment, infer_name_from_callsign, infer_latlon_from_callsign_dxcc, infer_grid_from_callsign_dxcc, \ + infer_latlon_from_callsign_qrz, infer_grid_from_callsign_qrz # Data class that defines a spot. @@ -76,6 +77,10 @@ class Spot: # Latitude & longitude, in degrees. This could be from a geographical reference e.g. POTA, or from a QRZ lookup latitude: float = None longitude: float = None + # Location source. Indicates how accurate the location might be. Values: "SPOT", "QRZ, "DXCC", "NONE" + location_source: str = None + # Location good. Indicates that the software thinks the location data is good enough to plot on a map. + location_good: bool = None # QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments. qrt: bool = None # Where we got the spot from, e.g. "POTA", "Cluster"... @@ -129,6 +134,8 @@ class Spot: self.longitude = ll[1] if self.latitude and self.longitude and not self.grid: self.grid = latlong_to_locator(self.latitude, self.longitude, 8) + if self.latitude: + self.location_source = "SPOT" # QRT comment detection if self.comment and not self.qrt: @@ -140,12 +147,24 @@ class Spot: if self.dx_call and not self.dx_name: self.dx_name = infer_name_from_callsign(self.dx_call) if self.dx_call and not self.latitude: - latlon = infer_latlon_from_callsign(self.dx_call) + latlon = infer_latlon_from_callsign_qrz(self.dx_call) if latlon: self.latitude = latlon[0] self.longitude = latlon[1] - if self.dx_call and not self.grid: - self.grid = infer_grid_from_callsign(self.dx_call) + self.grid = infer_grid_from_callsign_qrz(self.dx_call) + self.location_source = "QRZ" + + # Last resort for getting a position, use the DXCC entity. + if self.dx_call and not self.latitude: + latlon = infer_latlon_from_callsign_dxcc(self.dx_call) + self.latitude = latlon[0] + self.longitude = latlon[1] + self.grid = infer_grid_from_callsign_dxcc(self.dx_call) + self.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 + # is likely at home. + self.location_good = self.location_source == "SPOT" or (self.location_source == "QRZ" and not "/" in self.dx_call) # JSON serialise def to_json(self): diff --git a/webassets/apidocs/openapi.yml b/webassets/apidocs/openapi.yml index f9cd0cd..1d56299 100644 --- a/webassets/apidocs/openapi.yml +++ b/webassets/apidocs/openapi.yml @@ -417,6 +417,19 @@ components: 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: type: boolean description: QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.