mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 14:27:42 +00:00
157 lines
7.4 KiB
Python
157 lines
7.4 KiB
Python
import logging
|
|
import re
|
|
|
|
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
|
|
|
|
from core.constants import ACTIVITIES
|
|
from core.data_store import DATA_STORE
|
|
from core.enums import ActivityRefType
|
|
from core.geo_utils import wab_wai_square_to_lat_lon
|
|
from data.activity_ref import ActivityRef
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_activity_ref_info(activity_name, ref_id):
|
|
"""Look up details of an activity reference (e.g. POTA park) such as name, lat/lon, and grid. Takes in an
|
|
activity name and a reference ID (both strings) and returns an ActivityRef object populated with as much data
|
|
as we can find. This makes use of activity ref data in the data store, live lookups from the web, or just
|
|
automatic calculation depending on which activity we are getting data for."""
|
|
|
|
if activity_name is None or activity_name == "" or ref_id is None or ref_id == "":
|
|
logger.debug("Failed to look up activity ref info, activity or ref were not set.")
|
|
return None
|
|
|
|
# Sometimes we allow spaces instead of dashes in references due to common usage that way, but official reference
|
|
# lists never do, so convert them here.
|
|
ref_id = ref_id.replace(" ", "-")
|
|
|
|
# Prepare the object to be returned
|
|
activity_ref = ActivityRef(sig=activity_name, id=ref_id)
|
|
|
|
# We can always get the reference type and the icon from the activity itself
|
|
for activity in ACTIVITIES:
|
|
if activity.name.upper() == activity_name.upper():
|
|
activity_ref.ref_type = activity.ref_type
|
|
activity_ref.icon = activity.icon
|
|
|
|
try:
|
|
### FUDGES ###
|
|
#
|
|
# DME fudge. Our database has leading zeros padding to 5 digits which is the expected format, but not all
|
|
# activators add leading zeros. We also need to normalise "DME 01234" to "DME-01234" to match what's in our
|
|
# database.
|
|
if activity_name.upper() == "DME":
|
|
match = re.match(r"DME[\- ](\d{3,5})", ref_id, re.IGNORECASE)
|
|
if match:
|
|
number = match.group(1)
|
|
ref_id = f"DME-{number.zfill(5)}"
|
|
|
|
# DTMBA spotters sometimes include spaces and dashes, our regex allows them but they must be removed here so we
|
|
# can look up against the official list which doesn't have them
|
|
if activity_name.upper() == "DTMBA":
|
|
ref_id = ref_id.replace("-", "").replace(" ", "")
|
|
|
|
### NO DATA ACTIVITIES ###
|
|
#
|
|
# If the activity is HEMA or BIWOTA, we have no way to either generate useful data or look it up on a
|
|
# reference list, so just skip the lookup here.
|
|
if activity_name.upper() == "HEMA" or activity_name.upper() == "BIWOTA":
|
|
return activity_ref
|
|
|
|
### PROGRAMMATIC DATA GENERATION INSTEAD OF LOOKUPS ###
|
|
#
|
|
# If the activity is Tiles, WAB, WAI or BOTA (Beaches), we don't have anything to look up from the data
|
|
# store, we can calculate all the information we are going to get directly.
|
|
if activity_name.upper() == "TILES":
|
|
# Tiles on the Air just uses Maidenhead 6-digit squares, so ID, Name and Grid are all the same
|
|
if not activity_ref.name:
|
|
activity_ref.name = activity_ref.id
|
|
if not activity_ref.grid:
|
|
activity_ref.grid = activity_ref.id
|
|
if activity_ref.grid and (not activity_ref.latitude or not activity_ref.longitude):
|
|
ll = locator_to_latlong(str(activity_ref.grid))
|
|
activity_ref.latitude = ll[0]
|
|
activity_ref.longitude = ll[1]
|
|
return activity_ref
|
|
|
|
elif activity_name.upper() == "WAB" or activity_name.upper() == "WAI":
|
|
ll = wab_wai_square_to_lat_lon(ref_id)
|
|
if ll:
|
|
activity_ref.name = ref_id
|
|
try:
|
|
activity_ref.grid = latlong_to_locator(ll[0], ll[1], 6)
|
|
activity_ref.latitude = ll[0]
|
|
activity_ref.longitude = ll[1]
|
|
except Exception:
|
|
logger.warning("Invalid lat/lon received for WAB/WAI reference")
|
|
return activity_ref
|
|
|
|
elif activity_name.upper() == "BOTA":
|
|
# For BOTA all we can ever generate is the URL, there is no data file or lookup for lat/longs
|
|
if not activity_ref.name:
|
|
activity_ref.name = activity_ref.id
|
|
if activity_ref.name:
|
|
activity_ref.url = (
|
|
f"https://www.beachesontheair.com/beaches/{activity_ref.name.lower().replace(' ', '-')}"
|
|
)
|
|
return activity_ref
|
|
|
|
elif activity_name.upper() == "GMA Islands":
|
|
# GMA Islands is a bit of a mess of GMA and IOTA references. Try looking them both up and see what returns
|
|
# the best result.
|
|
iota_lookup = get_activity_ref_info("IOTA", ref_id)
|
|
gma_lookup = get_activity_ref_info("GMA", ref_id)
|
|
for key, value in iota_lookup.__dict__.items():
|
|
if value is not None and activity_ref.__dict__.get(key) is None:
|
|
activity_ref.__dict__[key] = value
|
|
for key, value in gma_lookup.__dict__.items():
|
|
if value is not None and activity_ref.__dict__.get(key) is None:
|
|
activity_ref.__dict__[key] = value
|
|
activity_ref.ref_type = ActivityRefType.ISLAND
|
|
return activity_ref
|
|
|
|
### ACTUAL LOOKUP ###
|
|
#
|
|
# OK, this is something we have to look up. Now check to see if our data store contains reference data and if
|
|
# so, copy the data into the activity_ref object
|
|
key = f"{activity_name}:{ref_id}"
|
|
try:
|
|
lookup_data = DATA_STORE.activity_refs.get(key) if key in DATA_STORE.activity_refs else None
|
|
if lookup_data:
|
|
for attr, value in lookup_data.__dict__.items():
|
|
if value is not None and activity_ref.__dict__.get(attr) is None:
|
|
activity_ref.__dict__[attr] = value
|
|
|
|
else:
|
|
# Maybe a super new reference we don't know about yet, but more likely a typo or a test reference,
|
|
# just silently ignore it.
|
|
logger.debug(f"{activity_name} database did not contain data for ref {ref_id}")
|
|
|
|
except (ValueError, KeyError):
|
|
# Catch exceptions due to e.g. old versions of objects in the cache that are no longer compatible,
|
|
# and remove them from the cache.
|
|
del DATA_STORE.activity_refs[key]
|
|
return None
|
|
|
|
except Exception:
|
|
logger.exception(f"Exception when looking up activity ref info for {activity_name} ref {ref_id}")
|
|
return activity_ref
|
|
|
|
|
|
def populate_missing_activity_ref_info(activity_ref):
|
|
"""Look up details of an activity reference (e.g. POTA park) such as name, lat/lon, and grid. Takes in an
|
|
activity_ref object which must at minimum have a "sig" and an "id". The rest of the object will be populated
|
|
and returned. Any data currently in the object will be kept, only missing data in the object will be populated
|
|
if it can be determined."""
|
|
|
|
lookup_data = get_activity_ref_info(activity_ref.sig, activity_ref.id)
|
|
|
|
if lookup_data:
|
|
# Copy new activity ref data into existing object where data was previously missing
|
|
for key, value in lookup_data.__dict__.items():
|
|
if value is not None and activity_ref.__dict__.get(key) is None:
|
|
activity_ref.__dict__[key] = value
|
|
|
|
return activity_ref
|