做海事数据这行的朋友,十有八九绕不开AIS。我最早接触船舶自动识别系统数据,是在一个港口物流可视化项目里,客户拿着一堆从各种渠道截下来的船位图问我:能不能自己拉一份全球AIS实时及历史数据,自己建库、自己出图、自己算指标。当时我第一反应是这东西公开广播、频段固定,应该不难,结果真正动手才发现,从天线架设到报文解码、从实时推送到历史回溯,中间每一环都有坑。船舶自动识别系统本质上是一套船舶之间、船岸之间自动交换航行信息的协作系统,它把船位、航向、航速、船名、呼号、目的港这些字段通过甚高频广播出去,谁收到谁就能解析。对航运监控、港口调度、渔船管理、海事研究、物流跟踪这些场景来说,AIS数据几乎是唯一能低成本拿到全球船舶动态的来源。这篇文章我把整套链路拆开讲——数据从哪来、怎么收、怎么解、怎么存、怎么查、怎么用,适合刚入门数据采集的工程师,也适合已经在做航运产品、想把数据链路吃透的从业者。文中涉及的具体参数、容量估算和排查方法,一部分来自我自己踩坑后的实测记录,一部分是基于公开协议规范整理的常见做法,你可以按自己项目规模做取舍。
1. 船舶自动识别系统到底在广播什么——信号到数据的完整链路
1.1 物理层与信道机制,决定了数据长什么样
AIS工作在甚高频海事频段,具体是两个专用信道:AIS 1 为161.975 MHz,AIS 2 为162.025 MHz,采用GMSK调制,码率9600 bps。这个数字很关键,因为它直接决定了AIS的"带宽天花板"——9600 bps共享给覆盖范围内所有船舶,所以协议必须设计得非常节省。它用的是TDMA时分多址,把一分钟切成2250个时隙,每条船占用其中一个或几个时隙来广播自己的报文。为什么这么设计?因为海上没有基站协调中心,如果大家同时发就会互相撞车,TDMA让每台设备根据自己从GPS拿到的时间自动对齐时隙,实现自组织广播。
这个机制直接带来三个后果,做数据的人必须知道。第一,AIS是"广播"不是"上报",没有任何确认重传机制,你收不到就是收不到,数据天然有缺失。第二,覆盖范围取决于接收天线高度和被遮挡情况,岸基站在理想海况下能覆盖约20到40海里,实际往往更短。第三,同一时刻同一海域船越多,时隙越紧张,报文广播间隔会自动拉长,高密度航道的数据密度反而可能下降。理解这三点,后面遇到"某段轨迹突然断了几分钟"就不会慌,那是协议特性,不是你的程序写错了。
1.2 报文类型与核心字段,别只盯着经纬度
很多人拿到AIS数据第一反应是只要经纬度,实际上AIS报文类型有二十多种,常用的至少七八种。位置报告主要是类型1、2、3(Class A船舶),以及类型18、19(Class B船舶和小型船)。静态和航次信息是类型5(Class A)和类型24(Class B),里面才有船名、呼号、IMO号、船舶类型、船长船宽、吃水、目的港、预计到港时间。助航设备用类型21,基站信息用类型4,长距离用类型27。
为什么一定要把静态报文也解析出来?因为位置报文里只有一个MMSI号,光看一串数字根本不知道是哪条船。MMSI是海上移动通信业务标识,前三位是MID国家码,能看出船籍。只有把类型5和类型1按MMSI关联起来,你才能从"某个坐标点在动"变成"某某轮正从某港驶向某港"。我见过不少项目只存了位置报文,后期想按船名查历史就得回炉重跑,非常痛苦。所以建库第一天就把静态表和动态表分开设计,用MMSI做关联键,这是经验之谈。
1.3 为什么值得单独搭一套AIS采集体系
市面上确实有现成的AIS聚合平台,但自己搭的理由也很实在。第一是数据主权,历史数据一旦依赖第三方接口,对方改限流、改字段、涨价,你的业务就得跟着抖。第二是自定义解析,比如你想做渔船作业行为识别,需要高频采样和自定义的航速阈值,公共接口往往做了降采样和脱敏。第三是成本,如果只是监控一个港口、一条航线,自建一套接收站加本地存储,长期摊下来比按调用量付费划算得多。
反过来说,如果你要全球覆盖,自建岸基站根本不现实——海洋面积太大,岸基只能覆盖近岸。这时候就得靠卫星AIS或者聚合数据源。所以选型的第一步不是技术,是明确你的覆盖范围和服务对象:近岸监控优先自建,全球追踪优先聚合,两者还能混合使用,近岸用自建高频数据,远洋用聚合数据补全,这是我做过最顺的一种组合。
2. 数据源选型:岸基、星基与聚合平台怎么挑
2.1 岸基接收站,成本与覆盖要先算清楚
自建岸基站的核心硬件其实不贵:一台支持AIS频段的接收机(常见的是基于软件无线电的方案)、一根谐振在162 MHz附近的VHF天线、馈线、一台常年开机的小主机。真正贵的是站点位置——天线架得越高越好,最好在海边、山顶或者高层楼顶,馈线损耗要控制在可接受范围,超过二三十米就得考虑加低噪声放大器。我实测过,同样一台接收机,天线从三楼阳台挪到楼顶,有效覆盖半径从七八海里提升到二十多海里,差距非常明显。
覆盖怎么估算?有个粗略的经验公式,视距约等于4.12乘以天线高度的平方根(单位:海里,高度单位米)。接收天线架在30米高处,视距约22海里;对方船舶天线假设20米高,再叠加约18海里,理论上限四五十海里,实际因为海况、船体遮挡、大气波导,能稳定收到二三十海里就不错了。所以别拿理论值做承诺。站点选好后,长期稳定性比覆盖更重要——供电、网络、防雷、防水,任何一项出问题都会导致数据断档,我建议至少配UPS和不间断网络,别问我是怎么知道的。
2.2 星基AIS与聚合数据源,覆盖广但不便宜
卫星AIS把接收机放到低轨卫星上,能覆盖远洋,但有个天然缺陷:卫星过顶时覆盖范围极大,同一时刻收到成千上万条船的信号,时隙冲突严重,导致单星数据完整度不高。所以星基AIS通常靠多星、多轨道、多时段拼接来补全,数据延迟也更高,从几分钟到几十分钟不等。如果你做的是远洋船期预测、大宗商品航运跟踪这类对延迟不敏感的场景,星基数据完全够用;但要做近岸避碰、实时调度,就必须用岸基或近岸聚合源。
聚合数据源本质上就是把各家岸基站和卫星数据汇聚后,通过API或数据流卖给你。选聚合源时我会重点看几个指标:单船日均报文条数(反映采样密度)、字段完整度(目标港、吃水这些静态字段有没有)、历史可回溯时长、接口的稳定性和限流策略。别只看价格,有的便宜源把降采样做到几分钟一条,做轨迹分析根本不够用。这里可以类比一下做行情数据推送的思路——就像股票行情接口要区分Level-1和Level-2,AIS数据源也有"快照级"和"逐笔级"的差别,你得根据下游需求选,而不是一律要最全的。
2.3 免费与付费数据源的真实差异
免费AIS数据主要来自几个渠道:公开的科研项目、爱好者社区共享的接收站网络、部分平台开放的有限查询。它们的共同问题是覆盖不稳定、字段不全、没有服务保证,今天能用的接口明天可能就挂了。我早期做过一个纯免费方案,靠社区站点的API拼数据,结果高峰时段丢包严重,而且不同站点的坐标系和时间基准还不统一,清洗成本比省下的钱还高。
付费源的优势在于稳定性和一致的数据规范,字段定义、时间戳时区、坐标精度都有明确说明,接入时省掉大量对账工作。但付费不等于完美,我建议接入前先做小范围对照测试:同一片海域、同一时间段,拿你的自建数据和付费数据做交叉比对,看看谁丢得多、谁延迟高。这个测试花不了多少时间,却能让你对数据质量心里有数,后面出问题也知道该怀疑哪一环。记住,没有哪一路数据是绝对可靠的,做AIS系统一定要有"多源融合"的思维。
3. 实时数据链路的落地实现
3.1 硬件侧:接收机与天线怎么配
接收机选型上,常见路线是专用AIS接收机(输出标准NMEA 0183语句,插上就能用)和软件无线电方案(灵活但需要自己跑解调程序)。前者省心,后者灵活,我个人倾向近岸固定站用专用机,做实验或移动接收用软件无线电。天线必须是VHF海事频段专用的,别拿普通对讲机天线凑合,驻波比不对接收灵敏度会掉一大截。馈线用低损耗同轴电缆,接头做好防水,海上盐雾腐蚀很猛,一个没封好的接头两三个月就能氧化到接触不良。
连接方式上,专用接收机一般通过串口或网口输出。串口要注意波特率和数据位设置,常见是38400或4800,设错了就是一堆乱码。网口的话很多设备支持TCP服务端模式,你直接连上去读流就行。我在站点上会额外接一个看门狗和远程重启模块,一旦接收程序卡死或者串口无数据超过设定时间,自动重启服务并记录日志。这套小机制让站点的可用性从"三天两头要人去现场"变成"几个月不用管",强烈建议加上。
3.2 解码层:NMEA报文解析与校验不能省
AIS接收机输出的原始数据是NMEA 0183格式的语句,长这样:!AIVDM,1,1,,A,15M67FC000G?ufbE\FepT@3n00Sa,0*5C`。字段含义依次是:语句类型(AIVDM表示收到的他船报文,AIVDO表示本船)、分片总数、当前分片序号、连续报文序号、信道、载荷、填充位数、校验和。载荷是6位ASCII编码的,需要先做6位解码(每个字符值减48,大于40的再减8),再按报文类型对应的位偏移去取字段。
校验和一定要验,格式是*后面的两位十六进制,计算方法是从!后第一位到*前一位所有字符做异或。网上能搜到现成的实现,自己写也就几行:
def nmea_checksum(sentence: str) -> str: body = sentence.split('*')[0].lstrip('!') cs = 0 for ch in body: cs ^= ord(ch) return f"{cs:02X}"为什么不建议跳过校验?因为海上电磁环境复杂,接收机偶尔会吐出被干扰的语句,如果直接解析,可能得到经纬度瞬间跳到几千公里外的"幽灵船",污染你的轨迹库。我习惯把校验失败的报文单独存到一个错误表里,定期统计错误率,错误率突然升高往往意味着天线、馈线或者附近有强干扰源。
为了少造轮子,Python里可以直接用pyais来解码:
from pyais import decode raw = b"!AIVDM,1,1,,A,15M67FC000G?ufbE`FepT@3n00Sa,0*5C" msg = decode(raw) print(msg.asdict())但要注意,多分片报文(一条完整信息被拆成多条NMEA语句发送)需要先拼装再解码,直接单条解会报错。类型5这种长报文经常分两片,务必实现分片缓存和超时清理,否则内存会慢慢涨上去。这类细节文档里往往一笔带过,实际却是最常见的坑。
3.3 推送层:从消息队列到前端订阅
实时链路的设计核心是"解耦"。接收程序只负责收和解,解出来的结构化数据丢进消息队列(Redis Stream、Kafka、NATS都可以,小规模用Redis最省事),下游的入库程序、告警程序、推送程序各自订阅,互不影响。这样做的好处是任何一环挂了都不会拖垮整条链,重启后从队列里接着消费就行。
前端要实时看船位,通常走WebSocket推送。这里可以借鉴做实时行情推送的经验——都是"订阅+增量推送"模式,服务端维护每个客户端的订阅范围(比如某个矩形海域),新报文进来后只推给覆盖该区域的订阅者。千万别做成前端每隔几秒轮询全量接口,船一多接口直接被打爆。我一般会在推送层加一个合并窗口,把同一船500毫秒内的多条报文合并成一条再推,既降低前端压力,轨迹看起来也更顺。实测下来,这个窗口设300到1000毫秒比较舒服,太短没效果,太长动画会一顿一顿的。
4. 历史数据存储与回溯查询怎么设计
4.1 存储方案选型与容量估算
先算容量,这是决定方案的关键。假设你监控一万条船,平均每条船每10秒一条位置报文,一天就是8640条/船,一万船约8640万条记录/天。每条记录若存时间戳、MMSI、经纬度、航速、航向、船艏向、导航状态等字段,压缩前的行开销按200字节算,一天约17 GB,一年就是6 TB出头。这个量级用单机MySQL会很吃力,必须上专门的方案。
我的推荐是PostgreSQL加PostGIS做空间索引,再配合TimescaleDB这类时序扩展做自动分区和压缩;如果写入量更大,可以上ClickHouse,列式存储在"按时间范围扫"的查询上优势明显。选型逻辑是:查询模式决定存储形态。如果你查得最多的是"某船某段时间轨迹"和"某区域某时刻有哪些船",那就是典型的时间+空间组合查询,带空间索引的关系库或时序库最合适。纯列存虽然聚合快,但单船轨迹查询和更新操作体验一般。
4.2 轨迹重建与数据清洗
原始AIS数据不能直接用,必须清洗。常见脏数据有三类:一是位置跳变,两条相邻报文间算出的速度远超物理可能(比如一分钟移动50海里),这类点要剔除或标记;二是MMSI异常,有些设备配置错误或故意冒用,同一MMSI出现两条相距极远的轨迹,需要按距离聚类拆开;三是时间戳问题,多源数据时区不统一、设备时钟漂移都会导致排序错乱。
轨迹重建还有个现实问题:数据点不均匀。近岸密集、远洋稀疏,高速航段间隔短、锚泊时几分钟一条。做轨迹展示时如果直接连点,锚泊的船会画出一团乱麻。我的做法是按状态分段:锚泊状态的点做质心聚合,航行状态的点用插值补齐到等时间间隔,转弯段保留原始点以保证形状准确。插值方法上线性插值够用,对精度要求高的场景可以用三次样条或者结合航向做圆弧插值,但别过度,AIS本身的定位精度也就十米级,插得太花反而失真。
4.3 历史查询的时间窗口优化
历史数据查询最怕全表扫描。优化思路有三条。第一,按时间分区,比如按月或按周建分区表,查询时带上时间范围让优化器自动裁剪分区。第二,给MMSI和时间戳建联合索引,覆盖"某船某时段"这个最高频的查询模式。第三,给经纬度建空间索引,处理"某区域某时段"的区域查询。
对于超大规模的历史回溯,还有个技巧是预聚合。比如你要展示某条航线过去一年的船流热力图,每次实时算会非常慢,可以离线把数据按网格和时间桶聚合好,查询时直接读结果表。这就跟做长期统计报表是一个思路——原始明细留一份,聚合结果留一份,各司其职。我特别想强调"稳定工作4年"这件事,一个AIS存储系统真正的考验不是上线那天,而是长期运行后数据膨胀、索引失效、分区越来越多时还能不能秒级响应。所以分区策略、归档策略、监控告警必须在第一天就设计好,别等数据涨到TB级再回头改架构,代价极大。
5. 典型应用场景与落地案例拆解
5.1 港口调度与船期预测
港口最关心的是"船什么时候到、什么时候靠泊、码头资源怎么排"。用AIS历史数据能算出船舶的实际航速分布、锚地等待时长、进港航道通过时间,这些统计比船公司报的ETA靠谱得多。做法是:先划出港口周边的锚地和航道多边形区域,用位置报文判断船舶处于"在航、锚泊、靠泊"哪个状态,再统计各阶段的持续时间。我做过一个统计,某港口散货船的平均锚泊等待时间比官方口径高出不少,客户拿这个数据去和船代谈,议价空间一下就出来了。
船期预测可以基于历史航段的平均速度加实时速度做回归,简单模型就能给出不错的结果。别一上来就上深度学习,AIS数据的规律性其实很强,同一条航线、同一种船型,速度曲线高度相似,用历史均值加实时修正,误差往往就在几小时以内,完全够调度用。
5.2 异常行为识别
AIS的一大价值是发现"不正常"。常见的异常包括:长时间关闭AIS(轨迹凭空断掉)、异常徘徊(渔船在大范围海域反复打转)、身份不符(船名和MMSI登记信息对不上)、吃水突变(可能涉及装卸或过驳)。识别方法基本是"建基线+比偏差":先统计某类船在某海域的正常行为分布,再用实时数据去比对。
拿徘徊识别举例,可以计算一段时间内的航向变化累积量和位移距离之比,比值高说明在原地反复转,比值低说明在直线航行。这个指标简单但有效。要注意的是,正常作业的渔船、施工船、拖轮也会徘徊,所以必须结合船舶类型过滤,否则误报一大堆。经验是把静态报文里的船舶类型字段用起来,别只用位置数据,这是很多新手容易忽略的点。
5.3 数据可视化与分析产出
最后落到展示。船位图、轨迹回放、热力图、流量统计是四类最常见的产出。技术上,Web端地图用支持大量点渲染的库,别用几千个marker硬堆,会卡死;轨迹回放用时间轴控件配增量渲染;热力图用网格聚合。如果要做时间维度的动画回放,提前把轨迹数据重采样成等间隔并做好索引,播放时按时间片拉取,体验会顺很多。
我还建议把AIS数据和其他数据打通,比如关联港口作业数据、天气数据、货物流向数据,才能产生真正有价值的分析。单独看AIS,它就是一串坐标;结合业务上下文,它才能回答"为什么这条船绕行了""为什么这个港口拥堵了"这类问题。数据本身不产生价值,被用起来才产生价值。
6. 常见问题与排查技巧实录
6.1 数据质量问题速查表
下面这张表是我这些年遇到问题后整理的速查清单,出问题时对着排查能省不少时间:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 收到大量乱码 | 串口波特率或数据位设错 | 核对接收机参数,常见38400/4800 |
| 校验频繁失败 | 天线驻波比差、馈线进水、强干扰 | 检查天线和接头,测驻波比 |
| 轨迹大段缺失 | 超出覆盖范围、时隙冲突 | 对照周边站点数据,判断是覆盖还是协议 |
| 同一MMSI两条轨迹 | MMSI被冒用或设备配置错误 | 按空间距离聚类拆分 |
| 位置瞬间跳变 | 干扰或定位漂移 | 用速度阈值剔除异常点 |
| 静态信息缺失 | 未收到类型5或类型24报文 | 检查分片拼装逻辑 |
| 时间戳混乱 | 多源时区不统一、设备时钟漂移 | 统一转UTC并做时钟校正 |
6.2 接收端与网络端故障排查
接收端最常见的问题是"程序在跑但没数据"。排查顺序是:先看串口/网口有没有原始字节流,没有就是硬件或连接问题;有字节流但解不出结构化数据,就是解析或分片逻辑问题;能解出但入库为空,就是消费或数据库问题。这样一层层切,很快能定位。我习惯在每一层都打心跳日志,记录单位时间收到的报文数,一旦某个站点的报文数突然掉到零或异常升高,告警立刻触发。
网络端要防的是"半死不活"的连接——TCP连接还在,但对端已经不推数据了。解决办法是应用层加心跳,一段时间没收到数据就主动断开重连。别指望TCP自己发现,某些网络设备下连接会僵很久。这个小设置能救很多"看起来正常其实已经断了好几天"的事故。
6.3 几个我踩过的坑和实操心得
第一个坑是内存泄漏,根因就是前面说的多分片报文缓存没清理,某条报文的后续分片永远不来,缓存就一直在。加上超时淘汰后问题解决。第二个坑是数据库写入瓶颈,早期用逐条insert,数据一多直接卡死,改成批量写入加连接池后吞吐提升一个数量级。第三个坑是坐标精度,某些数据源给的经纬度是度分格式而非十进制度,直接用会偏到十万八千里,接入新源时一定先拿几条已知位置的船验证。
最后分享一个实用习惯:给整个系统建一个"数据健康度"看板,核心指标包括各站点在线率、报文接收速率、校验失败率、入库延迟、单船日均报文数。这几个数字平时看着平淡,一旦有异常,它们是最早报警的。做AIS这种长期运行的系统,预防永远比救火划算。