mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
80 lines
3.5 KiB
Python
80 lines
3.5 KiB
Python
import re
|
|
from time import sleep
|
|
from typing import Any
|
|
|
|
import requests
|
|
from fastkml import kml
|
|
from fastkml.containers import Document, Folder
|
|
from fastkml.features import Placemark
|
|
from fastkml.geometry import Point
|
|
from pyhamtools.locator import latlong_to_locator
|
|
|
|
from core.enums import ActivityRefType
|
|
from data.activity_ref import ActivityRef
|
|
from providers.activityrefdata.file_download_activity_ref_data_provider import (
|
|
FileDownloadActivityRefDataProvider,
|
|
)
|
|
|
|
|
|
class ParksNPeaksKMLActivityRefDataProvider(FileDownloadActivityRefDataProvider):
|
|
"""Base class for activity ref data providers that use parksnpeaks.org KML POI feeds and have references that
|
|
use the VKFF refs rather than their own system (i.e. KRMNPA and SANPCPA)."""
|
|
|
|
REF_PATTERN = re.compile(r"VKFF-\d+")
|
|
|
|
def __init__(self, sig_name: str, provider_config: dict[str, Any], url: str, poll_interval: float) -> None:
|
|
"""Set up the provider, note poll_interval is in *days*."""
|
|
super().__init__(sig_name, provider_config, url, poll_interval)
|
|
|
|
def _http_response_to_data(self, http_response: requests.Response) -> list[ActivityRef]:
|
|
new_data: list[ActivityRef] = []
|
|
|
|
# KML content may carry an XML encoding declaration, which lxml's parser (used internally here) refuses to
|
|
# accept as a decoded str, so bytes must be passed even though the type stub only declares str.
|
|
k = kml.KML.from_string(http_response.content) # type: ignore[arg-type]
|
|
|
|
for document in k.features:
|
|
if not isinstance(document, Document):
|
|
continue
|
|
for folder in document.features:
|
|
if not isinstance(folder, Folder):
|
|
continue
|
|
for placemark in folder.features:
|
|
if not isinstance(placemark, Placemark):
|
|
continue
|
|
description = placemark.description or ""
|
|
match = self.REF_PATTERN.search(description)
|
|
if not match:
|
|
# No VKFF reference found in this placemark - skip it (e.g. non-park waypoints)
|
|
continue
|
|
ref_id = match.group(0)
|
|
|
|
if not isinstance(placemark.geometry, Point):
|
|
# Not a point location (e.g. a boundary polygon) - skip it, we can't get a single lat/lon from it
|
|
continue
|
|
longitude, latitude = placemark.geometry.x, placemark.geometry.y
|
|
|
|
ref = ActivityRef(
|
|
sig=self.sig_name,
|
|
id=ref_id,
|
|
name=placemark.name,
|
|
ref_type=ActivityRefType.PARK,
|
|
url=f"https://parksnpeaks.org/getPark.php?actPark={ref_id}",
|
|
latitude=latitude,
|
|
longitude=longitude,
|
|
)
|
|
if latitude and longitude:
|
|
ref.grid = latlong_to_locator(latitude, longitude, 6)
|
|
new_data.append(ref)
|
|
|
|
# Bail out if a stop has been requested, i.e. the program is shutting down - no need to
|
|
# parse the rest of the data in this case
|
|
if self._stop_event.is_set():
|
|
return new_data
|
|
|
|
# Very short pause. This will extend the time to handle activity refs by a few seconds but will ensure some time
|
|
# is available for other threads e.g. the web server.
|
|
sleep(0.001)
|
|
|
|
return new_data
|