release: bump version to 0.46.2
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
- [earth-news-source-configuration-and-collector-plan.md](/home/ray/dev/linkong/planet/docs/plans/earth-news-source-configuration-and-collector-plan.md)
|
||||
- [earth-news-cruise-summary-plan.md](/home/ray/dev/linkong/planet/docs/plans/earth-news-cruise-summary-plan.md)
|
||||
- [earth-vessel-rendering-performance-plan.md](/home/ray/dev/linkong/planet/docs/plans/earth-vessel-rendering-performance-plan.md)
|
||||
- [AIS 多源采集、冲突记录与聚合接口计划](/home/ray/dev/linkong/planet/docs/plans/earth-vessel-ais-aggregation-plan.md)
|
||||
- [earth-interactable-layer-plan.md](/home/ray/dev/linkong/planet/docs/plans/earth-interactable-layer-plan.md)
|
||||
- [frontend-public-docs-site-plan.md](/home/ray/dev/linkong/planet/docs/plans/frontend-public-docs-site-plan.md)
|
||||
- [frontend-ai-playground-development-plan.md](/home/ray/dev/linkong/planet/docs/plans/frontend-ai-playground-development-plan.md)
|
||||
|
||||
@@ -272,7 +272,8 @@ hover / locked 使用少量 overlay:
|
||||
### Phase 3:迁移算力中心并评估登陆点
|
||||
|
||||
- 算力中心保留现有业务 icon,但接入统一 hover / locked / glow。(已完成)
|
||||
- 登陆点曾接入同一套 `Points` 渲染,但 pin 类 SVG 在地球边缘会被深度测试裁切;当前保留专用 `THREE.Sprite`,并使用 canvas 生成黄色扁平球,贴到海缆层级。后续如要重新设计登陆点,需要先确认图标能在边缘视角完整显示。
|
||||
- 登陆点曾接入同一套 `Points` 渲染,但 pin 类 SVG 在地球边缘会被深度测试裁切;当前保留专用 `THREE.Sprite`,并使用 canvas 生成黄色扁平球,贴到海缆层级。
|
||||
- TODO:登陆点暂不迁移到完整 Interactable。后续若要统一交互接口,优先考虑 Sprite-backed adapter,只对齐 `getMarkers()`、`getPointerIntersections()`、`setMarkerState()`、`updateVisualState()` 等外观协议,不强行复用 `THREE.Points`、atlas 和跨图层避让。
|
||||
- 检查图例、搜索和 info-card 是否只依赖业务 payload,而不是依赖渲染对象类型。
|
||||
|
||||
### Phase 4:形成 Earth 图标层规范
|
||||
|
||||
261
docs/plans/earth-vessel-ais-aggregation-plan.md
Normal file
261
docs/plans/earth-vessel-ais-aggregation-plan.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# AIS 多源采集、冲突记录与聚合接口计划
|
||||
|
||||
**状态**:规划中
|
||||
**创建日期**:2026-04-30
|
||||
**核心原则**:采集器只写原始观测;去重、合并、冲突解释放在聚合接口中完成
|
||||
|
||||
## 已确认决策
|
||||
|
||||
| 项目 | 决策 |
|
||||
|-----|------|
|
||||
| AISStream 接入方式 | 单独实现 WebSocket 采集器,不塞进现有 BarentsWatch HTTP collector |
|
||||
| 采集器职责 | 连接上游、标准化字段、写入原始观测,不直接决定最终展示值 |
|
||||
| 去重合并位置 | 放在聚合服务和聚合 API 中,而不是散落在每个 collector 的保存逻辑里 |
|
||||
| 冲突处理 | 先记录冲突事实和当前选择原因,后续再开放用户规则配置 |
|
||||
| 默认可信度 | 同类 AIS 数据源优先按 `delivery_mode` 评估:`realtime_stream` 优于 `batch_stream`,再优于 `polling` 和 `snapshot` |
|
||||
| 过期保护 | 实时流源断流超过 freshness 窗口后,不能仅凭“实时源”身份压过更新的轮询数据 |
|
||||
|
||||
## 背景
|
||||
|
||||
当前 AIS 链路以 BarentsWatch 为主。它是 HTTP polling 模式,覆盖挪威附近海域,适合作为稳定的免费起点,但不适合承担全球实时船只数据的全部职责。后续接入 AISStream 后,会出现同一个 MMSI 被多个来源同时上报的情况:
|
||||
|
||||
- 位置、航速、航向可能在多个来源之间存在秒级差异。
|
||||
- 船名、IMO、呼号、船型、尺寸等静态字段可能不完整,甚至互相冲突。
|
||||
- WebSocket 或其他实时流通常更接近实时,但也可能断流或批量延迟。
|
||||
- 如果每个 collector 自己做去重合并,规则会分散、不可审计,也很难让用户后续配置“某个字段信任哪个来源”。
|
||||
|
||||
因此 v1 不应让采集器直接覆盖最终船只表。更稳的方式是先保留观测事实,再由聚合接口统一给出当前展示视图。
|
||||
|
||||
## 目标架构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[BarentsWatch HTTP collector] --> D[AIS raw observations]
|
||||
B[AISStream WebSocket collector] --> D
|
||||
C[Custom mapped vessel_ais sources] --> D
|
||||
D --> E[AIS aggregation service]
|
||||
E --> F[Conflict records]
|
||||
E --> G[GeoJSON vessels API]
|
||||
E --> H[Vessel detail API]
|
||||
I[Aggregation strategy config] --> E
|
||||
```
|
||||
|
||||
### 原始观测层
|
||||
|
||||
原始观测层保存每个来源看到的事实。建议模型包含:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|-----|------|
|
||||
| `target_schema` | 例如 `vessel_ais` |
|
||||
| `source` | 例如 `barentswatch_vessels`、`aisstream_vessels` |
|
||||
| `entity_key` | AIS 使用 MMSI |
|
||||
| `delivery_mode` | `realtime_stream`、`batch_stream`、`polling`、`snapshot` |
|
||||
| `transport` | `websocket`、`sse`、`http`、`file` 等 |
|
||||
| `observed_at` | 上游数据时间,优先使用 AIS 消息时间 |
|
||||
| `collected_at` | 本系统接收或采集时间 |
|
||||
| `normalized_payload` | 标准化后的 AIS JSON |
|
||||
| `raw_payload` | 可选,保存原始或裁剪后的上游记录 |
|
||||
|
||||
`delivery_mode` 和 `transport` 不应混为一谈。WebSocket 是传输方式;streaming 是交付模式。聚合可信度主要看 `delivery_mode`,`transport` 只作为辅助信息。
|
||||
|
||||
### 冲突记录层
|
||||
|
||||
聚合服务发现同一个实体、同一个字段存在多个非空不同值时,写入冲突记录。冲突记录不代表错误,只代表“有多个可用候选值”。
|
||||
|
||||
```json
|
||||
{
|
||||
"target_schema": "vessel_ais",
|
||||
"entity_key": "257123000",
|
||||
"field": "name",
|
||||
"candidates": {
|
||||
"barentswatch_vessels": "OSLO TRADER",
|
||||
"aisstream_vessels": "OSLO TRADER II"
|
||||
},
|
||||
"selected_source": "aisstream_vessels",
|
||||
"selected_value": "OSLO TRADER II",
|
||||
"selected_reason": "delivery_mode_priority",
|
||||
"resolved_by": "system",
|
||||
"status": "open"
|
||||
}
|
||||
```
|
||||
|
||||
第一阶段只需要记录冲突和当前选择原因,不需要做人工逐条确认。后续 UI 的目标也不是让用户处理每条冲突,而是把冲突沉淀成字段级规则。
|
||||
|
||||
## 聚合规则
|
||||
|
||||
### 字段分类
|
||||
|
||||
| 类型 | 字段 | 默认策略 |
|
||||
|-----|------|----------|
|
||||
| 动态位置 | `lat`、`lon`、`sog`、`cog`、`heading`、`nav_status` | 优先最新 `observed_at`,同时间再按来源优先级 |
|
||||
| 静态身份 | `name`、`callsign`、`imo`、`flag` | 非空优先,再按字段策略或来源优先级 |
|
||||
| 静态规格 | `vessel_type`、`vessel_type_name`、`length`、`width`、`draught` | 非空优先;冲突时记录候选值 |
|
||||
| 元信息 | `field_sources`、`conflict_count`、`selected_reasons` | 聚合接口生成,便于调试和后续 UI 展示 |
|
||||
|
||||
### 默认优先级
|
||||
|
||||
默认优先级应使用两个维度:
|
||||
|
||||
```yaml
|
||||
delivery_mode_priority:
|
||||
- realtime_stream
|
||||
- batch_stream
|
||||
- polling
|
||||
- snapshot
|
||||
|
||||
transport_priority:
|
||||
- websocket
|
||||
- sse
|
||||
- http
|
||||
- file
|
||||
```
|
||||
|
||||
`delivery_mode_priority` 是主判断。比如 AISStream 如果提供实时推送,应标记为 `realtime_stream + websocket`;BarentsWatch 当前是 `polling + http`。
|
||||
|
||||
### 断流保护
|
||||
|
||||
实时流不能永久凭身份占优。聚合时需要 freshness 窗口:
|
||||
|
||||
```yaml
|
||||
freshness:
|
||||
realtime_stream_seconds: 900
|
||||
polling_seconds: 3600
|
||||
```
|
||||
|
||||
如果 `aisstream_vessels` 最近 15 分钟没有该 MMSI 的新观测,而 BarentsWatch 轮询源有更新位置,则位置类字段应采用 BarentsWatch 的更新观测,并记录选择原因 `newest_observation` 或 `freshness_fallback`。
|
||||
|
||||
## 聚合接口
|
||||
|
||||
现有展示接口应逐步改为消费聚合服务,而不是自己直接拼 `VesselPosition + VesselStatic`。
|
||||
|
||||
```text
|
||||
GET /api/v1/visualization/geo/vessels
|
||||
GET /api/v1/visualization/vessels/{mmsi}
|
||||
GET /api/v1/visualization/vessels/{mmsi}/track
|
||||
GET /api/v1/visualization/vessels/{mmsi}/conflicts
|
||||
```
|
||||
|
||||
GeoJSON properties 建议增加:
|
||||
|
||||
```json
|
||||
{
|
||||
"mmsi": 257123000,
|
||||
"name": "OSLO TRADER",
|
||||
"lat": 59.91,
|
||||
"lon": 10.73,
|
||||
"received_at": "2026-04-30T10:00:00Z",
|
||||
"field_sources": {
|
||||
"name": "aisstream_vessels",
|
||||
"lat": "aisstream_vessels",
|
||||
"lon": "aisstream_vessels",
|
||||
"vessel_type": "barentswatch_vessels"
|
||||
},
|
||||
"selected_reasons": {
|
||||
"name": "delivery_mode_priority",
|
||||
"lat": "newest_observation",
|
||||
"vessel_type": "non_empty_priority"
|
||||
},
|
||||
"conflict_count": 2
|
||||
}
|
||||
```
|
||||
|
||||
## 开放配置计划
|
||||
|
||||
### Phase 1 — 内置默认策略和只读解释
|
||||
|
||||
- 实现后端默认策略。
|
||||
- 聚合接口返回 `field_sources`、`selected_reasons`、`conflict_count`。
|
||||
- 冲突记录可查询,但不允许用户修改。
|
||||
- 保持现有前端船只图层接口形状基本兼容,新增字段只作为调试和后续 UI 输入。
|
||||
|
||||
### Phase 2 — 系统设置中的 JSON/YAML 策略配置
|
||||
|
||||
新增系统设置项,例如:
|
||||
|
||||
```yaml
|
||||
collector_aggregation:
|
||||
vessel_ais:
|
||||
source_priority:
|
||||
- aisstream_vessels
|
||||
- barentswatch_vessels
|
||||
field_rules:
|
||||
name:
|
||||
mode: source_priority
|
||||
vessel_type:
|
||||
mode: source_priority
|
||||
source_priority:
|
||||
- barentswatch_vessels
|
||||
- aisstream_vessels
|
||||
lat:
|
||||
mode: newest
|
||||
lon:
|
||||
mode: newest
|
||||
```
|
||||
|
||||
配置校验要求:
|
||||
|
||||
- 未知 source 只警告,不阻断保存,便于先配置后启用。
|
||||
- 未知 field 必须拒绝,避免拼写错误悄悄失效。
|
||||
- 动态位置字段默认不允许被固定来源永久锁死,除非显式开启高级选项。
|
||||
- 空值不覆盖非空值是全局保护,不建议开放关闭。
|
||||
|
||||
### Phase 3 — 冲突治理 UI
|
||||
|
||||
基于冲突记录提供页面或 drawer:
|
||||
|
||||
- 查看某个 MMSI 的冲突字段。
|
||||
- 查看每个字段的候选来源和值。
|
||||
- 查看当前选择原因。
|
||||
- 将一次人工选择保存成字段规则,而不是只处理单条冲突。
|
||||
- 支持恢复默认策略。
|
||||
|
||||
## AISStream 采集器计划
|
||||
|
||||
AISStream 采集器单独实现,建议命名为 `aisstream_vessels`。它的职责是:
|
||||
|
||||
- 维护 WebSocket 连接、订阅范围和重连。
|
||||
- 将上游 AIS 消息标准化为 `vessel_ais` payload。
|
||||
- 标记 `delivery_mode = realtime_stream`,`transport = websocket`。
|
||||
- 写入原始观测层。
|
||||
- 不直接 upsert 最终展示数据。
|
||||
|
||||
配置应放入采集器设置,而不是硬编码:
|
||||
|
||||
```yaml
|
||||
aisstream_vessels:
|
||||
api_key: "${AISSTREAM_API_KEY}"
|
||||
bounding_boxes:
|
||||
- [[-180, -90], [180, 90]]
|
||||
message_types:
|
||||
- PositionReport
|
||||
- ShipStaticData
|
||||
```
|
||||
|
||||
## 实施顺序
|
||||
|
||||
1. 新增原始观测模型和冲突记录模型。
|
||||
2. 实现 AIS 聚合服务,先从现有 `vessel_position` / `vessel_static` 兼容读取,再逐步切换到原始观测层。
|
||||
3. 将 `/geo/vessels` 和 `/vessels/{mmsi}` 改为走聚合服务。
|
||||
4. 改造 BarentsWatch 保存逻辑,让它写入原始观测,同时保留现有表作为兼容缓存。
|
||||
5. 实现 AISStream WebSocket collector。
|
||||
6. 接入系统设置中的聚合策略配置。
|
||||
7. 做冲突治理 UI。
|
||||
|
||||
## 测试计划
|
||||
|
||||
- 同一来源同一 `mmsi + observed_at + lat + lon` 重复记录只聚合一次。
|
||||
- 多来源同一 MMSI 的位置字段优先选择最新观测。
|
||||
- 实时流和轮询源同时间冲突时,实时流优先。
|
||||
- 实时流过期后,更新的轮询源可以接管动态字段。
|
||||
- 静态字段不会被空值覆盖。
|
||||
- 静态字段冲突会写入冲突记录。
|
||||
- 字段级配置可以覆盖默认来源优先级。
|
||||
- 聚合接口在没有冲突表时仍可返回兼容 GeoJSON。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- [实时船只监控系统计划](/home/ray/dev/linkong/planet/docs/plans/earth-vessel-tracking-plan.md)
|
||||
- [自定义 API 数据源与 LLM 映射系统计划](/home/ray/dev/linkong/planet/docs/plans/datasource-custom-api-mapping-plan.md)
|
||||
- [BarentsWatch AIS collector](/home/ray/dev/linkong/planet/backend/app/services/collectors/vessel_ais.py)
|
||||
- [船只模型](/home/ray/dev/linkong/planet/backend/app/models/vessel.py)
|
||||
- [可视化 API](/home/ray/dev/linkong/planet/backend/app/api/v1/visualization.py)
|
||||
@@ -12,7 +12,7 @@
|
||||
| 船只规模 | BarentsWatch 阶段全部显示;全球数据接入后按需加船型过滤(默认 Cargo + Tanker + Passenger) |
|
||||
| 更新频率 | 准实时:前端 5 分钟轮询,后端 Collector 每分钟拉取写库 |
|
||||
| 历史轨迹 | 保留(`vessel_position` 表保留 24h,后期按需扩展) |
|
||||
| 推送方式 | HTTP 轮询(不用 WebSocket);换实时数据源后再评估升级 |
|
||||
| 推送方式 | 前端展示仍可先用 HTTP 拉取聚合结果;AISStream 等实时源应单独实现 WebSocket 采集器 |
|
||||
|
||||
---
|
||||
|
||||
@@ -36,13 +36,19 @@
|
||||
- 字段:mmsi, lat, lon, sog, cog, heading, nav_status, name, vessel_type, flag
|
||||
- 刷新频率:数据约 30–60s 更新一次,可随意轮询
|
||||
|
||||
### TODO:付费数据源接入
|
||||
### 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 多源采集、冲突记录与聚合接口计划](/home/ray/dev/linkong/planet/docs/plans/earth-vessel-ais-aggregation-plan.md)。
|
||||
|
||||
---
|
||||
|
||||
## 二、实施计划
|
||||
@@ -151,12 +157,13 @@ GeoJSON Feature 格式:
|
||||
|
||||
#### 1.4 更新机制
|
||||
|
||||
**HTTP 轮询**(不使用 WebSocket):
|
||||
**前端聚合结果拉取 + 后端实时采集**:
|
||||
|
||||
- 前端 `setInterval(fetchVessels, 5 * 60 * 1000)` 定期拉取最新快照
|
||||
- 后端 Collector 每 60s 从 BarentsWatch 拉取并写库,`vessel_latest` 物化视图随时可查
|
||||
- WebSocket 留给告警/事件驱动场景(BGP、系统通知),不混入周期性位置刷新
|
||||
- 换用 AISHub / MarineTraffic 实时流后,届时再评估是否升级为 WebSocket delta push
|
||||
- 后端 BarentsWatch collector 继续以 HTTP polling 方式采集
|
||||
- AISStream 等实时源以独立 WebSocket collector 写入原始观测层
|
||||
- 展示接口从聚合服务读取当前船只视图,而不是由单个 collector 决定最终展示值
|
||||
- 前端是否升级为 WebSocket delta push 是独立优化,不影响后端采集器可以使用 WebSocket 接上游实时源
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user