Skip to content

Add a tool to find AIS test cases #261

@schwehr

Description

@schwehr

I have this old python 2 scratch code. It might be good to clean this up and add it as a contrib binary.

#!/usr/bin/env python

"""Find good test messages in AIS hub data.

Going to create some classes that tracking finding instances of each type of message that we would
like to have a test message for.

See also:
  http://git.savannah.gnu.org/cgit/gpsd.git/tree/test/sample.aivdm

"""

import ais
import datetime
from pprint import pprint
import re
import sys
import Queue

# how do I define what a good set it?
# Want over range in all fields if possible

MMSI_LIST =  (0, 1, 10, 99, 111, 1111, 10101, 111111, 123456, 1193046, 10101010,
              10000000, 100000000, 111111111, 123456789, 999999999)

def AlmostEqual(a, b, percent = 0.1):
    if abs(a) > abs(b):
        delta = abs(a) * (percent * 1e-2)
        if b > a + delta or b < a - delta:
            return False
    else:
        delta = abs(b) * (percent * 1e-2)
        if a > b + delta or a < b - delta:
            return False
    return True

def AisSplit(line):
  fields = line.split(',')
  if len(fields) == 9:
    hdr, total, line_num, seq, chan, body, checksum, station, timestamp = fields
  elif len(fields) > 9:
    hdr, total, line_num, seq, chan, body, checksum = fields[:7]
    timestamp = fields[-1]
    station = 'runknown'
  else:
    print 'Wrong number of fields', len(fields)
    print '  line str:', line
    return None

  if seq: seq = int(seq)
  return {
      'hdr': hdr,
      'total': int(total),
      'line_num': int(line_num),
      'seq': int(seq) if seq else seq,
      'chan': chan,
      'body': body,
      'checksum':  checksum.split('*')[1],
      'pad': int(checksum.split('*')[0]),
      'station': station,
      'timestamp': int(timestamp),
      'raw': line.strip(),
      }

class Normalize(Queue.Queue):
  """Single station AIS normalize"""

  def __init__(self):
    self.channels = {}
    for chan in range(10):
      self.channels[chan] = []
    Queue.Queue.__init__(self)

  def put(self, line_str, line_num=None):
    if not isinstance(line_str, str): raise TypeError('Expect single lines')

    line = AisSplit(line_str)
    seq = line['seq']

    if seq == '':
      line['raw'] = [line['raw']]  # always a list
      Queue.Queue.put(self, line)
      return

    if line['line_num'] == 1:
      self.channels[seq] = [line]
      return

    if line['line_num'] != line['total']:
      self.channels[seq].append(line)
      return

    # Complete multi line
    line['raw'] = [l['raw'] for l in self.channels[seq]] + [line['raw']]
    if len(line['raw']) != line['total']:
      self.channels[seq] = []
      return

    line['body'] = ''.join([l['body'] for l in self.channels[seq]]) + line['body']
    line['total'] = 1
    line['line_num'] = 1
    self.channels[seq] = []
    Queue.Queue.put(self, line)


def Sog(msg, s):
  sog = msg['sog']
  if sog == 0:   s.add('sog_0')
  elif sog > 5 and sog < 10:   s.add('sog_low')
  elif sog > 30 and sog < 50:   s.add('sog_high')
  elif sog > 50 and sog < 100:   s.add('sog_crazy')
  elif sog > 102.19 and sog < 102.21:   s.add('sog_or_higher')
  elif sog > 102.29 and sog < 102.31:   s.add('sog_or_unknown')

def LonLat(msg, s, prefix=''):
  x = msg['x'];  y = msg['y']
  # These are bad
  if x > 180 and y < 90: s.add(prefix + 'x_undef')
  if x < 180 and y > 90: s.add(prefix + 'y_undef')
  # This is okay
  if x > 180 and y > 90: s.add(prefix + 'xy_undef')

  # All bad
  if x < -180 and y > -90: s.add(prefix + 'x_neg_undef')
  if x > -180 and y < -90:   s.add(prefix + 'y_neg_undef')
  if x < -180 and y < -90:   s.add(prefix + 'xy_neg_undef')

  if x == 0 and y == 0:  s.add(prefix + 'x0_y0')
  if x > 2 and x < 15:
    if y > 2 and y < 15:  s.add(prefix + 'x_low_y_low')
    if y > 60 and y < 90:  s.add(prefix + 'x_low_y_high')
    if y < -2 and y > -15:  s.add(prefix + 'x_low_y_neglow')
    if y < -50 and y > -90:  s.add(prefix + 'x_low_y_neghigh')

  if x < -2 and x > -15:
    if y >   2 and y <  15:  s.add(prefix + 'x_neglow_y_low')
    if y >  60 and y <  90:  s.add(prefix + 'x_neglow_y_high')
    if y <  -2 and y > -15:  s.add(prefix + 'x_neglow_y_neglow')
    if y < -50 and y > -90:  s.add(prefix + 'x_neglow_y_neghigh')

  if x == -180:  s.add(prefix + 'x_neg180') # Unlikely
  if x == 180:  s.add(prefix + 'x180') # Unlikely
  if x <= -179 and x > -180:
      if y > 2 and y < 15:  s.add(prefix + 'x_neg179_y_low')
      if y > 60 and y < 90:  s.add(prefix + 'x_neg179_y_high')
      if y < -2 and y > -15:  s.add(prefix + 'x_neg179_y_neglow')
      if y < -50 and y > -90:  s.add(prefix + 'x_neg179_y_neghigh')
  if x >= 179 and x < 180:
      if y > 2 and y < 15:  s.add(prefix + 'x_179_y_low')
      if y > 60 and y < 90:  s.add(prefix + 'x_179_y_high')
      if y < -2 and y > -15:  s.add(prefix + 'x_179_y_neglow')
      if y < -50 and y > -90:  s.add(prefix + 'x_179_y_neghigh')

  if y == -90: s.add(prefix + 'y_neg90')  # Unlikely
  if y ==  90: s.add(prefix + 'y90')  # Unlikely


def Cog(msg, s):
  cog = msg['cog'] # steps of 0.1
  if cog == 0:  s.add('cog0')
  if cog > 9 and cog < 10:  s.add('cog9_9') # has a decimal
  if cog > 89.9 and cog <= 90:  s.add('cog90')
  if cog > 179.8 and cog <= 180:  s.add('cog180')
  if cog > 269.8 and cog <= 270:  s.add('cog270')
  if cog == 360:  s.add('cog360')
  if cog > 365:  s.add('cogTooHigh')


def TrueHeading(msg, s):
  th = msg['true_heading']
  if th in (0, 90, 180, 270, 359): s.add('th%d' % th)
  if th == 360:  s.add('th_bad_360')  # NOT valid
  if th > 400 and th < 500:  s.add('th_wonky')
  if th == 511:  s.add('th_na')

class Msg1(object):

    def __init__(self):
        self.states = set()
        self.key_set = set()

    def CheckKeep(self, msg):
        if 1:
            for key in msg:
                self.key_set.add(key)

        s = self.states
        s_len_begin = len(s)

        if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
        s.add('repeat%d' % msg['repeat_indicator'])

        s.add('ns_%d' % msg['nav_status'])

        # +/- 708
        rot = msg['rot']
        if rot > 709:    s.add('rot_over')
        elif rot < -709:    s.add('rot_under')
        elif rot > 20 and rot < 100:   s.add('rot_pos')
        elif rot < -20 and rot > -100:   s.add('rot_neg')
        elif rot == 0:   s.add('rot_0')

        Sog(msg, s)
        s.add('pa_%d' % msg['position_accuracy'])
        LonLat(msg, s)

        Cog(msg, s)
        TrueHeading(msg,s)

        # 61 is hard to find
        ts = msg['timestamp']
        if ts in (0,30,59,60,61,62,63): s.add('ts%d' % ts)

        # 0,1,2.  3 is not valid
        s.add('sm%d' % msg['special_manoeuvre'])

        s.add('spare%d' % msg['spare'])
        s.add('raim%d' % msg['raim'])

        # SOTDMA

        if msg['id'] in (1,2):
          # 2 is hard to find
          s.add('ss%d' % msg['sync_state'] )

          s.add('sm%d' % msg['slot_timeout'])
          if 'received_stations' in msg:
                rs = msg['received_stations']
                if rs in (0,1,5): s.add('rs%d' % rs)
                if rs > 50 and rs < 100:  s.add('rs_med')
                if rs > 100 and rs < 500:  s.add('rs_high')
                if rs > 500 and rs < 1000:  s.add('rs_very_high')
                if rs > 1000 and rs < 15000:  s.add('rs_insane')
                if rs > 16000:   s.add('rs_top')
                if rs == 2**14-1:   s.add('rs_max')

          if 'slot_number' in msg:
                sn = msg['slot_number']
                if sn in (0,1,10,2248, 2249):
                    s.add('sn%d' % sn)
                if sn > 2250:  s.add('sn_too_high')

          if 'utc_hour' in msg:
                hr = msg['utc_hour']; mn = msg['utc_min']; u_spare = msg['utc_spare']
                if hr in (0, 12, 23, 24, 31):
                    s.add('hr%d' % hr)
                if hr > 24 and hr < 31:   s.add('hr_over')
                if mn in (0, 11, 59, 60, 127): s.add('mn%d' % mn)
                if mn > 60 and mn < 127: s.add('mn_over')
                s.add('utc_spare%d' % u_spare)

          if 'slot_offset' in msg:
                so = msg['slot_offset']
                if so in (0,1, 2248, 2249, 2250, 2**14-2,  2**14-1):
                    s.add('so%d' % so)
                if so > 2260 and so < 15000: s.add('so_too_high')

        # ITDMA
        if msg['id'] == 3:
          # 2 is hard to find
          s.add('itdma_ss%d' % msg['sync_state'])
          slot_inc = msg['slot_increment']
          if slot_inc in (0, 1, 2248, 2249, 2250, 8191): # last 2 not valid
            s.add('slot_inc%d' % slot_inc)
          s.add('nslots%d' % msg['slots_to_allocate'])
          s.add('keep%d' % msg['keep_flag'])

        return len(s) - s_len_begin > 0


class Msg4(object):
    def __init__(self):
        self.states = set()
        self.key_set = set()
        self.now = datetime.datetime.utcnow()

    def CheckKeep(self, msg):
        if 1:
            for key in msg:
                self.key_set.add(key)
        s = self.states
        s_len_begin = len(s)

        s.add('repeat%d' % msg['repeat_indicator'])
        s.add('year%d' % msg['year'])

        if msg['month'] in (0,1,12,13,15): s.add('mon%d' % msg['month'])
        if msg['day'] in (0,1,15,31): s.add('day%d' % msg['day'])
        if msg['hour'] in (0,12,23,24,25,31): s.add('hr%d' % msg['hour'])
        if msg['minute'] in (0,1,59,60,61,63): s.add('mn%d' % msg['minute'])
        if msg['second'] in (0,1,59,60,61,63): s.add('sec%d' % msg['second'])

        s.add('pa_%d' % msg['position_accuracy'])

        LonLat(msg, s)

        s.add('ft%d' % msg['fix_type'])

        # TODO: Transmission control for long-range broadcast message
        s.add('transmission_ctl_%d' % msg['transmission_ctl'])
        spare = msg['spare']
        if not spare: s.add('spare0')
        if spare > 0 and spare < 2**9-1: s.add('spare_invalid')
        if spare == 2**9-1: s.add('spare_max')

        s.add('raim_%d' % msg['raim'])

        Sotdma(msg, s)

        return len(s) - s_len_begin > 0


def TrimAisStr(a_str):
    at_pos = a_str.find('@')
    if -1 == at_pos: return a_str
    return a_str[:at_pos]


def Name(msg, s, length = 20):
  name_raw = msg['name']
  name = TrimAisStr(name_raw)
  if len(name) == length / 2:
      s.add('namelen%d'%len(name))
  if len(name) < 4 or len(name) > length - 4:
      s.add('namelen%d'%len(name))
  if name_raw.count('@') < 4 or name_raw.count('@') > length - 4:
      s.add('name@cnt%d' % name_raw.count('@'))
  if re.search(r'[^ @]+@+\W+', name_raw): s.add('name_space_after@')
  # #$&(+,-.0123456789:;<=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\ Careful with the last slash
  for c in  r'#$&(+,-.0189:;<=?@AB YZ\\':
    if c in name: s.add('name_%s' % c)


def Dim(msg, s):
  for c in 'abcd':
    dim = msg['dim_%s' % c]
    sizes = [0,1,30,63]
    if c in 'ab': sizes.append(511)
    if dim in sizes: s.add('dim_%s_%d' % (c, dim))


class Msg5(object):
    def __init__(self):
        self.states = set()
        self.key_set = set()
        self.now = datetime.datetime.utcnow()
        self.junk = set()

    def CheckKeep(self, msg):
        if 1:
            for key in msg:
                self.key_set.add(key)
        s = self.states
        s_len_begin = len(s)

        mmsi = msg['mmsi']
        if mmsi in MMSI_LIST: s.add('mmsi%d' % mmsi)
        s.add('repeat%d' % msg['repeat_indicator'])
        s.add('ver%d' % msg['ais_version'])

        if 1:
            imo = msg['imo_num']
            if imo in (0,1,int(1e9), 123456789, 999999998,999999999):
                s.add('imo%d' % imo)
            if imo > 200000000 and imo < 800000000:  s.add('imo_9dig')
            if imo > 20000000  and imo < 80000000:  s.add('imo_8dig')
            if imo > 2000000   and imo < 8000000:  s.add('imo_7dig')
            if imo > 200000    and imo < 800000:  s.add('imo_6dig')
            if imo > 20000     and imo < 80000:  s.add('imo_5dig')
            if imo > 2000      and imo < 8000:  s.add('imo_4dig')
            if imo > 200       and imo < 800:  s.add('imo_3dig')
            if imo > 20        and imo < 80:  s.add('imo_2dig')
            if imo > 2         and imo < 8:  s.add('imo_1dig')

        if 1:
            call_raw = msg['callsign']
            call = TrimAisStr(call_raw)
            s.add('calllen%d'%len(call))
            s.add('call@cnt%d' % call_raw.count('@'))
            if re.search(r'[^ @]+@+\W+', call_raw): s.add('call_space_after@')
            # #$&(+,-.0123456789:;<=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\ Careful with the last slash
            for c in  r'#$&(+,-.0189:;<=?@AB YZ\\':
                if c in call: s.add('imo_%s' % c)

        Name(msg, s)
        s.add('tc%d' % msg['type_and_cargo'])

        Dim(msg, s)
        s.add('ft%d' % msg['fix_type'])

        mon = msg['eta_month'];  day = msg['eta_day']
        hr = msg['eta_hour'];     mn = msg['eta_minute']
        if mon in (0,1,12,13,15):  s.add('mon%d' % mon)
        if day in (0,1,15,31): s.add('day%d' % day)
        if hr in (0, 12, 23, 24, 31):  s.add('hr%d' % hr)
        if hr > 24 and hr < 31: s.add('hr_over')
        if mn in (0, 11, 59, 60, 127): s.add('mn%d' % mn)
        if mn > 60 and mn < 127:   s.add('mn_over')

        dr = msg['draught']
        for val in (0.0, 0.1, 12.4, 25.5):
            if AlmostEqual(dr, val): s.add('dr%.1f' % val)

        dest_raw = msg['destination']
        dest = TrimAisStr(dest_raw)
        if len(dest) < 4 or len(dest) > 16:
            s.add('destlen%d'%len(dest))
        if dest_raw.count('@') < 4 or dest_raw.count('@') > 16:
            s.add('dest@cnt%d' % dest_raw.count('@'))
        if re.search(r'[^ @]+@+\W+', dest_raw): s.add('dest_space_after@')
        for c in  r'#$&(+,-.0189:;<=?@AB YZ\\':
            if c in dest: s.add('dest_%s' % c)

        s.add('dte%d' % msg['dte'])
        s.add('spare%d' % msg['spare'])

        return len(s) - s_len_begin > 0


def AisString(msg, s, str_name, length = 20, prefix=''):
  if prefix: prefix = prefix + '_'
  name_raw = msg[str_name]
  name = TrimAisStr(name_raw)
  if len(name) == length / 2:
      s.add(prefix+'namelen%d'%len(name))
  if len(name) < 4 or len(name) > length - 4:
      s.add(prefix+'namelen%d'%len(name))
  if name_raw.count('@') < 4 or name_raw.count('@') > length - 4:
      s.add(prefix+'name@cnt%d' % name_raw.count('@'))
  if re.search(r'[^ @]+@+\W+', name_raw): s.add(prefix+'name_space_after@')
  # #$&(+,-.0123456789:;<=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\ Careful with the last slash
  for c in  r'#$&(+,-.0189:;<=?@AB YZ\\':
    if c in name: s.add(prefix+'name_%s' % c)


class Msg6(object):
    def __init__(self):
        self.states = set()
        self.key_set = set()
        #self.now = datetime.datetime.utcnow()
        #self.junk = set()

        #self.dacs = {1: Msg6Dac1()}

    def CheckKeep(self, msg):
        if 1:
          for key in msg:
            self.key_set.add(key)
        s = self.states
        s_len_begin = len(s)

        #if  msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' %  msg['mmsi'])
        #s.add('repeat%d' % msg['repeat_indicator'])
        #s.add('dac%d_fi%d' % (msg['dac'], msg['fi']))
        #s.add('seq%d' % msg['seq'])
        #s.add('spare%d' % msg['spare'])
        #dac = msg['dac']
        #if dac not in self.dacs: return len(s) - s_len_begin > 0
        # retransmit

        dac = msg['dac']
        fi = msg['fi']
        if 1 == dac:
          if 0 == fi:
            s.add('m6_%d_%d_ack_required_%s' % (dac, fi, msg['ack_required']))
            if msg['msg_seq'] < 10 or msg['msg_seq'] > (2**11-10):
              s.add('m6_%d_%d_msg_seq_%s' % (dac, fi, msg['msg_seq']))
            AisString(msg, s, 'text', length=154, prefix='m6_%d_%d_text' % (dac, fi))
            s.add('m6_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))
          if 1 == fi:
            s.add('m6_%d_%d_ack_dac_%s' % (dac, fi, msg['ack_dac']))
            if msg['msg_seq'] < 10 or msg['msg_seq'] > (2**11-10):
              s.add('m6_%d_%d_msg_seq_%s' % (dac, fi, msg['msg_seq']))
            s.add('m6_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))
          if 2 == fi:
            s.add('m6_%d_%d_req_dac_%s' % (dac, fi, msg['req_dac']))
            s.add('m6_%d_%d_req_fi_%s' % (dac, fi, msg['req_fi']))
          if 3 == fi:
            s.add('m6_%d_%d_req_dac_%s' % (dac, fi, msg['req_dac']))
            s.add('m6_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))
          if 4 == fi and 'ack_dac' in msg:
            s.add('m6_%d_%d_ack_dac_%s' % (dac, fi, msg['ack_dac']))
            # TODO: check all the bits of the bloody array
            s.add('m6_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

          if 12 == fi and 'imo_cat' in msg:
            # TODO: no messages yet found that work
            AisString(msg, s, 'last_port', length=154, prefix='m6_%d_%d_last_port' % (dac, fi))
            s.add('m6_%d_%d_utc_month_dep_%s' % (dac, fi, msg['utc_month_dep']))
            s.add('m6_%d_%d_utc_day_dep_%s' % (dac, fi, msg['utc_day_dep']))
            s.add('m6_%d_%d_utc_hour_dep_%s' % (dac, fi, msg['utc_hour_dep']))
            s.add('m6_%d_%d_utc_min_dep_%s' % (dac, fi, msg['utc_min_dep']))
            AisString(msg, s, 'next_port', length=154, prefix='m6_%d_%d_next_port' % (dac, fi))
            s.add('m6_%d_%d_utc_month_next_%s' % (dac, fi, msg['utc_month_next']))
            s.add('m6_%d_%d_utc_day_next_%s' % (dac, fi, msg['utc_day_next']))
            s.add('m6_%d_%d_utc_hour_next_%s' % (dac, fi, msg['utc_hour_next']))
            s.add('m6_%d_%d_utc_min_next_%s' % (dac, fi, msg['utc_min_next']))
            s.add('m6_%d_%d_main_danger_%s' % (dac, fi, msg['main_danger']))
            s.add('m6_%d_%d_imo_cat_%s' % (dac, fi, msg['imo_cat']))
            s.add('m6_%d_%d_un_%s' % (dac, fi, msg['un']))
            s.add('m6_%d_%d_value_%s' % (dac, fi, msg['value']))
            s.add('m6_%d_%d_value_unit_%s' % (dac, fi, msg['value_unit']))
            s.add('m6_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

          if 14 == fi and 'x' in msg:
            LonLat(msg, s, prefix='m6_%d_%d_' % (dac, fi))
            s.add('m6_%d_%d_utc_hour_from_%s' % (dac, fi, msg['utc_hour_from']))
            s.add('m6_%d_%d_utc_min_from_%s' % (dac, fi, msg['utc_min_from']))
            s.add('m6_%d_%d_utc_hour_to_%s' % (dac, fi, msg['utc_hour_to']))
            s.add('m6_%d_%d_utc_min_to_%s' % (dac, fi, msg['utc_min_to']))
            s.add('m6_%d_%d_cur_dir_%s' % (dac, fi, msg['cur_dir']))
            s.add('m6_%d_%d_cur_speed_%s' % (dac, fi, msg['cur_speed']))


          if 18 == fi:
            # IMO Circ 289
            LonLat(msg, s, prefix='m6_%d_%d_' % (dac, fi))
            s.add('m6_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
            s.add('m6_%d_%d_utc_month_%s' % (dac, fi, msg['utc_month']))
            s.add('m6_%d_%d_utc_day_%s' % (dac, fi, msg['utc_day']))
            s.add('m6_%d_%d_utc_hour_%s' % (dac, fi, msg['utc_hour']))
            s.add('m6_%d_%d_utc_min_%s' % (dac, fi, msg['utc_min']))
            AisString(msg, s, 'port_berth', length=20, prefix='m6_%d_%d_port' % (dac, fi))
            AisString(msg, s, 'dest', length=5, prefix='m6_%d_%d_dest' % (dac, fi))

            # TODO focus spare2
            s.add('m6_%d_%d_spare2_0_%s' % (dac, fi, msg['spare2_0']))
            s.add('m6_%d_%d_spare2_1_%s' % (dac, fi, msg['spare2_1']))

          if 20 == fi and 'link_id' in msg:
            s.add('m6_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
            s.add('m6_%d_%d_length_%s' % (dac, fi, msg['length']))
            s.add('m6_%d_%d_depth_%s' % (dac, fi, msg['depth']))
            s.add('m6_%d_%d_position_%s' % (dac, fi, msg['position']))
            s.add('m6_%d_%d_utc_month_%s' % (dac, fi, msg['utc_month']))
            s.add('m6_%d_%d_utc_day_%s' % (dac, fi, msg['utc_day']))
            s.add('m6_%d_%d_utc_hour_%s' % (dac, fi, msg['utc_hour']))
            s.add('m6_%d_%d_utc_min_%s' % (dac, fi, msg['utc_min']))
            for i in range(26):
              s.add('m6_%d_%d_services%d_%s' % (dac, fi, i, msg['services'][i]))
            AisString(msg, s, 'name', length=20, prefix='m6_%d_%d_name' % (dac, fi))

          if 20 == fi and 'amount' in msg:
            s.add('m6_%d_%d_amount_unit_%s' % (dac, fi, msg['amount_unit']))
            s.add('m6_%d_%d_amount_%s' % (dac, fi, msg['amount']))
            for cargo in msg['cargos']:
              if 'imdg' in cargo: s.add('m6_%d_%d_imdg_%s' % (dac, fi, msg['imdg']))
              if 'spare' in cargo: s.add('m6_%d_%d_spare_%s' % (dac, fi, msg['spare']))
              if 'un' in cargo: s.add('m6_%d_%d_un_%s' % (dac, fi, msg['un']))
              if 'bc' in cargo: s.add('m6_%d_%d_bc_%s' % (dac, fi, msg['bc']))
              if 'marpol_oil' in cargo: s.add('m6_%d_%d_marpol_oil_%s' % (dac, fi, msg['marpol_oil']))
              if 'marpol_cat' in cargo: s.add('m6_%d_%d_marpol_cat_%s' % (dac, fi, msg['marpol_cat']))

          # TODO: 28 Route
          # TODO: 30 Addressed Text
          if 32 == fi and 'utc_month' in msg:
            s.add('m6_%d_%d_utc_month_%s' % (dac, fi, msg['utc_month']))
            s.add('m6_%d_%d_utc_day_%s' % (dac, fi, msg['utc_day']))
            for win_num, window in enumerate(msg['windows']):
              LonLat(msg, s, prefix='m6_%d_%d_window%d' % (dac, fi, win_num))
              s.add('m6_%d_%d_from_utc_hour_%s' % (dac, fi, msg['from_utc_hour']))
              s.add('m6_%d_%d_from_utc_min_%s' % (dac, fi, msg['from_utc_min']))
              s.add('m6_%d_%d_to_utc_hour_%s' % (dac, fi, msg['to_utc_hour']))
              s.add('m6_%d_%d_to_utc_min_%s' % (dac, fi, msg['to_utc_min']))
              s.add('m6_%d_%d_cur_dir_%s' % (dac, fi, msg['cur_dir']))
              s.add('m6_%d_%d_cur_speed_%s' % (dac, fi, msg['cur_speed']))

          if 40 == fi:
            s.add('m6_%d_%d_persons_%s' % (dac, fi, msg['persons']))
            # TODO: check all the bits of the bloody array
            s.add('m6_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))


#            s.add('m6_%d_%d__%s' % (dac, fi, msg['']))

        return len(s) - s_len_begin > 0


class Msg7_13(object):
    def __init__(self):
        self.states = set()
        self.key_set = set()
        self.now = datetime.datetime.utcnow()
        self.junk = set()

    def CheckKeep(self, msg):
        if 1:
            for key in msg:
                self.key_set.add(key)
        s = self.states
        s_len_begin = len(s)

        if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
        s.add('repeat%d' % msg['repeat_indicator'])
        s.add('ack_len%d' % len(msg['acks']))
        for i in range(len(msg['acks'])):
          s.add('ack_seq_%d_%d' % (i, msg['acks'][i][1]))

        return len(s) - s_len_begin > 0


######################################################################
# 8... this is complicated

class Msg8(object):
    def __init__(self):
        self.states = set()
        self.key_set = set()
        self.now = datetime.datetime.utcnow()
        self.junk = set()

    def CheckKeep(self, msg):
        if 1:
            for key in msg:
                self.key_set.add(key)
        s = self.states
        s_len_begin = len(s)

        if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
        s.add('repeat%d' % msg['repeat_indicator'])
        s.add('dac%d_fi%d' % (msg['dac'], msg['fi']))

        dac = msg['dac']
        fi = msg['fi']
        if 1 == dac:
          if 0 == fi and 'text' in msg:

            s.add('m8_%d_%d_ack_required_%s' % (dac, fi, msg['ack_required']))
            if msg['msg_seq'] < 10 or msg['msg_seq'] > (2**11-10):
              s.add('m8_%d_%d_msg_seq_%s' % (dac, fi, msg['msg_seq']))
            AisString(msg, s, 'text', length=154, prefix='m8_%d_%d_text' % (dac, fi))  # TODO: length bits or characters?
            s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))
          if 11 == fi and 'x' in msg:
            LonLat(msg, s, prefix='m8_%d_%d_' % (dac, fi))
            # TODO: limit most of these
            s.add('m8_%d_%d_wind_ave_%s' % (dac, fi, msg['wind_ave']))
            s.add('m8_%d_%d_wind_gust_%s' % (dac, fi, msg['wind_gust']))
            s.add('m8_%d_%d_wind_dir_%s' % (dac, fi, msg['wind_dir']))
            s.add('m8_%d_%d_wind_gust_dir_%s' % (dac, fi, msg['wind_gust_dir']))
            s.add('m8_%d_%d_air_temp_%s' % (dac, fi, msg['air_temp']))
            s.add('m8_%d_%d_rel_humid_%s' % (dac, fi, msg['rel_humid']))
            s.add('m8_%d_%d_dew_point_%s' % (dac, fi, msg['dew_point']))
            s.add('m8_%d_%d_air_pres_%s' % (dac, fi, msg['air_pres']))
            s.add('m8_%d_%d_air_pres_trend_%s' % (dac, fi, msg['air_pres_trend']))
            s.add('m8_%d_%d_horz_vis_%s' % (dac, fi, msg['horz_vis']))
            s.add('m8_%d_%d_water_level_%s' % (dac, fi, msg['water_level']))
            s.add('m8_%d_%d_water_level_trend_%s' % (dac, fi, msg['water_level_trend']))
            s.add('m8_%d_%d_surf_cur_speed_%s' % (dac, fi, msg['surf_cur_speed']))
            s.add('m8_%d_%d_surf_cur_dir_%s' % (dac, fi, msg['surf_cur_dir']))
            s.add('m8_%d_%d_cur_speed_2_%s' % (dac, fi, msg['cur_speed_2']))
            s.add('m8_%d_%d_cur_dir_2_%s' % (dac, fi, msg['cur_dir_2']))
            s.add('m8_%d_%d_cur_depth_2_%s' % (dac, fi, msg['cur_depth_2']))
            s.add('m8_%d_%d_cur_speed_3_%s' % (dac, fi, msg['cur_speed_3']))
            s.add('m8_%d_%d_cur_dir_3_%s' % (dac, fi, msg['cur_dir_3']))
            s.add('m8_%d_%d_cur_depth_3_%s' % (dac, fi, msg['cur_depth_3']))
            s.add('m8_%d_%d_wave_height_%s' % (dac, fi, msg['wave_height']))
            s.add('m8_%d_%d_wave_period_%s' % (dac, fi, msg['wave_period']))
            s.add('m8_%d_%d_wave_dir_%s' % (dac, fi, msg['wave_dir']))
            s.add('m8_%d_%d_swell_height_%s' % (dac, fi, msg['swell_height']))
            s.add('m8_%d_%d_swell_period_%s' % (dac, fi, msg['swell_period']))
            s.add('m8_%d_%d_swell_dir_%s' % (dac, fi, msg['swell_dir']))
            s.add('m8_%d_%d_sea_state_%s' % (dac, fi, msg['sea_state']))
            s.add('m8_%d_%d_water_temp_%s' % (dac, fi, msg['water_temp']))
            s.add('m8_%d_%d_precip_type_%s' % (dac, fi, msg['precip_type']))
            s.add('m8_%d_%d_ice_%s' % (dac, fi, msg['ice']))
            s.add('m8_%d_%d_ext_water_level_%s' % (dac, fi, msg['ext_water_level']))
            s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

          if 13 == fi and 'spare2' in msg:
            # TODO: cleanup many of these have wide ranges
            s.add('m8_%d_%d_reason_%s' % (dac, fi, msg['reason']))
            s.add('m8_%d_%d_location_from_%s' % (dac, fi, msg['location_from']))
            s.add('m8_%d_%d_location_to_%s' % (dac, fi, msg['location_to']))
            s.add('m8_%d_%d_radius_%s' % (dac, fi, msg['radius']))
            s.add('m8_%d_%d_units_%s' % (dac, fi, msg['units']))
            s.add('m8_%d_%d_day_from_%s' % (dac, fi, msg['day_from']))
            s.add('m8_%d_%d_month_from_%s' % (dac, fi, msg['month_from']))
            s.add('m8_%d_%d_hour_from_%s' % (dac, fi, msg['hour_from']))
            s.add('m8_%d_%d_minute_from_%s' % (dac, fi, msg['minute_from']))
            s.add('m8_%d_%d_day_to_%s' % (dac, fi, msg['day_to']))
            s.add('m8_%d_%d_month_to_%s' % (dac, fi, msg['month_to']))
            s.add('m8_%d_%d_hour_to_%s' % (dac, fi, msg['hour_to']))
            s.add('m8_%d_%d_minute_to_%s' % (dac, fi, msg['minute_to']))
            s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

          if 15 == fi and 'spare2' in msg:
            # TODO: untested
            s.add('m8_%d_%d_air_draught_%s' % (dac, fi, msg['air_draught']))
            s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

          if 16 == fi and 'spare2' in msg:
            s.add('m8_%d_%d_persons_%s' % (dac, fi, msg['persons']))
            s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

          if 17 == fi and 'targets' in msg:
            # TODO: untested
            for target_num, target in enumerate(msg['targets']):
              LonLat(msg, s, prefix='m8_%d_%d_%d' % (dac, fi, target_num))
              s.add('m8_%d_%d_type_%s' % (dac, fi, msg['type']))
              s.add('m8_%d_%d_id_%s' % (dac, fi, msg['id']))
              s.add('m8_%d_%d_spare_%s' % (dac, fi, msg['spare']))
              s.add('m8_%d_%d_cog_%s' % (dac, fi, msg['cog']))
              s.add('m8_%d_%d_timestamp_%s' % (dac, fi, msg['timestamp']))
              s.add('m8_%d_%d_sog_%s' % (dac, fi, msg['sog']))

          if 19 == fi and 'link_id' in msg:
            LonLat(msg, s, prefix='m8_%d_%d' % (dac, fi))
            s.add('m8_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
            s.add('m8_%d_%d_name_%s' % (dac, fi, msg['name']))
            s.add('m8_%d_%d_status_%s' % (dac, fi, msg['status']))
            s.add('m8_%d_%d_signal_%s' % (dac, fi, msg['signal']))
            s.add('m8_%d_%d_utc_hour_next_%s' % (dac, fi, msg['utc_hour_next']))
            s.add('m8_%d_%d_utc_min_next_%s' % (dac, fi, msg['utc_min_next']))
            s.add('m8_%d_%d_next_signal_%s' % (dac, fi, msg['next_signal']))
            s.add('m8_%d_%d_spare2_0_%s' % (dac, fi, msg['spare2_0']))
            s.add('m8_%d_%d_spare2_1_%s' % (dac, fi, msg['spare2_1']))
            s.add('m8_%d_%d_spare2_2_%s' % (dac, fi, msg['spare2_2']))
            s.add('m8_%d_%d_spare2_3_%s' % (dac, fi, msg['spare2_3']))

          if 21 == fi and 'x' in msg:
            LonLat(msg, s, prefix='m8_%d_%d' % (dac, fi))
            # TODO: remove duplicates and limit what we are saving for each.
            if 'utc_month' in msg: s.add('m8_%d_%d_utc_month_%s' % (dac, fi, msg['utc_month']))
            if 'utc_day' in msg: s.add('m8_%d_%d_utc_day_%s' % (dac, fi, msg['utc_day']))
            if 'utc_hour' in msg: s.add('m8_%d_%d_utc_hour_%s' % (dac, fi, msg['utc_hour']))
            if 'utc_min' in msg: s.add('m8_%d_%d_utc_min_%s' % (dac, fi, msg['utc_min']))

            if 'location' in msg: s.add('m8_%d_%d_location_%s' % (dac, fi, msg['location']))
            if 'wx' in msg: s.add('m8_%d_%d_wx_%s' % (dac, fi, msg['wx']))
            if 'horz_viz' in msg: s.add('m8_%d_%d_horz_viz_%s' % (dac, fi, msg['horz_viz']))
            if 'humidity' in msg: s.add('m8_%d_%d_humidity_%s' % (dac, fi, msg['humidity']))
            if 'wind_speed' in msg: s.add('m8_%d_%d_wind_speed_%s' % (dac, fi, msg['wind_speed']))
            if 'wind_dir' in msg: s.add('m8_%d_%d_wind_dir_%s' % (dac, fi, msg['wind_dir']))
            if 'pressure' in msg: s.add('m8_%d_%d_pressure_%s' % (dac, fi, msg['pressure']))
            if 'pressure_tendency' in msg: s.add('m8_%d_%d_pressure_tendency_%s' % (dac, fi, msg['pressure_tendency']))
            if 'air_temp' in msg: s.add('m8_%d_%d_air_temp_%s' % (dac, fi, msg['air_temp']))
            if 'water_temp' in msg: s.add('m8_%d_%d_water_temp_%s' % (dac, fi, msg['water_temp']))
            if 'wave_period' in msg: s.add('m8_%d_%d_wave_period_%s' % (dac, fi, msg['wave_period']))
            if 'wave_height' in msg: s.add('m8_%d_%d_wave_height_%s' % (dac, fi, msg['wave_height']))
            if 'wave_dir' in msg: s.add('m8_%d_%d_wave_dir_%s' % (dac, fi, msg['wave_dir']))
            if 'swell_height' in msg: s.add('m8_%d_%d_swell_height_%s' % (dac, fi, msg['swell_height']))
            if 'swell_dir' in msg: s.add('m8_%d_%d_swell_dir_%s' % (dac, fi, msg['swell_dir']))
            if 'swell_period' in msg: s.add('m8_%d_%d_swell_period_%s' % (dac, fi, msg['swell_period']))
            if 'spare2' in msg: s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))

            if 'cog' in msg: s.add('m8_%d_%d_cog_%s' % (dac, fi, msg['cog']))
            if 'sog' in msg: s.add('m8_%d_%d_sog_%s' % (dac, fi, msg['sog']))
            if 'heading' in msg: s.add('m8_%d_%d_heading_%s' % (dac, fi, msg['heading']))
            if 'pressure", msg.pressure);' in msg: s.add('m8_%d_%d_pressure", msg.pressure);_%s' % (dac, fi, msg['pressure", msg.pressure);']))
            if 'rel_pressure' in msg: s.add('m8_%d_%d_rel_pressure_%s' % (dac, fi, msg['rel_pressure']))
            if 'pressure_tendency' in msg: s.add('m8_%d_%d_pressure_tendency_%s' % (dac, fi, msg['pressure_tendency']))
            if 'wind_dir' in msg: s.add('m8_%d_%d_wind_dir_%s' % (dac, fi, msg['wind_dir']))
            if 'wind_speed_ms' in msg: s.add('m8_%d_%d_wind_speed_ms_%s' % (dac, fi, msg['wind_speed_ms']))
            if 'wind_dir_rel' in msg: s.add('m8_%d_%d_wind_dir_rel_%s' % (dac, fi, msg['wind_dir_rel']))
            if 'wind_speed_rel' in msg: s.add('m8_%d_%d_wind_speed_rel_%s' % (dac, fi, msg['wind_speed_rel']))
            if 'wind_gust_speed' in msg: s.add('m8_%d_%d_wind_gust_speed_%s' % (dac, fi, msg['wind_gust_speed']))
            if 'wind_gust_dir' in msg: s.add('m8_%d_%d_wind_gust_dir_%s' % (dac, fi, msg['wind_gust_dir']))
            if 'air_temp_raw' in msg: s.add('m8_%d_%d_air_temp_raw_%s' % (dac, fi, msg['air_temp_raw']))
            if 'humidity' in msg: s.add('m8_%d_%d_humidity_%s' % (dac, fi, msg['humidity']))
            if 'water_temp_raw' in msg: s.add('m8_%d_%d_water_temp_raw_%s' % (dac, fi, msg['water_temp_raw']))
            if 'horz_viz' in msg: s.add('m8_%d_%d_horz_viz_%s' % (dac, fi, msg['horz_viz']))
            if 'wx' in msg: s.add('m8_%d_%d_wx_%s' % (dac, fi, msg['wx']))
            if 'wx_next1' in msg: s.add('m8_%d_%d_wx_next1_%s' % (dac, fi, msg['wx_next1']))
            if 'wx_next2' in msg: s.add('m8_%d_%d_wx_next2_%s' % (dac, fi, msg['wx_next2']))
            if 'cloud_total' in msg: s.add('m8_%d_%d_cloud_total_%s' % (dac, fi, msg['cloud_total']))
            if 'cloud_low' in msg: s.add('m8_%d_%d_cloud_low_%s' % (dac, fi, msg['cloud_low']))
            if 'cloud_low_type' in msg: s.add('m8_%d_%d_cloud_low_type_%s' % (dac, fi, msg['cloud_low_type']))
            if 'cloud_middle_type' in msg: s.add('m8_%d_%d_cloud_middle_type_%s' % (dac, fi, msg['cloud_middle_type']))
            if 'cloud_high_type' in msg: s.add('m8_%d_%d_cloud_high_type_%s' % (dac, fi, msg['cloud_high_type']))
            if 'alt_lowest_cloud_base' in msg: s.add('m8_%d_%d_alt_lowest_cloud_base_%s' % (dac, fi, msg['alt_lowest_cloud_base']))
            if 'wave_period' in msg: s.add('m8_%d_%d_wave_period_%s' % (dac, fi, msg['wave_period']))
            if 'wave_height' in msg: s.add('m8_%d_%d_wave_height_%s' % (dac, fi, msg['wave_height']))
            if 'swell_dir' in msg: s.add('m8_%d_%d_swell_dir_%s' % (dac, fi, msg['swell_dir']))
            if 'swell_period' in msg: s.add('m8_%d_%d_swell_period_%s' % (dac, fi, msg['swell_period']))
            if 'swell_height' in msg: s.add('m8_%d_%d_swell_height_%s' % (dac, fi, msg['swell_height']))
            if 'swell_dir_2' in msg: s.add('m8_%d_%d_swell_dir_2_%s' % (dac, fi, msg['swell_dir_2']))
            if 'swell_period_2' in msg: s.add('m8_%d_%d_swell_period_2_%s' % (dac, fi, msg['swell_period_2']))
            if 'swell_height_2' in msg: s.add('m8_%d_%d_swell_height_2_%s' % (dac, fi, msg['swell_height_2']))
            if 'ice_thickness' in msg: s.add('m8_%d_%d_ice_thickness_%s' % (dac, fi, msg['ice_thickness']))
            if 'ice_accretion' in msg: s.add('m8_%d_%d_ice_accretion_%s' % (dac, fi, msg['ice_accretion']))
            if 'ice_accretion_cause' in msg: s.add('m8_%d_%d_ice_accretion_cause_%s' % (dac, fi, msg['ice_accretion_cause']))
            if 'sea_ice_concentration' in msg: s.add('m8_%d_%d_sea_ice_concentration_%s' % (dac, fi, msg['sea_ice_concentration']))
            if 'amt_type_ice' in msg: s.add('m8_%d_%d_amt_type_ice_%s' % (dac, fi, msg['amt_type_ice']))
            if 'ice_situation' in msg: s.add('m8_%d_%d_ice_situation_%s' % (dac, fi, msg['ice_situation']))
            if 'ice_devel' in msg: s.add('m8_%d_%d_ice_devel_%s' % (dac, fi, msg['ice_devel']))
            if 'bearing_ice_edge' in msg: s.add('m8_%d_%d_bearing_ice_edge_%s' % (dac, fi, msg['bearing_ice_edge']))

          if 22 == fi and 'link_id' in msg:
            # TODO: not yet tested
            s.add('m8_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
            s.add('m8_%d_%d_notice_type_%s' % (dac, fi, msg['notice_type']))
            s.add('m8_%d_%d_month_%s' % (dac, fi, msg['month']))
            s.add('m8_%d_%d_day_%s' % (dac, fi, msg['day']))
            s.add('m8_%d_%d_hour_%s' % (dac, fi, msg['hour']))
            s.add('m8_%d_%d_minute_%s' % (dac, fi, msg['minute']))

          if 24 == fi and 'link_id' in msg:
            # TODO not yet tested - no messages found

            for item_num, item in msg['solas']:
              s.add('m8_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
              s.add('m8_%d_%d_air_draught_%s' % (dac, fi, msg['air_draught']))
              s.add('m8_%d_%d_last_port_%s' % (dac, fi, msg['last_port'])) # TODO: string
              s.add('m8_%d_%d_next_port_0_%s' % (dac, fi, msg['next_port'][0])) # TODO: string
              s.add('m8_%d_%d_next_port_1_%s' % (dac, fi, msg['next_port'][1])) # TODO: string

              for solas_num in range(26):
                s.add('m8_%d_%d_solas_%d_%d' % (dac, fi, item_num, msg['solas'][item_num]))

              s.add('m8_%d_%d_ice_class_%s' % (dac, fi, msg['ice_class']))
              s.add('m8_%d_%d_shaft_power_%s' % (dac, fi, msg['shaft_power']))
              s.add('m8_%d_%d_vhf_%s' % (dac, fi, msg['vhf']))
              s.add('m8_%d_%d_lloyds_ship_type_%s' % (dac, fi, msg['lloyds_ship_type']))
              s.add('m8_%d_%d_gross_tonnage_%s' % (dac, fi, msg['gross_tonnage']))
              s.add('m8_%d_%d_laden_ballast_%s' % (dac, fi, msg['laden_ballast']))
              s.add('m8_%d_%d_heavy_oil_%s' % (dac, fi, msg['heavy_oil']))
              s.add('m8_%d_%d_light_oil_%s' % (dac, fi, msg['light_oil']))
              s.add('m8_%d_%d_diesel_%s' % (dac, fi, msg['diesel']))
              s.add('m8_%d_%d_bunker_oil_%s' % (dac, fi, msg['bunker_oil']))
              s.add('m8_%d_%d_persons_%s' % (dac, fi, msg['persons']))
              s.add('m8_%d_%d_spare2_%s' % (dac, fi, msg['spare2']))


          if 26 == fi: # and 'link_id' in msg:
            # TODO not yet tested

          if 27 == fi and 'link_id' in msg:
            # TODO not yet tested - no messages found
            s.add('m8_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
            s.add('m8_%d_%d_sender_type_%s' % (dac, fi, msg['sender_type']))
            s.add('m8_%d_%d_route_type_%s' % (dac, fi, msg['route_type']))
            s.add('m8_%d_%d_utc_month_%s' % (dac, fi, msg['utc_month']))
            s.add('m8_%d_%d_utc_day_%s' % (dac, fi, msg['utc_day']))
            s.add('m8_%d_%d_utc_hour_%s' % (dac, fi, msg['utc_hour']))
            s.add('m8_%d_%d_utc_min_%s' % (dac, fi, msg['utc_min']))
            s.add('m8_%d_%d_duration_%s' % (dac, fi, msg['duration']))

            for waypt_num, waypt in msg['waypoints']:
              waypt_msg = {'x': waypt[0], 'y': waypt[1]}
              LonLat(waypt_msg, s, prefix='m8_%d_%d_%d' % (dac, fi, waypt_num))

          if 29 == fi and 'link_id' in msg:
             s.add('m8_%d_%d_link_id_%s' % (dac, fi, msg['link_id']))
             AisString(msg, s, 'text', length=161, prefix='m8_%d_%d_text' % (dac, fi))

          if 31 == fi and 'x' in msg:
            LonLat(msg, s, prefix='m8_%d_%d_' % (dac, fi))
            # TODO: limit a lot of these...
            s.add('m8_%d_%d_position_accuracy_%s' % (dac, fi, msg['position_accuracy']))
            s.add('m8_%d_%d_utc_day_%s' % (dac, fi, msg['utc_day']))
            s.add('m8_%d_%d_utc_hour_%s' % (dac, fi, msg['utc_hour']))
            s.add('m8_%d_%d_utc_min_%s' % (dac, fi, msg['utc_min']))
            s.add('m8_%d_%d_wind_ave_%s' % (dac, fi, msg['wind_ave']))
            s.add('m8_%d_%d_wind_gust_%s' % (dac, fi, msg['wind_gust']))
            s.add('m8_%d_%d_wind_dir_%s' % (dac, fi, msg['wind_dir']))
            s.add('m8_%d_%d_wind_gust_dir_%s' % (dac, fi, msg['wind_gust_dir']))
            s.add('m8_%d_%d_air_temp_%s' % (dac, fi, msg['air_temp']))
            s.add('m8_%d_%d_rel_humid_%s' % (dac, fi, msg['rel_humid']))
            s.add('m8_%d_%d_dew_point_%s' % (dac, fi, msg['dew_point']))
            s.add('m8_%d_%d_air_pres_%s' % (dac, fi, msg['air_pres']))
            s.add('m8_%d_%d_air_pres_trend_%s' % (dac, fi, msg['air_pres_trend']))
            s.add('m8_%d_%d_horz_vis_%s' % (dac, fi, msg['horz_vis']))
            s.add('m8_%d_%d_water_level_%s' % (dac, fi, msg['water_level']))
            s.add('m8_%d_%d_water_level_trend_%s' % (dac, fi, msg['water_level_trend']))

            s.add('m8_%d_%d_surf_cur_speed_%s' % (dac, fi, msg['surf_cur_speed']))
            s.add('m8_%d_%d_surf_cur_dir_%s' % (dac, fi, msg['surf_cur_dir']))
            s.add('m8_%d_%d_cur_speed_2_%s' % (dac, fi, msg['cur_speed_2']))
            s.add('m8_%d_%d_cur_dir_2_%s' % (dac, fi, msg['cur_dir_2']))
            s.add('m8_%d_%d_cur_depth_2_%s' % (dac, fi, msg['cur_depth_2']))
            s.add('m8_%d_%d_cur_speed_3_%s' % (dac, fi, msg['cur_speed_3']))
            s.add('m8_%d_%d_cur_dir_3_%s' % (dac, fi, msg['cur_dir_3']))
            s.add('m8_%d_%d_cur_depth_3_%s' % (dac, fi, msg['cur_depth_3']))

            s.add('m8_%d_%d_wave_height_%s' % (dac, fi, msg['wave_height']))
            s.add('m8_%d_%d_wave_period_%s' % (dac, fi, msg['wave_period']))
            s.add('m8_%d_%d_wave_dir_%s' % (dac, fi, msg['wave_dir']))
            s.add('m8_%d_%d_swell_height_%s' % (dac, fi, msg['swell_height']))
            s.add('m8_%d_%d_swell_period_%s' % (dac, fi, msg['swell_period']))
            s.add('m8_%d_%d_swell_dir_%s' % (dac, fi, msg['swell_dir']))
            s.add('m8_%d_%d_sea_state_%s' % (dac, fi, msg['sea_state']))
            s.add('m8_%d_%d_water_temp_%s' % (dac, fi, msg['water_temp']))
            s.add('m8_%d_%d_precip_type_%s' % (dac, fi, msg['precip_type']))
            s.add('m8_%d_%d_salinity_%s' % (dac, fi, msg['salinity']))
            s.add('m8_%d_%d_ice_%s' % (dac, fi, msg['ice']))


        return len(s) - s_len_begin > 0


######################################################################
# 9 - Standard SAR aircraft position report

class Msg9(object):
    def __init__(self):
        self.states = set()
        self.key_set = set()
        self.now = datetime.datetime.utcnow()
        self.junk = set()

    def CheckKeep(self, msg):
        if 1:
            for key in msg:
                self.key_set.add(key)
        s = self.states
        s_len_begin = len(s)

        if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
        s.add('repeat%d' % msg['repeat_indicator'])

        sog = msg['sog']
        if sog == 0:  s.add('sog_0')
        elif sog > 5 and sog < 10:  s.add('sog_low')
        elif sog > 30 and sog < 50:  s.add('sog_high')
        elif sog > 50 and sog < 100:  s.add('sog_crazy')
        elif sog > 102.19 and sog < 102.21: s.add('sog_or_higher')
        elif sog > 102.29 and sog < 102.31: s.add('sog_or_unknown')

        s.add('pa_%d' % msg['position_accuracy'])

        x = msg['x'];  y = msg['y']
        # These are bad
        if x > 180 and y < 90:  s.add('x_undef')
        if x < 180 and y > 90:  s.add('y_undef')
        # This is okay
        if x > 180 and y > 90:  s.add('xy_undef')

        # All bad
        if x < -180 and y > -90:  s.add('x_neg_undef')
        if x > -180 and y < -90:  s.add('y_neg_undef')
        if x < -180 and y < -90:  s.add('xy_neg_undef')

        if x == 0 and y == 0: s.add('x0_y0')
        if x > 2 and x < 15:
          if y > 2 and y < 15: s.add('x_low_y_low')
          if y > 60 and y < 90: s.add('x_low_y_high')
          if y < -2 and y > -15: s.add('x_low_y_neglow')
          if y < -50 and y > -90: s.add('x_low_y_neghigh')

        cog = msg['cog'] # steps of 0.1
        if cog == 0:  s.add('cog0')
        if cog > 9 and cog < 10: s.add('cog9_9') # has a decimal
        if cog > 89.9 and cog <= 90: s.add('cog90')
        if cog > 179.8 and cog <= 180: s.add('cog180')
        if cog > 269.8 and cog <= 270: s.add('cog270')
        if cog == 360: s.add('cog360')
        if cog > 365: s.add('cogTooHigh')

        ts = msg['timestamp']
        if ts in (0,30,59,60,61,62,63):  # 61 is hard to find
            s.add('ts%d' % ts)
        s.add('alt_sensor%d' % msg['alt_sensor'])
        if msg['spare'] in (0, 1, 126, 127): s.add('spare%d' % msg['spare'])
        s.add('dte%d' % msg['dte'])
        s.add('spare2_%d' % msg['spare2'])

        return len(s) - s_len_begin > 0


class Msg10(object):  # ':'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])
    s.add('spare_%d' %msg['spare'])
    if msg['dest_mmsi'] in MMSI_LIST: s.add('dest_mmsi%d' % msg['dest_mmsi'])
    s.add('spare2_%d' %msg['spare2'])

    return len(s) - s_len_begin > 0


Msg11 = Msg4


class Msg12(object):  # '<'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    s.add('seq_%d' %msg['seq_num'])

    if msg['dest_mmsi'] in MMSI_LIST: s.add('dest_mmsi%d' % msg['dest_mmsi'])
    s.add('retransmitted_%s' %msg['retransmitted'])
    s.add('spare_%d' %msg['spare'])

    lengths = range(12) + [48, 49, 85, 86, 122, 123, 156]
    if len(msg['text']) in lengths:
        s.add('text_%d' % len(msg['text']))

    return len(s) - s_len_begin > 0

#Msg13 = Msg7

class Msg14(object):  # '<'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])
    s.add('spare_%d' %msg['spare'])

    lengths = [0, 1, 2, 15, 16, 17, 53, 54, 90, 91, 128, 129, 161]
    if len(msg['text']) in lengths:
        s.add('text_%d' % len(msg['text']))

    return len(s) - s_len_begin > 0


class Msg15(object):  # '?'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    s.add('spare_%d' %msg['spare'])
    if msg['mmsi_1'] in MMSI_LIST: s.add('mmsi_1_%d' % msg['mmsi_1'])
    mmsi1 = msg['mmsi_1']
    s.add('msg_1_1_%d' % msg['msg_1_1'])
    if msg['slot_offset_1_1'] in (0,1,100, 4094, 4095):
      s.add('slot_offset_1_1_%d' % msg['slot_offset_1_1'])

    s.add('spare2_%d' %msg['spare2'])
    s.add('msg_1_1_%d' % msg['msg_1_1'])
    if msg['slot_offset_1_1'] in (0,1,100, 4094, 4095):
      s.add('slot_offset_1_1_%d' % msg['slot_offset_1_1'])

    s.add('spare3_%d' %msg['spare3'])
    if msg['mmsi_2'] in MMSI_LIST: s.add('mmsi_2_%d' % msg['mmsi_2'])
    s.add('msg_2_%d' % msg['msg_2'])
    if msg['slot_offset_2'] in (0,1,100, 4094, 4095):
      s.add('slot_offset_2_%d' % msg['slot_offset_2'])

    return len(s) - s_len_begin > 0


class Msg16(object):  # '?'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])
    s.add('spare_%d' % msg['spare'])

    if msg['dest_mmsi_a'] in MMSI_LIST: s.add('dest_mmsi_a_%d' % msg['mmsi'])
    if msg['offset_a'] in (0,1,10,4094,4095): s.add('offset_a_%d' % msg['offset_a'])
    if msg['inc_a'] in (0,1,8,1022, 1023): s.add('inc_a_%d' % msg['inc_a'])

    if 'dest_mmsi_b' in msg:
      if msg['dest_mmsi_b'] in MMSI_LIST: s.add('dest_mmsi_b_%d' % msg['mmsi'])
      if msg['offset_b'] in (0,1,10,4094,4095): s.add('offset_b_%d' % msg['offset_b'])
      if msg['inc_b'] in (0,1,8,1022, 1023): s.add('inc_b_%d' % msg['inc_b'])

    if 'spare2' in msg: s.add('spare2_%d' % msg['spare2'])

    return len(s) - s_len_begin > 0


class Msg17(object):  # 'A'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])
    s.add('spare_%d' % msg['spare'])

    # TODO: fix this

    if 'spare2' in msg: s.add('spare2_%d' % msg['spare2'])

    return len(s) - s_len_begin > 0

def Sotdma(msg, s):

  if 'sync_state' in msg: s.add('ss%d' % msg['sync_state'] )
  if 'slot_timeout' in msg: s.add('sm%d' % msg['slot_timeout'])
  if 'received_stations' in msg:
        rs = msg['received_stations']
        if rs in (0,1,5): s.add('rs%d' % rs)
        if rs > 50 and rs < 100:  s.add('rs_med')
        if rs > 100 and rs < 500:  s.add('rs_high')
        if rs > 500 and rs < 1000:  s.add('rs_very_high')
        if rs > 1000 and rs < 15000:  s.add('rs_insane')
        if rs > 16000:   s.add('rs_top')
        if rs == 2**14-1:   s.add('rs_max')

  if 'slot_number' in msg:
        sn = msg['slot_number']
        if sn in (0,1,10,2248, 2249):
            s.add('sn%d' % sn)
        if sn > 2250:  s.add('sn_too_high')

  if 'utc_hour' in msg:
        hr = msg['utc_hour']; mn = msg['utc_min']; u_spare = msg['utc_spare']
        if hr in (0, 12, 23, 24, 31):
            s.add('hr%d' % hr)
        if hr > 24 and hr < 31:   s.add('hr_over')
        if mn in (0, 11, 59, 60, 127): s.add('mn%d' % mn)
        if mn > 60 and mn < 127: s.add('mn_over')
        s.add('utc_spare%d' % u_spare)

  if 'slot_offset' in msg:
        so = msg['slot_offset']
        if so in (0,1, 2248, 2249, 2250, 2**14-2,  2**14-1):
            s.add('so%d' % so)
        if so > 2260 and so < 15000: s.add('so_too_high')

def Itdma(msg, s):
  if 'sync_state' in msg: s.add('itdma_ss%d' % msg['sync_state'])
  slot_inc = msg['slot_increment']
  # last 2 not valid
  if slot_inc in (0, 1, 2248, 2249, 2250, 8191): s.add('slot_inc%d' % slot_inc)
  s.add('nslots%d' % msg['slots_to_allocate'])
  s.add('keep%d' % msg['keep_flag'])


class Msg18(object):  # 'B'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])
    s.add('spare_%d' % msg['spare'])

    Sog(msg, s)
    # sog = msg['sog']
    # if sog == 0:   s.add('sog_0')
    # elif sog > 5 and sog < 10:   s.add('sog_low')
    # elif sog > 30 and sog < 50:   s.add('sog_high')
    # elif sog > 50 and sog < 100:   s.add('sog_crazy')
    # elif sog > 102.19 and sog < 102.21:   s.add('sog_or_higher')
    # elif sog > 102.29 and sog < 102.31:   s.add('sog_or_unknown')

    s.add('pa_%d' % msg['position_accuracy'])
    x = msg['x'];  y = msg['y']
    # These are bad
    if x > 180 and y < 90: s.add('x_undef')
    if x < 180 and y > 90: s.add('y_undef')
    # This is okay
    if x > 180 and y > 90: s.add('xy_undef')
    if x == 0 and y == 0:  s.add('x0_y0')
    if x > 2 and x < 15:
      if y > 2 and y < 15:  s.add('x_low_y_low')
      if y > 60 and y < 90:  s.add('x_low_y_high')
      if y < -2 and y > -15:  s.add('x_low_y_neglow')
      if y < -50 and y > -90:  s.add('x_low_y_neghigh')
    cog = msg['cog'] # steps of 0.1
    if cog == 0:  s.add('cog0')
    if cog > 9 and cog < 10:  s.add('cog9_9') # has a decimal
    if cog > 89.9 and cog <= 90:  s.add('cog90')
    if cog > 179.8 and cog <= 180:  s.add('cog180')
    if cog > 269.8 and cog <= 270:  s.add('cog270')
    if cog == 360:  s.add('cog360')
    if cog > 365:  s.add('cogTooHigh')

    th = msg['true_heading']
    if th in (0, 90, 180, 270, 359):
      s.add('th%d' % th)
    if th == 360:  s.add('th_bad_360')  # NOT valid
    if th > 400 and th < 500:  s.add('th_wonky')
    if th == 511:  s.add('th_na')
    ts = msg['timestamp']
    if ts in (0,30,59,60,61,62,63): s.add('ts%d' % ts)

    if 'spare2' in msg: s.add('spare2_%d' % msg['spare2'])

    s.add('unit_flag_%d' % msg['unit_flag'])
    s.add('display_flag_%d' % msg['display_flag'])
    s.add('dsc_flag_%d' % msg['dsc_flag'])
    s.add('band_flag_%d' % msg['band_flag'])
    s.add('m22_flag_%d' % msg['m22_flag'])
    s.add('mode_flag_%d' % msg['mode_flag'])

    s.add('raim_%d' % msg['raim'])

    if not msg['commstate_flag']:
      Sotdma(msg, s)
    elif not msg['unit_flag']:
      Itdma(msg, s)

    return len(s) - s_len_begin > 0

class Msg19(object):  # 'C'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    if 1:
        for key in msg:
            self.key_set.add(key)
    s = self.states
    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])
    s.add('spare_%d' % msg['spare'])

    s.add('spare_%d' % msg['spare'])
    Sog(msg, s)
    s.add('pa_%d' % msg['position_accuracy'])
    LonLat(msg, s)
    Cog(msg, s)
    TrueHeading(msg, s)
    # 61 is hard to find
    ts = msg['timestamp']
    if ts in (0,30,59,60,61,62,63): s.add('ts%d' % ts)

    s.add('spare2_%d' % msg['spare2'])
    Name(msg, s)
    s.add('tc%d' % msg['type_and_cargo'])
    Dim(msg, s)
    s.add('ft%d' % msg['fix_type'])

    s.add('raim_%d' % msg['raim'])

    s.add('dte%d' % msg['dte'])
    s.add('assigned_mode_%d' % msg['assigned_mode'])
    s.add('spare_3%d' % msg['spare3'])

    return len(s) - s_len_begin > 0


class Msg20(object):  # 'D'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    s.add('spare_%d' % msg['spare'])
    for res_num, res in enumerate(msg['reservations']):
      if res['offset'] in (0,1, 10, 4094, 4095):
        s.add('offset_%d_%d' % (res_num, res['offset']))
      s.add('num_slots_%d_%d' % (res_num, res['num_slots']))
      s.add('timeout_%d_%d' % (res_num, res['timeout']))
      if res['incr'] in (0,1, 10, 2046, 2047):
        s.add('incr_%d_%d' % (res_num, res['incr']))

    return len(s) - s_len_begin > 0


class Msg21(object):  # 'E'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    s.add('aton_type_%d' % msg['aton_type'])
    # TODO: Name length issue
    Name(msg, s, length=20+14)
    s.add('pa_%d' % msg['position_accuracy'])
    LonLat(msg, s)
    Dim(msg, s)
    s.add('ft%d' % msg['fix_type'])
    ts = msg['timestamp']
    if ts in (0,30,59,60,61,62,63): s.add('ts%d' % ts)

    for field in ('off_pos', 'aton_status', 'raim', 'virtual_aton', 'assigned_mode'):
      s.add('%s_%d' % (field, msg[field]))

    return len(s) - s_len_begin > 0


class Msg22(object):  # 'F'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    for field in ('spare', 'chan_a', 'chan_b', 'txrx_mode', 'power_low',
                  'chan_a_bandwidth', 'chan_b_bandwidth', 'zone_size'):
      s.add('%s_%d' % (field, msg[field]))
    if 'x1' in msg:
      for which in (1,2):
        x = msg['x%d' % which];  y = msg['y%d' % which]
        if x > 180 and y < 90: s.add('x_undef%d' % which)
        if x < 180 and y > 90: s.add('y_undef%d' % which)
        if x > 180 and y > 90: s.add('xy_undef%d' % which)
        if x < -180 and y > -90: s.add('x_neg_undef%d' % which)
        if x > -180 and y < -90: s.add('y_neg_undef%d' % which)
        if x < -180 and y < -90: s.add('xy_neg_undef%d' % which)
        if x == 0 and y == 0: s.add('x0_y0%d' % which)

    else:
      if msg['dest_mmsi_1'] in MMSI_LIST: s.add('dest_mmsi_1_%d' % msg['dest_mmsi_1'])
      if msg['dest_mmsi_2'] in MMSI_LIST: s.add('dest_mmsi_2_%d' % msg['dest_mmsi_2'])

    if msg['spare2'] in (0, 1, 49407, 547448, 549235, 558017, 8388606, 8388607):
      s.add('spare2_%d' % msg['spare2'])

    return len(s) - s_len_begin > 0


class Msg23(object):  # 'G'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    for which in (1,2):
      x = msg['x%d' % which];  y = msg['y%d' % which]
      if x > 180 and y < 90: s.add('x_undef%d' % which)
      if x < 180 and y > 90: s.add('y_undef%d' % which)
      if x > 180 and y > 90: s.add('xy_undef%d' % which)
      if x < -180 and y > -90: s.add('x_neg_undef%d' % which)
      if x > -180 and y < -90: s.add('y_neg_undef%d' % which)
      if x < -180 and y < -90: s.add('xy_neg_undef%d' % which)
      if x == 0 and y == 0: s.add('x0_y0%d' % which)

    for field in ('station_type', 'type_and_cargo', 'txrx_mode', 'interval_raw',
                  'quiet'):
      s.add('%s_%d' % (field, msg[field]))

    if msg['spare2'] in (0, 1, 2, 4, 4194302, 4194303):
      s.add('spare2_%d' % msg['spare2'])

    if msg['spare3'] in (0, 1, 2, 4, 60, 61, 62, 63):
      s.add('spare3_%d' % msg['spare3'])

    return len(s) - s_len_begin > 0


class Msg24(object):  # 'H'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    if msg['part_num'] == 0:
      AisString(msg, s, 'name', length = 20)
    elif msg['part_num'] == 1:
      s.add('type_and_cargo_%s' % msg['type_and_cargo'])
      AisString(msg, s, 'vendor_id', length=7)
      AisString(msg, s, 'callsign', length=7)
      Dim(msg, s)
      s.add('spare_%s' % msg['spare'])
    elif msg['part_num'] == 2:
      if 'part_2' not in s: print 'Ais24 part 2 not yet defined'
      s.add('part_2')
    elif msg['part_num'] == 3:
      if 'part_3' not in s: print 'Ais24 part 3 not yet defined'
      s.add('part_3')

    return len(s) - s_len_begin > 0


class Msg25(object):  # 'I'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    if 'dest_mmsi' in msg and msg['dest_mmsi'] in MMSI_LIST:
      s.add('dest_mmsi_%d' % msg['dest_mmsi'])

    if 'dac' in msg: s.add('dac_fi_%d_%d' % (msg['dac'], msg['fi']))
    else: s.add('no_dac_fi')

    # TODO: handle payloads

    return len(s) - s_len_begin > 0


class Msg26(object):  # 'K'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    if 'dest_mmsi' in msg and msg['dest_mmsi'] in MMSI_LIST:
      s.add('dest_mmsi_%d' % msg['dest_mmsi'])

    if 'dac' in msg: s.add('dac_fi_%d_%d' % (msg['dac'], msg['fi']))
    else: s.add('no_dac_fi')

    # TODO: handle payloads

    # TODO: commstate

    return len(s) - s_len_begin > 0


class Msg27(object):  # 'L'
  def __init__(self):
    self.states = set()
    self.key_set = set()

  def CheckKeep(self, msg):
    for key in msg:
          self.key_set.add(key)
    s = self.states

    s_len_begin = len(s)

    if msg['mmsi'] in MMSI_LIST: s.add('mmsi%d' % msg['mmsi'])
    s.add('repeat%d' % msg['repeat_indicator'])

    s.add('pa_%d' % msg['position_accuracy'])
    s.add('raim%d' % msg['raim'])
    s.add('ns_%d' % msg['nav_status'])
    LonLat(msg, s)
    if msg['sog'] in (0, 1, 8, 16, 61, 62, 63): s.add('sog_%d' % msg['sog'])
    if msg['cog'] in (0, 1, 8, 16, 90, 180, 270, 359, 360, 361, 510, 511): s.add('cog_%d' % msg['cog'])

    s.add('gnss_%d' % msg['gnss'])
    s.add('spare_%d' % msg['spare'])


    return len(s) - s_len_begin > 0

def main():
  norm = Normalize()

  handlers = {
      '1': Msg1(),
      '2': Msg1(),
      '3': Msg1(),
      '4': Msg4(),
      '5': Msg5(),
      '6': Msg6(),
      '7': Msg7_13(),
      '8': Msg8(),
      '9': Msg9(),
      ':': Msg10(),
      ';': Msg11(),  # 11 and 4 are the same
      '<': Msg12(),
      '=': Msg7_13(),  # No message 13's found
      '>': Msg14(),
      '?': Msg15(),
      '@': Msg16(),
      'A': Msg17(),
      'B': Msg18(),
      'C': Msg19(),
      'D': Msg20(),
      'E': Msg21(),
      'F': Msg22(),
      'G': Msg23(),
      'H': Msg24(),
      'I': Msg25(),
      'J': Msg26(),
      'K': Msg27(),
      # 'L': Msg28(), # Not yet defined
      }

  lengths = {}
  decoded_counts = {}

  unhandled_msg_types = set()

  o = open('keep.ais', 'w')

  for filename in sys.argv[1:]:
    print '# file:', filename
    for line_num, line in enumerate(open(filename)):
      if line_num % 100000 == 0:
        print 'line:', line_num
      if not line.startswith('!AIVD'):
        continue

      #if line_num >= 1000: break
      norm.put(line.strip(), line_num)
      del line  # Do not use this any more!

      try:
        result = norm.get(False)
      except Queue.Empty:
        continue

      body = result['body']
      if not body:
        continue
      msg_type = body[0]
      pad = result['pad']

      key = '%s_%d' % (msg_type, 6*len(body)-pad)
      if key not in lengths:
        lengths[key] = 0
      else:
        lengths[key] += 1

      if msg_type not in handlers:
        if msg_type not in unhandled_msg_types:
          unhandled_msg_types.add(msg_type)
          print 'not handled: msg="%s"' % msg_type
        continue

      try:
        msg = ais.decode(body, pad)
      except Exception as e:
        if 'AIS_ERR_BAD_BIT_COUNT' not in str(e) and 'AIS_ERR_MSG_TOO_LONG' not in str(e):
          sys.stderr.write('Error: %s: %s' % (e, result) )
        continue

      if msg_type not in decoded_counts:
        decoded_counts[msg_type] = 1
      else:
        decoded_counts[msg_type] += 1

      msg_handler = handlers[msg_type]
      if msg_handler.CheckKeep(msg):
        if 1:
          for raw in result['raw']:
            o.write(raw)
            o.write('\n')
        pass

  if 0:
    for h in handlers:
      if handlers[h].states:
        m = handlers[h]
        print 'states:', h, len(m.states), sorted(m.states)
        print 'keys:', h, sorted([str(key) for key in m.key_set])

    print 'lengths:', lengths
    print 'decoded_counts:', decoded_counts

if __name__ == '__main__':
  main()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions