mirror of
https://github.com/jketterl/openwebrx.git
synced 2026-01-19 15:10:21 +01:00
decode ground data
This commit is contained in:
parent
d8e5c84839
commit
6091b4bfef
|
|
@ -3,6 +3,7 @@ from math import sqrt, atan2, pi, floor, acos, cos
|
|||
from owrx.map import LatLngLocation, IncrementalUpdate, TTLUpdate, Location, Map
|
||||
from owrx.metrics import Metrics, CounterMetric
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
import time
|
||||
|
||||
import logging
|
||||
|
|
@ -12,10 +13,6 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
FEET_PER_METER = 3.28084
|
||||
|
||||
nz = 15
|
||||
d_lat_even = 360 / (4 * nz)
|
||||
d_lat_odd = 360 / (4 * nz - 1)
|
||||
|
||||
|
||||
class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation):
|
||||
mapKeys = [
|
||||
|
|
@ -74,23 +71,44 @@ class AirplaneLocation(IncrementalUpdate, TTLUpdate, LatLngLocation):
|
|||
return timedelta(seconds=self.ttl)
|
||||
|
||||
|
||||
class CprRecordType(Enum):
|
||||
AIR = ("air", 360)
|
||||
GROUND = ("ground", 90)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
name, baseAngle = args
|
||||
obj = object.__new__(cls)
|
||||
obj._value_ = name
|
||||
obj.baseAngle = baseAngle
|
||||
return obj
|
||||
|
||||
|
||||
class CprCache:
|
||||
def __init__(self):
|
||||
self.aircraft = {}
|
||||
self.airRecords = {}
|
||||
self.groundRecords = {}
|
||||
|
||||
def getRecentData(self, icao: str):
|
||||
if icao not in self.aircraft:
|
||||
def __getRecords(self, cprType: CprRecordType):
|
||||
if cprType is CprRecordType.AIR:
|
||||
return self.airRecords
|
||||
elif cprType is CprRecordType.GROUND:
|
||||
return self.groundRecords
|
||||
|
||||
def getRecentData(self, icao: str, cprType: CprRecordType):
|
||||
records = self.__getRecords(cprType)
|
||||
if icao not in records:
|
||||
return []
|
||||
now = time.time()
|
||||
filtered = [r for r in self.aircraft[icao] if now - r["timestamp"] < 10]
|
||||
records = sorted(filtered, key=lambda r: r["timestamp"])
|
||||
self.aircraft[icao] = records
|
||||
return [r["data"] for r in records]
|
||||
filtered = [r for r in records[icao] if now - r["timestamp"] < 10]
|
||||
records_sorted = sorted(filtered, key=lambda r: r["timestamp"])
|
||||
records[icao] = records_sorted
|
||||
return [r["data"] for r in records_sorted]
|
||||
|
||||
def addRecord(self, icao: str, data: any):
|
||||
if icao not in self.aircraft:
|
||||
self.aircraft[icao] = []
|
||||
self.aircraft[icao].append({"timestamp": time.time(), "data": data})
|
||||
def addRecord(self, icao: str, data: any, cprType: CprRecordType):
|
||||
records = self.__getRecords(cprType)
|
||||
if icao not in records:
|
||||
records[icao] = []
|
||||
records[icao].append({"timestamp": time.time(), "data": data})
|
||||
|
||||
|
||||
class ModeSParser(PickleModule):
|
||||
|
|
@ -132,12 +150,41 @@ class ModeSParser(PickleModule):
|
|||
|
||||
elif type in [5, 6, 7, 8]:
|
||||
# surface position
|
||||
pass
|
||||
# there's no altitude data in this message type, but the type implies the aircraft is on ground
|
||||
message["altitude"] = "ground"
|
||||
|
||||
movement = ((input[4] & 0b00000111) << 4) | ((input[5] & 0b11110000) >> 4)
|
||||
if movement == 1:
|
||||
message["groundspeed"] = 0
|
||||
elif 2 <= movement < 9:
|
||||
message["groundspeed"] = (movement - 1) * .0125
|
||||
elif 9 <= movement < 13:
|
||||
message["groundspeed"] = 1 + (movement - 8) * .25
|
||||
elif 13 <= movement < 39:
|
||||
message["groundspeed"] = 2 + (movement - 12) * .5
|
||||
elif 39 <= movement < 94:
|
||||
message["groundspeed"] = 15 + (movement - 38) # * 1
|
||||
elif 94 <= movement < 109:
|
||||
message["groundspeed"] = 70 + (movement - 108) * 2
|
||||
elif 109 <= movement < 124:
|
||||
message["groundspeeed"] = 100 + (movement - 123) * 5
|
||||
|
||||
if (input[5] & 0b00001000) >> 3:
|
||||
track = ((input[5] & 0b00000111) << 3) | ((input[6] & 0b11110000) >> 4)
|
||||
message["groundtrack"] = (360 * track) / 128
|
||||
|
||||
cpr = self.__getCprData(icao, input, CprRecordType.GROUND)
|
||||
if cpr is not None:
|
||||
lat, lon = cpr
|
||||
message["lat"] = lat
|
||||
message["lon"] = lon
|
||||
|
||||
logger.debug("decoded ads-b ground data: %s", icao, message)
|
||||
|
||||
elif type in [9, 10, 11, 12, 13, 14, 15, 16, 17, 18]:
|
||||
# airborne position (w/ baro altitude)
|
||||
|
||||
cpr = self.__getCprData(icao, input)
|
||||
cpr = self.__getCprData(icao, input, CprRecordType.AIR)
|
||||
if cpr is not None:
|
||||
lat, lon = cpr
|
||||
message["lat"] = lat
|
||||
|
|
@ -215,7 +262,7 @@ class ModeSParser(PickleModule):
|
|||
elif type in [20, 21, 22]:
|
||||
# airborne position (w/GNSS height)
|
||||
|
||||
cpr = self.__getCprData(icao, input)
|
||||
cpr = self.__getCprData(icao, input, CprRecordType.AIR)
|
||||
if cpr is not None:
|
||||
lat, lon = cpr
|
||||
message["lat"] = lat
|
||||
|
|
@ -249,14 +296,14 @@ class ModeSParser(PickleModule):
|
|||
|
||||
return message
|
||||
|
||||
def __getCprData(self, icao: str, input):
|
||||
def __getCprData(self, icao: str, input, cprType: CprRecordType):
|
||||
self.cprCache.addRecord(icao, {
|
||||
"cpr_format": (input[6] & 0b00000100) >> 2,
|
||||
"lat_cpr": ((input[6] & 0b00000011) << 15) | (input[7] << 7) | ((input[8] & 0b11111110) >> 1),
|
||||
"lon_cpr": ((input[8] & 0b00000001) << 16) | (input[9] << 8) | (input[10]),
|
||||
})
|
||||
}, cprType)
|
||||
|
||||
records = self.cprCache.getRecentData(icao)
|
||||
records = self.cprCache.getRecentData(icao, cprType)
|
||||
|
||||
try:
|
||||
# records are sorted by timestamp, last should be newest
|
||||
|
|
@ -270,6 +317,10 @@ class ModeSParser(PickleModule):
|
|||
# latitude zone index
|
||||
j = floor(59 * lat_cpr_even - 60 * lat_cpr_odd + .5)
|
||||
|
||||
nz = 15
|
||||
d_lat_even = cprType.baseAngle / (4 * nz)
|
||||
d_lat_odd = cprType.baseAngle / (4 * nz - 1)
|
||||
|
||||
lat_even = d_lat_even * ((j % 60) + lat_cpr_even)
|
||||
lat_odd = d_lat_odd * ((j % 59) + lat_cpr_odd)
|
||||
|
||||
|
|
@ -308,8 +359,8 @@ class ModeSParser(PickleModule):
|
|||
n_even = max(nl_lat, 1)
|
||||
n_odd = max(nl_lat - 1, 1)
|
||||
|
||||
d_lon_even = 360 / n_even
|
||||
d_lon_odd = 360 / n_odd
|
||||
d_lon_even = cprType.baseAngle / n_even
|
||||
d_lon_odd = cprType.baseAngle / n_odd
|
||||
|
||||
lon_even = d_lon_even * (m % n_even + lon_cpr_even)
|
||||
lon_odd = d_lon_odd * (m % n_odd + lon_cpr_odd)
|
||||
|
|
|
|||
Loading…
Reference in a new issue