新闻详情

新闻详情

首页 / 资讯中心 / 详情

AIS数据协议解析:从NMEA语句到船舶动态的完整解码指南

发布时间:2026/10/2 16:09:20来源:尧图网络
AIS数据协议解析:从NMEA语句到船舶动态的完整解码指南
简介这是一份面向船舶通信、岸基监控及海事信息化开发者的自动识别系统数据协议解析技术资料系统讲解报文封装格式、语句类型、信道指示、数据部分长度限制、八位循环冗余校验字段与语句结束标志并给出动态信息与静态信息的分类及对应报文类型可解决从原始AIS语句中拆包、解码和校验的实际难题。资源包内仅一个DOC文档大小678KB篇幅紧凑且层次分明便于作为协议字典随时查阅。文档后半部分提供了基于C语言的解析思路与关键函数示例涵盖八位字节流转换为六位字节流、报文头字段提取以及按消息标识符解析本船他船动态静态信息等流程并以5号包分两帧发送后的合并解析为具体案例呈现从物理层语句到上层应用数据的完整映射过程工程实用性强。该资料已有3749人学习适合正在开发船舶自动识别系统、船舶交通管理系统或港口管理相关功能的研发人员参考。1. AIS数据协议解析一条AIVDM语句如何变成可用的船舶动态做船舶监控、VTS系统或者航运数据分析的同行应该都有过这种体验AIS接收机插上电串口里刷刷往外吐数据一眼看过去全是!AIVDM开头的字符串但想从里面拿一条船名或经纬度却发现自己和这些字符之间隔着一层说不清的黑匣子。这套AIS数据协议解析就是把ITU-R M.1371定义的消息结构、6bit编码和NMEA 0183外壳一层层剥开最终还原成结构化字段。这份《AIS数据协议解析完整版.doc》覆盖了AIS常见消息类型、字段位宽、编码规则与解析边界适合做船舶定位、海事监管、航运数据分析和AIS设备集成的工程人员。照着下面的步骤走下来你能独立完成从NMEA语句到结构化船舶动态的完整解析链路。2. 从NMEA外壳到6bit编码拆开AIS报文的两个必须层AIS报文和很多工业协议一样外部套了一层NMEA 0183的壳内部才是真正的数据载荷。先分清这两层后面解析才不会绕路。2.1 NMEA语句的七段结构!AIVDM后面的值分别是什么一条完整的AIS NMEA语句长这样!AIVDM,1,1,,A,15M67FC000G?ufbEFepT3n00Sb,0*4A按逗号拆开每一段的含义在解析器里几乎都能一一对应到代码变量位置内容说明前缀!AIVDM消息来源标识AIVDM表示本机接收到的AIS消息AIVDO表示本机发送字段11本条消息的总帧数分包消息会大于1字段21当前帧序号从1开始字段3空多消息标识0-9用于关联同一逻辑消息单帧常为空字段4A接收通道A或B字段515M67...有效载荷payload由6bit字符组成字段60填充位数0-5告诉解码器末尾要舍弃几个bit校验*4A星号后两个十六进制字符校验和字段5的payload是整个协议的核心字段6的填充位容易被忽略。payload被转成位串后末尾会多出0-5个无意义的填充bit必须在按字段切分前丢掉否则后面的字段整体错位一位这也是新手最容易翻车的点之一。2.2 6bit编码表为什么你看到的字符是大写字母加标点payload里每个字符并不直接对应ASCII而是对应一个0到63的数字每个数字再转成6bit。AIS用的字符集是固定的值012...252627...3132...57...63字符012...Z:;...?...y的位置由值决定...w更严谨地说索引表是0123456789:;?ABCDEFGHIJKLMNOPQRSTUVWXYZ第0个字符是0第12个字符是第32个字符是。解码时把每个字符在这个表里的下标取出来转成6位二进制按顺序拼成一条长位串。举个例子字符1在表中索引为1二进制是000001字符5索引是5二进制是000101。拼接后的位串就是后续按位提取字段的原料。这个过程不复杂但序列化方向是固定的从payload第一个字符的高位开始逐字符向低位拼接不能倒过来。2.3 消息类型速览先知道有哪些类型再决定解码分支消息ID占据位串最前面的6bit取值范围1到27。实际数据流里最常见的是下面几类类型名称关键内容1/2/3A类位置报告MMSI、经纬度、SOG、COG、HDG、航行状态4基站报告基站位置、UTC时间5A类静态数据船名、呼号、IMO、船舶类型、尺寸18B类位置报告B类设备的位置和速度19B类扩展位置报告位置加船型、尺寸24B类静态数据分Part A和Part B含船名、呼号、尺寸27远程位置报告长距离、低功耗场景只有粗略位置一般来说解析器的主分支只需关心1/2/3、5、18、24这六类其余消息类型可以走一个通用兜底逻辑只提取MMSI和消息ID避免程序被未知类型打断。3. 字段级解码经纬度、航速、船名与呼号的位级换算进入正题之前先把一个概念说清楚AIS报文的位串是从第0位开始编号的每个字段都有一组确定的起止位。解码本质上就是按偏移量切片再按各自的比例尺换算成物理量。3.1 类型1/2/3位置报告28bit经度与27bit纬度是最后的大头类型1/2/3的位偏移我按标准字段顺序整理了一张简化表实际解析时直接对着取数就行字段起始位位宽单位与换算消息ID061/2/3重复指示620-3MMSI830十进制整数导航状态3840-15ROT旋回率428带符号见下文说明SOG对地航速5010值 × 0.1单位节位置精度6010/1经度6128值 / 600000单位度纬度8927值 / 600000单位度COG对地航向11612值 × 0.1单位度HDG船首向1289值单位度UTC秒13760-59经纬度的换算最容易让人疑惑。AIS编码里经纬度单位是1/10000分钟而1分钟等于1/60度所以字段值要除以600000才能得到十进制度数。例如经度字段是114000000实际经度就是190度不对这个数偏大实际海上经度字段通常是1亿左右的量级除以600000后正好落在合理的度数范围内。纬度同理。SOG字段是10bit的无符号数最大值1023。其中1022表示速度大于等于102.2节1023表示不可用。COG字段3600表示不可用。HDG字段511表示不可用。这些特殊值在做数据入库前必须处理掉不然你会在一张动态表里看到一条船以1023节的速度在陆地上漂移。ROT字段按标准需要做一个平方根变换常见公式是ROT 4.733 × sqrt(ROT_field)方向由符号位决定。实际业务里多数场景只关心船是否在旋回不关心具体旋回率所以把这个字段原值输出即可。如果将来要做旋回告警再按标准做完整换算。3.2 类型5静态数据船名解码的20字符规则类型5的位串长度达到424bit字段布局和类型1完全不同。核心字段如下字段起始位位宽说明IMO编号3830十进制呼号68427个6bit字符船名11012020个6bit字符船舶类型2308查码表到船首距离2389单位米到船尾距离2479单位米到左舷距离2566单位米到右舷距离2626单位米船名和呼号不是ASCII而是用6bit字符编码。文本解码的规则是取出每个6bit值0到31映射到字符表ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_32到63映射到空格!#$%()*,-./0123456789:;?。实际实现时值小于32就用chr(value 64)转换大于等于32直接chr(value)转换。船名字段不足20个字符时用填充所以解码后要把尾部的去掉否则入库的船名会带一串尾巴。3.3 类型18与类型24B类设备的报文边界B类船舶设备是AIS数据流里数量最多的信号源它们的报文和A类有一处关键差异类型24分成Part A和Part B两条消息。Part A里位偏移38到39是部件号值为0后面跟着船名、船舶类型和尺寸。Part B的部件号为1后面是呼号和设备类型。解析时必须先读这个2bit的Part字段再决定后面的字段如何解释。很多初学者把类型24当成一条完整消息去解结果Part B的数据被强行按Part A的字段宽度切船名和呼号全乱。类型18是B类位置报告字段布局接近类型1但没有导航状态和ROT且MMSI后面直接跟经纬度。写解析器时类型18的偏移表必须单独建不能和类型1共用一套切片配置。4. 校验和与数据质量排查三个最容易翻车的解析点我把这部分单独拿出来是因为AIS解析的坑几乎不在协议本身而在数据质量的边界处理上。下面三条是我在实际项目中真实踩过的每一条都足够让一天的排查白费。4.1 校验和算不对程序没错是异或范围错了现象解析器连续把几十条NMEA语句判为校验和不匹配换了好几种校验算法都一样。原因AIS的校验和是!到*之间所有字符的ASCII码按位异或注意是!之后到*之前不包括!本身也不包括*。有人从!开始异或有人把逗号漏掉还有人把*后面那两个十六进制字符也当成参与校验的字节。这三种都是常见误用。解决按这个顺序写基本不会错——先取*左边的部分再从索引1开始遍历到末尾逐个字符与累计值异或最后取低8位转成两位大写十六进制和星号后的字段比对。这一步如果放在解析入口统一做后面所有解码分支的数据源就是干净的。4.2 分包消息重组错乱序列号被忽略船名拼成乱码现象类型5的船名偶尔正常偶尔乱码或者MMSI和船名对不上号。原因类型5和类型24里一部分消息是分帧的一个payload可能被切成两条NMEA语句输出。如果解析器每收到一帧就立刻解码那么第二条分片会拿一半位串去解所有字段全部错位。另一个常见原因是分包后用线程逐条处理不同消息的分片交替到达线程各自缓存序列号没有用来归类。解决看到字段1大于1时把这条帧先送到一个按序列号字段分组的缓存里收齐后按帧序号顺序拼接payload再进入解码流程。缓存要做超时清理比如超过10秒没凑齐就丢弃防止内存被异常网络慢慢吃满。4.3 无效值当作真实数据SOG1023和纬度91度的玄学现象数据库里出现航速1023节、经度190度、船首向511度的记录做统计时这些点把平均航速拉高好几倍。原因这些值对应的是协议里的特殊标记。SOG1022表示大于102.2节1023表示传感器不可用COG3600表示不可用HDG511表示无船首向信息经纬度字段超出正常值域时表示位置无效。如果不加判断这些数字会直接进业务逻辑。解决在解码完成之后统一过一遍值域过滤器。SOG大于等于102.2置空COG大于等于3600置空HDG等于511置空纬度绝对值大于90或经度绝对值大于180直接置空。这一步做完后续关联、插值、告警才不会拿脏数据当输入。5. 用Python把协议跑起来解析器骨架与接入参数调优文档对应的内容最终要落到能跑的代码上。这里给出一版我平时用的解析器骨架只保留核心解码分支端口和串口接入部分单独说明。5.1 从语句到字典核心解析器代码import re _CHARSET 0123456789:;?ABCDEFGHIJKLMNOPQRSTUVWXYZ class AisParser: AIS NMEA 语句解析器单帧消息解码分包重组由上层完成。 staticmethod def checksum_ok(sentence: str) - bool: body, check sentence.rsplit(*, 1) cksum 0 # 从 ! 之后开始异或到 * 之前结束 for ch in body[1:]: cksum ^ ord(ch) return f{cksum:02X} check.strip().upper() staticmethod def bits_from_payload(payload: str) - str: bits for c in payload: v _CHARSET.index(c) bits f{v:06b} # 每个字符补足 6 位顺序拼接 return bits staticmethod def field(bits: str, start: int, length: int) - int: return int(bits[start:start length], 2) staticmethod def text6(bits: str, start: int, length: int) - str: # 按 6bit 切分还原成可读文本去掉尾部 填充 chars [] for pos in range(0, length, 6): v int(bits[start pos:start pos 6], 2) chars.append(chr(v 64) if v 32 else chr(v)) return .join(chars).rstrip().strip() def parse(self, sentence: str) - dict: sentence sentence.strip() if not self.checksum_ok(sentence): raise ValueError(checksum mismatch) parts sentence.split(,) total int(parts[1]) seq parts[3] payload parts[5] fill int(parts[6].split(*)[0]) if total 1: # 分帧消息先返回缓存标识由上层收齐后合并 payload 再解析 return {fragment: True, total: total, seq: seq} bits self.bits_from_payload(payload) if fill: bits bits[:-fill] # 丢弃末尾填充位 msg_type self.field(bits, 0, 6) if msg_type in (1, 2, 3): return self._parse_position(bits, msg_type) if msg_type 5: return self._parse_static(bits) if msg_type 24: return self._parse_type24(bits) return {type: msg_type, mmsi: self.field(bits, 8, 30)} def _parse_position(self, bits, msg_type): d { type: msg_type, repeat: self.field(bits, 6, 2), mmsi: self.field(bits, 8, 30), nav_status: self.field(bits, 38, 4), sog: self.field(bits, 50, 10) / 10.0, lon: self.field(bits, 61, 28) / 600000.0, lat: self.field(bits, 89, 27) / 600000.0, cog: self.field(bits, 116, 12) / 10.0, hdg: self.field(bits, 128, 9), utc_sec: self.field(bits, 137, 6), } # 协议特殊值统一置空 if d[sog] 102.2: d[sog] None if d[cog] 3600: d[cog] None if d[hdg] 511: d[hdg] None if abs(d[lat]) 90 or abs(d[lon]) 180: d[lat] d[lon] None return d def _parse_static(self, bits): return { type: 5, mmsi: self.field(bits, 8, 30), imo: self.field(bits, 38, 30), callsign: self.text6(bits, 68, 42), shipname: self.text6(bits, 110, 120), shiptype: self.field(bits, 230, 8), } def _parse_type24(self, bits): part self.field(bits, 38, 2) if part 0: return { type: 24, part: A, mmsi: self.field(bits, 8, 30), shipname: self.text6(bits, 40, 120), shiptype: self.field(bits, 160, 8), } return { type: 24, part: B, mmsi: self.field(bits, 8, 30), callsign: self.text6(bits, 40, 42), }这段代码里checksum_ok放在最前面保证非法语句不进业务逻辑。bits_from_payload把payload整串变成二进制位串fill参数在后面裁掉填充位。field函数是取数主入口三个参数分别是位串、起始位、位宽。text6专解船名呼号这类文本字段对填充做了清除。使用时候注意几点一是field的起始位和位宽要严格对照协议偏移表类型5的船名从110位起、宽120位这类硬编码参数最好抽成常量方便后续对照标准维护。二是分帧消息在parse里只返回了fragment标记实际项目中需要在外面维护一个字典以seq为键缓存分片收齐后把多个payload合并再一次调用bits_from_payload。三是_parse_position里对特殊值的过滤放在最后统一处理这样上层拿到的字典里就不可能出现超出物理意义的数值。5.2 时间戳与去重从真实数据流里找增量位置报告里的UTC秒字段只精确到秒没有日期部分。真实项目必须结合接收端本地时间和船位判断来推算日期否则同一秒内多条船舶消息的时间会重叠。常见做法是用本地时间兜底当消息跨零点时优先保留上一次收到的日期。重复消息去重方面AIS同一个MMSI的消息会以不同速率到达静态消息可能几分钟才发一次位置报告则可能每秒一条。建议用(mmsi, type, sog, lon, lat, cog)做一次指纹去重时间窗口设3秒。这个组合对正常航行中的船几乎不会误伤但能有效滤掉同一秒内的冗余广播。5.3 串口与UDP接入时的参数调优AIS接收机最常见的输出是串口波特率一般用384008数据位、无校验、1停止位。如果接到多船共用设备可能要用4800波特率的老协议这个要先看设备说明书。网络接入时AIS共享服务和部分接收设备支持UDP输出常见端口是10110。UDP接收要设置一个稍大的缓冲区因为AIS广播峰值时一秒可能到几十条。我一般会把接收socket的SO_RCVBUF调到512KB以上再配一个阻塞队列做缓冲解析线程从队列取数据。如果直接同步解析UDP突发流量一来回调里处理不过来的消息直接丢数据完整性很难保证。6. 用回放验证解析结果三条检查习惯与进阶收口代码写完之后真正花时间的往往是验证。我给自己的工作要求是每接入一个新的AIS数据源必须强制走一遍下面三条检查。第一条跑一次校验和统计。拿原始数据流跑一个小时统计校验和不通过的语句数量。正常数据源这个比例应该在千分之一以下。如果超过百分之一先别急着分析业务数据回到数据源更新固件或调整串口配置。这一条能过滤掉八成脏数据问题。第二条挑三条不同类型的消息做人工比对。每条真实NMEA语句解析出的MMSI、船名、经纬度去AIS在线查询平台核对一下确认船名一致、位置落在合理海域。我见过解析器偏移表配错所有船的船名都是乱码但动态数据看着正常这种问题只能靠静态消息比对暴露。第三条看单船轨迹连续性。把同一MMSI的消息按时间排序检查两分钟内轨迹点之间是否出现超过一海里的跳变。如果出现大量跳点说明要么经纬度换算比例尺配错要么该船的AIS设备定位质量本来就差。前者改代码后者在业务侧标记数据可信度。验证通过之后剩下的就是围绕解析结果的工程优化了。AIS动态数据可以做轨迹插值和停留点识别静态数据可以按船舶类型、船长分组做港口船舶画像。这些都需要先有一套稳定、可复现的解析器作基础。我最早做AIS解析时就是跳过了校验和直接解payload结果被脏数据坑了一天。从那以后我每次拿到新数据源都强制先跑一遍校验和统计和无效值扫描再谈后续。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

从零手搓AI工程:RAG全链路实战与避坑指南 2026/10/2 16:52:09

从零手搓AI工程:RAG全链路实战与避坑指南

1. 从零手搓AI工程:为什么我不建议你直接调包很多人一听到“AI工程”这四个字,第一反应就是打开某个云平台,调一个现成的大模型接口,写几行胶水代码,然后对外宣称自己做了个AI应用。我承认,这条路确实能在半…

阅读更多 →
AI Skill查数据难?scripts、CLI、MCP三条通道帮你打通 2026/10/2 16:52:09

AI Skill查数据难?scripts、CLI、MCP三条通道帮你打通

1. Skill"查不了数据"的病根:知识进来了,管道没接上1.1 Skill到底是什么:它是说明书,不是执行器先回到最基本的问题。很多人从社区下载了一个AI Skill,比如"AI备课Skill"、"AI像素动画Skill&…

阅读更多 →
API密钥错误排查指南:OpenClaw 与 Claude 的 config.toml 配置骨架 2026/10/2 16:52:09

API密钥错误排查指南:OpenClaw 与 Claude 的 config.toml 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI每日资讯|AI落地|最新情报|skill精选|2026年07月28日(11案例+10爆款Skill)TaoToken 统一 Key 通道实测 2026/10/2 16:52:02

AI每日资讯|AI落地|最新情报|skill精选|2026年07月28日(11案例+10爆款Skill)TaoToken 统一 Key 通道实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
办公自动化新选择,OpenClaw 桌面智能体 Windows 实测记录:把 settings 改到 TaoToken 2026/10/2 16:52:02

办公自动化新选择,OpenClaw 桌面智能体 Windows 实测记录:把 settings 改到 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Claude Code 接入模型(deepseek、glm):把 settings 改到 TaoToken 的完整配置 2026/10/2 16:51:55

Claude Code 接入模型(deepseek、glm):把 settings 改到 TaoToken 的完整配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉