ADS-B Reference
This discussion is about the universal ADS-B mode ES which stands for Extended Squit on 1090MHz originated from aircraft, and not the USA UAT mode requiring collaboration with ground stations.
Message structure
An ADS-B message is 112 bits long, and consists of 5 parts.
+--------+--------+-----------+--------------------------+---------+ | DF 5 | ** 3 | ICAO 24 | DATA 56 | PI 24 | +--------+--------+-----------+--------------------------+---------+
Any ADS-B must start with the Downlink Format 17, or 18 in case of TIS-B message. They correspond to 10001 or 10010 in binary for the first 5 bits. Bits 6-8 are used as an additional identifier, which has different meanings within each ADS-B subtype.
The key information of a ADS-B message is listed in the following table.
| Number bits used | Bits | Bit Abbr. | Name |
|---|---|---|---|
| 5 | 1 - 5 | DF | Downlink Format |
| 3 | 6 - 8 | CA | Capability (additional identifier) |
| 24 | 9 - 32 | ICAO | ICAO aircraft address |
| 56 | 33 - 88 | DATA | Data |
| [33 - 37] | [TC] | Type code | |
| 24 | 89 - 112 | PI | Parity/Interrogator ID |
It is worth noting that the ADS-B Extended Squitter sent from a Mode S transponder uses Downlink Format 17 (DF=17).
Non-Transponder-Based ADS-B Transmitting Subsystems and TIS-B Transmitting equipment use Downlink Format 18 (DF=18). By using DF=18 instead of DF=17, an ADS-B/TIS-B Receiving Subsystem will know that the message comes from equipment that cannot be interrogated.
ICAO address
In each ADS-B message, the sender (originating aircraft) can be identified using the ICAO address. It is located from 9 to 32 bits in binary (or 3 to 8 in hexadecimal).
An unique ICAO address is assigned to each Mode-S transponder of an aircraft. Thus this is a unique identifier for each aircraft. You can use the query tool (World Aircraft Database) from mode-s.org to find out more about the aircraft with a given ICAO address. For instance, using ICAO 4840D6 example, it will return the result of a Fokker 70 with registration of PH-KZD.
In addition, you can download the database from the aforementioned https://junzis.com/adb/ in CSV format.
ADS-B message types
Data carried is determined by the Type Code of the message, indicated at bits 33 - 37 of the ADS-B message
| ADS-B Type Code | Content |
|---|---|
| 1 - 4 | Aircraft identification |
| 5 - 8 | Surface position |
| 9 - 18 | Airborne position (w/ Baro Altitude) |
| 19 | Airborne velocities |
| 20 - 22 | Airborne position (w/ GNSS Height) |
| 23 - 27 | Reserved |
| 28 | Aircraft status |
| 29 | Target state and status information |
| 31 | Aircraft operation status |
ADS-B Checksum
ADS-B uses a cyclic redundancy check to validate the correctness of the received message, where the last 24 bits are the parity bits. The following pseudo-code describes the CRC process:
GENERATOR = 1111111111111010000001001
MSG = binary("8D4840D6202CC371C32CE0576098") # total 112 bits
FOR i FROM 0 TO 88: # 112 - 24 parity bits
if MSG[i] is 1:
MSG[i:i+24] = MSG[i:i+24] ^ GENERATOR
CRC = MSG[-24:] # last 24 bits
IF CRC not 0:
MSG is corrupted
For the implementation of CRC encoder in Python, refer to the pyModeS library function: pyModeS.crc()
A comprehensive documentation on Mode-S parity coding can be found:
Gertz, Jeffrey L. Fundamentals of mode s parity coding. No. ATC-117. MASSACHUSETTS INST OF TECH LEXINGTON LINCOLN LAB, 1984. APA
Compact Position Reporting
The position information in ADS-B messages is encoded in a compact position reporting (CPR) format. The general idea behind CPR is to be able to encode more coordinate decimals using less bits. It is achieved by trading global position ambiguity and time with local position accuracy.
An easy example to understand the principle behind CPR:

Imaging the world is constructed by 16 grid, which we have divided into two levels, each level is encoded with two bits. Higher levels in color are 00 (yellow), 01 (blue), 10 (red), 11 (green). And within each color grid, the lower levels are also encoded similarly.
Then each grid can be represented as 4 digits from 0000 to 1111. Now, we want to describe the movement indicated as the arrows in the green grids 1100 -> 1101, but we only have 3 bits to encode each position.
It is easy to see that the high 2 bits appeared in all positions, so we can define a structure to do the following:
The last two bits shall represent the local position
The combination of first digit from two messages defines the higher grid
Then the two messages can be sent as 1 00 -> 1 01.
From lower bits 00 -> 01, we have four different possibility of movement as shown in dashed arrows, and from the two first bits combination 11, we know that the arrow shall represent the movement in the green grids:

The CPR and functions
The actual CPR algorithm of course is more complicated, but the principle is very similar to the previous example. If only one message is given, it is possible to find multiple solutions that are spaced around the world. The combination of two (different types of) messages will yield the final result.
In CPR encoding, the Earth is divided in many zones (similar to the grid in the previous example). And the encoding algorithm is also more complicated (described in a later section). First, we will list some of the parameters and common functions used in the decoding process here.
NZ
Number of geographic latitude zones between equator and a pole. It is set to NZ = 15 for Mode-S CPR encoding.
floor(x)
the floor function floor(x) defines as the greatest integer value k, such that k<=x, for example:
floor(5.6) = 5 floor(-5.6) = -6
mod(x, y)
the modulus function mod(x, y) returns:

where y can not be zero
NL(lat)
Denotes the “number of longitude zones” function, given the latitude angle lat. The returned integer value is constrained within [1, 59], calculated as:

For latitudes that are close to the equator or the poles, one of following values is returned:
lat = 0 -> NL = 59 lat = +87 -> NL = 2 lat = -87 -> NL = 2 lat > +87 -> NL = 1 lat < -87 -> NL = 1
Position Reports
Airborne Reports
Airborne Positions
An aircraft airborne position message has downlink format 17 (or 18) with type code from 9 to 18
Messages are composed as follows:
| Data Bits | MSG Bits | N-bit | Abbr | Content |
|---|---|---|---|---|
| 33 - 37 | 1 - 5 | 5 | TC | Type code |
| 38 - 39 | 6 - 7 | 2 | SS | Surveillance status |
| 40 | 8 | 1 | NICsb | NIC supplement-B |
| 41 - 52 | 9 - 20 | 12 | ALT | Altitude |
| 53 | 21 | 1 | T | Time |
| 54 | 22 | 1 | F | CPR odd/even frame flag |
| 55 - 71 | 23 - 39 | 17 | LAT-CPR | Latitude in CPR format |
| 72 - 88 | 40 - 56 | 17 | LON-CPR | Longitude in CPR format |
Two types of the position messages (odd and even frames) are broadcast alternately. There are two different ways to decode an airborne position based on these messages:
- Unknown position, using both types of messages (aka globally unambiguous position)
- Knowing previous position, using only one message (aka locally unambiguous position)
Note: The definition of functions NL(lat), floor(x), and mod(x,y) are described above.
Globally unambiguous position (decoding with two messages)
odd or even message?
For each frame, bit 54 determines whether it is an odd or even frame:
0 -> Even frame 1 -> Odd frame
For example, the two following messages are received:
8D40621D58C382D690C8AC2863A7 8D40621D58C386435CC412692AD6 |----|--------|----------------|--------| | | ICAO24 | DATA | CRC | |----|--------|----------------|--------| | 8D | 40621D | 58C382D690C8AC | 2863A7 | | 8D | 40621D | 58C386435CC412 | 692AD6 | |----|--------|----------------|--------| The payload data in binary format: |============================================================================| | DATA | |============================================================================| | TC | ... | ALT | T | F | CPR-LAT | CPR-LON | |-------|-----|--------------|---|---|-------------------|-------------------| | 01011 | 000 | 110000111000 | 0 | 0 | 10110101101001000 | 01100100010101100 | | 01011 | 000 | 110000111000 | 0 | 1 | 10010000110101110 | 01100010000010010 | |============================================================================|
In both messages we can find DF=17 and TC=11, with the same ICAO24 address 40621D. So, those two frames are valid for decoding the positions of this aircraft. Assume the first message is the newest message received.
The CPR representation of coordinates
|---|-------------------|-------------------| | F | CPR Latitude | CPR Longitude | |---|-------------------|-------------------| | 0 | 10110101101001000 | 01100100010101100 | -> newest | 1 | 10010000110101110 | 01100010000010010 | |---|-------------------|-------------------|
In decimal:
|---|-------------------|-------------------| | 0 | 93000 | 51372 | | 1 | 74158 | 50194 | |---|-------------------|-------------------| CPR_LAT_EVEN: 93000 / 131072 -> 0.7095 CPR_LON_EVEN: 51372 / 131072 -> 0.3919 CPR_LAT_ODD: 74158 / 131072 -> 0.5658 CPR_LON_ODD: 50194 / 131072 -> 0.3829
Since CPR latitude and longitude are encoded in 17 bits, 13107 2^17 is the maximum value.
Lattitude
Lat_EVEN = 52.25720214843750 Lat_ODD = 52.26578017412606 Lat = Lat_EVEN = 52.25720
Longitude
In the example:
Lon: 3.91937
Here is a Python implementation: https://github.com/junzis/pyModeS/blob/faf4313/pyModeS/adsb.py#L166
Calculate altitude
The altitude of the aircraft is much easier to compute from the data frame. The bits in the altitude field (either odd or even frame) are as follows:
1100001 1 1000
^
Q-bit
This Q-bit (bit 48) indicates whether the altitude is encoded in multiples of 25 or 100 ft (0: 100 ft, 1: 25 ft).
For Q = 1, we can calculate the altitude as follows:
First, remove the Q-bit :
N = 1100001 1000 => 1560 (in decimal)
The final altitude value will be:
Alt=N⋅25−1000(ft.)
In this example, the altitude at which the aircraft is flying is:
1560 * 25 - 1000 = 38000 ft.
Note that the altitude has the accuracy of +/- 25 ft when the Q-bit is 1, and the value can represent altitudes from -1000 to +50175 ft.
The final position
Finally, we have all three components (latitude/longitude/altitude) of the aircraft position:
LAT: 52.25720 (degrees N) LON: 3.91937 (degrees E) ALT: 38000 ft
Locally unambiguous position (decoding with one message)
This method gives the possibility of decoding aircraft using only one message knowing a reference position. This method computes the latitude index (j) and the longitude index (m) based on such reference, and can be used with either type of the messages.
The reference position
The reference position should be close to the actual position (eg. position of aircraft previously decoded, or the location of ADS-B antenna), and must be within a 180 NM range.
calculate local latitude

calculate local longitude

example
For the same example message:
8D40621D58C382D690C8AC2863A7
Reference position: LAT: 52.258 LON: 3.918
The structure of the message is:
8D40621D58C382D690C8AC2863A7
|----|--------|----------------|--------| | | ICAO24 | DATA | CRC | |----|--------|----------------|--------| | 8D | 40621D | 58C382D690C8AC | 2863A7 |
Data in binary:
| DATA | |============================================================================| | TC | ... | ALT | T | F | CPR-LAT | CPR-LON | |-------|-----|--------------|---|---|-------------------|-------------------| | 01011 | 000 | 110000111000 | 0 | 0 | 10110101101001000 | 01100100010101100 |
CPR representation:
| F | CPR Latitude | CPR Longitude | |---|-------------------|-------------------| | 0 | 10110101101001000 | 01100100010101100 | |---|-------------------|-------------------| | | 93000 / 131072 | 51372 / 131072 | | | 0.7095 | 0.3919 | |---|-------------------|-------------------|
d_lat: 6 j: 8 lat: 52.25720 m: 0 d_lon: 10 lon: 3.91937
references
- ADSB
- https://en.wikipedia.org/wiki/Automatic_dependent_surveillance_%E2%80%93_broadcast
- https://mode-s.org/decode/adsb/airborne-position.html
- https://www.eurocontrol.int/sites/default/files/content/documents/nm/asterix/archives/asterix-cat021-asterix-ads-b-messages-part-12-v1.6-102010.pdf.pdf
- https://mode-s.org/decode/index.html
- https://www.researchgate.net/publication/318940434_Optimum_Receiver_for_Decoding_Automatic_Dependent_Surveillance_Broadcast_ADS-B_Signals
- https://www.sciencedirect.com/science/article/pii/S2405959517302783
- SBS
- databases
- flightaware https://github.com/flightaware
picap
I wrote a small utility called piCap to capture piaware data running on any raspberry pi.
- perl see http://irgis:8080/tree/pi%2Fpi-perl-cap.git/master/pi-perl-cap
- java http://irgis:8080/tree/pi%2Fpi-cap.git/master/pi-cap%2Fsrc%2Fpi
The latest variant, under utils, also formats and send data as ir-xml to activemq topics TRACKS and ATC.
- activemq utils http://irgis:8080/blob/IR-GIS%2Fir.git/heat-map/utils%2Fsrc%2Fmain%2Fjava%2Firgis%2Futils%2Fmain%2FPiCap.java


