diff --git a/pyModeS/c_decoder/adsb.pyx b/pyModeS/c_decoder/adsb.pyx new file mode 100644 index 0000000..7f947fc --- /dev/null +++ b/pyModeS/c_decoder/adsb.pyx @@ -0,0 +1,235 @@ +# Copyright (C) 2015 Junzi Sun (TU Delft) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# cython: language_level=3 + +"""ADS-B Wrapper. + +The ADS-B wrapper also imports functions from the following modules: + +- pyModeS.decoder.bds.bds05 + Functions: ``airborne_position``, ``airborne_position_with_ref``, ``altitude`` +- pyModeS.decoder.bds.bds06 + Functions: ``surface_position``, ``surface_position_with_ref``, ``surface_velocity`` +- pyModeS.decoder.bds.bds08 + Functions: ``category``, ``callsign`` +- pyModeS.decoder.bds.bds09 + Functions: ``airborne_velocity``, ``altitude_diff`` + +""" + +from libc.math cimport NAN as nan + +# from pyModeS.decoder.bds import bds05, bds06, bds09 +from .common cimport typecode, icao as c_icao, df, hex2bin, bin2int, char_to_int + +from .bds.bds05 import ( + airborne_position, + airborne_position_with_ref, + altitude, +) +from .bds.bds06 import ( + surface_position, + surface_position_with_ref, + surface_velocity, +) +from .bds.bds08 import category, callsign +from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff + +def icao(bytes msg): + return c_icao(msg) + +def position(bytes msg0 not None, bytes msg1 not None, int t0, int t1, double lat_ref=nan, double lon_ref=nan): + """Decode position from a pair of even and odd position message + (works with both airborne and surface position messages) + + 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 + + Returns: + (float, float): (latitude, longitude) of the aircraft + """ + cdef int tc0 = typecode(msg0) + cdef int tc1 = typecode(msg1) + + if 5 <= tc0 <= 8 and 5 <= tc1 <= 8: + if (lat_ref != lat_ref) or (lon_ref != 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 <= tc0 <= 18 and 9 <= tc1 <= 18: + # Airborne position with barometric height + return airborne_position(msg0, msg1, t0, t1) + + elif 20 <= tc0 <= 22 and 20 <= tc1 <= 22: + # Airborne position with GNSS height + return airborne_position(msg0, msg1, t0, t1) + + else: + raise RuntimeError("incorrect or inconsistant message types") + + +def position_with_ref(bytes msg not None, double lat_ref, double lon_ref): + """Decode position with only one message, + knowing reference nearby location, such as previously + calculated location, ground station, or airport location, etc. + 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: + msg (string): even message (28 bytes hexadecimal string) + lat_ref: previous known latitude + lon_ref: previous known longitude + + Returns: + (float, float): (latitude, longitude) of the aircraft + """ + + cdef int tc = typecode(msg) + + if 5 <= tc <= 8: + return surface_position_with_ref(msg, lat_ref, lon_ref) + + elif 9 <= tc <= 18 or 20 <= tc <= 22: + return airborne_position_with_ref(msg, lat_ref, lon_ref) + + else: + raise RuntimeError("incorrect or inconsistant message types") + + +def altitude(bytes msg): + """Decode aircraft altitude + + Args: + msg (string): 28 bytes hexadecimal message string + + Returns: + int: altitude in feet + """ + + cdef int tc = typecode(msg) + + if tc < 5 or tc == 19 or tc > 22: + raise RuntimeError("%s: Not a position message" % msg) + + if tc >= 5 and tc <= 8: + # surface position, altitude 0 + return 0 + + cdef bytearray msgbin = hex2bin(msg) + cdef int q = char_to_int(msgbin[47]) + cdef int n + cdef double alt + if q: + n = bin2int(msgbin[40:47] + msgbin[48:52]) + alt = n * 25 - 1000 + return alt + else: + return nan + + +def velocity(bytes msg, bint rtn_sources=False): + """Calculate the speed, heading, and vertical rate + (handles both airborne or surface message) + + Args: + msg (string): 28 bytes hexadecimal message string + rtn_source (boolean): If the function will return + the sources for direction of travel and vertical + rate. This will change the return value from a four + element array to a six element array. + + Returns: + (int, float, int, string, string, string): speed (kt), + ground track or heading (degree), + rate of climb/descent (ft/min), speed type + ('GS' for ground speed, 'AS' for airspeed), + direction source ('true_north' for ground track / true north + as refrence, 'mag_north' for magnetic north as reference), + rate of climb/descent source ('Baro' for barometer, 'GNSS' + for GNSS constellation). + + In the case of surface messages, None will be put in place + for vertical rate and its respective sources. + """ + + cdef int tc = typecode(msg) + + if 5 <= tc <= 8: + return surface_velocity(msg, rtn_sources) + + elif tc == 19: + return airborne_velocity(msg, rtn_sources) + + else: + raise RuntimeError( + "incorrect or inconsistant message types, expecting 4 4: + raise RuntimeError("%s: Not a identification message" % msg) + + cdef bytearray msgbin = common.hex2bin(msg) + mebin = msgbin[32:87] + return common.bin2int(mebin[5:8]) + + +def callsign(bytes msg): + """Aircraft callsign + + Args: + msg (string): 28 bytes hexadecimal message string + + Returns: + string: callsign + """ + + cdef int tc = common.typecode(msg) + if tc < 1 or tc > 4: + raise RuntimeError("%s: Not a identification message" % msg) + + cdef bytearray _chars = bytearray( + b"#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######" + ) + cdef unsigned char[:] chars = _chars + cdef bytearray msgbin = common.hex2bin(msg) + cdef bytearray csbin = msgbin[40:96] + + cdef bytearray _cs = bytearray(8) + cdef unsigned char[:] cs = _cs + cs[0] = chars[common.bin2int(csbin[0:6])] + cs[1] = chars[common.bin2int(csbin[6:12])] + cs[2] = chars[common.bin2int(csbin[12:18])] + cs[3] = chars[common.bin2int(csbin[18:24])] + cs[4] = chars[common.bin2int(csbin[24:30])] + cs[5] = chars[common.bin2int(csbin[30:36])] + cs[6] = chars[common.bin2int(csbin[36:42])] + cs[7] = chars[common.bin2int(csbin[42:48])] + + # clean string, remove spaces and marks, if any. + # cs = cs.replace('_', '') + return _cs.decode().replace("#", "") diff --git a/setup.py b/setup.py index 103b162..ae17fca 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ extensions = [ Extension("pyModeS.c_decoder.adsb", ["pyModeS/c_decoder/adsb.pyx"]), Extension("pyModeS.c_decoder.bds.bds05", ["pyModeS/c_decoder/bds/bds05.pyx"]), Extension("pyModeS.c_decoder.bds.bds06", ["pyModeS/c_decoder/bds/bds06.pyx"]), + Extension("pyModeS.c_decoder.bds.bds08", ["pyModeS/c_decoder/bds/bds08.pyx"]), ]