import csv import logging from pyhamtools.locator import latlong_to_locator, locator_to_latlong from core.cache_utils import SEMI_STATIC_URL_DATA_CACHE from core.constants import SIGS, HTTP_HEADERS from core.geo_utils import wab_wai_square_to_lat_lon # Load Spanish municipality data for the DME programme. There's no convenient lookup API for this, so we embed the data # file in Spothole and load it on startup. with open("datafiles/MUNICIPIOS.csv", encoding="latin-1") as _f: _DME_INDEX = {row["COD_INE"][:5]: row for row in csv.DictReader(_f, delimiter=";")} def get_ref_regex_for_sig(sig): """Utility function to get the regex string for a SIG reference for a named SIG. If no match is found, None will be returned.""" for s in SIGS: if s.name.upper() == sig.upper(): return s.ref_regex return None def populate_sig_ref_info(sig_ref): """Look up details of a SIG reference (e.g. POTA park) such as name, lat/lon, and grid. Takes in a sig_ref object which must at minimum have a "sig" and an "id". The rest of the object will be populated and returned. Note there is currently no support for KRMNPA location lookup, see issue #61.""" if sig_ref.sig is None or sig_ref.id is None: logging.warning("Failed to look up sig_ref info, sig or id were not set.") sig = sig_ref.sig or "" ref_id = sig_ref.id try: if sig.upper() == "POTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://api.pota.app/park/" + ref_id, headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if data: fullname = str(data["name"]) if "name" in data else None if fullname and "parktypeDesc" in data and data["parktypeDesc"] != "": fullname = fullname + " " + data["parktypeDesc"] sig_ref.name = fullname sig_ref.url = "https://pota.app/#/park/" + ref_id sig_ref.grid = data["grid6"] if "grid6" 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 elif sig.upper() == "SOTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://api-db2.sota.org.uk/api/summits/" + ref_id, headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if data: sig_ref.name = data["name"] if "name" in data else None sig_ref.url = "https://www.sotadata.org.uk/en/summit/" + ref_id sig_ref.grid = data["locator"] if "locator" 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.activation_score = data["points"] if "points" in data else None elif sig.upper() == "WWBOTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://api.wwbota.org/bunkers/" + ref_id, headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if data: sig_ref.name = data["name"] if "name" in data else None sig_ref.url = "https://bunkerwiki.org/?s=" + ref_id if ref_id.startswith("B/G") else None sig_ref.grid = data["locator"] if "locator" in data else None sig_ref.latitude = data["lat"] if "lat" in data else None sig_ref.longitude = data["long"] if "long" in data else None elif sig.upper() == "GMA" or sig.upper() == "ARLHS" or sig.upper() == "ILLW" or sig.upper() == "WCA" or sig.upper() == "MOTA" or sig.upper() == "IOTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://www.cqgma.org/api/ref/?" + ref_id, headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if data: sig_ref.name = data["name"] if "name" in data else None sig_ref.url = "https://www.cqgma.org/zinfo.php?ref=" + ref_id sig_ref.grid = data["locator"] if "locator" 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 elif sig.upper() == "WWFF": wwff_response = SEMI_STATIC_URL_DATA_CACHE.get("https://wwff.co/wwff-data/wwff_directory.csv", headers=HTTP_HEADERS) if not wwff_response.ok: logging.warning("HTTP %d looking up %s ref %s", wwff_response.status_code, sig, ref_id) return sig_ref wwff_index = {row["reference"]: row for row in csv.DictReader(wwff_response.content.decode().splitlines())} row = wwff_index.get(ref_id) if row: sig_ref.name = row["name"] if "name" in row else None sig_ref.url = "https://wwff.co/directory/?showRef=" + ref_id sig_ref.grid = row["iaruLocator"] if "iaruLocator" in row and row["iaruLocator"] != "-" else None sig_ref.latitude = float(row["latitude"]) if "latitude" in row and row["latitude"] != "-" else None sig_ref.longitude = float(row["longitude"]) if "longitude" in row and row["longitude"] != "-" else None elif sig.upper() == "SIOTA": siota_response = SEMI_STATIC_URL_DATA_CACHE.get("https://www.silosontheair.com/data/silos.csv", headers=HTTP_HEADERS) if not siota_response.ok: logging.warning("HTTP %d looking up %s ref %s", siota_response.status_code, sig, ref_id) return sig_ref siota_index = {row["SILO_CODE"]: row for row in csv.DictReader(siota_response.content.decode().splitlines())} row = siota_index.get(ref_id) if row: sig_ref.name = row["NAME"] if "NAME" 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.longitude = float(row["LNG"]) if "LNG" in row else None elif sig.upper() == "WOTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://www.wota.org.uk/mapping/data/summits.json", headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if data: for feature in data.get("features", []): if feature["properties"]["wotaId"] == ref_id: sig_ref.name = feature["properties"]["title"] # Fudge WOTA URLs. Outlying fell (LDO) URLs don't match their ID numbers but require 214 to be # added to them sig_ref.url = "https://www.wota.org.uk/MM_" + ref_id if ref_id.upper().startswith("LDO-"): number = int(ref_id.upper().replace("LDO-", "")) sig_ref.url = "https://www.wota.org.uk/MM_LDO-" + str(number + 214) sig_ref.grid = feature["properties"]["qthLocator"] sig_ref.latitude = feature["geometry"]["coordinates"][1] sig_ref.longitude = feature["geometry"]["coordinates"][0] break elif sig.upper() == "ZLOTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://ontheair.nz/assets/assets.json", headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if isinstance(data, list): for asset in data: if asset["code"] == ref_id: sig_ref.name = asset["name"] sig_ref.url = "https://ontheair.nz/assets/ZLI_OT-030" + ref_id.replace("/", "_") try: sig_ref.grid = latlong_to_locator(asset["y"], asset["x"], 6) except: logging.debug("Invalid lat/lon received for reference") sig_ref.latitude = asset["y"] sig_ref.longitude = asset["x"] break elif sig.upper() == "BOTA": if not sig_ref.name: sig_ref.name = sig_ref.id sig_ref.url = "https://www.beachesontheair.com/beaches/" + sig_ref.name.lower().replace(" ", "-") elif sig.upper() == "LLOTA": response = SEMI_STATIC_URL_DATA_CACHE.get("https://llota.app/api/public/references", headers=HTTP_HEADERS) if not response.ok: logging.warning("HTTP %d looking up %s ref %s", response.status_code, sig, ref_id) data = response.json() if response.ok else None if isinstance(data, list): for ref in data: if ref["reference_code"] == ref_id: sig_ref.name = str(ref["name"]) sig_ref.url = "https://llota.app/list/ref/" + ref_id sig_ref.grid = str(ref["grid_locator"]) ll = locator_to_latlong(sig_ref.grid) sig_ref.latitude = ll[0] sig_ref.longitude = ll[1] break elif sig.upper() == "WWTOTA": if not sig_ref.name: sig_ref.name = sig_ref.id sig_ref.url = "https://wwtota.com/seznam/karta_rozhledny.php?ref=" + str(sig_ref.name) elif sig.upper() == "TILES": # Tiles on the Air just uses Maidenhead 6-digit squares, so ID, Name and Grid are all the same if not sig_ref.name: sig_ref.name = sig_ref.id if not sig_ref.grid: sig_ref.grid = sig_ref.id if sig_ref.grid and not sig_ref.latitude: ll = locator_to_latlong(str(sig_ref.grid)) sig_ref.latitude = ll[0] sig_ref.longitude = ll[1] elif sig.upper() == "WAB" or sig.upper() == "WAI": ll = wab_wai_square_to_lat_lon(ref_id) if ll: sig_ref.name = ref_id try: sig_ref.grid = latlong_to_locator(ll[0], ll[1], 6) sig_ref.latitude = ll[0] sig_ref.longitude = ll[1] except: logging.debug("Invalid lat/lon received for reference") elif sig.upper() == "DME": # Zero-pad to 5 digits to match our source data row = _DME_INDEX.get(ref_id.zfill(5)) if row: sig_ref.name = row["NOMBRE_ACTUAL"] + ", " + row["PROVINCIA"] sig_ref.latitude = float(row["LATITUD_ETRS89_REGCAN95"].replace(",", ".")) if row.get("LATITUD_ETRS89_REGCAN95") else None sig_ref.longitude = float(row["LONGITUD_ETRS89_REGCAN95"].replace(",", ".")) if row.get("LONGITUD_ETRS89_REGCAN95") else None if sig_ref.latitude and sig_ref.longitude: try: sig_ref.grid = latlong_to_locator(sig_ref.latitude, sig_ref.longitude, 6) except Exception: logging.debug("Invalid lat/lon received for reference") except Exception: logging.warning("Failed to look up sig_ref info for " + sig + " ref " + ref_id, exc_info=True) return sig_ref # Regex matching any SIG ANY_SIG_REGEX = r"(" + r"|".join(list(map(lambda p: p.name, SIGS))) + r")" # Regex matching any SIG reference ANY_XOTA_SIG_REF_REGEX = r"[\w\/]+\-\d+"