openwebrx/owrx/map.py

186 lines
5.4 KiB
Python
Raw Normal View History

2019-07-07 15:52:24 +02:00
from datetime import datetime, timedelta
from owrx.config import Config
from owrx.bands import Band
2023-09-06 16:36:38 +02:00
from abc import abstractmethod, ABC, ABCMeta
import threading
import time
2020-01-05 18:41:46 +01:00
import sys
2019-07-07 15:52:24 +02:00
import logging
2019-07-07 15:52:24 +02:00
logger = logging.getLogger(__name__)
class Location(object):
2023-09-06 15:18:04 +02:00
def getTTL(self) -> timedelta:
pm = Config.get()
return timedelta(seconds=pm["map_position_retention_time"])
def __dict__(self):
2023-09-06 15:18:04 +02:00
return {
"ttl": self.getTTL().total_seconds() * 1000
}
2023-09-06 16:36:38 +02:00
class Source(ABC):
@abstractmethod
def getKey(self) -> str:
pass
def __dict__(self):
return {}
class Map(object):
sharedInstance = None
2020-01-05 18:41:46 +01:00
creationLock = threading.Lock()
@staticmethod
def getSharedInstance():
2020-01-05 18:41:46 +01:00
with Map.creationLock:
if Map.sharedInstance is None:
Map.sharedInstance = Map()
return Map.sharedInstance
def __init__(self):
self.clients = []
self.positions = {}
self.positionsLock = threading.Lock()
2019-07-07 15:52:24 +02:00
def removeLoop():
loops = 0
2019-07-07 15:52:24 +02:00
while True:
try:
self.removeOldPositions()
except Exception:
logger.exception("error while removing old map positions")
loops += 1
# rebuild the positions dictionary every once in a while, it consumes lots of memory otherwise
if loops == 60:
try:
self.rebuildPositions()
except Exception:
logger.exception("error while rebuilding positions")
loops = 0
2019-07-07 15:52:24 +02:00
time.sleep(60)
2020-08-14 20:22:25 +02:00
threading.Thread(target=removeLoop, daemon=True, name="map_removeloop").start()
super().__init__()
def broadcast(self, update):
for c in self.clients:
c.write_update(update)
def addClient(self, client):
self.clients.append(client)
client.write_update(
[
{
2023-09-06 16:36:38 +02:00
"source": record["source"].__dict__(),
"location": record["location"].__dict__(),
"lastseen": record["updated"].timestamp() * 1000,
"mode": record["mode"],
"band": record["band"].getName() if record["band"] is not None else None,
}
2022-11-30 01:07:16 +01:00
for record in self.positions.values()
]
)
def removeClient(self, client):
try:
self.clients.remove(client)
except ValueError:
pass
2023-09-06 16:36:38 +02:00
def updateLocation(self, source: Source, loc: Location, mode: str, band: Band = None):
2019-07-07 15:52:24 +02:00
ts = datetime.now()
2023-09-06 16:36:38 +02:00
key = source.getKey()
with self.positionsLock:
2023-08-25 21:15:29 +02:00
if isinstance(loc, IncrementalUpdate) and key in self.positions:
loc.update(self.positions[key]["location"])
2022-11-30 01:07:16 +01:00
self.positions[key] = {"source": source, "location": loc, "updated": ts, "mode": mode, "band": band}
self.broadcast(
[
{
2023-09-06 16:36:38 +02:00
"source": source.__dict__(),
"location": loc.__dict__(),
"lastseen": ts.timestamp() * 1000,
"mode": mode,
"band": band.getName() if band is not None else None,
}
]
)
2019-07-07 15:52:24 +02:00
2023-09-06 16:36:38 +02:00
def touchLocation(self, source: Source):
# not implemented on the client side yet, so do not use!
ts = datetime.now()
2023-09-06 16:36:38 +02:00
key = source.getKey()
with self.positionsLock:
2022-11-30 01:07:16 +01:00
if key in self.positions:
self.positions[key]["updated"] = ts
2023-09-06 16:36:38 +02:00
self.broadcast([{"source": source.__dict__(), "lastseen": ts.timestamp() * 1000}])
2022-11-30 01:07:16 +01:00
def removeLocation(self, key):
with self.positionsLock:
2022-11-30 01:07:16 +01:00
del self.positions[key]
# TODO broadcast removal to clients
2019-07-07 15:52:24 +02:00
def removeOldPositions(self):
now = datetime.now()
with self.positionsLock:
2023-09-06 15:18:04 +02:00
to_be_removed = [
key for (key, pos) in self.positions.items() if now - pos["location"].getTTL() > pos["updated"]
]
2022-11-30 01:07:16 +01:00
for key in to_be_removed:
self.removeLocation(key)
def rebuildPositions(self):
2020-01-05 18:41:46 +01:00
logger.debug("rebuilding map storage; size before: %i", sys.getsizeof(self.positions))
with self.positionsLock:
p = {key: value for key, value in self.positions.items()}
self.positions = p
2020-01-05 18:41:46 +01:00
logger.debug("rebuild complete; size after: %i", sys.getsizeof(self.positions))
class LatLngLocation(Location):
2019-09-18 18:50:48 +02:00
def __init__(self, lat: float, lon: float):
self.lat = lat
self.lon = lon
def __dict__(self):
2023-09-06 15:18:04 +02:00
res = super().__dict__()
res.update(
{"type": "latlon", "lat": self.lat, "lon": self.lon}
)
return res
class LocatorLocation(Location):
def __init__(self, locator: str):
self.locator = locator
def __dict__(self):
2023-09-06 15:18:04 +02:00
res = super().__dict__()
res.update(
{"type": "locator", "locator": self.locator}
)
return res
2023-08-25 21:15:29 +02:00
class IncrementalUpdate(Location, metaclass=ABCMeta):
@abstractmethod
def update(self, previousLocation: Location):
pass
2023-09-06 16:36:38 +02:00
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}