mirror of
https://github.com/jketterl/openwebrx.git
synced 2026-01-14 20:50:41 +01:00
more work on extracting airplane infos
This commit is contained in:
parent
018104617d
commit
095bdf38bf
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
75
owrx/aeronautical.py
Normal 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)
|
||||
)
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue