complete surface position decoding
This commit is contained in:
parent
b648f4e7a5
commit
4bafa1de19
17
README.rst
17
README.rst
@ -62,15 +62,24 @@ Core functions for ADS-B decoding:
|
|||||||
|
|
||||||
pms.adsb.icao(msg)
|
pms.adsb.icao(msg)
|
||||||
pms.adsb.callsign(msg)
|
pms.adsb.callsign(msg)
|
||||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd)
|
|
||||||
|
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||||
|
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||||
|
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||||
|
|
||||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||||
|
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||||
|
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||||
|
|
||||||
pms.adsb.altitude(msg)
|
pms.adsb.altitude(msg)
|
||||||
pms.adsb.velocity(msg)
|
pms.adsb.velocity(msg)
|
||||||
pms.adsb.speed_heading(msg)
|
pms.adsb.speed_heading(msg)
|
||||||
|
|
||||||
**Hint: When you have a fix position of the aircraft or you know the
|
**Hint: When you have a fix position of the aircraft, it is convenient to
|
||||||
location of your receiver, it is convinent to use `position_with_ref()` method
|
use `position_with_ref()` method to decode with only one position message
|
||||||
to decode with only one position message (either odd or even)**
|
(either odd or even). This works with both airborne and surface position
|
||||||
|
messages. But the reference position shall be with in 180NM (airborne)
|
||||||
|
or 45NM (surface) of the true position.**
|
||||||
|
|
||||||
Core functions for EHS decoding:
|
Core functions for EHS decoding:
|
||||||
|
|
||||||
|
108
pyModeS/adsb.py
108
pyModeS/adsb.py
@ -167,7 +167,7 @@ def cprlon(msg):
|
|||||||
return util.bin2int(msgbin[71:88])
|
return util.bin2int(msgbin[71:88])
|
||||||
|
|
||||||
|
|
||||||
def position(msg0, msg1, t0, t1):
|
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||||
"""Decode position from a pair of even and odd position message
|
"""Decode position from a pair of even and odd position message
|
||||||
(works with both airborne and surface position messages)
|
(works with both airborne and surface position messages)
|
||||||
|
|
||||||
@ -181,7 +181,12 @@ def position(msg0, msg1, t0, t1):
|
|||||||
(float, float): (latitude, longitude) of the aircraft
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
"""
|
"""
|
||||||
if (5 <= typecode(msg0) <= 8 and 5 <= typecode(msg1) <= 8):
|
if (5 <= typecode(msg0) <= 8 and 5 <= typecode(msg1) <= 8):
|
||||||
return surface_position(msg0, msg1, t0, t1)
|
if (not lat_ref) or (not lon_ref):
|
||||||
|
raise RuntimeError("Surface position encountered, a reference \
|
||||||
|
position lat/lon required. Location of \
|
||||||
|
receiver can be used.")
|
||||||
|
else:
|
||||||
|
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
||||||
|
|
||||||
elif (9 <= typecode(msg0) <= 18 and 9 <= typecode(msg1) <= 18):
|
elif (9 <= typecode(msg0) <= 18 and 9 <= typecode(msg1) <= 18):
|
||||||
return airborne_position(msg0, msg1, t0, t1)
|
return airborne_position(msg0, msg1, t0, t1)
|
||||||
@ -233,17 +238,17 @@ def airborne_position(msg0, msg1, t0, t1):
|
|||||||
|
|
||||||
# compute ni, longitude index m, and longitude
|
# compute ni, longitude index m, and longitude
|
||||||
if (t0 > t1):
|
if (t0 > t1):
|
||||||
ni = max(_cprNL(lat_even), 1)
|
|
||||||
m = util.floor(cprlon_even * (_cprNL(lat_even)-1) -
|
|
||||||
cprlon_odd * _cprNL(lat_even) + 0.5)
|
|
||||||
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
|
||||||
lat = lat_even
|
lat = lat_even
|
||||||
|
nl = _cprNL(lat)
|
||||||
|
ni = max(_cprNL(lat)- 0, 1)
|
||||||
|
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
||||||
else:
|
else:
|
||||||
ni = max(_cprNL(lat_odd) - 1, 1)
|
|
||||||
m = util.floor(cprlon_even * (_cprNL(lat_odd)-1) -
|
|
||||||
cprlon_odd * _cprNL(lat_odd) + 0.5)
|
|
||||||
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
|
||||||
lat = lat_odd
|
lat = lat_odd
|
||||||
|
nl = _cprNL(lat)
|
||||||
|
ni = max(_cprNL(lat) - 1, 1)
|
||||||
|
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
||||||
|
|
||||||
if lon > 180:
|
if lon > 180:
|
||||||
lon = lon - 360
|
lon = lon - 360
|
||||||
@ -255,7 +260,9 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
"""Decode position with only one message,
|
"""Decode position with only one message,
|
||||||
knowing reference nearby location, such as previously
|
knowing reference nearby location, such as previously
|
||||||
calculated location, ground station, or airport location, etc.
|
calculated location, ground station, or airport location, etc.
|
||||||
(works with both airborne and surface position messages)
|
Works with both airborne and surface position messages.
|
||||||
|
The reference position shall be with in 180NM (airborne) or 45NM (surface)
|
||||||
|
of the true position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg0 (string): even message (28 bytes hexadecimal string)
|
msg0 (string): even message (28 bytes hexadecimal string)
|
||||||
@ -279,7 +286,8 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||||
"""Decode airborne position with only one message,
|
"""Decode airborne position with only one message,
|
||||||
knowing reference nearby location, such as previously calculated location,
|
knowing reference nearby location, such as previously calculated location,
|
||||||
ground station, or airport location, etc.
|
ground station, or airport location, etc. The reference position shall
|
||||||
|
be with in 180NM of the true position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): even message (28 bytes hexadecimal string)
|
msg (string): even message (28 bytes hexadecimal string)
|
||||||
@ -317,15 +325,83 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
return round(lat, 5), round(lon, 5)
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def surface_position(msg0, msg1, t0, t1):
|
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||||
# TODO: implement surface positon
|
"""Decode surface position from a pair of even and odd position message,
|
||||||
raise RuntimeError('suface position decoding to be implemented soon...')
|
the lat/lon of receiver must be provided to yield the correct solution.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg0 (string): even message (28 bytes hexadecimal string)
|
||||||
|
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||||
|
t0 (int): timestamps for the even message
|
||||||
|
t1 (int): timestamps for the odd message
|
||||||
|
lat_ref (float): latitude of the receiver
|
||||||
|
lon_ref (float): longitude of the receiver
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
|
"""
|
||||||
|
|
||||||
|
msgbin0 = util.hex2bin(msg0)
|
||||||
|
msgbin1 = util.hex2bin(msg1)
|
||||||
|
|
||||||
|
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||||
|
cprlat_even = util.bin2int(msgbin0[54:71]) / 131072.0
|
||||||
|
cprlon_even = util.bin2int(msgbin0[71:88]) / 131072.0
|
||||||
|
cprlat_odd = util.bin2int(msgbin1[54:71]) / 131072.0
|
||||||
|
cprlon_odd = util.bin2int(msgbin1[71:88]) / 131072.0
|
||||||
|
|
||||||
|
air_d_lat_even = 90.0 / 60
|
||||||
|
air_d_lat_odd = 90.0 / 59
|
||||||
|
|
||||||
|
# compute latitude index 'j'
|
||||||
|
j = util.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||||
|
|
||||||
|
# solution for north hemisphere
|
||||||
|
lat_even_n = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||||
|
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||||
|
|
||||||
|
# solution for north hemisphere
|
||||||
|
lat_even_s = lat_even_n - 90.0
|
||||||
|
lat_odd_s = lat_odd_n - 90.0
|
||||||
|
|
||||||
|
# chose which solution corrispondes to receiver location
|
||||||
|
lat_even = lat_even_n if lat_ref > 0 else lat_even_s
|
||||||
|
lat_odd = lat_odd_n if lat_ref > 0 else lat_odd_s
|
||||||
|
|
||||||
|
# check if both are in the same latidude zone, rare but possible
|
||||||
|
if _cprNL(lat_even) != _cprNL(lat_odd):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# compute ni, longitude index m, and longitude
|
||||||
|
if (t0 > t1):
|
||||||
|
lat = lat_even
|
||||||
|
nl = _cprNL(lat_even)
|
||||||
|
ni = max(_cprNL(lat_even) - 0, 1)
|
||||||
|
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
||||||
|
else:
|
||||||
|
lat = lat_odd
|
||||||
|
nl = _cprNL(lat_odd)
|
||||||
|
ni = max(_cprNL(lat_odd) - 1, 1)
|
||||||
|
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||||
|
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
||||||
|
|
||||||
|
# four possible longitude solutions
|
||||||
|
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
||||||
|
|
||||||
|
# the closest solution to receiver is the correct one
|
||||||
|
dls = [abs(lon_ref - l) for l in lons]
|
||||||
|
imin = min(range(4), key=dls.__getitem__)
|
||||||
|
lon = lons[imin]
|
||||||
|
|
||||||
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||||
"""Decode surface position with only one message,
|
"""Decode surface position with only one message,
|
||||||
knowing reference nearby location, such as previously calculated location,
|
knowing reference nearby location, such as previously calculated location,
|
||||||
ground station, or airport location, etc.
|
ground station, or airport location, etc. The reference position shall
|
||||||
|
be with in 45NM of the true position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): even message (28 bytes hexadecimal string)
|
msg (string): even message (28 bytes hexadecimal string)
|
||||||
|
@ -37,6 +37,13 @@ def test_adsb_surface_position_with_ref():
|
|||||||
assert pos == (-43.48564, 175.87195)
|
assert pos == (-43.48564, 175.87195)
|
||||||
|
|
||||||
|
|
||||||
|
def test_adsb_surface_position():
|
||||||
|
pos = adsb.surface_position("8CC8200A3AC8F009BCDEF2000000",
|
||||||
|
"8FC8200A3AB8F5F893096B000000",
|
||||||
|
0, 2,
|
||||||
|
-43.496, 172.558)
|
||||||
|
assert pos == (-43.48564, 172.53942)
|
||||||
|
|
||||||
def test_adsb_alt():
|
def test_adsb_alt():
|
||||||
assert adsb.altitude("8D40058B58C901375147EFD09357") == 39000
|
assert adsb.altitude("8D40058B58C901375147EFD09357") == 39000
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ def test_ehs_BDS():
|
|||||||
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
|
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
|
||||||
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
|
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
|
||||||
assert ehs.BDS("A000029CFFBAA11E2004727281F1") == 'BDS60'
|
assert ehs.BDS("A000029CFFBAA11E2004727281F1") == 'BDS60'
|
||||||
assert ehs.BDS("A0281838CAE9E12FA03FFF2DDDE5") is None
|
assert ehs.BDS("A0281838CAE9E12FA03FFF2DDDE5") == 'BDS44'
|
||||||
|
assert ehs.BDS("A00017B0C8480030A4000024512F") is None
|
||||||
|
|
||||||
|
|
||||||
def test_ehs_BDS20_callsign():
|
def test_ehs_BDS20_callsign():
|
||||||
|
Loading…
Reference in New Issue
Block a user