def airborne_position(msg0, msg1, t0, t1): """Decode airborn position from a pair of even and odd position message 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 """ 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 = 360.0 / 60 air_d_lat_odd = 360.0 / 59 # compute latitude index 'j' j = util.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5) lat_even = float(air_d_lat_even * (j % 60 + cprlat_even)) lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd)) if lat_even >= 270: lat_even = lat_even - 360 if lat_odd >= 270: lat_odd = lat_odd - 360 # check if both are in the same latidude zone, exit if not 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) 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: 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: lon = lon - 360 return round(lat, 5), round(lon, 5)
def callsign(msg): """Aircraft callsign Args: msg (string): 28 bytes hexadecimal message string Returns: string: callsign """ if typecode(msg) < 1 or typecode(msg) > 4: raise RuntimeError("%s: Not a identification message" % msg) chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######' msgbin = util.hex2bin(msg) csbin = msgbin[40:96] cs = '' cs += chars[util.bin2int(csbin[0:6])] cs += chars[util.bin2int(csbin[6:12])] cs += chars[util.bin2int(csbin[12:18])] cs += chars[util.bin2int(csbin[18:24])] cs += chars[util.bin2int(csbin[24:30])] cs += chars[util.bin2int(csbin[30:36])] cs += chars[util.bin2int(csbin[36:42])] cs += chars[util.bin2int(csbin[42:48])] # clean string, remove spaces and marks, if any. # cs = cs.replace('_', '') cs = cs.replace('#', '') return cs
def _generateRandomID( self, c1 = constants.CRYPTO_CHALLENGE_C1, c2 = constants.CRYPTO_CHALLENGE_C2): """Generates the NodeID by solving two cryptographic puzzles.""" # Solve the static cryptographic puzzle. rsaKey = None p = 0x1 # non-zero value pub = None randomStream = Crypto.Random.new().read while not util.hasNZeroBitPrefix(p, c1): rsaKey = Crypto.PublicKey.RSA.generate(constants.RSA_BITS, randomStream) pub = str(rsaKey.n) + str(rsaKey.e) p = util.hsh2int(Crypto.Hash.SHA.new(Crypto.Hash.SHA.new(pub).digest())) # created correct NodeID self.rsaKey = rsaKey nodeID = Crypto.Hash.SHA.new(pub) # Solve the dynamic cryptographic puzzle. p, x = 0x1, None while not util.hasNZeroBitPrefix(p, c2): x = util.bin2int(util.generateRandomString(constants.ID_LENGTH)) # This is madness! p = util.hsh2int( Crypto.Hash.SHA.new( util.int2bin( (util.hsh2int(nodeID) ^ x)))) # Found a correct value of X and nodeID self.x = x return nodeID.digest()
def typecode(msg): """Type code of ADS-B message Args: msg (string): 28 bytes hexadecimal message string Returns: int: type code number """ msgbin = util.hex2bin(msg) return util.bin2int(msgbin[32:37])
def _verifyID(self, nodeID, x): '''Verifies if a user's ID has been generated using the ''' p1 = util.hsh2int(Crypto.Hash.SHA.new(nodeID)) p2 = util.hsh2int(Crypto.Hash.SHA.new( util.int2bin((util.bin2int(nodeID) ^ x)))) # check preceeding c_i bits in P1 and P2 using sharesXPrefices. return ( util.hasNZeroBitPrefix(p1, constants.CRYPTO_CHALLENGE_C1) and util.hasNZeroBitPrefix(p2, constants.CRYPTO_CHALLENGE_C2))
def pbaro(msg): """Barometric pressure setting Args: msg (String): 28 bytes hexadecimal message (BDS40) string Returns: float: pressure in millibar """ d = util.hex2bin(data(msg)) p = util.bin2int(d[27:39]) * 0.1 + 800 # millibar return p
def gs(msg): """Aircraft ground speed Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: int: ground speed in knots """ d = util.hex2bin(data(msg)) spd = util.bin2int(d[24:34]) * 2 # kts return spd
def alt_fms(msg): """Selected altitude, FMS Args: msg (String): 28 bytes hexadecimal message (BDS40) string Returns: int: altitude in feet """ d = util.hex2bin(data(msg)) alt = util.bin2int(d[14:26]) * 16 # ft return alt
def mach(msg): """Aircraft MACH number Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: float: MACH number """ d = util.hex2bin(data(msg)) mach = util.bin2int(d[24:34]) * 2.048 / 512.0 return round(mach, 3)
def ias(msg): """Indicated airspeed Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: indicated airspeed in knots """ d = util.hex2bin(data(msg)) ias = util.bin2int(d[13:23]) # kts return ias
def tas(msg): """Aircraft true airspeed Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: int: true airspeed in knots """ d = util.hex2bin(data(msg)) spd = util.bin2int(d[46:56]) * 2 # kts return spd
def velocity(msg): """Calculate the speed, heading, and vertical rate Args: msg (string): 28 bytes hexadecimal message string Returns: (int, float, int, string): speed (kt), heading (degree), rate of climb/descend (ft/min), and speed type ('GS' for ground speed, 'AS' for airspeed) """ if typecode(msg) != 19: raise RuntimeError("%s: Not a airborne velocity message" % msg) msgbin = util.hex2bin(msg) subtype = util.bin2int(msgbin[37:40]) if subtype in (1, 2): v_ew_sign = util.bin2int(msgbin[45]) v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity v_ns_sign = util.bin2int(msgbin[56]) v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity v_we = -1*v_ew if v_ew_sign else v_ew v_sn = -1*v_ns if v_ns_sign else v_ns spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts hdg = math.atan2(v_we, v_sn) hdg = math.degrees(hdg) # convert to degrees hdg = hdg if hdg >= 0 else hdg + 360 # no negative val tag = 'GS' else: hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0 spd = util.bin2int(msgbin[57:67]) tag = 'AS' vr_sign = util.bin2int(msgbin[68]) vr = util.bin2int(msgbin[68:77]) # vertical rate rocd = -1*vr if vr_sign else vr # rate of climb/descend return int(spd), round(hdg, 1), int(rocd), tag
def surface_velocity(msg): """Decode surface velocity from from a surface position message Args: msg (string): 28 bytes hexadecimal message string Returns: (int, float, int, string): speed (kt), heading (degree), rate of climb/descend (ft/min), and speed type ('GS' for ground speed, 'AS' for airspeed) """ if typecode(msg) < 5 or typecode(msg) > 8: raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg) msgbin = util.hex2bin(msg) # heading hdg_status = int(msgbin[44]) if hdg_status == 1: hdg = util.bin2int(msgbin[45:52]) * 360.0 / 128.0 hdg = round(hdg, 1) else: hdg = None # ground movment / speed mov = util.bin2int(msgbin[37:44]) if mov == 0 or mov > 124: spd = None elif mov == 1: spd = 0 elif mov == 124: spd = 175 else: movs = [2, 9, 13, 39, 94, 109, 124] kts = [0.125, 1, 2, 15, 70, 100, 175] i = next(m[0] for m in enumerate(movs) if m[1] > mov) step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1]) spd = kts[i - 1] + (mov - movs[i - 1]) * step spd = round(spd, 2) return spd, hdg, 0, 'GS'
def velocity(msg): """Calculate the speed, heading, and vertical rate Args: msg (string): 28 bytes hexadecimal message string Returns: (int, float, int, string): speed (kt), heading (degree), rate of climb/descend (ft/min), and speed type ('GS' for ground speed, 'AS' for airspeed) """ if typecode(msg) != 19: raise RuntimeError("%s: Not a airborne velocity message" % msg) msgbin = util.hex2bin(msg) subtype = util.bin2int(msgbin[37:40]) if subtype in (1, 2): v_ew_sign = util.bin2int(msgbin[45]) v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity v_ns_sign = util.bin2int(msgbin[56]) v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity v_we = -1 * v_ew if v_ew_sign else v_ew v_sn = -1 * v_ns if v_ns_sign else v_ns spd = math.sqrt(v_sn * v_sn + v_we * v_we) # unit in kts hdg = math.atan2(v_we, v_sn) hdg = math.degrees(hdg) # convert to degrees hdg = hdg if hdg >= 0 else hdg + 360 # no negative val tag = 'GS' else: hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0 spd = util.bin2int(msgbin[57:67]) tag = 'AS' vr_sign = util.bin2int(msgbin[68]) vr = util.bin2int(msgbin[68:77]) # vertical rate rocd = -1 * vr if vr_sign else vr # rate of climb/descend return int(spd), round(hdg, 1), int(rocd), tag
def track(msg): """True track angle Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle in degrees to true north (from 0 to 360) """ d = util.hex2bin(data(msg)) sign = int(d[12]) # 1 -> west value = util.bin2int(d[13:23]) * 90 / 512.0 # degree angle = 360 - value if sign else value return round(angle, 1)
def baro_vr(msg): """Vertical rate from barometric measurement Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: vertical rate in feet/minutes """ d = util.hex2bin(data(msg)) sign = d[35] # 1 -> minus value = util.bin2int(d[36:45]) * 32 # feet/min roc = -1 * value if sign else value return roc
def cprlon(msg): """CPR encoded longitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: encoded longitude """ if typecode(msg) < 5 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = util.hex2bin(msg) return util.bin2int(msgbin[71:88])
def category(msg): """Aircraft category number Args: msg (string): 28 bytes hexadecimal message string Returns: int: category number """ if typecode(msg) < 1 or typecode(msg) > 4: raise RuntimeError("%s: Not a identification message" % msg) msgbin = util.hex2bin(msg) return util.bin2int(msgbin[5:8])
def heading(msg): """Megnetic heading of aircraft Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: float: heading in degrees to megnetic north (from 0 to 360) """ d = util.hex2bin(data(msg)) sign = int(d[1]) # 1 -> west value = util.bin2int(d[2:12]) * 90 / 512.0 # degree hdg = 360 - value if sign else value return round(hdg, 1)
def rtrack(msg): """Track angle rate Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle rate in degrees/second """ d = util.hex2bin(data(msg)) sign = int(d[35]) # 1 -> minus value = util.bin2int(d[36:45]) * 8 / 256.0 # degree / sec angle = -1 * value if sign else value return round(angle, 3)
def ins_vr(msg): """Vertical rate messured by onbard equiments (IRS, AHRS) Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: vertical rate in feet/minutes """ d = util.hex2bin(data(msg)) sign = d[46] # 1 -> minus value = util.bin2int(d[47:56]) * 32 # feet/min roc = -1 * value if sign else value return roc
def baro_vr(msg): """Vertical rate from barometric measurement Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: vertical rate in feet/minutes """ d = util.hex2bin(data(msg)) sign = d[35] # 1 -> minus value = util.bin2int(d[36:45]) * 32 # feet/min roc = -1*value if sign else value return roc
def ins_vr(msg): """Vertical rate messured by onbard equiments (IRS, AHRS) Args: msg (String): 28 bytes hexadecimal message (BDS60) string Returns: int: vertical rate in feet/minutes """ d = util.hex2bin(data(msg)) sign = d[46] # 1 -> minus value = util.bin2int(d[47:56]) * 32 # feet/min roc = -1*value if sign else value return roc
def isBDS20(msg): """Check if a message is likely to be BDS code 2,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ # status bit 1, 14, and 27 d = util.hex2bin(data(msg)) result = True if util.bin2int(d[0:4]) != 2 or util.bin2int(d[4:8]) != 0: result &= False cs = callsign(msg) if '#' in cs: result &= False return result
def roll(msg): """Aircraft roll angle Args: msg (String): 28 bytes hexadecimal message (BDS50) string Returns: float: angle in degrees, negative->left wing down, positive->right wing down """ d = util.hex2bin(data(msg)) sign = int(d[1]) # 1 -> left wing down value = util.bin2int(d[2:11]) * 45 / 256.0 # degree angle = -1 * value if sign else value return round(angle, 1)
def checkbits(data, sb, msb, lsb): """Check if the status bit and field bits are consistency. This Function is used for checking BDS code versions. """ # status bit, most significant bit, least significant bit status = int(data[sb - 1]) value = util.bin2int(data[msb - 1:lsb]) if not status: if value != 0: return False return True
def checkbits(data, sb, msb, lsb): """Check if the status bit and field bits are consistency. This Function is used for checking BDS code versions. """ # status bit, most significant bit, least significant bit status = int(data[sb-1]) value = util.bin2int(data[msb-1:lsb]) if not status: if value != 0: return False return True
def surface_position_with_ref(msg, lat_ref, lon_ref): """Decode surface position with only one message, knowing reference nearby location, such as previously calculated location, ground station, or airport location, etc. The reference position shall be with in 45NM 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 """ i = oe_flag(msg) d_lat = 90.0 / 59 if i else 90.0 / 60 msgbin = util.hex2bin(msg) cprlat = util.bin2int(msgbin[54:71]) / 131072.0 cprlon = util.bin2int(msgbin[71:88]) / 131072.0 j = util.floor(lat_ref / d_lat) \ + util.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat) lat = d_lat * (j + cprlat) ni = _cprNL(lat) - i if ni > 0: d_lon = 90.0 / ni else: d_lon = 90.0 m = util.floor(lon_ref / d_lon) \ + util.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon) lon = d_lon * (m + cprlon) return round(lat, 5), round(lon, 5)
def isBDS40(msg): """Check if a message is likely to be BDS code 4,0 Args: msg (String): 28 bytes hexadecimal message string Returns: bool: True or False """ # status bit 1, 14, and 27 d = util.hex2bin(data(msg)) result = True result = result & checkbits(d, 1, 2, 13) \ & checkbits(d, 14, 15, 26) & checkbits(d, 27, 28, 39) # bits 40-47 and 52-53 shall all be zero if util.bin2int(d[39:47]) != 0: result &= False if util.bin2int(d[51:53]) != 0: result &= False return result
def icao(msg): """Calculate the ICAO address from an Mode-S message with DF4, DF5, DF20, DF21 Args: msg (String): 28 bytes hexadecimal message string Returns: String: ICAO address in 6 bytes hexadecimal string """ if df(msg) not in (4, 5, 20, 21): # raise RuntimeError("Message DF must be in (4, 5, 20, 21)") return None c0 = util.bin2int(crc(msg, encode=True)) c1 = util.hex2int(msg[-6:]) icao = '%06X' % (c0 ^ c1) return icao
def altitude(msg): """Decode aircraft altitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: altitude in feet """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = util.hex2bin(msg) q = msgbin[47] if q: n = util.bin2int(msgbin[40:47]+msgbin[48:52]) alt = n * 25 - 1000 return alt else: return None
def altitude(msg): """Decode aircraft altitude Args: msg (string): 28 bytes hexadecimal message string Returns: int: altitude in feet """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError("%s: Not a position message" % msg) msgbin = util.hex2bin(msg) q = msgbin[47] if q: n = util.bin2int(msgbin[40:47] + msgbin[48:52]) alt = n * 25 - 1000 return alt else: return None
def nic(msg): """Calculate NIC, navigation integrity category Args: msg (string): 28 bytes hexadecimal message string Returns: int: NIC number (from 0 to 11), -1 if not applicable """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError( "%s: Not a airborne position message, expecting 8<TC<19" % msg) msgbin = util.hex2bin(msg) tc = typecode(msg) nic_sup_b = util.bin2int(msgbin[39]) if tc in [0, 18, 22]: nic = 0 elif tc == 17: nic = 1 elif tc == 16: if nic_sup_b: nic = 3 else: nic = 2 elif tc == 15: nic = 4 elif tc == 14: nic = 5 elif tc == 13: nic = 6 elif tc == 12: nic = 7 elif tc == 11: if nic_sup_b: nic = 9 else: nic = 8 elif tc in [10, 21]: nic = 10 elif tc in [9, 20]: nic = 11 else: nic = -1 return nic
def nic(msg): """Calculate NIC, navigation integrity category Args: msg (string): 28 bytes hexadecimal message string Returns: int: NIC number (from 0 to 11), -1 if not applicable """ if typecode(msg) < 9 or typecode(msg) > 18: raise RuntimeError("%s: Not a airborne position message" % msg) msgbin = util.hex2bin(msg) tc = typecode(msg) nic_sup_b = util.bin2int(msgbin[39]) if tc in [0, 18, 22]: nic = 0 elif tc == 17: nic = 1 elif tc == 16: if nic_sup_b: nic = 3 else: nic = 2 elif tc == 15: nic = 4 elif tc == 14: nic = 5 elif tc == 13: nic = 6 elif tc == 12: nic = 7 elif tc == 11: if nic_sup_b: nic = 9 else: nic = 8 elif tc in [10, 21]: nic = 10 elif tc in [9, 20]: nic = 11 else: nic = -1 return nic
def callsign(msg): """Aircraft callsign Args: msg (String): 28 bytes hexadecimal message (BDS40) string Returns: string: callsign, max. 8 chars """ chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######' d = util.hex2bin(data(msg)) cs = '' cs += chars[util.bin2int(d[8:14])] cs += chars[util.bin2int(d[14:20])] cs += chars[util.bin2int(d[20:26])] cs += chars[util.bin2int(d[26:32])] cs += chars[util.bin2int(d[32:38])] cs += chars[util.bin2int(d[38:44])] cs += chars[util.bin2int(d[44:50])] cs += chars[util.bin2int(d[50:56])] return cs
def altitude_diff(msg): """Decode the differece between GNSS and barometric altitude Args: msg (string): 28 bytes hexadecimal message string, TC=19 Returns: int: Altitude difference in ft. Negative value indicates GNSS altitude below barometric altitude. """ if typecode(msg) != 19: raise RuntimeError("incorrect message types, expecting TC=19") msgbin = util.hex2bin(msg) sign = -1 if int(msgbin[80]) else 1 value = util.bin2int(msgbin[81:88]) if value == 0 or value == 127: return None else: return sign * (value - 1) * 25 # in ft.
def position(msg0, msg1, t0, t1): """Decode position from the combination of even and odd position message 131072 is 2^17, since CPR lat and lon are 17 bits each. 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 """ if typecode(msg0) < 5 or typecode(msg0) > 18: raise RuntimeError("%s: Not a position message" % msg0) if typecode(msg1) < 5 or typecode(msg1) > 18: raise RuntimeError("%s: Not a position message" % msg1) msgbin0 = util.hex2bin(msg0) msgbin1 = util.hex2bin(msg1) 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 = 360.0 / 60 air_d_lat_odd = 360.0 / 59 # compute latitude index 'j' j = int(math.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)) lat_even = float(air_d_lat_even * (j % 60 + cprlat_even)) lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd)) if lat_even >= 270: lat_even = lat_even - 360 if lat_odd >= 270: lat_odd = lat_odd - 360 # check if both are in the same latidude zone, exit if not if _cprNL(lat_even) != _cprNL(lat_odd): return None # compute ni, longitude index m, and longitude if (t0 > t1): ni = _cprN(lat_even, 0) m = math.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 else: ni = _cprN(lat_odd, 1) m = math.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 if lon > 180: lon = lon - 360 return round(lat, 5), round(lon, 5)
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref): """Decode surface position from a pair of even and odd position message, 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 position(msg0, msg1, t0, t1): """Decode position from the combination of even and odd position message 131072 is 2^17, since CPR lat and lon are 17 bits each. 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 """ if typecode(msg0) < 5 or typecode(msg0) > 18: raise RuntimeError("%s: Not a position message" % msg0) if typecode(msg1) < 5 or typecode(msg1) > 18: raise RuntimeError("%s: Not a position message" % msg1) msgbin0 = util.hex2bin(msg0) msgbin1 = util.hex2bin(msg1) 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 = 360.0 / 60 air_d_lat_odd = 360.0 / 59 # compute latitude index 'j' j = int(math.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)) lat_even = float(air_d_lat_even * (j % 60 + cprlat_even)) lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd)) if lat_even >= 270: lat_even = lat_even - 360 if lat_odd >= 270: lat_odd = lat_odd - 360 # check if both are in the same latidude zone, exit if not if _cprNL(lat_even) != _cprNL(lat_odd): return None # compute ni, longitude index m, and longitude if (t0 > t1): ni = _cprN(lat_even, 0) m = math.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 else: ni = _cprN(lat_odd, 1) m = math.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 if lon > 180: lon = lon - 360 return round(lat, 5), round(lon, 5)