4. AIS航迹数据采集任务设计

AIS航迹数据采集并不是“打开接收机、把报文存下来”这么简单。同一片水域,采集目标不同,最终得到的数据集可能完全不同:做船舶交通流分析,需要覆盖整个航道、以分钟级间隔记录;做单船行为识别,则要锁定目标MMSI、尽量不丢报文;做长期港口吞吐统计,则更关心存储成本和查询效率。因此,采集任务设计的第一步,是把“要回答什么问题”翻译成可执行的采集参数。

一、采集目标与范围定义

采集目标应写成可验证的句子,而不是笼统的“收集AIS数据”。例如:“采集2024年3月至5月长江口AIS基站覆盖范围内所有Class A船舶的动态报文,用于计算航道断面交通量与船速分布”。这句话已经隐含了四个约束:时间范围、空间范围、船舶类型、报文类型。

空间范围通常用两种方式界定:一是按接收站覆盖半径,二是按地理围栏(多边形)。前者实现简单,但边界模糊,远端船舶可能时断时续;后者边界清晰,适合航道、锚地、港池等特定区域分析。实际工程中建议两者结合:先以基站覆盖确定原始数据池,再用多边形围栏做二次过滤。时间范围要明确起止时刻和时区(AIS报文时间戳通常为UTC,分析时需统一换算)。船舶类型则通过报文中的船型代码(Ship Type)筛选,例如30表示渔船、70~79表示货船、80~89表示油船。

范围定义时建议同时记录“排除规则”,例如排除MMSI小于100000000的基站/助航设备报文、排除船速为0且连续多日的锚泊船,避免后续清洗阶段反复返工。

二、采样频率与时空分辨率权衡

AIS的发射间隔由船舶运动状态决定,并非固定值。Class A船舶在航行时通常每2~10秒发送一次位置报告,锚泊时约3分钟一次;Class B船舶间隔更长。这意味着“采样频率”实际上不是我们能随意设定的,而是由目标船舶自身行为决定的。我们能设计的是接收端的记录策略和后续的重采样规则。

时空分辨率需要权衡三方面:分析精度、数据量和存储成本。以一条船每天航行8小时、平均报文间隔10秒估算,单船单日约产生2880条位置记录;若监测500艘船、持续90天,原始记录量约1.3亿条。若把记录间隔放宽到30秒,数据量降至约4300万条,但会丢失加减速、转向等细节。对于交通流统计,30秒甚至60秒的重采样通常足够;对于碰撞风险、避让行为分析,则必须保留原始间隔。

一个实用的做法是“原始层+应用层”双分辨率:原始报文按接收到的真实时间全部落盘,应用层再按分析需求生成1分钟、5分钟等重采样轨迹。重采样时建议采用“时间最近邻”或线性插值,并记录插值标志,避免把插值点误当作实测点。

AIS采集任务设计:从目标到存储的分层关系 采集目标 交通流 / 单船行为 港口统计 / 风险分析 范围定义 时间窗 + 地理围栏 船型代码 + 报文类型 时空分辨率 原始间隔 vs 重采样 精度 / 数据量权衡 存储选型 CSV / Parquet 数据库 采集脚本框架与异常处理链路 连接/订阅 TCP/UDP 接收 报文解析 NMEA 解码 过滤/校验 范围+MMSI+CRC 缓冲写入 批量落盘 异常:断连重试 / 坏报文丢弃 / 写失败转本地队列

三、数据存储格式选型

CSV、Parquet和数据库各有适用场景,关键看数据规模、查询方式和后续分析工具链。

格式优势局限适用场景
CSV通用、可直接用Excel/文本工具查看体积大、无类型、查询慢小批量调试、交付给非技术方
Parquet列式压缩、按列查询快、体积约为CSV的1/5~1/10需专用库读取、不适合频繁单条更新中大规模离线分析、按时间/区域扫描
数据库(如PostgreSQL+PostGIS)支持空间索引、并发写入、增量更新运维成本高、大批量导入需调优在线查询、多用户共享、空间检索

实践中常见的组合是:采集端先写本地Parquet或按天分区的CSV作为原始层,再批量导入数据库供查询。若数据量在千万条以内,单机Parquet配合DuckDB即可完成大部分分析;若需要按多边形围栏实时检索,则PostGIS的空间索引更有优势。无论选哪种格式,都应保留原始报文时间戳、接收站标识和解析状态字段,便于追溯。

四、采集脚本框架与异常处理

一个可用的采集脚本通常包含四个环节:连接与订阅、报文解析、过滤校验、缓冲写入。以Python为例,接收端通过TCP连接AIS接收机或基站服务,持续读取NMEA语句;解析环节将!AIVDM语句解码为MMSI、经纬度、对地航速、航向、时间戳等字段;过滤环节按预设的MMSI白名单、地理围栏和报文类型做筛选;写入环节采用批量缓冲,例如每500条或每5秒落盘一次,避免频繁小文件写入拖慢磁盘。

# 采集脚本核心结构(示例框架,非实测代码)
buffer = []
while running:
    try:
        line = reader.readline()          # 读取NMEA语句
        msg = decode_ais(line)            # 解析为结构化字段
        if not in_scope(msg):             # 范围/船型过滤
            continue
        buffer.append(msg)
        if len(buffer) >= 500:
            writer.write(buffer)          # 批量写入Parquet/CSV
            buffer.clear()
    except DecodeError:
        bad_count += 1                    # 坏报文计数,不中断
    except ConnectionError:
        reconnect_with_backoff()          # 指数退避重连
    except IOError:
        spill_to_local_queue(buffer)      # 写失败转本地队列

异常处理要区分“可恢复”和“不可恢复”。坏报文、单条解析失败属于可恢复,记录计数后跳过即可;连接中断属于可恢复但需退避重连,避免频繁重试压垮接收端;磁盘写满或权限错误则属于需要告警的严重异常,应把缓冲区数据转存到本地临时队列,待恢复后补写。建议脚本运行时输出心跳日志,记录每分钟接收报文数、坏报文比例和写入延迟,这三个指标能快速反映采集链路是否健康。

采集任务设计完成后,建议先用1~2小时的小样本试运行,核对报文数量、时间戳连续性和字段完整性,再扩展到正式采集周期。直接长时间运行而未验证,往往在数天后才发现时间戳错位或围栏配置错误,返工成本很高。

把目标、范围、分辨率、存储和异常处理这五件事在设计阶段定清楚,采集脚本才只是“按图施工”。后续的数据清洗、轨迹重建和分析,都建立在这一次设计是否扎实的基础上。