more work on extracting airplane infos

This commit is contained in:
Jakob Ketterl 2023-09-08 15:38:48 +02:00
parent 018104617d
commit 095bdf38bf
7 changed files with 186 additions and 39 deletions

View file

@ -1,6 +1,5 @@
from csdr.chain.demodulator import ServiceDemodulator
from csdr.module import JsonParser
from owrx.vdl2.dumpvdl2 import DumpVDL2Module
from owrx.vdl2.dumpvdl2 import DumpVDL2Module, VDL2MessageParser
from pycsdr.modules import Convert
from pycsdr.types import Format
@ -10,7 +9,7 @@ class DumpVDL2(ServiceDemodulator):
super().__init__([
Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT),
DumpVDL2Module(),
JsonParser("VDL2")
VDL2MessageParser(),
])
def getFixedAudioRate(self) -> int:

View file

@ -469,6 +469,9 @@ AircraftMessagePanel = function(el) {
AircraftMessagePanel.prototype = Object.create(MessagePanel.prototype);
AircraftMessagePanel.prototype.renderAcars = function(acars) {
if (acars['more']) {
return '<h4>Partial ACARS message</h4>';
}
var details = '<h4>ACARS message</h4>';
if ('flight' in acars) {
details += '<div>Flight: ' + acars['flight'] + '</div>';
@ -505,6 +508,7 @@ AircraftMessagePanel.prototype.renderAcars = function(acars) {
}
} else {
// plain text
details += '<div>Label: ' + acars['label'] + '</div>';
details += '<div class="acars-message">' + acars['msg_text'] + '</div>';
}
return details;

View file

@ -404,10 +404,11 @@ $(function(){
};
var linkifyAircraft = function(source, identification) {
var aircraftString = identification || source.icao || source.flight;
var aircraftString = identification || source.humanReadable || source.flight || source.icao;
var link = false;
switch (aircraft_tracking_service) {
case 'flightaware':
if (!source.icao) break;
link = 'https://flightaware.com/live/modes/' + source.icao;
if (identification) link += "/ident/" + identification
link += '/redirect';

View file

@ -1,14 +1,15 @@
from csdr.module import PickleModule
from math import sqrt, atan2, pi, floor, acos, cos
from owrx.map import LatLngLocation, IncrementalUpdate, Location, Map, Source
from owrx.map import IncrementalUpdate, Location, Map, Source
from owrx.metrics import Metrics, CounterMetric
from owrx.aeronautical import AirplaneLocation, IcaoSource
from datetime import datetime, timedelta
from enum import Enum
FEET_PER_METER = 3.28084
class AirplaneLocation(IncrementalUpdate, LatLngLocation):
class AdsbLocation(IncrementalUpdate, AirplaneLocation):
mapKeys = [
"lat",
"lon",
@ -26,12 +27,7 @@ class AirplaneLocation(IncrementalUpdate, LatLngLocation):
def __init__(self, message):
self.history = []
self.timestamp = datetime.now()
self.props = message
if "lat" in message and "lon" in message:
super().__init__(message["lat"], message["lon"])
else:
self.lat = None
self.lon = None
super().__init__(message)
def update(self, previousLocation: Location):
history = previousLocation.history
@ -53,29 +49,11 @@ class AirplaneLocation(IncrementalUpdate, LatLngLocation):
if "lon" in merged:
self.lon = merged["lon"]
def __dict__(self):
res = super().__dict__()
res.update(self.props)
return res
class AdsbLocation(AirplaneLocation):
def getTTL(self) -> timedelta:
# fixed ttl for adsb-locations for now
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)
@ -290,8 +268,8 @@ class ModeSParser(PickleModule):
self.metrics.inc()
if "icao" in message and AirplaneLocation.mapKeys & message.keys():
data = {k: message[k] for k in AirplaneLocation.mapKeys if k in message}
if "icao" in message and AdsbLocation.mapKeys & message.keys():
data = {k: message[k] for k in AdsbLocation.mapKeys if k in message}
loc = AdsbLocation(data)
Map.getSharedInstance().updateLocation(IcaoSource(message['icao']), loc, "ADS-B", None)

75
owrx/aeronautical.py Normal file
View file

@ -0,0 +1,75 @@
from owrx.map import Map, LatLngLocation, Source
from csdr.module import JsonParser
from abc import ABCMeta
class AirplaneLocation(LatLngLocation):
def __init__(self, message):
self.props = message
if "lat" in message and "lon" in message:
super().__init__(message["lat"], message["lon"])
else:
self.lat = None
self.lon = None
def __dict__(self):
res = super().__dict__()
res.update(self.props)
return res
class IcaoSource(Source):
def __init__(self, icao: str, humanReadable: str = None):
self.icao = icao
self.humanReadable = humanReadable
def getKey(self) -> str:
return "icao:{}".format(self.icao)
def __dict__(self):
d = {"icao": self.icao}
if self.humanReadable is not None:
d["humanReadable"] = self.humanReadable
return d
class AcarsSource(Source):
def __init__(self, flight):
self.flight = flight
def getKey(self) -> str:
return "acars:{}".format(self.flight)
def __dict__(self):
return {"flight": self.flight}
class AcarsProcessor(JsonParser, metaclass=ABCMeta):
def processAcars(self, acars: dict, icao: str = None):
if "flight" in acars:
flight_id = acars["flight"]
elif "reg" in acars:
flight_id = acars['reg']
else:
return
if "arinc622" in acars:
arinc622 = acars["arinc622"]
if "adsc" in arinc622:
adsc = arinc622["adsc"]
if "tags" in adsc:
for tag in adsc["tags"]:
if "basic_report" in tag:
basic_report = tag["basic_report"]
msg = {
"lat": basic_report["lat"],
"lon": basic_report["lon"],
"altitude": basic_report["alt"]
}
if icao is not None:
source = IcaoSource(icao, humanReadable=flight_id)
else:
source = AcarsSource(flight_id)
Map.getSharedInstance().updateLocation(
source, AirplaneLocation(msg), "ACARS over {}".format(self.mode)
)

View file

@ -1,7 +1,6 @@
from pycsdr.modules import ExecModule
from pycsdr.types import Format
from csdr.module import JsonParser
from owrx.adsb.modes import AirplaneLocation
from owrx.aeronautical import AirplaneLocation, AcarsProcessor, IcaoSource
from owrx.map import Map, Source
@ -37,7 +36,7 @@ class DumpHFDLModule(ExecModule):
)
class HFDLMessageParser(JsonParser):
class HFDLMessageParser(AcarsProcessor):
def __init__(self):
super().__init__("HFDL")
@ -47,11 +46,34 @@ class HFDLMessageParser(JsonParser):
payload = msg["hfdl"]
if "lpdu" in payload:
lpdu = payload["lpdu"]
icao = lpdu["src"]["ac_info"]["icao"] if "ac_info" in lpdu["src"] else None
if lpdu["type"]["id"] in [13, 29]:
hfnpdu = lpdu["hfnpdu"]
if hfnpdu["type"]["id"] == 209:
if "pos" in hfnpdu:
pos = hfnpdu['pos']
if abs(pos['lat']) <= 90 and abs(pos['lon']) <= 180:
Map.getSharedInstance().updateLocation(HfdlSource(hfnpdu["flight_id"]), HfdlAirplaneLocation(pos), "HFDL")
# performance data
self.processPosition(hfnpdu, icao)
elif hfnpdu["type"]["id"] == 255:
# enveloped data
if "acars" in hfnpdu:
self.processAcars(hfnpdu["acars"], icao)
elif lpdu["type"]["id"] in [79, 143, 191]:
if "ac_info" in lpdu:
icao = lpdu["ac_info"]["icao"]
self.processPosition(lpdu["hfnpdu"], icao)
return msg
def processPosition(self, hfnpdu, icao=None):
if "pos" in hfnpdu:
pos = hfnpdu["pos"]
if abs(pos['lat']) <= 90 and abs(pos['lon']) <= 180:
msg = {
"lat": pos["lat"],
"lon": pos["lon"],
"flight": hfnpdu["flight_id"]
}
if icao is None:
source = HfdlSource(hfnpdu["flight_id"])
else:
source = IcaoSource(icao, humanReadable=hfnpdu["flight_id"])
Map.getSharedInstance().updateLocation(source, HfdlAirplaneLocation(msg), "HFDL")

View file

@ -1,5 +1,8 @@
from pycsdr.modules import ExecModule
from pycsdr.types import Format
from owrx.aeronautical import AcarsProcessor
from owrx.map import Map
from owrx.aeronautical import AirplaneLocation, IcaoSource
class DumpVDL2Module(ExecModule):
@ -15,3 +18,68 @@ class DumpVDL2Module(ExecModule):
"--output", "decoded:json:file:path=-",
]
)
class VDL2MessageParser(AcarsProcessor):
def __init__(self):
super().__init__("VDL2")
def process(self, line):
msg = super().process(line)
if msg is not None:
payload = msg["vdl2"]
if "avlc" in payload:
avlc = payload["avlc"]
src = avlc["src"]["addr"]
if avlc["frame_type"] == "I":
if "acars" in avlc:
self.processAcars(avlc["acars"])
elif "x25" in avlc:
x25 = avlc["x25"]
if "clnp" in x25:
clnp = x25["clnp"]
if "cotp" in clnp:
cotp = clnp["cotp"]
if "adsc_v2" in cotp:
adsc_v2 = cotp["adsc_v2"]
if "adsc_report" in adsc_v2:
adsc_report = adsc_v2["adsc_report"]
if "periodic_report" in adsc_report["data"]:
periodic_report = adsc_report["data"]["periodic_report"]
report_data = periodic_report["report_data"]
self.processReport(report_data, src)
return msg
def processReport(self, report, icao):
if "position" not in report:
return
msg = {
"lat": self.convertLatitude(**report["position"]["lat"]),
"lon": self.convertLongitude(**report["position"]["lon"]),
"altitude": report["position"]["alt"]["val"],
}
if "ground_vector" in report:
msg.update({
"groundtrack": report["ground_vector"]["ground_track"]["val"],
"groundspeed": report["ground_vector"]["ground_speed"]["val"],
})
if "air_vector" in report:
msg.update({
"verticalspeed": report["air_vector"]["vertical_rate"]["val"],
})
Map.getSharedInstance().updateLocation(IcaoSource(icao), AirplaneLocation(msg), "VDL2")
def convertLatitude(self, dir, **args) -> float:
coord = self.convertCoordinate(**args)
if dir == "south":
coord *= -1
return coord
def convertLongitude(self, dir, **args) -> float:
coord = self.convertCoordinate(**args)
if dir == "west":
coord *= -1
return coord
def convertCoordinate(self, deg, min, sec) -> float:
return deg + float(min) / 60 + float(sec) / 3600