Files
planet/docs/plans/earth-vessel-tracking-plan.md
2026-04-30 16:56:37 +08:00

11 KiB
Raw Blame History

实时船只监控系统 — 实施计划

状态:规划中 创建日期2026-04-27 优先数据源BarentsWatch AIS免费但需要 OAuth client credentials→ AISHub / MarineTrafficTODO付费

已确认决策

项目 决策
数据源 BarentsWatch 先行AISHub / MarineTraffic TODO
船只规模 BarentsWatch 阶段全部显示;全球数据接入后按需加船型过滤(默认 Cargo + Tanker + Passenger
更新频率 准实时:前端 5 分钟轮询,后端 Collector 每分钟拉取写库
历史轨迹 保留(vessel_position 表保留 24h后期按需扩展
推送方式 前端展示仍可先用 HTTP 拉取聚合结果AISStream 等实时源应单独实现 WebSocket 采集器

一、技术背景

船只通过 AIS自动识别系统每 210 秒广播位置、航速、航向、目的地等信息。全球约 50 万艘持证船只在线,实时数据通过以下方式获取:

来源类型 典型服务 覆盖范围 成本 状态
BarentsWatch AIS API live.ais.barentswatch.no 挪威海域实时 免费,需要 AIS API client credentials 当前使用
AISHub aishub.net 全球实时 免费/小额 TODO付费接入
MarineTraffic API marinetraffic.com 全球实时 $50$500/月 TODO评估 tier
VesselFinder API vesselfinder.com 全球实时 $50$300/月 TODO备选
自建 SDR 接收 RTL-SDR + AIS-catcher 仅本地 3050km 硬件 $30 不考虑
NOAA 历史数据 Marine Cadastre 美国近海历史 免费 可用于冷启动

BarentsWatch AIS API

  • 端点:https://live.ais.barentswatch.no/v1/latest/combined
  • 需要在 BarentsWatch developer portal 创建 AIS - API client通过 client credentials 获取 scope=ais 的 access token 后请求 AIS endpoint
  • 字段mmsi, lat, lon, sog, cog, heading, nav_status, name, vessel_type, flag
  • 刷新频率:数据约 3060s 更新一次,可随意轮询

TODO多源 AIS 与实时流接入

  • 接入 AISStream WebSocket 采集器,作为 BarentsWatch 覆盖不足的实时补充
  • 将 BarentsWatch、AISStream、自定义 vessel_ais 映射源统一写入原始观测层
  • 通过聚合接口做去重、字段合并、冲突记录和默认来源选择
  • 开放字段级聚合策略配置,让用户决定不同字段优先信任哪个来源
  • 评估 AISHub 订阅(全球覆盖,约 $30/月),接入全球实时流
  • 评估 MarineTraffic API tier对比 AISHub 数据质量与成本
  • 实现多数据源适配器,通过 datasource_config 切换
  • 真实高频 AIS 稳定接入后,评估将 vessel_position 迁移为 TimescaleDB hypertable保留 Postgres 原生分区作为备选)

多源 AIS 的详细设计见 AIS 多源采集、冲突记录与聚合接口计划


二、实施计划

Phase 0 — 数据源验证与链路打通12 天)

  • 接入 BarentsWatch AIS API验证 OAuth token、数据格式与字段
  • 构建全球 mock 数据生成器(用于前端渲染压测,补充 BarentsWatch 的地域限制)
  • 确认前端可渲染船只点,整条链路走通

Phase 1 — 后端基础设施34 天)

1.1 数据库 Schema

-- 船只静态信息(每 6h 刷新一次)
CREATE TABLE vessel_static (
    mmsi          BIGINT PRIMARY KEY,
    name          VARCHAR(128),
    callsign      VARCHAR(16),
    vessel_type   SMALLINT,
    vessel_type_name VARCHAR(64),
    flag          VARCHAR(4),         -- ISO 国家码
    length        FLOAT,
    width         FLOAT,
    draught       FLOAT,
    imo           BIGINT,
    updated_at    TIMESTAMPTZ
);

-- 船只实时位置(高频写入,保留 24h 轨迹)
CREATE TABLE vessel_position (
    id            BIGSERIAL PRIMARY KEY,
    mmsi          BIGINT NOT NULL,
    lat           FLOAT NOT NULL,
    lon           FLOAT NOT NULL,
    sog           FLOAT,              -- Speed over ground
    cog           FLOAT,              -- Course over ground
    heading       SMALLINT,           -- 真北航向
    nav_status    SMALLINT,           -- 0=航行 1=锚泊 5=停靠 ...
    received_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_vessel_pos_mmsi_time ON vessel_position(mmsi, received_at DESC);
CREATE INDEX idx_vessel_pos_time      ON vessel_position(received_at DESC);

-- 最新位置物化视图(地图渲染主数据源,避免全表扫描)
CREATE MATERIALIZED VIEW vessel_latest AS
SELECT DISTINCT ON (mmsi)
    vp.*, vs.name, vs.vessel_type_name, vs.flag, vs.length
FROM vessel_position vp
LEFT JOIN vessel_static vs USING (mmsi)
ORDER BY mmsi, received_at DESC;

CREATE UNIQUE INDEX ON vessel_latest(mmsi);

后期如需完整历史轨迹查询,迁移 vessel_position 到 TimescaleDB 或按天分区。

1.2 CollectorVesselAISCollector

文件:backend/app/services/collectors/vessel_ais.py

  • 继承 BaseCollector,注册到 collector_registry
  • 轮询间隔3060s由数据源限速决定
  • 支持多数据源切换,通过 datasource_config 配置 URL + API Key
  • 写入逻辑upsert vessel_latestappend vessel_position
  • 接入现有调度系统(scheduler.py

1.3 API 端点

GET /api/v1/visualization/geo/vessels
    ?bbox=lon_min,lat_min,lon_max,lat_max   # 视口裁剪
    ?type=cargo,tanker,passenger             # 船型过滤
    ?limit=0                                 # 可选;不传或 0 表示不裁剪数量
→ GeoJSON FeatureCollectionPoint

GET /api/v1/visualization/vessels/{mmsi}           # 单船详情
GET /api/v1/visualization/vessels/{mmsi}/track     # 历史轨迹(默认 6h
    ?hours=6
→ GeoJSON LineString

GeoJSON Feature 格式:

{
  "type": "Feature",
  "geometry": { "type": "Point", "coordinates": [lon, lat] },
  "properties": {
    "mmsi": 123456789,
    "name": "EVER GIVEN",
    "vessel_type": 70,
    "vessel_type_name": "Cargo",
    "flag": "PA",
    "sog": 12.4,
    "cog": 247.0,
    "heading": 245,
    "nav_status": 0,
    "length": 400,
    "received_at": "2026-04-27T10:00:00Z"
  }
}

1.4 更新机制

前端聚合结果拉取 + 后端实时采集

  • 前端 setInterval(fetchVessels, 5 * 60 * 1000) 定期拉取最新快照
  • 后端 BarentsWatch collector 继续以 HTTP polling 方式采集
  • AISStream 等实时源以独立 WebSocket collector 写入原始观测层
  • 展示接口从聚合服务读取当前船只视图,而不是由单个 collector 决定最终展示值
  • 前端默认不再给 /geo/vesselslimit=5000VESSEL_CONFIG.maxRenderedMarkers = 0 表示不做前端数量裁剪;后续如性能不足再引入显式 LOD 上限
  • marker 颜色、详情卡、hover 和搜索结果必须共享 vessel_type_display 船型归一化结果,避免 AIS 数字类型码已驱动颜色但卡片仍显示 Other
  • 前端是否升级为 WebSocket delta push 是独立优化,不影响后端采集器可以使用 WebSocket 接上游实时源

Phase 2 — 前端渲染34 天)

文件:frontend/public/earth/js/vessels.js

2.1 渲染方案

参考现有卫星系统(satellites.js)的 InstancedMesh 模式:

  • THREE.InstancedMesh:每个实例 = 一艘船,矩阵包含位置 + 旋转(朝向 COG
  • 行进船:三角箭头图标,朝向 COG 方向
  • 静止/锚泊船:圆点图标
  • SVG 图标输出到 frontend/public/earth/assets/icons/vessel-arrow.svgvessel-dot.svg

2.2 船型颜色规范

船型 颜色
货轮 Cargo #4A90D9
油轮 Tanker #E85D04 橙红
客船 Passenger #06D6A0 绿
渔船 Fishing #FFD166
军舰 Military #73797E
其他 #9B9B9B 浅灰
锚泊/停靠 降低饱和度 0.4x

2.3 LOD相机距离细节层次

相机距离 渲染策略
> 400 默认渲染当前接口返回的全部船只;如性能不足,再引入可配置 LOD 上限
200400 默认渲染当前接口返回的全部船只;如性能不足,再引入可配置 LOD 上限
< 200 渲染当前视口 bbox 内全部船只

前端根据相机位置动态计算 bbox附加到 API 请求中。

2.4 图层集成

接入现有图层系统,新增"船只"图层项,支持:

  • 图层开/关,状态持久化
  • 子过滤(按船型选择显示哪类,可在图例或设置面板中配置)
  • 与海缆、BGP、卫星层级共存renderOrder 待定,参考现有层级文档)

2.5 Info Card

复用 showInfoCard 机制,点击船只弹出:

EVER GIVEN                              🚢
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MMSI        123456789
IMO         9811000
旗帜        巴拿马 🇵🇦
船型        散货轮
当前航速     12.4 kn
航向        247°
状态        航行中
目的地      ROTTERDAM
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[ 查看轨迹 ]   [ MarineTraffic ↗ ]

2.6 轨迹可视化

点击"查看轨迹" → 请求 /vessels/{mmsi}/track → 用 THREE.CatmullRomCurve3 渲染插值轨迹线,风格与海缆一致。


Phase 3 — 功能完善23 天)

功能 说明
船只搜索 接入现有搜索面板,按名称 / MMSI 搜索
统计 HUD 显示当前在线船只数、各类型分布
密度热图 超低 zoom 时切换为 hex-bin 热力图(避免点云爆炸)
港口标注 加载 WorldPorts 数据集,显示主要港口标记
关键水道监控 马六甲、霍尔木兹、苏伊士等高亮 + 流量统计

Phase 4 — 性能与生产化23 天)

  • vessel_position 按天分区7 天自动清理
  • TODO真实数据量达到百万级/日后,将 vessel_position 升级为 TimescaleDB hypertable配置 retention policy 与压缩策略
  • GeoJSON endpoint 用 Redis 缓存 15s
  • 若需 bbox 精确查询,引入 PostGIS geography + ST_DWithin
  • InstancedMesh + frustum culling目标 5 万船只 60fps

三、工作量估算

Phase 内容 估计时间
Phase 0 数据源验证、mock 12 天
Phase 1 后端 Schema + Collector + API 34 天
Phase 2 前端渲染InstancedMesh + 图层 + Info Card 34 天
Phase 3 搜索 + 统计 + 轨迹 23 天
Phase 4 性能优化 + 生产数据源接入 23 天
合计 约 23 周

四、参考资料