Decode AIS messages with Python

I recently played around with MarineTraffic and got interested in the tech behind it. Along with Vesseltracker, MarineTraffic is one of the leading and best-known providers of ship data based on AIS. It uses the Automatic Identification System (AIS) as a radio system for the exchange of navigation and other ship data.

AIS

AIS refers to a radio system that improves the safety and guidance of vessel traffic by exchanging navigational and other vessel data.

AIS serves the following purposes in accordance with the Performance Standard of the International Maritime Organization (IMO):

  • collision prevention between ships
  • as a means for coastal states to obtain information about ships and their cargoes
  • monitor illegal fishing
  • as an aid for controlling vessel traffic

AIS is based on VHF and is therefore more reliable than a typical radar. In particular, AIS works independently of visual sight, so that ships located behind a cape or behind a river bend can also be detected, provided that the signals transmitted in the VHF frequency range penetrate. Data is periodically transmitted, using TDMA (Time Division Multiple Access) technology similar to the way cellphones do to avoid mutual interference.

Typically, not only the position, course and speed of the surrounding vessels are transmitted, but also vessel data (vessel name, MMSI number, radio call sign, etc.).

Message format

Every AIS message contains at least two things:

  1. A MMSI number (which is a 9-digit number that identifies the transmitter)
  2. A message type (number between 1 to 27) indicating which kind of message is being transmitted

Each message type (1-27) serves a different purpose and holds different data. Some are more common than others. Thus, messages of type 1,2,3 are seen relatively often because they contain navigation data (longitude, latitude, heading, etc.), while messages of type 25, 26 are not so common. They are used to transmit binary data between different devices.

AIS equipment exchanges information with other equipment using NMEA 0183sentences as ASCII data packets. AIS packets have the introducer “!AIVDM” or “!AIVDO”; AIVDM packets are reports from other ships and AIVDO packets are reports from your own ship.

Typical NMEA 0183 standard AIS message looks like this:

!AIVDM,1,1,,A,14eG;o@034o8sd<L9i:a;WF>062D,0*7D

This message can be split by each , into seven parts:

  1. !AIVDM: NMEA message type
  2. 1: Total number of sentences (1 to 9)
  3. 1: Sentence Number (like the sequence number for ICMP packets)
  4. : Sequential message ID (Multiline messages)
  5. A: Channel - either A or B
  6. 14eG;o@034o8sd<L9i:a;WF>062D: AIS-ASCII6 encoded payload
  7. 0*7D: EOD (End of data) and checksum

Messing around with AIS

Because I live near the sea, I decided to build my own AIS receiver. It transformed an old Raspberry PI into a VHF receiver and started listening for messages via UDP. This sound easier than it actually was. I spent quite some time, trying to get everything to work. Ultimately I was able to open a UDP socket and could receive data.

Once I wanted to actually decode these messages using Python I noticed that no publicly available AIS decoder was present on Pypi. So I decided to take the chance and built my very own.

Introducing Pyais

Pyais is a decoder for AIS messages written in pure Python. Currently this module is able to decode most message types. There are only a few exceptions. These are messages that only occur in very rare cases and that you will probably never observe. The module was able to completely decode a 4 hour stream with real-time data from San Francisco Bay Area without any errors or problems.

In the following section I will describe how you can use this module in order to decode AIS messages. If you want to follow along, make sure that you:

  • have Python3 installed
  • download the sample data from MarineTraffic

Installing

You can install pyais via pip with:

$ pip install pyais

If you have downloaded the data from MarineTraffic you can unzip it using unzip (if you are using Ubuntu).

Using

The ZIP archive contains a single file with nearly a thousand AIS messages. In order to decode the contained messages use the following code:

from pyais.stream import FileReaderStream

filename = "nmea_data_sample.ais"

for msg in FileReaderStream(filename):
    decoded_message = msg.decode()
    ais_content = decoded_message.content
    # Do something with the ais message
    print(ais_content)

This will print the decoded message to your terminal. The AIS content is a dict with different entries depending on the type of message:

{'type': 1, 'repeat': 0, 'mmsi': '355104000', 'status': <NavigationStatus.UnderWayUsingEngine: 0>, 'turn': 0, 'speed': 13.5, 'accuracy': False, 'lon': 20.387166666666666, 'lat': 37.50501666666667, 'course': 148.0, 'heading': 149, 'second': 15, 'maneuver': <ManeuverIndicator.NotAvailable: 0>, 'raim': False, 'radio': 33450}

This way you could print all ship names:

    ...
    # Get the Shipname
    if 'shipname' in ais_content:
        print(ais_content['shipname'])

or you could get a list of all anchored ships:

  if ais_content.get('status', None) == NavigationStatus.AtAnchor:
        print(ais_content['mmsi'])

Another common use case is the reception of messages via UDP. This lib comes with an UDPStream class that enables just that. This stream class also handles out-of-order delivery of messages, which can occur when using UDP.

from pyais.stream import UDPStream

host = "127.0.0.1"
port = 55555

for msg in UDPStream(host, port):
    msg.decode()
    # do something with it