Refactor of caching & data storage part 8 #118

This commit is contained in:
Ian Renton
2026-08-01 11:13:02 +01:00
parent 41aecc6007
commit 5bfe480f7c
6 changed files with 106 additions and 41 deletions
+6
View File
@@ -172,6 +172,12 @@ static-data-providers:
- class: "K0SWE" - class: "K0SWE"
enabled: true enabled: true
- class: "CQZoneData"
enabled: true
- class: "ITUZoneData"
enabled: true
# SIG reference data providers to use. This allows Spothole to download, for example, the WWFF directory that maps WWFF # SIG reference data providers to use. This allows Spothole to download, for example, the WWFF directory that maps WWFF
# park IDs to their name and location. # park IDs to their name and location.
+6
View File
@@ -16,10 +16,12 @@ class DataStore:
lookup data using different caching strategies for each.""" lookup data using different caching strategies for each."""
def __init__(self): def __init__(self):
# Constants
self._MAX_SPOT_COUNT = 100000 self._MAX_SPOT_COUNT = 100000
self._MAX_ALERT_COUNT = 100000 self._MAX_ALERT_COUNT = 100000
self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC = 300 self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC = 300
self._CALLSIGN_DATA_TTL_SEC = 30 * 24 * 60 * 60 self._CALLSIGN_DATA_TTL_SEC = 30 * 24 * 60 * 60
# Caches
self.alerts = None self.alerts = None
self.spots = None self.spots = None
self.callsigns = None self.callsigns = None
@@ -30,6 +32,10 @@ class DataStore:
self._status = None self._status = None
self.solar_conditions = None self.solar_conditions = None
self._solar = None self._solar = None
# ITU/CQ zone GeoJSON data is only ever loaded statically from a local file so these don't even need to be
# caches, they can just be straight objects
self.cq_zone_data = None
self.itu_zone_data = None
def setup(self): def setup(self):
Path(CACHE_DIR).mkdir(parents=True, exist_ok=True) Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
+6 -14
View File
@@ -1,32 +1,23 @@
import json
import logging import logging
import re import re
from math import floor from math import floor
import geopandas
from pyproj import Transformer from pyproj import Transformer
from shapely import prepare
from shapely.geometry import Point, Polygon from shapely.geometry import Point, Polygon
from core.data_store import DATA_STORE
TRANSFORMER_OS_GRID_TO_WGS84 = Transformer.from_crs("EPSG:27700", "EPSG:4326") TRANSFORMER_OS_GRID_TO_WGS84 = Transformer.from_crs("EPSG:27700", "EPSG:4326")
TRANSFORMER_IRISH_GRID_TO_WGS84 = Transformer.from_crs("EPSG:29903", "EPSG:4326") TRANSFORMER_IRISH_GRID_TO_WGS84 = Transformer.from_crs("EPSG:29903", "EPSG:4326")
TRANSFORMER_CI_UTM_GRID_TO_WGS84 = Transformer.from_crs("+proj=utm +zone=30 +ellps=WGS84", "EPSG:4326") TRANSFORMER_CI_UTM_GRID_TO_WGS84 = Transformer.from_crs("+proj=utm +zone=30 +ellps=WGS84", "EPSG:4326")
with open("datafiles/cqzones.geojson") as f:
cq_zone_data = geopandas.GeoDataFrame.from_features(json.load(f)["features"])
with open("datafiles/ituzones.geojson") as f:
itu_zone_data = geopandas.GeoDataFrame.from_features(json.load(f)["features"])
for idx in cq_zone_data.index:
prepare(cq_zone_data.at[idx, 'geometry'])
for idx in itu_zone_data.index:
prepare(itu_zone_data.at[idx, 'geometry'])
def lat_lon_to_cq_zone(lat, lon): def lat_lon_to_cq_zone(lat, lon):
"""Finds out which CQ zone a lat/lon point is in.""" """Finds out which CQ zone a lat/lon point is in."""
if DATA_STORE.cq_zone_data is not None:
lon = ((lon + 180) % 360) - 180 lon = ((lon + 180) % 360) - 180
for index, row in cq_zone_data.iterrows(): for index, row in DATA_STORE.cq_zone_data.iterrows():
polygon = Polygon(row["geometry"]) polygon = Polygon(row["geometry"])
test_point = Point(lon, lat) test_point = Point(lon, lat)
if polygon.contains(test_point): if polygon.contains(test_point):
@@ -46,8 +37,9 @@ def lat_lon_to_cq_zone(lat, lon):
def lat_lon_to_itu_zone(lat, lon): def lat_lon_to_itu_zone(lat, lon):
"""Finds out which ITU zone a lat/lon point is in.""" """Finds out which ITU zone a lat/lon point is in."""
if DATA_STORE.itu_zone_data is not None:
lon = ((lon + 180) % 360) - 180 lon = ((lon + 180) % 360) - 180
for index, row in itu_zone_data.iterrows(): for index, row in DATA_STORE.itu_zone_data.iterrows():
polygon = Polygon(row["geometry"]) polygon = Polygon(row["geometry"])
test_point = Point(lon, lat) test_point = Point(lon, lat)
if polygon.contains(test_point): if polygon.contains(test_point):
+30
View File
@@ -0,0 +1,30 @@
import json
import logging
import geopandas
from shapely import prepare
from core.data_store import DATA_STORE
from staticdataproviders.local_file_static_data_provider import LocalFileStaticDataProvider
class CQZoneData(LocalFileStaticDataProvider):
"""Static data provider for CQ zone geodata."""
PATH = "datafiles/cqzones.geojson"
def __init__(self, provider_config):
super().__init__("CQ Zone Data", provider_config, self.PATH)
def _load_data(self, path):
try:
with open(path) as f:
cq_zone_data = geopandas.GeoDataFrame.from_features(json.load(f)["features"])
for idx in cq_zone_data.index:
prepare(cq_zone_data.at[idx, 'geometry'])
DATA_STORE.cq_zone_data = cq_zone_data
return True
except Exception as e:
logging.error("Exception when loading CQ zone data.", e, exc_info=True)
return False
+30
View File
@@ -0,0 +1,30 @@
import json
import logging
import geopandas
from shapely import prepare
from core.data_store import DATA_STORE
from staticdataproviders.local_file_static_data_provider import LocalFileStaticDataProvider
class ITUZoneData(LocalFileStaticDataProvider):
"""Static data provider for ITU zone geodata."""
PATH = "datafiles/ituzones.geojson"
def __init__(self, provider_config):
super().__init__("ITU Zone Data", provider_config, self.PATH)
def _load_data(self, path):
try:
with open(path) as f:
itu_zone_data = geopandas.GeoDataFrame.from_features(json.load(f)["features"])
for idx in itu_zone_data.index:
prepare(itu_zone_data.at[idx, 'geometry'])
DATA_STORE.itu_zone_data = itu_zone_data
return True
except Exception as e:
logging.error("Exception when loading ITU zone data.", e, exc_info=True)
return False
@@ -20,9 +20,10 @@ class LocalFileStaticDataProvider(StaticDataProvider):
if ok: if ok:
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.info("Updated static reference data for " + self.name)
else: else:
self.status = "Error" self.status = "Error"
logging.info("Failed to load data for " + self.name) logging.error("Failed to load data for " + self.name)
except Exception as e: except Exception as e:
self.status = "Error" self.status = "Error"
logging.error("Exception in local file Static Data Provider (" + self.name + ")", e, exc_info=True) logging.error("Exception in local file Static Data Provider (" + self.name + ")", e, exc_info=True)