277 lines
11 KiB
Markdown
277 lines
11 KiB
Markdown
# 实时船只监控系统 — 实施计划
|
||
|
||
**状态**:历史计划;实时 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
|