diff --git a/htdocs/map.js b/htdocs/map.js index 1c75e2da..7d9dc128 100644 --- a/htdocs/map.js +++ b/htdocs/map.js @@ -30,8 +30,6 @@ $(function(){ var receiverMarker; var updateQueue = []; - // reasonable default; will be overriden by server - var retention_time = 2 * 60 * 60 * 1000; var strokeOpacity = 0.8; var fillOpacity = 0.35; var callsign_service; @@ -160,6 +158,7 @@ $(function(){ marker.mode = update.mode; marker.band = update.band; marker.comment = update.location.comment; + marker.ttl = update.location.ttl; if (expectedCallsign && shallowEquals(expectedCallsign, update.source)) { map.panTo(pos); @@ -200,6 +199,7 @@ $(function(){ rectangle.mode = update.mode; rectangle.band = update.band; rectangle.center = center; + rectangle.ttl = update.location.ttl; rectangle.setOptions($.extend({ strokeColor: color, @@ -313,9 +313,6 @@ $(function(){ title: config['receiver_name'] }); } - if ('map_position_retention_time' in config) { - retention_time = config.map_position_retention_time * 1000; - } if ('callsign_service' in config) { callsign_service = config['callsign_service']; } @@ -521,25 +518,25 @@ $(function(){ infowindow.open(map, marker); }; - var getScale = function(lastseen) { + var getScale = function(lastseen, ttl) { var age = new Date().getTime() - lastseen; var scale = 1; - if (age >= retention_time / 2) { - scale = (retention_time - age) / (retention_time / 2); + if (age >= ttl / 2) { + scale = (ttl - age) / (ttl / 2); } return Math.max(0, Math.min(1, scale)); }; - var getRectangleOpacityOptions = function(lastseen) { - var scale = getScale(lastseen); + var getRectangleOpacityOptions = function(lastseen, ttl) { + var scale = getScale(lastseen, ttl); return { strokeOpacity: strokeOpacity * scale, fillOpacity: fillOpacity * scale }; }; - var getMarkerOpacityOptions = function(lastseen) { - var scale = getScale(lastseen); + var getMarkerOpacityOptions = function(lastseen, ttl) { + var scale = getScale(lastseen, ttl); return { opacity: scale }; @@ -550,21 +547,21 @@ $(function(){ var now = new Date().getTime(); Object.values(rectangles).forEach(function(m){ var age = now - m.lastseen; - if (age > retention_time) { + if (age > m.ttl) { delete rectangles[sourceToKey(m.source)]; m.setMap(); return; } - m.setOptions(getRectangleOpacityOptions(m.lastseen)); + m.setOptions(getRectangleOpacityOptions(m.lastseen, m.ttl)); }); Object.values(markers).forEach(function(m) { var age = now - m.lastseen; - if (age > retention_time || (m.ttl && age > m.ttl)) { + if (age > m.ttl) { delete markers[sourceToKey(m.source)]; m.setMap(); return; } - m.setOptions(getMarkerOpacityOptions(m.lastseen)); + m.setOptions(getMarkerOpacityOptions(m.lastseen, m.ttl)); }); }, 1000); diff --git a/owrx/adsb/modes.py b/owrx/adsb/modes.py index cea97328..3e7b51c3 100644 --- a/owrx/adsb/modes.py +++ b/owrx/adsb/modes.py @@ -1,15 +1,14 @@ from csdr.module import PickleModule 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 datetime import timedelta +from datetime import datetime, timedelta from enum import Enum -import time FEET_PER_METER = 3.28084 -class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation): +class AirplaneLocation(IncrementalUpdate, LatLngLocation): mapKeys = [ "lat", "lon", @@ -23,11 +22,10 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation): "IAS", "heading", ] - ttl = 30 def __init__(self, icao, message): self.history = [] - self.timestamp = time.time() + self.timestamp = datetime.now() self.props = message self.icao = icao if "lat" in message and "lon" in message: @@ -38,8 +36,8 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation): def update(self, previousLocation: Location): history = previousLocation.history - now = time.time() - history = [p for p in history if now - p["timestamp"] < self.ttl] + now = datetime.now() + history = [p for p in history if now - p["timestamp"] < self.getTTL()] history += [{ "timestamp": self.timestamp, "props": self.props, @@ -62,8 +60,11 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation): dict["icao"] = self.icao return dict + +class AdsbLocation(AirplaneLocation): def getTTL(self) -> timedelta: - return timedelta(seconds=self.ttl) + # fixed ttl for adsb-locations for now + return timedelta(seconds=30) class CprRecordType(Enum): @@ -93,8 +94,8 @@ class CprCache: records = self.__getRecords(cprType) if icao not in records: return [] - now = time.time() - filtered = [r for r in records[icao] if now - r["timestamp"] < 10] + now = datetime.now() + filtered = [r for r in records[icao] if now - r["timestamp"] < timedelta(seconds=10)] records_sorted = sorted(filtered, key=lambda r: r["timestamp"]) records[icao] = records_sorted return [r["data"] for r in records_sorted] @@ -103,7 +104,7 @@ class CprCache: records = self.__getRecords(cprType) if icao not in records: records[icao] = [] - records[icao].append({"timestamp": time.time(), "data": data}) + records[icao].append({"timestamp": datetime.now(), "data": data}) class ModeSParser(PickleModule): @@ -282,7 +283,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 = AirplaneLocation(message["icao"], data) + loc = AdsbLocation(message["icao"], data) Map.getSharedInstance().updateLocation({"icao": message['icao']}, loc, "ADS-B", None) return message diff --git a/owrx/connection.py b/owrx/connection.py index ee8f0745..31d19ba6 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -458,7 +458,6 @@ class MapConnection(OpenWebRxClient): filtered_config = pm.filter( "google_maps_api_key", "receiver_gps", - "map_position_retention_time", "callsign_service", "aircraft_tracking_service", "receiver_name", diff --git a/owrx/hfdl/dumphfdl.py b/owrx/hfdl/dumphfdl.py index 55f98bd1..16dfe703 100644 --- a/owrx/hfdl/dumphfdl.py +++ b/owrx/hfdl/dumphfdl.py @@ -3,16 +3,12 @@ from pycsdr.types import Format from csdr.module import JsonParser from owrx.adsb.modes import AirplaneLocation from owrx.map import Map -from datetime import timedelta class HfdlAirplaneLocation(AirplaneLocation): def __init__(self, message): super().__init__(None, message) - def getTTL(self) -> timedelta: - return timedelta(minutes=60) - class DumpHFDLModule(ExecModule): def __init__(self): diff --git a/owrx/map.py b/owrx/map.py index 315074ec..6b7448a6 100644 --- a/owrx/map.py +++ b/owrx/map.py @@ -12,8 +12,14 @@ logger = logging.getLogger(__name__) class Location(object): + def getTTL(self) -> timedelta: + pm = Config.get() + return timedelta(seconds=pm["map_position_retention_time"]) + def __dict__(self): - return {} + return { + "ttl": self.getTTL().total_seconds() * 1000 + } class Map(object): @@ -120,21 +126,12 @@ class Map(object): # TODO broadcast removal to clients def removeOldPositions(self): - pm = Config.get() - retention = timedelta(seconds=pm["map_position_retention_time"]) 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: - 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: self.removeLocation(key) @@ -152,7 +149,10 @@ class LatLngLocation(Location): self.lon = lon 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 @@ -161,21 +161,14 @@ class LocatorLocation(Location): self.locator = locator 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): @abstractmethod def update(self, previousLocation: Location): 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