apply ttl approach to all locations

This commit is contained in:
Jakob Ketterl 2023-09-06 15:18:04 +02:00
parent 22b6d3e645
commit 26896cf2d5
5 changed files with 46 additions and 60 deletions

View File

@ -30,8 +30,6 @@ $(function(){
var receiverMarker; var receiverMarker;
var updateQueue = []; var updateQueue = [];
// reasonable default; will be overriden by server
var retention_time = 2 * 60 * 60 * 1000;
var strokeOpacity = 0.8; var strokeOpacity = 0.8;
var fillOpacity = 0.35; var fillOpacity = 0.35;
var callsign_service; var callsign_service;
@ -160,6 +158,7 @@ $(function(){
marker.mode = update.mode; marker.mode = update.mode;
marker.band = update.band; marker.band = update.band;
marker.comment = update.location.comment; marker.comment = update.location.comment;
marker.ttl = update.location.ttl;
if (expectedCallsign && shallowEquals(expectedCallsign, update.source)) { if (expectedCallsign && shallowEquals(expectedCallsign, update.source)) {
map.panTo(pos); map.panTo(pos);
@ -200,6 +199,7 @@ $(function(){
rectangle.mode = update.mode; rectangle.mode = update.mode;
rectangle.band = update.band; rectangle.band = update.band;
rectangle.center = center; rectangle.center = center;
rectangle.ttl = update.location.ttl;
rectangle.setOptions($.extend({ rectangle.setOptions($.extend({
strokeColor: color, strokeColor: color,
@ -313,9 +313,6 @@ $(function(){
title: config['receiver_name'] title: config['receiver_name']
}); });
} }
if ('map_position_retention_time' in config) {
retention_time = config.map_position_retention_time * 1000;
}
if ('callsign_service' in config) { if ('callsign_service' in config) {
callsign_service = config['callsign_service']; callsign_service = config['callsign_service'];
} }
@ -521,25 +518,25 @@ $(function(){
infowindow.open(map, marker); infowindow.open(map, marker);
}; };
var getScale = function(lastseen) { var getScale = function(lastseen, ttl) {
var age = new Date().getTime() - lastseen; var age = new Date().getTime() - lastseen;
var scale = 1; var scale = 1;
if (age >= retention_time / 2) { if (age >= ttl / 2) {
scale = (retention_time - age) / (retention_time / 2); scale = (ttl - age) / (ttl / 2);
} }
return Math.max(0, Math.min(1, scale)); return Math.max(0, Math.min(1, scale));
}; };
var getRectangleOpacityOptions = function(lastseen) { var getRectangleOpacityOptions = function(lastseen, ttl) {
var scale = getScale(lastseen); var scale = getScale(lastseen, ttl);
return { return {
strokeOpacity: strokeOpacity * scale, strokeOpacity: strokeOpacity * scale,
fillOpacity: fillOpacity * scale fillOpacity: fillOpacity * scale
}; };
}; };
var getMarkerOpacityOptions = function(lastseen) { var getMarkerOpacityOptions = function(lastseen, ttl) {
var scale = getScale(lastseen); var scale = getScale(lastseen, ttl);
return { return {
opacity: scale opacity: scale
}; };
@ -550,21 +547,21 @@ $(function(){
var now = new Date().getTime(); var now = new Date().getTime();
Object.values(rectangles).forEach(function(m){ Object.values(rectangles).forEach(function(m){
var age = now - m.lastseen; var age = now - m.lastseen;
if (age > retention_time) { if (age > m.ttl) {
delete rectangles[sourceToKey(m.source)]; delete rectangles[sourceToKey(m.source)];
m.setMap(); m.setMap();
return; return;
} }
m.setOptions(getRectangleOpacityOptions(m.lastseen)); m.setOptions(getRectangleOpacityOptions(m.lastseen, m.ttl));
}); });
Object.values(markers).forEach(function(m) { Object.values(markers).forEach(function(m) {
var age = now - m.lastseen; var age = now - m.lastseen;
if (age > retention_time || (m.ttl && age > m.ttl)) { if (age > m.ttl) {
delete markers[sourceToKey(m.source)]; delete markers[sourceToKey(m.source)];
m.setMap(); m.setMap();
return; return;
} }
m.setOptions(getMarkerOpacityOptions(m.lastseen)); m.setOptions(getMarkerOpacityOptions(m.lastseen, m.ttl));
}); });
}, 1000); }, 1000);

View File

@ -1,15 +1,14 @@
from csdr.module import PickleModule from csdr.module import PickleModule
from math import sqrt, atan2, pi, floor, acos, cos from math import sqrt, atan2, pi, floor, acos, cos
from owrx.map import LatLngLocation, IncrementalUpdate, TTLUpdate, Location, Map from owrx.map import LatLngLocation, IncrementalUpdate, Location, Map
from owrx.metrics import Metrics, CounterMetric from owrx.metrics import Metrics, CounterMetric
from datetime import timedelta from datetime import datetime, timedelta
from enum import Enum from enum import Enum
import time
FEET_PER_METER = 3.28084 FEET_PER_METER = 3.28084
class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation): class AirplaneLocation(IncrementalUpdate, LatLngLocation):
mapKeys = [ mapKeys = [
"lat", "lat",
"lon", "lon",
@ -23,11 +22,10 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation):
"IAS", "IAS",
"heading", "heading",
] ]
ttl = 30
def __init__(self, icao, message): def __init__(self, icao, message):
self.history = [] self.history = []
self.timestamp = time.time() self.timestamp = datetime.now()
self.props = message self.props = message
self.icao = icao self.icao = icao
if "lat" in message and "lon" in message: if "lat" in message and "lon" in message:
@ -38,8 +36,8 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation):
def update(self, previousLocation: Location): def update(self, previousLocation: Location):
history = previousLocation.history history = previousLocation.history
now = time.time() now = datetime.now()
history = [p for p in history if now - p["timestamp"] < self.ttl] history = [p for p in history if now - p["timestamp"] < self.getTTL()]
history += [{ history += [{
"timestamp": self.timestamp, "timestamp": self.timestamp,
"props": self.props, "props": self.props,
@ -62,8 +60,11 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation):
dict["icao"] = self.icao dict["icao"] = self.icao
return dict return dict
class AdsbLocation(AirplaneLocation):
def getTTL(self) -> timedelta: def getTTL(self) -> timedelta:
return timedelta(seconds=self.ttl) # fixed ttl for adsb-locations for now
return timedelta(seconds=30)
class CprRecordType(Enum): class CprRecordType(Enum):
@ -93,8 +94,8 @@ class CprCache:
records = self.__getRecords(cprType) records = self.__getRecords(cprType)
if icao not in records: if icao not in records:
return [] return []
now = time.time() now = datetime.now()
filtered = [r for r in records[icao] if now - r["timestamp"] < 10] filtered = [r for r in records[icao] if now - r["timestamp"] < timedelta(seconds=10)]
records_sorted = sorted(filtered, key=lambda r: r["timestamp"]) records_sorted = sorted(filtered, key=lambda r: r["timestamp"])
records[icao] = records_sorted records[icao] = records_sorted
return [r["data"] for r in records_sorted] return [r["data"] for r in records_sorted]
@ -103,7 +104,7 @@ class CprCache:
records = self.__getRecords(cprType) records = self.__getRecords(cprType)
if icao not in records: if icao not in records:
records[icao] = [] records[icao] = []
records[icao].append({"timestamp": time.time(), "data": data}) records[icao].append({"timestamp": datetime.now(), "data": data})
class ModeSParser(PickleModule): class ModeSParser(PickleModule):
@ -282,7 +283,7 @@ class ModeSParser(PickleModule):
if "icao" in message and AirplaneLocation.mapKeys & message.keys(): if "icao" in message and AirplaneLocation.mapKeys & message.keys():
data = {k: message[k] for k in AirplaneLocation.mapKeys if k in message} data = {k: message[k] for k in AirplaneLocation.mapKeys if k in message}
loc = AirplaneLocation(message["icao"], data) loc = AdsbLocation(message["icao"], data)
Map.getSharedInstance().updateLocation({"icao": message['icao']}, loc, "ADS-B", None) Map.getSharedInstance().updateLocation({"icao": message['icao']}, loc, "ADS-B", None)
return message return message

View File

@ -458,7 +458,6 @@ class MapConnection(OpenWebRxClient):
filtered_config = pm.filter( filtered_config = pm.filter(
"google_maps_api_key", "google_maps_api_key",
"receiver_gps", "receiver_gps",
"map_position_retention_time",
"callsign_service", "callsign_service",
"aircraft_tracking_service", "aircraft_tracking_service",
"receiver_name", "receiver_name",

View File

@ -3,16 +3,12 @@ from pycsdr.types import Format
from csdr.module import JsonParser from csdr.module import JsonParser
from owrx.adsb.modes import AirplaneLocation from owrx.adsb.modes import AirplaneLocation
from owrx.map import Map from owrx.map import Map
from datetime import timedelta
class HfdlAirplaneLocation(AirplaneLocation): class HfdlAirplaneLocation(AirplaneLocation):
def __init__(self, message): def __init__(self, message):
super().__init__(None, message) super().__init__(None, message)
def getTTL(self) -> timedelta:
return timedelta(minutes=60)
class DumpHFDLModule(ExecModule): class DumpHFDLModule(ExecModule):
def __init__(self): def __init__(self):

View File

@ -12,8 +12,14 @@ logger = logging.getLogger(__name__)
class Location(object): class Location(object):
def getTTL(self) -> timedelta:
pm = Config.get()
return timedelta(seconds=pm["map_position_retention_time"])
def __dict__(self): def __dict__(self):
return {} return {
"ttl": self.getTTL().total_seconds() * 1000
}
class Map(object): class Map(object):
@ -120,21 +126,12 @@ class Map(object):
# TODO broadcast removal to clients # TODO broadcast removal to clients
def removeOldPositions(self): def removeOldPositions(self):
pm = Config.get()
retention = timedelta(seconds=pm["map_position_retention_time"])
now = datetime.now() now = datetime.now()
cutoff = now - retention
def isExpired(pos):
if pos["updated"] < cutoff:
return True
if isinstance(pos["location"], TTLUpdate):
if now - pos["location"].getTTL() > pos["updated"]:
return True
return False
with self.positionsLock: with self.positionsLock:
to_be_removed = [key for (key, pos) in self.positions.items() if isExpired(pos)] to_be_removed = [
key for (key, pos) in self.positions.items() if now - pos["location"].getTTL() > pos["updated"]
]
for key in to_be_removed: for key in to_be_removed:
self.removeLocation(key) self.removeLocation(key)
@ -152,7 +149,10 @@ class LatLngLocation(Location):
self.lon = lon self.lon = lon
def __dict__(self): def __dict__(self):
res = {"type": "latlon", "lat": self.lat, "lon": self.lon} res = super().__dict__()
res.update(
{"type": "latlon", "lat": self.lat, "lon": self.lon}
)
return res return res
@ -161,21 +161,14 @@ class LocatorLocation(Location):
self.locator = locator self.locator = locator
def __dict__(self): def __dict__(self):
return {"type": "locator", "locator": self.locator} res = super().__dict__()
res.update(
{"type": "locator", "locator": self.locator}
)
return res
class IncrementalUpdate(Location, metaclass=ABCMeta): class IncrementalUpdate(Location, metaclass=ABCMeta):
@abstractmethod @abstractmethod
def update(self, previousLocation: Location): def update(self, previousLocation: Location):
pass pass
class TTLUpdate(Location, metaclass=ABCMeta):
@abstractmethod
def getTTL(self) -> timedelta:
pass
def __dict__(self):
res = super().__dict__()
res["ttl"] = self.getTTL().total_seconds() * 1000
return res