From 26b84231935b2ca546f5b26f48c366e57d1bf84c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 6 Sep 2023 16:36:38 +0200 Subject: [PATCH] introduce source classes for the map --- owrx/adsb/modes.py | 15 +++++++++++++-- owrx/aprs/__init__.py | 18 +++++++++++++++-- owrx/hfdl/dumphfdl.py | 15 +++++++++++++-- owrx/js8.py | 4 ++-- owrx/map.py | 45 +++++++++++++++++++++++++++---------------- owrx/meta.py | 6 +++--- owrx/wsjt.py | 4 ++-- 7 files changed, 77 insertions(+), 30 deletions(-) diff --git a/owrx/adsb/modes.py b/owrx/adsb/modes.py index a58c4617..40bcfa5b 100644 --- a/owrx/adsb/modes.py +++ b/owrx/adsb/modes.py @@ -1,6 +1,6 @@ from csdr.module import PickleModule from math import sqrt, atan2, pi, floor, acos, cos -from owrx.map import LatLngLocation, IncrementalUpdate, Location, Map +from owrx.map import LatLngLocation, IncrementalUpdate, Location, Map, Source from owrx.metrics import Metrics, CounterMetric from datetime import datetime, timedelta from enum import Enum @@ -65,6 +65,17 @@ class AdsbLocation(AirplaneLocation): return timedelta(seconds=30) +class IcaoSource(Source): + def __init__(self, icao: str): + self.icao = icao + + def getKey(self) -> str: + return "icao:{}".format(self.icao) + + def __dict__(self): + return {"icao": self.icao} + + class CprRecordType(Enum): AIR = ("air", 360) GROUND = ("ground", 90) @@ -282,7 +293,7 @@ class ModeSParser(PickleModule): if "icao" in message and AirplaneLocation.mapKeys & message.keys(): data = {k: message[k] for k in AirplaneLocation.mapKeys if k in message} loc = AdsbLocation(data) - Map.getSharedInstance().updateLocation({"icao": message['icao']}, loc, "ADS-B", None) + Map.getSharedInstance().updateLocation(IcaoSource(message['icao']), loc, "ADS-B", None) return message diff --git a/owrx/aprs/__init__.py b/owrx/aprs/__init__.py index 111335f9..91b586bf 100644 --- a/owrx/aprs/__init__.py +++ b/owrx/aprs/__init__.py @@ -1,4 +1,4 @@ -from owrx.map import Map, LatLngLocation +from owrx.map import Map, LatLngLocation, Source from owrx.metrics import Metrics, CounterMetric from owrx.bands import Bandplan from datetime import datetime, timezone @@ -155,6 +155,20 @@ class AprsLocation(LatLngLocation): return res +class AprsSource(Source): + def __init__(self, source): + self.source = source + + def getKey(self) -> str: + callsign = self.source["callsign"] + if "ssid" in self.source: + callsign += "-{}".format(self.source["ssid"]) + return "aprs:{}".format(callsign) + + def __dict__(self): + return self.source + + class AprsParser(PickleModule): def __init__(self): super().__init__() @@ -215,7 +229,7 @@ class AprsParser(PickleModule): source["item"] = mapData["item"] elif mapData["type"] == "object" and "object" in mapData: source["object"] = mapData["object"] - Map.getSharedInstance().updateLocation(source, loc, "APRS", self.band) + Map.getSharedInstance().updateLocation(AprsSource(source), loc, "APRS", self.band) def hasCompressedCoordinates(self, raw): return raw[0] == "/" or raw[0] == "\\" diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index 6c48ceaf..7a90b10c 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -2,13 +2,24 @@ from pycsdr.modules import ExecModule from pycsdr.types import Format from csdr.module import JsonParser from owrx.adsb.modes import AirplaneLocation -from owrx.map import Map +from owrx.map import Map, Source class HfdlAirplaneLocation(AirplaneLocation): pass +class HfdlSource(Source): + def __init__(self, flight): + self.flight = flight + + def getKey(self) -> str: + return "hfdl:{}".format(self.flight) + + def __dict__(self): + return {"flight": self.flight} + + class DumpHFDLModule(ExecModule): def __init__(self): super().__init__( @@ -42,5 +53,5 @@ class HFDLMessageParser(JsonParser): if "pos" in hfnpdu: pos = hfnpdu['pos'] if abs(pos['lat']) <= 90 and abs(pos['lon']) <= 180: - Map.getSharedInstance().updateLocation({"flight": hfnpdu["flight_id"]}, HfdlAirplaneLocation(pos), "HFDL") + Map.getSharedInstance().updateLocation(HfdlSource(hfnpdu["flight_id"]), HfdlAirplaneLocation(pos), "HFDL") return msg diff --git a/owrx/js8.py b/owrx/js8.py index bf83a47f..e358a760 100644 --- a/owrx/js8.py +++ b/owrx/js8.py @@ -3,7 +3,7 @@ from owrx.audio.chopper import AudioChopperParser import re from js8py import Js8 from js8py.frames import Js8FrameHeartbeat, Js8FrameCompound -from owrx.map import Map, LocatorLocation +from owrx.map import Map, LocatorLocation, CallsignSource from owrx.metrics import Metrics, CounterMetric from owrx.config import Config from abc import ABCMeta, abstractmethod @@ -103,7 +103,7 @@ class Js8Parser(AudioChopperParser): if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid: Map.getSharedInstance().updateLocation( - frame.source, LocatorLocation(frame.grid), "JS8", band + CallsignSource(**frame.source), LocatorLocation(frame.grid), "JS8", band ) ReportingEngine.getSharedInstance().spot( { diff --git a/owrx/map.py b/owrx/map.py index 6b7448a6..52a718e9 100644 --- a/owrx/map.py +++ b/owrx/map.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from owrx.config import Config from owrx.bands import Band -from abc import abstractmethod, ABCMeta +from abc import abstractmethod, ABC, ABCMeta import threading import time import sys @@ -22,6 +22,15 @@ class Location(object): } +class Source(ABC): + @abstractmethod + def getKey(self) -> str: + pass + + def __dict__(self): + return {} + + class Map(object): sharedInstance = None creationLock = threading.Lock() @@ -67,7 +76,7 @@ class Map(object): client.write_update( [ { - "source": record["source"], + "source": record["source"].__dict__(), "location": record["location"].__dict__(), "lastseen": record["updated"].timestamp() * 1000, "mode": record["mode"], @@ -83,18 +92,9 @@ class Map(object): except ValueError: pass - def _sourceToKey(self, source): - if "ssid" in source: - return "{callsign}-{ssid}".format(**source) - elif "icao" in source: - return source["icao"] - elif "flight" in source: - return source["flight"] - return source["callsign"] - - def updateLocation(self, source, loc: Location, mode: str, band: Band = None): + def updateLocation(self, source: Source, loc: Location, mode: str, band: Band = None): ts = datetime.now() - key = self._sourceToKey(source) + key = source.getKey() with self.positionsLock: if isinstance(loc, IncrementalUpdate) and key in self.positions: loc.update(self.positions[key]["location"]) @@ -102,7 +102,7 @@ class Map(object): self.broadcast( [ { - "source": source, + "source": source.__dict__(), "location": loc.__dict__(), "lastseen": ts.timestamp() * 1000, "mode": mode, @@ -111,14 +111,14 @@ class Map(object): ] ) - def touchLocation(self, source): + def touchLocation(self, source: Source): # not implemented on the client side yet, so do not use! ts = datetime.now() - key = self._sourceToKey(source) + key = source.getKey() with self.positionsLock: if key in self.positions: self.positions[key]["updated"] = ts - self.broadcast([{"source": source, "lastseen": ts.timestamp() * 1000}]) + self.broadcast([{"source": source.__dict__(), "lastseen": ts.timestamp() * 1000}]) def removeLocation(self, key): with self.positionsLock: @@ -172,3 +172,14 @@ class IncrementalUpdate(Location, metaclass=ABCMeta): @abstractmethod def update(self, previousLocation: Location): pass + + +class CallsignSource(Source): + def __init__(self, callsign: str): + self.callsign = callsign + + def getKey(self) -> str: + return "callsign:{}".format(self.callsign) + + def __dict__(self): + return {"callsign": self.callsign} diff --git a/owrx/meta.py b/owrx/meta.py index ab576e9f..a69f547e 100644 --- a/owrx/meta.py +++ b/owrx/meta.py @@ -11,7 +11,7 @@ from urllib.error import HTTPError from csdr.module import PickleModule from owrx.aprs import AprsParser, AprsLocation from owrx.config import Config -from owrx.map import Map, LatLngLocation +from owrx.map import Map, LatLngLocation, CallsignSource from owrx.bands import Bandplan logger = logging.getLogger(__name__) @@ -129,7 +129,7 @@ class DigihamEnricher(Enricher, metaclass=ABCMeta): callsign = self.getCallsign(meta) if callsign is not None and "lat" in meta and "lon" in meta: loc = LatLngLocation(meta["lat"], meta["lon"]) - Map.getSharedInstance().updateLocation({"callsign": callsign}, loc, mode, self.parser.getBand()) + Map.getSharedInstance().updateLocation(CallsignSource(callsign), loc, mode, self.parser.getBand()) return meta @abstractmethod @@ -202,7 +202,7 @@ class DStarEnricher(DigihamEnricher): if "ourcall" in meta: # send location info to map as well (it will show up with the correct symbol there!) loc = AprsLocation(data) - Map.getSharedInstance().updateLocation({"callsign": meta["ourcall"]}, loc, "DPRS", self.parser.getBand()) + Map.getSharedInstance().updateLocation(CallsignSource(meta["ourcall"]), loc, "DPRS", self.parser.getBand()) except Exception: logger.exception("Error while parsing DPRS data") diff --git a/owrx/wsjt.py b/owrx/wsjt.py index 8fce19d1..b1fdd9c9 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -1,6 +1,6 @@ from datetime import datetime, timezone from typing import List -from owrx.map import Map, LocatorLocation +from owrx.map import Map, LocatorLocation, CallsignSource from owrx.metrics import Metrics, CounterMetric from owrx.reporting import ReportingEngine from owrx.audio import AudioChopperProfile, StaticProfileSource, ConfigWiredProfileSource @@ -289,7 +289,7 @@ class WsjtParser(AudioChopperParser): self.pushDecode(mode, band) if "source" in out and "locator" in out: Map.getSharedInstance().updateLocation( - out["source"], LocatorLocation(out["locator"]), mode, band + CallsignSource(**out["source"]), LocatorLocation(out["locator"]), mode, band ) ReportingEngine.getSharedInstance().spot(out)