apply ttl approach to all locations
This commit is contained in:
parent
22b6d3e645
commit
26896cf2d5
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
45
owrx/map.py
45
owrx/map.py
|
|
@ -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
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue