# 实时船只监控系统 — 实施计划 **状态**:历史计划;实时 AIS 与聚合接口已由 [AIS 多源采集、冲突记录与聚合接口计划](/home/ray/dev/linkong/planet/docs/plans/earth-vessel-ais-aggregation-plan.md) 接管 **更新**:`0.70.0` 起 Earth 船只展示使用 `vessel_current_state` 全球当前状态快照;本文中按当前视口 bbox 驱动显示的 LOD 设想仅作为历史记录。 **创建日期**:2026-04-27 **优先数据源**:BarentsWatch AIS(免费但需要 OAuth client credentials)→ AISStream realtime;AISHub / MarineTraffic 保留为付费备选 ## 已确认决策 | 项目 | 决策 | |-----|------| | 数据源 | BarentsWatch 先行;AISStream realtime 已成为全球实时补充;AISHub / MarineTraffic 保留为付费备选 | | 船只规模 | BarentsWatch 阶段全部显示;全球数据接入后按需加船型过滤(默认 Cargo + Tanker + Passenger) | | 更新频率 | 准实时:前端 5 分钟轮询,后端 Collector 每分钟拉取写库 | | 历史轨迹 | 保留(`vessel_position` 表保留 24h,后期按需扩展) | | 推送方式 | 前端展示仍可先用 HTTP 拉取聚合结果;AISStream 等实时源应单独实现 WebSocket 采集器 | --- ## 一、技术背景 船只通过 AIS(自动识别系统)每 2–10 秒广播位置、航速、航向、目的地等信息。全球约 50 万艘持证船只在线,实时数据通过以下方式获取: | 来源类型 | 典型服务 | 覆盖范围 | 成本 | 状态 | |---------|---------|---------|------|------| | **BarentsWatch AIS API** | live.ais.barentswatch.no | 挪威海域实时 | 免费,需要 AIS API client credentials | **当前使用** | | **AISHub** | aishub.net | 全球实时 | 免费/小额 | 付费备选 | | **MarineTraffic API** | marinetraffic.com | 全球实时 | $50–$500/月 | 待评估 tier | | **VesselFinder API** | vesselfinder.com | 全球实时 | $50–$300/月 | 备选 | | **自建 SDR 接收** | RTL-SDR + AIS-catcher | 仅本地 30–50km | 硬件 $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 - 刷新频率:数据约 30–60s 更新一次,可随意轮询 ### 多源 AIS 与实时流接入历史 AISStream WebSocket collector、`/api/v1/vessels/snapshot` 和 `/ws` vessels channel 已在后续计划中落地。仍有价值的后续项集中维护在根目录 [TODO](/home/ray/dev/linkong/planet/TODO.md) 的 AIS / Vessels 小节。 多源 AIS 的详细设计见 [AIS 多源采集、冲突记录与聚合接口计划](/home/ray/dev/linkong/planet/docs/plans/earth-vessel-ais-aggregation-plan.md)。 --- ## 二、实施计划 ### Phase 0 — 数据源验证与链路打通(1–2 天) - 接入 BarentsWatch AIS API,验证 OAuth token、数据格式与字段 - 构建全球 mock 数据生成器(用于前端渲染压测,补充 BarentsWatch 的地域限制) - 确认前端可渲染船只点,整条链路走通 ### Phase 1 — 后端基础设施(3–4 天) #### 1.1 数据库 Schema ```sql -- 船只静态信息(每 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 Collector:VesselAISCollector 文件:`backend/app/services/collectors/vessel_ais.py` - 继承 `BaseCollector`,注册到 `collector_registry` - 轮询间隔:30–60s(由数据源限速决定) - 支持多数据源切换,通过 `datasource_config` 配置 URL + API Key - 写入逻辑:upsert `vessel_latest`,append `vessel_position` - 接入现有调度系统(`scheduler.py`) #### 1.3 API 端点 ```http GET /api/v1/vessels/snapshot ?bbox=lon_min,lat_min,lon_max,lat_max # 必填,视口裁剪 ?zoom=12 # 必填,当前缩放 ?type=cargo,tanker,passenger # 船型过滤 ?limit=1000 # 默认 1000,最大 5000 → GeoJSON FeatureCollection(Point) GET /api/v1/visualization/vessels/{mmsi} # 单船详情 GET /api/v1/visualization/vessels/{mmsi}/track # 历史轨迹(默认 6h) ?hours=6 → GeoJSON LineString ``` GeoJSON Feature 格式: ```json { "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 决定最终展示值 - 旧 `/api/v1/visualization/geo/vessels` 路由已移除,前端必须使用受控 snapshot 接口。 - marker 颜色、详情卡、hover 和搜索结果必须共享 `vessel_type_display` 船型归一化结果,避免 AIS 数字类型码已驱动颜色但卡片仍显示 `Other` - 前端是否升级为 WebSocket delta push 是独立优化,不影响后端采集器可以使用 WebSocket 接上游实时源 --- ### Phase 2 — 前端渲染(3–4 天) 文件:`frontend/public/earth/js/vessels.js` #### 2.1 渲染方案 参考现有卫星系统(`satellites.js`)的 InstancedMesh 模式: - `THREE.InstancedMesh`:每个实例 = 一艘船,矩阵包含位置 + 旋转(朝向 COG) - 行进船:三角箭头图标,朝向 COG 方向 - 静止/锚泊船:圆点图标 - SVG 图标输出到 `frontend/public/earth/assets/icons/vessel-arrow.svg` 和 `vessel-dot.svg` #### 2.2 船型颜色规范 | 船型 | 颜色 | |-----|------| | 货轮 Cargo | `#4A90D9` 蓝 | | 油轮 Tanker | `#E85D04` 橙红 | | 客船 Passenger | `#06D6A0` 绿 | | 渔船 Fishing | `#FFD166` 黄 | | 军舰 Military | `#73797E` 灰 | | 其他 | `#9B9B9B` 浅灰 | | 锚泊/停靠 | 降低饱和度 0.4x | #### 2.3 LOD(相机距离细节层次) | 相机距离 | 渲染策略 | |---------|---------| | > 400 | 默认渲染当前接口返回的全部船只;如性能不足,再引入可配置 LOD 上限 | | 200–400 | 默认渲染当前接口返回的全部船只;如性能不足,再引入可配置 LOD 上限 | | < 200 | 历史设想:渲染当前视口 bbox 内全部船只;当前实现仍使用全球当前状态快照 | 该视口驱动方案已撤回。当前前端使用全球 bbox 请求 `/api/v1/vessels/snapshot`,旋转和缩放不触发船只重拉。 #### 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 — 功能完善(2–3 天) | 功能 | 说明 | |-----|------| | **船只搜索** | 接入现有搜索面板,按名称 / MMSI 搜索 | | **统计 HUD** | 显示当前在线船只数、各类型分布 | | **密度热图** | 超低 zoom 时切换为 hex-bin 热力图(避免点云爆炸) | | **港口标注** | 加载 WorldPorts 数据集,显示主要港口标记 | | **关键水道监控** | 马六甲、霍尔木兹、苏伊士等高亮 + 流量统计 | --- ### Phase 4 — 性能与生产化(2–3 天) - `vessel_position` 按天分区,7 天自动清理 - 真实数据量达到百万级/日后,再评估是否将船只时序数据升级为 TimescaleDB hypertable,并配置 retention policy 与压缩策略 - GeoJSON endpoint 用 Redis 缓存 15s - 若需 bbox 精确查询,引入 PostGIS `geography` + `ST_DWithin` - InstancedMesh + frustum culling,目标 5 万船只 60fps --- ## 三、工作量估算 | Phase | 内容 | 估计时间 | |-------|-----|---------| | Phase 0 | 数据源验证、mock | 1–2 天 | | Phase 1 | 后端 Schema + Collector + API | 3–4 天 | | Phase 2 | 前端渲染(InstancedMesh + 图层 + Info Card) | 3–4 天 | | Phase 3 | 搜索 + 统计 + 轨迹 | 2–3 天 | | Phase 4 | 性能优化 + 生产数据源接入 | 2–3 天 | | **合计** | | **约 2–3 周** | --- ## 四、参考资料 - BarentsWatch AIS API 文档:https://www.barentswatch.no/en/developer/ais-api/ - MarineTraffic API:https://www.marinetraffic.com/en/ais-api-services - AISHub:https://www.aishub.net/api - AIS 导航状态码:ITU-R M.1371-5 - 船型编码(vessel_type):ITU/IMO AIS Message 5 Type and Cargo - WorldPorts 数据集:https://msi.nga.mil/Publications/WPI