N Number & ICAO address

Here is some Python code that I wrote to go to / from N to ICAO. It is based on looking at the structure of BartJr’s table. Spot-checking a few flights on the air it seems to match. This has not been extensively tested and may have errors. Also there is no protection against invalid inputs in the tail to ICAO routine.

The general principle is that an N number has one to three digits after the N. The first digit is never a zero. These are most significant in setting the ICAO number. A number N2nnxx wiil always be exactly 101711 (decimal) away from its N1nnxx counterpart. After this one, two, or three digit number, a suffix of zero to two letters can be applied. If there are 3 digits after the N, the suffix may contain numbers, but if the first digit of the suffix is a letter, the second one (if present) must also be a letter (“N123A1” is illegal). This gives the effect of a 4 or 5 digit N number, but actually internally these are extensions of a 3 digit number with what I call a “base 35” suffix.


base9 = '123456789'  # The first digit (after the "N") is always one of these.
base10 = '0123456789' # The possible second and third digits are one of these.
# Note that "I" and "O" are never used as letters, to prevent confusion with "1" and "0"
base34 = 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789' 
icaooffset = 0xA00001 # The lowest possible number, N1, is this.
b1 = 101711 # basis between N1... and N2...
b2 = 10111 # basis between N10.... and N11....

def suffix(rem):
    """ Produces the alpha(numeric) suffix from a number 0 - 950 """
    if rem == 0:
        suf = ''
    else:
        if rem <= 600: #Class A suffix -- only letters.
            rem = rem - 1
            suf = base34[rem // 25]
            if rem % 25 > 0:
                suf = suf + base34(rem % 25) - 1] # second class A letter, if present.
        else:  #rem > 600 : First digit of suffix is a number.  Second digit may be blank, letter, or number.
            rem = rem - 601
            suf = base10[rem // 35]
            if rem % 35 > 0:
                suf = suf + base34(rem % 35) - 1]
    return suf
    
def enc_suffix(suf):
    """ Produces a remainder from a 0 - 2 digit suffix. 
    No error checking.  Using illegal strings will have strange results."""
    if len(suf) == 0:
        return 0
    r0 = base34.find(suf[0])
    if len(suf) == 1:
        r1 = 0
    else:
        r1 = base34.find(suf[1]) + 1
    if r0 < 24: # first char is a letter, use base 25
        return r0 * 25 + r1 + 1
    else:  # first is a number -- base 35.
        return r0 * 35 + r1 - 239   
    
def icao_to_tail(icao):
    if (icao < 0) or (icao > 0xadf7c7):
        return "Undefined"
    icao = icao - icaooffset
    d1 = icao // b1
    nnum = 'N' + base9[d1]
    r1 = icao % b1
    if r1 < 601:
        nnum = nnum + suffix(r1) # of the form N1ZZ
    else:
        d2 = (r1 - 601) // b2  # find second digit.
        nnum = nnum + base10[d2]
        r2 = (r1 - 601) % b2  # and residue after that
        if r2 < 601:  # No third digit. (form N12ZZ)
            nnum = nnum + suffix(r2)
        else:
            d3 = (r2 - 601) // 951 # Three-digits have extended suffix.
            r3 = (r2 - 601) % 951   
            nnum = nnum + base10[d3] + suffix(r3)
    return nnum 
    
def tail_to_icao(tail):
    if tail[0] != 'N':
        return -1
    icao = icaooffset
    icao = icao + base9.find(tail[1]) * b1
    if len(tail) == 2: # simple 'N3' etc.
        return icao
    d2 = base10.find(tail[2])
    if d2 == -1: # Form N1A
        icao = icao + enc_suffix(tail[2:4])
        return icao
    else: # Form N11... or N111..
        icao = icao + d2 * b2 + 601
        d3 = base10.find(tail[3])
        if d3 > -1: #Form N111 Suffix is base 35.
            icao = icao + d3 * 951 + 601
            icao = icao + enc_suffix(tail[4:6])
            return icao
        else:  #Form N11A
            icao = icao + enc_suffix(tail[3:5])
            return icao