3. AIS原始报文解析与航迹字段提取

AIS航迹数据的源头是一串串以“!”或“$”开头的ASCII文本,即NMEA 0183语句。无论后续做轨迹可视化、船舶行为分析还是碰撞风险识别,第一步都必须把这些“看起来像乱码”的报文还原为可计算的经纬度、航速、航向和时间戳。本节围绕语句结构、报文类型解码、字段含义与校验四个层面展开,目标是让读者能够独立完成从原始报文到航迹字段的提取。

NMEA 0183语句结构:先拆封装,再谈内容

AIS常用的封装语句有四种:!AIVDM(其他船发来的AIS数据)、!AIVDO(本船自身数据)、!AIVDM的多片段形式以及基站转发形式。一条典型语句如下:

!AIVDM,1,1,,A,15Muq?P0000G?t@E>4p@0?vN0<0u,0*5C

按逗号切分后各字段含义为:语句标识AIVDM;片段总数1;当前片段序号1;多片段消息ID(此处为空);AIS信道A;有效载荷15Muq?P...;填充位数0;校验和5C。其中有效载荷是6位ASCII编码的二进制串,填充位数表示末尾补了几个无效比特,校验和是“$”与“*”之间所有字符的异或值。解析前必须先做校验和验证,再按填充位截断,否则后续位偏移会整体错位。

多片段报文(片段总数大于1)需要先按消息ID缓存并拼接,再统一解码;单片段直接解码即可。拼接时注意保持片段顺序,不能按到达顺序简单追加。

报文类型1/2/3/5/18/19/24的解码方法

AIS报文类型由有效载荷的前6位(bit 0–5)给出。类型1/2/3为A类船舶的动态信息,结构相同,仅消息类型值不同;类型5为A类船舶的静态与航次信息;类型18为B类船舶动态信息;类型19为B类船舶静态与动态合并信息;类型24为B类船舶静态信息(分A、B两部分)。解码的核心是“按位取值”:把6位ASCII载荷还原为二进制串后,依据各类型的位定义表逐字段截取。

以类型1为例,关键字段的位偏移为:消息类型bit 0–5;MMSI bit 8–37;航行状态bit 38–41;转向率bit 42–49;对地航速bit 50–59;位置精度bit 60;经度bit 61–88;纬度bit 89–115;对地航向bit 116–127;时间戳bit 128–136。经度和纬度均为1/10000分的有符号整数,需除以600000转换为度;航速单位为0.1节,除以10;航向单位为0.1度,除以10。类型18的字段顺序与类型1不同,例如航速位于bit 46–55,经度位于bit 57–84,纬度位于bit 85–112,解码时必须使用对应类型的位表,不能混用。

类型5的载荷较长(通常为2个片段),包含船名、呼号、船型、尺寸、目的地和ETA等静态字段。船名位于bit 112–231,共20个6位字符,需按AIS 6位字符表映射为可读文本;尺寸字段分为船首到参考点、船尾到参考点、左舷和右舷四个值,单位均为米。类型24分为Part A(船名)和Part B(船型、尺寸、呼号),解析时需根据Part Number字段区分,并将同一MMSI的两部分合并。

AIS报文解析链路:从NMEA语句到航迹字段 原始NMEA语句 !AIVDM,1,1,,A,...*5C 校验和 / 片段拼接 6位ASCII载荷 还原二进制位串 按填充位截断 类型识别 bit 0–5 消息类型 选择对应位表 字段提取 按位偏移截取 单位换算 动态信息(类型1/2/3/18) MMSI:船舶唯一标识 经度/纬度:1/10000分 ÷ 600000 对地航速:0.1节 ÷ 10 对地航向:0.1度 ÷ 10 时间戳:UTC秒(60=不可用) 静态信息(类型5/19/24) 船名/呼号:6位字符表映射 船型:两位数字编码 尺寸:首/尾/左/右(米) 目的地/ETA:类型5专有 Part A/B:类型24需合并 字段校验要点 经度范围:-180~180 纬度范围:-90~90 航速:0~102.2节,1023为不可用 航向:0~359.9度,3600为不可用 MMSI:9位数字,首位为船舶类别 航迹字段输出示例(类型1解码结果,示例数据) MMSI=413123456 | 经度=121.4732°E | 纬度=31.2304°N | 航速=8.6节 | 航向=45.2° | 时间戳=32s 校验结论:经纬度在合法范围内,航速与航向非“不可用”值,时间戳有效 → 该点可写入航迹表 注:以上数值为教学示例,非实测数据;实际解析结果以原始报文位段计算为准。

动态信息与静态信息字段含义

动态信息描述船舶“此刻在哪里、怎么走”,更新频率高(A类船在航行时约2–10秒一次),是航迹重建的主体。核心字段包括:MMSI(海上移动通信业务标识,9位数字,前三位为MID国家码)、经度、纬度、对地航速(SOG)、对地航向(COG)、真航向(HDG)、航行状态(如“在航”“锚泊”“受限操纵”)、转向率(ROT)和时间戳。需要特别注意“不可用”约定:经度181°、纬度91°、航速1023、航向3600、时间戳60均表示该字段无效,解析后应置为空值而非直接参与计算。

静态信息描述船舶“是谁、长什么样”,更新频率低(通常6分钟一次),包括船名、呼号、IMO编号、船型、尺寸、目的地和ETA。静态字段用于给航迹点“贴标签”,例如按船型筛选货船与渔船,或按尺寸估算船舶占位。类型19将静态与动态合并在一条报文中,适合B类船舶;类型24则把静态信息拆成两部分,解析时需按MMSI缓存合并,否则会出现“有船名无尺寸”的残缺记录。

解析实战与字段校验

下面给出一段Python示例,演示从单条类型1报文到航迹字段的完整提取过程(示例参数,非实测):

import binascii

def sixbit_to_bin(payload):
    bits = ""
    for ch in payload:
        v = ord(ch) - 48
        if v > 40:
            v -= 8
        bits += format(v, "06b")
    return bits

def decode_type1(payload, fill):
    bits = sixbit_to_bin(payload)[:len(sixbit_to_bin(payload)) - fill]
    mmsi = int(bits[8:38], 2)
    sog = int(bits[50:60], 2) / 10.0
    lon = int(bits[61:89], 2)
    lat = int(bits[89:116], 2)
    if lon & (1 << 27): lon -= (1 << 28)
    if lat & (1 << 26): lat -= (1 << 27)
    lon /= 600000.0
    lat /= 600000.0
    cog = int(bits[116:128], 2) / 10.0
    ts = int(bits[128:137], 2)
    return {"mmsi": mmsi, "sog": sog, "lon": lon, "lat": lat, "cog": cog, "ts": ts}

print(decode_type1("15Muq?P0000G?t@E>4p@0?vN0<0u", 0))

校验环节建议至少做四类检查:其一,校验和比对,不一致的语句直接丢弃;其二,范围检查,经度超出±180、纬度超出±90即判为异常;其三,不可用值检查,航速1023、航向3600、时间戳60应转为空值;其四,连续性检查,同一MMSI相邻两点的距离与时间差若推算出不合理速度(例如超过60节),应标记为跳点。经过这四步,写入航迹表的每条记录才具备可用性。

类型1/2/3与类型18的位偏移不同,切勿用同一套偏移量解码所有动态报文;类型5和类型24的文本字段需按AIS 6位字符表映射,直接按ASCII处理会得到乱码。

完成上述解析与校验后,每条航迹记录应至少包含:MMSI、UTC时间、经度、纬度、SOG、COG、船名、船型、数据来源类型。这张结构化的航迹表,才是后续轨迹聚类、航速分布统计和异常行为识别可以直接使用的输入。