Initial import of NavSea pbf project
This commit is contained in:
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Python environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
|
||||||
|
# Editor / OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Local temp and logs
|
||||||
|
.tmp/
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Test / coverage caches
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Local runtime outputs
|
||||||
|
report/*.tmp
|
||||||
|
report/*.cache
|
||||||
197
NavSea_Delivery_vs_Source_Audit_Report_Karatsu_10nm.md
Normal file
197
NavSea_Delivery_vs_Source_Audit_Report_Karatsu_10nm.md
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
# NavSea 使用版与原始版审计报告
|
||||||
|
|
||||||
|
范围:唐津中心 10 海里 AOI
|
||||||
|
原始版目录:`/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
|
||||||
|
使用版目录:`/home/wwwroot/pbf-delivery-karatsu-10nm`
|
||||||
|
使用版样式:[navsea-delivery.json](/home/wwwroot/newpec/navsea-delivery.json)
|
||||||
|
|
||||||
|
## 1. 结论
|
||||||
|
|
||||||
|
在唐津中心 10 海里范围内,使用版与原始版相比:
|
||||||
|
|
||||||
|
- 瓦片文件集合一致
|
||||||
|
- feature 总数一致
|
||||||
|
- `source-layer` 集合一致
|
||||||
|
- 图层 `extent` 一致
|
||||||
|
- 没有发生 layer 重排
|
||||||
|
|
||||||
|
这意味着:
|
||||||
|
|
||||||
|
- 使用版没有删对象
|
||||||
|
- 使用版没有少瓦片
|
||||||
|
- 使用版没有改几何精度
|
||||||
|
- 使用版的主要变化集中在属性增强,而不是空间内容变化
|
||||||
|
|
||||||
|
## 2. 审计范围
|
||||||
|
|
||||||
|
- AOI 瓦片数:`59`
|
||||||
|
- zoom 分布:`0, 5, 6, 7, 8, 9, 10, 11, 12`
|
||||||
|
- 使用版样式名称:`NavSea Delivery Karatsu 10nm`
|
||||||
|
- 使用版瓦片源:`http://192.168.200.184/pbf-delivery-karatsu-10nm/{z}/{x}/{y}.pbf`
|
||||||
|
|
||||||
|
## 3. 瓦片集合审计
|
||||||
|
|
||||||
|
- 使用版瓦片数:`59`
|
||||||
|
- 同名原始瓦片存在数:`59`
|
||||||
|
- 使用版缺少原始对应文件:`0`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 使用版与原始版在 `z/x/y.pbf` 文件名层面一一对应
|
||||||
|
|
||||||
|
## 4. Feature 数量审计
|
||||||
|
|
||||||
|
- 原始版 feature 总数:`142424`
|
||||||
|
- 使用版 feature 总数:`142424`
|
||||||
|
- feature 数量不一致的瓦片数:`0`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 唐津 10 海里范围内,使用版没有减少或增加 feature
|
||||||
|
|
||||||
|
## 5. Layer 结构审计
|
||||||
|
|
||||||
|
- 原始版 distinct `source-layer` 数:`37`
|
||||||
|
- 使用版 distinct `source-layer` 数:`37`
|
||||||
|
- layer 重排 feature 数:`0`
|
||||||
|
- `extent` 不一致层数:`0`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 本 AOI 内,使用版仍沿用原始 layer 结构
|
||||||
|
- `render_layer` 虽然被写入了属性,但这 10 海里范围内没有出现 `source_layer -> render_layer` 的实际迁移
|
||||||
|
- 几何编码精度和原始版一致
|
||||||
|
|
||||||
|
## 6. 属性差异审计
|
||||||
|
|
||||||
|
### 6.1 新增字段
|
||||||
|
|
||||||
|
使用版在原始属性基础上新增了这些字段:
|
||||||
|
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `fid_algo_id`
|
||||||
|
- `fid_key_id`
|
||||||
|
- `render_layer`
|
||||||
|
- `semantic_key`
|
||||||
|
|
||||||
|
### 6.2 删除字段
|
||||||
|
|
||||||
|
审计结果中,没有发现“原始版有、使用版没有”的属性键:
|
||||||
|
|
||||||
|
- 删除字段数:`0`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 使用版保留了原始字段体系
|
||||||
|
- 变化不是“删旧字段”,而是“在旧字段上增加新字段”
|
||||||
|
|
||||||
|
### 6.3 `fid` 的变化
|
||||||
|
|
||||||
|
`fid` 键仍然存在,但值已经从旧版原始值改成了 NavSea 加密后的 16 进制值。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
- 原始版 `/12/3525/1642.pbf`:`fid=10004352`
|
||||||
|
- 使用版 `/12/3525/1642.pbf`:`fid=3493BD24`
|
||||||
|
|
||||||
|
同时使用版会补充:
|
||||||
|
|
||||||
|
- `fid_algo_id=feistel32_aes_cyclewalk_v1`
|
||||||
|
- `fid_key_id=navsea-fid-key-v1`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 使用版不再直接暴露旧版原始 `fid`
|
||||||
|
- 与工程版不同,使用版不会保留 `fid_legacy_raw`、`fid_navsea_int` 这类追溯字段
|
||||||
|
|
||||||
|
## 7. 样例对比
|
||||||
|
|
||||||
|
### 7.1 `/0/0/0.pbf`
|
||||||
|
|
||||||
|
原始版样例:
|
||||||
|
|
||||||
|
- layer:`P基本線`
|
||||||
|
- `fid=5002231`
|
||||||
|
|
||||||
|
使用版样例:
|
||||||
|
|
||||||
|
- layer:`P基本線`
|
||||||
|
- `fid=3720E480`
|
||||||
|
- `fid_algo_id=feistel32_aes_cyclewalk_v1`
|
||||||
|
- `fid_key_id=navsea-fid-key-v1`
|
||||||
|
- `canonical_object_type=2-5m`
|
||||||
|
- `canonical_family=surface`
|
||||||
|
- `detection_key=surface:2-5m`
|
||||||
|
- `render_layer=P基本線`
|
||||||
|
|
||||||
|
### 7.2 `/12/3525/1642.pbf`
|
||||||
|
|
||||||
|
原始版样例:
|
||||||
|
|
||||||
|
- layer:`L739`
|
||||||
|
- `fid=10004352`
|
||||||
|
|
||||||
|
使用版样例:
|
||||||
|
|
||||||
|
- layer:`L739`
|
||||||
|
- `fid=3493BD24`
|
||||||
|
- `fid_algo_id=feistel32_aes_cyclewalk_v1`
|
||||||
|
- `fid_key_id=navsea-fid-key-v1`
|
||||||
|
- `canonical_object_type=平水境界`
|
||||||
|
- `canonical_family=line`
|
||||||
|
- `detection_key=line:平水境界`
|
||||||
|
- `render_layer=L739`
|
||||||
|
|
||||||
|
## 8. 对渲染的影响
|
||||||
|
|
||||||
|
从这次审计结果看,使用版对渲染最重要的结论有两条:
|
||||||
|
|
||||||
|
- 几何、feature 数量、layer 结构没有变化,所以“画不出来”的风险不来自空间内容减少
|
||||||
|
- 真正变化的是属性层,尤其是 `fid` 被替换为加密值,以及增加了语义字段
|
||||||
|
|
||||||
|
因此,对旧样式兼容性的影响主要来自:
|
||||||
|
|
||||||
|
- 样式是否依赖旧 `fid` 直接做逻辑
|
||||||
|
- 样式是否开始使用新的 `canonical_*` / `render_layer` / `detection_key`
|
||||||
|
|
||||||
|
而不是来自:
|
||||||
|
|
||||||
|
- 瓦片缺失
|
||||||
|
- layer 缺失
|
||||||
|
- feature 丢失
|
||||||
|
- extent 改变
|
||||||
|
|
||||||
|
## 9. 与工程版的区别
|
||||||
|
|
||||||
|
使用版与工程版相比,主要少了这些追溯字段:
|
||||||
|
|
||||||
|
- `fid_legacy_raw`
|
||||||
|
- `fid_navsea_int`
|
||||||
|
- `source_layer_jp`
|
||||||
|
- `source_layer_std`
|
||||||
|
- `normalization_bundle_id`
|
||||||
|
- `source_layer_rule_id`
|
||||||
|
- `trace_status`
|
||||||
|
- `feature_id`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 使用版适合渲染、检索、业务使用
|
||||||
|
- 工程版适合审计、回查、问题定位
|
||||||
|
|
||||||
|
## 10. 最终判断
|
||||||
|
|
||||||
|
唐津 10 海里范围内,使用版相对于原始版的变化可以概括为:
|
||||||
|
|
||||||
|
- 空间内容未变
|
||||||
|
- 图层结构未变
|
||||||
|
- 原始字段未删
|
||||||
|
- `fid` 被替换为 NavSea 加密值
|
||||||
|
- 叠加了新的语义字段
|
||||||
|
- 去掉了工程追溯字段
|
||||||
|
|
||||||
|
一句话总结:
|
||||||
|
|
||||||
|
> 使用版不是“瘦身后的原始版”,而是“保留原始内容、替换对象 ID、叠加语义信息、去掉工程追溯字段”的交付版。
|
||||||
107
NavSea_Geometry_Enablement.md
Normal file
107
NavSea_Geometry_Enablement.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# NavSea Geometry Enablement
|
||||||
|
|
||||||
|
## 目的
|
||||||
|
|
||||||
|
为 `navsea_detection_objects` 补上真实 geometry,并在数据库中启用真正的空间索引能力。
|
||||||
|
|
||||||
|
## 当前现状
|
||||||
|
|
||||||
|
当前 `pbf_analysis` 库中没有真实坐标 geometry 列,只有:
|
||||||
|
|
||||||
|
- `feature_id`
|
||||||
|
- `z/x/y`
|
||||||
|
- `vt_layer`
|
||||||
|
- `geom_type`
|
||||||
|
|
||||||
|
因此当前系统只能做:
|
||||||
|
|
||||||
|
- 语义分类
|
||||||
|
- tile 级查询优化
|
||||||
|
- 基于 `z/x/y` 的近似空间诊断
|
||||||
|
|
||||||
|
不能做:
|
||||||
|
|
||||||
|
- 真正的半径查询
|
||||||
|
- 真正的航线走廊查询
|
||||||
|
- 真正的船首前方空间查询
|
||||||
|
- 真正的 `SPATIAL INDEX` 查询计划
|
||||||
|
|
||||||
|
## 启用方式
|
||||||
|
|
||||||
|
使用脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_geometry_enable.py
|
||||||
|
```
|
||||||
|
|
||||||
|
默认要求数据库中存在 staging 表:
|
||||||
|
|
||||||
|
```text
|
||||||
|
feature_geometry_stage
|
||||||
|
```
|
||||||
|
|
||||||
|
可以通过环境变量改名:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NAVSEA_GEOMETRY_SOURCE_TABLE=your_table .venv/bin/python navsea_geometry_enable.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的 staging 结构
|
||||||
|
|
||||||
|
脚本支持以下任一输入形式:
|
||||||
|
|
||||||
|
### 1. 直接 geometry 列
|
||||||
|
|
||||||
|
```sql
|
||||||
|
feature_id BIGINT
|
||||||
|
geometry GEOMETRY
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. WKT
|
||||||
|
|
||||||
|
```sql
|
||||||
|
feature_id BIGINT
|
||||||
|
geometry_wkt LONGTEXT
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. WKB
|
||||||
|
|
||||||
|
```sql
|
||||||
|
feature_id BIGINT
|
||||||
|
geometry_wkb BLOB
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 点坐标
|
||||||
|
|
||||||
|
```sql
|
||||||
|
feature_id BIGINT
|
||||||
|
lon DOUBLE
|
||||||
|
lat DOUBLE
|
||||||
|
```
|
||||||
|
|
||||||
|
仅适用于 Point 类对象。
|
||||||
|
|
||||||
|
## 脚本输出
|
||||||
|
|
||||||
|
如果 staging 表满足要求,脚本会创建:
|
||||||
|
|
||||||
|
- `navsea_feature_geometry`
|
||||||
|
- `navsea_detection_objects_spatial`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `navsea_feature_geometry` 保存真实 geometry,并建立 `SPATIAL KEY idx_geometry`
|
||||||
|
- `navsea_detection_objects_spatial` 是 `navsea_detection_objects` 的空间版只读视图
|
||||||
|
|
||||||
|
## 推荐的后续查询
|
||||||
|
|
||||||
|
当 geometry 可用后,可以在 `navsea_detection_objects_spatial` 上执行:
|
||||||
|
|
||||||
|
- 半径查询
|
||||||
|
- 航线缓冲带查询
|
||||||
|
- 前方扇形查询
|
||||||
|
- 空间相交 / 邻近查询
|
||||||
|
|
||||||
|
## 当前阻塞
|
||||||
|
|
||||||
|
当前唯一阻塞不是脚本,而是缺少 geometry staging 数据源。
|
||||||
347
NavSea_Legacy_Field_Exit_Roadmap.md
Normal file
347
NavSea_Legacy_Field_Exit_Roadmap.md
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
# NavSea 摆脱旧字段体系路线图
|
||||||
|
|
||||||
|
目标:让 NavSea 的使用版样式与交付版 `pbf` 不再依赖旧海图字段体系,最终做到:
|
||||||
|
|
||||||
|
- 样式主逻辑不再直接读取 `分類番号`
|
||||||
|
- 样式主逻辑不再直接读取 `形状分類番号`
|
||||||
|
- 样式主逻辑不再直接读取 `表示用番号`
|
||||||
|
- 样式主逻辑不再直接读取 `灯色`、`灯略記`、`明弧/分孤`
|
||||||
|
- 检索和渲染都优先依赖 `canonical_*` 与 `chart_*`
|
||||||
|
|
||||||
|
## 1. 当前状态
|
||||||
|
|
||||||
|
当前使用版已经迈出第一步,但还没有真正摆脱旧字段体系。
|
||||||
|
|
||||||
|
### 1.1 已完成
|
||||||
|
|
||||||
|
- `fid` 已替换为 NavSea 自有 16 进制 ID
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `semantic_key`
|
||||||
|
- `render_layer`
|
||||||
|
|
||||||
|
### 1.2 仍未完成
|
||||||
|
|
||||||
|
当前使用版样式仍直接读取这些旧字段:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `表示位置`
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `日本語地名`
|
||||||
|
- `英文字地名`
|
||||||
|
- `水深値(m)`
|
||||||
|
- `高さ/深度(m)`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 其中 `名称`、`名称補助`、`日本語地名`、`英文字地名` 不应视为要被完全淘汰的“坏字段”
|
||||||
|
- 它们属于内容字段,应保留或映射为 `chart_label_*`
|
||||||
|
- 真正要退出的是“旧渲染控制字段”
|
||||||
|
|
||||||
|
## 2. 旧字段退出的对象范围
|
||||||
|
|
||||||
|
优先退出的旧字段:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `明弧/分孤`
|
||||||
|
- `表示位置`
|
||||||
|
- `水深値(m)`
|
||||||
|
- `高さ(m)`
|
||||||
|
- `高さ/深度(m)`
|
||||||
|
- `角度`
|
||||||
|
|
||||||
|
不建议简单删除,而应先完成“等价替代字段”。
|
||||||
|
|
||||||
|
## 3. 对应的新字段体系
|
||||||
|
|
||||||
|
### 3.1 对象语义层
|
||||||
|
|
||||||
|
继续保留并强化:
|
||||||
|
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `semantic_key`
|
||||||
|
|
||||||
|
这层回答:
|
||||||
|
|
||||||
|
- 这个对象是什么
|
||||||
|
|
||||||
|
### 3.2 渲染语义层
|
||||||
|
|
||||||
|
必须补齐:
|
||||||
|
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_text_style`
|
||||||
|
- `chart_priority`
|
||||||
|
- `chart_visibility_min`
|
||||||
|
- `chart_visibility_max`
|
||||||
|
- `chart_collision_group`
|
||||||
|
|
||||||
|
这层回答:
|
||||||
|
|
||||||
|
- 这个对象应该怎么画
|
||||||
|
|
||||||
|
### 3.3 专项语义层
|
||||||
|
|
||||||
|
必须补齐:
|
||||||
|
|
||||||
|
- `chart_label_text`
|
||||||
|
- `chart_label_subtext`
|
||||||
|
- `chart_label_anchor`
|
||||||
|
- `chart_label_dx`
|
||||||
|
- `chart_label_dy`
|
||||||
|
- `light_color_code`
|
||||||
|
- `light_character_code`
|
||||||
|
- `light_sector_mode`
|
||||||
|
- `hazard_class`
|
||||||
|
- `hazard_severity`
|
||||||
|
- `area_usage_class`
|
||||||
|
- `depth_value_m`
|
||||||
|
- `clearance_height_m`
|
||||||
|
- `least_depth_m`
|
||||||
|
- `bearing_deg`
|
||||||
|
|
||||||
|
这层回答:
|
||||||
|
|
||||||
|
- 具体符号、文字和业务检索所需的细节是什么
|
||||||
|
|
||||||
|
## 4. 替代关系
|
||||||
|
|
||||||
|
### 4.1 灯标体系
|
||||||
|
|
||||||
|
旧字段:
|
||||||
|
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `明弧/分孤`
|
||||||
|
|
||||||
|
新字段:
|
||||||
|
|
||||||
|
- `chart_symbol_family=navigation_light` 或 `navigation_mark`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `light_color_code`
|
||||||
|
- `light_character_code`
|
||||||
|
- `light_sector_mode`
|
||||||
|
- `chart_label_text`
|
||||||
|
- `chart_label_subtext`
|
||||||
|
|
||||||
|
退出条件:
|
||||||
|
|
||||||
|
- 样式中灯标相关层不再直接 `get` 旧灯标字段
|
||||||
|
|
||||||
|
### 4.2 危险物/鱼礁体系
|
||||||
|
|
||||||
|
旧字段:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
|
||||||
|
新字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `hazard_class`
|
||||||
|
- `hazard_severity`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_fill_style`
|
||||||
|
|
||||||
|
退出条件:
|
||||||
|
|
||||||
|
- 危险物和鱼礁图形不再依赖旧编码号判断
|
||||||
|
|
||||||
|
### 4.3 等深线/水深体系
|
||||||
|
|
||||||
|
旧字段:
|
||||||
|
|
||||||
|
- `表示用番号`
|
||||||
|
- `水深値(m)`
|
||||||
|
- `高さ/深度(m)`
|
||||||
|
|
||||||
|
新字段:
|
||||||
|
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_text_style`
|
||||||
|
- `depth_value_m`
|
||||||
|
- `least_depth_m`
|
||||||
|
|
||||||
|
退出条件:
|
||||||
|
|
||||||
|
- 等深线粗细、类别和数字显示不再依赖旧字段
|
||||||
|
|
||||||
|
### 4.4 边界/区域体系
|
||||||
|
|
||||||
|
旧字段:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
|
||||||
|
新字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `area_usage_class`
|
||||||
|
|
||||||
|
退出条件:
|
||||||
|
|
||||||
|
- 锚地、航路、鱼业区、危险界等区域表现不再用旧编码做主判断
|
||||||
|
|
||||||
|
### 4.5 地名体系
|
||||||
|
|
||||||
|
旧字段:
|
||||||
|
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `日本語地名`
|
||||||
|
- `英文字地名`
|
||||||
|
- `表示位置`
|
||||||
|
|
||||||
|
新字段:
|
||||||
|
|
||||||
|
- `chart_label_text`
|
||||||
|
- `chart_label_subtext`
|
||||||
|
- `chart_label_anchor`
|
||||||
|
- `chart_label_dx`
|
||||||
|
- `chart_label_dy`
|
||||||
|
- `chart_text_style`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 地名文本本身不是要淘汰的“旧体系”
|
||||||
|
- 要退出的是“用原始字段名直接驱动样式”的方式
|
||||||
|
|
||||||
|
## 5. 实施阶段
|
||||||
|
|
||||||
|
## 阶段 A:补齐数据字段
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- builder 输出完整 `chart_*`
|
||||||
|
- builder 输出完整 `light_*`
|
||||||
|
- builder 输出完整 `hazard_*`
|
||||||
|
- builder 输出完整数值归一化字段
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 使用版 `pbf` 中,核心对象都能拿到可直接渲染的新字段
|
||||||
|
|
||||||
|
## 阶段 B:建立纯新字段样式
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 新样式不再直接读取 `分類番号`
|
||||||
|
- 新样式不再直接读取 `形状分類番号`
|
||||||
|
- 新样式不再直接读取 `表示用番号`
|
||||||
|
|
||||||
|
例外:
|
||||||
|
|
||||||
|
- 文本内容字段暂时允许保留
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- `style json` 中 `["get", "..."]` 不再出现旧渲染控制字段
|
||||||
|
|
||||||
|
## 阶段 C:做等价性审计
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 对比旧样式与新样式的主要对象覆盖
|
||||||
|
- 对比使用版旧字段驱动与新字段驱动的渲染结果
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 核心对象组通过审计:
|
||||||
|
- 灯标
|
||||||
|
- 危险物
|
||||||
|
- 鱼礁
|
||||||
|
- 等深线
|
||||||
|
- 水深数字
|
||||||
|
- 锚地/航路/鱼业区
|
||||||
|
- 地名
|
||||||
|
|
||||||
|
## 阶段 D:旧字段降级为兼容字段
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 使用版样式停止直接依赖旧渲染控制字段
|
||||||
|
- 旧字段只保留在数据里,供兼容、回查或历史对照
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 使用版样式完全跑在新字段体系上
|
||||||
|
- 工程版仍保留旧字段和 trace 以便审计
|
||||||
|
|
||||||
|
## 阶段 E:旧字段退场
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 仅在确认无样式、无检索、无检测依赖后,才从交付版中移除旧渲染控制字段
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 这不是近期动作
|
||||||
|
- 必须放在最后
|
||||||
|
|
||||||
|
## 6. 退出门禁
|
||||||
|
|
||||||
|
一个旧字段只有在同时满足以下条件时,才允许从使用版交付中移除:
|
||||||
|
|
||||||
|
- 已存在稳定的新字段替代
|
||||||
|
- 使用版样式不再直接读取它
|
||||||
|
- 检索规则不再直接读取它
|
||||||
|
- 碰撞或检测链路不再直接读取它
|
||||||
|
- 工程版仍能回查原值
|
||||||
|
- 审计报告确认关键对象渲染未明显退化
|
||||||
|
|
||||||
|
## 7. 下一步最值得做的事
|
||||||
|
|
||||||
|
如果目标是“真正摆脱旧字段体系”,下一步最值得直接落地的是:
|
||||||
|
|
||||||
|
1. 在 builder 里补齐第一批 `chart_*` 字段
|
||||||
|
重点对象:
|
||||||
|
- 灯标
|
||||||
|
- 危险物
|
||||||
|
- 鱼礁
|
||||||
|
- 等深线
|
||||||
|
- 水深数字
|
||||||
|
- 锚地/航路/鱼业区
|
||||||
|
|
||||||
|
2. 出一份“纯新字段样式”
|
||||||
|
要求:
|
||||||
|
- 不再读取 `分類番号`
|
||||||
|
- 不再读取 `形状分類番号`
|
||||||
|
- 不再读取 `表示用番号`
|
||||||
|
|
||||||
|
3. 对唐津 10 海里做一次新旧样式对照审计
|
||||||
|
目标:
|
||||||
|
- 看哪些对象已经可以完全脱离旧字段
|
||||||
|
- 看哪些对象还需要旧字段兜底
|
||||||
|
|
||||||
|
## 8. 最终判断
|
||||||
|
|
||||||
|
完全摆脱旧字段体系是可行的,但前提不是“删除旧字段”,而是:
|
||||||
|
|
||||||
|
- 先补齐新字段
|
||||||
|
- 再迁移样式
|
||||||
|
- 再做等价性审计
|
||||||
|
- 最后才让旧字段退出主流程
|
||||||
|
|
||||||
|
一句话总结:
|
||||||
|
|
||||||
|
> 下一步不是“把旧字段删掉”,而是“把旧字段一项一项替换掉,直到样式和业务都不再依赖它们”。
|
||||||
182
NavSea_Object_Taxonomy_Manual_Review.md
Normal file
182
NavSea_Object_Taxonomy_Manual_Review.md
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# NavSea Object Taxonomy Manual Review
|
||||||
|
|
||||||
|
## 1 Review Objective
|
||||||
|
|
||||||
|
This package is for manual confirmation of the current NavSea object taxonomy derived from the `pbf_analysis` database snapshot in this workspace.
|
||||||
|
|
||||||
|
The review focuses on confirming:
|
||||||
|
|
||||||
|
- canonical object type naming
|
||||||
|
- family grouping
|
||||||
|
- detection grouping
|
||||||
|
- geometry unification or splitting
|
||||||
|
- ambiguous object handling
|
||||||
|
|
||||||
|
The current pipeline already separates source-preserved render layers, semantic object layers, and detection-oriented object taxonomy. This review package highlights the places where human confirmation is still needed before treating the taxonomy as stable.
|
||||||
|
|
||||||
|
## 2 Review Priority Groups
|
||||||
|
|
||||||
|
### Priority A - Immediate navigation safety relevance
|
||||||
|
|
||||||
|
Hazards, wrecks, rocks, reefs, dangerous obstacles, navigation marks, route controls, anchorage controls, restricted areas, and fishery hazards that can affect collision, grounding, or entanglement decisions.
|
||||||
|
|
||||||
|
### Priority B - Navigation context / operational relevance
|
||||||
|
|
||||||
|
Boundaries, facilities, subsea cables and pipelines, piers, floating structures, and other contextual objects that matter to operations even when they are not direct collision targets.
|
||||||
|
|
||||||
|
### Priority C - Background / rendering objects
|
||||||
|
|
||||||
|
Labels, bathymetry support layers, clip layers, land masks, and other source-preserved layers that may be rendering support rather than semantic end objects.
|
||||||
|
|
||||||
|
## 3 Manual Review Candidate Table
|
||||||
|
|
||||||
|
| review_priority | layer_name | class_name | shape_name | vt_layer | observed_geom_types | total_feature_count | suggested_canonical_object_type | suggested_canonical_family | suggested_detection_class | suggested_review_reason | needs_manual_review |
|
||||||
|
| --- | --- | --- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |
|
||||||
|
| A | 航行危険障害物 | 暗岩 | | p航行危険障害物 | Point | 10,366 | 暗岩 | hazard | obstacle | Direct grounding hazard; confirm whether rock / reef subtyping should remain separate from generic obstacle handling. | YES |
|
||||||
|
| A | 航行危険障害物 | 洗岩 | | p航行危険障害物 | Point | 3,323 | 洗岩 | hazard | obstacle | High-salience grounding hazard; confirm stable canonical naming under hazard family. | YES |
|
||||||
|
| A | 航行危険障害物 | 干出岩 | | p航行危険障害物 | Point | 1,178 | 干出岩 | hazard | obstacle | Intertidal hazard object with clear navigation relevance; confirm whether it stays separate from other rock hazards. | YES |
|
||||||
|
| A | 投錨注意障害物 | 険悪物 | | p投錨注意障害物 / P投錨注意障害物 | Point, Polygon, MultiPolygon | 26,836 | 険悪物 | hazard | obstacle | Same object appears as point and area; confirm whether symbolic and areal forms should be unified or split. | YES |
|
||||||
|
| A | 投錨注意障害物 | 魚礁 | | p投錨注意障害物 / P投錨注意障害物 | Point, Polygon, MultiPolygon | 16,608 | 魚礁 | hazard | obstacle | Artificial reef can imply fishery semantics or navigation hazard; family assignment needs human confirmation. | YES |
|
||||||
|
| A | 航路標識点 | | 灯 (Lt) | p航路標識群 | Point | 14,936 | 灯 (Lt) | navigation_aid | navigation_mark | Core navigation mark subtype; confirm whether light subtypes should be normalized under a broader canonical lighthouse / light object. | YES |
|
||||||
|
| A | 航路標識点 | | 防波堤灯台 | p航路標識群 | Point | 4,679 | 防波堤灯台 | navigation_aid | navigation_mark | Shape-driven subtype may be a naming variant of a broader light / lighthouse family. | YES |
|
||||||
|
| A | 錨泊地等 | | | p錨泊地等 / P錨泊地等 | Point, Polygon | 234 | 錨泊 (指定)地 | anchorage | anchorage | Same anchorage concept appears as symbol and area; confirm whether annotation and area extent should remain one object type. | YES |
|
||||||
|
| A | 施設・境界線等 | 港則法による境界 | | L741 | LineString, MultiLineString | 1,696 | 港則法による境界 | boundary | boundary_control | Regulatory meaning affects route compliance and alerting; boundary family and detection role should be confirmed manually. | YES |
|
||||||
|
| A | 海岸線等深線等 | 浅所危険界 | | P基本線 | Polygon, MultiPolygon | 31,537 | 浅所危険界 | hazard | obstacle | Shoal hazard boundary may be treated as hazard extent, boundary object, or bathymetry support; requires explicit confirmation. | YES |
|
||||||
|
| A | 漁具定置箇所 | 区画漁業 | | P漁具定置箇所 | Polygon, MultiPolygon | 19,025 | 区画漁業 | fishery | fishery_area | Fishery extent has entanglement and operational relevance; confirm fishery-area grouping and naming. | YES |
|
||||||
|
| A | 漁具定置箇所 | 定置漁業 | | P漁具定置箇所 | Polygon, MultiPolygon | 3,570 | 定置漁業 | fishery | fishery_area | Fixed fishery installations should be checked against other fishery-area types for canonical consolidation. | YES |
|
||||||
|
| B | 海岸線等深線等 | 浮施設・桟橋 | | L基本線 / P基本線 | LineString, MultiLineString, Polygon, MultiPolygon | 30,090 | 浮施設・桟橋 | infrastructure | structure | Strong multi-geometry ambiguity across line and area representations; confirm unify vs split between centerline and footprint semantics. | YES |
|
||||||
|
| B | 海岸線等深線等 | 未測海域 | | L危険界 / P基本線 | LineString, Polygon, MultiPolygon | 4,554 | 未測海域 | water_area | water_area | Appears both as boundary and area extent; confirm whether unsurveyed-region boundary should be separated from area fill. | YES |
|
||||||
|
| B | 施設・境界線等 | 海底線 (電信電話) | | L海底線 | LineString, MultiLineString | 12,817 | 海底線 (電信電話) | utility | utility_line | Utility line can also imply entanglement / anchor risk; confirm utility vs hazard emphasis. | YES |
|
||||||
|
| B | 施設・境界線等 | 海底輸送管 (水) | | L海底線 | LineString, MultiLineString | 655 | 海底輸送管 (水) | utility | utility_line | Pipeline semantics are operationally important and come from a container layer; confirm naming and grouping. | YES |
|
||||||
|
| B | 施設・境界線等 | 養殖場 | | P施設・境界線等 | Polygon | 716 | 養殖場 | fishery | fishery_area | Fishery object currently originates from a facilities / boundary container layer; confirm semantic extraction is correct. | YES |
|
||||||
|
| B | 施設・境界線等 | 漁網 | | P施設・境界線等 | Polygon | 178 | 漁網 | fishery | fishery_area | Net-related entanglement risk should be confirmed as fishery semantics rather than generic facility content. | YES |
|
||||||
|
| B | 航路 | 航路 (法律による航路) | | L航路 / P航路 | LineString, Polygon, MultiPolygon | 404 | 航路 (法律による航路) | route | route_reference | Legal route appears as line and area; confirm whether centerline guidance and regulated area should be split. | YES |
|
||||||
|
| C | | | | P基本線ククリ | LineString, MultiLineString | 1,080,780 | P基本線ククリ | overlay_support | render_support | High-volume clip / outline layer likely acts as rendering support rather than semantic object content. | YES |
|
||||||
|
| C | | | | P危険界ククリ | LineString, MultiLineString | 51,962 | P危険界ククリ | overlay_support | render_support | Source-preserved hazard-outline layer; confirm it should stay container / render-only. | YES |
|
||||||
|
| C | | | | P投錨注意障害物ククリ | LineString, MultiLineString | 8,743 | P投錨注意障害物ククリ | overlay_support | render_support | Outline layer around anchoring hazards may be symbolic support rather than independent semantic object. | YES |
|
||||||
|
| C | | | | P航路ククリ | LineString, MultiLineString | 864 | P航路ククリ | overlay_support | render_support | Route clip / outline layer should likely remain render support; confirm no semantic promotion is needed. | YES |
|
||||||
|
| C | | | | L海底地形 | LineString, MultiLineString | 2,075,172 | L海底地形 | overlay_support | render_support | Very large source-preserved layer may be bathymetry render support rather than end-user semantic taxonomy. | YES |
|
||||||
|
| C | | | | p地名 | Point | 36,247 | p地名 | overlay_support | render_support | Label layer behaves as background annotation support, not operational semantic object content. | NO |
|
||||||
|
| C | | | | p地名陸 | Point | 35,388 | p地名陸 | overlay_support | render_support | Label layer behaves as background annotation support, not operational semantic object content. | NO |
|
||||||
|
| C | | | | P穴 | Polygon, MultiPolygon | 367,217 | P穴 | overlay_support | render_support | Land-mask support layer appears stable as rendering support and does not currently require semantic taxonomy splitting. | NO |
|
||||||
|
|
||||||
|
## 4 Ambiguity Categories
|
||||||
|
|
||||||
|
### Multi-Geometry Ambiguity
|
||||||
|
|
||||||
|
Current high-value multi-geometry objects include:
|
||||||
|
|
||||||
|
- 浮施設・桟橋: 30,090 features across `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`
|
||||||
|
- 険悪物: 26,836 features across `Point`, `Polygon`, `MultiPolygon`
|
||||||
|
- 魚礁: 16,608 features across `Point`, `Polygon`, `MultiPolygon`
|
||||||
|
- 未測海域: 4,554 features across `LineString`, `Polygon`, `MultiPolygon`
|
||||||
|
- 航路 (法律による航路): 404 features across `LineString`, `Polygon`, `MultiPolygon`
|
||||||
|
- 錨泊 (指定)地: 234 features across `Point`, `Polygon`
|
||||||
|
|
||||||
|
Primary review question: should these remain one canonical object with mixed geometry support, or should point-symbol, line-boundary, and area-extent variants be split into separate semantic objects?
|
||||||
|
|
||||||
|
### Navigation Meaning Ambiguity
|
||||||
|
|
||||||
|
The following objects directly affect collision, grounding, anchor, or entanglement reasoning and should be reviewed first:
|
||||||
|
|
||||||
|
- 暗岩
|
||||||
|
- 洗岩
|
||||||
|
- 干出岩
|
||||||
|
- 険悪物
|
||||||
|
- 魚礁
|
||||||
|
- 浅所危険界
|
||||||
|
- 漁網
|
||||||
|
- 養殖場
|
||||||
|
- 海底線 / 海底輸送管
|
||||||
|
|
||||||
|
Primary review question: does each object belong in `hazard`, `fishery`, `utility`, or another family when detection behavior is considered?
|
||||||
|
|
||||||
|
### Source Layer Container Ambiguity
|
||||||
|
|
||||||
|
The current layer rules identify several likely container layers:
|
||||||
|
|
||||||
|
- `P基本線`: `container_layer`, 34 semantic object types, 701,625 features
|
||||||
|
- `P施設・境界線等`: `container_layer`, 16 semantic object types, 6,425 features
|
||||||
|
- `L海底線`: `container_layer`, 5 semantic object types, 14,391 features
|
||||||
|
- `L基本線`: `container_layer`, 5 semantic object types, 53,615 features
|
||||||
|
|
||||||
|
Primary review question: should these layers remain source-preserved render containers while downstream products use extracted semantic objects instead?
|
||||||
|
|
||||||
|
### Regulatory / Boundary Ambiguity
|
||||||
|
|
||||||
|
Objects with strong legal or control semantics include:
|
||||||
|
|
||||||
|
- 港則法による境界
|
||||||
|
- 航路 (法律による航路)
|
||||||
|
- 錨泊 (指定)地
|
||||||
|
- 浅所危険界
|
||||||
|
- 未測海域
|
||||||
|
|
||||||
|
Primary review question: should regulatory boundary geometry and filled-area geometry be modeled as the same object type or as separate rule / extent objects?
|
||||||
|
|
||||||
|
## 5 First-Pass Recommended Review Scope
|
||||||
|
|
||||||
|
Focus first on classes and layers most similar to the task brief:
|
||||||
|
|
||||||
|
- 航行危険障害物
|
||||||
|
- 投錨注意障害物
|
||||||
|
- 航路標識点
|
||||||
|
- 航路境界等 / 航路 (法律による航路)
|
||||||
|
- 錨泊地等
|
||||||
|
- 施設・境界線等
|
||||||
|
- 海岸線等深線等 / 海岸線等深線等小縮尺版
|
||||||
|
- 漁具定置箇所
|
||||||
|
|
||||||
|
Recommended first-pass order:
|
||||||
|
|
||||||
|
1. Confirm hazard objects and shoal / reef / wreck naming
|
||||||
|
2. Confirm navigation-mark subtype normalization
|
||||||
|
3. Confirm anchorage, route, and regulatory boundary grouping
|
||||||
|
4. Confirm fishery and utility objects extracted from container layers
|
||||||
|
5. Confirm which clip / support layers remain render-only
|
||||||
|
|
||||||
|
## 6 Proposed Review Decisions
|
||||||
|
|
||||||
|
| Candidate Object | Suggested canonical_object_type | Suggested canonical_family | Suggested detection_class | Reason | Manual confirmation required |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 険悪物 | 険悪物 | hazard | obstacle | Large mixed point / area hazard set; important to decide whether symbol and extent remain unified. | YES |
|
||||||
|
| 魚礁 | 魚礁 | hazard | obstacle | Could be treated as hazard, fishery, or hybrid operational object depending on downstream use. | YES |
|
||||||
|
| 暗岩 / 洗岩 / 干出岩 | Keep as separate named hazard objects | hazard | obstacle | Distinct rock-hazard names are meaningful for chart interpretation and should not be collapsed without explicit approval. | YES |
|
||||||
|
| 浅所危険界 | 浅所危険界 | hazard | obstacle | Current suggestion treats it as hazard extent, but it may need a boundary-style subtype. | YES |
|
||||||
|
| 灯 (Lt) and light subtypes | Normalize under light / navigation mark family while preserving subtype label | navigation_aid | navigation_mark | Many shape-driven names likely belong to a common aid-to-navigation family with subtype metadata. | YES |
|
||||||
|
| 錨泊 (指定)地 | 錨泊 (指定)地 | anchorage | anchorage | Point annotation and polygon extent probably represent the same concept, but a split between label and area remains plausible. | YES |
|
||||||
|
| 港則法による境界 | 港則法による境界 | boundary | boundary_control | Legal boundary semantics justify keeping a distinct boundary object if user confirms. | YES |
|
||||||
|
| 航路 (法律による航路) | 航路 (法律による航路) | route | route_reference | Line and polygon forms may need separate centerline and regulated-area objects. | YES |
|
||||||
|
| 浮施設・桟橋 | 浮施設・桟橋 | infrastructure | structure | Strong line / area dual representation suggests a possible split into linear and areal facility objects. | YES |
|
||||||
|
| 未測海域 | 未測海域 | water_area | water_area | Boundary plus filled-area modeling should be reviewed before finalizing canonical geometry rules. | YES |
|
||||||
|
| 海底線 / 海底輸送管 | Keep separate utility object types by infrastructure kind | utility | utility_line | Operationally relevant and extracted from a container layer; keep distinct unless user prefers a broader subsea-utility object. | YES |
|
||||||
|
| 区画漁業 / 定置漁業 / 養殖場 / 漁網 | Keep as fishery-area objects with subtype distinction | fishery | fishery_area | These support entanglement-aware review and likely should stay distinct within one fishery family. | YES |
|
||||||
|
| P基本線ククリ and similar `ククリ` layers | Keep as source-preserved render support | overlay_support | render_support | Current evidence suggests these are clip / outline / render-control layers rather than semantic end objects. | YES |
|
||||||
|
| p地名 / p地名陸 / P穴 | Keep as render-support / background objects | overlay_support | render_support | These appear stable and low-risk as non-semantic support layers. | NO |
|
||||||
|
|
||||||
|
## 7 Layer Review Appendix
|
||||||
|
|
||||||
|
| source_layer | semantic_granularity | current_rule_guess | review_reason | needs_manual_review |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| P基本線 | container_layer | Container surface layer feeding bathymetry, hazard, water-area, and facility objects | Very high semantic fan-out: 34 object types across 701,625 features. | YES |
|
||||||
|
| P施設・境界線等 | container_layer | Container surface layer for facilities, boundaries, fishery, and place objects | Mixed semantic content suggests it should remain a container layer rather than a single semantic layer. | YES |
|
||||||
|
| L海底線 | container_layer | Container linear layer for cable and pipeline objects | Contains multiple subsea utility types with shared rendering source. | YES |
|
||||||
|
| L基本線 | container_layer | Container linear layer for piers, berths, removed objects, and linear marine structures | Same source layer mixes several operationally different linear object types. | YES |
|
||||||
|
| p航路標識群 | semantic_layer | Stable semantic point layer with shape-driven navigation-mark subtypes | Semantic layer is good, but subtype normalization still needs manual confirmation. | YES |
|
||||||
|
| p投錨注意障害物 | semantic_layer | Stable semantic hazard layer with mixed object meanings | Contains hazards, reefs, current phenomena, and seabed-related points that may need family confirmation. | YES |
|
||||||
|
| p航行危険障害物 | semantic_layer | Stable semantic hazard layer | High-priority safety layer; naming and family assignments should be checked even if the layer itself stays semantic. | YES |
|
||||||
|
| P錨泊地等 | semantic_layer | Semantic area layer for anchorage and restriction objects | Needs confirmation that point and area anchorage variants are modeled consistently. | YES |
|
||||||
|
| L等深線 | semantic_layer | Stable contour layer | Geometry behavior looks expected for contour lines; low ambiguity. | NO |
|
||||||
|
| p地名 | style_or_source_layer | Source-preserved label layer | Background label support; not currently a semantic review focus. | NO |
|
||||||
|
| p地名陸 | style_or_source_layer | Source-preserved land label layer | Background label support; not currently a semantic review focus. | NO |
|
||||||
|
| P穴 | style_or_source_layer | Source-preserved area support layer | Stable render-support role; no strong evidence for semantic promotion. | NO |
|
||||||
|
|
||||||
|
## 8 Review Outcome Guidance
|
||||||
|
|
||||||
|
Please confirm the following decisions after manual review:
|
||||||
|
|
||||||
|
1. Confirm canonical object naming for hazards, aids to navigation, route controls, anchorage objects, fishery objects, and utility objects.
|
||||||
|
2. Confirm family grouping for ambiguous objects such as `魚礁`, `浅所危険界`, `未測海域`, and subsea infrastructure.
|
||||||
|
3. Confirm detection grouping, especially where the current suggestion uses `obstacle`, `navigation_mark`, `anchorage`, `boundary_control`, `utility_line`, or `fishery_area`.
|
||||||
|
4. Confirm geometry unification rules for mixed point / line / area objects such as `浮施設・桟橋`, `険悪物`, `魚礁`, `未測海域`, `航路 (法律による航路)`, and `錨泊 (指定)地`.
|
||||||
|
5. Confirm which source layers remain container or render-support layers, especially `P基本線`, `P施設・境界線等`, `L海底線`, `L基本線`, and the `ククリ` layers.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"original_feature_instances": 136461,
|
||||||
|
"engineering_feature_instances": 136461,
|
||||||
|
"result_count": 136461,
|
||||||
|
"status_counts": {
|
||||||
|
"exact_match": 136461
|
||||||
|
},
|
||||||
|
"source_layer_issue_counts": [],
|
||||||
|
"mismatch_examples": []
|
||||||
|
}
|
||||||
29
NavSea_Original_vs_Engineering_Render_Audit_Karatsu_10nm.md
Normal file
29
NavSea_Original_vs_Engineering_Render_Audit_Karatsu_10nm.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# NavSea 原始版 vs 工程版渲染审计报告
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- 原始样式: `/mnt/sda1/www/newpec/style.patched.local.json`
|
||||||
|
- 工程样式: `/mnt/sda1/www/newpec/navsea-engineering.json`
|
||||||
|
- 原始瓦片根目录: `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
|
||||||
|
- 工程瓦片根目录: `/home/wwwroot/pbf-engineering-karatsu-10nm`
|
||||||
|
- 原始 feature 实例数: `136461`
|
||||||
|
- 工程 feature 实例数: `136461`
|
||||||
|
- 审计结果数: `136461`
|
||||||
|
|
||||||
|
## 匹配口径
|
||||||
|
|
||||||
|
- 主键优先使用 legacy `fid`。
|
||||||
|
- 没有 `fid` 的对象,回退到 `geometry + 稳定旧属性`。
|
||||||
|
- 审计粒度保留 tile 实例,因为渲染具有 zoom 敏感性。
|
||||||
|
|
||||||
|
## 结果统计
|
||||||
|
|
||||||
|
- `exact_match`: `136461`
|
||||||
|
|
||||||
|
## 主要问题层
|
||||||
|
|
||||||
|
- 没有发现差异。
|
||||||
|
|
||||||
|
## 差异样例
|
||||||
|
|
||||||
|
- 没有差异样例。
|
||||||
10
NavSea_Original_vs_Engineering_Render_Audit_Kyushu.json
Normal file
10
NavSea_Original_vs_Engineering_Render_Audit_Kyushu.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"original_feature_instances": 2301189,
|
||||||
|
"engineering_feature_instances": 2301189,
|
||||||
|
"result_count": 2301189,
|
||||||
|
"status_counts": {
|
||||||
|
"exact_match": 2301189
|
||||||
|
},
|
||||||
|
"source_layer_issue_counts": [],
|
||||||
|
"mismatch_examples": []
|
||||||
|
}
|
||||||
29
NavSea_Original_vs_Engineering_Render_Audit_Kyushu.md
Normal file
29
NavSea_Original_vs_Engineering_Render_Audit_Kyushu.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# NavSea 原始版 vs 工程版渲染审计报告
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- 原始样式: `/mnt/sda1/www/newpec/style.patched.local.json`
|
||||||
|
- 工程样式: `/mnt/sda1/www/newpec/navsea-engineering-kyushu.json`
|
||||||
|
- 原始瓦片根目录: `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
|
||||||
|
- 工程瓦片根目录: `/home/wwwroot/pbf-engineering-kyushu`
|
||||||
|
- 原始 feature 实例数: `2301189`
|
||||||
|
- 工程 feature 实例数: `2301189`
|
||||||
|
- 审计结果数: `2301189`
|
||||||
|
|
||||||
|
## 匹配口径
|
||||||
|
|
||||||
|
- 主键优先使用 legacy `fid`。
|
||||||
|
- 没有 `fid` 的对象,回退到 `geometry + 稳定旧属性`。
|
||||||
|
- 审计粒度保留 tile 实例,因为渲染具有 zoom 敏感性。
|
||||||
|
|
||||||
|
## 结果统计
|
||||||
|
|
||||||
|
- `exact_match`: `2301189`
|
||||||
|
|
||||||
|
## 主要问题层
|
||||||
|
|
||||||
|
- 没有发现差异。
|
||||||
|
|
||||||
|
## 差异样例
|
||||||
|
|
||||||
|
- 没有差异样例。
|
||||||
405
NavSea_Render_Semantic_Redesign_Report.md
Normal file
405
NavSea_Render_Semantic_Redesign_Report.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# NavSea 渲染语义重构报告
|
||||||
|
|
||||||
|
## 1 目标
|
||||||
|
|
||||||
|
本报告提出一套更清晰的 NavSea PBF 交付渲染描述体系。
|
||||||
|
|
||||||
|
这次重构的目标,不是立刻移除旧的海图渲染体系,而是把下面三件事情拆开:
|
||||||
|
|
||||||
|
- 对象本身的语义
|
||||||
|
- 海图渲染意图
|
||||||
|
- 最终样式实现
|
||||||
|
|
||||||
|
这样 NavSea 才能同时支持:
|
||||||
|
|
||||||
|
- 兼容旧海图风格的渲染
|
||||||
|
- 更清晰、可维护的样式逻辑
|
||||||
|
- 碰撞检测、检索与语义分析
|
||||||
|
- 逐步摆脱对原始旧海图编码的强依赖
|
||||||
|
|
||||||
|
## 2 当前问题
|
||||||
|
|
||||||
|
当前渲染栈把多种职责混在了一起。
|
||||||
|
|
||||||
|
### 2.1 当前渲染依赖的输入
|
||||||
|
|
||||||
|
旧版渲染主要依赖这些字段:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `明弧/分孤`
|
||||||
|
- `表示位置`
|
||||||
|
- `名称`
|
||||||
|
- 展开的 `at` 属性
|
||||||
|
|
||||||
|
这些字段当然有用,但它们的问题也很明显:它们直接承载的是“海图怎么画”的细节编码,而不是清晰、稳定的渲染语义,因此维护成本高、理解成本高、迁移成本也高。
|
||||||
|
|
||||||
|
### 2.2 当前已经叠加的新语义
|
||||||
|
|
||||||
|
新 `pbf` 已经增加了这些语义字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `canonical_family`
|
||||||
|
- `semantic_key`
|
||||||
|
- `detection_key`
|
||||||
|
- `render_layer`
|
||||||
|
|
||||||
|
这些字段已经能很好地服务于检测和分类,但还不足以完整替代海图渲染所需的细粒度表达。
|
||||||
|
|
||||||
|
### 2.3 当前的核心缺口
|
||||||
|
|
||||||
|
现在的数据层同时存在两套东西:
|
||||||
|
|
||||||
|
- 面向海图绘制细节的旧编码
|
||||||
|
- 面向系统理解的语义分类
|
||||||
|
|
||||||
|
但缺少一层专门回答“应该怎么画”的渲染语义层。也就是目前还没有一套稳定字段,明确回答这些问题:
|
||||||
|
|
||||||
|
- 该使用哪一类符号族
|
||||||
|
- 该按点、线、面还是文字渲染
|
||||||
|
- 该显示什么文本
|
||||||
|
- 渲染优先级是什么
|
||||||
|
- 应该选用哪一种线型、面型或符号变体
|
||||||
|
|
||||||
|
## 3 建议的三层模型
|
||||||
|
|
||||||
|
NavSea 的渲染体系建议拆成三层。
|
||||||
|
|
||||||
|
### 3.1 A 层:对象语义层
|
||||||
|
|
||||||
|
这一层回答“这是什么对象”。
|
||||||
|
|
||||||
|
代表字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `canonical_family`
|
||||||
|
- `detection_key`
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
- `暗岩`
|
||||||
|
- `魚礁`
|
||||||
|
- `港湾灯台`
|
||||||
|
- `港則法による境界`
|
||||||
|
|
||||||
|
### 3.2 B 层:渲染语义层
|
||||||
|
|
||||||
|
这一层回答“这个对象在海图上应该怎么画”。
|
||||||
|
|
||||||
|
这是本报告建议新增的核心层。
|
||||||
|
|
||||||
|
代表字段:
|
||||||
|
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_label_text`
|
||||||
|
- `chart_label_subtext`
|
||||||
|
- `chart_label_anchor`
|
||||||
|
- `chart_priority`
|
||||||
|
|
||||||
|
### 3.3 C 层:样式层
|
||||||
|
|
||||||
|
MapLibre 样式层尽量只消费“渲染语义层”以及少量保留的原始字段。
|
||||||
|
|
||||||
|
这样可以让样式规则明显缩短,也更容易维护和演进。
|
||||||
|
|
||||||
|
## 4 建议新增的渲染字段
|
||||||
|
|
||||||
|
建议在新的交付 `pbf` 中补充如下字段。
|
||||||
|
|
||||||
|
### 4.1 核心渲染字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `chart_render_type` | string | 主渲染类型:`symbol`、`line`、`fill`、`label`、`none` |
|
||||||
|
| `chart_symbol_family` | string | 符号族,例如 `navigation_light`、`buoy`、`beacon`、`hazard`、`facility`、`depth_mark` |
|
||||||
|
| `chart_symbol_code` | string | 稳定的符号编码,例如 `lighthouse`、`rock_awash`、`anchorage_mark` |
|
||||||
|
| `chart_line_style` | string | 稳定的线型预设,例如 `boundary_dashed`、`contour_minor`、`subsea_cable` |
|
||||||
|
| `chart_fill_style` | string | 稳定的填充预设,例如 `depth_zone_0_5`、`fishery_area`、`hazard_area` |
|
||||||
|
| `chart_priority` | integer | 绘制优先级,用于排序和碰撞决策 |
|
||||||
|
| `chart_visibility_min` | integer | 最小显示缩放级别 |
|
||||||
|
| `chart_visibility_max` | integer | 最大显示缩放级别 |
|
||||||
|
|
||||||
|
### 4.2 标注字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `chart_label_text` | string | 主标注文本 |
|
||||||
|
| `chart_label_subtext` | string | 次级标注,例如辅助说明或灯略记 |
|
||||||
|
| `chart_label_anchor` | string | 标准化锚点,例如 `top`、`bottom`、`left`、`right`、`center` |
|
||||||
|
| `chart_label_dx` | number | 文本 X 方向偏移 |
|
||||||
|
| `chart_label_dy` | number | 文本 Y 方向偏移 |
|
||||||
|
| `chart_text_style` | string | 文字样式预设,例如 `place_name`、`light_name`、`depth_text` |
|
||||||
|
|
||||||
|
### 4.3 灯标专项字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `light_color_code` | string | 归一化后的灯色,例如 `white`、`red`、`green`、`yellow`、`mixed` |
|
||||||
|
| `light_character_code` | string | 归一化后的灯质,例如 `Fl`、`Oc`、`Iso`、`F`、`V-AIS` |
|
||||||
|
| `light_arc_code` | string | 可选的灯弧编码,用于需要时表达分区差异 |
|
||||||
|
| `light_sector_mode` | string | `sector`、`omni`、`none` |
|
||||||
|
|
||||||
|
### 4.4 危险物 / 区域专项字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `hazard_class` | string | `rock`、`wreck`、`obstruction`、`reef`、`shoal`、`current` 等 |
|
||||||
|
| `hazard_severity` | string | `critical`、`major`、`minor`、`context` |
|
||||||
|
| `area_usage_class` | string | `anchorage`、`route`、`fishery`、`restricted`、`facility`、`land`、`water` |
|
||||||
|
|
||||||
|
## 5 建议的映射策略
|
||||||
|
|
||||||
|
新的渲染语义层,应该同时从以下两类输入推导得到:
|
||||||
|
|
||||||
|
- 新的语义分类字段
|
||||||
|
- 为保留海图细节而保留的部分旧字段
|
||||||
|
|
||||||
|
### 5.1 映射原则
|
||||||
|
|
||||||
|
对象身份和大类语义,优先使用新的 taxonomy 字段。
|
||||||
|
|
||||||
|
只有在确实需要保留海图细节表现时,才继续引用旧字段。
|
||||||
|
|
||||||
|
### 5.2 映射示例
|
||||||
|
|
||||||
|
#### 灯标 / 航标
|
||||||
|
|
||||||
|
输入字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `名称`
|
||||||
|
|
||||||
|
输出字段:
|
||||||
|
|
||||||
|
- `chart_render_type = symbol`
|
||||||
|
- `chart_symbol_family = navigation_light`
|
||||||
|
- `chart_symbol_code = lighthouse | light_beacon | buoy | beacon | vais`
|
||||||
|
- `light_color_code = ...`
|
||||||
|
- `light_character_code = ...`
|
||||||
|
- `chart_label_text = 名称`
|
||||||
|
- `chart_label_subtext = 灯略記`
|
||||||
|
|
||||||
|
#### 危险点状对象
|
||||||
|
|
||||||
|
输入字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `分類番号`
|
||||||
|
|
||||||
|
输出字段:
|
||||||
|
|
||||||
|
- `chart_render_type = symbol`
|
||||||
|
- `chart_symbol_family = hazard`
|
||||||
|
- `chart_symbol_code = rock_awash | isolated_danger | wreck | reef | obstruction`
|
||||||
|
- `hazard_class = rock | reef | wreck | obstruction`
|
||||||
|
- `hazard_severity = critical | major | minor`
|
||||||
|
|
||||||
|
#### 危险面 / 鱼礁区 / 礁盘区
|
||||||
|
|
||||||
|
输入字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `分類番号`
|
||||||
|
- geometry type
|
||||||
|
|
||||||
|
输出字段:
|
||||||
|
|
||||||
|
- `chart_render_type = fill`
|
||||||
|
- `chart_fill_style = hazard_area | reef_area | obstruction_area`
|
||||||
|
- `hazard_class = reef | obstruction | hazard_zone`
|
||||||
|
|
||||||
|
#### 水深分带
|
||||||
|
|
||||||
|
输入字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
|
||||||
|
输出字段:
|
||||||
|
|
||||||
|
- `chart_render_type = fill`
|
||||||
|
- `chart_fill_style = depth_zone_0_2 | depth_zone_0_5 | depth_zone_5_10 ...`
|
||||||
|
- `area_usage_class = water`
|
||||||
|
|
||||||
|
#### 边界类对象
|
||||||
|
|
||||||
|
输入字段:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
|
||||||
|
输出字段:
|
||||||
|
|
||||||
|
- `chart_render_type = line`
|
||||||
|
- `chart_line_style = regulatory_boundary | route_boundary | hazard_boundary`
|
||||||
|
|
||||||
|
## 6 建议继续保留的原始字段
|
||||||
|
|
||||||
|
即使完成重构,也不建议立刻把所有旧字段从交付 `pbf` 里删除。
|
||||||
|
|
||||||
|
### 6.1 为渲染保留
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `明弧/分孤`
|
||||||
|
- `表示位置`
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `日本語地名`
|
||||||
|
- `英文字地名`
|
||||||
|
|
||||||
|
### 6.2 为关联 / 检索保留
|
||||||
|
|
||||||
|
- `fid`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `canonical_family`
|
||||||
|
- `detection_key`
|
||||||
|
|
||||||
|
### 6.3 后续可评估移除
|
||||||
|
|
||||||
|
这些字段建议在新的渲染语义层稳定之后,再评估是否移除:
|
||||||
|
|
||||||
|
- `semantic_key`
|
||||||
|
- 大块原始 `at` 负载,前提是所需信息都已经物化为显式字段
|
||||||
|
- 不再被样式、检索或检测引用的旧字段
|
||||||
|
|
||||||
|
## 7 样式架构建议
|
||||||
|
|
||||||
|
### 7.1 当前样式模式
|
||||||
|
|
||||||
|
当前样式主要是直接对旧编码做分支判断。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- 如果 `分類番号 = 403`,就使用某一个图标
|
||||||
|
- 如果 `表示用番号 = 31135504`,就切换到另一个图标
|
||||||
|
|
||||||
|
这种方式精确,但维护难度很高。
|
||||||
|
|
||||||
|
### 7.2 建议中的新样式模式
|
||||||
|
|
||||||
|
未来样式应优先基于“渲染语义字段”分支。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- 如果 `chart_symbol_family = navigation_light`,就进入灯标符号体系
|
||||||
|
- 如果 `chart_fill_style = depth_zone_0_5`,就使用浅水区填充
|
||||||
|
- 如果 `chart_line_style = regulatory_boundary`,就使用监管边界线型
|
||||||
|
|
||||||
|
只有少量特殊情形,才继续回退到旧字段。
|
||||||
|
|
||||||
|
### 7.3 预期结果
|
||||||
|
|
||||||
|
这样做会带来以下收益:
|
||||||
|
|
||||||
|
- 样式文件更短
|
||||||
|
- 规则归属更清晰
|
||||||
|
- 后续迁移更容易
|
||||||
|
- 多产品之间更容易保持一致
|
||||||
|
|
||||||
|
## 8 迁移计划
|
||||||
|
|
||||||
|
### 阶段 1:混合交付
|
||||||
|
|
||||||
|
保留:
|
||||||
|
|
||||||
|
- 旧渲染关键字段
|
||||||
|
- 新的语义 taxonomy 字段
|
||||||
|
|
||||||
|
新增:
|
||||||
|
|
||||||
|
- 新的渲染语义字段
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
|
||||||
|
- 旧样式继续承担兼容渲染
|
||||||
|
- 混合样式用于逐步迁移
|
||||||
|
|
||||||
|
### 阶段 2:迁移到语义样式
|
||||||
|
|
||||||
|
构建一套新的样式,优先读取:
|
||||||
|
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_label_text`
|
||||||
|
|
||||||
|
旧字段只在少量细节场景下兜底。
|
||||||
|
|
||||||
|
### 阶段 3:稳定渲染字段体系
|
||||||
|
|
||||||
|
当渲染语义层经过验证后:
|
||||||
|
|
||||||
|
- 逐步减少样式对原始旧编码的直接依赖
|
||||||
|
- 只保留少量用于回退和兼容的旧字段
|
||||||
|
|
||||||
|
## 9 示例输出 Feature 模型
|
||||||
|
|
||||||
|
灯台示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fid": 12345678,
|
||||||
|
"canonical_object_type": "港湾灯台",
|
||||||
|
"canonical_family": "navigation_aid",
|
||||||
|
"detection_key": "symbol:港湾灯台",
|
||||||
|
"chart_render_type": "symbol",
|
||||||
|
"chart_symbol_family": "navigation_light",
|
||||||
|
"chart_symbol_code": "lighthouse",
|
||||||
|
"chart_priority": 900,
|
||||||
|
"chart_visibility_min": 7,
|
||||||
|
"chart_label_text": "鷹島灯台",
|
||||||
|
"chart_label_subtext": "Fl W 5s",
|
||||||
|
"light_color_code": "white",
|
||||||
|
"light_character_code": "Fl"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
鱼礁危险区示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fid": 23456789,
|
||||||
|
"canonical_object_type": "魚礁",
|
||||||
|
"canonical_family": "hazard",
|
||||||
|
"detection_key": "mixed:魚礁",
|
||||||
|
"chart_render_type": "fill",
|
||||||
|
"chart_symbol_family": "hazard",
|
||||||
|
"chart_fill_style": "reef_area",
|
||||||
|
"hazard_class": "reef",
|
||||||
|
"hazard_severity": "major",
|
||||||
|
"chart_priority": 850
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10 建议的下一步
|
||||||
|
|
||||||
|
建议按下面的顺序推进实现:
|
||||||
|
|
||||||
|
1. 在出瓦片流程中定义新的渲染语义字段
|
||||||
|
2. 将这些字段物化到交付 `pbf`
|
||||||
|
3. 在过渡期继续保留旧的渲染关键字段
|
||||||
|
4. 构建一套优先读取新渲染语义字段的混合样式
|
||||||
|
5. 优先验证灯标体系、危险物体系、鱼业/鱼礁对象和边界对象,再推进更大范围清理
|
||||||
|
|
||||||
|
## 11 结论
|
||||||
|
|
||||||
|
渲染描述体系完全可以重构,而且值得重构。
|
||||||
|
|
||||||
|
正确的方向,不是立刻抛弃旧海图字段,而是在“原始海图属性”和“最终样式”之间,增加一层专门的“渲染语义层”。
|
||||||
|
|
||||||
|
这层语义应当提供稳定、明确、可维护的海图渲染含义,同时在必要时保留与旧海图表达方式的兼容能力。
|
||||||
677
NavSea_Semantic_Pipeline.md
Normal file
677
NavSea_Semantic_Pipeline.md
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
# NavSea 语义重建流水线说明
|
||||||
|
|
||||||
|
## 1. 流水线概览
|
||||||
|
|
||||||
|
NavSea 当前的数据库流水线可以分成两段:
|
||||||
|
|
||||||
|
1. 上游导入段
|
||||||
|
将原始 PBF 拆入数据库,形成 `features` 与 `properties` 两张核心表。
|
||||||
|
|
||||||
|
2. 语义重建与校验段
|
||||||
|
在不修改现有渲染逻辑、不修改 `style.json`、不修改原始 PBF 结构的前提下,建立语义叠加层与校验层。
|
||||||
|
|
||||||
|
当前真实的流水线可概括为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
原始 PBF
|
||||||
|
→ PBF 导入(外部前置步骤,结果进入 features / properties)
|
||||||
|
→ 属性展开(at_attributes)
|
||||||
|
→ 语义投影(feature_semantic)
|
||||||
|
→ 对象类型候选生成(object_type_candidates)
|
||||||
|
→ 审计与统计(object_catalog / object_type_stats / style_layers / tile_density)
|
||||||
|
→ 规范对象规则(canonical_object_rules / canonical_layer_rules)
|
||||||
|
→ 语义叠加层构建(pbf_relayer_candidates)
|
||||||
|
→ 检测目录与渲染兼容层(pbf_detection_catalog / pbf_render_compatibility)
|
||||||
|
→ 语义校验(classification / geometry / render / detection / spatial)
|
||||||
|
→ 最终 NavSea 语义数据集(数据库中的已校验语义叠加结果)
|
||||||
|
```
|
||||||
|
|
||||||
|
需要明确一点:当前仓库中的脚本并不直接生成“新的 NavSea 矢量瓦片文件”。当前系统产出的是一套可重建、可审计、可校验的数据库语义数据集;下游若要生成新的 NavSea 矢量瓦片,应以这些表为输入继续执行后续生产步骤。
|
||||||
|
|
||||||
|
## 2. 分步流水线
|
||||||
|
|
||||||
|
### Step 00 - 原始 PBF 导入
|
||||||
|
|
||||||
|
目的
|
||||||
|
将原始 PBF 解析并导入数据库。
|
||||||
|
|
||||||
|
输入
|
||||||
|
原始 PBF 数据
|
||||||
|
|
||||||
|
输出
|
||||||
|
`features`
|
||||||
|
`properties`
|
||||||
|
|
||||||
|
说明
|
||||||
|
这一步是当前语义流水线的前置条件。当前仓库中没有提供这一步的导入脚本,但后续所有步骤都假定这两张表已存在。
|
||||||
|
|
||||||
|
### Step 01 - 提取 `at` 属性
|
||||||
|
|
||||||
|
目的
|
||||||
|
把 `properties` 表中 `k='at'` 的 JSON 属性展开成结构化键值表。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`properties`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`at_attributes`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 02 - 构建语义投影表
|
||||||
|
|
||||||
|
目的
|
||||||
|
把原始要素与已展开的属性汇总到单要素视角,形成后续语义分析使用的基础表。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`features`
|
||||||
|
`at_attributes`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`feature_semantic`
|
||||||
|
|
||||||
|
字段要点
|
||||||
|
`feature_id`、`z/x/y`、`vt_layer`、`geom_type`、`class_name`、`shape_name`、`layer_name`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 03 - 构建对象目录
|
||||||
|
|
||||||
|
目的
|
||||||
|
从 `feature_semantic` 聚合出对象目录,用于观察每类对象在不同来源层和几何类型上的分布。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`feature_semantic`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`object_catalog`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 04 - 生成对象类型候选
|
||||||
|
|
||||||
|
目的
|
||||||
|
为每个要素生成对象类型候选,优先级为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
class_name > shape_name > vt_layer
|
||||||
|
```
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`feature_semantic`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`object_type_candidates`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 05 - 构建对象类型统计
|
||||||
|
|
||||||
|
目的
|
||||||
|
统计每个对象类型与几何类型的分布,为规则生成和几何白名单提供依据。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`object_type_candidates`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`object_type_stats`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 06 - 构建样式层分析
|
||||||
|
|
||||||
|
目的
|
||||||
|
从 `style.json` 中抽取现有渲染层信息,并与对象候选做交叉比对,确认渲染轨仍可复用。
|
||||||
|
|
||||||
|
输入
|
||||||
|
`src/pbf/style.json`
|
||||||
|
`object_type_candidates`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`style_layers`
|
||||||
|
`style_mapping`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 07 - 构建密度与基础异常分析
|
||||||
|
|
||||||
|
目的
|
||||||
|
计算瓦片密度与审计阶段的几何异常,为后续语义校验提供分析输入。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`features`
|
||||||
|
`feature_semantic`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`tile_density`
|
||||||
|
`tile_layer_density`
|
||||||
|
`tile_density_top100`
|
||||||
|
`anomaly_navigation_geom`
|
||||||
|
`anomaly_reef_geom`
|
||||||
|
`geometry_consistency_audit`
|
||||||
|
|
||||||
|
说明
|
||||||
|
`geometry_consistency_audit` 是审计阶段保留下来的旧版几何分布快照,不是当前正式校验表。
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_audit.py`
|
||||||
|
|
||||||
|
### Step 08 - 构建来源层规则
|
||||||
|
|
||||||
|
目的
|
||||||
|
基于现有 `source_layer`、样式绑定关系和语义密度,判断每个来源层属于:
|
||||||
|
|
||||||
|
```text
|
||||||
|
semantic_layer
|
||||||
|
container_layer
|
||||||
|
style_or_source_layer
|
||||||
|
```
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`feature_semantic`
|
||||||
|
`style_layers`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`canonical_layer_rules`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_relayer.py`
|
||||||
|
|
||||||
|
### Step 09 - 构建规范对象规则
|
||||||
|
|
||||||
|
目的
|
||||||
|
为每个 `canonical_object_type` 固化规范对象规则,包括:
|
||||||
|
|
||||||
|
- `canonical_family`
|
||||||
|
- `classification_basis`
|
||||||
|
- `source_layer_scope`
|
||||||
|
- `allowed_geom_types`
|
||||||
|
- `preferred_geom_type`
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`object_catalog`
|
||||||
|
`object_type_stats`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`canonical_object_rules`
|
||||||
|
|
||||||
|
补充输出表
|
||||||
|
`object_geometry_allowlist`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_relayer.py`
|
||||||
|
|
||||||
|
### Step 10 - 构建语义叠加层
|
||||||
|
|
||||||
|
目的
|
||||||
|
在保留现有渲染轨的前提下,为每个要素附加:
|
||||||
|
|
||||||
|
- 规范对象类型
|
||||||
|
- 规范对象族
|
||||||
|
- 检测键
|
||||||
|
- 语义键
|
||||||
|
- 渲染兼容层
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`feature_semantic`
|
||||||
|
`object_type_candidates`
|
||||||
|
`canonical_layer_rules`
|
||||||
|
`canonical_object_rules`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`pbf_relayer_candidates`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_relayer.py`
|
||||||
|
|
||||||
|
### Step 11 - 构建语义统计与检测目录
|
||||||
|
|
||||||
|
目的
|
||||||
|
从语义叠加层生成两个下游核心视图:
|
||||||
|
|
||||||
|
1. 渲染兼容统计
|
||||||
|
用于确认 `render_layer` 与原有 `source_layer` 的数量关系
|
||||||
|
|
||||||
|
2. 检测目录
|
||||||
|
用于安全检测、对象检测和规则匹配
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`pbf_relayer_candidates`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`pbf_source_object_stats`
|
||||||
|
`pbf_render_compatibility`
|
||||||
|
`pbf_detection_catalog`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_relayer.py`
|
||||||
|
|
||||||
|
### Step 12 - 运行分类校验
|
||||||
|
|
||||||
|
目的
|
||||||
|
确认每个 `canonical_object_type` 的映射是否稳定,是否存在未知对象、歧义映射或规则冲突。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`pbf_source_object_stats`
|
||||||
|
`canonical_object_rules`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`classification_validation`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_semantic_validation.py`
|
||||||
|
|
||||||
|
### Step 13 - 运行几何一致性校验
|
||||||
|
|
||||||
|
目的
|
||||||
|
确认每个规范对象的几何类型是否符合白名单与规则定义。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`pbf_detection_catalog`
|
||||||
|
`object_geometry_allowlist`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`geometry_consistency`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_semantic_validation.py`
|
||||||
|
|
||||||
|
### Step 14 - 运行渲染等价校验
|
||||||
|
|
||||||
|
目的
|
||||||
|
确认语义叠加层没有破坏现有 style 渲染轨。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`style_layers`
|
||||||
|
`features`
|
||||||
|
`pbf_render_compatibility`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`style_render_equivalence`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_semantic_validation.py`
|
||||||
|
|
||||||
|
### Step 15 - 运行检测目录完整性校验
|
||||||
|
|
||||||
|
目的
|
||||||
|
确认 `detection_key` 对象目录稳定且几何一致。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`pbf_detection_catalog`
|
||||||
|
`geometry_consistency`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`detection_catalog_integrity`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_semantic_validation.py`
|
||||||
|
|
||||||
|
### Step 16 - 运行空间密度诊断
|
||||||
|
|
||||||
|
目的
|
||||||
|
基于 `features` 直接计算的瓦片密度和分 zoom 阈值,识别空间热点瓦片。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`tile_density`
|
||||||
|
`tile_layer_density`
|
||||||
|
`features`
|
||||||
|
`pbf_relayer_candidates`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`spatial_anomalies`
|
||||||
|
|
||||||
|
规则要点
|
||||||
|
- `tile_density` 只允许来自 `features`
|
||||||
|
- 每个 zoom 单独计算阈值
|
||||||
|
- 跳过 `z=0`
|
||||||
|
- 支持 `DENSITY_STATISTICS_ERROR`
|
||||||
|
- 每个异常 tile 单独落一行
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_semantic_validation.py`
|
||||||
|
|
||||||
|
### Step 17 - 生成总体验证结论
|
||||||
|
|
||||||
|
目的
|
||||||
|
把分类、几何、渲染、检测目录和空间诊断汇总成一条总状态。
|
||||||
|
|
||||||
|
输入表
|
||||||
|
`classification_validation`
|
||||||
|
`geometry_consistency`
|
||||||
|
`style_render_equivalence`
|
||||||
|
`detection_catalog_integrity`
|
||||||
|
`spatial_anomalies`
|
||||||
|
|
||||||
|
输出表
|
||||||
|
`semantic_validation_summary`
|
||||||
|
|
||||||
|
脚本
|
||||||
|
`navsea_semantic_validation.py`
|
||||||
|
|
||||||
|
## 3. 表依赖图
|
||||||
|
|
||||||
|
### 3.1 主干数据血缘
|
||||||
|
|
||||||
|
```text
|
||||||
|
原始 PBF
|
||||||
|
↓
|
||||||
|
features + properties
|
||||||
|
↓
|
||||||
|
at_attributes
|
||||||
|
↓
|
||||||
|
feature_semantic
|
||||||
|
├─→ object_catalog
|
||||||
|
├─→ object_type_candidates
|
||||||
|
│ ↓
|
||||||
|
│ object_type_stats
|
||||||
|
│
|
||||||
|
└─→ style_layers ← style.json
|
||||||
|
|
||||||
|
object_catalog + object_type_stats
|
||||||
|
↓
|
||||||
|
canonical_object_rules
|
||||||
|
↓
|
||||||
|
object_geometry_allowlist
|
||||||
|
|
||||||
|
feature_semantic + style_layers
|
||||||
|
↓
|
||||||
|
canonical_layer_rules
|
||||||
|
|
||||||
|
feature_semantic + object_type_candidates + canonical_layer_rules + canonical_object_rules
|
||||||
|
↓
|
||||||
|
pbf_relayer_candidates
|
||||||
|
↓
|
||||||
|
pbf_source_object_stats
|
||||||
|
├─→ pbf_render_compatibility
|
||||||
|
└─→ pbf_detection_catalog
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 并行校验血缘
|
||||||
|
|
||||||
|
```text
|
||||||
|
pbf_source_object_stats + canonical_object_rules
|
||||||
|
↓
|
||||||
|
classification_validation
|
||||||
|
|
||||||
|
pbf_detection_catalog + object_geometry_allowlist
|
||||||
|
↓
|
||||||
|
geometry_consistency
|
||||||
|
|
||||||
|
style_layers + features + pbf_render_compatibility
|
||||||
|
↓
|
||||||
|
style_render_equivalence
|
||||||
|
|
||||||
|
pbf_detection_catalog + geometry_consistency
|
||||||
|
↓
|
||||||
|
detection_catalog_integrity
|
||||||
|
|
||||||
|
features
|
||||||
|
↓
|
||||||
|
tile_density + tile_layer_density
|
||||||
|
↓
|
||||||
|
spatial_anomalies
|
||||||
|
|
||||||
|
classification_validation
|
||||||
|
geometry_consistency
|
||||||
|
style_render_equivalence
|
||||||
|
detection_catalog_integrity
|
||||||
|
spatial_anomalies
|
||||||
|
↓
|
||||||
|
semantic_validation_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 表分类
|
||||||
|
|
||||||
|
下表按照当前数据库中的实际表进行分类。
|
||||||
|
|
||||||
|
| 表名 | 分类 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `features` | CORE TABLE | 原始要素主表,保存 `id`、`z/x/y`、`vt_layer`、`geom_type` |
|
||||||
|
| `properties` | CORE TABLE | 原始属性键值表 |
|
||||||
|
| `at_attributes` | SEMANTIC TABLE | `properties.k='at'` 的展开结果 |
|
||||||
|
| `feature_semantic` | SEMANTIC TABLE | 单要素语义投影表 |
|
||||||
|
| `object_type_candidates` | SEMANTIC TABLE | 对象类型候选表 |
|
||||||
|
| `object_catalog` | ANALYSIS TABLE | 对象目录与分布分析 |
|
||||||
|
| `object_type_stats` | ANALYSIS TABLE | 对象类型与几何统计 |
|
||||||
|
| `style_layers` | ANALYSIS TABLE | 从 `style.json` 提取的渲染层信息 |
|
||||||
|
| `style_mapping` | ANALYSIS TABLE | 样式层与对象候选的交叉映射 |
|
||||||
|
| `canonical_layer_rules` | SEMANTIC TABLE | 来源层规则表 |
|
||||||
|
| `canonical_object_rules` | SEMANTIC TABLE | 规范对象规则表 |
|
||||||
|
| `object_geometry_allowlist` | SEMANTIC TABLE | 对象几何白名单 |
|
||||||
|
| `pbf_relayer_candidates` | SEMANTIC TABLE | 语义叠加主表 |
|
||||||
|
| `pbf_source_object_stats` | ANALYSIS TABLE | 语义叠加聚合统计表 |
|
||||||
|
| `pbf_render_compatibility` | ANALYSIS TABLE | 渲染兼容统计表 |
|
||||||
|
| `pbf_detection_catalog` | SEMANTIC TABLE | 安全检测目录表 |
|
||||||
|
| `classification_validation` | VALIDATION TABLE | 分类稳定性校验表 |
|
||||||
|
| `geometry_consistency` | VALIDATION TABLE | 当前正式几何一致性校验表 |
|
||||||
|
| `geometry_consistency_audit` | ANALYSIS TABLE | 审计阶段保留的旧版几何快照 |
|
||||||
|
| `style_render_equivalence` | VALIDATION TABLE | 渲染等价校验表 |
|
||||||
|
| `detection_catalog_integrity` | VALIDATION TABLE | 检测目录完整性校验表 |
|
||||||
|
| `spatial_anomalies` | VALIDATION TABLE | 空间密度异常表 |
|
||||||
|
| `semantic_validation_summary` | VALIDATION TABLE | 整体验证摘要 |
|
||||||
|
| `tile_density` | ANALYSIS TABLE | 每瓦片 feature 数量 |
|
||||||
|
| `tile_layer_density` | ANALYSIS TABLE | 每瓦片、每来源层 feature 数量 |
|
||||||
|
| `tile_density_top100` | ANALYSIS TABLE | 最高密度瓦片 Top100 |
|
||||||
|
| `anomaly_navigation_geom` | ANALYSIS TABLE | 航标类几何异常审计 |
|
||||||
|
| `anomaly_reef_geom` | ANALYSIS TABLE | 鱼礁类几何异常审计 |
|
||||||
|
| `feature_flat` | TEMP TABLE | 当前数据库中的遗留/临时表,不在现行语义脚本主路径上 |
|
||||||
|
|
||||||
|
## 5. 官方执行顺序
|
||||||
|
|
||||||
|
当前建议的正式执行顺序如下:
|
||||||
|
|
||||||
|
1. 导入原始 PBF,生成 `features` 和 `properties`
|
||||||
|
2. 运行 `navsea_audit.py`,构建基础语义表、分析表和密度表
|
||||||
|
3. 运行 `navsea_relayer.py`,生成规范规则与语义叠加层
|
||||||
|
4. 运行 `navsea_semantic_validation.py`,生成分类、几何、渲染、检测目录和空间校验结果
|
||||||
|
5. 读取 `semantic_validation_summary` 确认总体状态
|
||||||
|
6. 以 `pbf_relayer_candidates` 为主表,结合 `features`、`properties`、`at_attributes` 产出下游 NavSea 数据集或矢量瓦片生产输入
|
||||||
|
|
||||||
|
## 6. 执行命令
|
||||||
|
|
||||||
|
### 6.1 构建基础语义与分析表
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_audit.py
|
||||||
|
```
|
||||||
|
|
||||||
|
作用
|
||||||
|
从 `features` / `properties` 出发,构建:
|
||||||
|
|
||||||
|
- `at_attributes`
|
||||||
|
- `feature_semantic`
|
||||||
|
- `object_catalog`
|
||||||
|
- `geometry_consistency_audit`
|
||||||
|
- `object_type_candidates`
|
||||||
|
- `object_type_stats`
|
||||||
|
- `style_layers`
|
||||||
|
- `style_mapping`
|
||||||
|
- `tile_density`
|
||||||
|
- `tile_layer_density`
|
||||||
|
- `tile_density_top100`
|
||||||
|
- 审计异常表
|
||||||
|
|
||||||
|
### 6.2 构建语义叠加层
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_relayer.py
|
||||||
|
```
|
||||||
|
|
||||||
|
作用
|
||||||
|
生成:
|
||||||
|
|
||||||
|
- `canonical_layer_rules`
|
||||||
|
- `canonical_object_rules`
|
||||||
|
- `object_geometry_allowlist`
|
||||||
|
- `pbf_relayer_candidates`
|
||||||
|
- `pbf_source_object_stats`
|
||||||
|
- `pbf_render_compatibility`
|
||||||
|
- `pbf_detection_catalog`
|
||||||
|
|
||||||
|
### 6.3 运行语义校验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_semantic_validation.py
|
||||||
|
```
|
||||||
|
|
||||||
|
作用
|
||||||
|
生成:
|
||||||
|
|
||||||
|
- `classification_validation`
|
||||||
|
- `geometry_consistency`
|
||||||
|
- `style_render_equivalence`
|
||||||
|
- `detection_catalog_integrity`
|
||||||
|
- `spatial_anomalies`
|
||||||
|
- `semantic_validation_summary`
|
||||||
|
|
||||||
|
### 6.4 仅重建密度表
|
||||||
|
|
||||||
|
如果只需要更新空间密度与热点诊断,可以单独执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python - <<'PY'
|
||||||
|
from navsea_audit import NavSeaAudit, DbConfig
|
||||||
|
NavSeaAudit(DbConfig()).build_tile_density()
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
随后再执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_semantic_validation.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 最终输出数据集
|
||||||
|
|
||||||
|
### 7.1 当前系统中的“最终语义数据集”
|
||||||
|
|
||||||
|
当前系统中,最接近“最终 NavSea 语义要素表”的主表是:
|
||||||
|
|
||||||
|
`pbf_relayer_candidates`
|
||||||
|
|
||||||
|
它保存了:
|
||||||
|
|
||||||
|
- `feature_id`
|
||||||
|
- `z/x/y`
|
||||||
|
- `source_layer`
|
||||||
|
- `geom_type`
|
||||||
|
- `class_name`
|
||||||
|
- `shape_name`
|
||||||
|
- `layer_name`
|
||||||
|
- `object_type_source`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `semantic_granularity`
|
||||||
|
- `canonical_family`
|
||||||
|
- `classification_basis`
|
||||||
|
- `source_layer_scope`
|
||||||
|
- `semantic_key`
|
||||||
|
- `detection_key`
|
||||||
|
- `render_layer`
|
||||||
|
|
||||||
|
### 7.2 原始属性与语义属性的对应关系
|
||||||
|
|
||||||
|
`pbf_relayer_candidates` 本身并不保存原始全部属性值,因此最终语义数据集应按下述方式理解:
|
||||||
|
|
||||||
|
- 原始要素主键与瓦片定位来自 `features`
|
||||||
|
- 原始属性全集来自 `properties`
|
||||||
|
- 展开的 `at` 属性来自 `at_attributes`
|
||||||
|
- 语义投影属性来自 `feature_semantic`
|
||||||
|
- 规范对象分类来自 `pbf_relayer_candidates`
|
||||||
|
|
||||||
|
也就是说,当前 NavSea 最终语义数据集实际上是一个“主表 + 关联表”结构:
|
||||||
|
|
||||||
|
```text
|
||||||
|
主表:
|
||||||
|
pbf_relayer_candidates
|
||||||
|
|
||||||
|
关联恢复:
|
||||||
|
features → 原始要素标识、瓦片坐标、几何类型
|
||||||
|
properties → 原始属性键值
|
||||||
|
at_attributes → 展开的 at 属性
|
||||||
|
feature_semantic → class_name / shape_name / layer_name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 关于 geometry 的说明
|
||||||
|
|
||||||
|
当前数据库 schema 中,`features` 表仅保存:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `z/x/y`
|
||||||
|
- `vt_layer`
|
||||||
|
- `geom_type`
|
||||||
|
|
||||||
|
它不保存完整坐标几何体字段。
|
||||||
|
因此,当前数据库中的“最终语义数据集”是面向语义重建、校验和下游生成的语义索引层,而不是一张带完整坐标几何的空间实体表。
|
||||||
|
|
||||||
|
### 7.4 下游生成 NavSea 数据的推荐入口
|
||||||
|
|
||||||
|
如果后续要生成 NavSea 下游数据或重新生成矢量瓦片,推荐以:
|
||||||
|
|
||||||
|
- `pbf_relayer_candidates` 作为语义主表
|
||||||
|
- `features` 作为原始要素锚点
|
||||||
|
- `properties` / `at_attributes` 作为原始属性源
|
||||||
|
|
||||||
|
共同构成生产输入。
|
||||||
|
|
||||||
|
## 8. 从零重建步骤
|
||||||
|
|
||||||
|
### 最小可复现步骤
|
||||||
|
|
||||||
|
1. 将原始 PBF 导入数据库,得到 `features` 与 `properties`
|
||||||
|
2. 执行 `navsea_audit.py`
|
||||||
|
3. 执行 `navsea_relayer.py`
|
||||||
|
4. 执行 `navsea_semantic_validation.py`
|
||||||
|
5. 检查 `semantic_validation_summary`
|
||||||
|
6. 若需要下游数据生产,则从 `pbf_relayer_candidates` 联合 `features`、`properties`、`at_attributes` 提取最终数据集
|
||||||
|
|
||||||
|
### 推荐的重建命令顺序
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_audit.py
|
||||||
|
.venv/bin/python navsea_relayer.py
|
||||||
|
.venv/bin/python navsea_semantic_validation.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重建完成后的关键检查点
|
||||||
|
|
||||||
|
至少应检查以下表是否已刷新:
|
||||||
|
|
||||||
|
- `feature_semantic`
|
||||||
|
- `object_type_candidates`
|
||||||
|
- `canonical_object_rules`
|
||||||
|
- `canonical_layer_rules`
|
||||||
|
- `pbf_relayer_candidates`
|
||||||
|
- `pbf_detection_catalog`
|
||||||
|
- `classification_validation`
|
||||||
|
- `geometry_consistency`
|
||||||
|
- `style_render_equivalence`
|
||||||
|
- `detection_catalog_integrity`
|
||||||
|
- `spatial_anomalies`
|
||||||
|
- `semantic_validation_summary`
|
||||||
|
|
||||||
|
### 当前系统的审计终点
|
||||||
|
|
||||||
|
当前系统的“可审计终点”不是一个新的矢量瓦片文件,而是:
|
||||||
|
|
||||||
|
- 一套可复现的语义规则表
|
||||||
|
- 一套可复现的语义叠加主表
|
||||||
|
- 一套可重复执行的验证表
|
||||||
|
|
||||||
|
即:
|
||||||
|
|
||||||
|
```text
|
||||||
|
canonical_layer_rules
|
||||||
|
canonical_object_rules
|
||||||
|
pbf_relayer_candidates
|
||||||
|
pbf_detection_catalog
|
||||||
|
semantic_validation_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
这套终点定义了 NavSea 当前语义流水线的正式数据库输出。
|
||||||
54
NavSea_Source_Layer_Consumption_Table.md
Normal file
54
NavSea_Source_Layer_Consumption_Table.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# NavSea Source Layer Consumption Table
|
||||||
|
|
||||||
|
用途:汇总当前交付体系中每个 `source-layer` 的全量 feature 数,以及当前 [navsea.json](/home/wwwroot/newpec/navsea.json) 里哪些 style layer 正在消费它。
|
||||||
|
|
||||||
|
统计口径:
|
||||||
|
|
||||||
|
- feature 数来自 `pbf_analysis.pbf_relayer_candidates`
|
||||||
|
- 样式消费关系来自当前 [navsea.json](/home/wwwroot/newpec/navsea.json)
|
||||||
|
- 仅保留当前交付 `pbf` 与当前样式实际使用的 `37` 个 `source-layer`
|
||||||
|
|
||||||
|
| source-layer | feature_count | style_layer_count | style_layer_ids |
|
||||||
|
| --- | ---: | ---: | --- |
|
||||||
|
| `L739` | 429 | 1 | `water-boundary` |
|
||||||
|
| `L741` | 1696 | 1 | `regulatory-boundary` |
|
||||||
|
| `L基本線` | 53615 | 1 | `coast-structures-line` |
|
||||||
|
| `L概略等深線` | 5551 | 1 | `depth-contours-major` |
|
||||||
|
| `L海底地形` | 2075172 | 2 | `bathymetry-support`, `bathymetry-depth-labels` |
|
||||||
|
| `L海底線` | 14391 | 1 | `subsea-cables` |
|
||||||
|
| `L等深線` | 445051 | 2 | `depth-contours`, `depth-contour-labels` |
|
||||||
|
| `L陸上構造物陸` | 1258262 | 1 | `land-structures-line` |
|
||||||
|
| `L高さ制限` | 1028 | 1 | `height-limit-line` |
|
||||||
|
| `P754ククリ` | 771 | 1 | `special-pattern-line-754` |
|
||||||
|
| `P危険界ククリ` | 51962 | 1 | `danger-outline` |
|
||||||
|
| `P基本線` | 701625 | 6 | `sea-area-fill`, `tidal-flat`, `river-water`, `unsurveyed-area`, `shoal-danger-area`, `coast-structures-area` |
|
||||||
|
| `P基本線ククリ` | 1080780 | 1 | `clip-outline` |
|
||||||
|
| `P投錨注意障害物` | 58626 | 1 | `hazard-polygons` |
|
||||||
|
| `P投錨注意障害物ククリ` | 8743 | 1 | `anchor-danger-outline` |
|
||||||
|
| `P施設・境界線等` | 0 | 1 | `facility-zones` |
|
||||||
|
| `P施設・境界線等ククリ` | 566 | 1 | `facility-zone-outline` |
|
||||||
|
| `P橋りょう等構造物` | 25775 | 1 | `bridge-area` |
|
||||||
|
| `P漁具定置箇所` | 22595 | 1 | `fishery-areas` |
|
||||||
|
| `P潜堤` | 3881 | 1 | `submerged-structures` |
|
||||||
|
| `P穴` | 367217 | 1 | `land-hole` |
|
||||||
|
| `P航行危険障害物` | 0 | 1 | `navigation-hazard-polygons` |
|
||||||
|
| `P航行危険障害物ククリ` | 198 | 1 | `navigation-hazard-outline` |
|
||||||
|
| `P錨泊地等` | 0 | 1 | `anchorage-areas` |
|
||||||
|
| `P錨泊地等ククリ` | 297 | 1 | `anchorage-outline` |
|
||||||
|
| `P陸上構造物陸` | 35622 | 1 | `land-structures-area` |
|
||||||
|
| `P陸域` | 198655 | 1 | `land-area` |
|
||||||
|
| `p地名` | 36247 | 1 | `place-labels-sea` |
|
||||||
|
| `p地名陸` | 35388 | 1 | `place-labels-land` |
|
||||||
|
| `p底質` | 99863 | 2 | `bottom-material-points`, `bottom-material-labels` |
|
||||||
|
| `p投錨注意障害物` | 0 | 1 | `anchor-hazard-points` |
|
||||||
|
| `p施設・境界線等` | 6425 | 1 | `facility-points` |
|
||||||
|
| `p航行危険障害物` | 19427 | 1 | `hazard-points` |
|
||||||
|
| `p航路標識群` | 38520 | 4 | `nav-light-flare`, `nav-marks`, `nav-mark-labels`, `nav-light-abbr-labels` |
|
||||||
|
| `p錨泊地等` | 674 | 1 | `anchorage-symbols` |
|
||||||
|
| `p陸上構造物` | 32708 | 1 | `landmark-points` |
|
||||||
|
| `p高さ制限` | 858 | 1 | `height-limit-points` |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 上表里的 `feature_count = 0`,表示当前 `pbf_relayer_candidates` 全量统计里没有对应记录,但在唐津 AOI 测试瓦片中这些 layer 仍然实际存在并已被样式消费。
|
||||||
|
- 当前 [navsea.json](/home/wwwroot/newpec/navsea.json) 对这 `37` 个 `source-layer` 都已经有覆盖,不存在“数据里有 layer 但样式完全不消费”的情况。
|
||||||
273
NavSea_Style_Render_Change_Audit_Karatsu_10nm.md
Normal file
273
NavSea_Style_Render_Change_Audit_Karatsu_10nm.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# NavSea 原始样式与使用版样式渲染变更审计
|
||||||
|
|
||||||
|
范围:唐津中心 10 海里 AOI
|
||||||
|
原始样式:[style.json](/root/weather/src/pbf/style.json)
|
||||||
|
使用版样式:[navsea-delivery.json](/home/wwwroot/newpec/navsea-delivery.json)
|
||||||
|
使用版瓦片:`/home/wwwroot/pbf-delivery-karatsu-10nm`
|
||||||
|
|
||||||
|
## 1. 结论
|
||||||
|
|
||||||
|
结合原始 `style.json` 和使用版样式来看,渲染的“对象内容”没有减少,但渲染“表达方式”已经明显改变。
|
||||||
|
|
||||||
|
最核心的判断是:
|
||||||
|
|
||||||
|
- AOI 内真实存在的 `37` 个 `source-layer`,原始样式和使用版样式都覆盖到了
|
||||||
|
- 但样式层组织从 `87` 层收成了 `50` 层
|
||||||
|
- 很多 `source-layer` 的渲染被重新归并、重命名、重分层
|
||||||
|
- 样式直接依赖的旧字段减少了,新版开始混合使用旧字段和新语义字段
|
||||||
|
|
||||||
|
一句话总结:
|
||||||
|
|
||||||
|
> 这不是“少画了东西”,而是“同一批东西换了一套渲染组织方式”。
|
||||||
|
|
||||||
|
## 2. 总体统计
|
||||||
|
|
||||||
|
### 2.1 样式规模
|
||||||
|
|
||||||
|
- 原始样式层数:`87`
|
||||||
|
- 使用版样式层数:`50`
|
||||||
|
|
||||||
|
### 2.2 AOI 内实际图层覆盖
|
||||||
|
|
||||||
|
- AOI 内实际存在的 `source-layer`:`37`
|
||||||
|
- 原始样式覆盖到的 AOI `source-layer`:`37`
|
||||||
|
- 使用版样式覆盖到的 AOI `source-layer`:`37`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 在唐津 10 海里范围内,使用版并没有漏掉某个实际存在的 `source-layer`
|
||||||
|
- 变化主要不是“有没有画”,而是“怎么画”
|
||||||
|
|
||||||
|
## 3. 字段依赖变化
|
||||||
|
|
||||||
|
### 3.1 原始样式直接读取的字段数
|
||||||
|
|
||||||
|
原始样式通过 `["get", ...]` 直接依赖的字段共 `21` 个,主要包括:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `日本語地名`
|
||||||
|
- `明弧/分孤`
|
||||||
|
- `水深値(m)`
|
||||||
|
- `灯略記`
|
||||||
|
- `灯色`
|
||||||
|
- `英文字地名`
|
||||||
|
- `表示位置`
|
||||||
|
- `表示用番号`
|
||||||
|
- `角度`
|
||||||
|
- `高さ(m)`
|
||||||
|
- `高さ/深度(m)`
|
||||||
|
- `cog`
|
||||||
|
- `delay`
|
||||||
|
- `hdg`
|
||||||
|
- `length`
|
||||||
|
- `rot`
|
||||||
|
- `sog`
|
||||||
|
|
||||||
|
### 3.2 使用版样式直接读取的字段数
|
||||||
|
|
||||||
|
使用版样式直接依赖的字段共 `13` 个:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `分類番号`
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `日本語地名`
|
||||||
|
- `水深値(m)`
|
||||||
|
- `灯略記`
|
||||||
|
- `灯色`
|
||||||
|
- `英文字地名`
|
||||||
|
- `表示位置`
|
||||||
|
- `表示用番号`
|
||||||
|
- `高さ/深度(m)`
|
||||||
|
|
||||||
|
### 3.3 差异判断
|
||||||
|
|
||||||
|
使用版不再直接读取这些旧字段:
|
||||||
|
|
||||||
|
- `明弧/分孤`
|
||||||
|
- `角度`
|
||||||
|
- `高さ(m)`
|
||||||
|
- `cog`
|
||||||
|
- `delay`
|
||||||
|
- `hdg`
|
||||||
|
- `length`
|
||||||
|
- `rot`
|
||||||
|
- `sog`
|
||||||
|
|
||||||
|
同时新增了新语义字段依赖:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 使用版已经开始摆脱部分旧海图编码和动态字段依赖
|
||||||
|
- 但当前仍保留了对 `分類番号`、`形状分類番号`、`表示用番号`、`灯色` 等旧字段的依赖
|
||||||
|
|
||||||
|
## 4. AOI 内渲染组织变化
|
||||||
|
|
||||||
|
### 4.1 没变的部分
|
||||||
|
|
||||||
|
- 两边都覆盖了 AOI 内的全部 `37` 个 `source-layer`
|
||||||
|
- 本 AOI 内不存在“旧样式能画、使用版完全不画”的数据层
|
||||||
|
|
||||||
|
### 4.2 变化最大的部分
|
||||||
|
|
||||||
|
以下 `source-layer` 在使用版里被显著重组了:
|
||||||
|
|
||||||
|
- `L概略等深線`
|
||||||
|
- 原始样式:`8` 个样式层
|
||||||
|
- 使用版:`1` 个样式层 `depth-contours-major`
|
||||||
|
- `L等深線`
|
||||||
|
- 原始样式:`5` 个样式层
|
||||||
|
- 使用版:`2` 个样式层 `depth-contours` + `depth-contour-labels`
|
||||||
|
- `P基本線`
|
||||||
|
- 原始样式:`4` 个样式层
|
||||||
|
- 使用版:`6` 个样式层
|
||||||
|
- 说明:使用版把同一面层按语义拆成了多种面表现,比如 `sea-area-fill`、`tidal-flat`、`river-water`、`unsurveyed-area`、`shoal-danger-area`
|
||||||
|
- `p航路標識群`
|
||||||
|
- 原始样式:`5` 个样式层
|
||||||
|
- 使用版:`4` 个样式层
|
||||||
|
- `p底質`
|
||||||
|
- 原始样式:`1` 个样式层
|
||||||
|
- 使用版:`2` 个样式层
|
||||||
|
- 说明:使用版把底质本体和文字拆开了
|
||||||
|
|
||||||
|
### 4.3 被简化的部分
|
||||||
|
|
||||||
|
这些层在使用版中从多层压缩为单层:
|
||||||
|
|
||||||
|
- `P橋りょう等構造物`:`2 -> 1`
|
||||||
|
- `P漁具定置箇所`:`2 -> 1`
|
||||||
|
- `P潜堤`:`2 -> 1`
|
||||||
|
- `p地名`:`2 -> 1`
|
||||||
|
- `p地名陸`:`2 -> 1`
|
||||||
|
- `p高さ制限`:`2 -> 1`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 使用版不是简单复制旧样式
|
||||||
|
- 使用版对不少对象做了“压层”和“归并”
|
||||||
|
- 同时也对少数核心面层做了“按语义细分”
|
||||||
|
|
||||||
|
## 5. 典型图层对比
|
||||||
|
|
||||||
|
### 5.1 `L739`
|
||||||
|
|
||||||
|
- 原始样式:`#b#d2#d5#d10#BK#at#L739`
|
||||||
|
- 使用版:`water-boundary`
|
||||||
|
|
||||||
|
变化:
|
||||||
|
|
||||||
|
- 从旧式缩尺/颜色编码命名,变成语义命名
|
||||||
|
|
||||||
|
### 5.2 `L741`
|
||||||
|
|
||||||
|
- 原始样式:`#b#d2#d5#d10#BS#at#L741`
|
||||||
|
- 使用版:`regulatory-boundary`
|
||||||
|
|
||||||
|
变化:
|
||||||
|
|
||||||
|
- 图层语义比旧版更清楚
|
||||||
|
|
||||||
|
### 5.3 `L海底地形`
|
||||||
|
|
||||||
|
- 原始样式:`#b#海底地形`、`#b#C000`
|
||||||
|
- 使用版:`bathymetry-support`、`bathymetry-depth-labels`
|
||||||
|
|
||||||
|
变化:
|
||||||
|
|
||||||
|
- 使用版明确拆成“支撑线”和“水深数字”
|
||||||
|
|
||||||
|
### 5.4 `L等深線`
|
||||||
|
|
||||||
|
- 原始样式:`#b#L等深線`、`#d2#L等深線`、`#d5#L等深線`、`#d10#L等深線`、`#b#d2#d5#d10#C140`
|
||||||
|
- 使用版:`depth-contours`、`depth-contour-labels`
|
||||||
|
|
||||||
|
变化:
|
||||||
|
|
||||||
|
- 原始样式按缩尺和强调层堆叠
|
||||||
|
- 使用版变成“线 + 标注”的直接语义拆分
|
||||||
|
|
||||||
|
### 5.5 `P基本線`
|
||||||
|
|
||||||
|
- 原始样式:`#b#at#P基本線`、`#d2#at#P基本線`、`#d5#at#P基本線`、`#d10#at#P基本線`
|
||||||
|
- 使用版:
|
||||||
|
- `sea-area-fill`
|
||||||
|
- `tidal-flat`
|
||||||
|
- `river-water`
|
||||||
|
- `unsurveyed-area`
|
||||||
|
- `shoal-danger-area`
|
||||||
|
- `coast-structures-area`
|
||||||
|
|
||||||
|
变化:
|
||||||
|
|
||||||
|
- 使用版在这一层上变化最大
|
||||||
|
- 同一个原始面层被拆成多个语义面层
|
||||||
|
- 视觉表达会比旧样式更“语义化”
|
||||||
|
|
||||||
|
## 6. 原始样式中存在、使用版中不再出现的 `source-layer`
|
||||||
|
|
||||||
|
原始样式定义了 `57` 个 `source-layer`,使用版只有 `37` 个。
|
||||||
|
|
||||||
|
但这里要注意:
|
||||||
|
|
||||||
|
- 这 `20` 个差异层并不是“使用版漏画了 AOI 数据”
|
||||||
|
- 它们是原始样式里定义过、但在当前 AOI 与当前使用版中未出现的层
|
||||||
|
|
||||||
|
主要包括:
|
||||||
|
|
||||||
|
- `L700`
|
||||||
|
- `L701`
|
||||||
|
- `L702`
|
||||||
|
- `L725`
|
||||||
|
- `L738`
|
||||||
|
- `L740`
|
||||||
|
- `L748`
|
||||||
|
- `L749`
|
||||||
|
- `L危険界`
|
||||||
|
- `L航路`
|
||||||
|
- `P721ククリ`
|
||||||
|
- `P730ククリ`
|
||||||
|
- `P投錨注意障害物透明`
|
||||||
|
- `P施設・境界線等透明`
|
||||||
|
- `P航路`
|
||||||
|
- `P航路ククリ`
|
||||||
|
- `P誘導線ククリ`
|
||||||
|
- `dummy`
|
||||||
|
- `pパイロットステーション`
|
||||||
|
- `p航路境界等`
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
- 这是“原始样式定义范围更大”
|
||||||
|
- 不是“使用版把 AOI 内实际对象删掉了”
|
||||||
|
|
||||||
|
## 7. 最终判断
|
||||||
|
|
||||||
|
结合原始样式和使用版样式来看,渲染变化可以归纳成三类:
|
||||||
|
|
||||||
|
### 7.1 没变的
|
||||||
|
|
||||||
|
- AOI 内实际要画的数据层都还在
|
||||||
|
- 使用版没有丢掉任何 AOI 内真实存在的 `source-layer`
|
||||||
|
|
||||||
|
### 7.2 变了但不属于缺失
|
||||||
|
|
||||||
|
- 图层命名体系从旧的技术型命名变成了新的语义命名
|
||||||
|
- 很多对象从“按缩尺堆层”变成“按语义拆层”
|
||||||
|
- 样式总层数明显减少
|
||||||
|
- 旧字段依赖减少,新语义字段开始进入样式
|
||||||
|
|
||||||
|
### 7.3 需要继续注意的
|
||||||
|
|
||||||
|
- 使用版虽然是交付版,但还没有完全摆脱旧字段体系
|
||||||
|
- 某些旧样式细节如果依赖 `明弧/分孤`、`角度`、`高さ(m)` 等字段,在使用版里会弱化或消失
|
||||||
|
- 现在的变化更偏“渲染体系重构”,不是“数据减少”
|
||||||
|
|
||||||
|
一句话总结:
|
||||||
|
|
||||||
|
> 从原始 `style.json` 切到使用版样式后,唐津 10 海里范围内“画的对象”没有少,但“画法”已经发生了统计上明确可见的重构。
|
||||||
634
PBF_ANALYSIS_DATABASE_TABLES.md
Normal file
634
PBF_ANALYSIS_DATABASE_TABLES.md
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
# pbf_analysis 数据库表功能说明
|
||||||
|
|
||||||
|
## 1. 目的
|
||||||
|
|
||||||
|
这份文档说明当前 `pbf_analysis` 数据库中各张 NavSea 相关表的用途、所处阶段、与当前工程的关系。
|
||||||
|
|
||||||
|
目标是让后续接手的人能快速回答这些问题:
|
||||||
|
|
||||||
|
- 哪些表是原始解析结果
|
||||||
|
- 哪些表是语义分类与归层结果
|
||||||
|
- 哪些表是规则注册表
|
||||||
|
- 哪些表是检测/碰撞/航行分析专题视图
|
||||||
|
- 哪些表是样式分析与渲染审计结果
|
||||||
|
- 哪些表是历史分析或中间产物
|
||||||
|
|
||||||
|
## 2. 总体分层
|
||||||
|
|
||||||
|
当前 `pbf_analysis` 里的表可以按用途分成 6 类:
|
||||||
|
|
||||||
|
1. 原始与基础解析层
|
||||||
|
2. 语义分类与归层层
|
||||||
|
3. 几何与空间支撑层
|
||||||
|
4. 规则注册与映射层
|
||||||
|
5. 样式分析与渲染审计层
|
||||||
|
6. 业务专题与异常分析层
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `BASE TABLE` 表示实体表
|
||||||
|
- `VIEW` 表示视图
|
||||||
|
- `table_rows` 为 MySQL 估计值,仅用于了解量级
|
||||||
|
|
||||||
|
## 3. 关键原则
|
||||||
|
|
||||||
|
当前数据库不是只存“最终结果”,而是同时承担四类职责:
|
||||||
|
|
||||||
|
- 保存原始解析和展平结果
|
||||||
|
- 保存分类和标准化结果
|
||||||
|
- 保存可追溯规则注册表
|
||||||
|
- 保存审计与专题查询结果
|
||||||
|
|
||||||
|
后续开发应优先遵循:
|
||||||
|
|
||||||
|
- 映射规则尽量收口到规则表
|
||||||
|
- 审计结果进专门审计表
|
||||||
|
- 原始值与标准值并存,避免不可逆覆盖
|
||||||
|
|
||||||
|
## 4. 表分组说明
|
||||||
|
|
||||||
|
### 4.1 原始与基础解析层
|
||||||
|
|
||||||
|
这些表主要用于把原始 `pbf` 解码成可查询的结构化数据。
|
||||||
|
|
||||||
|
#### `features`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6653014`
|
||||||
|
- 关键字段:
|
||||||
|
- `id, z, x, y, vt_layer, geom_type`
|
||||||
|
- 用途:
|
||||||
|
- 保存原始瓦片的基础 feature 索引
|
||||||
|
- 作为很多后续表的原始主干
|
||||||
|
|
||||||
|
#### `properties`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`22213975`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, k, v`
|
||||||
|
- 用途:
|
||||||
|
- 存原始属性键值对
|
||||||
|
- 便于后续抽取 `fid`、`分類番号`、`名称` 等字段
|
||||||
|
|
||||||
|
#### `at_attributes`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6674189`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, k, v`
|
||||||
|
- 用途:
|
||||||
|
- 拆分原始 `at` 属性集合
|
||||||
|
- 为分类、渲染兼容、规则归纳提供细粒度字段来源
|
||||||
|
|
||||||
|
#### `feature_flat`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, vt_layer, class_id, fid, at`
|
||||||
|
- 用途:
|
||||||
|
- 对原始 feature 做便于分析的展平视图
|
||||||
|
- 常用于快速查看对象分类号和 `fid`
|
||||||
|
|
||||||
|
#### `feature_semantic`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6367524`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, z, x, y, vt_layer, geom_type, class_name, shape_name, layer_name`
|
||||||
|
- 用途:
|
||||||
|
- 原始对象语义的早期抽取层
|
||||||
|
- 为后续 taxonomy 和 render 分析提供基础口径
|
||||||
|
|
||||||
|
#### `feature_geojson_lookup`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6636967`
|
||||||
|
- 关键字段:
|
||||||
|
- `z, x, y, feature_id, fid, vt_layer, geom_type`
|
||||||
|
- 用途:
|
||||||
|
- 做 feature 与瓦片实例的查询映射
|
||||||
|
- 便于回查到具体 tile
|
||||||
|
|
||||||
|
### 4.2 几何与空间支撑层
|
||||||
|
|
||||||
|
这些表负责把原始 feature 补成可空间分析的对象。
|
||||||
|
|
||||||
|
#### `feature_geometry_stage`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`13597637`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, geometry_wkt`
|
||||||
|
- 用途:
|
||||||
|
- 几何导入与中间转换阶段表
|
||||||
|
- 为后续空间启用、几何核查服务
|
||||||
|
|
||||||
|
#### `navsea_feature_geometry`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6685117`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, geometry, geometry_source`
|
||||||
|
- 用途:
|
||||||
|
- NavSea 当前正式使用的 feature 几何表
|
||||||
|
- 支撑碰撞、空间专题和检测视图
|
||||||
|
|
||||||
|
#### `geometry_consistency`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`327`
|
||||||
|
- 关键字段:
|
||||||
|
- `canonical_object_type, geometry_type, feature_count, geometry_consistency_status`
|
||||||
|
- 用途:
|
||||||
|
- 统计某类对象的几何类型是否符合预期
|
||||||
|
|
||||||
|
#### `geometry_consistency_audit`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`331`
|
||||||
|
- 关键字段:
|
||||||
|
- `canonical_object_type, geometry_type, feature_count, geometry_consistency_status`
|
||||||
|
- 用途:
|
||||||
|
- 与 `geometry_consistency` 类似,用于审计记录与复核
|
||||||
|
|
||||||
|
#### `object_geometry_allowlist`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`310`
|
||||||
|
- 关键字段:
|
||||||
|
- `canonical_object_type, geometry_type, rule_source, is_allowed, notes`
|
||||||
|
- 用途:
|
||||||
|
- 定义哪些对象类型允许哪些几何类型
|
||||||
|
- 是 geometry 审计的规则依据之一
|
||||||
|
|
||||||
|
### 4.3 语义分类与归层层
|
||||||
|
|
||||||
|
这些表描述“对象是什么”和“应该归到哪一层”。
|
||||||
|
|
||||||
|
#### `object_catalog`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`319`
|
||||||
|
- 关键字段:
|
||||||
|
- `layer_name, class_name, shape_name, vt_layer, geom_type, feature_count`
|
||||||
|
- 用途:
|
||||||
|
- 原始对象目录
|
||||||
|
- 手工 taxonomy 审查和统计的基础表
|
||||||
|
|
||||||
|
#### `object_type_candidates`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6473314`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, object_type_source, object_type, vt_layer, geom_type`
|
||||||
|
- 用途:
|
||||||
|
- 某个 feature 可能属于哪些对象类型的候选集合
|
||||||
|
- 是 taxonomy 判定前的候选层
|
||||||
|
|
||||||
|
#### `object_type_stats`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`255`
|
||||||
|
- 关键字段:
|
||||||
|
- `object_type, geom_type, feature_count`
|
||||||
|
- 用途:
|
||||||
|
- 对候选对象类型做统计
|
||||||
|
|
||||||
|
#### `canonical_object_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`221`
|
||||||
|
- 关键字段:
|
||||||
|
- `canonical_object_type, canonical_family, classification_basis, source_layer_scope, ...`
|
||||||
|
- 用途:
|
||||||
|
- 旧版人工整理的对象级归类规则汇总表
|
||||||
|
- 偏分析结果,不是最新规则注册表
|
||||||
|
|
||||||
|
#### `canonical_layer_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`49`
|
||||||
|
- 关键字段:
|
||||||
|
- `source_layer, semantic_granularity, canonical_family, render_strategy, ...`
|
||||||
|
- 用途:
|
||||||
|
- 旧版人工整理的层级归并规则表
|
||||||
|
- 用于理解老系统如何按层做语义合并
|
||||||
|
|
||||||
|
#### `feature_semantic`
|
||||||
|
|
||||||
|
- 已在基础层列出
|
||||||
|
- 同时也是对象语义候选的重要来源
|
||||||
|
|
||||||
|
#### `pbf_relayer_candidates`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`6547009`
|
||||||
|
- 关键字段:
|
||||||
|
- `feature_id, z, x, y, source_layer, geom_type, ... canonical_object_type, canonical_family, semantic_key, detection_key, render_layer`
|
||||||
|
- 用途:
|
||||||
|
- 当前构建 `pbf` 时最重要的结果表之一
|
||||||
|
- 保存 feature 经过分类和归层后的候选结果
|
||||||
|
- `navsea_tile_builder.py` 会结合该表与原始 `pbf` 一起输出新瓦片
|
||||||
|
|
||||||
|
#### `pbf_source_object_stats`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`215`
|
||||||
|
- 关键字段:
|
||||||
|
- `source_layer, render_layer, canonical_family, canonical_object_type, geom_type, detection_key, semantic_key, feature_count`
|
||||||
|
- 用途:
|
||||||
|
- 统计不同源层在重构后如何分布
|
||||||
|
|
||||||
|
#### `pbf_render_compatibility`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`58`
|
||||||
|
- 关键字段:
|
||||||
|
- `source_layer, render_layer, canonical_family, semantic_granularity, style_bound, feature_count, canonical_object_types`
|
||||||
|
- 用途:
|
||||||
|
- 评估重构后数据与旧样式兼容性的分析表
|
||||||
|
|
||||||
|
#### `pbf_detection_catalog`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`359`
|
||||||
|
- 关键字段:
|
||||||
|
- `detection_key, canonical_family, canonical_object_type, geom_type, feature_count, source_layers`
|
||||||
|
- 用途:
|
||||||
|
- 检测视角的对象目录
|
||||||
|
- 支撑 detection_key 设计与校验
|
||||||
|
|
||||||
|
### 4.4 规则注册与映射层
|
||||||
|
|
||||||
|
这组表是现在最重要的“规则中心”,用于收口旧版到新版的映射逻辑。
|
||||||
|
|
||||||
|
#### `navsea_rule_bundle`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`1`
|
||||||
|
- 关键字段:
|
||||||
|
- `bundle_id, bundle_version, status, effective_date, ...`
|
||||||
|
- 用途:
|
||||||
|
- 规则包总表
|
||||||
|
- 记录当前使用的是哪一套 taxonomy/render/source-layer/field-value 规则
|
||||||
|
|
||||||
|
#### `navsea_source_layer_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`49`
|
||||||
|
- 关键字段:
|
||||||
|
- `source_layer_jp, source_layer_std, semantic_granularity, canonical_family, render_strategy, ...`
|
||||||
|
- 用途:
|
||||||
|
- 日文 `source-layer` 到标准层名的映射规则
|
||||||
|
- 是 source-layer 可逆标准化的正式注册表
|
||||||
|
|
||||||
|
#### `navsea_field_name_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`17`
|
||||||
|
- 关键字段:
|
||||||
|
- `field_name_jp, field_name_std, field_group_name, keep_in_delivery, keep_in_engineering, ...`
|
||||||
|
- 用途:
|
||||||
|
- 旧字段名到标准字段名的映射
|
||||||
|
- 同时定义字段在哪种输出中保留
|
||||||
|
|
||||||
|
#### `navsea_field_value_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`20`
|
||||||
|
- 关键字段:
|
||||||
|
- `rule_id, field_name_jp, legacy_value, standardized_field, standardized_value, ...`
|
||||||
|
- 用途:
|
||||||
|
- 旧字段值到新标准字段值的映射
|
||||||
|
- 例如灯色、表示位置等值映射
|
||||||
|
|
||||||
|
#### `navsea_taxonomy_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`8`
|
||||||
|
- 关键字段:
|
||||||
|
- `rule_id, priority, enabled, match_scope, match_expr_json, output_json, canonical_family, canonical_object_type, detection_key_template`
|
||||||
|
- 用途:
|
||||||
|
- 正式 taxonomy 规则表
|
||||||
|
- 定义 feature 在什么条件下被归成哪个 `canonical_object_type`
|
||||||
|
|
||||||
|
#### `navsea_render_rules`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`9`
|
||||||
|
- 关键字段:
|
||||||
|
- `rule_id, priority, match_expr_json, output_json, chart_render_type, chart_symbol_code, chart_fill_style, ...`
|
||||||
|
- 用途:
|
||||||
|
- 正式渲染语义规则表
|
||||||
|
- 定义 feature 应输出哪些 `chart_* / hazard_* / area_*` 字段
|
||||||
|
|
||||||
|
#### `navsea_mapping_registry_vw`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 统一查看当前各类映射规则
|
||||||
|
- 适合人工巡检和管理
|
||||||
|
|
||||||
|
#### `navsea_legacy_to_new_mapping_vw`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 从旧值查新值
|
||||||
|
- 适合排查“旧字段为什么被映射成这个新字段”
|
||||||
|
|
||||||
|
#### `navsea_new_to_legacy_mapping_vw`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 从新值反查旧值
|
||||||
|
- 适合可逆追述和规则审计
|
||||||
|
|
||||||
|
### 4.5 NavSea 对象字典与能力表
|
||||||
|
|
||||||
|
#### `navsea_object_taxonomy`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`127`
|
||||||
|
- 关键字段:
|
||||||
|
- `canonical_object_type, object_family, detection_class, description`
|
||||||
|
- 用途:
|
||||||
|
- NavSea 对象字典
|
||||||
|
- 定义各 canonical object 的基础说明
|
||||||
|
|
||||||
|
#### `navsea_object_capabilities`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`259`
|
||||||
|
- 关键字段:
|
||||||
|
- `canonical_object_type, capability`
|
||||||
|
- 用途:
|
||||||
|
- 定义对象能力,例如是否用于碰撞、导航参考、缠绕风险等
|
||||||
|
- 给下游专题视图提供能力标签
|
||||||
|
|
||||||
|
### 4.6 样式分析与渲染审计层
|
||||||
|
|
||||||
|
#### `style_layers`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`113`
|
||||||
|
- 关键字段:
|
||||||
|
- `layer_id, source_layer, icon, line_color, fill_color, layer_type`
|
||||||
|
- 用途:
|
||||||
|
- 解析旧样式后的 layer 清单
|
||||||
|
|
||||||
|
#### `style_mapping`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`42`
|
||||||
|
- 关键字段:
|
||||||
|
- `layer_id, source_layer, layer_type, icon, line_color, fill_color, object_type, geom_type, feature_count`
|
||||||
|
- 用途:
|
||||||
|
- 分析旧样式的 source-layer 消费关系
|
||||||
|
|
||||||
|
#### `style_render_equivalence`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`53`
|
||||||
|
- 关键字段:
|
||||||
|
- `style_layer, feature_count_before, feature_count_after, difference, render_status`
|
||||||
|
- 用途:
|
||||||
|
- 用于分析重构前后样式层渲染覆盖是否等价
|
||||||
|
|
||||||
|
#### `navsea_render_audit_run`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`1`
|
||||||
|
- 关键字段:
|
||||||
|
- `audit_name, original_style_path, engineering_style_path, original_tile_root, engineering_tile_root, result_count, status_counts_json, ...`
|
||||||
|
- 用途:
|
||||||
|
- 存放“最新一次”渲染审计的摘要
|
||||||
|
- 当前约定只保留最新一轮结果
|
||||||
|
|
||||||
|
#### `navsea_render_audit_result`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`2357346`
|
||||||
|
- 关键字段:
|
||||||
|
- `audit_name, object_instance_key, object_instance_id, object_id, fid_legacy, tile_z, tile_x, tile_y, source_layer, status, original_components_json, engineering_components_json`
|
||||||
|
- 用途:
|
||||||
|
- 渲染审计结果明细表
|
||||||
|
- 可按 `fid_legacy` 追查某个对象在原始样式和工程样式下的渲染差异
|
||||||
|
|
||||||
|
### 4.7 业务专题与异常分析层
|
||||||
|
|
||||||
|
这组表/视图偏业务消费和专题分析。
|
||||||
|
|
||||||
|
#### `navsea_detection_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 检测对象总视图
|
||||||
|
- 面向 detection 任务使用
|
||||||
|
|
||||||
|
#### `navsea_detection_objects_spatial`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 检测对象的空间分析视图
|
||||||
|
|
||||||
|
#### `navsea_collision_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 碰撞相关对象视图
|
||||||
|
|
||||||
|
#### `navsea_grounding_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 搁浅风险相关对象视图
|
||||||
|
|
||||||
|
#### `navsea_entangle_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 缠绕风险相关对象视图
|
||||||
|
|
||||||
|
#### `navsea_boundary_reference_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 边界参考对象视图
|
||||||
|
|
||||||
|
#### `navsea_route_reference_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 航路参考对象视图
|
||||||
|
|
||||||
|
#### `navsea_navigation_mark_objects`
|
||||||
|
|
||||||
|
- 类型:`VIEW`
|
||||||
|
- 用途:
|
||||||
|
- 航标专题对象视图
|
||||||
|
|
||||||
|
#### `anomaly_navigation_geom`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`0`
|
||||||
|
- 用途:
|
||||||
|
- 导航相关几何异常对象表
|
||||||
|
|
||||||
|
#### `anomaly_reef_geom`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`13`
|
||||||
|
- 用途:
|
||||||
|
- 鱼礁/礁石类几何异常对象表
|
||||||
|
|
||||||
|
#### `spatial_anomalies`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`693`
|
||||||
|
- 关键字段:
|
||||||
|
- `anomaly_type, affected_feature_count, region_hint, severity`
|
||||||
|
- 用途:
|
||||||
|
- 空间异常摘要
|
||||||
|
|
||||||
|
#### `classification_validation`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`177`
|
||||||
|
- 用途:
|
||||||
|
- 分类结果验证摘要
|
||||||
|
|
||||||
|
#### `detection_catalog_integrity`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`343`
|
||||||
|
- 用途:
|
||||||
|
- 检测目录完整性校验
|
||||||
|
|
||||||
|
#### `semantic_validation_summary`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`1`
|
||||||
|
- 用途:
|
||||||
|
- 语义验证总摘要
|
||||||
|
|
||||||
|
### 4.8 密度与覆盖统计层
|
||||||
|
|
||||||
|
#### `tile_density`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`65467`
|
||||||
|
- 关键字段:
|
||||||
|
- `z, x, y, feature_count`
|
||||||
|
- 用途:
|
||||||
|
- 每个 tile 的 feature 密度统计
|
||||||
|
|
||||||
|
#### `tile_density_top100`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`100`
|
||||||
|
- 用途:
|
||||||
|
- feature 最密的 top100 tile
|
||||||
|
|
||||||
|
#### `tile_layer_density`
|
||||||
|
|
||||||
|
- 类型:`BASE TABLE`
|
||||||
|
- 估计行数:`207315`
|
||||||
|
- 关键字段:
|
||||||
|
- `z, x, y, vt_layer, feature_count`
|
||||||
|
- 用途:
|
||||||
|
- 按 tile 和 layer 分解的密度统计
|
||||||
|
|
||||||
|
## 5. 当前工程最重要的表
|
||||||
|
|
||||||
|
如果只看当前构建与审计链路,最重要的是这些:
|
||||||
|
|
||||||
|
### 构建时必看
|
||||||
|
|
||||||
|
- `pbf_relayer_candidates`
|
||||||
|
- `navsea_rule_bundle`
|
||||||
|
- `navsea_source_layer_rules`
|
||||||
|
- `navsea_field_name_rules`
|
||||||
|
- `navsea_field_value_rules`
|
||||||
|
- `navsea_taxonomy_rules`
|
||||||
|
- `navsea_render_rules`
|
||||||
|
- `navsea_feature_geometry`
|
||||||
|
|
||||||
|
### 审计时必看
|
||||||
|
|
||||||
|
- `navsea_render_audit_run`
|
||||||
|
- `navsea_render_audit_result`
|
||||||
|
- `style_layers`
|
||||||
|
- `style_mapping`
|
||||||
|
- `style_render_equivalence`
|
||||||
|
|
||||||
|
### 数据理解时必看
|
||||||
|
|
||||||
|
- `object_catalog`
|
||||||
|
- `pbf_source_object_stats`
|
||||||
|
- `pbf_detection_catalog`
|
||||||
|
- `navsea_object_taxonomy`
|
||||||
|
- `navsea_object_capabilities`
|
||||||
|
|
||||||
|
## 6. 当前推荐使用方式
|
||||||
|
|
||||||
|
### 想查某个对象为什么被分类成某个新对象
|
||||||
|
|
||||||
|
优先查:
|
||||||
|
|
||||||
|
- `pbf_relayer_candidates`
|
||||||
|
- `navsea_taxonomy_rules`
|
||||||
|
- `navsea_legacy_to_new_mapping_vw`
|
||||||
|
|
||||||
|
### 想查某个对象为什么被渲染成某种 `chart_*`
|
||||||
|
|
||||||
|
优先查:
|
||||||
|
|
||||||
|
- `navsea_render_rules`
|
||||||
|
- `navsea_mapping_registry_vw`
|
||||||
|
- 工程版 `pbf` 中对应 feature 的输出字段
|
||||||
|
|
||||||
|
### 想查某个对象原始版和工程版渲染是否一致
|
||||||
|
|
||||||
|
优先查:
|
||||||
|
|
||||||
|
- `navsea_render_audit_result`
|
||||||
|
- `navsea_render_audit_run`
|
||||||
|
|
||||||
|
### 想做碰撞、搁浅、航路专题分析
|
||||||
|
|
||||||
|
优先查:
|
||||||
|
|
||||||
|
- `navsea_collision_objects`
|
||||||
|
- `navsea_grounding_objects`
|
||||||
|
- `navsea_route_reference_objects`
|
||||||
|
- `navsea_navigation_mark_objects`
|
||||||
|
|
||||||
|
## 7. 维护建议
|
||||||
|
|
||||||
|
当前数据库里同时有“旧分析表”和“新规则注册表”,后续维护建议如下:
|
||||||
|
|
||||||
|
- 正式规则源以 `tasks/pbf/mappings/*.yaml` 为主
|
||||||
|
- 数据库规则表作为运行时注册中心
|
||||||
|
- 不要再把同一套逻辑分散写进多个临时分析表
|
||||||
|
- 新增规则时优先更新:
|
||||||
|
- `navsea_rule_bundle`
|
||||||
|
- `navsea_source_layer_rules`
|
||||||
|
- `navsea_field_name_rules`
|
||||||
|
- `navsea_field_value_rules`
|
||||||
|
- `navsea_taxonomy_rules`
|
||||||
|
- `navsea_render_rules`
|
||||||
|
|
||||||
|
## 8. 与 Git 管理的关系
|
||||||
|
|
||||||
|
建议把下面这些内容一起纳入项目 Git:
|
||||||
|
|
||||||
|
- 本文档
|
||||||
|
- `tasks/pbf/` 下的规范文档
|
||||||
|
- `tasks/pbf/mappings/` 下的规则源文件
|
||||||
|
- `navsea_mapping_registry_sync.py`
|
||||||
|
- `navsea_tile_builder.py`
|
||||||
|
- `navsea_render_audit.py`
|
||||||
|
|
||||||
|
这样数据库中的规则、代码中的构建逻辑、审计流程就能形成完整闭环。
|
||||||
|
|
||||||
139
PROJECT_PATH_AND_GIT_PLAN.md
Normal file
139
PROJECT_PATH_AND_GIT_PLAN.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# 项目路径与 Git 接管说明
|
||||||
|
|
||||||
|
## 正式项目路径
|
||||||
|
|
||||||
|
当前项目的正式工作目录是:
|
||||||
|
|
||||||
|
- `/root/sourceserver/pbf`
|
||||||
|
|
||||||
|
以后所有相关命令、脚本、VSCode 工作区、日志路径,都应以这个目录为准。
|
||||||
|
|
||||||
|
旧路径:
|
||||||
|
|
||||||
|
- `/root/weather`
|
||||||
|
|
||||||
|
当前已经不是主项目目录,不能再作为构建和审计脚本的启动路径使用。
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
已确认以下关键文件都在正式目录中:
|
||||||
|
|
||||||
|
- `navsea_tile_builder.py`
|
||||||
|
- `navsea_render_audit.py`
|
||||||
|
- `navsea_mapping_registry.py`
|
||||||
|
- `navsea_mapping_registry_sync.py`
|
||||||
|
- `tasks/pbf/`
|
||||||
|
- `src/pbf/`
|
||||||
|
|
||||||
|
已确认当前目录还不是 Git 仓库:
|
||||||
|
|
||||||
|
- `git rev-parse --is-inside-work-tree` 返回 `NOT_GIT`
|
||||||
|
|
||||||
|
## 后续统一约定
|
||||||
|
|
||||||
|
以后统一使用:
|
||||||
|
|
||||||
|
- 工作目录:`/root/sourceserver/pbf`
|
||||||
|
- Python 环境:`/root/sourceserver/pbf/.venv`
|
||||||
|
- 构建日志目录:`/root/sourceserver/pbf/logs`
|
||||||
|
|
||||||
|
## 全量工程版构建命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /root/sourceserver/pbf/logs
|
||||||
|
cd /root/sourceserver/pbf
|
||||||
|
nohup env \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONPATH=/root/sourceserver/pbf/.venv/lib/python3.12/site-packages \
|
||||||
|
python3 /root/sourceserver/pbf/navsea_tile_builder.py \
|
||||||
|
--all-tiles \
|
||||||
|
--zmin 0 \
|
||||||
|
--zmax 14 \
|
||||||
|
--output /home/wwwroot/pbf-engineering-full \
|
||||||
|
--workers 1 \
|
||||||
|
--engineering \
|
||||||
|
--fid-key 'thisMyWorld@2026' \
|
||||||
|
--fid-key-id navsea-fid-key-v1 \
|
||||||
|
--bundle-id navsea-core \
|
||||||
|
> /root/sourceserver/pbf/logs/navsea_full_engineering_build.log 2>&1 &
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git 服务器接管建议
|
||||||
|
|
||||||
|
目标服务器:
|
||||||
|
|
||||||
|
- `192.168.200.184`
|
||||||
|
|
||||||
|
建议流程:
|
||||||
|
|
||||||
|
1. 在服务器上先创建空仓库
|
||||||
|
2. 在本地项目目录初始化 Git
|
||||||
|
3. 添加 `.gitignore`
|
||||||
|
4. 首次提交
|
||||||
|
5. 添加远端并推送
|
||||||
|
|
||||||
|
## 推荐命令
|
||||||
|
|
||||||
|
### 1. 初始化本地仓库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/sourceserver/pbf
|
||||||
|
git init
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 创建主分支
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git branch -M main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 添加远端
|
||||||
|
|
||||||
|
如果 Git 服务器上仓库地址是:
|
||||||
|
|
||||||
|
- `ssh://git@192.168.200.184/<group>/pbf.git`
|
||||||
|
|
||||||
|
则命令为:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote add origin ssh://git@192.168.200.184/<group>/pbf.git
|
||||||
|
```
|
||||||
|
|
||||||
|
如果是 HTTP 地址,则改成对应 URL。
|
||||||
|
|
||||||
|
### 4. 首次提交
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial import of NavSea pbf project"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 首次推送
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 提交前建议排除的内容
|
||||||
|
|
||||||
|
建议不要直接提交这些目录或文件:
|
||||||
|
|
||||||
|
- `.venv/`
|
||||||
|
- `__pycache__/`
|
||||||
|
- `logs/`
|
||||||
|
- 大体量生成产物
|
||||||
|
- 临时输出目录
|
||||||
|
- 本地缓存
|
||||||
|
|
||||||
|
如果准备正式接管 Git,建议先补一份 `.gitignore`,再做首次提交。
|
||||||
|
|
||||||
|
## 推荐下一步
|
||||||
|
|
||||||
|
最稳的顺序是:
|
||||||
|
|
||||||
|
1. 以后统一从 `/root/sourceserver/pbf` 工作
|
||||||
|
2. 先补 `.gitignore`
|
||||||
|
3. 初始化 Git
|
||||||
|
4. 在 `192.168.200.184` 上创建空仓库
|
||||||
|
5. 推送首版代码
|
||||||
|
|
||||||
53
RUNNING.md
Normal file
53
RUNNING.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Running the weather pipeline
|
||||||
|
|
||||||
|
## Python
|
||||||
|
|
||||||
|
Use `python3`. This workspace does not provide a `python` shim.
|
||||||
|
After installing dependencies in the project virtual environment, prefer `.venv/bin/python`.
|
||||||
|
|
||||||
|
## Required packages
|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv .venv
|
||||||
|
.venv/bin/pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The pipeline requires these runtime dependencies:
|
||||||
|
|
||||||
|
- `requests`
|
||||||
|
- `numpy`
|
||||||
|
- `xarray`
|
||||||
|
- `cfgrib`
|
||||||
|
- `eccodes`
|
||||||
|
- `mercantile`
|
||||||
|
- `mapbox-vector-tile`
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Run the full pipeline:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python src/weather_pipeline.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Run individual steps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python src/gfs_downloader.py
|
||||||
|
.venv/bin/python src/grid_builder_v2.py
|
||||||
|
.venv/bin/python src/vector_tile_generator.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Wave fields in the weather tiles come from the downloader and grid builder. If you want `wh`/`wdir`/`wp` to contain real data instead of compatibility zeros from older grid JSON, rerun the downloader before rebuilding grids and tiles.
|
||||||
|
|
||||||
|
The weather tile generator now builds `z=2,4,6,8,10,12` by default so wind barb density stays usable after zooming in. If you need a custom set, override with `WEATHER_TILE_ZOOMS`, for example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WEATHER_TILE_ZOOMS=2,4,6,8,10,12,14 .venv/bin/python src/vector_tile_generator.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known environment blockers in this session
|
||||||
|
|
||||||
|
- Network access may prevent `src/gfs_downloader.py` from reaching NOAA.
|
||||||
115
UPGRADE_REPORT.md
Normal file
115
UPGRADE_REPORT.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# GFS Downloader 升级报告
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
优化 gfs_downloader.py,改进数据下载效率,特别是对于日本附近区域的气象数据。
|
||||||
|
|
||||||
|
## 升级内容
|
||||||
|
|
||||||
|
### 1. API 变更
|
||||||
|
|
||||||
|
**旧方法**: 直接下载完整 GFS 文件
|
||||||
|
```
|
||||||
|
BASE_URL = https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod
|
||||||
|
文件大小: ~500MB/file
|
||||||
|
```
|
||||||
|
|
||||||
|
**新方法**: 使用 NOAA GRIB Subset API,按需下载指定区域和变量
|
||||||
|
```
|
||||||
|
BASE_URL = https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl
|
||||||
|
文件大小: ~4.2MB/file
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据范围限制
|
||||||
|
|
||||||
|
**下载区域**:
|
||||||
|
- 左经度: 120°E
|
||||||
|
- 右经度: 150°E
|
||||||
|
- 上纬度: 50°N
|
||||||
|
- 下纬度: 20°N
|
||||||
|
- 覆盖: 日本海、东海、太平洋日本海域
|
||||||
|
|
||||||
|
**下载变量** (5个):
|
||||||
|
- UGRD: 东西方向风速
|
||||||
|
- VGRD: 南北方向风速
|
||||||
|
- APCP: 累积降水量
|
||||||
|
- PRMSL: 平均海平面气压
|
||||||
|
- TMP: 温度
|
||||||
|
|
||||||
|
注: 移除了 HTSGW、DIRPW、PERPW(浪高、浪向、浪周期)
|
||||||
|
原因: 这些变量在 GFS 0.25°数据集中不可用,应从波浪数据集获取
|
||||||
|
|
||||||
|
**下载高度层** (3个):
|
||||||
|
- 10m above ground
|
||||||
|
- Surface
|
||||||
|
- Mean sea level
|
||||||
|
|
||||||
|
### 3. 性能改进
|
||||||
|
|
||||||
|
| 指标 | 旧方法 | 新方法 | 改进 |
|
||||||
|
|------|-------|-------|------|
|
||||||
|
| 单文件大小 | 485.6 MB | 3.3-4.4 MB | **99.3%** ↓ |
|
||||||
|
| 72小时预报文件数 | 6个 | 25个 | 100% 完整率 |
|
||||||
|
| 72小时总大小 | 2.7 GB | 105 MB | **96%** ↓ |
|
||||||
|
| 平均单位数据量 | 450 MB/预报 | 4.2 MB/预报 | **99%** ↓ |
|
||||||
|
| 下载时间 (估计) | ~1小时+ | ~10分钟 | 85% ↓ |
|
||||||
|
|
||||||
|
### 4. 测试结果
|
||||||
|
|
||||||
|
✅ **数据完整性**: 25个预报时次全部下载成功
|
||||||
|
✅ **文件有效性**: GRIB 格式正确,数据可正常解析
|
||||||
|
✅ **覆盖范围**: 日本附近区域完整覆盖 (1440×721 格点)
|
||||||
|
✅ **变量可用性**: UGRD、VGRD、PRMSL、TMP、APCP 全部可读
|
||||||
|
|
||||||
|
### 5. 下载统计
|
||||||
|
|
||||||
|
```
|
||||||
|
下载文件数: 25个
|
||||||
|
总数据量: 105 MB
|
||||||
|
预报周期: 2026-03-12 00Z
|
||||||
|
分辨率: 0.25° × 0.25°
|
||||||
|
时间间距: 3小时 (f000-f072)
|
||||||
|
|
||||||
|
文件大小分布:
|
||||||
|
3.4 MB: 1个
|
||||||
|
4.0 MB: 1个
|
||||||
|
4.1 MB: 2个
|
||||||
|
4.2 MB: 9个
|
||||||
|
4.3 MB: 9个
|
||||||
|
4.4 MB: 3个
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键改进
|
||||||
|
|
||||||
|
1. **存储优化**: 数据量从 2.7GB 减至 105MB,节省 96% 存储空间
|
||||||
|
2. **网络优化**: 下载时间减少 85%,更快获得气象数据
|
||||||
|
3. **区域专化**: 仅下载日本附近数据,避免全球数据浪费
|
||||||
|
4. **自动化**: 脚本自动重试、跳过已完成文件,支持续下载
|
||||||
|
|
||||||
|
## 下一步建议
|
||||||
|
|
||||||
|
1. **波浪数据集成**: 单独配置浪高、浪向、浪周期数据源
|
||||||
|
- 来源: NOAA WW3 或 GFS Wave 数据
|
||||||
|
- 下载器: filter_gfs_wave_0p25.pl
|
||||||
|
|
||||||
|
2. **多区域支持**: 配置不同地区的下载参数
|
||||||
|
- 东南亚区域、太平洋区域等
|
||||||
|
|
||||||
|
3. **增量更新**: 每 3 小时更新最新的 GFS 预报周期
|
||||||
|
|
||||||
|
4. **数据验证**: 添加 MD5 校验、数据完整性检查
|
||||||
|
|
||||||
|
## 验证命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看下载的文件
|
||||||
|
ls -lh data/grib/*.grib2
|
||||||
|
|
||||||
|
# 验证 GRIB 文件可读性
|
||||||
|
source /root/weather_test_env/bin/activate
|
||||||
|
python3 << 'EOF'
|
||||||
|
import xarray as xr
|
||||||
|
ds = xr.open_dataset("data/grib/20260312_00_f000.grib2",
|
||||||
|
engine="cfgrib",
|
||||||
|
filter_by_keys={'typeOfLevel': 'surface'})
|
||||||
|
print(ds.data_vars)
|
||||||
629
navsea_audit.py
Normal file
629
navsea_audit.py
Normal file
@@ -0,0 +1,629 @@
|
|||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
STYLE_JSON_PATH = ROOT / "src" / "pbf" / "style.json"
|
||||||
|
REPORT_PATH = ROOT / "navsea_classification_report.md"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
|
||||||
|
port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
|
||||||
|
user: str = os.getenv("NAVSEA_DB_USER", "root")
|
||||||
|
password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
|
||||||
|
database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
|
||||||
|
unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
|
||||||
|
|
||||||
|
|
||||||
|
class NavSeaAudit:
|
||||||
|
def __init__(self, db_config: DbConfig) -> None:
|
||||||
|
self.db_config = db_config
|
||||||
|
self.metrics: dict[str, int | float | str] = {}
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def connect(self, *, streaming: bool = False, local_infile: bool = False):
|
||||||
|
kwargs = {
|
||||||
|
"host": self.db_config.host,
|
||||||
|
"port": self.db_config.port,
|
||||||
|
"user": self.db_config.user,
|
||||||
|
"password": self.db_config.password,
|
||||||
|
"database": self.db_config.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
"local_infile": local_infile,
|
||||||
|
"cursorclass": pymysql.cursors.SSCursor if streaming else pymysql.cursors.Cursor,
|
||||||
|
}
|
||||||
|
if self.db_config.unix_socket and self.db_config.host in {"localhost", "127.0.0.1"}:
|
||||||
|
kwargs["unix_socket"] = self.db_config.unix_socket
|
||||||
|
connection = pymysql.connect(**kwargs)
|
||||||
|
try:
|
||||||
|
yield connection
|
||||||
|
connection.commit()
|
||||||
|
except Exception:
|
||||||
|
connection.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
start = time.time()
|
||||||
|
self.validate_inputs()
|
||||||
|
self.parse_at_attributes()
|
||||||
|
self.build_feature_semantic()
|
||||||
|
self.build_object_catalog()
|
||||||
|
self.build_geometry_consistency()
|
||||||
|
self.build_object_type_candidates()
|
||||||
|
self.build_object_type_stats()
|
||||||
|
self.build_style_layers()
|
||||||
|
self.build_style_mapping()
|
||||||
|
self.build_tile_density()
|
||||||
|
self.build_anomaly_tables()
|
||||||
|
self.generate_report()
|
||||||
|
self.metrics["pipeline_runtime_seconds"] = round(time.time() - start, 2)
|
||||||
|
print(f"Pipeline completed in {self.metrics['pipeline_runtime_seconds']} seconds")
|
||||||
|
|
||||||
|
def validate_inputs(self) -> None:
|
||||||
|
if not STYLE_JSON_PATH.exists():
|
||||||
|
raise FileNotFoundError(f"style.json not found: {STYLE_JSON_PATH}")
|
||||||
|
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for table in ("features", "properties"):
|
||||||
|
cur.execute("SHOW TABLES LIKE %s", (table,))
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
raise RuntimeError(f"required table missing: {table}")
|
||||||
|
|
||||||
|
cur.execute("SELECT COUNT(*) FROM features")
|
||||||
|
self.metrics["source_feature_count"] = cur.fetchone()[0]
|
||||||
|
cur.execute("SELECT COUNT(*) FROM properties")
|
||||||
|
self.metrics["source_property_count"] = cur.fetchone()[0]
|
||||||
|
cur.execute("SELECT COUNT(*) FROM properties WHERE k='at'")
|
||||||
|
self.metrics["source_at_count"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Validated inputs:",
|
||||||
|
self.metrics["source_feature_count"],
|
||||||
|
"features,",
|
||||||
|
self.metrics["source_property_count"],
|
||||||
|
"properties,",
|
||||||
|
self.metrics["source_at_count"],
|
||||||
|
"at rows",
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse_at_attributes(self) -> None:
|
||||||
|
print("Parsing at attributes...")
|
||||||
|
parsed_rows = 0
|
||||||
|
source_rows = 0
|
||||||
|
parse_errors = 0
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
mode="w",
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="",
|
||||||
|
suffix=".tsv",
|
||||||
|
delete=False,
|
||||||
|
dir="/tmp",
|
||||||
|
) as temp_file:
|
||||||
|
temp_path = Path(temp_file.name)
|
||||||
|
writer = csv.writer(
|
||||||
|
temp_file,
|
||||||
|
delimiter="\t",
|
||||||
|
quotechar='"',
|
||||||
|
escapechar="\\",
|
||||||
|
lineterminator="\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.connect(streaming=True) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT feature_id, v FROM properties WHERE k='at'")
|
||||||
|
for feature_id, raw_value in cur:
|
||||||
|
source_rows += 1
|
||||||
|
try:
|
||||||
|
pairs = json.loads(raw_value)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
parse_errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(pairs, list):
|
||||||
|
parse_errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
for pair in pairs:
|
||||||
|
if not isinstance(pair, list) or len(pair) != 2:
|
||||||
|
parse_errors += 1
|
||||||
|
continue
|
||||||
|
key, value = pair
|
||||||
|
writer.writerow(
|
||||||
|
(
|
||||||
|
int(feature_id),
|
||||||
|
"" if key is None else str(key),
|
||||||
|
"" if value is None else str(value),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
parsed_rows += 1
|
||||||
|
|
||||||
|
with self.connect(local_infile=True) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS at_attributes")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE at_attributes (
|
||||||
|
feature_id BIGINT NOT NULL,
|
||||||
|
k VARCHAR(100) NOT NULL,
|
||||||
|
v TEXT NULL,
|
||||||
|
KEY idx_feature_id (feature_id),
|
||||||
|
KEY idx_k (k),
|
||||||
|
KEY idx_feature_k (feature_id, k)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
LOAD DATA LOCAL INFILE %s
|
||||||
|
INTO TABLE at_attributes
|
||||||
|
CHARACTER SET utf8mb4
|
||||||
|
FIELDS TERMINATED BY '\t'
|
||||||
|
ENCLOSED BY '"'
|
||||||
|
ESCAPED BY '\\\\'
|
||||||
|
LINES TERMINATED BY '\n'
|
||||||
|
(feature_id, k, v)
|
||||||
|
""",
|
||||||
|
(str(temp_path),),
|
||||||
|
)
|
||||||
|
|
||||||
|
temp_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
self.metrics["at_source_rows"] = source_rows
|
||||||
|
self.metrics["at_attribute_rows"] = parsed_rows
|
||||||
|
self.metrics["at_parse_errors"] = parse_errors
|
||||||
|
print(
|
||||||
|
"Parsed at attributes:",
|
||||||
|
source_rows,
|
||||||
|
"source rows ->",
|
||||||
|
parsed_rows,
|
||||||
|
"attribute rows,",
|
||||||
|
parse_errors,
|
||||||
|
"parse errors",
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_feature_semantic(self) -> None:
|
||||||
|
print("Building feature_semantic...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS feature_semantic")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE feature_semantic AS
|
||||||
|
SELECT
|
||||||
|
f.id AS feature_id,
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y,
|
||||||
|
f.vt_layer,
|
||||||
|
f.geom_type,
|
||||||
|
MAX(CASE WHEN a.k='分類' THEN a.v END) AS class_name,
|
||||||
|
MAX(CASE WHEN a.k='形状分類' THEN a.v END) AS shape_name,
|
||||||
|
MAX(CASE WHEN a.k='レイヤ' THEN a.v END) AS layer_name
|
||||||
|
FROM features f
|
||||||
|
LEFT JOIN at_attributes a
|
||||||
|
ON f.id = a.feature_id
|
||||||
|
GROUP BY
|
||||||
|
f.id,
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y,
|
||||||
|
f.vt_layer,
|
||||||
|
f.geom_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE feature_semantic ADD PRIMARY KEY (feature_id)")
|
||||||
|
cur.execute("ALTER TABLE feature_semantic ADD KEY idx_vt_layer (vt_layer)")
|
||||||
|
cur.execute("ALTER TABLE feature_semantic ADD KEY idx_class_name (class_name(100))")
|
||||||
|
cur.execute("ALTER TABLE feature_semantic ADD KEY idx_geom_type (geom_type)")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM feature_semantic")
|
||||||
|
self.metrics["feature_semantic_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_object_catalog(self) -> None:
|
||||||
|
print("Building object_catalog...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS object_catalog")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE object_catalog AS
|
||||||
|
SELECT
|
||||||
|
layer_name,
|
||||||
|
class_name,
|
||||||
|
shape_name,
|
||||||
|
vt_layer,
|
||||||
|
geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM feature_semantic
|
||||||
|
GROUP BY
|
||||||
|
layer_name,
|
||||||
|
class_name,
|
||||||
|
shape_name,
|
||||||
|
vt_layer,
|
||||||
|
geom_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE object_catalog ADD KEY idx_class_name (class_name(100))")
|
||||||
|
cur.execute("ALTER TABLE object_catalog ADD KEY idx_vt_layer (vt_layer)")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM object_catalog")
|
||||||
|
self.metrics["object_catalog_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_geometry_consistency(self) -> None:
|
||||||
|
print("Building geometry_consistency...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS geometry_consistency")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE geometry_consistency AS
|
||||||
|
SELECT
|
||||||
|
class_name,
|
||||||
|
geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM feature_semantic
|
||||||
|
GROUP BY class_name, geom_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE geometry_consistency ADD KEY idx_class_name (class_name(100))")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM geometry_consistency")
|
||||||
|
self.metrics["geometry_consistency_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_object_type_candidates(self) -> None:
|
||||||
|
print("Building object_type_candidates...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS object_type_candidates")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE object_type_candidates AS
|
||||||
|
SELECT
|
||||||
|
feature_id,
|
||||||
|
CASE
|
||||||
|
WHEN class_name IS NOT NULL AND class_name <> '' THEN 'class_name'
|
||||||
|
WHEN shape_name IS NOT NULL AND shape_name <> '' THEN 'shape_name'
|
||||||
|
ELSE 'vt_layer'
|
||||||
|
END AS object_type_source,
|
||||||
|
COALESCE(NULLIF(class_name, ''), NULLIF(shape_name, ''), vt_layer) AS object_type,
|
||||||
|
vt_layer,
|
||||||
|
geom_type
|
||||||
|
FROM feature_semantic
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE object_type_candidates ADD PRIMARY KEY (feature_id)")
|
||||||
|
cur.execute("ALTER TABLE object_type_candidates ADD KEY idx_object_type (object_type(100))")
|
||||||
|
cur.execute("ALTER TABLE object_type_candidates ADD KEY idx_vt_layer (vt_layer)")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM object_type_candidates")
|
||||||
|
self.metrics["object_type_candidate_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_object_type_stats(self) -> None:
|
||||||
|
print("Building object_type_stats...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS object_type_stats")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE object_type_stats AS
|
||||||
|
SELECT
|
||||||
|
object_type,
|
||||||
|
geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM object_type_candidates
|
||||||
|
GROUP BY object_type, geom_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE object_type_stats ADD KEY idx_object_type (object_type(100))")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM object_type_stats")
|
||||||
|
self.metrics["object_type_stats_rows"] = cur.fetchone()[0]
|
||||||
|
cur.execute("SELECT COUNT(DISTINCT object_type) FROM object_type_candidates")
|
||||||
|
self.metrics["distinct_object_types"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_style_layers(self) -> None:
|
||||||
|
print("Building style_layers from style.json...")
|
||||||
|
style_data = json.loads(STYLE_JSON_PATH.read_text(encoding="utf-8"))
|
||||||
|
rows = []
|
||||||
|
for layer in style_data.get("layers", []):
|
||||||
|
paint = layer.get("paint", {})
|
||||||
|
layout = layer.get("layout", {})
|
||||||
|
rows.append(
|
||||||
|
(
|
||||||
|
layer.get("id"),
|
||||||
|
layer.get("source-layer"),
|
||||||
|
self.stringify_style_value(layout.get("icon-image")),
|
||||||
|
self.stringify_style_value(paint.get("line-color")),
|
||||||
|
self.stringify_style_value(paint.get("fill-color")),
|
||||||
|
layer.get("type"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS style_layers")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE style_layers (
|
||||||
|
layer_id VARCHAR(200) NOT NULL,
|
||||||
|
source_layer VARCHAR(200) NULL,
|
||||||
|
icon VARCHAR(2000) NULL,
|
||||||
|
line_color VARCHAR(2000) NULL,
|
||||||
|
fill_color VARCHAR(2000) NULL,
|
||||||
|
layer_type VARCHAR(50) NULL,
|
||||||
|
KEY idx_source_layer (source_layer),
|
||||||
|
KEY idx_layer_id (layer_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO style_layers
|
||||||
|
(layer_id, source_layer, icon, line_color, fill_color, layer_type)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
rows,
|
||||||
|
)
|
||||||
|
self.metrics["style_layer_rows"] = len(rows)
|
||||||
|
|
||||||
|
def build_style_mapping(self) -> None:
|
||||||
|
print("Building style_mapping...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS style_mapping")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE style_mapping AS
|
||||||
|
SELECT
|
||||||
|
s.layer_id,
|
||||||
|
s.source_layer,
|
||||||
|
s.layer_type,
|
||||||
|
s.icon,
|
||||||
|
s.line_color,
|
||||||
|
s.fill_color,
|
||||||
|
o.object_type,
|
||||||
|
o.geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM style_layers s
|
||||||
|
JOIN object_type_candidates o
|
||||||
|
ON s.source_layer = o.object_type
|
||||||
|
GROUP BY
|
||||||
|
s.layer_id,
|
||||||
|
s.source_layer,
|
||||||
|
s.layer_type,
|
||||||
|
s.icon,
|
||||||
|
s.line_color,
|
||||||
|
s.fill_color,
|
||||||
|
o.object_type,
|
||||||
|
o.geom_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE style_mapping ADD KEY idx_object_type (object_type(100))")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM style_mapping")
|
||||||
|
self.metrics["style_mapping_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_tile_density(self) -> None:
|
||||||
|
print("Building tile_density, tile_layer_density and tile_density_top100...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS tile_density")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE tile_density AS
|
||||||
|
SELECT
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y,
|
||||||
|
COUNT(DISTINCT f.id) AS feature_count
|
||||||
|
FROM features AS f
|
||||||
|
GROUP BY
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE tile_density ADD KEY idx_tile (z, x, y)")
|
||||||
|
cur.execute("ALTER TABLE tile_density ADD KEY idx_feature_count (feature_count)")
|
||||||
|
cur.execute("DROP TABLE IF EXISTS tile_layer_density")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE tile_layer_density AS
|
||||||
|
SELECT
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y,
|
||||||
|
f.vt_layer,
|
||||||
|
COUNT(DISTINCT f.id) AS feature_count
|
||||||
|
FROM features AS f
|
||||||
|
GROUP BY
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y,
|
||||||
|
f.vt_layer
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE tile_layer_density ADD KEY idx_tile (z, x, y)")
|
||||||
|
cur.execute("ALTER TABLE tile_layer_density ADD KEY idx_layer (vt_layer)")
|
||||||
|
cur.execute("ALTER TABLE tile_layer_density ADD KEY idx_feature_count (feature_count)")
|
||||||
|
cur.execute("DROP TABLE IF EXISTS tile_density_top100")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE tile_density_top100 AS
|
||||||
|
SELECT *
|
||||||
|
FROM tile_density
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
LIMIT 100
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("SELECT COUNT(*) FROM tile_density")
|
||||||
|
self.metrics["tile_density_rows"] = cur.fetchone()[0]
|
||||||
|
cur.execute("SELECT COUNT(*) FROM tile_layer_density")
|
||||||
|
self.metrics["tile_layer_density_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def build_anomaly_tables(self) -> None:
|
||||||
|
print("Building anomaly tables...")
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS anomaly_navigation_geom")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE anomaly_navigation_geom AS
|
||||||
|
SELECT *
|
||||||
|
FROM feature_semantic
|
||||||
|
WHERE class_name='灯台'
|
||||||
|
AND geom_type <> 'Point'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("DROP TABLE IF EXISTS anomaly_reef_geom")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE anomaly_reef_geom AS
|
||||||
|
SELECT *
|
||||||
|
FROM feature_semantic
|
||||||
|
WHERE class_name='魚礁'
|
||||||
|
AND geom_type NOT IN ('Point', 'Polygon')
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("SELECT COUNT(*) FROM anomaly_navigation_geom")
|
||||||
|
self.metrics["anomaly_navigation_geom_rows"] = cur.fetchone()[0]
|
||||||
|
cur.execute("SELECT COUNT(*) FROM anomaly_reef_geom")
|
||||||
|
self.metrics["anomaly_reef_geom_rows"] = cur.fetchone()[0]
|
||||||
|
|
||||||
|
def generate_report(self) -> None:
|
||||||
|
print("Generating navsea_classification_report.md...")
|
||||||
|
total_features = self.scalar("SELECT COUNT(*) FROM features")
|
||||||
|
total_object_types = self.scalar("SELECT COUNT(DISTINCT object_type) FROM object_type_candidates")
|
||||||
|
top_object_types = self.fetchall(
|
||||||
|
"""
|
||||||
|
SELECT object_type, geom_type, feature_count
|
||||||
|
FROM object_type_stats
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
LIMIT 50
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
geometry_consistency = self.fetchall(
|
||||||
|
"""
|
||||||
|
SELECT class_name, geom_type, feature_count
|
||||||
|
FROM geometry_consistency
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
LIMIT 100
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
style_mapping = self.fetchall(
|
||||||
|
"""
|
||||||
|
SELECT layer_id, source_layer, object_type, geom_type, feature_count
|
||||||
|
FROM style_mapping
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
LIMIT 100
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
tile_density = self.fetchall(
|
||||||
|
"""
|
||||||
|
SELECT z, x, y, feature_count
|
||||||
|
FROM tile_density_top100
|
||||||
|
ORDER BY feature_count DESC, z, x, y
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
source_breakdown = self.fetchall(
|
||||||
|
"""
|
||||||
|
SELECT object_type_source, COUNT(*) AS feature_count
|
||||||
|
FROM object_type_candidates
|
||||||
|
GROUP BY object_type_source
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# NavSea Classification Report",
|
||||||
|
"",
|
||||||
|
"## Dataset Summary",
|
||||||
|
"",
|
||||||
|
f"- Total features: {total_features}",
|
||||||
|
f"- Total properties: {self.metrics['source_property_count']}",
|
||||||
|
f"- Total `at` rows: {self.metrics['source_at_count']}",
|
||||||
|
f"- Parsed `at` attribute rows: {self.metrics['at_attribute_rows']}",
|
||||||
|
f"- `at` parse errors: {self.metrics['at_parse_errors']}",
|
||||||
|
f"- Total object types: {total_object_types}",
|
||||||
|
f"- Style layers parsed: {self.metrics['style_layer_rows']}",
|
||||||
|
f"- Style mappings found: {self.metrics['style_mapping_rows']}",
|
||||||
|
"",
|
||||||
|
"## Object Type Source Breakdown",
|
||||||
|
"",
|
||||||
|
self.render_table(
|
||||||
|
["object_type_source", "feature_count"],
|
||||||
|
source_breakdown,
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
"## Top Object Types",
|
||||||
|
"",
|
||||||
|
self.render_table(["object_type", "geom_type", "feature_count"], top_object_types),
|
||||||
|
"",
|
||||||
|
"## Geometry Consistency",
|
||||||
|
"",
|
||||||
|
self.render_table(["class_name", "geom_type", "feature_count"], geometry_consistency),
|
||||||
|
"",
|
||||||
|
"## Style Mapping",
|
||||||
|
"",
|
||||||
|
self.render_table(
|
||||||
|
["layer_id", "source_layer", "object_type", "geom_type", "feature_count"],
|
||||||
|
style_mapping,
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
"## Tile Density Top 100",
|
||||||
|
"",
|
||||||
|
self.render_table(["z", "x", "y", "feature_count"], tile_density),
|
||||||
|
"",
|
||||||
|
"## Spatial Sanity Checks",
|
||||||
|
"",
|
||||||
|
f"- anomaly_navigation_geom rows: {self.metrics['anomaly_navigation_geom_rows']}",
|
||||||
|
f"- anomaly_reef_geom rows: {self.metrics['anomaly_reef_geom_rows']}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
REPORT_PATH.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
|
||||||
|
def scalar(self, sql: str):
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql)
|
||||||
|
return cur.fetchone()[0]
|
||||||
|
|
||||||
|
def fetchall(self, sql: str):
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(sql)
|
||||||
|
return cur.fetchall()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def stringify_style_value(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value
|
||||||
|
return json.dumps(value, ensure_ascii=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def render_table(headers, rows) -> str:
|
||||||
|
lines = [
|
||||||
|
"| " + " | ".join(headers) + " |",
|
||||||
|
"| " + " | ".join(["---"] * len(headers)) + " |",
|
||||||
|
]
|
||||||
|
for row in rows:
|
||||||
|
lines.append("| " + " | ".join("" if value is None else str(value) for value in row) + " |")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
NavSeaAudit(DbConfig()).run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
337
navsea_classification_report.md
Normal file
337
navsea_classification_report.md
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# NavSea Classification Report
|
||||||
|
|
||||||
|
## Dataset Summary
|
||||||
|
|
||||||
|
- Total features: 6685117
|
||||||
|
- Total properties: 23659423
|
||||||
|
- Total `at` rows: 2823055
|
||||||
|
- Parsed `at` attribute rows: 6614767
|
||||||
|
- `at` parse errors: 0
|
||||||
|
- Total object types: 177
|
||||||
|
- Style layers parsed: 87
|
||||||
|
- Style mappings found: 42
|
||||||
|
|
||||||
|
## Object Type Source Breakdown
|
||||||
|
|
||||||
|
| object_type_source | feature_count |
|
||||||
|
| --- | --- |
|
||||||
|
| vt_layer | 3862062 |
|
||||||
|
| class_name | 2784535 |
|
||||||
|
| shape_name | 38520 |
|
||||||
|
|
||||||
|
## Top Object Types
|
||||||
|
|
||||||
|
| object_type | geom_type | feature_count |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| L海底地形 | LineString | 1866386 |
|
||||||
|
| 道路 | LineString | 1256228 |
|
||||||
|
| P基本線ククリ | LineString | 1074484 |
|
||||||
|
| 等深線 | LineString | 434442 |
|
||||||
|
| P穴 | Polygon | 361754 |
|
||||||
|
| L海底地形 | MultiLineString | 208786 |
|
||||||
|
| P陸域 | Polygon | 193114 |
|
||||||
|
| 干潮帯 | Polygon | 114265 |
|
||||||
|
| 底質 | Point | 99863 |
|
||||||
|
| 5-10m | Polygon | 66271 |
|
||||||
|
| 防波堤 | Polygon | 62117 |
|
||||||
|
| 2-5m | Polygon | 59238 |
|
||||||
|
| P危険界ククリ | LineString | 51743 |
|
||||||
|
| 0-2m | Polygon | 47385 |
|
||||||
|
| p地名 | Point | 36247 |
|
||||||
|
| p地名陸 | Point | 35388 |
|
||||||
|
| 10-20m | Polygon | 34190 |
|
||||||
|
| 浅所危険界 | Polygon | 29950 |
|
||||||
|
| ビル | Polygon | 26813 |
|
||||||
|
| 0-5m | Polygon | 26366 |
|
||||||
|
| 橋 (水門) | Polygon | 25708 |
|
||||||
|
| 一本線で表わす桟橋 | LineString | 25497 |
|
||||||
|
| 険悪物 | Point | 22314 |
|
||||||
|
| 5-10m | MultiPolygon | 21217 |
|
||||||
|
| 区画漁業 | Polygon | 18759 |
|
||||||
|
| 浮施設・桟橋 | LineString | 16873 |
|
||||||
|
| 1000-2000m | Polygon | 15663 |
|
||||||
|
| 灯 (Lt) | Point | 14936 |
|
||||||
|
| 0-5m | MultiPolygon | 14841 |
|
||||||
|
| 干潮帯 | MultiPolygon | 14617 |
|
||||||
|
| 20-100m | Polygon | 14605 |
|
||||||
|
| 2000-3000m | Polygon | 14540 |
|
||||||
|
| 100-200m | Polygon | 13018 |
|
||||||
|
| 浮施設・桟橋 | Polygon | 12763 |
|
||||||
|
| 海底線 (電信電話) | LineString | 12712 |
|
||||||
|
| 魚礁 | Point | 12477 |
|
||||||
|
| 500-1000m | Polygon | 12363 |
|
||||||
|
| 3000-4000m | Polygon | 11683 |
|
||||||
|
| 5000-6000m | Polygon | 11147 |
|
||||||
|
| 200-500m | Polygon | 10932 |
|
||||||
|
| 塔、やぐら、風車 | Point | 10818 |
|
||||||
|
| 暗岩 | Point | 10366 |
|
||||||
|
| 2-5m | MultiPolygon | 10183 |
|
||||||
|
| 4000-5000m | Polygon | 10099 |
|
||||||
|
| 10-20m | MultiPolygon | 9881 |
|
||||||
|
| 山頂 | Point | 9686 |
|
||||||
|
| 一本線で表わす海上バース | LineString | 9366 |
|
||||||
|
| 河川域 | Polygon | 9081 |
|
||||||
|
| タンク | Polygon | 8765 |
|
||||||
|
| P投錨注意障害物ククリ | LineString | 8465 |
|
||||||
|
|
||||||
|
## Geometry Consistency
|
||||||
|
|
||||||
|
| class_name | geom_type | feature_count |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| | LineString | 3003658 |
|
||||||
|
| 道路 | LineString | 1256228 |
|
||||||
|
| | Polygon | 554868 |
|
||||||
|
| 等深線 | LineString | 434442 |
|
||||||
|
| | MultiLineString | 215850 |
|
||||||
|
| | Point | 115202 |
|
||||||
|
| 干潮帯 | Polygon | 114265 |
|
||||||
|
| 底質 | Point | 99863 |
|
||||||
|
| 5-10m | Polygon | 66271 |
|
||||||
|
| 防波堤 | Polygon | 62117 |
|
||||||
|
| 2-5m | Polygon | 59238 |
|
||||||
|
| 0-2m | Polygon | 47385 |
|
||||||
|
| 10-20m | Polygon | 34190 |
|
||||||
|
| 浅所危険界 | Polygon | 29950 |
|
||||||
|
| ビル | Polygon | 26813 |
|
||||||
|
| 0-5m | Polygon | 26366 |
|
||||||
|
| 橋 (水門) | Polygon | 25708 |
|
||||||
|
| 一本線で表わす桟橋 | LineString | 25497 |
|
||||||
|
| 険悪物 | Point | 22314 |
|
||||||
|
| 5-10m | MultiPolygon | 21217 |
|
||||||
|
| 区画漁業 | Polygon | 18759 |
|
||||||
|
| 浮施設・桟橋 | LineString | 16873 |
|
||||||
|
| 1000-2000m | Polygon | 15663 |
|
||||||
|
| 0-5m | MultiPolygon | 14841 |
|
||||||
|
| 干潮帯 | MultiPolygon | 14617 |
|
||||||
|
| 20-100m | Polygon | 14605 |
|
||||||
|
| 2000-3000m | Polygon | 14540 |
|
||||||
|
| 100-200m | Polygon | 13018 |
|
||||||
|
| 浮施設・桟橋 | Polygon | 12763 |
|
||||||
|
| 海底線 (電信電話) | LineString | 12712 |
|
||||||
|
| 魚礁 | Point | 12477 |
|
||||||
|
| 500-1000m | Polygon | 12363 |
|
||||||
|
| 3000-4000m | Polygon | 11683 |
|
||||||
|
| 5000-6000m | Polygon | 11147 |
|
||||||
|
| | MultiPolygon | 11004 |
|
||||||
|
| 200-500m | Polygon | 10932 |
|
||||||
|
| 塔、やぐら、風車 | Point | 10818 |
|
||||||
|
| 暗岩 | Point | 10366 |
|
||||||
|
| 2-5m | MultiPolygon | 10183 |
|
||||||
|
| 4000-5000m | Polygon | 10099 |
|
||||||
|
| 10-20m | MultiPolygon | 9881 |
|
||||||
|
| 山頂 | Point | 9686 |
|
||||||
|
| 一本線で表わす海上バース | LineString | 9366 |
|
||||||
|
| 河川域 | Polygon | 9081 |
|
||||||
|
| タンク | Polygon | 8765 |
|
||||||
|
| 煙突 | Point | 8123 |
|
||||||
|
| 等深線 | MultiLineString | 6736 |
|
||||||
|
| 0-2m | MultiPolygon | 6312 |
|
||||||
|
| 概略等深線 | LineString | 5497 |
|
||||||
|
| 0-10m | Polygon | 5435 |
|
||||||
|
| 6000-7000m | Polygon | 5016 |
|
||||||
|
| 険悪物 | Polygon | 4519 |
|
||||||
|
| 未測海域 | Polygon | 4190 |
|
||||||
|
| 海草 | Point | 4175 |
|
||||||
|
| 魚礁 | Polygon | 4118 |
|
||||||
|
| 潜提 | Polygon | 3780 |
|
||||||
|
| 定置漁業 | Polygon | 3566 |
|
||||||
|
| 洗岩 | Point | 3323 |
|
||||||
|
| 漁港 | Point | 3169 |
|
||||||
|
| 湖沼等深線 | LineString | 3124 |
|
||||||
|
| 防波堤 | MultiPolygon | 3032 |
|
||||||
|
| 漁業協同組合 | Point | 2914 |
|
||||||
|
| 河川域 | MultiPolygon | 2714 |
|
||||||
|
| 撤去跡 | Polygon | 2407 |
|
||||||
|
| サンドウェーブ | Point | 2356 |
|
||||||
|
| 7000-8000m | Polygon | 2306 |
|
||||||
|
| 急潮・波紋・激潮 | Point | 2206 |
|
||||||
|
| ポンツーン、パイル、杭 | Point | 2119 |
|
||||||
|
| 0-10m | MultiPolygon | 2082 |
|
||||||
|
| 道路 | MultiLineString | 2034 |
|
||||||
|
| 20-100m | MultiPolygon | 1867 |
|
||||||
|
| 港則法による境界 | LineString | 1665 |
|
||||||
|
| 浅所危険界 | MultiPolygon | 1587 |
|
||||||
|
| 500-1000m | MultiPolygon | 1461 |
|
||||||
|
| 2-10m | Polygon | 1367 |
|
||||||
|
| 1000-2000m | MultiPolygon | 1364 |
|
||||||
|
| 干出岩 | Point | 1178 |
|
||||||
|
| 2000-3000m | MultiPolygon | 1141 |
|
||||||
|
| 100-200m | MultiPolygon | 1135 |
|
||||||
|
| 0-20m | Polygon | 1111 |
|
||||||
|
| 200-500m | MultiPolygon | 1100 |
|
||||||
|
| 8000-9000m | Polygon | 1100 |
|
||||||
|
| 一本線で表わす潜提 | LineString | 1096 |
|
||||||
|
| 架空線、送電線 | LineString | 1028 |
|
||||||
|
| 陸上水域 | Polygon | 971 |
|
||||||
|
| 湖沼域 | Polygon | 870 |
|
||||||
|
| 5-20m | Polygon | 838 |
|
||||||
|
| 孤立危険物 | Point | 824 |
|
||||||
|
| 全沈没船 (危険なし) | Point | 811 |
|
||||||
|
| 3000-4000m | MultiPolygon | 801 |
|
||||||
|
| 一本線で表わす撤去跡 | LineString | 780 |
|
||||||
|
| ドルフィン | Point | 756 |
|
||||||
|
| 湖沼等深線 | MultiLineString | 749 |
|
||||||
|
| 5000-6000m | MultiPolygon | 737 |
|
||||||
|
| 海底線 (電力) | LineString | 735 |
|
||||||
|
| 4000-5000m | MultiPolygon | 730 |
|
||||||
|
| 養殖場 | Polygon | 716 |
|
||||||
|
| 海底設置物、放水口、取水口 | Point | 715 |
|
||||||
|
| 海底輸送管 (水) | LineString | 649 |
|
||||||
|
| 一般港湾 (港則法区域、重要港湾) | Point | 610 |
|
||||||
|
|
||||||
|
## Style Mapping
|
||||||
|
|
||||||
|
| layer_id | source_layer | object_type | geom_type | feature_count |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| #b#C000 | L海底地形 | L海底地形 | LineString | 1866386 |
|
||||||
|
| #b#海底地形 | L海底地形 | L海底地形 | LineString | 1866386 |
|
||||||
|
| #b#d2#d5#d10#P基本線ククリ | P基本線ククリ | P基本線ククリ | LineString | 1074484 |
|
||||||
|
| #b#d2#d5#d10#P穴 | P穴 | P穴 | Polygon | 361754 |
|
||||||
|
| #b#C000 | L海底地形 | L海底地形 | MultiLineString | 208786 |
|
||||||
|
| #b#海底地形 | L海底地形 | L海底地形 | MultiLineString | 208786 |
|
||||||
|
| #b#d2#d5#d10#land#P100 | P陸域 | P陸域 | Polygon | 193114 |
|
||||||
|
| #b#d2#d5#d10#P危険界ククリ | P危険界ククリ | P危険界ククリ | LineString | 51743 |
|
||||||
|
| #b#d2#d5#d10#C800#EN | p地名 | p地名 | Point | 36247 |
|
||||||
|
| #b#d2#d5#d10#C800#JA | p地名 | p地名 | Point | 36247 |
|
||||||
|
| #b#d2#d5#d10#land#C800#EN | p地名陸 | p地名陸 | Point | 35388 |
|
||||||
|
| #b#d2#d5#d10#land#C800#JA | p地名陸 | p地名陸 | Point | 35388 |
|
||||||
|
| #b#d2#d5#d10#L4#P投錨注意障害物ククリ | P投錨注意障害物ククリ | P投錨注意障害物ククリ | LineString | 8465 |
|
||||||
|
| #b#d2#d5#d10#P基本線ククリ | P基本線ククリ | P基本線ククリ | MultiLineString | 6296 |
|
||||||
|
| #b#d2#d5#d10#land#P100 | P陸域 | P陸域 | MultiPolygon | 5541 |
|
||||||
|
| #b#d2#d5#d10#P穴 | P穴 | P穴 | MultiPolygon | 5463 |
|
||||||
|
| #b#d2#d5#d10#at#P4#P投錨注意障害物 | P投錨注意障害物 | p投錨注意障害物 | Point | 3926 |
|
||||||
|
| #b#d2#d5#d10#at#S4#S投錨注意障害物 | p投錨注意障害物 | p投錨注意障害物 | Point | 3926 |
|
||||||
|
| #b#d2#d5#d10#C600#名称 | p高さ制限 | p高さ制限 | Point | 858 |
|
||||||
|
| #b#d2#d5#d10#C600#高さ | p高さ制限 | p高さ制限 | Point | 858 |
|
||||||
|
| #b#d2#d5#d10#BK#P航路ククリ | P航路ククリ | P航路ククリ | LineString | 755 |
|
||||||
|
| #b#d2#d5#d10#BS#P754ククリ | P754ククリ | P754ククリ | LineString | 730 |
|
||||||
|
| #b#d2#d5#d10#BS#P施設・境界線等ククリ | P施設・境界線等ククリ | P施設・境界線等ククリ | LineString | 521 |
|
||||||
|
| #b#d2#d5#d10#L4#P投錨注意障害物ククリ | P投錨注意障害物ククリ | P投錨注意障害物ククリ | MultiLineString | 278 |
|
||||||
|
| #b#d2#d5#d10#BB#P錨泊地等ククリ | P錨泊地等ククリ | P錨泊地等ククリ | LineString | 240 |
|
||||||
|
| #b#d2#d5#d10#at#BB#P錨泊地等 | P錨泊地等 | p錨泊地等 | Point | 221 |
|
||||||
|
| #b#d2#d5#d10#at#BB#S720 | p錨泊地等 | p錨泊地等 | Point | 221 |
|
||||||
|
| #b#d2#d5#d10#P危険界ククリ | P危険界ククリ | P危険界ククリ | MultiLineString | 219 |
|
||||||
|
| #b#d2#d5#d10#L4#P航行危険障害物ククリ | P航行危険障害物ククリ | P航行危険障害物ククリ | LineString | 191 |
|
||||||
|
| #b#d2#d5#d10#BK#P航路ククリ | P航路ククリ | P航路ククリ | MultiLineString | 109 |
|
||||||
|
| #b#d2#d5#d10#BS#P誘導線ククリ | P誘導線ククリ | P誘導線ククリ | LineString | 101 |
|
||||||
|
| #b#d2#d5#d10#BB#P錨泊地等ククリ | P錨泊地等ククリ | P錨泊地等ククリ | MultiLineString | 57 |
|
||||||
|
| #b#d2#d5#d10#BS#P施設・境界線等ククリ | P施設・境界線等ククリ | P施設・境界線等ククリ | MultiLineString | 45 |
|
||||||
|
| #b#d2#d5#d10#BS#P754ククリ | P754ククリ | P754ククリ | MultiLineString | 41 |
|
||||||
|
| #b#d2#d5#d10#at#P4#P航行危険障害物 | P航行危険障害物 | p航行危険障害物 | Point | 24 |
|
||||||
|
| #b#d2#d5#d10#at#S4#S航行危険障害物 | p航行危険障害物 | p航行危険障害物 | Point | 24 |
|
||||||
|
| #b#d2#d5#d10#BB#P721ククリ | P721ククリ | P721ククリ | LineString | 24 |
|
||||||
|
| #b#d2#d5#d10#at#BK#S760 | p航路境界等 | p航路境界等 | Point | 18 |
|
||||||
|
| #b#d2#d5#d10#BS#P730ククリ | P730ククリ | P730ククリ | LineString | 18 |
|
||||||
|
| #b#d2#d5#d10#BS#P730ククリ | P730ククリ | P730ククリ | MultiLineString | 9 |
|
||||||
|
| #b#d2#d5#d10#L4#P航行危険障害物ククリ | P航行危険障害物ククリ | P航行危険障害物ククリ | MultiLineString | 7 |
|
||||||
|
| #b#d2#d5#d10#BB#P721ククリ | P721ククリ | P721ククリ | MultiLineString | 3 |
|
||||||
|
|
||||||
|
## Tile Density Top 100
|
||||||
|
|
||||||
|
| z | x | y | feature_count |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 7 | 110 | 51 | 32300 |
|
||||||
|
| 6 | 55 | 25 | 25153 |
|
||||||
|
| 7 | 111 | 51 | 21202 |
|
||||||
|
| 5 | 27 | 12 | 20867 |
|
||||||
|
| 7 | 111 | 50 | 16595 |
|
||||||
|
| 8 | 222 | 102 | 16494 |
|
||||||
|
| 11 | 1750 | 869 | 14203 |
|
||||||
|
| 8 | 220 | 103 | 13548 |
|
||||||
|
| 11 | 1794 | 814 | 13256 |
|
||||||
|
| 5 | 28 | 12 | 13157 |
|
||||||
|
| 11 | 1794 | 813 | 13068 |
|
||||||
|
| 11 | 1793 | 813 | 12948 |
|
||||||
|
| 11 | 1759 | 815 | 12137 |
|
||||||
|
| 11 | 1777 | 815 | 11981 |
|
||||||
|
| 11 | 1760 | 813 | 11881 |
|
||||||
|
| 11 | 1736 | 878 | 11198 |
|
||||||
|
| 11 | 1802 | 810 | 10984 |
|
||||||
|
| 7 | 109 | 54 | 10901 |
|
||||||
|
| 7 | 112 | 51 | 10530 |
|
||||||
|
| 11 | 1815 | 790 | 10515 |
|
||||||
|
| 11 | 1785 | 815 | 10512 |
|
||||||
|
| 7 | 112 | 50 | 10308 |
|
||||||
|
| 8 | 220 | 102 | 10217 |
|
||||||
|
| 6 | 56 | 25 | 10198 |
|
||||||
|
| 8 | 218 | 108 | 9803 |
|
||||||
|
| 11 | 1781 | 815 | 9684 |
|
||||||
|
| 7 | 109 | 51 | 9655 |
|
||||||
|
| 11 | 1792 | 813 | 9357 |
|
||||||
|
| 11 | 1845 | 752 | 9303 |
|
||||||
|
| 9 | 444 | 204 | 8858 |
|
||||||
|
| 11 | 1761 | 823 | 8657 |
|
||||||
|
| 11 | 1780 | 817 | 8365 |
|
||||||
|
| 11 | 1829 | 783 | 8304 |
|
||||||
|
| 11 | 1818 | 809 | 8031 |
|
||||||
|
| 11 | 1802 | 816 | 8004 |
|
||||||
|
| 11 | 1751 | 868 | 7873 |
|
||||||
|
| 7 | 113 | 50 | 7820 |
|
||||||
|
| 8 | 223 | 101 | 7614 |
|
||||||
|
| 11 | 1786 | 815 | 7600 |
|
||||||
|
| 7 | 114 | 48 | 7588 |
|
||||||
|
| 7 | 110 | 52 | 7577 |
|
||||||
|
| 11 | 1831 | 780 | 7476 |
|
||||||
|
| 7 | 109 | 53 | 7472 |
|
||||||
|
| 11 | 1768 | 818 | 7452 |
|
||||||
|
| 8 | 228 | 97 | 7437 |
|
||||||
|
| 11 | 1761 | 824 | 7433 |
|
||||||
|
| 11 | 1729 | 881 | 7388 |
|
||||||
|
| 9 | 440 | 205 | 7366 |
|
||||||
|
| 11 | 1796 | 821 | 7328 |
|
||||||
|
| 11 | 1824 | 761 | 7293 |
|
||||||
|
| 11 | 1763 | 830 | 7277 |
|
||||||
|
| 5 | 27 | 13 | 7181 |
|
||||||
|
| 11 | 1781 | 816 | 7147 |
|
||||||
|
| 8 | 224 | 102 | 7108 |
|
||||||
|
| 8 | 219 | 103 | 7023 |
|
||||||
|
| 8 | 221 | 102 | 7018 |
|
||||||
|
| 11 | 1826 | 787 | 6960 |
|
||||||
|
| 11 | 1783 | 821 | 6912 |
|
||||||
|
| 12 | 3659 | 1567 | 6889 |
|
||||||
|
| 11 | 1777 | 823 | 6873 |
|
||||||
|
| 11 | 1801 | 811 | 6862 |
|
||||||
|
| 11 | 1777 | 816 | 6848 |
|
||||||
|
| 11 | 1791 | 813 | 6773 |
|
||||||
|
| 11 | 1818 | 808 | 6717 |
|
||||||
|
| 9 | 437 | 217 | 6661 |
|
||||||
|
| 12 | 3520 | 1626 | 6639 |
|
||||||
|
| 11 | 1830 | 782 | 6622 |
|
||||||
|
| 11 | 1790 | 812 | 6591 |
|
||||||
|
| 11 | 1784 | 814 | 6566 |
|
||||||
|
| 11 | 1757 | 826 | 6563 |
|
||||||
|
| 11 | 1778 | 816 | 6550 |
|
||||||
|
| 11 | 1803 | 798 | 6488 |
|
||||||
|
| 11 | 1781 | 807 | 6424 |
|
||||||
|
| 12 | 3587 | 1626 | 6414 |
|
||||||
|
| 11 | 1782 | 822 | 6394 |
|
||||||
|
| 11 | 1786 | 814 | 6371 |
|
||||||
|
| 11 | 1819 | 806 | 6289 |
|
||||||
|
| 11 | 1829 | 755 | 6237 |
|
||||||
|
| 11 | 1780 | 816 | 6231 |
|
||||||
|
| 11 | 1800 | 816 | 6212 |
|
||||||
|
| 11 | 1798 | 803 | 6184 |
|
||||||
|
| 12 | 3586 | 1626 | 6156 |
|
||||||
|
| 9 | 439 | 206 | 6111 |
|
||||||
|
| 8 | 219 | 107 | 6084 |
|
||||||
|
| 11 | 1778 | 818 | 6074 |
|
||||||
|
| 11 | 1757 | 825 | 6063 |
|
||||||
|
| 11 | 1804 | 799 | 6057 |
|
||||||
|
| 11 | 1750 | 868 | 5961 |
|
||||||
|
| 12 | 3500 | 1738 | 5955 |
|
||||||
|
| 7 | 114 | 49 | 5942 |
|
||||||
|
| 11 | 1797 | 820 | 5918 |
|
||||||
|
| 12 | 3589 | 1628 | 5917 |
|
||||||
|
| 12 | 3555 | 1631 | 5852 |
|
||||||
|
| 9 | 457 | 195 | 5849 |
|
||||||
|
| 11 | 1824 | 769 | 5796 |
|
||||||
|
| 12 | 3665 | 1727 | 5740 |
|
||||||
|
| 11 | 1793 | 815 | 5727 |
|
||||||
|
| 12 | 3649 | 1523 | 5715 |
|
||||||
|
| 12 | 3588 | 1626 | 5697 |
|
||||||
|
| 11 | 1832 | 863 | 5684 |
|
||||||
|
|
||||||
|
## Spatial Sanity Checks
|
||||||
|
|
||||||
|
- anomaly_navigation_geom rows: 0
|
||||||
|
- anomaly_reef_geom rows: 13
|
||||||
434
navsea_detection_pipeline.py
Normal file
434
navsea_detection_pipeline.py
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
|
||||||
|
port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
|
||||||
|
user: str = os.getenv("NAVSEA_DB_USER", "root")
|
||||||
|
password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
|
||||||
|
database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
|
||||||
|
unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
|
||||||
|
|
||||||
|
|
||||||
|
FAMILY_DEFAULT_CAPABILITIES = {
|
||||||
|
"hazard": {"collision"},
|
||||||
|
"navigation_aid": {"navigation_mark"},
|
||||||
|
"bathymetry": {"depth_reference"},
|
||||||
|
"seabed": {"bottom_reference"},
|
||||||
|
"fishery": {"entangle"},
|
||||||
|
"route": {"route_reference"},
|
||||||
|
"boundary": {"boundary_reference"},
|
||||||
|
"anchorage": {"anchorage_reference"},
|
||||||
|
"restricted_area": {"boundary_reference"},
|
||||||
|
"infrastructure": {"structure_reference"},
|
||||||
|
"utility": {"utility_hazard"},
|
||||||
|
"landmark": {"landmark_reference"},
|
||||||
|
"place": {"place_reference"},
|
||||||
|
"water_area": {"water_reference"},
|
||||||
|
"monitoring": {"traffic_monitoring"},
|
||||||
|
"overlay_support": {"render_support"},
|
||||||
|
"unknown": {"review_required"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DetectionPipeline:
|
||||||
|
def __init__(self, config: DbConfig) -> None:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
kwargs = {
|
||||||
|
"host": self.config.host,
|
||||||
|
"port": self.config.port,
|
||||||
|
"user": self.config.user,
|
||||||
|
"password": self.config.password,
|
||||||
|
"database": self.config.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
}
|
||||||
|
if self.config.unix_socket and self.config.host in {"localhost", "127.0.0.1"}:
|
||||||
|
kwargs["unix_socket"] = self.config.unix_socket
|
||||||
|
return pymysql.connect(**kwargs)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
object_types = self.fetch_object_types(cur)
|
||||||
|
taxonomy_rows = [self.classify_object_type(name) for name in object_types]
|
||||||
|
capability_rows = self.build_capabilities(taxonomy_rows)
|
||||||
|
self.create_taxonomy_table(cur, taxonomy_rows)
|
||||||
|
self.create_capabilities_table(cur, capability_rows)
|
||||||
|
self.create_detection_view(cur)
|
||||||
|
self.create_capability_views(cur)
|
||||||
|
self.ensure_query_indexes(cur)
|
||||||
|
conn.commit()
|
||||||
|
print(
|
||||||
|
"Built navsea_object_taxonomy, navsea_object_capabilities, "
|
||||||
|
"navsea_detection_objects, capability views and supporting query indexes"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fetch_object_types(cur) -> list[str]:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT canonical_object_type
|
||||||
|
FROM canonical_object_rules
|
||||||
|
ORDER BY canonical_object_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
return [row[0] for row in cur.fetchall()]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_depth_band(name: str) -> bool:
|
||||||
|
compact = name.replace(" ", "")
|
||||||
|
return compact.endswith("m") or "m以深" in compact
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def classify_object_type(name: str) -> tuple[str, str, str, str]:
|
||||||
|
if name.startswith(("P", "L", "p")) and (
|
||||||
|
name.endswith("ククリ")
|
||||||
|
or name in {
|
||||||
|
"P穴",
|
||||||
|
"P陸域",
|
||||||
|
"P危険界ククリ",
|
||||||
|
"P投錨注意障害物ククリ",
|
||||||
|
"P施設・境界線等ククリ",
|
||||||
|
"P航路ククリ",
|
||||||
|
"P錨泊地等ククリ",
|
||||||
|
"P754ククリ",
|
||||||
|
"P721ククリ",
|
||||||
|
"P730ククリ",
|
||||||
|
"L海底地形",
|
||||||
|
"p地名",
|
||||||
|
"p地名陸",
|
||||||
|
"p高さ制限",
|
||||||
|
}
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
name,
|
||||||
|
"overlay_support",
|
||||||
|
"render_support",
|
||||||
|
"保留原始 source_layer 的叠加支持对象,不作为独立语义实体替换原始渲染层。",
|
||||||
|
)
|
||||||
|
|
||||||
|
if DetectionPipeline.is_depth_band(name):
|
||||||
|
return (name, "bathymetry", "depth_zone", "水深分带对象,用于浅滩、深度范围和搁浅风险分析。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("等深線",)):
|
||||||
|
return (name, "bathymetry", "depth_contour", "等深线对象,用于水深变化与航线安全分析。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("底質", "海底地形", "サンドウェーブ", "海底火山")):
|
||||||
|
return (name, "seabed", "seabed_feature", "海底形态或底质对象,用于海底环境识别。")
|
||||||
|
|
||||||
|
if any(
|
||||||
|
token in name
|
||||||
|
for token in (
|
||||||
|
"険悪物",
|
||||||
|
"障害物",
|
||||||
|
"危険物",
|
||||||
|
"危険全沈没船",
|
||||||
|
"沈船",
|
||||||
|
"全沈没船",
|
||||||
|
"沈木",
|
||||||
|
"暗岩",
|
||||||
|
"洗岩",
|
||||||
|
"干出岩",
|
||||||
|
"水上岩",
|
||||||
|
"孤立危険物",
|
||||||
|
"魚礁",
|
||||||
|
"浅所危険界",
|
||||||
|
"撤去跡",
|
||||||
|
"掃海済み",
|
||||||
|
"サンゴ礁",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return (name, "hazard", "obstacle", "对航行存在碰撞或搁浅风险的危险物对象。")
|
||||||
|
|
||||||
|
if any(
|
||||||
|
token in name
|
||||||
|
for token in (
|
||||||
|
"灯",
|
||||||
|
"灯台",
|
||||||
|
"灯標",
|
||||||
|
"灯浮標",
|
||||||
|
"浮標",
|
||||||
|
"立標",
|
||||||
|
"導灯",
|
||||||
|
"指向灯",
|
||||||
|
"V-AIS",
|
||||||
|
"管制信号所",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return (name, "navigation_aid", "navigation_mark", "航标、灯标或导助航对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("航路", "進路矢印", "分離通航方式", "誘導線", "指導線")):
|
||||||
|
return (name, "route", "route_reference", "航路、导向线或通航组织对象。")
|
||||||
|
|
||||||
|
if any(
|
||||||
|
token in name
|
||||||
|
for token in (
|
||||||
|
"境界",
|
||||||
|
"危険界",
|
||||||
|
"境界線",
|
||||||
|
"制限区域",
|
||||||
|
"専用用途海域",
|
||||||
|
"航泊禁止区域",
|
||||||
|
"錨泊禁止区域",
|
||||||
|
"航空機進入区域",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return (name, "boundary", "boundary_control", "边界、限制区或规则控制对象。")
|
||||||
|
|
||||||
|
if "錨泊" in name or "錨地" in name:
|
||||||
|
return (name, "anchorage", "anchorage", "锚地或锚泊控制对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("漁", "養殖場", "漁網", "海草")):
|
||||||
|
return (name, "fishery", "fishery_area", "渔业、养殖或缠绕风险相关对象。")
|
||||||
|
|
||||||
|
if any(
|
||||||
|
token in name
|
||||||
|
for token in (
|
||||||
|
"防波堤",
|
||||||
|
"潜提",
|
||||||
|
"桟橋",
|
||||||
|
"海上バース",
|
||||||
|
"ドルフィン",
|
||||||
|
"ポンツーン",
|
||||||
|
"橋",
|
||||||
|
"道路",
|
||||||
|
"ビル",
|
||||||
|
"タンク",
|
||||||
|
"煙突",
|
||||||
|
"塔",
|
||||||
|
"やぐら",
|
||||||
|
"ケーソン",
|
||||||
|
"土砂捨て場",
|
||||||
|
"石油開発台",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return (name, "infrastructure", "structure", "港口、岸线或陆上海工构造物对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("海底線", "輸送管", "送電線", "架空線", "放水口", "取水口")):
|
||||||
|
return (name, "utility", "utility_line", "海底线缆、管道或公用设施对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("河川域", "湖沼域", "陸上水域", "干潮帯", "未測海域", "平水境界")):
|
||||||
|
return (name, "water_area", "water_area", "水域、潮滩或未测区域对象。")
|
||||||
|
|
||||||
|
if any(
|
||||||
|
token in name
|
||||||
|
for token in (
|
||||||
|
"その他 (記念碑等)",
|
||||||
|
"陸上顕著物",
|
||||||
|
"地名",
|
||||||
|
"山頂",
|
||||||
|
"港湾",
|
||||||
|
"漁港",
|
||||||
|
"マリーナ",
|
||||||
|
"フィッシャリーナ",
|
||||||
|
"海の駅",
|
||||||
|
"漁業協同組合",
|
||||||
|
"海事関係署",
|
||||||
|
"税関",
|
||||||
|
"パイロットステーション",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return (name, "place", "place_label", "地名、显著地物或港区服务设施类参考对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("廃棄物捨て場", "貯木場")):
|
||||||
|
return (name, "infrastructure", "structure", "作业、堆置或弃置区域类设施对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("無線局", "レーダー局")):
|
||||||
|
return (name, "monitoring", "monitoring_station", "通信、雷达或监测设施对象。")
|
||||||
|
|
||||||
|
if any(token in name for token in ("急潮", "波紋", "激潮", "渦流", "水源方向")):
|
||||||
|
return (name, "hazard", "current_hazard", "流态、水流或潮流风险相关对象。")
|
||||||
|
|
||||||
|
return (name, "unknown", "review_required", "尚未细化归类的对象,需要人工复核。")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_capabilities(
|
||||||
|
taxonomy_rows: list[tuple[str, str, str, str]]
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
capability_rows: set[tuple[str, str]] = set()
|
||||||
|
|
||||||
|
for canonical_object_type, object_family, detection_class, _ in taxonomy_rows:
|
||||||
|
capabilities = set(FAMILY_DEFAULT_CAPABILITIES.get(object_family, {"review_required"}))
|
||||||
|
|
||||||
|
if detection_class in {"obstacle"}:
|
||||||
|
capabilities.add("collision")
|
||||||
|
if detection_class in {"depth_zone", "depth_contour"}:
|
||||||
|
capabilities.add("grounding")
|
||||||
|
if "魚礁" in canonical_object_type or "浅所" in canonical_object_type or "干潮帯" in canonical_object_type:
|
||||||
|
capabilities.add("grounding")
|
||||||
|
if any(token in canonical_object_type for token in ("漁", "養殖場", "漁網", "海草")):
|
||||||
|
capabilities.add("entangle")
|
||||||
|
if any(token in canonical_object_type for token in ("防波堤",)):
|
||||||
|
capabilities.add("wave_barrier")
|
||||||
|
if any(token in canonical_object_type for token in ("架空線", "送電線", "高さ制限")):
|
||||||
|
capabilities.add("overhead_clearance")
|
||||||
|
if any(token in canonical_object_type for token in ("航路", "誘導線", "進路矢印", "分離通航方式")):
|
||||||
|
capabilities.add("route_reference")
|
||||||
|
if any(token in canonical_object_type for token in ("錨泊", "錨地")):
|
||||||
|
capabilities.add("anchorage_reference")
|
||||||
|
if any(token in canonical_object_type for token in ("境界", "区域", "危険界")):
|
||||||
|
capabilities.add("boundary_reference")
|
||||||
|
if any(token in canonical_object_type for token in ("海底線", "輸送管")):
|
||||||
|
capabilities.add("snag_risk")
|
||||||
|
if any(token in canonical_object_type for token in ("灯", "標", "浮標", "導灯", "V-AIS", "管制信号所")):
|
||||||
|
capabilities.add("navigation_mark")
|
||||||
|
if object_family in {"place", "infrastructure", "monitoring"}:
|
||||||
|
capabilities.add("landmark_reference")
|
||||||
|
|
||||||
|
for capability in sorted(capabilities):
|
||||||
|
capability_rows.add((canonical_object_type, capability))
|
||||||
|
|
||||||
|
return sorted(capability_rows)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_taxonomy_table(cur, rows: list[tuple[str, str, str, str]]) -> None:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS navsea_object_taxonomy")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE navsea_object_taxonomy (
|
||||||
|
canonical_object_type VARCHAR(191) NOT NULL,
|
||||||
|
object_family VARCHAR(64) NOT NULL,
|
||||||
|
detection_class VARCHAR(64) NOT NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
PRIMARY KEY (canonical_object_type),
|
||||||
|
KEY idx_family_class (object_family, detection_class)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_object_taxonomy (
|
||||||
|
canonical_object_type,
|
||||||
|
object_family,
|
||||||
|
detection_class,
|
||||||
|
description
|
||||||
|
)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_capabilities_table(cur, rows: list[tuple[str, str]]) -> None:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS navsea_object_capabilities")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE navsea_object_capabilities (
|
||||||
|
canonical_object_type VARCHAR(191) NOT NULL,
|
||||||
|
capability VARCHAR(64) NOT NULL,
|
||||||
|
PRIMARY KEY (canonical_object_type, capability),
|
||||||
|
KEY idx_capability (capability, canonical_object_type)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_object_capabilities (
|
||||||
|
canonical_object_type,
|
||||||
|
capability
|
||||||
|
)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
""",
|
||||||
|
rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_detection_view(cur) -> None:
|
||||||
|
cur.execute("DROP VIEW IF EXISTS navsea_detection_objects")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE VIEW navsea_detection_objects AS
|
||||||
|
SELECT
|
||||||
|
r.feature_id,
|
||||||
|
CAST(NULL AS CHAR(1)) AS geometry,
|
||||||
|
r.z,
|
||||||
|
r.x,
|
||||||
|
r.y,
|
||||||
|
r.geom_type,
|
||||||
|
r.source_layer,
|
||||||
|
r.class_name,
|
||||||
|
r.shape_name,
|
||||||
|
r.layer_name,
|
||||||
|
t.canonical_object_type,
|
||||||
|
t.object_family,
|
||||||
|
t.detection_class,
|
||||||
|
c.capability,
|
||||||
|
r.detection_key
|
||||||
|
FROM pbf_relayer_candidates r
|
||||||
|
JOIN navsea_object_taxonomy t
|
||||||
|
ON r.canonical_object_type = t.canonical_object_type
|
||||||
|
LEFT JOIN navsea_object_capabilities c
|
||||||
|
ON r.canonical_object_type = c.canonical_object_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_capability_views(cur) -> None:
|
||||||
|
views = {
|
||||||
|
"navsea_collision_objects": "collision",
|
||||||
|
"navsea_grounding_objects": "grounding",
|
||||||
|
"navsea_entangle_objects": "entangle",
|
||||||
|
"navsea_navigation_mark_objects": "navigation_mark",
|
||||||
|
"navsea_route_reference_objects": "route_reference",
|
||||||
|
"navsea_boundary_reference_objects": "boundary_reference",
|
||||||
|
}
|
||||||
|
for view_name, capability in views.items():
|
||||||
|
cur.execute(f"DROP VIEW IF EXISTS {view_name}")
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
CREATE VIEW {view_name} AS
|
||||||
|
SELECT *
|
||||||
|
FROM navsea_detection_objects
|
||||||
|
WHERE capability = %s
|
||||||
|
""",
|
||||||
|
(capability,),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_index_if_missing(cur, table: str, index_name: str, ddl: str) -> None:
|
||||||
|
cur.execute(f"SHOW INDEX FROM {table} WHERE Key_name=%s", (index_name,))
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
cur.execute(ddl)
|
||||||
|
|
||||||
|
def ensure_query_indexes(self, cur) -> None:
|
||||||
|
self.add_index_if_missing(
|
||||||
|
cur,
|
||||||
|
"pbf_relayer_candidates",
|
||||||
|
"idx_navsea_tile",
|
||||||
|
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_tile (z, x, y)",
|
||||||
|
)
|
||||||
|
self.add_index_if_missing(
|
||||||
|
cur,
|
||||||
|
"pbf_relayer_candidates",
|
||||||
|
"idx_navsea_object",
|
||||||
|
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_object (canonical_object_type(100), geom_type)",
|
||||||
|
)
|
||||||
|
self.add_index_if_missing(
|
||||||
|
cur,
|
||||||
|
"pbf_relayer_candidates",
|
||||||
|
"idx_navsea_detection_key",
|
||||||
|
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_detection_key (detection_key(64))",
|
||||||
|
)
|
||||||
|
self.add_index_if_missing(
|
||||||
|
cur,
|
||||||
|
"pbf_relayer_candidates",
|
||||||
|
"idx_navsea_source_layer",
|
||||||
|
"ALTER TABLE pbf_relayer_candidates ADD KEY idx_navsea_source_layer (source_layer(100), geom_type)",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
DetectionPipeline(DbConfig()).run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
292
navsea_geojson_stage_loader.py
Normal file
292
navsea_geojson_stage_loader.py
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
|
||||||
|
TILE_PATTERN = re.compile(r"^(?P<z>\d+)_(?P<x>\d+)_(?P<y>\d+)\.json$")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
|
||||||
|
port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
|
||||||
|
user: str = os.getenv("NAVSEA_DB_USER", "root")
|
||||||
|
password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
|
||||||
|
database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
|
||||||
|
unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
|
||||||
|
|
||||||
|
|
||||||
|
class GeoJsonStageLoader:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: DbConfig,
|
||||||
|
geojson_dir: Path,
|
||||||
|
stage_table: str = "feature_geometry_stage",
|
||||||
|
) -> None:
|
||||||
|
self.config = config
|
||||||
|
self.geojson_dir = geojson_dir
|
||||||
|
self.stage_table = stage_table
|
||||||
|
|
||||||
|
def connect(self, *, streaming: bool = False, local_infile: bool = False):
|
||||||
|
kwargs = {
|
||||||
|
"host": self.config.host,
|
||||||
|
"port": self.config.port,
|
||||||
|
"user": self.config.user,
|
||||||
|
"password": self.config.password,
|
||||||
|
"database": self.config.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
"local_infile": local_infile,
|
||||||
|
"cursorclass": pymysql.cursors.SSCursor if streaming else pymysql.cursors.Cursor,
|
||||||
|
}
|
||||||
|
if self.config.unix_socket and self.config.host in {"localhost", "127.0.0.1"}:
|
||||||
|
kwargs["unix_socket"] = self.config.unix_socket
|
||||||
|
return pymysql.connect(**kwargs)
|
||||||
|
|
||||||
|
def load(self) -> None:
|
||||||
|
tile_files = self.list_tile_files()
|
||||||
|
if not tile_files:
|
||||||
|
raise RuntimeError(f"no geojson files found in {self.geojson_dir}")
|
||||||
|
|
||||||
|
lookup = {tile: path for tile, path in tile_files}
|
||||||
|
ordered_tiles = [tile for tile, _ in tile_files]
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
mode="w",
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="",
|
||||||
|
suffix=".tsv",
|
||||||
|
delete=False,
|
||||||
|
dir="/tmp",
|
||||||
|
) as temp_file:
|
||||||
|
temp_path = Path(temp_file.name)
|
||||||
|
self.build_lookup_table()
|
||||||
|
staged_rows = self.stream_and_stage(lookup, ordered_tiles, temp_file)
|
||||||
|
|
||||||
|
with self.connect(local_infile=True) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
self.create_stage_table(cur)
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
LOAD DATA LOCAL INFILE %s
|
||||||
|
INTO TABLE {self.stage_table}
|
||||||
|
CHARACTER SET utf8mb4
|
||||||
|
FIELDS TERMINATED BY '\t'
|
||||||
|
LINES TERMINATED BY '\n'
|
||||||
|
(feature_id, geometry_wkt)
|
||||||
|
""",
|
||||||
|
(str(temp_path),),
|
||||||
|
)
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {self.stage_table}")
|
||||||
|
loaded_rows = cur.fetchone()[0]
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
temp_path.unlink(missing_ok=True)
|
||||||
|
print(f"Loaded {loaded_rows} rows into {self.stage_table} from {len(tile_files)} geojson tiles")
|
||||||
|
if loaded_rows != staged_rows:
|
||||||
|
raise RuntimeError(f"stage row mismatch: staged={staged_rows}, loaded={loaded_rows}")
|
||||||
|
|
||||||
|
def list_tile_files(self) -> list[tuple[tuple[int, int, int], Path]]:
|
||||||
|
rows = []
|
||||||
|
for path in self.geojson_dir.glob("*.json"):
|
||||||
|
match = TILE_PATTERN.match(path.name)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
rows.append(
|
||||||
|
(
|
||||||
|
(int(match.group("z")), int(match.group("x")), int(match.group("y"))),
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rows.sort()
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def stream_and_stage(self, lookup: dict[tuple[int, int, int], Path], ordered_tiles: list[tuple[int, int, int]], temp_file) -> int:
|
||||||
|
staged_rows = 0
|
||||||
|
tile_index = 0
|
||||||
|
current_tile = None
|
||||||
|
current_rows: list[tuple[int, str, str, str]] = []
|
||||||
|
|
||||||
|
with self.connect(streaming=True) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
feature_id,
|
||||||
|
fid,
|
||||||
|
vt_layer,
|
||||||
|
geom_type
|
||||||
|
FROM feature_geojson_lookup FORCE INDEX (idx_tile_order)
|
||||||
|
ORDER BY z, x, y, feature_id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
for z, x, y, feature_id, fid, vt_layer, geom_type in cur:
|
||||||
|
tile = (int(z), int(x), int(y))
|
||||||
|
if current_tile is None:
|
||||||
|
current_tile = tile
|
||||||
|
if tile != current_tile:
|
||||||
|
staged_rows += self.flush_tile(
|
||||||
|
current_tile,
|
||||||
|
current_rows,
|
||||||
|
lookup,
|
||||||
|
temp_file,
|
||||||
|
)
|
||||||
|
tile_index += 1
|
||||||
|
if tile_index % 5000 == 0:
|
||||||
|
print(f"processed {tile_index}/{len(ordered_tiles)} tiles, staged {staged_rows} geometries")
|
||||||
|
current_tile = tile
|
||||||
|
current_rows = []
|
||||||
|
|
||||||
|
current_rows.append((int(feature_id), str(fid), str(vt_layer), str(geom_type)))
|
||||||
|
|
||||||
|
if current_tile is not None:
|
||||||
|
staged_rows += self.flush_tile(current_tile, current_rows, lookup, temp_file)
|
||||||
|
tile_index += 1
|
||||||
|
|
||||||
|
if tile_index != len(ordered_tiles):
|
||||||
|
raise RuntimeError(f"tile count mismatch: db_stream={tile_index}, geojson={len(ordered_tiles)}")
|
||||||
|
return staged_rows
|
||||||
|
|
||||||
|
def build_lookup_table(self) -> None:
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS feature_geojson_lookup")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE feature_geojson_lookup AS
|
||||||
|
SELECT
|
||||||
|
f.z,
|
||||||
|
f.x,
|
||||||
|
f.y,
|
||||||
|
f.id AS feature_id,
|
||||||
|
pfid.v AS fid,
|
||||||
|
pvt.v AS vt_layer,
|
||||||
|
f.geom_type
|
||||||
|
FROM features f FORCE INDEX (idx_tile_order)
|
||||||
|
LEFT JOIN properties pfid
|
||||||
|
ON pfid.feature_id = f.id AND pfid.k = 'fid'
|
||||||
|
LEFT JOIN properties pvt
|
||||||
|
ON pvt.feature_id = f.id AND pvt.k = 'vt_layer'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE feature_geojson_lookup ADD PRIMARY KEY (feature_id)")
|
||||||
|
cur.execute("ALTER TABLE feature_geojson_lookup ADD KEY idx_tile_order (z, x, y, feature_id)")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def flush_tile(
|
||||||
|
self,
|
||||||
|
tile: tuple[int, int, int],
|
||||||
|
db_rows: list[tuple[int, str, str, str]],
|
||||||
|
lookup: dict[tuple[int, int, int], Path],
|
||||||
|
temp_file,
|
||||||
|
) -> int:
|
||||||
|
path = lookup.get(tile)
|
||||||
|
if path is None:
|
||||||
|
raise RuntimeError(f"missing geojson for tile {tile}")
|
||||||
|
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
features = data.get("features", [])
|
||||||
|
|
||||||
|
geo_groups: dict[tuple[str, str, str], list[str]] = defaultdict(list)
|
||||||
|
for feature in features:
|
||||||
|
props = feature.get("properties", {})
|
||||||
|
fid = props.get("fid")
|
||||||
|
vt_layer = props.get("vt_layer")
|
||||||
|
geometry = feature.get("geometry")
|
||||||
|
if fid is None or vt_layer is None or geometry is None:
|
||||||
|
continue
|
||||||
|
key = (str(fid), str(vt_layer), str(geometry.get("type")))
|
||||||
|
geo_groups[key].append(self.geometry_to_wkt(geometry))
|
||||||
|
|
||||||
|
db_groups: dict[tuple[str, str, str], list[int]] = defaultdict(list)
|
||||||
|
for feature_id, fid, vt_layer, geom_type in db_rows:
|
||||||
|
key = (fid, vt_layer, geom_type)
|
||||||
|
db_groups[key].append(feature_id)
|
||||||
|
|
||||||
|
if set(geo_groups) != set(db_groups):
|
||||||
|
missing_in_db = list(sorted(set(geo_groups) - set(db_groups)))[:10]
|
||||||
|
missing_in_geo = list(sorted(set(db_groups) - set(geo_groups)))[:10]
|
||||||
|
raise RuntimeError(
|
||||||
|
f"tile key mismatch for {path.name}: "
|
||||||
|
f"missing_in_db={missing_in_db}, missing_in_geo={missing_in_geo}"
|
||||||
|
)
|
||||||
|
|
||||||
|
inserted = 0
|
||||||
|
for key in sorted(geo_groups):
|
||||||
|
geo_wkts = geo_groups[key]
|
||||||
|
db_feature_ids = db_groups[key]
|
||||||
|
if len(geo_wkts) != len(db_feature_ids):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"tile multiplicity mismatch for {path.name}, key={key}: "
|
||||||
|
f"geojson={len(geo_wkts)}, db={len(db_feature_ids)}"
|
||||||
|
)
|
||||||
|
for feature_id, geometry_wkt in zip(db_feature_ids, geo_wkts):
|
||||||
|
temp_file.write(f"{feature_id}\t{geometry_wkt}\n")
|
||||||
|
inserted += 1
|
||||||
|
|
||||||
|
return inserted
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_stage_table(cur) -> None:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS feature_geometry_stage")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE feature_geometry_stage (
|
||||||
|
feature_id BIGINT NOT NULL,
|
||||||
|
geometry_wkt LONGTEXT NOT NULL,
|
||||||
|
PRIMARY KEY (feature_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def geometry_to_wkt(geometry: dict) -> str:
|
||||||
|
geom_type = geometry["type"]
|
||||||
|
coordinates = geometry["coordinates"]
|
||||||
|
|
||||||
|
def fmt_pair(pair):
|
||||||
|
return f"{pair[0]} {pair[1]}"
|
||||||
|
|
||||||
|
def fmt_ring(ring):
|
||||||
|
return "(" + ", ".join(fmt_pair(pair) for pair in ring) + ")"
|
||||||
|
|
||||||
|
if geom_type == "Point":
|
||||||
|
return f"POINT ({fmt_pair(coordinates)})"
|
||||||
|
if geom_type == "MultiPoint":
|
||||||
|
return "MULTIPOINT (" + ", ".join(f"({fmt_pair(pair)})" for pair in coordinates) + ")"
|
||||||
|
if geom_type == "LineString":
|
||||||
|
return "LINESTRING (" + ", ".join(fmt_pair(pair) for pair in coordinates) + ")"
|
||||||
|
if geom_type == "MultiLineString":
|
||||||
|
return "MULTILINESTRING (" + ", ".join("(" + ", ".join(fmt_pair(pair) for pair in line) + ")" for line in coordinates) + ")"
|
||||||
|
if geom_type == "Polygon":
|
||||||
|
return "POLYGON (" + ", ".join(fmt_ring(ring) for ring in coordinates) + ")"
|
||||||
|
if geom_type == "MultiPolygon":
|
||||||
|
polygons = []
|
||||||
|
for polygon in coordinates:
|
||||||
|
polygons.append("(" + ", ".join(fmt_ring(ring) for ring in polygon) + ")")
|
||||||
|
return "MULTIPOLYGON (" + ", ".join(polygons) + ")"
|
||||||
|
raise RuntimeError(f"unsupported geometry type: {geom_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
loader = GeoJsonStageLoader(
|
||||||
|
DbConfig(),
|
||||||
|
Path("/home/wwwroot/newpec/exported_auto/geojson"),
|
||||||
|
)
|
||||||
|
loader.load()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
174
navsea_geometry_enable.py
Normal file
174
navsea_geometry_enable.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
|
||||||
|
port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
|
||||||
|
user: str = os.getenv("NAVSEA_DB_USER", "root")
|
||||||
|
password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
|
||||||
|
database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
|
||||||
|
unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
|
||||||
|
source_table: str = os.getenv("NAVSEA_GEOMETRY_SOURCE_TABLE", "feature_geometry_stage")
|
||||||
|
|
||||||
|
|
||||||
|
class GeometryEnablement:
|
||||||
|
def __init__(self, config: DbConfig) -> None:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
kwargs = {
|
||||||
|
"host": self.config.host,
|
||||||
|
"port": self.config.port,
|
||||||
|
"user": self.config.user,
|
||||||
|
"password": self.config.password,
|
||||||
|
"database": self.config.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
}
|
||||||
|
if self.config.unix_socket and self.config.host in {"localhost", "127.0.0.1"}:
|
||||||
|
kwargs["unix_socket"] = self.config.unix_socket
|
||||||
|
return pymysql.connect(**kwargs)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
source_mode = self.detect_source_mode(cur)
|
||||||
|
self.create_geometry_table(cur, source_mode)
|
||||||
|
self.create_spatial_view(cur)
|
||||||
|
conn.commit()
|
||||||
|
print("Built navsea_feature_geometry and navsea_detection_objects_spatial")
|
||||||
|
|
||||||
|
def detect_source_mode(self, cur) -> str:
|
||||||
|
cur.execute("SHOW TABLES LIKE %s", (self.config.source_table,))
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"geometry staging table missing: "
|
||||||
|
f"{self.config.source_table}. "
|
||||||
|
"Expected one of: "
|
||||||
|
"(feature_id, geometry_wkt), "
|
||||||
|
"(feature_id, geometry_wkb), "
|
||||||
|
"(feature_id, lon, lat), "
|
||||||
|
"or (feature_id, geometry GEOMETRY)."
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT COLUMN_NAME, DATA_TYPE
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s
|
||||||
|
""",
|
||||||
|
(self.config.database, self.config.source_table),
|
||||||
|
)
|
||||||
|
columns = {name: data_type for name, data_type in cur.fetchall()}
|
||||||
|
|
||||||
|
if "feature_id" not in columns:
|
||||||
|
raise RuntimeError(f"{self.config.source_table} missing required column: feature_id")
|
||||||
|
|
||||||
|
if "geometry" in columns and columns["geometry"] == "geometry":
|
||||||
|
return "geometry"
|
||||||
|
if "geometry_wkt" in columns:
|
||||||
|
return "geometry_wkt"
|
||||||
|
if "geometry_wkb" in columns:
|
||||||
|
return "geometry_wkb"
|
||||||
|
if {"lon", "lat"}.issubset(columns):
|
||||||
|
return "lonlat"
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{self.config.source_table} does not expose a supported geometry source. "
|
||||||
|
"Supported layouts: geometry GEOMETRY, geometry_wkt, geometry_wkb, or lon/lat."
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_geometry_table(self, cur, source_mode: str) -> None:
|
||||||
|
cur.execute("DROP TABLE IF EXISTS navsea_feature_geometry")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE navsea_feature_geometry (
|
||||||
|
feature_id BIGINT NOT NULL,
|
||||||
|
geometry GEOMETRY NOT NULL,
|
||||||
|
geometry_source VARCHAR(32) NOT NULL,
|
||||||
|
PRIMARY KEY (feature_id),
|
||||||
|
SPATIAL KEY idx_geometry (geometry)
|
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
source_table = self.config.source_table
|
||||||
|
if source_mode == "geometry":
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
|
||||||
|
SELECT feature_id, geometry, 'geometry'
|
||||||
|
FROM {source_table}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif source_mode == "geometry_wkt":
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
|
||||||
|
SELECT feature_id, GeomFromText(geometry_wkt), 'geometry_wkt'
|
||||||
|
FROM {source_table}
|
||||||
|
WHERE geometry_wkt IS NOT NULL AND geometry_wkt <> ''
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif source_mode == "geometry_wkb":
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
|
||||||
|
SELECT feature_id, GeomFromWKB(geometry_wkb), 'geometry_wkb'
|
||||||
|
FROM {source_table}
|
||||||
|
WHERE geometry_wkb IS NOT NULL
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif source_mode == "lonlat":
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
INSERT INTO navsea_feature_geometry (feature_id, geometry, geometry_source)
|
||||||
|
SELECT feature_id, Point(lon, lat), 'lonlat'
|
||||||
|
FROM {source_table}
|
||||||
|
WHERE lon IS NOT NULL AND lat IS NOT NULL
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"unsupported source mode: {source_mode}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_spatial_view(cur) -> None:
|
||||||
|
cur.execute("DROP VIEW IF EXISTS navsea_detection_objects_spatial")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE VIEW navsea_detection_objects_spatial AS
|
||||||
|
SELECT
|
||||||
|
d.feature_id,
|
||||||
|
g.geometry,
|
||||||
|
d.z,
|
||||||
|
d.x,
|
||||||
|
d.y,
|
||||||
|
d.geom_type,
|
||||||
|
d.source_layer,
|
||||||
|
d.class_name,
|
||||||
|
d.shape_name,
|
||||||
|
d.layer_name,
|
||||||
|
d.canonical_object_type,
|
||||||
|
d.object_family,
|
||||||
|
d.detection_class,
|
||||||
|
d.capability,
|
||||||
|
d.detection_key
|
||||||
|
FROM navsea_detection_objects d
|
||||||
|
JOIN navsea_feature_geometry g
|
||||||
|
ON d.feature_id = g.feature_id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
GeometryEnablement(DbConfig()).run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
237
navsea_mapping_registry.py
Normal file
237
navsea_mapping_registry.py
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FieldValueRule:
|
||||||
|
field_name_jp: str
|
||||||
|
legacy_value: str
|
||||||
|
standardized_field: str
|
||||||
|
standardized_value: str
|
||||||
|
geom_scope: tuple[str, ...]
|
||||||
|
source_layer_scope: tuple[str, ...]
|
||||||
|
canonical_object_scope: tuple[str, ...]
|
||||||
|
|
||||||
|
def matches(self, context: dict[str, str]) -> bool:
|
||||||
|
if self.geom_scope and context.get("geom_type", "") not in self.geom_scope:
|
||||||
|
return False
|
||||||
|
if self.source_layer_scope and context.get("source_layer", "") not in self.source_layer_scope:
|
||||||
|
return False
|
||||||
|
if self.canonical_object_scope and context.get("canonical_object_type", "") not in self.canonical_object_scope:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RenderRule:
|
||||||
|
rule_id: str
|
||||||
|
priority: int
|
||||||
|
match_expr: dict[str, Any]
|
||||||
|
output: dict[str, Any]
|
||||||
|
|
||||||
|
def matches(self, context: dict[str, Any]) -> bool:
|
||||||
|
return evaluate_match_expr(self.match_expr, context)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_csv_scope(value: str | None) -> tuple[str, ...]:
|
||||||
|
if not value:
|
||||||
|
return ()
|
||||||
|
return tuple(part.strip() for part in str(value).split(",") if part.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_match_expr(match_expr: dict[str, Any], context: dict[str, Any]) -> bool:
|
||||||
|
clauses = match_expr.get("all") or []
|
||||||
|
for clause in clauses:
|
||||||
|
field = clause.get("field")
|
||||||
|
op = clause.get("op")
|
||||||
|
expected = clause.get("value")
|
||||||
|
actual = context.get(str(field))
|
||||||
|
actual_text = "" if actual is None else str(actual)
|
||||||
|
|
||||||
|
if op == "eq":
|
||||||
|
if actual_text != str(expected):
|
||||||
|
return False
|
||||||
|
elif op == "neq":
|
||||||
|
if actual_text == str(expected):
|
||||||
|
return False
|
||||||
|
elif op == "in":
|
||||||
|
if actual_text not in {str(item) for item in (expected or [])}:
|
||||||
|
return False
|
||||||
|
elif op == "contains":
|
||||||
|
if str(expected) not in actual_text:
|
||||||
|
return False
|
||||||
|
elif op == "contains_any":
|
||||||
|
values = [str(item) for item in (expected or [])]
|
||||||
|
if not any(item in actual_text for item in values):
|
||||||
|
return False
|
||||||
|
elif op == "is_null":
|
||||||
|
if actual not in (None, ""):
|
||||||
|
return False
|
||||||
|
elif op == "not_null":
|
||||||
|
if actual in (None, ""):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class NavSeaMappingRegistry:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bundle_id: str,
|
||||||
|
bundle_version: str,
|
||||||
|
source_layer_rules: dict[str, dict[str, str]],
|
||||||
|
field_value_rules: dict[tuple[str, str], list[FieldValueRule]],
|
||||||
|
render_rules: list[RenderRule],
|
||||||
|
) -> None:
|
||||||
|
self.bundle_id = bundle_id
|
||||||
|
self.bundle_version = bundle_version
|
||||||
|
self.source_layer_rules = source_layer_rules
|
||||||
|
self.field_value_rules = field_value_rules
|
||||||
|
self.render_rules = render_rules
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(
|
||||||
|
cls,
|
||||||
|
conn: pymysql.Connection,
|
||||||
|
bundle_id: str | None = None,
|
||||||
|
) -> "NavSeaMappingRegistry":
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
if bundle_id:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT bundle_id, bundle_version
|
||||||
|
FROM navsea_rule_bundle
|
||||||
|
WHERE bundle_id = %s
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(bundle_id,),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT bundle_id, bundle_version
|
||||||
|
FROM navsea_rule_bundle
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
bundle = cur.fetchone()
|
||||||
|
if not bundle:
|
||||||
|
raise RuntimeError("navsea mapping registry is empty")
|
||||||
|
|
||||||
|
resolved_bundle_id = str(bundle["bundle_id"])
|
||||||
|
resolved_bundle_version = str(bundle["bundle_version"])
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT source_layer_jp, source_layer_std
|
||||||
|
FROM navsea_source_layer_rules
|
||||||
|
WHERE bundle_id = %s AND bundle_version = %s
|
||||||
|
""",
|
||||||
|
(resolved_bundle_id, resolved_bundle_version),
|
||||||
|
)
|
||||||
|
source_layer_rules = {
|
||||||
|
str(row["source_layer_jp"]): {
|
||||||
|
"source_layer_std": str(row["source_layer_std"]),
|
||||||
|
"source_layer_rule_id": f"SLR:{row['source_layer_jp']}",
|
||||||
|
}
|
||||||
|
for row in cur.fetchall()
|
||||||
|
}
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
field_name_jp,
|
||||||
|
legacy_value,
|
||||||
|
standardized_field,
|
||||||
|
standardized_value,
|
||||||
|
geom_scope,
|
||||||
|
source_layer_scope,
|
||||||
|
canonical_object_scope
|
||||||
|
FROM navsea_field_value_rules
|
||||||
|
WHERE bundle_id = %s AND bundle_version = %s
|
||||||
|
ORDER BY rule_priority ASC, rule_id ASC, rule_revision ASC
|
||||||
|
""",
|
||||||
|
(resolved_bundle_id, resolved_bundle_version),
|
||||||
|
)
|
||||||
|
field_value_rules: dict[tuple[str, str], list[FieldValueRule]] = {}
|
||||||
|
for row in cur.fetchall():
|
||||||
|
key = (str(row["field_name_jp"]), str(row["legacy_value"]))
|
||||||
|
field_value_rules.setdefault(key, []).append(
|
||||||
|
FieldValueRule(
|
||||||
|
field_name_jp=str(row["field_name_jp"]),
|
||||||
|
legacy_value=str(row["legacy_value"]),
|
||||||
|
standardized_field=str(row["standardized_field"]),
|
||||||
|
standardized_value=str(row["standardized_value"]),
|
||||||
|
geom_scope=normalize_csv_scope(row.get("geom_scope")),
|
||||||
|
source_layer_scope=normalize_csv_scope(row.get("source_layer_scope")),
|
||||||
|
canonical_object_scope=normalize_csv_scope(row.get("canonical_object_scope")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT rule_id, priority, match_expr_json, output_json
|
||||||
|
FROM navsea_render_rules
|
||||||
|
WHERE bundle_id = %s AND bundle_version = %s AND enabled = 1
|
||||||
|
ORDER BY priority ASC, rule_id ASC, rule_revision ASC
|
||||||
|
""",
|
||||||
|
(resolved_bundle_id, resolved_bundle_version),
|
||||||
|
)
|
||||||
|
render_rules = []
|
||||||
|
for row in cur.fetchall():
|
||||||
|
render_rules.append(
|
||||||
|
RenderRule(
|
||||||
|
rule_id=str(row["rule_id"]),
|
||||||
|
priority=int(row["priority"]),
|
||||||
|
match_expr=json.loads(row["match_expr_json"]),
|
||||||
|
output=json.loads(row["output_json"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
bundle_id=resolved_bundle_id,
|
||||||
|
bundle_version=resolved_bundle_version,
|
||||||
|
source_layer_rules=source_layer_rules,
|
||||||
|
field_value_rules=field_value_rules,
|
||||||
|
render_rules=render_rules,
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve_source_layer(self, source_layer_jp: str) -> tuple[str, str]:
|
||||||
|
rule = self.source_layer_rules.get(source_layer_jp)
|
||||||
|
if not rule:
|
||||||
|
return source_layer_jp, f"SLR:{source_layer_jp}:identity"
|
||||||
|
return rule["source_layer_std"], rule["source_layer_rule_id"]
|
||||||
|
|
||||||
|
def standardize_field_value(
|
||||||
|
self,
|
||||||
|
field_name_jp: str,
|
||||||
|
legacy_value: object,
|
||||||
|
standardized_field: str,
|
||||||
|
*,
|
||||||
|
context: dict[str, str] | None = None,
|
||||||
|
) -> str | None:
|
||||||
|
if legacy_value in (None, ""):
|
||||||
|
return None
|
||||||
|
key = (field_name_jp, str(legacy_value))
|
||||||
|
rules = self.field_value_rules.get(key, [])
|
||||||
|
match_context = context or {}
|
||||||
|
for rule in rules:
|
||||||
|
if rule.standardized_field != standardized_field:
|
||||||
|
continue
|
||||||
|
if rule.matches(match_context):
|
||||||
|
return rule.standardized_value
|
||||||
|
return None
|
||||||
|
|
||||||
|
def resolve_render_rule(self, context: dict[str, Any]) -> tuple[dict[str, Any], str | None]:
|
||||||
|
for rule in self.render_rules:
|
||||||
|
if rule.matches(context):
|
||||||
|
return dict(rule.output), rule.rule_id
|
||||||
|
return {}, None
|
||||||
524
navsea_mapping_registry_sync.py
Normal file
524
navsea_mapping_registry_sync.py
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
MAPPINGS_DIR = ROOT / "tasks" / "pbf" / "mappings"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = "localhost"
|
||||||
|
port: int = 3306
|
||||||
|
user: str = "root"
|
||||||
|
password: str = "2chi9ks2"
|
||||||
|
database: str = "pbf_analysis"
|
||||||
|
unix_socket: str | None = "/tmp/mysql.sock"
|
||||||
|
|
||||||
|
|
||||||
|
DDL_STATEMENTS = [
|
||||||
|
"DROP VIEW IF EXISTS navsea_new_to_legacy_mapping_vw",
|
||||||
|
"DROP VIEW IF EXISTS navsea_legacy_to_new_mapping_vw",
|
||||||
|
"DROP VIEW IF EXISTS navsea_mapping_registry_vw",
|
||||||
|
"DROP TABLE IF EXISTS navsea_render_rules",
|
||||||
|
"DROP TABLE IF EXISTS navsea_taxonomy_rules",
|
||||||
|
"DROP TABLE IF EXISTS navsea_field_value_rules",
|
||||||
|
"DROP TABLE IF EXISTS navsea_field_name_rules",
|
||||||
|
"DROP TABLE IF EXISTS navsea_source_layer_rules",
|
||||||
|
"DROP TABLE IF EXISTS navsea_rule_bundle",
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_rule_bundle (
|
||||||
|
bundle_id VARCHAR(64) NOT NULL,
|
||||||
|
bundle_version VARCHAR(32) NOT NULL,
|
||||||
|
status VARCHAR(20) NOT NULL,
|
||||||
|
effective_date DATE DEFAULT NULL,
|
||||||
|
taxonomy_ruleset VARCHAR(64) DEFAULT NULL,
|
||||||
|
render_ruleset VARCHAR(64) DEFAULT NULL,
|
||||||
|
source_layer_ruleset VARCHAR(64) DEFAULT NULL,
|
||||||
|
field_name_ruleset VARCHAR(64) DEFAULT NULL,
|
||||||
|
field_value_ruleset VARCHAR(64) DEFAULT NULL,
|
||||||
|
source_of_truth VARCHAR(32) NOT NULL DEFAULT 'yaml+sql',
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (bundle_id, bundle_version)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_source_layer_rules (
|
||||||
|
bundle_id VARCHAR(64) NOT NULL,
|
||||||
|
bundle_version VARCHAR(32) NOT NULL,
|
||||||
|
source_layer_jp VARCHAR(100) NOT NULL,
|
||||||
|
source_layer_std VARCHAR(100) NOT NULL,
|
||||||
|
semantic_granularity VARCHAR(50) DEFAULT NULL,
|
||||||
|
canonical_family VARCHAR(100) DEFAULT NULL,
|
||||||
|
render_strategy VARCHAR(50) DEFAULT NULL,
|
||||||
|
preserve_source_layer TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
style_bound TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
style_types_json LONGTEXT,
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (bundle_id, bundle_version, source_layer_jp),
|
||||||
|
KEY idx_source_layer_std (source_layer_std),
|
||||||
|
KEY idx_source_layer_family (canonical_family)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_field_name_rules (
|
||||||
|
bundle_id VARCHAR(64) NOT NULL,
|
||||||
|
bundle_version VARCHAR(32) NOT NULL,
|
||||||
|
field_name_jp VARCHAR(100) NOT NULL,
|
||||||
|
field_name_std VARCHAR(100) NOT NULL,
|
||||||
|
field_group_name VARCHAR(50) NOT NULL,
|
||||||
|
keep_in_delivery TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
keep_in_engineering TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
normalization_class VARCHAR(64) NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (bundle_id, bundle_version, field_name_jp),
|
||||||
|
UNIQUE KEY uniq_field_name_std (bundle_id, bundle_version, field_name_std)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_field_value_rules (
|
||||||
|
bundle_id VARCHAR(64) NOT NULL,
|
||||||
|
bundle_version VARCHAR(32) NOT NULL,
|
||||||
|
rule_id VARCHAR(64) NOT NULL,
|
||||||
|
rule_revision INT NOT NULL,
|
||||||
|
field_name_jp VARCHAR(100) NOT NULL,
|
||||||
|
legacy_value VARCHAR(191) NOT NULL,
|
||||||
|
standardized_field VARCHAR(100) NOT NULL,
|
||||||
|
standardized_value VARCHAR(191) NOT NULL,
|
||||||
|
geom_scope VARCHAR(100) DEFAULT NULL,
|
||||||
|
source_layer_scope VARCHAR(200) DEFAULT NULL,
|
||||||
|
canonical_object_scope VARCHAR(191) DEFAULT NULL,
|
||||||
|
rule_priority INT NOT NULL DEFAULT 100,
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (bundle_id, bundle_version, rule_id, rule_revision),
|
||||||
|
KEY idx_field_value_lookup (field_name_jp, legacy_value),
|
||||||
|
KEY idx_standardized_lookup (standardized_field, standardized_value)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_taxonomy_rules (
|
||||||
|
bundle_id VARCHAR(64) NOT NULL,
|
||||||
|
bundle_version VARCHAR(32) NOT NULL,
|
||||||
|
rule_id VARCHAR(64) NOT NULL,
|
||||||
|
rule_revision INT NOT NULL,
|
||||||
|
priority INT NOT NULL,
|
||||||
|
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
match_scope VARCHAR(32) NOT NULL,
|
||||||
|
match_expr_json LONGTEXT NOT NULL,
|
||||||
|
output_json LONGTEXT NOT NULL,
|
||||||
|
canonical_family VARCHAR(100) NOT NULL,
|
||||||
|
canonical_object_type VARCHAR(191) NOT NULL,
|
||||||
|
detection_key_template VARCHAR(191) DEFAULT NULL,
|
||||||
|
rule_reason TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (bundle_id, bundle_version, rule_id, rule_revision),
|
||||||
|
KEY idx_taxonomy_object (canonical_object_type),
|
||||||
|
KEY idx_taxonomy_family (canonical_family)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_render_rules (
|
||||||
|
bundle_id VARCHAR(64) NOT NULL,
|
||||||
|
bundle_version VARCHAR(32) NOT NULL,
|
||||||
|
rule_id VARCHAR(64) NOT NULL,
|
||||||
|
rule_revision INT NOT NULL,
|
||||||
|
priority INT NOT NULL,
|
||||||
|
enabled TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
match_expr_json LONGTEXT NOT NULL,
|
||||||
|
output_json LONGTEXT NOT NULL,
|
||||||
|
chart_render_type VARCHAR(32) DEFAULT NULL,
|
||||||
|
chart_symbol_family VARCHAR(64) DEFAULT NULL,
|
||||||
|
chart_symbol_code VARCHAR(64) DEFAULT NULL,
|
||||||
|
chart_line_style VARCHAR(64) DEFAULT NULL,
|
||||||
|
chart_fill_style VARCHAR(64) DEFAULT NULL,
|
||||||
|
chart_text_style VARCHAR(64) DEFAULT NULL,
|
||||||
|
chart_priority INT DEFAULT NULL,
|
||||||
|
chart_visibility_min INT DEFAULT NULL,
|
||||||
|
chart_visibility_max INT DEFAULT NULL,
|
||||||
|
area_usage_class VARCHAR(64) DEFAULT NULL,
|
||||||
|
hazard_class VARCHAR(64) DEFAULT NULL,
|
||||||
|
hazard_severity VARCHAR(64) DEFAULT NULL,
|
||||||
|
rule_reason TEXT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (bundle_id, bundle_version, rule_id, rule_revision),
|
||||||
|
KEY idx_render_symbol (chart_symbol_code),
|
||||||
|
KEY idx_render_fill (chart_fill_style),
|
||||||
|
KEY idx_render_text (chart_text_style)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE VIEW navsea_mapping_registry_vw AS
|
||||||
|
SELECT
|
||||||
|
'source_layer' AS mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
source_layer_jp AS legacy_key,
|
||||||
|
source_layer_jp AS legacy_value,
|
||||||
|
'source_layer_std' AS new_key,
|
||||||
|
source_layer_std AS new_value,
|
||||||
|
NULL AS rule_id,
|
||||||
|
notes
|
||||||
|
FROM navsea_source_layer_rules
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'field_name' AS mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
field_name_jp AS legacy_key,
|
||||||
|
field_name_jp AS legacy_value,
|
||||||
|
'field_name_std' AS new_key,
|
||||||
|
field_name_std AS new_value,
|
||||||
|
NULL AS rule_id,
|
||||||
|
notes
|
||||||
|
FROM navsea_field_name_rules
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'field_value' AS mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
field_name_jp AS legacy_key,
|
||||||
|
legacy_value AS legacy_value,
|
||||||
|
standardized_field AS new_key,
|
||||||
|
standardized_value AS new_value,
|
||||||
|
rule_id AS rule_id,
|
||||||
|
notes
|
||||||
|
FROM navsea_field_value_rules
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'taxonomy' AS mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
'match_expr' AS legacy_key,
|
||||||
|
CAST(match_expr_json AS CHAR(4096)) AS legacy_value,
|
||||||
|
'canonical_object_type' AS new_key,
|
||||||
|
canonical_object_type AS new_value,
|
||||||
|
rule_id AS rule_id,
|
||||||
|
rule_reason AS notes
|
||||||
|
FROM navsea_taxonomy_rules
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'render' AS mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
'match_expr' AS legacy_key,
|
||||||
|
CAST(match_expr_json AS CHAR(4096)) AS legacy_value,
|
||||||
|
'render_output' AS new_key,
|
||||||
|
CAST(output_json AS CHAR(4096)) AS new_value,
|
||||||
|
rule_id AS rule_id,
|
||||||
|
rule_reason AS notes
|
||||||
|
FROM navsea_render_rules
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE VIEW navsea_legacy_to_new_mapping_vw AS
|
||||||
|
SELECT
|
||||||
|
mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
legacy_key,
|
||||||
|
legacy_value,
|
||||||
|
new_key,
|
||||||
|
new_value,
|
||||||
|
rule_id,
|
||||||
|
notes
|
||||||
|
FROM navsea_mapping_registry_vw
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE VIEW navsea_new_to_legacy_mapping_vw AS
|
||||||
|
SELECT
|
||||||
|
mapping_type,
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
new_key,
|
||||||
|
new_value,
|
||||||
|
legacy_key,
|
||||||
|
legacy_value,
|
||||||
|
rule_id,
|
||||||
|
notes
|
||||||
|
FROM navsea_mapping_registry_vw
|
||||||
|
""",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml(path: Path) -> dict[str, Any]:
|
||||||
|
with path.open("r", encoding="utf-8") as f:
|
||||||
|
return yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
|
||||||
|
def json_or_none(value: Any) -> str | None:
|
||||||
|
if value in (None, "", [], {}):
|
||||||
|
return None
|
||||||
|
return json.dumps(value, ensure_ascii=False, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
|
def connect(db: DbConfig) -> pymysql.Connection:
|
||||||
|
kwargs: dict[str, Any] = {
|
||||||
|
"host": db.host,
|
||||||
|
"port": db.port,
|
||||||
|
"user": db.user,
|
||||||
|
"password": db.password,
|
||||||
|
"database": db.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
"cursorclass": pymysql.cursors.DictCursor,
|
||||||
|
}
|
||||||
|
if db.unix_socket:
|
||||||
|
kwargs["unix_socket"] = db.unix_socket
|
||||||
|
return pymysql.connect(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def purge_bundle(cur: pymysql.cursors.Cursor, bundle_id: str, bundle_version: str) -> None:
|
||||||
|
tables = (
|
||||||
|
"navsea_source_layer_rules",
|
||||||
|
"navsea_field_name_rules",
|
||||||
|
"navsea_field_value_rules",
|
||||||
|
"navsea_taxonomy_rules",
|
||||||
|
"navsea_render_rules",
|
||||||
|
"navsea_rule_bundle",
|
||||||
|
)
|
||||||
|
for table in tables:
|
||||||
|
cur.execute(
|
||||||
|
f"DELETE FROM {table} WHERE bundle_id=%s AND bundle_version=%s",
|
||||||
|
(bundle_id, bundle_version),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_canonical_layer_rules(cur: pymysql.cursors.Cursor) -> dict[str, dict[str, Any]]:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
source_layer,
|
||||||
|
semantic_granularity,
|
||||||
|
canonical_family,
|
||||||
|
render_strategy,
|
||||||
|
preserve_source_layer,
|
||||||
|
style_bound,
|
||||||
|
style_types,
|
||||||
|
notes
|
||||||
|
FROM canonical_layer_rules
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
return {row["source_layer"]: row for row in cur.fetchall()}
|
||||||
|
|
||||||
|
|
||||||
|
def sync() -> dict[str, int]:
|
||||||
|
bundle = load_yaml(MAPPINGS_DIR / "navsea_rule_bundle_v1.yaml")
|
||||||
|
source_layer_rules = load_yaml(MAPPINGS_DIR / "navsea_source_layer_rules_v1.yaml")
|
||||||
|
field_name_rules = load_yaml(MAPPINGS_DIR / "navsea_field_name_rules_v1.yaml")
|
||||||
|
field_value_rules = load_yaml(MAPPINGS_DIR / "navsea_field_value_rules_v1.yaml")
|
||||||
|
taxonomy_rules = load_yaml(MAPPINGS_DIR / "navsea_taxonomy_rules_v1.yaml")
|
||||||
|
render_rules = load_yaml(MAPPINGS_DIR / "navsea_render_rules_v1.yaml")
|
||||||
|
|
||||||
|
bundle_id = bundle["bundle_id"]
|
||||||
|
bundle_version = bundle["bundle_version"]
|
||||||
|
|
||||||
|
counts = {
|
||||||
|
"source_layer_rules": 0,
|
||||||
|
"field_name_rules": 0,
|
||||||
|
"field_value_rules": 0,
|
||||||
|
"taxonomy_rules": 0,
|
||||||
|
"render_rules": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
with connect(DbConfig()) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for ddl in DDL_STATEMENTS:
|
||||||
|
cur.execute(ddl)
|
||||||
|
|
||||||
|
purge_bundle(cur, bundle_id, bundle_version)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_rule_bundle (
|
||||||
|
bundle_id, bundle_version, status, effective_date,
|
||||||
|
taxonomy_ruleset, render_ruleset, source_layer_ruleset,
|
||||||
|
field_name_ruleset, field_value_ruleset, notes
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
bundle["status"],
|
||||||
|
bundle.get("effective_date"),
|
||||||
|
bundle.get("taxonomy_ruleset"),
|
||||||
|
bundle.get("render_ruleset"),
|
||||||
|
bundle.get("source_layer_ruleset"),
|
||||||
|
bundle.get("field_name_ruleset"),
|
||||||
|
bundle.get("field_value_ruleset"),
|
||||||
|
bundle.get("notes"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
canonical_layers = load_canonical_layer_rules(cur)
|
||||||
|
for rule in source_layer_rules.get("rules", []):
|
||||||
|
layer_meta = canonical_layers.get(rule["source_layer_jp"], {})
|
||||||
|
style_types = layer_meta.get("style_types")
|
||||||
|
style_types_json = None
|
||||||
|
if style_types:
|
||||||
|
style_types_json = json.dumps(
|
||||||
|
[part.strip() for part in str(style_types).split(",") if part.strip()],
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_source_layer_rules (
|
||||||
|
bundle_id, bundle_version, source_layer_jp, source_layer_std,
|
||||||
|
semantic_granularity, canonical_family, render_strategy,
|
||||||
|
preserve_source_layer, style_bound, style_types_json, notes
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
rule["source_layer_jp"],
|
||||||
|
rule["source_layer_std"],
|
||||||
|
layer_meta.get("semantic_granularity"),
|
||||||
|
layer_meta.get("canonical_family"),
|
||||||
|
layer_meta.get("render_strategy"),
|
||||||
|
int(layer_meta.get("preserve_source_layer", 1)),
|
||||||
|
int(layer_meta.get("style_bound", 1)),
|
||||||
|
style_types_json,
|
||||||
|
rule.get("notes") or layer_meta.get("notes"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
counts["source_layer_rules"] += 1
|
||||||
|
|
||||||
|
for rule in field_name_rules.get("rules", []):
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_field_name_rules (
|
||||||
|
bundle_id, bundle_version, field_name_jp, field_name_std, field_group_name,
|
||||||
|
keep_in_delivery, keep_in_engineering, normalization_class, notes
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
rule["field_name_jp"],
|
||||||
|
rule["field_name_std"],
|
||||||
|
rule["field_group"],
|
||||||
|
int(bool(rule["keep_in_delivery"])),
|
||||||
|
int(bool(rule["keep_in_engineering"])),
|
||||||
|
rule["normalization_class"],
|
||||||
|
rule.get("notes"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
counts["field_name_rules"] += 1
|
||||||
|
|
||||||
|
for rule in field_value_rules.get("rules", []):
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_field_value_rules (
|
||||||
|
bundle_id, bundle_version, rule_id, rule_revision, field_name_jp, legacy_value,
|
||||||
|
standardized_field, standardized_value, geom_scope, source_layer_scope,
|
||||||
|
canonical_object_scope, rule_priority, notes
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
rule["rule_id"],
|
||||||
|
int(rule["rule_revision"]),
|
||||||
|
rule["field_name_jp"],
|
||||||
|
str(rule["legacy_value"]),
|
||||||
|
rule["standardized_field"],
|
||||||
|
rule["standardized_value"],
|
||||||
|
rule.get("geom_scope"),
|
||||||
|
rule.get("source_layer_scope"),
|
||||||
|
rule.get("canonical_object_scope"),
|
||||||
|
int(rule.get("rule_priority", 100)),
|
||||||
|
rule.get("notes"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
counts["field_value_rules"] += 1
|
||||||
|
|
||||||
|
for rule in taxonomy_rules.get("rules", []):
|
||||||
|
output = rule["output"]
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_taxonomy_rules (
|
||||||
|
bundle_id, bundle_version, rule_id, rule_revision, priority, enabled,
|
||||||
|
match_scope, match_expr_json, output_json, canonical_family,
|
||||||
|
canonical_object_type, detection_key_template, rule_reason
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
rule["rule_id"],
|
||||||
|
int(rule["rule_revision"]),
|
||||||
|
int(rule["priority"]),
|
||||||
|
int(bool(rule["enabled"])),
|
||||||
|
rule["match_scope"],
|
||||||
|
json_or_none(rule["match_expr"]),
|
||||||
|
json_or_none(output),
|
||||||
|
output["canonical_family"],
|
||||||
|
output["canonical_object_type"],
|
||||||
|
output.get("detection_key_template"),
|
||||||
|
rule.get("reason"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
counts["taxonomy_rules"] += 1
|
||||||
|
|
||||||
|
for rule in render_rules.get("rules", []):
|
||||||
|
output = rule["output"]
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_render_rules (
|
||||||
|
bundle_id, bundle_version, rule_id, rule_revision, priority, enabled,
|
||||||
|
match_expr_json, output_json, chart_render_type, chart_symbol_family,
|
||||||
|
chart_symbol_code, chart_line_style, chart_fill_style, chart_text_style,
|
||||||
|
chart_priority, chart_visibility_min, chart_visibility_max,
|
||||||
|
area_usage_class, hazard_class, hazard_severity, rule_reason
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
bundle_id,
|
||||||
|
bundle_version,
|
||||||
|
rule["rule_id"],
|
||||||
|
int(rule["rule_revision"]),
|
||||||
|
int(rule["priority"]),
|
||||||
|
int(bool(rule["enabled"])),
|
||||||
|
json_or_none(rule["match_expr"]),
|
||||||
|
json_or_none(output),
|
||||||
|
output.get("chart_render_type"),
|
||||||
|
output.get("chart_symbol_family"),
|
||||||
|
output.get("chart_symbol_code"),
|
||||||
|
output.get("chart_line_style"),
|
||||||
|
output.get("chart_fill_style"),
|
||||||
|
output.get("chart_text_style"),
|
||||||
|
output.get("chart_priority"),
|
||||||
|
output.get("chart_visibility_min"),
|
||||||
|
output.get("chart_visibility_max"),
|
||||||
|
output.get("area_usage_class"),
|
||||||
|
output.get("hazard_class"),
|
||||||
|
output.get("hazard_severity"),
|
||||||
|
rule.get("reason"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
counts["render_rules"] += 1
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
result = sync()
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||||
118
navsea_mvt_to_geojson.py
Normal file
118
navsea_mvt_to_geojson.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import mapbox_vector_tile
|
||||||
|
|
||||||
|
|
||||||
|
INPUT_ROOT = Path("/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles")
|
||||||
|
OUTPUT_ROOT = Path("/home/wwwroot/newpec/exported_auto/geojson_linux")
|
||||||
|
|
||||||
|
|
||||||
|
def tile_coord_to_lonlat(z: int, x: int, y: int, tx: float, ty: float, extent: int) -> list[float]:
|
||||||
|
lon = ((x + (tx / extent)) / (2**z)) * 360.0 - 180.0
|
||||||
|
lat = math.degrees(
|
||||||
|
math.atan(
|
||||||
|
math.sinh(
|
||||||
|
math.pi * (1.0 - (2.0 * (y + (ty / extent)) / (2**z)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return [lon, lat]
|
||||||
|
|
||||||
|
|
||||||
|
def convert_geometry(geometry: dict, z: int, x: int, y: int, extent: int) -> dict:
|
||||||
|
geom_type = geometry["type"]
|
||||||
|
coords = geometry["coordinates"]
|
||||||
|
|
||||||
|
if geom_type == "Point":
|
||||||
|
return {"type": "Point", "coordinates": tile_coord_to_lonlat(z, x, y, coords[0], coords[1], extent)}
|
||||||
|
if geom_type == "MultiPoint":
|
||||||
|
return {
|
||||||
|
"type": "MultiPoint",
|
||||||
|
"coordinates": [tile_coord_to_lonlat(z, x, y, px, py, extent) for px, py in coords],
|
||||||
|
}
|
||||||
|
if geom_type == "LineString":
|
||||||
|
return {
|
||||||
|
"type": "LineString",
|
||||||
|
"coordinates": [tile_coord_to_lonlat(z, x, y, px, py, extent) for px, py in coords],
|
||||||
|
}
|
||||||
|
if geom_type == "MultiLineString":
|
||||||
|
return {
|
||||||
|
"type": "MultiLineString",
|
||||||
|
"coordinates": [
|
||||||
|
[tile_coord_to_lonlat(z, x, y, px, py, extent) for px, py in line]
|
||||||
|
for line in coords
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if geom_type == "Polygon":
|
||||||
|
return {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[tile_coord_to_lonlat(z, x, y, px, py, extent) for px, py in ring]
|
||||||
|
for ring in coords
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if geom_type == "MultiPolygon":
|
||||||
|
return {
|
||||||
|
"type": "MultiPolygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[tile_coord_to_lonlat(z, x, y, px, py, extent) for px, py in ring]
|
||||||
|
for ring in polygon
|
||||||
|
]
|
||||||
|
for polygon in coords
|
||||||
|
],
|
||||||
|
}
|
||||||
|
raise RuntimeError(f"unsupported geometry type: {geom_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_tile(path: Path) -> tuple[dict, int]:
|
||||||
|
z = int(path.parent.parent.name)
|
||||||
|
x = int(path.parent.name)
|
||||||
|
y = int(path.stem)
|
||||||
|
|
||||||
|
with path.open("rb") as fh:
|
||||||
|
decoded = mapbox_vector_tile.decode(fh.read())
|
||||||
|
|
||||||
|
features = []
|
||||||
|
for vt_layer, payload in decoded.items():
|
||||||
|
extent = int(payload.get("extent") or 4096)
|
||||||
|
for feature in payload.get("features", []):
|
||||||
|
properties = dict(feature.get("properties") or {})
|
||||||
|
properties["vt_layer"] = vt_layer
|
||||||
|
out_feature = {
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": convert_geometry(feature["geometry"], z, x, y, extent),
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
if feature.get("id") is not None:
|
||||||
|
out_feature["id"] = feature["id"]
|
||||||
|
features.append(out_feature)
|
||||||
|
|
||||||
|
return {"type": "FeatureCollection", "features": features}, len(features)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
|
||||||
|
tile_paths = sorted(INPUT_ROOT.glob("*/*/*.pbf"))
|
||||||
|
total_features = 0
|
||||||
|
|
||||||
|
for index, path in enumerate(tile_paths, start=1):
|
||||||
|
collection, count = convert_tile(path)
|
||||||
|
total_features += count
|
||||||
|
output_path = OUTPUT_ROOT / f"{path.parent.parent.name}_{path.parent.name}_{path.stem}.json"
|
||||||
|
output_path.write_text(
|
||||||
|
json.dumps(collection, ensure_ascii=False, separators=(",", ":")),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
if index % 5000 == 0:
|
||||||
|
print(f"processed {index}/{len(tile_paths)} tiles, wrote {total_features} features")
|
||||||
|
|
||||||
|
print(f"Converted {len(tile_paths)} tiles into {OUTPUT_ROOT} with {total_features} features")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
660
navsea_relayer.py
Normal file
660
navsea_relayer.py
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
import json
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
STYLE_JSON_PATH = ROOT / "src" / "pbf" / "style.json"
|
||||||
|
|
||||||
|
MANUAL_OBJECT_RULES = {
|
||||||
|
"P754ククリ": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved clip/outline layer.",
|
||||||
|
},
|
||||||
|
"P危険界ククリ": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved clip/outline layer.",
|
||||||
|
},
|
||||||
|
"P基本線ククリ": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved clip/outline layer.",
|
||||||
|
},
|
||||||
|
"P投錨注意障害物ククリ": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved clip/outline layer.",
|
||||||
|
},
|
||||||
|
"P施設・境界線等ククリ": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved clip/outline layer.",
|
||||||
|
},
|
||||||
|
"P航路ククリ": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved clip/outline layer.",
|
||||||
|
},
|
||||||
|
"P穴": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Source-preserved area layer.",
|
||||||
|
},
|
||||||
|
"P陸域": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Source-preserved area layer.",
|
||||||
|
},
|
||||||
|
"L海底地形": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Source-preserved bathymetry line layer.",
|
||||||
|
},
|
||||||
|
"p地名": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Source-preserved place label layer.",
|
||||||
|
},
|
||||||
|
"p地名陸": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Source-preserved place label layer.",
|
||||||
|
},
|
||||||
|
"p高さ制限": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Source-preserved point annotation layer.",
|
||||||
|
},
|
||||||
|
"p錨泊地等": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Source-preserved point annotation layer.",
|
||||||
|
},
|
||||||
|
"サンドウェーブ": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Same object appears in multiple source layers but remains a point hazard.",
|
||||||
|
},
|
||||||
|
"海底設置物、放水口、取水口": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Stable point object despite multiple source layers.",
|
||||||
|
},
|
||||||
|
"全沈没船 (危険なし)": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Stable point object despite multiple source layers.",
|
||||||
|
},
|
||||||
|
"測定済みの沈船": {
|
||||||
|
"family": "symbol",
|
||||||
|
"allowed_geom_types": ["Point"],
|
||||||
|
"notes": "Stable point object despite multiple source layers.",
|
||||||
|
},
|
||||||
|
"道路": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Road centerlines legitimately appear in multi-part line form.",
|
||||||
|
},
|
||||||
|
"等深線": {
|
||||||
|
"family": "line",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString"],
|
||||||
|
"notes": "Depth contours legitimately appear in multi-part line form.",
|
||||||
|
},
|
||||||
|
"防波堤": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Breakwaters are modeled as areas, including multipart polygons.",
|
||||||
|
},
|
||||||
|
"障害物": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["Point", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Obstacle objects can be rendered as symbols or area extents.",
|
||||||
|
},
|
||||||
|
"未測海域": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["LineString", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Unsurveyed regions appear as boundaries and areas.",
|
||||||
|
},
|
||||||
|
"浮施設・桟橋": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["LineString", "MultiLineString", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Floating facilities and piers appear as line and area geometry.",
|
||||||
|
},
|
||||||
|
"航路 (法律による航路)": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["LineString", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Legally defined routes appear as lines and areas.",
|
||||||
|
},
|
||||||
|
"険悪物": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["Point", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Hazards may be represented as point symbols or area extents.",
|
||||||
|
},
|
||||||
|
"魚礁": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["Point", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Artificial reefs may be represented as point symbols or area extents.",
|
||||||
|
},
|
||||||
|
"養殖場": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Aquaculture zones are area features.",
|
||||||
|
},
|
||||||
|
"漁網": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Fishing nets are area extents in this dataset.",
|
||||||
|
},
|
||||||
|
"錨泊 (指定)地": {
|
||||||
|
"family": "mixed",
|
||||||
|
"allowed_geom_types": ["Point", "Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Anchorage can appear as symbol or area depending on source layer.",
|
||||||
|
},
|
||||||
|
"ケーソン仮置き場": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Caisson storage areas are polygons.",
|
||||||
|
},
|
||||||
|
"土砂捨て場": {
|
||||||
|
"family": "surface",
|
||||||
|
"allowed_geom_types": ["Polygon", "MultiPolygon"],
|
||||||
|
"notes": "Spoil grounds are polygons.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_GEOMETRIES = {
|
||||||
|
"symbol": ["Point"],
|
||||||
|
"line": ["LineString", "MultiLineString"],
|
||||||
|
"surface": ["Polygon", "MultiPolygon"],
|
||||||
|
"mixed": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
return pymysql.connect(
|
||||||
|
host="localhost",
|
||||||
|
user="root",
|
||||||
|
password="2chi9ks2",
|
||||||
|
database="pbf_analysis",
|
||||||
|
charset="utf8mb4",
|
||||||
|
unix_socket="/tmp/mysql.sock",
|
||||||
|
autocommit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_style_source_layers():
|
||||||
|
style = json.loads(STYLE_JSON_PATH.read_text(encoding="utf-8"))
|
||||||
|
layers = {}
|
||||||
|
for layer in style.get("layers", []):
|
||||||
|
source_layer = layer.get("source-layer")
|
||||||
|
if not source_layer:
|
||||||
|
continue
|
||||||
|
layers.setdefault(source_layer, {"style_rows": 0, "layer_types": set()})
|
||||||
|
layers[source_layer]["style_rows"] += 1
|
||||||
|
if layer.get("type"):
|
||||||
|
layers[source_layer]["layer_types"].add(layer["type"])
|
||||||
|
return layers
|
||||||
|
|
||||||
|
|
||||||
|
def geom_bucket(geom_type: str | None) -> str:
|
||||||
|
if geom_type in {"Point", "MultiPoint"}:
|
||||||
|
return "symbol"
|
||||||
|
if geom_type in {"LineString", "MultiLineString"}:
|
||||||
|
return "line"
|
||||||
|
if geom_type in {"Polygon", "MultiPolygon"}:
|
||||||
|
return "surface"
|
||||||
|
return "mixed"
|
||||||
|
|
||||||
|
|
||||||
|
def infer_family(geom_types: set[str]) -> str:
|
||||||
|
buckets = {geom_bucket(geom_type) for geom_type in geom_types if geom_type}
|
||||||
|
if not buckets:
|
||||||
|
return "mixed"
|
||||||
|
if len(buckets) == 1:
|
||||||
|
return next(iter(buckets))
|
||||||
|
return "mixed"
|
||||||
|
|
||||||
|
|
||||||
|
def choose_allowed_geometries(object_type: str, family: str, observed_geom_types: list[str]) -> tuple[list[str], str]:
|
||||||
|
manual = MANUAL_OBJECT_RULES.get(object_type)
|
||||||
|
if manual:
|
||||||
|
return manual["allowed_geom_types"], "manual_override"
|
||||||
|
defaults = DEFAULT_GEOMETRIES.get(family, [])
|
||||||
|
if defaults:
|
||||||
|
return defaults, "family_default"
|
||||||
|
return observed_geom_types, "observed_fallback"
|
||||||
|
|
||||||
|
|
||||||
|
def build_canonical_layer_rules(cur, style_layers):
|
||||||
|
cur.execute("DROP TABLE IF EXISTS canonical_layer_rules")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE canonical_layer_rules (
|
||||||
|
source_layer VARCHAR(100) NOT NULL,
|
||||||
|
semantic_granularity VARCHAR(50) NOT NULL,
|
||||||
|
canonical_family VARCHAR(100) NOT NULL,
|
||||||
|
render_strategy VARCHAR(50) NOT NULL,
|
||||||
|
preserve_source_layer TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
style_bound TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
style_rows INT NOT NULL DEFAULT 0,
|
||||||
|
style_types VARCHAR(200) NULL,
|
||||||
|
notes TEXT NULL,
|
||||||
|
PRIMARY KEY (source_layer)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
vt_layer,
|
||||||
|
SUM(feature_count) AS feature_count,
|
||||||
|
COUNT(DISTINCT COALESCE(NULLIF(class_name, ''), NULLIF(shape_name, ''), vt_layer)) AS semantic_type_count,
|
||||||
|
SUM(CASE WHEN class_name IS NULL OR class_name='' THEN feature_count ELSE 0 END) AS missing_class_count,
|
||||||
|
SUM(CASE WHEN shape_name IS NULL OR shape_name='' THEN feature_count ELSE 0 END) AS missing_shape_count
|
||||||
|
FROM object_catalog
|
||||||
|
GROUP BY vt_layer
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
inserts = []
|
||||||
|
for vt_layer, feature_count, semantic_type_count, missing_class_count, missing_shape_count in cur.fetchall():
|
||||||
|
style_info = style_layers.get(vt_layer, {"style_rows": 0, "layer_types": set()})
|
||||||
|
|
||||||
|
if vt_layer.startswith("p"):
|
||||||
|
canonical_family = "symbol"
|
||||||
|
elif vt_layer.startswith("L"):
|
||||||
|
canonical_family = "line"
|
||||||
|
elif vt_layer.startswith("P"):
|
||||||
|
canonical_family = "surface"
|
||||||
|
else:
|
||||||
|
canonical_family = "mixed"
|
||||||
|
|
||||||
|
if semantic_type_count >= 5 and (vt_layer.startswith("P") or vt_layer.startswith("L")):
|
||||||
|
semantic_granularity = "container_layer"
|
||||||
|
notes = (
|
||||||
|
f"Layer contains {semantic_type_count} semantic types across {feature_count} features; "
|
||||||
|
"retain source layer for rendering and derive semantic overlays for analysis."
|
||||||
|
)
|
||||||
|
elif missing_class_count == feature_count and missing_shape_count == feature_count:
|
||||||
|
semantic_granularity = "style_or_source_layer"
|
||||||
|
notes = "Layer has no semantic class/shape annotations; source layer remains the stable identity."
|
||||||
|
else:
|
||||||
|
semantic_granularity = "semantic_layer"
|
||||||
|
notes = "Layer is close to a stable semantic object and can be preserved as-is."
|
||||||
|
|
||||||
|
inserts.append(
|
||||||
|
(
|
||||||
|
vt_layer,
|
||||||
|
semantic_granularity,
|
||||||
|
canonical_family,
|
||||||
|
"keep_source_layer",
|
||||||
|
1,
|
||||||
|
1 if style_info["style_rows"] > 0 else 0,
|
||||||
|
style_info["style_rows"],
|
||||||
|
",".join(sorted(style_info["layer_types"])) or None,
|
||||||
|
notes,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO canonical_layer_rules (
|
||||||
|
source_layer,
|
||||||
|
semantic_granularity,
|
||||||
|
canonical_family,
|
||||||
|
render_strategy,
|
||||||
|
preserve_source_layer,
|
||||||
|
style_bound,
|
||||||
|
style_rows,
|
||||||
|
style_types,
|
||||||
|
notes
|
||||||
|
)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
inserts,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_canonical_object_rules(cur):
|
||||||
|
cur.execute("DROP TABLE IF EXISTS canonical_object_rules")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE canonical_object_rules (
|
||||||
|
canonical_object_type VARCHAR(191) NOT NULL,
|
||||||
|
canonical_family VARCHAR(100) NOT NULL,
|
||||||
|
classification_basis VARCHAR(50) NOT NULL,
|
||||||
|
source_layer_scope VARCHAR(50) NOT NULL,
|
||||||
|
source_layer_count INT NOT NULL,
|
||||||
|
geom_type_count INT NOT NULL,
|
||||||
|
object_type_sources VARCHAR(100) NOT NULL,
|
||||||
|
source_layers TEXT NULL,
|
||||||
|
observed_geom_types VARCHAR(200) NULL,
|
||||||
|
allowed_geom_types VARCHAR(200) NULL,
|
||||||
|
preferred_geom_type VARCHAR(20) NULL,
|
||||||
|
geometry_rule_source VARCHAR(50) NOT NULL,
|
||||||
|
notes TEXT NULL,
|
||||||
|
PRIMARY KEY (canonical_object_type)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
COALESCE(NULLIF(class_name, ''), NULLIF(shape_name, ''), vt_layer) AS object_type,
|
||||||
|
GROUP_CONCAT(
|
||||||
|
DISTINCT CASE
|
||||||
|
WHEN class_name IS NOT NULL AND class_name <> '' THEN 'class_name'
|
||||||
|
WHEN shape_name IS NOT NULL AND shape_name <> '' THEN 'shape_name'
|
||||||
|
ELSE 'vt_layer'
|
||||||
|
END
|
||||||
|
ORDER BY 1 SEPARATOR ','
|
||||||
|
) AS object_type_sources,
|
||||||
|
SUM(feature_count) AS feature_count,
|
||||||
|
COUNT(DISTINCT vt_layer) AS source_layer_count,
|
||||||
|
GROUP_CONCAT(DISTINCT vt_layer ORDER BY vt_layer SEPARATOR ',') AS source_layers,
|
||||||
|
COUNT(DISTINCT geom_type) AS geom_type_count,
|
||||||
|
GROUP_CONCAT(DISTINCT geom_type ORDER BY geom_type SEPARATOR ',') AS observed_geom_types
|
||||||
|
FROM object_catalog
|
||||||
|
GROUP BY COALESCE(NULLIF(class_name, ''), NULLIF(shape_name, ''), vt_layer)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
object_rows = cur.fetchall()
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
object_type,
|
||||||
|
geom_type,
|
||||||
|
feature_count
|
||||||
|
FROM object_type_stats
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
geom_counts: dict[str, Counter[str]] = defaultdict(Counter)
|
||||||
|
for object_type, geom_type, feature_count in cur.fetchall():
|
||||||
|
geom_counts[object_type][geom_type] = feature_count
|
||||||
|
|
||||||
|
inserts = []
|
||||||
|
for (
|
||||||
|
object_type,
|
||||||
|
object_type_sources,
|
||||||
|
feature_count,
|
||||||
|
source_layer_count,
|
||||||
|
source_layers,
|
||||||
|
geom_type_count,
|
||||||
|
observed_geom_types,
|
||||||
|
) in object_rows:
|
||||||
|
observed_geom_list = [part for part in (observed_geom_types or "").split(",") if part]
|
||||||
|
observed_geom_set = set(observed_geom_list)
|
||||||
|
manual = MANUAL_OBJECT_RULES.get(object_type)
|
||||||
|
canonical_family = manual["family"] if manual else infer_family(observed_geom_set)
|
||||||
|
classification_basis = (
|
||||||
|
"source_layer_preserved"
|
||||||
|
if object_type_sources == "vt_layer"
|
||||||
|
else "semantic_object"
|
||||||
|
)
|
||||||
|
source_layer_scope = "multi_source" if source_layer_count > 1 else "single_source"
|
||||||
|
allowed_geom_types, geometry_rule_source = choose_allowed_geometries(
|
||||||
|
object_type,
|
||||||
|
canonical_family,
|
||||||
|
observed_geom_list,
|
||||||
|
)
|
||||||
|
preferred_geom_type = None
|
||||||
|
if geom_counts.get(object_type):
|
||||||
|
preferred_geom_type = geom_counts[object_type].most_common(1)[0][0]
|
||||||
|
notes = (
|
||||||
|
manual["notes"]
|
||||||
|
if manual
|
||||||
|
else (
|
||||||
|
"Source-preserved object type derived from vt_layer only."
|
||||||
|
if classification_basis == "source_layer_preserved"
|
||||||
|
else "Semantic object type derived from class_name/shape_name."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
inserts.append(
|
||||||
|
(
|
||||||
|
object_type,
|
||||||
|
canonical_family,
|
||||||
|
classification_basis,
|
||||||
|
source_layer_scope,
|
||||||
|
source_layer_count,
|
||||||
|
geom_type_count,
|
||||||
|
object_type_sources,
|
||||||
|
source_layers,
|
||||||
|
",".join(observed_geom_list) or None,
|
||||||
|
",".join(allowed_geom_types) or None,
|
||||||
|
preferred_geom_type,
|
||||||
|
geometry_rule_source,
|
||||||
|
notes,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO canonical_object_rules (
|
||||||
|
canonical_object_type,
|
||||||
|
canonical_family,
|
||||||
|
classification_basis,
|
||||||
|
source_layer_scope,
|
||||||
|
source_layer_count,
|
||||||
|
geom_type_count,
|
||||||
|
object_type_sources,
|
||||||
|
source_layers,
|
||||||
|
observed_geom_types,
|
||||||
|
allowed_geom_types,
|
||||||
|
preferred_geom_type,
|
||||||
|
geometry_rule_source,
|
||||||
|
notes
|
||||||
|
)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
inserts,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_object_geometry_allowlist(cur):
|
||||||
|
cur.execute("DROP TABLE IF EXISTS object_geometry_allowlist")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE object_geometry_allowlist (
|
||||||
|
canonical_object_type VARCHAR(191) NOT NULL,
|
||||||
|
geometry_type VARCHAR(20) NOT NULL,
|
||||||
|
rule_source VARCHAR(50) NOT NULL,
|
||||||
|
is_allowed TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
notes TEXT NULL,
|
||||||
|
PRIMARY KEY (canonical_object_type, geometry_type)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
canonical_object_type,
|
||||||
|
geometry_rule_source,
|
||||||
|
allowed_geom_types,
|
||||||
|
notes
|
||||||
|
FROM canonical_object_rules
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
inserts = []
|
||||||
|
for canonical_object_type, geometry_rule_source, allowed_geom_types, notes in cur.fetchall():
|
||||||
|
for geometry_type in [part for part in (allowed_geom_types or "").split(",") if part]:
|
||||||
|
inserts.append(
|
||||||
|
(
|
||||||
|
canonical_object_type,
|
||||||
|
geometry_type,
|
||||||
|
geometry_rule_source,
|
||||||
|
1,
|
||||||
|
notes,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if inserts:
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO object_geometry_allowlist (
|
||||||
|
canonical_object_type,
|
||||||
|
geometry_type,
|
||||||
|
rule_source,
|
||||||
|
is_allowed,
|
||||||
|
notes
|
||||||
|
)
|
||||||
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
inserts,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_relayer_outputs(cur):
|
||||||
|
cur.execute("DROP TABLE IF EXISTS pbf_relayer_candidates")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE pbf_relayer_candidates AS
|
||||||
|
SELECT
|
||||||
|
o.feature_id,
|
||||||
|
s.z,
|
||||||
|
s.x,
|
||||||
|
s.y,
|
||||||
|
s.vt_layer AS source_layer,
|
||||||
|
s.geom_type,
|
||||||
|
s.class_name,
|
||||||
|
s.shape_name,
|
||||||
|
s.layer_name,
|
||||||
|
o.object_type_source,
|
||||||
|
o.object_type AS canonical_object_type,
|
||||||
|
r.semantic_granularity,
|
||||||
|
obj.canonical_family,
|
||||||
|
obj.classification_basis,
|
||||||
|
obj.source_layer_scope,
|
||||||
|
r.render_strategy,
|
||||||
|
r.preserve_source_layer,
|
||||||
|
r.style_bound,
|
||||||
|
CASE
|
||||||
|
WHEN obj.classification_basis = 'source_layer_preserved' THEN CONCAT('source:', s.vt_layer)
|
||||||
|
ELSE CONCAT(obj.canonical_family, ':', o.object_type)
|
||||||
|
END AS semantic_key,
|
||||||
|
CASE
|
||||||
|
WHEN obj.classification_basis = 'source_layer_preserved' THEN CONCAT(obj.canonical_family, ':', s.vt_layer)
|
||||||
|
ELSE CONCAT(obj.canonical_family, ':', o.object_type)
|
||||||
|
END AS detection_key,
|
||||||
|
CASE
|
||||||
|
WHEN r.preserve_source_layer = 1 THEN s.vt_layer
|
||||||
|
ELSE o.object_type
|
||||||
|
END AS render_layer
|
||||||
|
FROM feature_semantic s
|
||||||
|
JOIN object_type_candidates o
|
||||||
|
ON s.feature_id = o.feature_id
|
||||||
|
JOIN canonical_layer_rules r
|
||||||
|
ON s.vt_layer = r.source_layer
|
||||||
|
JOIN canonical_object_rules obj
|
||||||
|
ON o.object_type = obj.canonical_object_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute("DROP TABLE IF EXISTS pbf_source_object_stats")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE pbf_source_object_stats AS
|
||||||
|
SELECT
|
||||||
|
source_layer,
|
||||||
|
render_layer,
|
||||||
|
canonical_family,
|
||||||
|
classification_basis,
|
||||||
|
source_layer_scope,
|
||||||
|
semantic_granularity,
|
||||||
|
style_bound,
|
||||||
|
canonical_object_type,
|
||||||
|
geom_type,
|
||||||
|
detection_key,
|
||||||
|
semantic_key,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM pbf_relayer_candidates
|
||||||
|
GROUP BY
|
||||||
|
source_layer,
|
||||||
|
render_layer,
|
||||||
|
canonical_family,
|
||||||
|
classification_basis,
|
||||||
|
source_layer_scope,
|
||||||
|
semantic_granularity,
|
||||||
|
style_bound,
|
||||||
|
canonical_object_type,
|
||||||
|
geom_type,
|
||||||
|
detection_key,
|
||||||
|
semantic_key
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE pbf_source_object_stats ADD KEY idx_source_layer (source_layer(100))")
|
||||||
|
cur.execute("ALTER TABLE pbf_source_object_stats ADD KEY idx_render_layer (render_layer(100))")
|
||||||
|
cur.execute("ALTER TABLE pbf_source_object_stats ADD KEY idx_detection_key (detection_key(50))")
|
||||||
|
|
||||||
|
cur.execute("DROP TABLE IF EXISTS pbf_render_compatibility")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE pbf_render_compatibility AS
|
||||||
|
SELECT
|
||||||
|
s.source_layer,
|
||||||
|
s.render_layer,
|
||||||
|
s.canonical_family,
|
||||||
|
s.semantic_granularity,
|
||||||
|
s.style_bound,
|
||||||
|
SUM(s.feature_count) AS feature_count,
|
||||||
|
COUNT(*) AS canonical_object_types
|
||||||
|
FROM pbf_source_object_stats s
|
||||||
|
GROUP BY
|
||||||
|
s.source_layer,
|
||||||
|
s.render_layer,
|
||||||
|
s.canonical_family,
|
||||||
|
s.semantic_granularity,
|
||||||
|
s.style_bound
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute("DROP TABLE IF EXISTS pbf_detection_catalog")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE pbf_detection_catalog AS
|
||||||
|
SELECT
|
||||||
|
detection_key,
|
||||||
|
canonical_family,
|
||||||
|
canonical_object_type,
|
||||||
|
geom_type,
|
||||||
|
SUM(feature_count) AS feature_count,
|
||||||
|
COUNT(DISTINCT source_layer) AS source_layers
|
||||||
|
FROM pbf_source_object_stats
|
||||||
|
GROUP BY
|
||||||
|
detection_key,
|
||||||
|
canonical_family,
|
||||||
|
canonical_object_type,
|
||||||
|
geom_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_relayer_tables():
|
||||||
|
style_layers = load_style_source_layers()
|
||||||
|
|
||||||
|
with connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
build_canonical_layer_rules(cur, style_layers)
|
||||||
|
build_canonical_object_rules(cur)
|
||||||
|
build_object_geometry_allowlist(cur)
|
||||||
|
build_relayer_outputs(cur)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
refresh_relayer_tables()
|
||||||
|
print(
|
||||||
|
"Built canonical_layer_rules, canonical_object_rules, object_geometry_allowlist, "
|
||||||
|
"pbf_relayer_candidates, pbf_render_compatibility, pbf_detection_catalog"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
997
navsea_render_audit.py
Normal file
997
navsea_render_audit.py
Normal file
@@ -0,0 +1,997 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
NavSea render audit for original vs engineering tiles/styles.
|
||||||
|
|
||||||
|
This script compares one original style/tile set against one engineering style/tile set.
|
||||||
|
|
||||||
|
Audit goal:
|
||||||
|
- use legacy fid as the primary object identity
|
||||||
|
- fall back to geometry + stable legacy properties when fid is missing
|
||||||
|
- evaluate each style layer against decoded tile features
|
||||||
|
- extract per-object render observations (icon/text/line/fill)
|
||||||
|
- compare original and engineering render observations at tile-instance scope
|
||||||
|
- emit Markdown and JSON audit reports for review
|
||||||
|
- persist the latest audit result into MySQL for fid-level trace-back
|
||||||
|
|
||||||
|
Important scope note:
|
||||||
|
- render comparison is performed per tile feature instance
|
||||||
|
- identity is "fid first", but zoom/tile instance is preserved because rendering is zoom-sensitive
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import mapbox_vector_tile
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ORIGINAL_STYLE_PATH = Path("/mnt/sda1/www/newpec/style.patched.local.json")
|
||||||
|
DEFAULT_ENGINEERING_STYLE_PATH = Path("/mnt/sda1/www/newpec/navsea-engineering.json")
|
||||||
|
DEFAULT_ORIGINAL_TILE_ROOT = Path(
|
||||||
|
"/home/wwwroot/newpec/exported_auto/"
|
||||||
|
"tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles"
|
||||||
|
)
|
||||||
|
DEFAULT_ENGINEERING_TILE_ROOT = Path("/home/wwwroot/pbf-engineering-karatsu-10nm")
|
||||||
|
DEFAULT_REPORT_MD_PATH = Path("/root/weather/NavSea_Original_vs_Engineering_Render_Audit_Karatsu_10nm.md")
|
||||||
|
DEFAULT_REPORT_JSON_PATH = Path("/root/weather/NavSea_Original_vs_Engineering_Render_Audit_Karatsu_10nm.json")
|
||||||
|
DEFAULT_AUDIT_NAME = "navsea_original_vs_engineering_karatsu_10nm"
|
||||||
|
DB_BATCH_SIZE = 1000
|
||||||
|
MAX_EXAMPLES = 30
|
||||||
|
|
||||||
|
ORIGINAL_STYLE_PATH = DEFAULT_ORIGINAL_STYLE_PATH
|
||||||
|
ENGINEERING_STYLE_PATH = DEFAULT_ENGINEERING_STYLE_PATH
|
||||||
|
ORIGINAL_TILE_ROOT = DEFAULT_ORIGINAL_TILE_ROOT
|
||||||
|
ENGINEERING_TILE_ROOT = DEFAULT_ENGINEERING_TILE_ROOT
|
||||||
|
REPORT_MD_PATH = DEFAULT_REPORT_MD_PATH
|
||||||
|
REPORT_JSON_PATH = DEFAULT_REPORT_JSON_PATH
|
||||||
|
AUDIT_NAME = DEFAULT_AUDIT_NAME
|
||||||
|
|
||||||
|
DERIVED_PROPERTY_PREFIXES = (
|
||||||
|
"canonical_",
|
||||||
|
"semantic_",
|
||||||
|
"detection_",
|
||||||
|
"render_",
|
||||||
|
"chart_",
|
||||||
|
"light_",
|
||||||
|
"hazard_",
|
||||||
|
"area_",
|
||||||
|
"source_layer_",
|
||||||
|
)
|
||||||
|
DERIVED_PROPERTY_KEYS = {
|
||||||
|
"fid_algo_id",
|
||||||
|
"fid_key_id",
|
||||||
|
"fid_navsea_int",
|
||||||
|
"fid_legacy_raw",
|
||||||
|
"normalization_bundle_id",
|
||||||
|
"source_layer_rule_id",
|
||||||
|
"trace_status",
|
||||||
|
"feature_id",
|
||||||
|
"depth_value_m",
|
||||||
|
"clearance_height_m",
|
||||||
|
"least_depth_m",
|
||||||
|
"bearing_deg",
|
||||||
|
}
|
||||||
|
STABLE_FALLBACK_KEYS = (
|
||||||
|
"分類番号",
|
||||||
|
"形状分類番号",
|
||||||
|
"表示用番号",
|
||||||
|
"灯色",
|
||||||
|
"灯略記",
|
||||||
|
"明弧/分孤",
|
||||||
|
"表示位置",
|
||||||
|
"名称",
|
||||||
|
"名称補助",
|
||||||
|
"日本語地名",
|
||||||
|
"英文字地名",
|
||||||
|
"水深値(m)",
|
||||||
|
"高さ(m)",
|
||||||
|
"高さ/深度(m)",
|
||||||
|
"角度",
|
||||||
|
)
|
||||||
|
EXPRESSION_OPS = {
|
||||||
|
"get",
|
||||||
|
"match",
|
||||||
|
"coalesce",
|
||||||
|
"concat",
|
||||||
|
"number",
|
||||||
|
"literal",
|
||||||
|
"rgba",
|
||||||
|
"case",
|
||||||
|
"has",
|
||||||
|
"any",
|
||||||
|
"all",
|
||||||
|
"==",
|
||||||
|
"!=",
|
||||||
|
">=",
|
||||||
|
"<=",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"/",
|
||||||
|
"*",
|
||||||
|
"interpolate",
|
||||||
|
"step",
|
||||||
|
"zoom",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = "localhost"
|
||||||
|
port: int = 3306
|
||||||
|
user: str = "root"
|
||||||
|
password: str = "2chi9ks2"
|
||||||
|
database: str = "pbf_analysis"
|
||||||
|
unix_socket: str | None = "/tmp/mysql.sock"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AuditFeature:
|
||||||
|
dataset: str
|
||||||
|
tile_z: int
|
||||||
|
tile_x: int
|
||||||
|
tile_y: int
|
||||||
|
layer_name: str
|
||||||
|
geom_type: str
|
||||||
|
object_id: str
|
||||||
|
fid_legacy: str | None
|
||||||
|
properties: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RenderObservation:
|
||||||
|
component_type: str
|
||||||
|
style_layer_id: str
|
||||||
|
signature: str
|
||||||
|
payload: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_json(value: Any) -> str:
|
||||||
|
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
||||||
|
|
||||||
|
|
||||||
|
def hash_text(text: str) -> str:
|
||||||
|
return hashlib.sha1(text.encode("utf-8")).hexdigest()[:16]
|
||||||
|
|
||||||
|
|
||||||
|
def text_or_none(value: Any) -> str | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
text = str(value).strip()
|
||||||
|
return text or None
|
||||||
|
|
||||||
|
|
||||||
|
def numeric_or_text(value: Any) -> float | str | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value, (int, float)):
|
||||||
|
return float(value)
|
||||||
|
text = text_or_none(value)
|
||||||
|
if text is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(text)
|
||||||
|
except ValueError:
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_color(value: Any) -> str | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value
|
||||||
|
if isinstance(value, list):
|
||||||
|
return canonical_json(value)
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def compare_numbers(lhs: Any, rhs: Any, op: str) -> bool:
|
||||||
|
lhs_num = numeric_or_text(lhs)
|
||||||
|
rhs_num = numeric_or_text(rhs)
|
||||||
|
if isinstance(lhs_num, float) and isinstance(rhs_num, float):
|
||||||
|
if op == ">=":
|
||||||
|
return lhs_num >= rhs_num
|
||||||
|
if op == "<=":
|
||||||
|
return lhs_num <= rhs_num
|
||||||
|
if op == ">":
|
||||||
|
return lhs_num > rhs_num
|
||||||
|
if op == "<":
|
||||||
|
return lhs_num < rhs_num
|
||||||
|
lhs_text = "" if lhs is None else str(lhs)
|
||||||
|
rhs_text = "" if rhs is None else str(rhs)
|
||||||
|
if op == ">=":
|
||||||
|
return lhs_text >= rhs_text
|
||||||
|
if op == "<=":
|
||||||
|
return lhs_text <= rhs_text
|
||||||
|
if op == ">":
|
||||||
|
return lhs_text > rhs_text
|
||||||
|
return lhs_text < rhs_text
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_expression(expr: Any, feature: AuditFeature, zoom: int) -> Any:
|
||||||
|
if not isinstance(expr, list):
|
||||||
|
return expr
|
||||||
|
if not expr:
|
||||||
|
return expr
|
||||||
|
head = expr[0]
|
||||||
|
if not isinstance(head, str) or head not in EXPRESSION_OPS:
|
||||||
|
return [evaluate_expression(item, feature, zoom) for item in expr]
|
||||||
|
|
||||||
|
if head == "get":
|
||||||
|
key = expr[1]
|
||||||
|
return feature.properties.get(key)
|
||||||
|
if head == "literal":
|
||||||
|
return expr[1]
|
||||||
|
if head == "coalesce":
|
||||||
|
for item in expr[1:]:
|
||||||
|
value = evaluate_expression(item, feature, zoom)
|
||||||
|
if value not in (None, ""):
|
||||||
|
return value
|
||||||
|
return None
|
||||||
|
if head == "concat":
|
||||||
|
return "".join("" if (value := evaluate_expression(item, feature, zoom)) is None else str(value) for item in expr[1:])
|
||||||
|
if head == "number":
|
||||||
|
value = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
parsed = numeric_or_text(value)
|
||||||
|
if isinstance(parsed, float):
|
||||||
|
return parsed
|
||||||
|
fallback = evaluate_expression(expr[2], feature, zoom) if len(expr) > 2 else None
|
||||||
|
return fallback
|
||||||
|
if head == "rgba":
|
||||||
|
values = [evaluate_expression(item, feature, zoom) for item in expr[1:5]]
|
||||||
|
return f"rgba({values[0]},{values[1]},{values[2]},{values[3]})"
|
||||||
|
if head == "has":
|
||||||
|
key = expr[1]
|
||||||
|
return key in feature.properties and feature.properties.get(key) not in (None, "")
|
||||||
|
if head == "zoom":
|
||||||
|
return zoom
|
||||||
|
if head in {"==", "!="}:
|
||||||
|
lhs = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
rhs = evaluate_expression(expr[2], feature, zoom)
|
||||||
|
result = lhs == rhs
|
||||||
|
return result if head == "==" else not result
|
||||||
|
if head in {">=", "<=", ">", "<"}:
|
||||||
|
lhs = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
rhs = evaluate_expression(expr[2], feature, zoom)
|
||||||
|
return compare_numbers(lhs, rhs, head)
|
||||||
|
if head == "any":
|
||||||
|
return any(bool(evaluate_expression(item, feature, zoom)) for item in expr[1:])
|
||||||
|
if head == "all":
|
||||||
|
return all(bool(evaluate_expression(item, feature, zoom)) for item in expr[1:])
|
||||||
|
if head == "case":
|
||||||
|
clauses = expr[1:]
|
||||||
|
for idx in range(0, len(clauses) - 1, 2):
|
||||||
|
if bool(evaluate_expression(clauses[idx], feature, zoom)):
|
||||||
|
return evaluate_expression(clauses[idx + 1], feature, zoom)
|
||||||
|
return evaluate_expression(clauses[-1], feature, zoom) if clauses else None
|
||||||
|
if head == "match":
|
||||||
|
value = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
arms = expr[2:]
|
||||||
|
fallback = arms[-1] if arms else None
|
||||||
|
for idx in range(0, len(arms) - 1, 2):
|
||||||
|
label = arms[idx]
|
||||||
|
result = arms[idx + 1]
|
||||||
|
if isinstance(label, list):
|
||||||
|
if value in [evaluate_expression(item, feature, zoom) for item in label]:
|
||||||
|
return evaluate_expression(result, feature, zoom)
|
||||||
|
else:
|
||||||
|
if value == evaluate_expression(label, feature, zoom):
|
||||||
|
return evaluate_expression(result, feature, zoom)
|
||||||
|
return evaluate_expression(fallback, feature, zoom)
|
||||||
|
if head == "/":
|
||||||
|
lhs = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
rhs = evaluate_expression(expr[2], feature, zoom)
|
||||||
|
lhs_num = numeric_or_text(lhs)
|
||||||
|
rhs_num = numeric_or_text(rhs)
|
||||||
|
if isinstance(lhs_num, float) and isinstance(rhs_num, float) and rhs_num != 0:
|
||||||
|
return lhs_num / rhs_num
|
||||||
|
return None
|
||||||
|
if head == "*":
|
||||||
|
lhs = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
rhs = evaluate_expression(expr[2], feature, zoom)
|
||||||
|
lhs_num = numeric_or_text(lhs)
|
||||||
|
rhs_num = numeric_or_text(rhs)
|
||||||
|
if isinstance(lhs_num, float) and isinstance(rhs_num, float):
|
||||||
|
return lhs_num * rhs_num
|
||||||
|
return None
|
||||||
|
if head == "step":
|
||||||
|
input_value = evaluate_expression(expr[1], feature, zoom)
|
||||||
|
input_num = numeric_or_text(input_value)
|
||||||
|
if not isinstance(input_num, float):
|
||||||
|
return evaluate_expression(expr[2], feature, zoom)
|
||||||
|
result = evaluate_expression(expr[2], feature, zoom)
|
||||||
|
stops = expr[3:]
|
||||||
|
for idx in range(0, len(stops), 2):
|
||||||
|
stop = evaluate_expression(stops[idx], feature, zoom)
|
||||||
|
stop_num = numeric_or_text(stop)
|
||||||
|
if not isinstance(stop_num, float):
|
||||||
|
continue
|
||||||
|
if idx + 1 >= len(stops):
|
||||||
|
break
|
||||||
|
if input_num >= stop_num:
|
||||||
|
result = evaluate_expression(stops[idx + 1], feature, zoom)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
if head == "interpolate":
|
||||||
|
input_value = evaluate_expression(expr[2], feature, zoom)
|
||||||
|
input_num = numeric_or_text(input_value)
|
||||||
|
if not isinstance(input_num, float):
|
||||||
|
return None
|
||||||
|
stops = expr[3:]
|
||||||
|
prev_stop = None
|
||||||
|
prev_value = None
|
||||||
|
for idx in range(0, len(stops), 2):
|
||||||
|
stop_num = numeric_or_text(evaluate_expression(stops[idx], feature, zoom))
|
||||||
|
stop_value = evaluate_expression(stops[idx + 1], feature, zoom) if idx + 1 < len(stops) else None
|
||||||
|
if not isinstance(stop_num, float):
|
||||||
|
continue
|
||||||
|
if input_num == stop_num:
|
||||||
|
return stop_value
|
||||||
|
if input_num < stop_num:
|
||||||
|
return prev_value if prev_value is not None else stop_value
|
||||||
|
prev_stop = stop_num
|
||||||
|
prev_value = stop_value
|
||||||
|
return prev_value
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def layer_visible(layer: dict[str, Any], zoom: int) -> bool:
|
||||||
|
if zoom < int(layer.get("minzoom", 0)):
|
||||||
|
return False
|
||||||
|
maxzoom = layer.get("maxzoom")
|
||||||
|
if maxzoom is not None and zoom >= int(maxzoom):
|
||||||
|
return False
|
||||||
|
layout = layer.get("layout") or {}
|
||||||
|
if layout.get("visibility") == "none":
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def layer_matches_feature(layer: dict[str, Any], feature: AuditFeature) -> bool:
|
||||||
|
if layer.get("source-layer") != feature.layer_name:
|
||||||
|
return False
|
||||||
|
if not layer_visible(layer, feature.tile_z):
|
||||||
|
return False
|
||||||
|
filter_expr = layer.get("filter")
|
||||||
|
if filter_expr is None:
|
||||||
|
return True
|
||||||
|
return bool(evaluate_expression(filter_expr, feature, feature.tile_z))
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_style_value(layer: dict[str, Any], section: str, key: str, feature: AuditFeature) -> Any:
|
||||||
|
payload = layer.get(section) or {}
|
||||||
|
if key not in payload:
|
||||||
|
return None
|
||||||
|
return evaluate_expression(payload[key], feature, feature.tile_z)
|
||||||
|
|
||||||
|
|
||||||
|
def build_signature(component_type: str, payload: dict[str, Any]) -> str:
|
||||||
|
normalized = {
|
||||||
|
k: payload[k]
|
||||||
|
for k in sorted(payload)
|
||||||
|
if k != "layer_id" and payload[k] not in (None, "", [], {})
|
||||||
|
}
|
||||||
|
return f"{component_type}:{canonical_json(normalized)}"
|
||||||
|
|
||||||
|
|
||||||
|
def collect_layer_observations(layer: dict[str, Any], feature: AuditFeature) -> list[RenderObservation]:
|
||||||
|
observations: list[RenderObservation] = []
|
||||||
|
layer_id = str(layer["id"])
|
||||||
|
layer_type = str(layer.get("type", ""))
|
||||||
|
|
||||||
|
if layer_type == "symbol":
|
||||||
|
icon_image = evaluate_style_value(layer, "layout", "icon-image", feature)
|
||||||
|
text_value = evaluate_style_value(layer, "layout", "text-field", feature)
|
||||||
|
text_color = evaluate_style_value(layer, "paint", "text-color", feature)
|
||||||
|
text_anchor = evaluate_style_value(layer, "layout", "text-anchor", feature)
|
||||||
|
text_size = evaluate_style_value(layer, "layout", "text-size", feature)
|
||||||
|
icon_size = evaluate_style_value(layer, "layout", "icon-size", feature)
|
||||||
|
if icon_image not in (None, ""):
|
||||||
|
payload = {
|
||||||
|
"layer_id": layer_id,
|
||||||
|
"icon_image": icon_image,
|
||||||
|
"icon_size": icon_size,
|
||||||
|
}
|
||||||
|
observations.append(
|
||||||
|
RenderObservation(
|
||||||
|
component_type="icon",
|
||||||
|
style_layer_id=layer_id,
|
||||||
|
signature=build_signature("icon", payload),
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if text_value not in (None, ""):
|
||||||
|
payload = {
|
||||||
|
"layer_id": layer_id,
|
||||||
|
"text_value": str(text_value),
|
||||||
|
"text_color": normalize_color(text_color),
|
||||||
|
"text_anchor": text_anchor,
|
||||||
|
"text_size": text_size,
|
||||||
|
}
|
||||||
|
observations.append(
|
||||||
|
RenderObservation(
|
||||||
|
component_type="text",
|
||||||
|
style_layer_id=layer_id,
|
||||||
|
signature=build_signature("text", payload),
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return observations
|
||||||
|
|
||||||
|
if layer_type == "line":
|
||||||
|
payload = {
|
||||||
|
"layer_id": layer_id,
|
||||||
|
"line_color": normalize_color(evaluate_style_value(layer, "paint", "line-color", feature)),
|
||||||
|
"line_width": evaluate_style_value(layer, "paint", "line-width", feature),
|
||||||
|
"line_dasharray": evaluate_style_value(layer, "paint", "line-dasharray", feature),
|
||||||
|
"line_pattern": evaluate_style_value(layer, "paint", "line-pattern", feature),
|
||||||
|
}
|
||||||
|
observations.append(
|
||||||
|
RenderObservation(
|
||||||
|
component_type="line",
|
||||||
|
style_layer_id=layer_id,
|
||||||
|
signature=build_signature("line", payload),
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return observations
|
||||||
|
|
||||||
|
if layer_type == "fill":
|
||||||
|
payload = {
|
||||||
|
"layer_id": layer_id,
|
||||||
|
"fill_color": normalize_color(evaluate_style_value(layer, "paint", "fill-color", feature)),
|
||||||
|
"fill_pattern": evaluate_style_value(layer, "paint", "fill-pattern", feature),
|
||||||
|
"fill_outline_color": normalize_color(evaluate_style_value(layer, "paint", "fill-outline-color", feature)),
|
||||||
|
"fill_opacity": evaluate_style_value(layer, "paint", "fill-opacity", feature),
|
||||||
|
}
|
||||||
|
observations.append(
|
||||||
|
RenderObservation(
|
||||||
|
component_type="fill",
|
||||||
|
style_layer_id=layer_id,
|
||||||
|
signature=build_signature("fill", payload),
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return observations
|
||||||
|
|
||||||
|
return observations
|
||||||
|
|
||||||
|
|
||||||
|
def collect_render_observations(style: dict[str, Any], feature: AuditFeature) -> list[RenderObservation]:
|
||||||
|
observations: list[RenderObservation] = []
|
||||||
|
for layer in style.get("layers", []):
|
||||||
|
if "source-layer" not in layer:
|
||||||
|
continue
|
||||||
|
if not layer_matches_feature(layer, feature):
|
||||||
|
continue
|
||||||
|
observations.extend(collect_layer_observations(layer, feature))
|
||||||
|
return observations
|
||||||
|
|
||||||
|
|
||||||
|
def load_style(path: Path) -> dict[str, Any]:
|
||||||
|
return json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def iter_tile_paths(root: Path) -> list[Path]:
|
||||||
|
return sorted(root.glob("*/*/*.pbf"))
|
||||||
|
|
||||||
|
|
||||||
|
def stable_fallback_properties(properties: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
key: properties[key]
|
||||||
|
for key in STABLE_FALLBACK_KEYS
|
||||||
|
if key in properties and properties[key] not in (None, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def geometry_hash(geometry: dict[str, Any]) -> str:
|
||||||
|
return hash_text(canonical_json(geometry))
|
||||||
|
|
||||||
|
|
||||||
|
def extract_legacy_fid(properties: dict[str, Any]) -> str | None:
|
||||||
|
legacy = properties.get("fid_legacy_raw")
|
||||||
|
if legacy not in (None, ""):
|
||||||
|
return str(legacy)
|
||||||
|
fid = properties.get("fid")
|
||||||
|
if fid not in (None, ""):
|
||||||
|
return str(fid)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def object_identity(layer_name: str, geometry: dict[str, Any], properties: dict[str, Any]) -> tuple[str, str | None]:
|
||||||
|
geom_type = str(geometry.get("type", ""))
|
||||||
|
legacy_source_layer = text_or_none(properties.get("source_layer_jp")) or layer_name
|
||||||
|
legacy_fid = extract_legacy_fid(properties)
|
||||||
|
if legacy_fid:
|
||||||
|
return f"fid:{legacy_fid}|src:{legacy_source_layer}|geom:{geom_type}", legacy_fid
|
||||||
|
fallback_payload = {
|
||||||
|
"source_layer": legacy_source_layer,
|
||||||
|
"geom_type": geom_type,
|
||||||
|
"geometry_hash": geometry_hash(geometry),
|
||||||
|
"stable_props": stable_fallback_properties(properties),
|
||||||
|
}
|
||||||
|
return f"fallback:{hash_text(canonical_json(fallback_payload))}", None
|
||||||
|
|
||||||
|
|
||||||
|
def feature_instance_id(feature: AuditFeature) -> str:
|
||||||
|
return (
|
||||||
|
f"{feature.object_id}|z:{feature.tile_z}|x:{feature.tile_x}|"
|
||||||
|
f"y:{feature.tile_y}|layer:{feature.layer_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_tile_instances(dataset: str, tile_root: Path, style: dict[str, Any], tile_path: Path) -> dict[str, dict[str, Any]]:
|
||||||
|
instances: dict[str, dict[str, Any]] = {}
|
||||||
|
rel = tile_path.relative_to(tile_root)
|
||||||
|
z = int(rel.parts[0])
|
||||||
|
x = int(rel.parts[1])
|
||||||
|
y = int(tile_path.stem)
|
||||||
|
decoded = mapbox_vector_tile.decode(tile_path.read_bytes())
|
||||||
|
for layer_name, payload in decoded.items():
|
||||||
|
for feature in payload.get("features", []):
|
||||||
|
geometry = feature.get("geometry") or {}
|
||||||
|
properties = dict(feature.get("properties") or {})
|
||||||
|
object_id, legacy_fid = object_identity(layer_name, geometry, properties)
|
||||||
|
audit_feature = AuditFeature(
|
||||||
|
dataset=dataset,
|
||||||
|
tile_z=z,
|
||||||
|
tile_x=x,
|
||||||
|
tile_y=y,
|
||||||
|
layer_name=layer_name,
|
||||||
|
geom_type=str(geometry.get("type", "")),
|
||||||
|
object_id=object_id,
|
||||||
|
fid_legacy=legacy_fid,
|
||||||
|
properties=properties,
|
||||||
|
)
|
||||||
|
instance_id = feature_instance_id(audit_feature)
|
||||||
|
instances[instance_id] = {
|
||||||
|
"feature": audit_feature,
|
||||||
|
"observations": collect_render_observations(style, audit_feature),
|
||||||
|
}
|
||||||
|
return instances
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_observations(observations: list[RenderObservation]) -> dict[str, list[str]]:
|
||||||
|
grouped: dict[str, set[str]] = defaultdict(set)
|
||||||
|
for observation in observations:
|
||||||
|
grouped[observation.component_type].add(observation.signature)
|
||||||
|
return {key: sorted(values) for key, values in grouped.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_component_map(component_map: dict[str, list[str]]) -> dict[str, tuple[str, ...]]:
|
||||||
|
return {key: tuple(values) for key, values in sorted(component_map.items())}
|
||||||
|
|
||||||
|
|
||||||
|
def compare_instance(
|
||||||
|
original: dict[str, Any] | None,
|
||||||
|
engineering: dict[str, Any] | None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if original is None:
|
||||||
|
eng_feature = engineering["feature"]
|
||||||
|
return {
|
||||||
|
"status": "extra_in_engineering",
|
||||||
|
"object_instance_id": feature_instance_id(eng_feature),
|
||||||
|
"object_id": eng_feature.object_id,
|
||||||
|
"fid_legacy": eng_feature.fid_legacy,
|
||||||
|
"tile": f"{eng_feature.tile_z}/{eng_feature.tile_x}/{eng_feature.tile_y}",
|
||||||
|
"tile_z": eng_feature.tile_z,
|
||||||
|
"tile_x": eng_feature.tile_x,
|
||||||
|
"tile_y": eng_feature.tile_y,
|
||||||
|
"source_layer": eng_feature.layer_name,
|
||||||
|
"canonical_object_type": text_or_none(eng_feature.properties.get("canonical_object_type")),
|
||||||
|
"original_components": {},
|
||||||
|
"engineering_components": normalize_component_map(
|
||||||
|
summarize_observations(engineering["observations"])
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if engineering is None:
|
||||||
|
orig_feature = original["feature"]
|
||||||
|
return {
|
||||||
|
"status": "missing_in_engineering",
|
||||||
|
"object_instance_id": feature_instance_id(orig_feature),
|
||||||
|
"object_id": orig_feature.object_id,
|
||||||
|
"fid_legacy": orig_feature.fid_legacy,
|
||||||
|
"tile": f"{orig_feature.tile_z}/{orig_feature.tile_x}/{orig_feature.tile_y}",
|
||||||
|
"tile_z": orig_feature.tile_z,
|
||||||
|
"tile_x": orig_feature.tile_x,
|
||||||
|
"tile_y": orig_feature.tile_y,
|
||||||
|
"source_layer": orig_feature.layer_name,
|
||||||
|
"canonical_object_type": None,
|
||||||
|
"original_components": normalize_component_map(
|
||||||
|
summarize_observations(original["observations"])
|
||||||
|
),
|
||||||
|
"engineering_components": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
orig_feature = original["feature"]
|
||||||
|
eng_feature = engineering["feature"]
|
||||||
|
original_components = normalize_component_map(summarize_observations(original["observations"]))
|
||||||
|
engineering_components = normalize_component_map(summarize_observations(engineering["observations"]))
|
||||||
|
|
||||||
|
if original_components == engineering_components:
|
||||||
|
status = "exact_match"
|
||||||
|
elif not original_components and engineering_components:
|
||||||
|
status = "extra_in_engineering"
|
||||||
|
elif original_components and not engineering_components:
|
||||||
|
status = "missing_in_engineering"
|
||||||
|
else:
|
||||||
|
status = "mismatch"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": status,
|
||||||
|
"object_instance_id": feature_instance_id(orig_feature),
|
||||||
|
"object_id": orig_feature.object_id,
|
||||||
|
"fid_legacy": orig_feature.fid_legacy or eng_feature.fid_legacy,
|
||||||
|
"tile": f"{orig_feature.tile_z}/{orig_feature.tile_x}/{orig_feature.tile_y}",
|
||||||
|
"tile_z": orig_feature.tile_z,
|
||||||
|
"tile_x": orig_feature.tile_x,
|
||||||
|
"tile_y": orig_feature.tile_y,
|
||||||
|
"source_layer": text_or_none(eng_feature.properties.get("source_layer_jp")) or orig_feature.layer_name,
|
||||||
|
"canonical_object_type": text_or_none(eng_feature.properties.get("canonical_object_type")),
|
||||||
|
"original_components": original_components,
|
||||||
|
"engineering_components": engineering_components,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_component_map(component_map: dict[str, tuple[str, ...]] | dict[str, list[str]]) -> str:
|
||||||
|
if not component_map:
|
||||||
|
return "none"
|
||||||
|
parts = []
|
||||||
|
for key in sorted(component_map):
|
||||||
|
values = component_map[key]
|
||||||
|
parts.append(f"{key}={list(values)}")
|
||||||
|
return "; ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def write_reports(
|
||||||
|
*,
|
||||||
|
original_count: int,
|
||||||
|
engineering_count: int,
|
||||||
|
result_count: int,
|
||||||
|
status_counter: Counter[str],
|
||||||
|
source_layer_counter: Counter[tuple[str, str]],
|
||||||
|
mismatch_examples: list[dict[str, Any]],
|
||||||
|
) -> None:
|
||||||
|
payload = {
|
||||||
|
"original_feature_instances": original_count,
|
||||||
|
"engineering_feature_instances": engineering_count,
|
||||||
|
"result_count": result_count,
|
||||||
|
"status_counts": dict(status_counter),
|
||||||
|
"source_layer_issue_counts": [
|
||||||
|
{"status": status, "source_layer": source_layer, "count": count}
|
||||||
|
for (status, source_layer), count in source_layer_counter.most_common(30)
|
||||||
|
],
|
||||||
|
"mismatch_examples": mismatch_examples,
|
||||||
|
}
|
||||||
|
REPORT_JSON_PATH.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# NavSea 原始版 vs 工程版渲染审计报告",
|
||||||
|
"",
|
||||||
|
"## 范围",
|
||||||
|
"",
|
||||||
|
f"- 原始样式: `{ORIGINAL_STYLE_PATH}`",
|
||||||
|
f"- 工程样式: `{ENGINEERING_STYLE_PATH}`",
|
||||||
|
f"- 原始瓦片根目录: `{ORIGINAL_TILE_ROOT}`",
|
||||||
|
f"- 工程瓦片根目录: `{ENGINEERING_TILE_ROOT}`",
|
||||||
|
f"- 原始 feature 实例数: `{original_count}`",
|
||||||
|
f"- 工程 feature 实例数: `{engineering_count}`",
|
||||||
|
f"- 审计结果数: `{result_count}`",
|
||||||
|
"",
|
||||||
|
"## 匹配口径",
|
||||||
|
"",
|
||||||
|
"- 主键优先使用 legacy `fid`。",
|
||||||
|
"- 没有 `fid` 的对象,回退到 `geometry + 稳定旧属性`。",
|
||||||
|
"- 审计粒度保留 tile 实例,因为渲染具有 zoom 敏感性。",
|
||||||
|
"",
|
||||||
|
"## 结果统计",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
for status, count in status_counter.most_common():
|
||||||
|
lines.append(f"- `{status}`: `{count}`")
|
||||||
|
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
"## 主要问题层",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
if not source_layer_counter:
|
||||||
|
lines.append("- 没有发现差异。")
|
||||||
|
else:
|
||||||
|
for (status, source_layer), count in source_layer_counter.most_common(20):
|
||||||
|
lines.append(f"- `{status}` | `{source_layer}` | `{count}`")
|
||||||
|
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
"## 差异样例",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
if not mismatch_examples:
|
||||||
|
lines.append("- 没有差异样例。")
|
||||||
|
else:
|
||||||
|
for item in mismatch_examples:
|
||||||
|
lines.append(
|
||||||
|
f"- `{item['status']}` | tile=`{item['tile']}` | source_layer=`{item['source_layer']}` | "
|
||||||
|
f"fid=`{item['fid_legacy']}` | object=`{item['canonical_object_type'] or 'n/a'}`"
|
||||||
|
)
|
||||||
|
lines.append(f" original: {format_component_map(item['original_components'])}")
|
||||||
|
lines.append(f" engineering: {format_component_map(item['engineering_components'])}")
|
||||||
|
|
||||||
|
REPORT_MD_PATH.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def db_connect() -> pymysql.Connection:
|
||||||
|
cfg = DbConfig()
|
||||||
|
kwargs: dict[str, Any] = {
|
||||||
|
"host": cfg.host,
|
||||||
|
"port": cfg.port,
|
||||||
|
"user": cfg.user,
|
||||||
|
"password": cfg.password,
|
||||||
|
"database": cfg.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
}
|
||||||
|
if cfg.unix_socket:
|
||||||
|
kwargs["unix_socket"] = cfg.unix_socket
|
||||||
|
return pymysql.connect(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_audit_tables(cur: pymysql.cursors.Cursor) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_render_audit_run (
|
||||||
|
audit_name VARCHAR(128) NOT NULL,
|
||||||
|
original_style_path VARCHAR(512) NOT NULL,
|
||||||
|
engineering_style_path VARCHAR(512) NOT NULL,
|
||||||
|
original_tile_root VARCHAR(512) NOT NULL,
|
||||||
|
engineering_tile_root VARCHAR(512) NOT NULL,
|
||||||
|
original_feature_instances INT NOT NULL,
|
||||||
|
engineering_feature_instances INT NOT NULL,
|
||||||
|
result_count INT NOT NULL,
|
||||||
|
status_counts_json LONGTEXT NOT NULL,
|
||||||
|
source_layer_issue_counts_json LONGTEXT NOT NULL,
|
||||||
|
report_md_path VARCHAR(512) NOT NULL,
|
||||||
|
report_json_path VARCHAR(512) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (audit_name)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS navsea_render_audit_result (
|
||||||
|
audit_name VARCHAR(64) NOT NULL,
|
||||||
|
object_instance_key VARCHAR(64) NOT NULL,
|
||||||
|
object_instance_id TEXT NOT NULL,
|
||||||
|
object_id VARCHAR(255) NOT NULL,
|
||||||
|
fid_legacy VARCHAR(64) DEFAULT NULL,
|
||||||
|
tile_z INT NOT NULL,
|
||||||
|
tile_x INT NOT NULL,
|
||||||
|
tile_y INT NOT NULL,
|
||||||
|
source_layer VARCHAR(100) DEFAULT NULL,
|
||||||
|
canonical_object_type VARCHAR(191) DEFAULT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL,
|
||||||
|
original_components_json LONGTEXT NOT NULL,
|
||||||
|
engineering_components_json LONGTEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (audit_name, object_instance_key),
|
||||||
|
KEY idx_render_audit_fid (fid_legacy),
|
||||||
|
KEY idx_render_audit_status (status),
|
||||||
|
KEY idx_render_audit_layer (source_layer)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_result_batch(cur: pymysql.cursors.Cursor, rows: list[tuple[Any, ...]]) -> None:
|
||||||
|
if not rows:
|
||||||
|
return
|
||||||
|
cur.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_render_audit_result (
|
||||||
|
audit_name,
|
||||||
|
object_instance_key,
|
||||||
|
object_instance_id,
|
||||||
|
object_id,
|
||||||
|
fid_legacy,
|
||||||
|
tile_z,
|
||||||
|
tile_x,
|
||||||
|
tile_y,
|
||||||
|
source_layer,
|
||||||
|
canonical_object_type,
|
||||||
|
status,
|
||||||
|
original_components_json,
|
||||||
|
engineering_components_json
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def persist_audit_run(
|
||||||
|
cur: pymysql.cursors.Cursor,
|
||||||
|
*,
|
||||||
|
original_count: int,
|
||||||
|
engineering_count: int,
|
||||||
|
result_count: int,
|
||||||
|
status_counter: Counter[str],
|
||||||
|
source_layer_counter: Counter[tuple[str, str]],
|
||||||
|
) -> None:
|
||||||
|
source_layer_issue_counts = [
|
||||||
|
{"status": status, "source_layer": source_layer, "count": count}
|
||||||
|
for (status, source_layer), count in source_layer_counter.most_common(100)
|
||||||
|
]
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO navsea_render_audit_run (
|
||||||
|
audit_name,
|
||||||
|
original_style_path,
|
||||||
|
engineering_style_path,
|
||||||
|
original_tile_root,
|
||||||
|
engineering_tile_root,
|
||||||
|
original_feature_instances,
|
||||||
|
engineering_feature_instances,
|
||||||
|
result_count,
|
||||||
|
status_counts_json,
|
||||||
|
source_layer_issue_counts_json,
|
||||||
|
report_md_path,
|
||||||
|
report_json_path
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
AUDIT_NAME,
|
||||||
|
str(ORIGINAL_STYLE_PATH),
|
||||||
|
str(ENGINEERING_STYLE_PATH),
|
||||||
|
str(ORIGINAL_TILE_ROOT),
|
||||||
|
str(ENGINEERING_TILE_ROOT),
|
||||||
|
original_count,
|
||||||
|
engineering_count,
|
||||||
|
result_count,
|
||||||
|
json.dumps(dict(status_counter), ensure_ascii=False),
|
||||||
|
json.dumps(source_layer_issue_counts, ensure_ascii=False),
|
||||||
|
str(REPORT_MD_PATH),
|
||||||
|
str(REPORT_JSON_PATH),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Audit original vs engineering render outputs.")
|
||||||
|
parser.add_argument("--audit-name", default=DEFAULT_AUDIT_NAME)
|
||||||
|
parser.add_argument("--original-style", type=Path, default=DEFAULT_ORIGINAL_STYLE_PATH)
|
||||||
|
parser.add_argument("--engineering-style", type=Path, default=DEFAULT_ENGINEERING_STYLE_PATH)
|
||||||
|
parser.add_argument("--original-tile-root", type=Path, default=DEFAULT_ORIGINAL_TILE_ROOT)
|
||||||
|
parser.add_argument("--engineering-tile-root", type=Path, default=DEFAULT_ENGINEERING_TILE_ROOT)
|
||||||
|
parser.add_argument("--report-md", type=Path, default=DEFAULT_REPORT_MD_PATH)
|
||||||
|
parser.add_argument("--report-json", type=Path, default=DEFAULT_REPORT_JSON_PATH)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
global AUDIT_NAME
|
||||||
|
global ORIGINAL_STYLE_PATH
|
||||||
|
global ENGINEERING_STYLE_PATH
|
||||||
|
global ORIGINAL_TILE_ROOT
|
||||||
|
global ENGINEERING_TILE_ROOT
|
||||||
|
global REPORT_MD_PATH
|
||||||
|
global REPORT_JSON_PATH
|
||||||
|
|
||||||
|
args = parse_args()
|
||||||
|
AUDIT_NAME = args.audit_name
|
||||||
|
ORIGINAL_STYLE_PATH = args.original_style
|
||||||
|
ENGINEERING_STYLE_PATH = args.engineering_style
|
||||||
|
ORIGINAL_TILE_ROOT = args.original_tile_root
|
||||||
|
ENGINEERING_TILE_ROOT = args.engineering_tile_root
|
||||||
|
REPORT_MD_PATH = args.report_md
|
||||||
|
REPORT_JSON_PATH = args.report_json
|
||||||
|
|
||||||
|
original_style = load_style(ORIGINAL_STYLE_PATH)
|
||||||
|
engineering_style = load_style(ENGINEERING_STYLE_PATH)
|
||||||
|
engineering_tiles = iter_tile_paths(ENGINEERING_TILE_ROOT)
|
||||||
|
|
||||||
|
original_count = 0
|
||||||
|
engineering_count = 0
|
||||||
|
result_count = 0
|
||||||
|
status_counter: Counter[str] = Counter()
|
||||||
|
source_layer_counter: Counter[tuple[str, str]] = Counter()
|
||||||
|
mismatch_examples: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
with db_connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
ensure_audit_tables(cur)
|
||||||
|
cur.execute("DELETE FROM navsea_render_audit_result")
|
||||||
|
cur.execute("DELETE FROM navsea_render_audit_run")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
rows: list[tuple[Any, ...]] = []
|
||||||
|
for engineering_tile in engineering_tiles:
|
||||||
|
rel = engineering_tile.relative_to(ENGINEERING_TILE_ROOT)
|
||||||
|
original_tile = ORIGINAL_TILE_ROOT / rel
|
||||||
|
if not original_tile.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
original_instances = decode_tile_instances("original", ORIGINAL_TILE_ROOT, original_style, original_tile)
|
||||||
|
engineering_instances = decode_tile_instances(
|
||||||
|
"engineering", ENGINEERING_TILE_ROOT, engineering_style, engineering_tile
|
||||||
|
)
|
||||||
|
original_count += len(original_instances)
|
||||||
|
engineering_count += len(engineering_instances)
|
||||||
|
|
||||||
|
all_instance_ids = sorted(set(original_instances) | set(engineering_instances))
|
||||||
|
for instance_id in all_instance_ids:
|
||||||
|
item = compare_instance(original_instances.get(instance_id), engineering_instances.get(instance_id))
|
||||||
|
result_count += 1
|
||||||
|
status_counter[item["status"]] += 1
|
||||||
|
if item["status"] != "exact_match":
|
||||||
|
source_layer_counter[(item["status"], item["source_layer"] or "unknown")] += 1
|
||||||
|
if len(mismatch_examples) < MAX_EXAMPLES:
|
||||||
|
mismatch_examples.append(item)
|
||||||
|
|
||||||
|
rows.append(
|
||||||
|
(
|
||||||
|
AUDIT_NAME,
|
||||||
|
hash_text(item["object_instance_id"]),
|
||||||
|
item["object_instance_id"],
|
||||||
|
item["object_id"],
|
||||||
|
item["fid_legacy"],
|
||||||
|
item["tile_z"],
|
||||||
|
item["tile_x"],
|
||||||
|
item["tile_y"],
|
||||||
|
item["source_layer"],
|
||||||
|
item["canonical_object_type"],
|
||||||
|
item["status"],
|
||||||
|
json.dumps(item["original_components"], ensure_ascii=False),
|
||||||
|
json.dumps(item["engineering_components"], ensure_ascii=False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if len(rows) >= DB_BATCH_SIZE:
|
||||||
|
insert_result_batch(cur, rows)
|
||||||
|
conn.commit()
|
||||||
|
rows.clear()
|
||||||
|
|
||||||
|
insert_result_batch(cur, rows)
|
||||||
|
persist_audit_run(
|
||||||
|
cur,
|
||||||
|
original_count=original_count,
|
||||||
|
engineering_count=engineering_count,
|
||||||
|
result_count=result_count,
|
||||||
|
status_counter=status_counter,
|
||||||
|
source_layer_counter=source_layer_counter,
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
write_reports(
|
||||||
|
original_count=original_count,
|
||||||
|
engineering_count=engineering_count,
|
||||||
|
result_count=result_count,
|
||||||
|
status_counter=status_counter,
|
||||||
|
source_layer_counter=source_layer_counter,
|
||||||
|
mismatch_examples=mismatch_examples,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"original_feature_instances": original_count,
|
||||||
|
"engineering_feature_instances": engineering_count,
|
||||||
|
"results": result_count,
|
||||||
|
"report_md": str(REPORT_MD_PATH),
|
||||||
|
"report_json": str(REPORT_JSON_PATH),
|
||||||
|
"audit_name": AUDIT_NAME,
|
||||||
|
"db_run_table": "navsea_render_audit_run",
|
||||||
|
"db_result_table": "navsea_render_audit_result",
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
440
navsea_semantic_validation.py
Normal file
440
navsea_semantic_validation.py
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DbConfig:
|
||||||
|
host: str = os.getenv("NAVSEA_DB_HOST", "localhost")
|
||||||
|
port: int = int(os.getenv("NAVSEA_DB_PORT", "3306"))
|
||||||
|
user: str = os.getenv("NAVSEA_DB_USER", "root")
|
||||||
|
password: str = os.getenv("NAVSEA_DB_PASSWORD", "2chi9ks2")
|
||||||
|
database: str = os.getenv("NAVSEA_DB_NAME", "pbf_analysis")
|
||||||
|
unix_socket: str | None = os.getenv("NAVSEA_DB_SOCKET", "/tmp/mysql.sock")
|
||||||
|
|
||||||
|
|
||||||
|
class SemanticOverlayValidator:
|
||||||
|
REQUIRED_TABLES = {
|
||||||
|
"canonical_layer_rules": {"source_layer", "semantic_granularity", "canonical_family"},
|
||||||
|
"canonical_object_rules": {
|
||||||
|
"canonical_object_type",
|
||||||
|
"canonical_family",
|
||||||
|
"classification_basis",
|
||||||
|
"source_layer_scope",
|
||||||
|
"allowed_geom_types",
|
||||||
|
},
|
||||||
|
"object_geometry_allowlist": {
|
||||||
|
"canonical_object_type",
|
||||||
|
"geometry_type",
|
||||||
|
"is_allowed",
|
||||||
|
},
|
||||||
|
"pbf_relayer_candidates": {
|
||||||
|
"feature_id",
|
||||||
|
"source_layer",
|
||||||
|
"geom_type",
|
||||||
|
"canonical_object_type",
|
||||||
|
"canonical_family",
|
||||||
|
"classification_basis",
|
||||||
|
"source_layer_scope",
|
||||||
|
"semantic_granularity",
|
||||||
|
"detection_key",
|
||||||
|
"render_layer",
|
||||||
|
"object_type_source",
|
||||||
|
"style_bound",
|
||||||
|
"z",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
},
|
||||||
|
"pbf_render_compatibility": {
|
||||||
|
"source_layer",
|
||||||
|
"render_layer",
|
||||||
|
"feature_count",
|
||||||
|
},
|
||||||
|
"pbf_source_object_stats": {
|
||||||
|
"source_layer",
|
||||||
|
"render_layer",
|
||||||
|
"canonical_family",
|
||||||
|
"semantic_granularity",
|
||||||
|
"canonical_object_type",
|
||||||
|
"geom_type",
|
||||||
|
"feature_count",
|
||||||
|
},
|
||||||
|
"pbf_detection_catalog": {
|
||||||
|
"detection_key",
|
||||||
|
"canonical_object_type",
|
||||||
|
"canonical_family",
|
||||||
|
"geom_type",
|
||||||
|
"feature_count",
|
||||||
|
},
|
||||||
|
"style_layers": {"source_layer"},
|
||||||
|
"features": {"id", "z", "x", "y", "vt_layer", "geom_type"},
|
||||||
|
"tile_density": {"z", "x", "y", "feature_count"},
|
||||||
|
"tile_layer_density": {"z", "x", "y", "vt_layer", "feature_count"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, db_config: DbConfig) -> None:
|
||||||
|
self.db_config = db_config
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
kwargs = {
|
||||||
|
"host": self.db_config.host,
|
||||||
|
"port": self.db_config.port,
|
||||||
|
"user": self.db_config.user,
|
||||||
|
"password": self.db_config.password,
|
||||||
|
"database": self.db_config.database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
}
|
||||||
|
if self.db_config.unix_socket and self.db_config.host in {"localhost", "127.0.0.1"}:
|
||||||
|
kwargs["unix_socket"] = self.db_config.unix_socket
|
||||||
|
return pymysql.connect(**kwargs)
|
||||||
|
|
||||||
|
def validate_schema(self) -> None:
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for table, required_columns in self.REQUIRED_TABLES.items():
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT COLUMN_NAME
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s
|
||||||
|
""",
|
||||||
|
(self.db_config.database, table),
|
||||||
|
)
|
||||||
|
columns = {row[0] for row in cur.fetchall()}
|
||||||
|
if not columns:
|
||||||
|
raise RuntimeError(f"required table missing: {table}")
|
||||||
|
missing = required_columns - columns
|
||||||
|
if missing:
|
||||||
|
raise RuntimeError(f"table {table} missing columns: {sorted(missing)}")
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self.validate_schema()
|
||||||
|
with self.connect() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
self.drop_validation_tables(cur)
|
||||||
|
self.build_classification_validation(cur)
|
||||||
|
self.build_geometry_consistency(cur)
|
||||||
|
self.build_style_render_equivalence(cur)
|
||||||
|
self.build_detection_catalog_integrity(cur)
|
||||||
|
self.build_spatial_anomalies(cur)
|
||||||
|
self.build_summary(cur)
|
||||||
|
conn.commit()
|
||||||
|
print(
|
||||||
|
"Built classification_validation, geometry_consistency, "
|
||||||
|
"style_render_equivalence, detection_catalog_integrity, "
|
||||||
|
"spatial_anomalies, semantic_validation_summary"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def drop_validation_tables(cur) -> None:
|
||||||
|
for table in (
|
||||||
|
"classification_validation",
|
||||||
|
"geometry_consistency",
|
||||||
|
"style_render_equivalence",
|
||||||
|
"detection_catalog_integrity",
|
||||||
|
"spatial_anomalies",
|
||||||
|
"semantic_validation_summary",
|
||||||
|
):
|
||||||
|
cur.execute(f"DROP TABLE IF EXISTS {table}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_classification_validation(cur) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE classification_validation AS
|
||||||
|
SELECT
|
||||||
|
base.canonical_object_type,
|
||||||
|
base.feature_count,
|
||||||
|
base.distinct_source_layers,
|
||||||
|
base.distinct_geom_types,
|
||||||
|
CASE
|
||||||
|
WHEN base.canonical_object_type IS NULL OR base.canonical_object_type = '' THEN 'UNKNOWN_OBJECT'
|
||||||
|
WHEN rules.canonical_object_type IS NULL THEN 'UNKNOWN_OBJECT'
|
||||||
|
WHEN rules.classification_basis = 'source_layer_preserved' THEN 'OK'
|
||||||
|
WHEN base.distinct_families > 1 THEN 'RULE_CONFLICT'
|
||||||
|
WHEN rules.source_layer_scope = 'single_source' AND base.distinct_source_layers > 1 THEN 'AMBIGUOUS_MAPPING'
|
||||||
|
ELSE 'OK'
|
||||||
|
END AS classification_status
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
canonical_object_type,
|
||||||
|
SUM(feature_count) AS feature_count,
|
||||||
|
COUNT(DISTINCT source_layer) AS distinct_source_layers,
|
||||||
|
COUNT(DISTINCT geom_type) AS distinct_geom_types,
|
||||||
|
COUNT(DISTINCT canonical_family) AS distinct_families
|
||||||
|
FROM pbf_source_object_stats
|
||||||
|
GROUP BY canonical_object_type
|
||||||
|
) AS base
|
||||||
|
LEFT JOIN canonical_object_rules rules
|
||||||
|
ON base.canonical_object_type = rules.canonical_object_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE classification_validation ADD KEY idx_status (classification_status)")
|
||||||
|
cur.execute("ALTER TABLE classification_validation ADD KEY idx_object (canonical_object_type(100))")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_geometry_consistency(cur) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE geometry_consistency AS
|
||||||
|
SELECT
|
||||||
|
g.canonical_object_type,
|
||||||
|
g.geom_type AS geometry_type,
|
||||||
|
g.feature_count,
|
||||||
|
CASE
|
||||||
|
WHEN allowlist.is_allowed = 1 THEN 'OK'
|
||||||
|
WHEN g.feature_share < 0.05 THEN 'GEOMETRY_DRIFT'
|
||||||
|
ELSE 'UNEXPECTED_GEOMETRY'
|
||||||
|
END AS geometry_consistency_status
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
canonical_object_type,
|
||||||
|
canonical_family,
|
||||||
|
geom_type,
|
||||||
|
SUM(feature_count) AS feature_count,
|
||||||
|
SUM(feature_count) / totals.total_features AS feature_share
|
||||||
|
FROM pbf_detection_catalog
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
canonical_object_type,
|
||||||
|
SUM(feature_count) AS total_features
|
||||||
|
FROM pbf_detection_catalog
|
||||||
|
GROUP BY canonical_object_type
|
||||||
|
) AS totals
|
||||||
|
USING (canonical_object_type)
|
||||||
|
GROUP BY
|
||||||
|
canonical_object_type,
|
||||||
|
canonical_family,
|
||||||
|
geom_type,
|
||||||
|
totals.total_features
|
||||||
|
) AS g
|
||||||
|
LEFT JOIN object_geometry_allowlist allowlist
|
||||||
|
ON g.canonical_object_type = allowlist.canonical_object_type
|
||||||
|
AND g.geom_type = allowlist.geometry_type
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE geometry_consistency ADD KEY idx_object (canonical_object_type(100))")
|
||||||
|
cur.execute("ALTER TABLE geometry_consistency ADD KEY idx_status (geometry_consistency_status)")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_style_render_equivalence(cur) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE style_render_equivalence AS
|
||||||
|
SELECT
|
||||||
|
style_scope.style_layer,
|
||||||
|
COALESCE(before_counts.feature_count_before, 0) AS feature_count_before,
|
||||||
|
COALESCE(after_counts.feature_count_after, 0) AS feature_count_after,
|
||||||
|
COALESCE(after_counts.feature_count_after, 0) - COALESCE(before_counts.feature_count_before, 0) AS difference,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(before_counts.feature_count_before, 0) = COALESCE(after_counts.feature_count_after, 0) THEN 'OK'
|
||||||
|
WHEN COALESCE(before_counts.feature_count_before, 0) > 0 AND COALESCE(after_counts.feature_count_after, 0) = 0 THEN 'RENDER_BREAK_RISK'
|
||||||
|
ELSE 'COUNT_MISMATCH'
|
||||||
|
END AS render_status
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT source_layer AS style_layer
|
||||||
|
FROM style_layers
|
||||||
|
WHERE source_layer IS NOT NULL
|
||||||
|
) AS style_scope
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
vt_layer AS style_layer,
|
||||||
|
COUNT(*) AS feature_count_before
|
||||||
|
FROM features
|
||||||
|
GROUP BY vt_layer
|
||||||
|
) AS before_counts
|
||||||
|
ON style_scope.style_layer = before_counts.style_layer
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
LEFT(render_layer, 100) AS style_layer,
|
||||||
|
SUM(feature_count) AS feature_count_after
|
||||||
|
FROM pbf_render_compatibility
|
||||||
|
GROUP BY LEFT(render_layer, 100)
|
||||||
|
) AS after_counts
|
||||||
|
ON style_scope.style_layer = after_counts.style_layer
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE style_render_equivalence ADD KEY idx_status (render_status)")
|
||||||
|
cur.execute("ALTER TABLE style_render_equivalence ADD KEY idx_style_layer (style_layer)")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_detection_catalog_integrity(cur) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE detection_catalog_integrity AS
|
||||||
|
SELECT
|
||||||
|
d.detection_key,
|
||||||
|
SUM(d.feature_count) AS feature_count,
|
||||||
|
d.geom_type AS geometry_type,
|
||||||
|
CASE
|
||||||
|
WHEN d.detection_key IS NULL OR d.detection_key = '' THEN 'UNKNOWN_DETECTION_OBJECT'
|
||||||
|
WHEN COALESCE(object_meta.distinct_objects, 0) > 1 THEN 'UNKNOWN_DETECTION_OBJECT'
|
||||||
|
WHEN gc.geometry_consistency_status IS NOT NULL AND gc.geometry_consistency_status <> 'OK' THEN 'GEOMETRY_INCONSISTENT'
|
||||||
|
ELSE 'OK'
|
||||||
|
END AS catalog_status
|
||||||
|
FROM pbf_detection_catalog d
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
detection_key,
|
||||||
|
COUNT(DISTINCT canonical_object_type) AS distinct_objects
|
||||||
|
FROM pbf_detection_catalog
|
||||||
|
GROUP BY detection_key
|
||||||
|
) AS object_meta
|
||||||
|
ON d.detection_key = object_meta.detection_key
|
||||||
|
LEFT JOIN geometry_consistency gc
|
||||||
|
ON d.canonical_object_type = gc.canonical_object_type
|
||||||
|
AND d.geom_type = gc.geometry_type
|
||||||
|
GROUP BY
|
||||||
|
d.detection_key,
|
||||||
|
d.geom_type,
|
||||||
|
object_meta.distinct_objects,
|
||||||
|
gc.geometry_consistency_status
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE detection_catalog_integrity ADD KEY idx_status (catalog_status)")
|
||||||
|
cur.execute("ALTER TABLE detection_catalog_integrity ADD KEY idx_detection_key (detection_key(50))")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_spatial_anomalies(cur) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE spatial_anomalies AS
|
||||||
|
SELECT *
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
'OUT_OF_BOUNDS_TILE_INDEX' AS anomaly_type,
|
||||||
|
COUNT(*) AS affected_feature_count,
|
||||||
|
CONCAT('z=', MIN(z), '..', MAX(z), '; sample=', MIN(CONCAT(z, '/', x, '/', y))) AS region_hint,
|
||||||
|
'HIGH' AS severity
|
||||||
|
FROM pbf_relayer_candidates
|
||||||
|
WHERE z < 0
|
||||||
|
OR x < 0
|
||||||
|
OR y < 0
|
||||||
|
OR x >= POW(2, z)
|
||||||
|
OR y >= POW(2, z)
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'DENSITY_STATISTICS_ERROR' AS anomaly_type,
|
||||||
|
tile_rows.feature_count AS affected_feature_count,
|
||||||
|
CONCAT('z=', tile_rows.z, '; tile=', tile_rows.z, '/', tile_rows.x, '/', tile_rows.y) AS region_hint,
|
||||||
|
'HIGH' AS severity
|
||||||
|
FROM tile_density AS tile_rows
|
||||||
|
CROSS JOIN (
|
||||||
|
SELECT COUNT(*) AS total_features
|
||||||
|
FROM features
|
||||||
|
) AS dataset_stats
|
||||||
|
WHERE tile_rows.feature_count > dataset_stats.total_features
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN layer_hint.vt_layer IN ('L海底地形', 'L等深線', 'L概略等深線', 'L海底線', 'p底質')
|
||||||
|
THEN 'TILE_OVERDENSE_BATHYMETRY'
|
||||||
|
WHEN layer_hint.vt_layer IN ('L陸上構造物陸', 'P陸上構造物陸', 'P橋りょう等構造物', 'p陸上構造物')
|
||||||
|
THEN 'TILE_OVERDENSE_LAND_STRUCTURE'
|
||||||
|
WHEN layer_hint.vt_layer LIKE '%ククリ'
|
||||||
|
OR layer_hint.vt_layer IN ('P基本線', 'L基本線', 'P危険界ククリ', 'P投錨注意障害物ククリ')
|
||||||
|
THEN 'TILE_OVERDENSE_OUTLINE'
|
||||||
|
ELSE 'EXTREME_TILE_DENSITY'
|
||||||
|
END AS anomaly_type,
|
||||||
|
tile_rows.feature_count AS affected_feature_count,
|
||||||
|
CONCAT(
|
||||||
|
'z=',
|
||||||
|
tile_rows.z,
|
||||||
|
'; tile=',
|
||||||
|
tile_rows.z,
|
||||||
|
'/',
|
||||||
|
tile_rows.x,
|
||||||
|
'/',
|
||||||
|
tile_rows.y,
|
||||||
|
'; top_layer=',
|
||||||
|
COALESCE(layer_hint.vt_layer, '<unknown>'),
|
||||||
|
'; layer_features=',
|
||||||
|
COALESCE(layer_hint.feature_count, 0)
|
||||||
|
) AS region_hint,
|
||||||
|
'MEDIUM' AS severity
|
||||||
|
FROM tile_density AS tile_rows
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
z,
|
||||||
|
AVG(feature_count) AS avg_feature_count,
|
||||||
|
STD(feature_count) AS std_feature_count
|
||||||
|
FROM tile_density
|
||||||
|
GROUP BY z
|
||||||
|
) AS zoom_stats
|
||||||
|
ON tile_rows.z = zoom_stats.z
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
winners.z,
|
||||||
|
winners.x,
|
||||||
|
winners.y,
|
||||||
|
MIN(winners.vt_layer) AS vt_layer,
|
||||||
|
winners.feature_count
|
||||||
|
FROM tile_layer_density AS winners
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
MAX(feature_count) AS feature_count
|
||||||
|
FROM tile_layer_density
|
||||||
|
GROUP BY z, x, y
|
||||||
|
) AS maxima
|
||||||
|
ON winners.z = maxima.z
|
||||||
|
AND winners.x = maxima.x
|
||||||
|
AND winners.y = maxima.y
|
||||||
|
AND winners.feature_count = maxima.feature_count
|
||||||
|
GROUP BY
|
||||||
|
winners.z,
|
||||||
|
winners.x,
|
||||||
|
winners.y,
|
||||||
|
winners.feature_count
|
||||||
|
) AS layer_hint
|
||||||
|
ON tile_rows.z = layer_hint.z
|
||||||
|
AND tile_rows.x = layer_hint.x
|
||||||
|
AND tile_rows.y = layer_hint.y
|
||||||
|
WHERE tile_rows.z >= 1
|
||||||
|
AND tile_rows.feature_count > (
|
||||||
|
zoom_stats.avg_feature_count + (5 * COALESCE(zoom_stats.std_feature_count, 0))
|
||||||
|
)
|
||||||
|
) AS anomaly_union
|
||||||
|
WHERE affected_feature_count IS NOT NULL AND affected_feature_count > 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cur.execute("ALTER TABLE spatial_anomalies ADD KEY idx_severity (severity)")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_summary(cur) -> None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE semantic_validation_summary AS
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(*) FROM pbf_relayer_candidates) AS total_features_checked,
|
||||||
|
(SELECT COUNT(*) FROM classification_validation WHERE classification_status <> 'OK') AS classification_errors,
|
||||||
|
(SELECT COUNT(*) FROM geometry_consistency WHERE geometry_consistency_status <> 'OK') AS geometry_errors,
|
||||||
|
(SELECT COUNT(*) FROM style_render_equivalence WHERE render_status <> 'OK') AS render_equivalence_errors,
|
||||||
|
(SELECT COUNT(*) FROM detection_catalog_integrity WHERE catalog_status <> 'OK') AS detection_integrity_errors,
|
||||||
|
(SELECT COUNT(*) FROM spatial_anomalies) AS spatial_anomalies,
|
||||||
|
CASE
|
||||||
|
WHEN (SELECT COUNT(*) FROM style_render_equivalence WHERE render_status = 'RENDER_BREAK_RISK') > 0 THEN 'FAIL'
|
||||||
|
WHEN (SELECT COUNT(*) FROM classification_validation WHERE classification_status IN ('RULE_CONFLICT', 'UNKNOWN_OBJECT')) > 0 THEN 'FAIL'
|
||||||
|
WHEN (SELECT COUNT(*) FROM geometry_consistency WHERE geometry_consistency_status = 'UNEXPECTED_GEOMETRY') > 0 THEN 'FAIL'
|
||||||
|
WHEN (SELECT COUNT(*) FROM detection_catalog_integrity WHERE catalog_status <> 'OK') > 0 THEN 'WARNING'
|
||||||
|
WHEN (SELECT COUNT(*) FROM spatial_anomalies) > 0 THEN 'WARNING'
|
||||||
|
ELSE 'PASS'
|
||||||
|
END AS validation_status
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
SemanticOverlayValidator(DbConfig()).run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1349
navsea_tile_builder.py
Normal file
1349
navsea_tile_builder.py
Normal file
File diff suppressed because it is too large
Load Diff
140
report/classification_distribution.txt
Normal file
140
report/classification_distribution.txt
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
630 1258262
|
||||||
|
100 887065
|
||||||
|
130 358545
|
||||||
|
141 239851
|
||||||
|
120 185447
|
||||||
|
142 156922
|
||||||
|
590 99863
|
||||||
|
140 93628
|
||||||
|
148 87488
|
||||||
|
150 83499
|
||||||
|
145 74972
|
||||||
|
143 53198
|
||||||
|
200 44071
|
||||||
|
830 32781
|
||||||
|
420 31318
|
||||||
|
121 30090
|
||||||
|
610 26855
|
||||||
|
600 26206
|
||||||
|
123 25497
|
||||||
|
428 24683
|
||||||
|
122 24679
|
||||||
|
910 19025
|
||||||
|
160 18833
|
||||||
|
260 17027
|
||||||
|
220 16472
|
||||||
|
270 15681
|
||||||
|
230 14153
|
||||||
|
850 13939
|
||||||
|
250 13824
|
||||||
|
710 12817
|
||||||
|
280 12484
|
||||||
|
240 12032
|
||||||
|
820 11975
|
||||||
|
291 11884
|
||||||
|
290 10829
|
||||||
|
650 10818
|
||||||
|
404 10366
|
||||||
|
681 9686
|
||||||
|
125 9367
|
||||||
|
620 8765
|
||||||
|
175 8493
|
||||||
|
640 8123
|
||||||
|
292 5499
|
||||||
|
860 5030
|
||||||
|
151 4554
|
||||||
|
170 4319
|
||||||
|
425 4175
|
||||||
|
800 3873
|
||||||
|
900 3570
|
||||||
|
403 3323
|
||||||
|
520 3169
|
||||||
|
840 3128
|
||||||
|
661 2914
|
||||||
|
810 2607
|
||||||
|
293 2540
|
||||||
|
152 2446
|
||||||
|
421 2356
|
||||||
|
422 2206
|
||||||
|
432 2119
|
||||||
|
124 1761
|
||||||
|
146 1720
|
||||||
|
741 1696
|
||||||
|
870 1665
|
||||||
|
754 1487
|
||||||
|
732 1455
|
||||||
|
144 1388
|
||||||
|
742 1251
|
||||||
|
294 1248
|
||||||
|
402 1178
|
||||||
|
161 1107
|
||||||
|
176 1097
|
||||||
|
162 990
|
||||||
|
149 988
|
||||||
|
409 824
|
||||||
|
415 811
|
||||||
|
171 780
|
||||||
|
433 756
|
||||||
|
711 744
|
||||||
|
434 715
|
||||||
|
712 655
|
||||||
|
427 637
|
||||||
|
413 626
|
||||||
|
505 610
|
||||||
|
295 549
|
||||||
|
530 539
|
||||||
|
880 500
|
||||||
|
510 454
|
||||||
|
660 436
|
||||||
|
739 429
|
||||||
|
720 396
|
||||||
|
719 380
|
||||||
|
755 377
|
||||||
|
426 308
|
||||||
|
662 289
|
||||||
|
759 262
|
||||||
|
750 260
|
||||||
|
756 257
|
||||||
|
698 231
|
||||||
|
550 203
|
||||||
|
682 181
|
||||||
|
147 150
|
||||||
|
753 146
|
||||||
|
748 145
|
||||||
|
749 140
|
||||||
|
724 132
|
||||||
|
412 125
|
||||||
|
713 108
|
||||||
|
431 107
|
||||||
|
221 99
|
||||||
|
725 95
|
||||||
|
424 92
|
||||||
|
500 88
|
||||||
|
746 86
|
||||||
|
747 85
|
||||||
|
745 84
|
||||||
|
429 82
|
||||||
|
743 81
|
||||||
|
408 73
|
||||||
|
760 69
|
||||||
|
714 67
|
||||||
|
761 64
|
||||||
|
730 54
|
||||||
|
740 51
|
||||||
|
721 46
|
||||||
|
757 45
|
||||||
|
540 44
|
||||||
|
723 44
|
||||||
|
410 43
|
||||||
|
680 32
|
||||||
|
405 31
|
||||||
|
210 28
|
||||||
|
414 20
|
||||||
|
758 17
|
||||||
|
700 14
|
||||||
|
406 10
|
||||||
|
702 10
|
||||||
|
890 10
|
||||||
|
401 8
|
||||||
|
411 1
|
||||||
|
430 1
|
||||||
80
report/geometry_summary.txt
Normal file
80
report/geometry_summary.txt
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
L700 LineString 14
|
||||||
|
L702 LineString 10
|
||||||
|
L725 LineString 95
|
||||||
|
L739 LineString 424
|
||||||
|
L739 MultiLineString 5
|
||||||
|
L740 LineString 51
|
||||||
|
L741 LineString 1665
|
||||||
|
L741 MultiLineString 31
|
||||||
|
L748 LineString 145
|
||||||
|
L749 LineString 140
|
||||||
|
L危険界 LineString 47
|
||||||
|
L基本線 LineString 53612
|
||||||
|
L基本線 MultiLineString 3
|
||||||
|
L概略等深線 LineString 5497
|
||||||
|
L概略等深線 MultiLineString 54
|
||||||
|
L海底地形 LineString 1866386
|
||||||
|
L海底地形 MultiLineString 208786
|
||||||
|
L海底線 LineString 14271
|
||||||
|
L海底線 MultiLineString 120
|
||||||
|
L等深線 LineString 437566
|
||||||
|
L等深線 MultiLineString 7485
|
||||||
|
L航路 LineString 116
|
||||||
|
L陸上構造物陸 LineString 1256228
|
||||||
|
L陸上構造物陸 MultiLineString 2034
|
||||||
|
L高さ制限 LineString 1028
|
||||||
|
P721ククリ LineString 24
|
||||||
|
P721ククリ MultiLineString 3
|
||||||
|
P730ククリ LineString 18
|
||||||
|
P730ククリ MultiLineString 9
|
||||||
|
P754ククリ LineString 730
|
||||||
|
P754ククリ MultiLineString 41
|
||||||
|
pパイロットステーション Point 88
|
||||||
|
P危険界ククリ LineString 51743
|
||||||
|
P危険界ククリ MultiLineString 219
|
||||||
|
p地名 Point 36247
|
||||||
|
p地名陸 Point 35388
|
||||||
|
P基本線 MultiPolygon 99559
|
||||||
|
P基本線 Polygon 602066
|
||||||
|
P基本線ククリ LineString 1074484
|
||||||
|
P基本線ククリ MultiLineString 6296
|
||||||
|
p底質 Point 99863
|
||||||
|
P投錨注意障害物 MultiPolygon 16
|
||||||
|
p投錨注意障害物 Point 49907
|
||||||
|
P投錨注意障害物 Polygon 8703
|
||||||
|
P投錨注意障害物ククリ LineString 8465
|
||||||
|
P投錨注意障害物ククリ MultiLineString 278
|
||||||
|
p施設・境界線等 Point 5019
|
||||||
|
P施設・境界線等 Polygon 1406
|
||||||
|
P施設・境界線等ククリ LineString 521
|
||||||
|
P施設・境界線等ククリ MultiLineString 45
|
||||||
|
P施設・境界線等透明 Polygon 27
|
||||||
|
P橋りょう等構造物 MultiPolygon 67
|
||||||
|
P橋りょう等構造物 Polygon 25708
|
||||||
|
P漁具定置箇所 MultiPolygon 270
|
||||||
|
P漁具定置箇所 Polygon 22325
|
||||||
|
P潜堤 MultiPolygon 101
|
||||||
|
P潜堤 Polygon 3780
|
||||||
|
P穴 MultiPolygon 5463
|
||||||
|
P穴 Polygon 361754
|
||||||
|
p航行危険障害物 Point 19268
|
||||||
|
P航行危険障害物 Polygon 159
|
||||||
|
P航行危険障害物ククリ LineString 191
|
||||||
|
P航行危険障害物ククリ MultiLineString 7
|
||||||
|
P航路 MultiPolygon 3
|
||||||
|
P航路 Polygon 349
|
||||||
|
P航路ククリ LineString 755
|
||||||
|
P航路ククリ MultiLineString 109
|
||||||
|
p航路境界等 Point 395
|
||||||
|
p航路標識群 Point 38520
|
||||||
|
P誘導線ククリ LineString 101
|
||||||
|
p錨泊地等 Point 366
|
||||||
|
P錨泊地等 Polygon 308
|
||||||
|
P錨泊地等ククリ LineString 240
|
||||||
|
P錨泊地等ククリ MultiLineString 57
|
||||||
|
p陸上構造物 Point 32708
|
||||||
|
P陸上構造物陸 MultiPolygon 42
|
||||||
|
P陸上構造物陸 Polygon 35580
|
||||||
|
P陸域 MultiPolygon 5541
|
||||||
|
P陸域 Polygon 193114
|
||||||
|
p高さ制限 Point 858
|
||||||
204
report/layer_properties.txt
Normal file
204
report/layer_properties.txt
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
L700 at 14
|
||||||
|
L700 fid 14
|
||||||
|
L700 vt_layer 14
|
||||||
|
L700 分類番号 14
|
||||||
|
L702 at 10
|
||||||
|
L702 fid 10
|
||||||
|
L702 vt_layer 10
|
||||||
|
L702 分類番号 10
|
||||||
|
L725 at 95
|
||||||
|
L725 fid 95
|
||||||
|
L725 vt_layer 95
|
||||||
|
L725 分類番号 95
|
||||||
|
L739 at 429
|
||||||
|
L739 fid 429
|
||||||
|
L739 vt_layer 429
|
||||||
|
L739 分類番号 429
|
||||||
|
L740 at 51
|
||||||
|
L740 fid 51
|
||||||
|
L740 vt_layer 51
|
||||||
|
L740 分類番号 51
|
||||||
|
L741 at 1696
|
||||||
|
L741 fid 1696
|
||||||
|
L741 vt_layer 1696
|
||||||
|
L741 分類番号 1696
|
||||||
|
L748 at 145
|
||||||
|
L748 fid 145
|
||||||
|
L748 vt_layer 145
|
||||||
|
L748 分類番号 145
|
||||||
|
L749 at 140
|
||||||
|
L749 fid 140
|
||||||
|
L749 vt_layer 140
|
||||||
|
L749 分類番号 140
|
||||||
|
L危険界 at 47
|
||||||
|
L危険界 fid 47
|
||||||
|
L危険界 vt_layer 47
|
||||||
|
L危険界 分類番号 47
|
||||||
|
L基本線 at 53615
|
||||||
|
L基本線 fid 53615
|
||||||
|
L基本線 vt_layer 53615
|
||||||
|
L基本線 分類番号 53615
|
||||||
|
L概略等深線 at 5551
|
||||||
|
L概略等深線 fid 5551
|
||||||
|
L概略等深線 vt_layer 5551
|
||||||
|
L概略等深線 分類番号 5551
|
||||||
|
L概略等深線 高さ/深度(m) 5551
|
||||||
|
L海底地形 fid 2075172
|
||||||
|
L海底地形 vt_layer 2075172
|
||||||
|
L海底地形 水深値(m) 2075172
|
||||||
|
L海底線 at 14391
|
||||||
|
L海底線 fid 14391
|
||||||
|
L海底線 vt_layer 14391
|
||||||
|
L海底線 分類番号 14391
|
||||||
|
L等深線 at 445051
|
||||||
|
L等深線 fid 445051
|
||||||
|
L等深線 vt_layer 445051
|
||||||
|
L等深線 分類番号 445051
|
||||||
|
L等深線 高さ/深度(m) 445051
|
||||||
|
L航路 at 116
|
||||||
|
L航路 fid 116
|
||||||
|
L航路 vt_layer 116
|
||||||
|
L航路 分類番号 116
|
||||||
|
L陸上構造物陸 at 1258262
|
||||||
|
L陸上構造物陸 fid 1258262
|
||||||
|
L陸上構造物陸 vt_layer 1258262
|
||||||
|
L陸上構造物陸 分類番号 1258262
|
||||||
|
L高さ制限 at 1028
|
||||||
|
L高さ制限 fid 1028
|
||||||
|
L高さ制限 vt_layer 1028
|
||||||
|
L高さ制限 分類番号 1028
|
||||||
|
P721ククリ fid 27
|
||||||
|
P721ククリ vt_layer 27
|
||||||
|
P721ククリ 分類番号 27
|
||||||
|
P730ククリ fid 27
|
||||||
|
P730ククリ vt_layer 27
|
||||||
|
P730ククリ 分類番号 27
|
||||||
|
P754ククリ fid 771
|
||||||
|
P754ククリ vt_layer 771
|
||||||
|
P754ククリ 分類番号 771
|
||||||
|
pパイロットステーション at 88
|
||||||
|
pパイロットステーション fid 88
|
||||||
|
pパイロットステーション vt_layer 88
|
||||||
|
pパイロットステーション 分類番号 88
|
||||||
|
P危険界ククリ fid 51962
|
||||||
|
P危険界ククリ vt_layer 51962
|
||||||
|
P危険界ククリ 分類番号 51962
|
||||||
|
p地名 fid 36247
|
||||||
|
p地名 vt_layer 36247
|
||||||
|
p地名 分類番号 36247
|
||||||
|
p地名 日本語地名 36247
|
||||||
|
p地名 縮尺選択コード 36247
|
||||||
|
p地名 英文字地名 18969
|
||||||
|
p地名 表示重要度 36247
|
||||||
|
p地名陸 fid 35388
|
||||||
|
p地名陸 vt_layer 35388
|
||||||
|
p地名陸 分類番号 35388
|
||||||
|
p地名陸 日本語地名 35388
|
||||||
|
p地名陸 縮尺選択コード 35388
|
||||||
|
p地名陸 英文字地名 29131
|
||||||
|
p地名陸 表示重要度 35388
|
||||||
|
P基本線 at 701625
|
||||||
|
P基本線 fid 701625
|
||||||
|
P基本線 vt_layer 701625
|
||||||
|
P基本線 分類番号 701625
|
||||||
|
P基本線ククリ fid 1080780
|
||||||
|
P基本線ククリ vt_layer 1080780
|
||||||
|
P基本線ククリ 分類番号 1080780
|
||||||
|
p底質 at 99863
|
||||||
|
p底質 fid 99863
|
||||||
|
p底質 vt_layer 99863
|
||||||
|
p底質 分類番号 99863
|
||||||
|
p底質 名称 99863
|
||||||
|
p底質 表示位置 99863
|
||||||
|
p投錨注意障害物 at 54700
|
||||||
|
p投錨注意障害物 fid 58626
|
||||||
|
p投錨注意障害物 vt_layer 58626
|
||||||
|
p投錨注意障害物 分類番号 58626
|
||||||
|
P投錨注意障害物ククリ fid 8743
|
||||||
|
P投錨注意障害物ククリ vt_layer 8743
|
||||||
|
P投錨注意障害物ククリ 分類番号 8743
|
||||||
|
p施設・境界線等 at 6425
|
||||||
|
p施設・境界線等 fid 6425
|
||||||
|
p施設・境界線等 Sガイドページ 1752
|
||||||
|
p施設・境界線等 vt_layer 6425
|
||||||
|
p施設・境界線等 分類番号 6425
|
||||||
|
p施設・境界線等 名称 5019
|
||||||
|
P施設・境界線等ククリ fid 566
|
||||||
|
P施設・境界線等ククリ vt_layer 566
|
||||||
|
P施設・境界線等ククリ 分類番号 566
|
||||||
|
P施設・境界線等透明 at 27
|
||||||
|
P施設・境界線等透明 fid 27
|
||||||
|
P施設・境界線等透明 vt_layer 27
|
||||||
|
P施設・境界線等透明 分類番号 27
|
||||||
|
P橋りょう等構造物 at 25775
|
||||||
|
P橋りょう等構造物 fid 25775
|
||||||
|
P橋りょう等構造物 vt_layer 25775
|
||||||
|
P橋りょう等構造物 分類番号 25775
|
||||||
|
P漁具定置箇所 at 22595
|
||||||
|
P漁具定置箇所 fid 22595
|
||||||
|
P漁具定置箇所 vt_layer 22595
|
||||||
|
P漁具定置箇所 分類番号 22595
|
||||||
|
P潜堤 at 3881
|
||||||
|
P潜堤 fid 3881
|
||||||
|
P潜堤 vt_layer 3881
|
||||||
|
P潜堤 分類番号 3881
|
||||||
|
P穴 fid 367217
|
||||||
|
P穴 vt_layer 367217
|
||||||
|
p航行危険障害物 at 19403
|
||||||
|
p航行危険障害物 fid 19427
|
||||||
|
p航行危険障害物 vt_layer 19427
|
||||||
|
p航行危険障害物 分類番号 19427
|
||||||
|
P航行危険障害物ククリ fid 198
|
||||||
|
P航行危険障害物ククリ vt_layer 198
|
||||||
|
P航行危険障害物ククリ 分類番号 198
|
||||||
|
P航路 at 352
|
||||||
|
P航路 fid 352
|
||||||
|
P航路 vt_layer 352
|
||||||
|
P航路 分類番号 352
|
||||||
|
P航路ククリ fid 864
|
||||||
|
P航路ククリ vt_layer 864
|
||||||
|
P航路ククリ 分類番号 864
|
||||||
|
p航路境界等 at 377
|
||||||
|
p航路境界等 fid 395
|
||||||
|
p航路境界等 vt_layer 395
|
||||||
|
p航路境界等 分類番号 395
|
||||||
|
p航路境界等 角度 395
|
||||||
|
p航路標識群 at 38520
|
||||||
|
p航路標識群 fid 38520
|
||||||
|
p航路標識群 vt_layer 38520
|
||||||
|
p航路標識群 ローマ字名称 16765
|
||||||
|
p航路標識群 名称 17611
|
||||||
|
p航路標識群 名称補助 3559
|
||||||
|
p航路標識群 形状分類番号 38520
|
||||||
|
p航路標識群 明弧/分孤 1072
|
||||||
|
p航路標識群 灯略記 33446
|
||||||
|
p航路標識群 灯色 38520
|
||||||
|
p航路標識群 目的分類番号 38520
|
||||||
|
p航路標識群 表示位置 38520
|
||||||
|
p航路標識群 表示用番号 38520
|
||||||
|
P誘導線ククリ fid 101
|
||||||
|
P誘導線ククリ vt_layer 101
|
||||||
|
P誘導線ククリ 分類番号 101
|
||||||
|
P錨泊地等 at 453
|
||||||
|
P錨泊地等 fid 674
|
||||||
|
P錨泊地等 vt_layer 674
|
||||||
|
P錨泊地等 分類番号 674
|
||||||
|
P錨泊地等ククリ fid 297
|
||||||
|
P錨泊地等ククリ vt_layer 297
|
||||||
|
P錨泊地等ククリ 分類番号 297
|
||||||
|
p陸上構造物 at 32708
|
||||||
|
p陸上構造物 fid 32708
|
||||||
|
p陸上構造物 vt_layer 32708
|
||||||
|
p陸上構造物 分類番号 32708
|
||||||
|
P陸上構造物陸 at 35622
|
||||||
|
P陸上構造物陸 fid 35622
|
||||||
|
P陸上構造物陸 vt_layer 35622
|
||||||
|
P陸上構造物陸 分類番号 35622
|
||||||
|
P陸域 fid 198655
|
||||||
|
P陸域 vt_layer 198655
|
||||||
|
P陸域 分類番号 198655
|
||||||
|
p高さ制限 fid 858
|
||||||
|
p高さ制限 vt_layer 858
|
||||||
|
p高さ制限 分類番号 858
|
||||||
|
p高さ制限 名称 344
|
||||||
|
p高さ制限 高さ(m) 858
|
||||||
49
report/layer_summary.txt
Normal file
49
report/layer_summary.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
L海底地形 2075172
|
||||||
|
L陸上構造物陸 1258262
|
||||||
|
P基本線ククリ 1080780
|
||||||
|
P基本線 701625
|
||||||
|
L等深線 445051
|
||||||
|
P穴 367217
|
||||||
|
P陸域 198655
|
||||||
|
p底質 99863
|
||||||
|
p投錨注意障害物 58626
|
||||||
|
L基本線 53615
|
||||||
|
P危険界ククリ 51962
|
||||||
|
p航路標識群 38520
|
||||||
|
p地名 36247
|
||||||
|
P陸上構造物陸 35622
|
||||||
|
p地名陸 35388
|
||||||
|
p陸上構造物 32708
|
||||||
|
P橋りょう等構造物 25775
|
||||||
|
P漁具定置箇所 22595
|
||||||
|
p航行危険障害物 19427
|
||||||
|
L海底線 14391
|
||||||
|
P投錨注意障害物ククリ 8743
|
||||||
|
p施設・境界線等 6425
|
||||||
|
L概略等深線 5551
|
||||||
|
P潜堤 3881
|
||||||
|
L741 1696
|
||||||
|
L高さ制限 1028
|
||||||
|
P航路ククリ 864
|
||||||
|
p高さ制限 858
|
||||||
|
P754ククリ 771
|
||||||
|
P錨泊地等 674
|
||||||
|
P施設・境界線等ククリ 566
|
||||||
|
L739 429
|
||||||
|
p航路境界等 395
|
||||||
|
P航路 352
|
||||||
|
P錨泊地等ククリ 297
|
||||||
|
P航行危険障害物ククリ 198
|
||||||
|
L748 145
|
||||||
|
L749 140
|
||||||
|
L航路 116
|
||||||
|
P誘導線ククリ 101
|
||||||
|
L725 95
|
||||||
|
pパイロットステーション 88
|
||||||
|
L740 51
|
||||||
|
L危険界 47
|
||||||
|
P730ククリ 27
|
||||||
|
P721ククリ 27
|
||||||
|
P施設・境界線等透明 27
|
||||||
|
L700 14
|
||||||
|
L702 10
|
||||||
23
report/property_keys.txt
Normal file
23
report/property_keys.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
vt_layer 6685117
|
||||||
|
fid 6685117
|
||||||
|
分類番号 4204208
|
||||||
|
at 2823055
|
||||||
|
水深値(m) 2075172
|
||||||
|
高さ/深度(m) 450602
|
||||||
|
表示位置 138383
|
||||||
|
名称 122837
|
||||||
|
縮尺選択コード 71635
|
||||||
|
日本語地名 71635
|
||||||
|
表示重要度 71635
|
||||||
|
英文字地名 48100
|
||||||
|
表示用番号 38520
|
||||||
|
灯色 38520
|
||||||
|
目的分類番号 38520
|
||||||
|
形状分類番号 38520
|
||||||
|
灯略記 33446
|
||||||
|
ローマ字名称 16765
|
||||||
|
名称補助 3559
|
||||||
|
Sガイドページ 1752
|
||||||
|
明弧/分孤 1072
|
||||||
|
高さ(m) 858
|
||||||
|
角度 395
|
||||||
100
report/tile_density.txt
Normal file
100
report/tile_density.txt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
7 110 51 32300
|
||||||
|
6 55 25 25153
|
||||||
|
7 111 51 21202
|
||||||
|
5 27 12 20867
|
||||||
|
7 111 50 16595
|
||||||
|
8 222 102 16494
|
||||||
|
11 1750 869 14203
|
||||||
|
8 220 103 13548
|
||||||
|
11 1794 814 13256
|
||||||
|
5 28 12 13157
|
||||||
|
11 1794 813 13068
|
||||||
|
11 1793 813 12948
|
||||||
|
11 1759 815 12137
|
||||||
|
11 1777 815 11981
|
||||||
|
11 1760 813 11881
|
||||||
|
11 1736 878 11198
|
||||||
|
11 1802 810 10984
|
||||||
|
7 109 54 10901
|
||||||
|
7 112 51 10530
|
||||||
|
11 1815 790 10515
|
||||||
|
11 1785 815 10512
|
||||||
|
7 112 50 10308
|
||||||
|
8 220 102 10217
|
||||||
|
6 56 25 10198
|
||||||
|
8 218 108 9803
|
||||||
|
11 1781 815 9684
|
||||||
|
7 109 51 9655
|
||||||
|
11 1792 813 9357
|
||||||
|
11 1845 752 9303
|
||||||
|
9 444 204 8858
|
||||||
|
11 1761 823 8657
|
||||||
|
11 1780 817 8365
|
||||||
|
11 1829 783 8304
|
||||||
|
11 1818 809 8031
|
||||||
|
11 1802 816 8004
|
||||||
|
11 1751 868 7873
|
||||||
|
7 113 50 7820
|
||||||
|
8 223 101 7614
|
||||||
|
11 1786 815 7600
|
||||||
|
7 114 48 7588
|
||||||
|
7 110 52 7577
|
||||||
|
11 1831 780 7476
|
||||||
|
7 109 53 7472
|
||||||
|
11 1768 818 7452
|
||||||
|
8 228 97 7437
|
||||||
|
11 1761 824 7433
|
||||||
|
11 1729 881 7388
|
||||||
|
9 440 205 7366
|
||||||
|
11 1796 821 7328
|
||||||
|
11 1824 761 7293
|
||||||
|
11 1763 830 7277
|
||||||
|
5 27 13 7181
|
||||||
|
11 1781 816 7147
|
||||||
|
8 224 102 7108
|
||||||
|
8 219 103 7023
|
||||||
|
8 221 102 7018
|
||||||
|
11 1826 787 6960
|
||||||
|
11 1783 821 6912
|
||||||
|
12 3659 1567 6889
|
||||||
|
11 1777 823 6873
|
||||||
|
11 1801 811 6862
|
||||||
|
11 1777 816 6848
|
||||||
|
11 1791 813 6773
|
||||||
|
11 1818 808 6717
|
||||||
|
9 437 217 6661
|
||||||
|
12 3520 1626 6639
|
||||||
|
11 1830 782 6622
|
||||||
|
11 1790 812 6591
|
||||||
|
11 1784 814 6566
|
||||||
|
11 1757 826 6563
|
||||||
|
11 1778 816 6550
|
||||||
|
11 1803 798 6488
|
||||||
|
11 1781 807 6424
|
||||||
|
12 3587 1626 6414
|
||||||
|
11 1782 822 6394
|
||||||
|
11 1786 814 6371
|
||||||
|
11 1819 806 6289
|
||||||
|
11 1829 755 6237
|
||||||
|
11 1780 816 6231
|
||||||
|
11 1800 816 6212
|
||||||
|
11 1798 803 6184
|
||||||
|
12 3586 1626 6156
|
||||||
|
9 439 206 6111
|
||||||
|
8 219 107 6084
|
||||||
|
11 1778 818 6074
|
||||||
|
11 1757 825 6063
|
||||||
|
11 1804 799 6057
|
||||||
|
11 1750 868 5961
|
||||||
|
12 3500 1738 5955
|
||||||
|
7 114 49 5942
|
||||||
|
11 1797 820 5918
|
||||||
|
12 3589 1628 5917
|
||||||
|
12 3555 1631 5852
|
||||||
|
9 457 195 5849
|
||||||
|
11 1824 769 5796
|
||||||
|
12 3665 1727 5740
|
||||||
|
11 1793 815 5727
|
||||||
|
12 3649 1523 5715
|
||||||
|
12 3588 1626 5697
|
||||||
|
11 1832 863 5684
|
||||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
requests>=2.31
|
||||||
|
numpy>=1.26
|
||||||
|
xarray>=2024.6
|
||||||
|
cfgrib>=0.9.14
|
||||||
|
eccodes>=1.7
|
||||||
|
mercantile>=1.2
|
||||||
|
mapbox-vector-tile>=2.1
|
||||||
|
PyMySQL>=1.1
|
||||||
211
src/gfs_downloader.py
Normal file
211
src/gfs_downloader.py
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
|
||||||
|
requests = None
|
||||||
|
|
||||||
|
BASE_URL = "https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl"
|
||||||
|
WAVE_BASE_URL = "https://nomads.ncep.noaa.gov/cgi-bin/filter_gfswave.pl"
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
OUTPUT_DIR = PROJECT_ROOT / "data" / "grib"
|
||||||
|
REQUEST_TIMEOUT = (10, 120)
|
||||||
|
RETRY_LIMIT = 3
|
||||||
|
MIN_FILE_SIZE_BYTES = 1024
|
||||||
|
|
||||||
|
FORECAST_HOURS = [
|
||||||
|
0, 3, 6, 9, 12, 15, 18, 21, 24,
|
||||||
|
27, 30, 33, 36, 39, 42, 45,
|
||||||
|
48, 51, 54, 57, 60, 63, 66,
|
||||||
|
69, 72,
|
||||||
|
]
|
||||||
|
|
||||||
|
REGION = {
|
||||||
|
"leftlon": 120,
|
||||||
|
"rightlon": 150,
|
||||||
|
"toplat": 50,
|
||||||
|
"bottomlat": 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
ATMOS_VARIABLES = [
|
||||||
|
"UGRD",
|
||||||
|
"VGRD",
|
||||||
|
"APCP",
|
||||||
|
"PRMSL",
|
||||||
|
"TMP",
|
||||||
|
]
|
||||||
|
|
||||||
|
WAVE_VARIABLES = [
|
||||||
|
"HTSGW",
|
||||||
|
"DIRPW",
|
||||||
|
"PERPW",
|
||||||
|
]
|
||||||
|
|
||||||
|
ATMOS_LEVELS = [
|
||||||
|
"lev_10_m_above_ground",
|
||||||
|
"lev_surface",
|
||||||
|
"lev_mean_sea_level",
|
||||||
|
]
|
||||||
|
|
||||||
|
WAVE_LEVELS = [
|
||||||
|
"lev_surface",
|
||||||
|
]
|
||||||
|
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cycle(reference_time=None):
|
||||||
|
now = reference_time or datetime.now(timezone.utc)
|
||||||
|
# NOMADS availability usually lags the wall clock. Bias one cycle back.
|
||||||
|
candidate = now - timedelta(hours=5)
|
||||||
|
hour = (candidate.hour // 6) * 6
|
||||||
|
cycle_time = candidate.replace(hour=hour, minute=0, second=0, microsecond=0)
|
||||||
|
return cycle_time.strftime("%Y%m%d"), f"{cycle_time.hour:02d}"
|
||||||
|
|
||||||
|
|
||||||
|
def build_atmos_request(date, cycle, forecast_hour):
|
||||||
|
filename = f"gfs.t{cycle}z.pgrb2.0p25.f{forecast_hour}"
|
||||||
|
params = {
|
||||||
|
"file": filename,
|
||||||
|
"leftlon": REGION["leftlon"],
|
||||||
|
"rightlon": REGION["rightlon"],
|
||||||
|
"toplat": REGION["toplat"],
|
||||||
|
"bottomlat": REGION["bottomlat"],
|
||||||
|
"dir": f"/gfs.{date}/{cycle}/atmos",
|
||||||
|
}
|
||||||
|
|
||||||
|
for variable in ATMOS_VARIABLES:
|
||||||
|
params[f"var_{variable}"] = "on"
|
||||||
|
|
||||||
|
for level in ATMOS_LEVELS:
|
||||||
|
params[level] = "on"
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def build_wave_request(date, cycle, forecast_hour):
|
||||||
|
filename = f"gfswave.t{cycle}z.global.0p25.f{forecast_hour}.grib2"
|
||||||
|
params = {
|
||||||
|
"file": filename,
|
||||||
|
"leftlon": REGION["leftlon"],
|
||||||
|
"rightlon": REGION["rightlon"],
|
||||||
|
"toplat": REGION["toplat"],
|
||||||
|
"bottomlat": REGION["bottomlat"],
|
||||||
|
"dir": f"/gfs.{date}/{cycle}/wave/gridded",
|
||||||
|
}
|
||||||
|
|
||||||
|
for variable in WAVE_VARIABLES:
|
||||||
|
params[f"var_{variable}"] = "on"
|
||||||
|
|
||||||
|
for level in WAVE_LEVELS:
|
||||||
|
params[level] = "on"
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def validate_response(response):
|
||||||
|
content_type = response.headers.get("Content-Type", "").lower()
|
||||||
|
if "html" in content_type or "text/plain" in content_type:
|
||||||
|
preview = response.text[:200].strip().replace("\n", " ")
|
||||||
|
raise ValueError(f"unexpected response content type {content_type}: {preview}")
|
||||||
|
|
||||||
|
|
||||||
|
def is_fatal_network_error(error):
|
||||||
|
error_text = str(error)
|
||||||
|
fatal_markers = (
|
||||||
|
"NameResolutionError",
|
||||||
|
"Failed to resolve",
|
||||||
|
"Temporary failure in name resolution",
|
||||||
|
)
|
||||||
|
return any(marker in error_text for marker in fatal_markers)
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(session, base_url, params, output_path):
|
||||||
|
temp_path = output_path.with_suffix(".grib2.part")
|
||||||
|
|
||||||
|
if output_path.exists() and output_path.stat().st_size >= MIN_FILE_SIZE_BYTES:
|
||||||
|
print("skip", output_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
for attempt in range(1, RETRY_LIMIT + 1):
|
||||||
|
try:
|
||||||
|
print(f"downloading {output_path} (attempt {attempt}/{RETRY_LIMIT})")
|
||||||
|
with session.get(
|
||||||
|
base_url,
|
||||||
|
params=params,
|
||||||
|
stream=True,
|
||||||
|
timeout=REQUEST_TIMEOUT,
|
||||||
|
) as response:
|
||||||
|
response.raise_for_status()
|
||||||
|
validate_response(response)
|
||||||
|
|
||||||
|
with temp_path.open("wb") as file_handle:
|
||||||
|
for chunk in response.iter_content(1024 * 1024):
|
||||||
|
if chunk:
|
||||||
|
file_handle.write(chunk)
|
||||||
|
|
||||||
|
if temp_path.stat().st_size < MIN_FILE_SIZE_BYTES:
|
||||||
|
raise ValueError(f"downloaded file too small: {temp_path.stat().st_size} bytes")
|
||||||
|
|
||||||
|
temp_path.replace(output_path)
|
||||||
|
return
|
||||||
|
except (requests.RequestException, ValueError) as exc:
|
||||||
|
if temp_path.exists():
|
||||||
|
temp_path.unlink()
|
||||||
|
print(" download failed:", exc)
|
||||||
|
if is_fatal_network_error(exc):
|
||||||
|
raise RuntimeError("fatal network error while reaching NOAA") from exc
|
||||||
|
if attempt == RETRY_LIMIT:
|
||||||
|
raise
|
||||||
|
time.sleep(attempt * 2)
|
||||||
|
|
||||||
|
|
||||||
|
def download_forecast(session, date, cycle, forecast_hour):
|
||||||
|
atmos_path = OUTPUT_DIR / f"{date}_{cycle}_f{forecast_hour}.grib2"
|
||||||
|
wave_path = OUTPUT_DIR / f"{date}_{cycle}_f{forecast_hour}_wave.grib2"
|
||||||
|
|
||||||
|
download_file(
|
||||||
|
session,
|
||||||
|
BASE_URL,
|
||||||
|
build_atmos_request(date, cycle, forecast_hour),
|
||||||
|
atmos_path,
|
||||||
|
)
|
||||||
|
download_file(
|
||||||
|
session,
|
||||||
|
WAVE_BASE_URL,
|
||||||
|
build_wave_request(date, cycle, forecast_hour),
|
||||||
|
wave_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if requests is None:
|
||||||
|
raise RuntimeError("requests is required to download GFS data")
|
||||||
|
|
||||||
|
date, cycle = get_cycle()
|
||||||
|
print("cycle:", date, cycle)
|
||||||
|
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers["User-Agent"] = "weather-pipeline/1.0"
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
for forecast_hour in FORECAST_HOURS:
|
||||||
|
forecast_hour_str = f"{forecast_hour:03d}"
|
||||||
|
try:
|
||||||
|
download_forecast(session, date, cycle, forecast_hour_str)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
failures.append((forecast_hour_str, str(exc)))
|
||||||
|
break
|
||||||
|
except Exception as exc:
|
||||||
|
failures.append((forecast_hour_str, str(exc)))
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
for forecast_hour, error in failures:
|
||||||
|
print(f"failed forecast {forecast_hour}: {error}")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
189
src/grid_builder_v2.py
Normal file
189
src/grid_builder_v2.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import xarray as xr
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cfgrib
|
||||||
|
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
|
||||||
|
cfgrib = None
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
INPUT_DIR = PROJECT_ROOT / "data" / "grib"
|
||||||
|
OUTPUT_DIR = PROJECT_ROOT / "data" / "grid"
|
||||||
|
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
REGION = {
|
||||||
|
"lon_min": 120,
|
||||||
|
"lon_max": 150,
|
||||||
|
"lat_min": 20,
|
||||||
|
"lat_max": 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
VARIABLE_CANDIDATES = {
|
||||||
|
"u10": ("u10", "u"),
|
||||||
|
"v10": ("v10", "v"),
|
||||||
|
"tp": ("tp", "prate", "unknown"),
|
||||||
|
"msl": ("prmsl", "msl", "pres"),
|
||||||
|
"temp": ("t2m", "t"),
|
||||||
|
"wave_h": ("htsgw", "swh", "wvhgt"),
|
||||||
|
"wave_dir": ("dirpw", "mwd", "wvdir"),
|
||||||
|
"wave_period": ("perpw", "mwp", "wvper"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def compute_wind(u_component, v_component):
|
||||||
|
speed = np.sqrt(u_component ** 2 + v_component ** 2)
|
||||||
|
# Meteorological direction: where the wind comes from, in degrees clockwise from north.
|
||||||
|
direction = (270 - np.degrees(np.arctan2(v_component, u_component))) % 360
|
||||||
|
return speed, direction
|
||||||
|
|
||||||
|
|
||||||
|
def load_datasets(path):
|
||||||
|
if cfgrib is None:
|
||||||
|
raise RuntimeError("cfgrib is required to build grids from GRIB2 files")
|
||||||
|
with xr.set_options(use_new_combine_kwarg_defaults=True):
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings(
|
||||||
|
"ignore",
|
||||||
|
message="In a future version of xarray the default value for compat will change",
|
||||||
|
category=FutureWarning,
|
||||||
|
)
|
||||||
|
return cfgrib.xarray_store.open_datasets(str(path))
|
||||||
|
|
||||||
|
|
||||||
|
def find_variable(datasets, candidates):
|
||||||
|
for candidate in candidates:
|
||||||
|
for dataset in datasets:
|
||||||
|
if candidate in dataset:
|
||||||
|
return dataset[candidate]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_lat_lon(datasets):
|
||||||
|
for dataset in datasets:
|
||||||
|
if "latitude" in dataset and "longitude" in dataset:
|
||||||
|
return dataset["latitude"].values, dataset["longitude"].values
|
||||||
|
raise ValueError("no latitude/longitude coordinates found in GRIB datasets")
|
||||||
|
|
||||||
|
|
||||||
|
def to_2d_values(data_array, lat_size, lon_size):
|
||||||
|
if data_array is None:
|
||||||
|
return np.zeros((lat_size, lon_size), dtype=float)
|
||||||
|
|
||||||
|
values = np.asarray(data_array.squeeze().values)
|
||||||
|
if values.ndim != 2:
|
||||||
|
raise ValueError(f"expected 2D field, got shape {values.shape} for {data_array.name}")
|
||||||
|
if values.shape != (lat_size, lon_size):
|
||||||
|
raise ValueError(
|
||||||
|
f"field {data_array.name} shape {values.shape} does not match coordinates {(lat_size, lon_size)}"
|
||||||
|
)
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_longitudes(lon_values, fields):
|
||||||
|
lon_values = np.asarray(lon_values, dtype=float)
|
||||||
|
normalized_lon = ((lon_values + 180) % 360) - 180
|
||||||
|
sort_idx = np.argsort(normalized_lon)
|
||||||
|
normalized_lon = normalized_lon[sort_idx]
|
||||||
|
normalized_fields = [field[:, sort_idx] for field in fields]
|
||||||
|
return normalized_lon, normalized_fields
|
||||||
|
|
||||||
|
|
||||||
|
def select_region(lat_values, lon_values, fields):
|
||||||
|
lat_mask = (lat_values >= REGION["lat_min"]) & (lat_values <= REGION["lat_max"])
|
||||||
|
lon_mask = (lon_values >= REGION["lon_min"]) & (lon_values <= REGION["lon_max"])
|
||||||
|
|
||||||
|
lat_idx = np.where(lat_mask)[0]
|
||||||
|
lon_idx = np.where(lon_mask)[0]
|
||||||
|
if lat_idx.size == 0 or lon_idx.size == 0:
|
||||||
|
raise ValueError("selected region is empty after coordinate filtering")
|
||||||
|
|
||||||
|
return (
|
||||||
|
lat_values[lat_idx],
|
||||||
|
lon_values[lon_idx],
|
||||||
|
[field[np.ix_(lat_idx, lon_idx)] for field in fields],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(path):
|
||||||
|
datasets = load_datasets(path)
|
||||||
|
try:
|
||||||
|
wave_path = path.with_name(f"{path.stem}_wave.grib2")
|
||||||
|
wave_datasets = load_datasets(wave_path) if wave_path.exists() else []
|
||||||
|
all_datasets = [*datasets, *wave_datasets]
|
||||||
|
|
||||||
|
lat_values, lon_values = get_lat_lon(datasets)
|
||||||
|
lat_size = len(lat_values)
|
||||||
|
lon_size = len(lon_values)
|
||||||
|
|
||||||
|
u10 = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["u10"]), lat_size, lon_size)
|
||||||
|
v10 = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["v10"]), lat_size, lon_size)
|
||||||
|
rain = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["tp"]), lat_size, lon_size)
|
||||||
|
pressure = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["msl"]), lat_size, lon_size)
|
||||||
|
temp = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["temp"]), lat_size, lon_size)
|
||||||
|
wave_h = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["wave_h"]), lat_size, lon_size)
|
||||||
|
wave_dir = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["wave_dir"]), lat_size, lon_size)
|
||||||
|
wave_period = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["wave_period"]), lat_size, lon_size)
|
||||||
|
|
||||||
|
wind_speed, wind_dir = compute_wind(u10, v10)
|
||||||
|
|
||||||
|
lon_values, normalized_fields = normalize_longitudes(
|
||||||
|
lon_values,
|
||||||
|
[wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period],
|
||||||
|
)
|
||||||
|
wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period = normalized_fields
|
||||||
|
|
||||||
|
lat_region, lon_region, region_fields = select_region(
|
||||||
|
lat_values,
|
||||||
|
lon_values,
|
||||||
|
[wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period],
|
||||||
|
)
|
||||||
|
wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period = region_fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
"lat": lat_region.tolist(),
|
||||||
|
"lon": lon_region.tolist(),
|
||||||
|
"wind_speed": wind_speed.tolist(),
|
||||||
|
"wind_dir": wind_dir.tolist(),
|
||||||
|
"rain": rain.tolist(),
|
||||||
|
"temp": temp.tolist(),
|
||||||
|
"pressure": pressure.tolist(),
|
||||||
|
"wave_h": wave_h.tolist(),
|
||||||
|
"wave_dir": wave_dir.tolist(),
|
||||||
|
"wave_period": wave_period.tolist(),
|
||||||
|
}
|
||||||
|
finally:
|
||||||
|
for dataset in datasets:
|
||||||
|
dataset.close()
|
||||||
|
if 'wave_datasets' in locals():
|
||||||
|
for dataset in wave_datasets:
|
||||||
|
dataset.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for path in sorted(INPUT_DIR.glob("*.grib2")):
|
||||||
|
if path.stem.endswith("_wave"):
|
||||||
|
continue
|
||||||
|
print("processing", path.name)
|
||||||
|
try:
|
||||||
|
grid = process_file(path)
|
||||||
|
except Exception as exc:
|
||||||
|
print(" error processing", path.name, exc)
|
||||||
|
continue
|
||||||
|
|
||||||
|
output_path = OUTPUT_DIR / f"grid_{path.stem}.json"
|
||||||
|
payload = {
|
||||||
|
"time": path.stem,
|
||||||
|
"grid": grid,
|
||||||
|
}
|
||||||
|
with output_path.open("w", encoding="utf-8") as file_handle:
|
||||||
|
json.dump(payload, file_handle)
|
||||||
|
print("saved", output_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
104
src/pbf/audit_pbf_mysql.py
Normal file
104
src/pbf/audit_pbf_mysql.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import pymysql
|
||||||
|
import os
|
||||||
|
|
||||||
|
OUTPUT_DIR = "report"
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
conn = pymysql.connect(
|
||||||
|
host="localhost",
|
||||||
|
user="root",
|
||||||
|
password="2chi9ks2",
|
||||||
|
database="pbf_analysis",
|
||||||
|
charset="utf8mb4"
|
||||||
|
)
|
||||||
|
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
def write_file(name, rows):
|
||||||
|
|
||||||
|
path = os.path.join(OUTPUT_DIR, name)
|
||||||
|
|
||||||
|
with open(path,"w",encoding="utf8") as f:
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
f.write("\t".join([str(x) for x in r])+"\n")
|
||||||
|
|
||||||
|
print("write:",path)
|
||||||
|
|
||||||
|
|
||||||
|
print("Layer summary...")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT vt_layer, COUNT(*)
|
||||||
|
FROM features
|
||||||
|
GROUP BY vt_layer
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
""")
|
||||||
|
|
||||||
|
write_file("layer_summary.txt",cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
print("Geometry summary...")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT vt_layer, geom_type, COUNT(*)
|
||||||
|
FROM features
|
||||||
|
GROUP BY vt_layer, geom_type
|
||||||
|
ORDER BY vt_layer
|
||||||
|
""")
|
||||||
|
|
||||||
|
write_file("geometry_summary.txt",cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
print("Property keys...")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT k, COUNT(*)
|
||||||
|
FROM properties
|
||||||
|
GROUP BY k
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
""")
|
||||||
|
|
||||||
|
write_file("property_keys.txt",cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
print("Layer properties...")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT f.vt_layer, p.k, COUNT(*)
|
||||||
|
FROM properties p
|
||||||
|
JOIN features f ON p.feature_id=f.id
|
||||||
|
GROUP BY f.vt_layer,p.k
|
||||||
|
ORDER BY f.vt_layer
|
||||||
|
""")
|
||||||
|
|
||||||
|
write_file("layer_properties.txt",cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
print("Classification distribution...")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT v, COUNT(*)
|
||||||
|
FROM properties
|
||||||
|
WHERE k='分類番号'
|
||||||
|
GROUP BY v
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
""")
|
||||||
|
|
||||||
|
write_file("classification_distribution.txt",cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
print("Tile density...")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT z,x,y,COUNT(*)
|
||||||
|
FROM features
|
||||||
|
GROUP BY z,x,y
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
LIMIT 100
|
||||||
|
""")
|
||||||
|
|
||||||
|
write_file("tile_density.txt",cur.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
print("done.")
|
||||||
5652
src/pbf/style.json
Normal file
5652
src/pbf/style.json
Normal file
File diff suppressed because it is too large
Load Diff
5652
src/pbf/style.karatsu-10nm-compatible.json
Normal file
5652
src/pbf/style.karatsu-10nm-compatible.json
Normal file
File diff suppressed because it is too large
Load Diff
1892
src/pbf/style.karatsu-10nm-v2.json
Normal file
1892
src/pbf/style.karatsu-10nm-v2.json
Normal file
File diff suppressed because it is too large
Load Diff
5652
src/pbf/style.navsea-compatible.json
Normal file
5652
src/pbf/style.navsea-compatible.json
Normal file
File diff suppressed because it is too large
Load Diff
1391
src/pbf/style.navsea-delivery-karatsu-10nm.json
Normal file
1391
src/pbf/style.navsea-delivery-karatsu-10nm.json
Normal file
File diff suppressed because it is too large
Load Diff
1391
src/pbf/style.navsea-engineering-karatsu-10nm.json
Normal file
1391
src/pbf/style.navsea-engineering-karatsu-10nm.json
Normal file
File diff suppressed because it is too large
Load Diff
692
src/pbf/style.navsea-redesign.json
Normal file
692
src/pbf/style.navsea-redesign.json
Normal file
@@ -0,0 +1,692 @@
|
|||||||
|
{
|
||||||
|
"version": 8,
|
||||||
|
"name": "navsea-redesign",
|
||||||
|
"glyphs": "http://192.168.200.184/newpec/fonts/{fontstack}/{range}.pbf",
|
||||||
|
"sources": {
|
||||||
|
"navsea": {
|
||||||
|
"type": "vector",
|
||||||
|
"minzoom": 0,
|
||||||
|
"maxzoom": 12,
|
||||||
|
"tiles": [
|
||||||
|
"http://192.168.200.184/pbf/{z}/{x}/{y}.pbf"
|
||||||
|
],
|
||||||
|
"attribution": "NavSea semantic vector tiles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"id": "background",
|
||||||
|
"type": "background",
|
||||||
|
"paint": {
|
||||||
|
"background-color": "#dfe8d8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-area",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P陸域",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#d9cfab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-hole",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P穴",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#bfc8b1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "water-depth-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_family"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"surface"
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
"0-2m",
|
||||||
|
"#d8f3f6",
|
||||||
|
"0-5m",
|
||||||
|
"#d2eff4",
|
||||||
|
"2-5m",
|
||||||
|
"#c8eaf0",
|
||||||
|
"5-10m",
|
||||||
|
"#bce3ec",
|
||||||
|
"10-20m",
|
||||||
|
"#b0dbe8",
|
||||||
|
"20-100m",
|
||||||
|
"#a0d2e0",
|
||||||
|
"100-200m",
|
||||||
|
"#93c7d6",
|
||||||
|
"200-500m",
|
||||||
|
"#7eb7ca",
|
||||||
|
"500-1000m",
|
||||||
|
"#68a6bc",
|
||||||
|
"1000-2000m",
|
||||||
|
"#558eab",
|
||||||
|
"#dff3f7"
|
||||||
|
],
|
||||||
|
"fill-opacity": 0.9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shoal-hazard-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"==",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
"浅所危険界"
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#f2a65a",
|
||||||
|
"fill-opacity": 0.45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tidal-flat-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"==",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
"干潮帯"
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#d7d0b4",
|
||||||
|
"fill-opacity": 0.75
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "river-lake-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"河川域",
|
||||||
|
"湖沼域",
|
||||||
|
"陸上水域"
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#b8d8e8",
|
||||||
|
"fill-opacity": 0.85
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unsurveyed-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"==",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
"未測海域"
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#f7f0b8",
|
||||||
|
"fill-opacity": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coast-structure-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"防波堤",
|
||||||
|
"浮施設・桟橋",
|
||||||
|
"撤去跡"
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#8d8578",
|
||||||
|
"fill-opacity": 0.85
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bridge-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P橋りょう等構造物",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#736b5f",
|
||||||
|
"fill-opacity": 0.85
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fishery-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P漁具定置箇所",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#89b26f",
|
||||||
|
"fill-opacity": 0.45,
|
||||||
|
"fill-outline-color": "#56764a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-structure-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P陸上構造物陸",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#8e6f3e",
|
||||||
|
"fill-opacity": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coast-outline",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線ククリ",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#736b5f",
|
||||||
|
"line-width": 1.2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "danger-outline",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P危険界ククリ",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#c95c2a",
|
||||||
|
"line-width": 1.3,
|
||||||
|
"line-dasharray": [
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "anchor-danger-outline",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P投錨注意障害物ククリ",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#c03d3d",
|
||||||
|
"line-width": 1.1,
|
||||||
|
"line-dasharray": [
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bathymetry-lines",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L海底地形",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#7fa6b7",
|
||||||
|
"line-width": 0.5,
|
||||||
|
"line-opacity": 0.45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "depth-contours",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L等深線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#487b94",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
5,
|
||||||
|
0.3,
|
||||||
|
8,
|
||||||
|
0.6,
|
||||||
|
12,
|
||||||
|
1.1
|
||||||
|
],
|
||||||
|
"line-opacity": 0.85
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "subsea-lines",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L海底線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#8d4f9c",
|
||||||
|
"line-width": 1.2,
|
||||||
|
"line-dasharray": [
|
||||||
|
3,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coast-structure-lines",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L基本線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#60584d",
|
||||||
|
"line-width": 1.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-structure-lines",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L陸上構造物陸",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#805f2c",
|
||||||
|
"line-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "water-boundary-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L739",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#4b89a5",
|
||||||
|
"line-width": 1.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "regulatory-boundary-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L741",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#cf5b47",
|
||||||
|
"line-width": 1.2,
|
||||||
|
"line-dasharray": [
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "height-limit-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L高さ制限",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#b1466b",
|
||||||
|
"line-width": 1.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-area-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P投錨注意障害物",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#d85f4c",
|
||||||
|
"fill-opacity": 0.42,
|
||||||
|
"fill-outline-color": "#9b3324"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航行危険障害物",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
7,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"circle-color": "#d5452f",
|
||||||
|
"circle-stroke-color": "#fff4df",
|
||||||
|
"circle-stroke-width": 1.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "anchor-hazard-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p投錨注意障害物",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
7,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"circle-color": "#cf7d2b",
|
||||||
|
"circle-stroke-color": "#fff4df",
|
||||||
|
"circle-stroke-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nav-marks",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航路標識群",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
7,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"circle-color": "#f6ce4f",
|
||||||
|
"circle-stroke-color": "#313131",
|
||||||
|
"circle-stroke-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "facility-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p施設・境界線等",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
8,
|
||||||
|
2,
|
||||||
|
12,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"circle-color": "#4d6b86",
|
||||||
|
"circle-stroke-color": "#ffffff",
|
||||||
|
"circle-stroke-width": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "landmark-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p陸上構造物",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
8,
|
||||||
|
2,
|
||||||
|
12,
|
||||||
|
4.5
|
||||||
|
],
|
||||||
|
"circle-color": "#6b5840",
|
||||||
|
"circle-stroke-color": "#f7f1e2",
|
||||||
|
"circle-stroke-width": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bottom-material-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p底質",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": 1.5,
|
||||||
|
"circle-color": "#3f7183",
|
||||||
|
"circle-opacity": 0.55
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "height-limit-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p高さ制限",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": 3,
|
||||||
|
"circle-color": "#a83566",
|
||||||
|
"circle-stroke-color": "#ffffff",
|
||||||
|
"circle-stroke-width": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sea-place-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p地名",
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"日本語地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"英文字地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSerifCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
12,
|
||||||
|
14
|
||||||
|
],
|
||||||
|
"text-anchor": "center",
|
||||||
|
"text-max-width": 10
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#284857",
|
||||||
|
"text-halo-color": "#f6fbff",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-place-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p地名陸",
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"日本語地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"英文字地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSerifCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
12,
|
||||||
|
14
|
||||||
|
],
|
||||||
|
"text-anchor": "center",
|
||||||
|
"text-max-width": 10
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#3a3327",
|
||||||
|
"text-halo-color": "#fff9ef",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nav-object-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航路標識群",
|
||||||
|
"minzoom": 10,
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"名称"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSansCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": 11,
|
||||||
|
"text-offset": [
|
||||||
|
0,
|
||||||
|
1.1
|
||||||
|
],
|
||||||
|
"text-anchor": "top",
|
||||||
|
"text-max-width": 12
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#2f2f2f",
|
||||||
|
"text-halo-color": "#fff8e0",
|
||||||
|
"text-halo-width": 1.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航行危険障害物",
|
||||||
|
"minzoom": 10,
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"名称"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSansCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": 11,
|
||||||
|
"text-offset": [
|
||||||
|
0,
|
||||||
|
1.1
|
||||||
|
],
|
||||||
|
"text-anchor": "top"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#7f221a",
|
||||||
|
"text-halo-color": "#fff3eb",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
679
src/pbf/style.navsea-semantic-only.json
Normal file
679
src/pbf/style.navsea-semantic-only.json
Normal file
@@ -0,0 +1,679 @@
|
|||||||
|
{
|
||||||
|
"version": 8,
|
||||||
|
"name": "navsea-semantic-only",
|
||||||
|
"glyphs": "http://192.168.200.184/newpec/fonts/{fontstack}/{range}.pbf",
|
||||||
|
"sources": {
|
||||||
|
"navsea": {
|
||||||
|
"type": "vector",
|
||||||
|
"minzoom": 0,
|
||||||
|
"maxzoom": 12,
|
||||||
|
"tiles": [
|
||||||
|
"http://192.168.200.184/pbf/{z}/{x}/{y}.pbf"
|
||||||
|
],
|
||||||
|
"attribution": "NavSea semantic vector tiles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"id": "background",
|
||||||
|
"type": "background",
|
||||||
|
"paint": {
|
||||||
|
"background-color": "#edf3ef"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P陸域",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#d8cda8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hole-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P穴",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#c7cfbb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "surface-water",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"0-2m",
|
||||||
|
"0-5m",
|
||||||
|
"2-5m",
|
||||||
|
"5-10m",
|
||||||
|
"10-20m",
|
||||||
|
"20-100m",
|
||||||
|
"100-200m",
|
||||||
|
"200-500m",
|
||||||
|
"500-1000m",
|
||||||
|
"1000-2000m",
|
||||||
|
"2000-3000m",
|
||||||
|
"3000-4000m",
|
||||||
|
"4000-5000m",
|
||||||
|
"5000-6000m",
|
||||||
|
"6000-7000m",
|
||||||
|
"7000-8000m",
|
||||||
|
"8000-9000m",
|
||||||
|
"9000m以深",
|
||||||
|
"干潮帯",
|
||||||
|
"河川域",
|
||||||
|
"湖沼域",
|
||||||
|
"陸上水域"
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
"干潮帯",
|
||||||
|
"#d7cfb4",
|
||||||
|
"河川域",
|
||||||
|
"#afd8e7",
|
||||||
|
"湖沼域",
|
||||||
|
"#afd8e7",
|
||||||
|
"陸上水域",
|
||||||
|
"#afd8e7",
|
||||||
|
"0-2m",
|
||||||
|
"#d8f1f4",
|
||||||
|
"0-5m",
|
||||||
|
"#d0edf1",
|
||||||
|
"2-5m",
|
||||||
|
"#c4e7ed",
|
||||||
|
"5-10m",
|
||||||
|
"#b6dfe8",
|
||||||
|
"10-20m",
|
||||||
|
"#a8d6e1",
|
||||||
|
"20-100m",
|
||||||
|
"#96cbd8",
|
||||||
|
"100-200m",
|
||||||
|
"#84bfd0",
|
||||||
|
"200-500m",
|
||||||
|
"#71afc2",
|
||||||
|
"500-1000m",
|
||||||
|
"#629fb4",
|
||||||
|
"1000-2000m",
|
||||||
|
"#548ea4",
|
||||||
|
"2000-3000m",
|
||||||
|
"#497c93",
|
||||||
|
"3000-4000m",
|
||||||
|
"#3f6c84",
|
||||||
|
"4000-5000m",
|
||||||
|
"#365e75",
|
||||||
|
"5000-6000m",
|
||||||
|
"#2f5268",
|
||||||
|
"6000-7000m",
|
||||||
|
"#28475d",
|
||||||
|
"7000-8000m",
|
||||||
|
"#213d52",
|
||||||
|
"8000-9000m",
|
||||||
|
"#1d3448",
|
||||||
|
"#182c3f"
|
||||||
|
],
|
||||||
|
"fill-opacity": 0.88
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-area",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線",
|
||||||
|
"filter": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"浅所危険界",
|
||||||
|
"未測海域",
|
||||||
|
"浮施設・桟橋",
|
||||||
|
"防波堤",
|
||||||
|
"撤去跡"
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"paint": {
|
||||||
|
"fill-color": [
|
||||||
|
"match",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
],
|
||||||
|
"浅所危険界",
|
||||||
|
"#ee9f56",
|
||||||
|
"未測海域",
|
||||||
|
"#eadf9b",
|
||||||
|
"浮施設・桟橋",
|
||||||
|
"#88857d",
|
||||||
|
"防波堤",
|
||||||
|
"#7d796f",
|
||||||
|
"#9d7c67"
|
||||||
|
],
|
||||||
|
"fill-opacity": 0.45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fishery-area",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P漁具定置箇所",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#7db065",
|
||||||
|
"fill-opacity": 0.38,
|
||||||
|
"fill-outline-color": "#507541"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-polygon",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P投錨注意障害物",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#d15d49",
|
||||||
|
"fill-opacity": 0.4,
|
||||||
|
"fill-outline-color": "#8b3226"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bridge-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P橋りょう等構造物",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#71685b",
|
||||||
|
"fill-opacity": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-structure-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P陸上構造物陸",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#896d3d",
|
||||||
|
"fill-opacity": 0.82
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "submerged-fill",
|
||||||
|
"type": "fill",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P潜堤",
|
||||||
|
"paint": {
|
||||||
|
"fill-color": "#757d95",
|
||||||
|
"fill-opacity": 0.45
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "depth-lines",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L等深線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#447a94",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
5,
|
||||||
|
0.3,
|
||||||
|
8,
|
||||||
|
0.6,
|
||||||
|
12,
|
||||||
|
1.1
|
||||||
|
],
|
||||||
|
"line-opacity": 0.9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "major-depth-lines",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L概略等深線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#275f77",
|
||||||
|
"line-width": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
5,
|
||||||
|
0.5,
|
||||||
|
8,
|
||||||
|
0.9,
|
||||||
|
12,
|
||||||
|
1.5
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bathymetry-support",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L海底地形",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#8aadb9",
|
||||||
|
"line-width": 0.45,
|
||||||
|
"line-opacity": 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cable-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L海底線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#7d4a94",
|
||||||
|
"line-width": 1.2,
|
||||||
|
"line-dasharray": [
|
||||||
|
3,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coast-structure-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L基本線",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#635d53",
|
||||||
|
"line-width": 1.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-structure-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L陸上構造物陸",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#7b5d2e",
|
||||||
|
"line-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "boundary-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L739",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#4f8ba5",
|
||||||
|
"line-width": 1.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "regulatory-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L741",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#c95b46",
|
||||||
|
"line-width": 1.2,
|
||||||
|
"line-dasharray": [
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "height-limit-line",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "L高さ制限",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#a93a62",
|
||||||
|
"line-width": 1.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coast-outline",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P基本線ククリ",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#7d7566",
|
||||||
|
"line-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "danger-outline",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P危険界ククリ",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#c35f31",
|
||||||
|
"line-width": 1.2,
|
||||||
|
"line-dasharray": [
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "anchor-outline",
|
||||||
|
"type": "line",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "P投錨注意障害物ククリ",
|
||||||
|
"paint": {
|
||||||
|
"line-color": "#b64335",
|
||||||
|
"line-width": 1.1,
|
||||||
|
"line-dasharray": [
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航行危険障害物",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
7,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"circle-color": "#d1442f",
|
||||||
|
"circle-stroke-color": "#fff4ea",
|
||||||
|
"circle-stroke-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "anchor-hazard-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p投錨注意障害物",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
7,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"circle-color": "#d18a38",
|
||||||
|
"circle-stroke-color": "#fff6e7",
|
||||||
|
"circle-stroke-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nav-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航路標識群",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
7,
|
||||||
|
2,
|
||||||
|
10,
|
||||||
|
4,
|
||||||
|
12,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"circle-color": "#f1cb4d",
|
||||||
|
"circle-stroke-color": "#313131",
|
||||||
|
"circle-stroke-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "facility-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p施設・境界線等",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": 3.5,
|
||||||
|
"circle-color": "#4f6b86",
|
||||||
|
"circle-stroke-color": "#ffffff",
|
||||||
|
"circle-stroke-width": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bottom-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p底質",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": 1.8,
|
||||||
|
"circle-color": "#3d7182",
|
||||||
|
"circle-opacity": 0.65
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "landmark-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p陸上構造物",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": 3,
|
||||||
|
"circle-color": "#6a5640",
|
||||||
|
"circle-stroke-color": "#fff7eb",
|
||||||
|
"circle-stroke-width": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "height-points",
|
||||||
|
"type": "circle",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p高さ制限",
|
||||||
|
"paint": {
|
||||||
|
"circle-radius": 3,
|
||||||
|
"circle-color": "#aa365f",
|
||||||
|
"circle-stroke-color": "#ffffff",
|
||||||
|
"circle-stroke-width": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sea-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p地名",
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"日本語地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"英文字地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSerifCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
12,
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"text-anchor": "center",
|
||||||
|
"text-max-width": 10
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#264858",
|
||||||
|
"text-halo-color": "#f8fcff",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p地名陸",
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"日本語地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"英文字地名"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSerifCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": [
|
||||||
|
"interpolate",
|
||||||
|
[
|
||||||
|
"linear"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zoom"
|
||||||
|
],
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
12,
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"text-anchor": "center",
|
||||||
|
"text-max-width": 10
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#3a3228",
|
||||||
|
"text-halo-color": "#fff8ef",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nav-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航路標識群",
|
||||||
|
"minzoom": 10,
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"名称"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSansCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": 11,
|
||||||
|
"text-offset": [
|
||||||
|
0,
|
||||||
|
1.1
|
||||||
|
],
|
||||||
|
"text-anchor": "top"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#303030",
|
||||||
|
"text-halo-color": "#fff8df",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hazard-labels",
|
||||||
|
"type": "symbol",
|
||||||
|
"source": "navsea",
|
||||||
|
"source-layer": "p航行危険障害物",
|
||||||
|
"minzoom": 10,
|
||||||
|
"layout": {
|
||||||
|
"text-field": [
|
||||||
|
"coalesce",
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"名称"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"get",
|
||||||
|
"canonical_object_type"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text-font": [
|
||||||
|
"NotoSansCJK-Regular"
|
||||||
|
],
|
||||||
|
"text-size": 11,
|
||||||
|
"text-offset": [
|
||||||
|
0,
|
||||||
|
1.1
|
||||||
|
],
|
||||||
|
"text-anchor": "top"
|
||||||
|
},
|
||||||
|
"paint": {
|
||||||
|
"text-color": "#7c2219",
|
||||||
|
"text-halo-color": "#fff4ec",
|
||||||
|
"text-halo-width": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1391
src/pbf/style.navsea-v2.json
Normal file
1391
src/pbf/style.navsea-v2.json
Normal file
File diff suppressed because it is too large
Load Diff
2232
src/pbf/style.pruned.json
Normal file
2232
src/pbf/style.pruned.json
Normal file
File diff suppressed because it is too large
Load Diff
136
src/vector_tile_generator.py
Normal file
136
src/vector_tile_generator.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import os
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
import mapbox_vector_tile
|
||||||
|
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
|
||||||
|
mapbox_vector_tile = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import mercantile
|
||||||
|
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
|
||||||
|
mercantile = None
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
GRID_DIR = PROJECT_ROOT / "data" / "grid"
|
||||||
|
OUTPUT_DIR = Path("/home/wwwroot/weather")
|
||||||
|
|
||||||
|
DEFAULT_ZOOMS = [2, 4, 6, 8, 10, 12]
|
||||||
|
|
||||||
|
|
||||||
|
def get_zoom_levels():
|
||||||
|
raw_value = os.environ.get("WEATHER_TILE_ZOOMS", "")
|
||||||
|
if not raw_value.strip():
|
||||||
|
return DEFAULT_ZOOMS
|
||||||
|
|
||||||
|
zooms = []
|
||||||
|
for chunk in raw_value.split(","):
|
||||||
|
chunk = chunk.strip()
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
zoom = int(chunk)
|
||||||
|
if zoom < 0:
|
||||||
|
raise ValueError(f"Invalid zoom level: {zoom}")
|
||||||
|
zooms.append(zoom)
|
||||||
|
|
||||||
|
if not zooms:
|
||||||
|
raise ValueError("WEATHER_TILE_ZOOMS did not contain any usable zoom levels")
|
||||||
|
|
||||||
|
return sorted(set(zooms))
|
||||||
|
|
||||||
|
|
||||||
|
def load_grid(path):
|
||||||
|
with path.open(encoding="utf-8") as file_handle:
|
||||||
|
data = json.load(file_handle)
|
||||||
|
return data["time"], data["grid"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_grid_field(grid, field_name, latitudes, longitudes):
|
||||||
|
values = grid.get(field_name)
|
||||||
|
if values is not None:
|
||||||
|
return values
|
||||||
|
|
||||||
|
return [[0.0 for _ in longitudes] for _ in latitudes]
|
||||||
|
|
||||||
|
|
||||||
|
def grid_to_features(grid):
|
||||||
|
latitudes = grid["lat"]
|
||||||
|
longitudes = grid["lon"]
|
||||||
|
wind_speed = grid["wind_speed"]
|
||||||
|
wind_dir = grid["wind_dir"]
|
||||||
|
rain = grid["rain"]
|
||||||
|
temp = grid["temp"]
|
||||||
|
pressure = grid["pressure"]
|
||||||
|
wave_h = get_grid_field(grid, "wave_h", latitudes, longitudes)
|
||||||
|
wave_dir = get_grid_field(grid, "wave_dir", latitudes, longitudes)
|
||||||
|
wave_period = get_grid_field(grid, "wave_period", latitudes, longitudes)
|
||||||
|
|
||||||
|
features = []
|
||||||
|
for lat_index, latitude in enumerate(latitudes):
|
||||||
|
for lon_index, longitude in enumerate(longitudes):
|
||||||
|
features.append(
|
||||||
|
{
|
||||||
|
"geometry": {"type": "Point", "coordinates": [longitude, latitude]},
|
||||||
|
"properties": {
|
||||||
|
"ws": wind_speed[lat_index][lon_index],
|
||||||
|
"wd": wind_dir[lat_index][lon_index],
|
||||||
|
"r": rain[lat_index][lon_index],
|
||||||
|
"t": temp[lat_index][lon_index],
|
||||||
|
"p": pressure[lat_index][lon_index],
|
||||||
|
"wh": wave_h[lat_index][lon_index],
|
||||||
|
"wdir": wave_dir[lat_index][lon_index],
|
||||||
|
"wp": wave_period[lat_index][lon_index],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return features
|
||||||
|
|
||||||
|
|
||||||
|
def bucket_features_by_tile(features, zoom):
|
||||||
|
buckets = defaultdict(list)
|
||||||
|
for feature in features:
|
||||||
|
longitude, latitude = feature["geometry"]["coordinates"]
|
||||||
|
tile = mercantile.tile(longitude, latitude, zoom)
|
||||||
|
buckets[(tile.x, tile.y)].append(feature)
|
||||||
|
return buckets
|
||||||
|
|
||||||
|
|
||||||
|
def write_tile(tile_time, zoom, tile_x, tile_y, features):
|
||||||
|
bounds = mercantile.bounds(mercantile.Tile(x=tile_x, y=tile_y, z=zoom))
|
||||||
|
tile_dir = OUTPUT_DIR / tile_time / str(zoom) / str(tile_x)
|
||||||
|
tile_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
tile_path = tile_dir / f"{tile_y}.pbf"
|
||||||
|
|
||||||
|
layer = {"name": "weather", "features": features}
|
||||||
|
tile_data = mapbox_vector_tile.encode(
|
||||||
|
layer,
|
||||||
|
default_options={
|
||||||
|
"quantize_bounds": (bounds.west, bounds.south, bounds.east, bounds.north),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tile_path.write_bytes(tile_data)
|
||||||
|
print("tile", tile_time, zoom, tile_x, tile_y)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_tiles(tile_time, features):
|
||||||
|
for zoom in get_zoom_levels():
|
||||||
|
buckets = bucket_features_by_tile(features, zoom)
|
||||||
|
for (tile_x, tile_y), tile_features in buckets.items():
|
||||||
|
write_tile(tile_time, zoom, tile_x, tile_y, tile_features)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if mapbox_vector_tile is None or mercantile is None:
|
||||||
|
raise RuntimeError("mapbox-vector-tile and mercantile are required to generate vector tiles")
|
||||||
|
|
||||||
|
for path in sorted(GRID_DIR.glob("*.json")):
|
||||||
|
print("processing", path.name)
|
||||||
|
tile_time, grid = load_grid(path)
|
||||||
|
features = grid_to_features(grid)
|
||||||
|
generate_tiles(tile_time, features)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
86
src/weather_pipeline.py
Normal file
86
src/weather_pipeline.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import importlib.util
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
DOWNLOADER = SCRIPT_DIR / "gfs_downloader.py"
|
||||||
|
GRID_BUILDER = SCRIPT_DIR / "grid_builder_v2.py"
|
||||||
|
TILE_GENERATOR = SCRIPT_DIR / "vector_tile_generator.py"
|
||||||
|
|
||||||
|
STEP_DEPENDENCIES = {
|
||||||
|
"GFS Downloader": ("requests",),
|
||||||
|
"Grid Builder v2": ("numpy", "cfgrib"),
|
||||||
|
"Vector Tile Generator": ("mercantile", "mapbox_vector_tile"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_dependencies():
|
||||||
|
missing = []
|
||||||
|
for step_name, modules in STEP_DEPENDENCIES.items():
|
||||||
|
missing_modules = [module for module in modules if importlib.util.find_spec(module) is None]
|
||||||
|
if missing_modules:
|
||||||
|
missing.append(f"{step_name}: {', '.join(missing_modules)}")
|
||||||
|
return missing
|
||||||
|
|
||||||
|
|
||||||
|
def run_step(name, script_path):
|
||||||
|
print("\n==========================")
|
||||||
|
print("Running:", name)
|
||||||
|
print("==========================\n")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
command = [sys.executable, "-u", str(script_path)]
|
||||||
|
|
||||||
|
process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
cwd=str(SCRIPT_DIR.parent),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
bufsize=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert process.stdout is not None
|
||||||
|
for line in process.stdout:
|
||||||
|
print(line, end="")
|
||||||
|
|
||||||
|
return_code = process.wait()
|
||||||
|
if return_code != 0:
|
||||||
|
raise RuntimeError(f"{name} failed with exit code {return_code}")
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
print("\nFinished:", name)
|
||||||
|
print("Time:", round(end - start, 2), "seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Weather Pipeline Starting...")
|
||||||
|
print("Date:", time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
|
||||||
|
missing_dependencies = check_dependencies()
|
||||||
|
if missing_dependencies:
|
||||||
|
print("\n" + "!" * 50)
|
||||||
|
print("Pipeline Failed: missing runtime dependencies")
|
||||||
|
for item in missing_dependencies:
|
||||||
|
print("-", item)
|
||||||
|
print("!" * 50)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_step("GFS Downloader", DOWNLOADER)
|
||||||
|
run_step("Grid Builder v2", GRID_BUILDER)
|
||||||
|
run_step("Vector Tile Generator", TILE_GENERATOR)
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("Weather Pipeline Completed Successfully!")
|
||||||
|
print("=" * 50)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
print("\n" + "!" * 50)
|
||||||
|
print("Pipeline Failed:", str(exc))
|
||||||
|
print("!" * 50)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
54
tasks/NavSea_Semantic_Overlay_Validation_System.md
Normal file
54
tasks/NavSea_Semantic_Overlay_Validation_System.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# NavSea Semantic Overlay Validation System
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Validate the NavSea semantic overlay without touching rendering logic, style configuration, or vector tile structure.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
The task reads the existing semantic overlay and audit tables in database `pbf_analysis`, including:
|
||||||
|
|
||||||
|
- `canonical_layer_rules`
|
||||||
|
- `canonical_object_rules`
|
||||||
|
- `object_geometry_allowlist`
|
||||||
|
- `pbf_relayer_candidates`
|
||||||
|
- `pbf_render_compatibility`
|
||||||
|
- `pbf_detection_catalog`
|
||||||
|
- `style_layers`
|
||||||
|
- `features`
|
||||||
|
- `tile_density`
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
Rebuild the semantic overlay metadata and then run validation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python navsea_relayer.py
|
||||||
|
.venv/bin/python navsea_semantic_validation.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Tables
|
||||||
|
|
||||||
|
The validation task creates or refreshes:
|
||||||
|
|
||||||
|
- `classification_validation`
|
||||||
|
- `geometry_consistency`
|
||||||
|
- `style_render_equivalence`
|
||||||
|
- `detection_catalog_integrity`
|
||||||
|
- `spatial_anomalies`
|
||||||
|
- `semantic_validation_summary`
|
||||||
|
|
||||||
|
## Current Design Notes
|
||||||
|
|
||||||
|
- Rendering compatibility is verified at database level only.
|
||||||
|
- `canonical_object_rules` is the stable object-level rule source.
|
||||||
|
- `object_geometry_allowlist` is the geometry whitelist used to separate valid multi-geometry objects from true anomalies.
|
||||||
|
- `spatial_anomalies` is intentionally lightweight and currently focuses on tile-density and tile-index anomalies.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- No render equivalence break
|
||||||
|
- No classification errors
|
||||||
|
- No geometry consistency errors
|
||||||
|
- No detection catalog integrity errors
|
||||||
|
- Spatial anomalies limited to acceptable warnings
|
||||||
229
tasks/WeatherServer_GridBuilder
Normal file
229
tasks/WeatherServer_GridBuilder
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# NavSea Weather Server
|
||||||
|
Task: WeatherServer_GridBuilder
|
||||||
|
Architecture: NavSea V11
|
||||||
|
Codex: codex6
|
||||||
|
Status: TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1 任务目标
|
||||||
|
|
||||||
|
实现 Weather Grid Builder 模块。
|
||||||
|
|
||||||
|
功能:
|
||||||
|
|
||||||
|
将 GRIB 数据解析为统一的 Weather Grid 数据结构。
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
data/grib/*.grib2
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
data/grid/*.json
|
||||||
|
|
||||||
|
Weather Grid 将作为:
|
||||||
|
|
||||||
|
Vector Tile Generator
|
||||||
|
Routing Engine
|
||||||
|
Weather Analysis
|
||||||
|
|
||||||
|
的基础数据源。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2 输入数据
|
||||||
|
|
||||||
|
GRIB 文件来自 Downloader。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
data/grib/
|
||||||
|
|
||||||
|
20260312_00_f000.grib2
|
||||||
|
20260312_00_f003.grib2
|
||||||
|
20260312_00_f006.grib2
|
||||||
|
|
||||||
|
变量:
|
||||||
|
|
||||||
|
UGRD
|
||||||
|
VGRD
|
||||||
|
APCP
|
||||||
|
PRMSL
|
||||||
|
TMP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3 Weather Grid 数据结构
|
||||||
|
|
||||||
|
每个 grid 点结构:
|
||||||
|
|
||||||
|
{
|
||||||
|
"lat": float,
|
||||||
|
"lon": float,
|
||||||
|
"wind_speed": float,
|
||||||
|
"wind_dir": float,
|
||||||
|
"rain": float,
|
||||||
|
"temp": float,
|
||||||
|
"pressure": float
|
||||||
|
}
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
lat 纬度
|
||||||
|
lon 经度
|
||||||
|
|
||||||
|
wind_speed m/s
|
||||||
|
wind_dir 度
|
||||||
|
|
||||||
|
rain mm
|
||||||
|
|
||||||
|
temp 摄氏度
|
||||||
|
|
||||||
|
pressure hPa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4 风速计算
|
||||||
|
|
||||||
|
使用 U/V 分量计算:
|
||||||
|
|
||||||
|
wind_speed = sqrt(u² + v²)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5 风向计算
|
||||||
|
|
||||||
|
公式:
|
||||||
|
|
||||||
|
wind_dir = (atan2(u, v) * 180 / π + 360) % 360
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
0–360°
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6 Grid Builder 输出
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
data/grid/
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
grid_20260312_00_f000.json
|
||||||
|
|
||||||
|
结构:
|
||||||
|
|
||||||
|
{
|
||||||
|
"time": "20260312_00_f000",
|
||||||
|
"points": [...]
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7 创建文件
|
||||||
|
|
||||||
|
weather_server/grid/grid_builder.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8 实现代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
import xarray as xr
|
||||||
|
|
||||||
|
INPUT_DIR = "data/grib"
|
||||||
|
OUTPUT_DIR = "data/grid"
|
||||||
|
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_wind(u, v):
|
||||||
|
|
||||||
|
speed = np.sqrt(u**2 + v**2)
|
||||||
|
|
||||||
|
direction = (np.degrees(np.arctan2(u, v)) + 360) % 360
|
||||||
|
|
||||||
|
return speed, direction
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(path):
|
||||||
|
|
||||||
|
ds = xr.open_dataset(path, engine="cfgrib")
|
||||||
|
|
||||||
|
u = ds["u10"].values
|
||||||
|
v = ds["v10"].values
|
||||||
|
rain = ds["tp"].values
|
||||||
|
pressure = ds["msl"].values
|
||||||
|
temp = ds["t2m"].values
|
||||||
|
|
||||||
|
lat = ds.latitude.values
|
||||||
|
lon = ds.longitude.values
|
||||||
|
|
||||||
|
wind_speed, wind_dir = compute_wind(u, v)
|
||||||
|
|
||||||
|
points = []
|
||||||
|
|
||||||
|
for i in range(len(lat)):
|
||||||
|
|
||||||
|
for j in range(len(lon)):
|
||||||
|
|
||||||
|
point = {
|
||||||
|
|
||||||
|
"lat": float(lat[i]),
|
||||||
|
"lon": float(lon[j]),
|
||||||
|
|
||||||
|
"wind_speed": float(wind_speed[i][j]),
|
||||||
|
"wind_dir": float(wind_dir[i][j]),
|
||||||
|
|
||||||
|
"rain": float(rain[i][j]),
|
||||||
|
|
||||||
|
"temp": float(temp[i][j]),
|
||||||
|
|
||||||
|
"pressure": float(pressure[i][j])
|
||||||
|
}
|
||||||
|
|
||||||
|
points.append(point)
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
for file in os.listdir(INPUT_DIR):
|
||||||
|
|
||||||
|
if not file.endswith(".grib2"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = os.path.join(INPUT_DIR, file)
|
||||||
|
|
||||||
|
print("processing", file)
|
||||||
|
|
||||||
|
points = process_file(path)
|
||||||
|
|
||||||
|
output = os.path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
f"grid_{file.replace('.grib2','')}.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
|
||||||
|
"time": file.replace(".grib2",""),
|
||||||
|
"points": points
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(output, "w") as f:
|
||||||
|
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
print("saved", output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
main()
|
||||||
215
tasks/WeatherServer_GridBuilder_v2.md
Normal file
215
tasks/WeatherServer_GridBuilder_v2.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# NavSea Weather Server
|
||||||
|
Task: WeatherServer_GridBuilder_v2
|
||||||
|
Architecture: NavSea V11
|
||||||
|
Codex: codex6
|
||||||
|
Status: TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1 任务目标
|
||||||
|
|
||||||
|
升级 Grid Builder:
|
||||||
|
|
||||||
|
grid_builder.py → GridBuilder v2
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
1 裁剪日本区域 grid
|
||||||
|
2 使用数组结构保存 grid
|
||||||
|
3 大幅减少 JSON 文件大小
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2 当前问题
|
||||||
|
|
||||||
|
旧版 GridBuilder:
|
||||||
|
|
||||||
|
生成全球 grid:
|
||||||
|
|
||||||
|
1440 × 721
|
||||||
|
≈ 1,038,240 points
|
||||||
|
|
||||||
|
JSON 文件:
|
||||||
|
|
||||||
|
≈ 215MB
|
||||||
|
|
||||||
|
这是不可接受的。
|
||||||
|
|
||||||
|
Downloader 已经只下载:
|
||||||
|
|
||||||
|
120E – 150E
|
||||||
|
20N – 50N
|
||||||
|
|
||||||
|
GridBuilder 必须只输出这个区域。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3 区域范围
|
||||||
|
|
||||||
|
REGION:
|
||||||
|
|
||||||
|
lon_min = 120
|
||||||
|
lon_max = 150
|
||||||
|
|
||||||
|
lat_min = 20
|
||||||
|
lat_max = 50
|
||||||
|
|
||||||
|
理论 grid:
|
||||||
|
|
||||||
|
(150-120)/0.25 = 120
|
||||||
|
(50-20)/0.25 = 120
|
||||||
|
|
||||||
|
≈ 14400 grid points
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4 新 Grid 数据结构
|
||||||
|
|
||||||
|
旧结构:
|
||||||
|
|
||||||
|
points list
|
||||||
|
|
||||||
|
{
|
||||||
|
"points":[
|
||||||
|
{lat,lon,...}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
新结构:
|
||||||
|
|
||||||
|
grid arrays
|
||||||
|
|
||||||
|
{
|
||||||
|
"time": "...",
|
||||||
|
"lat": [...],
|
||||||
|
"lon": [...],
|
||||||
|
"wind_speed": [...],
|
||||||
|
"wind_dir": [...],
|
||||||
|
"rain": [...],
|
||||||
|
"temp": [...],
|
||||||
|
"pressure": [...]
|
||||||
|
}
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
1 文件更小
|
||||||
|
2 读取更快
|
||||||
|
3 tile generator 更容易
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5 创建文件
|
||||||
|
|
||||||
|
weather_server/grid/grid_builder_v2.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6 实现代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
import xarray as xr
|
||||||
|
|
||||||
|
INPUT_DIR = "data/grib"
|
||||||
|
OUTPUT_DIR = "data/grid"
|
||||||
|
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
REGION = {
|
||||||
|
"lon_min":120,
|
||||||
|
"lon_max":150,
|
||||||
|
"lat_min":20,
|
||||||
|
"lat_max":50
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def compute_wind(u, v):
|
||||||
|
|
||||||
|
speed = np.sqrt(u**2 + v**2)
|
||||||
|
|
||||||
|
direction = (np.degrees(np.arctan2(u, v)) + 360) % 360
|
||||||
|
|
||||||
|
return speed, direction
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(path):
|
||||||
|
|
||||||
|
ds = xr.open_dataset(path, engine="cfgrib")
|
||||||
|
|
||||||
|
u = ds["u10"].values
|
||||||
|
v = ds["v10"].values
|
||||||
|
rain = ds["tp"].values
|
||||||
|
pressure = ds["msl"].values
|
||||||
|
temp = ds["t2m"].values
|
||||||
|
|
||||||
|
lat = ds.latitude.values
|
||||||
|
lon = ds.longitude.values
|
||||||
|
|
||||||
|
wind_speed, wind_dir = compute_wind(u, v)
|
||||||
|
|
||||||
|
lat_idx = np.where(
|
||||||
|
(lat >= REGION["lat_min"]) &
|
||||||
|
(lat <= REGION["lat_max"])
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
lon_idx = np.where(
|
||||||
|
(lon >= REGION["lon_min"]) &
|
||||||
|
(lon <= REGION["lon_max"])
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
lat_region = lat[lat_idx]
|
||||||
|
lon_region = lon[lon_idx]
|
||||||
|
|
||||||
|
wind_speed = wind_speed[np.ix_(lat_idx, lon_idx)]
|
||||||
|
wind_dir = wind_dir[np.ix_(lat_idx, lon_idx)]
|
||||||
|
|
||||||
|
rain = rain[np.ix_(lat_idx, lon_idx)]
|
||||||
|
temp = temp[np.ix_(lat_idx, lon_idx)]
|
||||||
|
pressure = pressure[np.ix_(lat_idx, lon_idx)]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"lat": lat_region.tolist(),
|
||||||
|
"lon": lon_region.tolist(),
|
||||||
|
"wind_speed": wind_speed.tolist(),
|
||||||
|
"wind_dir": wind_dir.tolist(),
|
||||||
|
"rain": rain.tolist(),
|
||||||
|
"temp": temp.tolist(),
|
||||||
|
"pressure": pressure.tolist()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
for file in os.listdir(INPUT_DIR):
|
||||||
|
|
||||||
|
if not file.endswith(".grib2"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = os.path.join(INPUT_DIR, file)
|
||||||
|
|
||||||
|
print("processing", file)
|
||||||
|
|
||||||
|
grid = process_file(path)
|
||||||
|
|
||||||
|
output = os.path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
f"grid_{file.replace('.grib2','')}.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"time": file.replace(".grib2",""),
|
||||||
|
"grid": grid
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(output, "w") as f:
|
||||||
|
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
print("saved", output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
main()
|
||||||
248
tasks/WeatherServer_VectorTileGenerator.md
Normal file
248
tasks/WeatherServer_VectorTileGenerator.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# NavSea Weather Server
|
||||||
|
Task: WeatherServer_VectorTileGenerator
|
||||||
|
Architecture: NavSea V11
|
||||||
|
Codex: codex6
|
||||||
|
Status: TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1 任务目标
|
||||||
|
|
||||||
|
实现 Vector Tile Generator。
|
||||||
|
|
||||||
|
功能:
|
||||||
|
|
||||||
|
将 Weather Grid JSON 转换为 MapLibre Vector Tile (PBF)。
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
data/grid/*.json
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
output/weather/{time}/{z}/{x}/{y}.pbf
|
||||||
|
|
||||||
|
用于 NavSea 客户端加载天气图层。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2 使用库
|
||||||
|
|
||||||
|
需要安装:
|
||||||
|
|
||||||
|
pip install mercantile
|
||||||
|
pip install mapbox-vector-tile
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3 输入数据
|
||||||
|
|
||||||
|
GridBuilder v2 生成:
|
||||||
|
|
||||||
|
data/grid/
|
||||||
|
|
||||||
|
grid_20260312_00_f000.json
|
||||||
|
grid_20260312_00_f003.json
|
||||||
|
|
||||||
|
结构:
|
||||||
|
|
||||||
|
{
|
||||||
|
"time": "...",
|
||||||
|
"grid": {
|
||||||
|
"lat": [...],
|
||||||
|
"lon": [...],
|
||||||
|
"wind_speed": [[...]],
|
||||||
|
"wind_dir": [[...]],
|
||||||
|
"rain": [[...]],
|
||||||
|
"temp": [[...]],
|
||||||
|
"pressure": [[...]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid 尺寸:
|
||||||
|
|
||||||
|
121 × 121
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4 Tile Zoom 设计
|
||||||
|
|
||||||
|
Weather tile 只需要 4 个 zoom:
|
||||||
|
|
||||||
|
2
|
||||||
|
4
|
||||||
|
6
|
||||||
|
8
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5 Tile 输出结构
|
||||||
|
|
||||||
|
output/weather/
|
||||||
|
|
||||||
|
time/
|
||||||
|
z/
|
||||||
|
x/
|
||||||
|
y.pbf
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
output/weather/20260312_00_f000/4/10/7.pbf
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6 Feature 结构
|
||||||
|
|
||||||
|
每个 grid 点 → 一个 feature
|
||||||
|
|
||||||
|
geometry:
|
||||||
|
|
||||||
|
POINT(lon lat)
|
||||||
|
|
||||||
|
properties:
|
||||||
|
|
||||||
|
{
|
||||||
|
"ws": wind_speed
|
||||||
|
"wd": wind_dir
|
||||||
|
"r": rain
|
||||||
|
"t": temp
|
||||||
|
"p": pressure
|
||||||
|
}
|
||||||
|
|
||||||
|
字段缩写减少 tile 大小。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7 创建文件
|
||||||
|
|
||||||
|
weather_server/tiles/vector_tile_generator.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8 实现代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import mercantile
|
||||||
|
import mapbox_vector_tile
|
||||||
|
|
||||||
|
GRID_DIR = "data/grid"
|
||||||
|
OUTPUT_DIR = "output/weather"
|
||||||
|
|
||||||
|
ZOOMS = [2,4,6,8]
|
||||||
|
|
||||||
|
def load_grid(path):
|
||||||
|
|
||||||
|
with open(path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
return data["time"], data["grid"]
|
||||||
|
|
||||||
|
|
||||||
|
def grid_to_features(grid):
|
||||||
|
|
||||||
|
lat = grid["lat"]
|
||||||
|
lon = grid["lon"]
|
||||||
|
|
||||||
|
ws = grid["wind_speed"]
|
||||||
|
wd = grid["wind_dir"]
|
||||||
|
rain = grid["rain"]
|
||||||
|
temp = grid["temp"]
|
||||||
|
pres = grid["pressure"]
|
||||||
|
|
||||||
|
features = []
|
||||||
|
|
||||||
|
for i in range(len(lat)):
|
||||||
|
for j in range(len(lon)):
|
||||||
|
|
||||||
|
feature = {
|
||||||
|
"geometry":{
|
||||||
|
"type":"Point",
|
||||||
|
"coordinates":[lon[j], lat[i]]
|
||||||
|
},
|
||||||
|
"properties":{
|
||||||
|
"ws":ws[i][j],
|
||||||
|
"wd":wd[i][j],
|
||||||
|
"r":rain[i][j],
|
||||||
|
"t":temp[i][j],
|
||||||
|
"p":pres[i][j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
features.append(feature)
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
|
|
||||||
|
def generate_tiles(time, features):
|
||||||
|
|
||||||
|
for z in ZOOMS:
|
||||||
|
|
||||||
|
tiles = mercantile.tiles(120,20,150,50,z)
|
||||||
|
|
||||||
|
for tile in tiles:
|
||||||
|
|
||||||
|
bounds = mercantile.bounds(tile)
|
||||||
|
|
||||||
|
tile_features = []
|
||||||
|
|
||||||
|
for f in features:
|
||||||
|
|
||||||
|
lon, lat = f["geometry"]["coordinates"]
|
||||||
|
|
||||||
|
if (
|
||||||
|
bounds.west <= lon <= bounds.east
|
||||||
|
and bounds.south <= lat <= bounds.north
|
||||||
|
):
|
||||||
|
tile_features.append(f)
|
||||||
|
|
||||||
|
if not tile_features:
|
||||||
|
continue
|
||||||
|
|
||||||
|
layer = {
|
||||||
|
"weather": tile_features
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_data = mapbox_vector_tile.encode(layer)
|
||||||
|
|
||||||
|
path = os.path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
time,
|
||||||
|
str(z),
|
||||||
|
str(tile.x)
|
||||||
|
)
|
||||||
|
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
|
||||||
|
filename = os.path.join(
|
||||||
|
path,
|
||||||
|
f"{tile.y}.pbf"
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(filename,"wb") as f:
|
||||||
|
f.write(tile_data)
|
||||||
|
|
||||||
|
print("tile", time, z, tile.x, tile.y)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
for file in os.listdir(GRID_DIR):
|
||||||
|
|
||||||
|
if not file.endswith(".json"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = os.path.join(GRID_DIR, file)
|
||||||
|
|
||||||
|
print("processing", file)
|
||||||
|
|
||||||
|
time, grid = load_grid(path)
|
||||||
|
|
||||||
|
features = grid_to_features(grid)
|
||||||
|
|
||||||
|
generate_tiles(time, features)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
528
tasks/classfild.md
Normal file
528
tasks/classfild.md
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
# NavSea Data Classification & Validation Pipeline
|
||||||
|
Version: v1.0
|
||||||
|
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
从当前数据库中的 PBF 数据自动:
|
||||||
|
|
||||||
|
1. 解析 at 字段
|
||||||
|
2. 构建语义属性表
|
||||||
|
3. 构建对象目录
|
||||||
|
4. 自动推导对象类型
|
||||||
|
5. 验证分类正确性
|
||||||
|
6. 统计 tile 密度
|
||||||
|
7. 输出 NavSea 分类报告
|
||||||
|
|
||||||
|
所有中间结果写入 MySQL 表。
|
||||||
|
|
||||||
|
数据库在 192.168.200.184. root/2chi9ks2
|
||||||
|
可以建立数据库,也可以使用python代码,必要的时候可以下载所需的包。
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 1
|
||||||
|
Parse AT Attributes
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
解析 properties 表中 k='at' 的 JSON 字符串。
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS at_attributes (
|
||||||
|
feature_id BIGINT,
|
||||||
|
k VARCHAR(100),
|
||||||
|
v TEXT
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Logic:
|
||||||
|
|
||||||
|
1. 查询
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT feature_id, v
|
||||||
|
FROM properties
|
||||||
|
WHERE k='at';
|
||||||
|
```
|
||||||
|
|
||||||
|
2. v 是 JSON array
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
[["レイヤ","航路標識点"],["形状分類","シーバース灯"],["灯色","W (白)"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 解析后写入:
|
||||||
|
|
||||||
|
```
|
||||||
|
feature_id | k | v
|
||||||
|
--------------------
|
||||||
|
2682088 | レイヤ | 航路標識点
|
||||||
|
2682088 | 形状分類 | シーバース灯
|
||||||
|
2682088 | 灯色 | W (白)
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
|
||||||
|
Python
|
||||||
|
Libraries:
|
||||||
|
|
||||||
|
```
|
||||||
|
pymysql
|
||||||
|
json
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
table: at_attributes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 2
|
||||||
|
Build Feature Semantic Table
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
将 feature + vt_layer + at 属性合并。
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE feature_semantic AS
|
||||||
|
SELECT
|
||||||
|
f.id AS feature_id,
|
||||||
|
f.vt_layer,
|
||||||
|
f.geom_type,
|
||||||
|
MAX(CASE WHEN a.k='分類' THEN a.v END) AS class_name,
|
||||||
|
MAX(CASE WHEN a.k='形状分類' THEN a.v END) AS shape_name,
|
||||||
|
MAX(CASE WHEN a.k='レイヤ' THEN a.v END) AS layer_name
|
||||||
|
FROM features f
|
||||||
|
LEFT JOIN at_attributes a
|
||||||
|
ON f.id = a.feature_id
|
||||||
|
GROUP BY f.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output table:
|
||||||
|
|
||||||
|
```
|
||||||
|
feature_semantic
|
||||||
|
```
|
||||||
|
|
||||||
|
Columns:
|
||||||
|
|
||||||
|
```
|
||||||
|
feature_id
|
||||||
|
vt_layer
|
||||||
|
geom_type
|
||||||
|
class_name
|
||||||
|
shape_name
|
||||||
|
layer_name
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 3
|
||||||
|
Generate Object Catalog
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
统计所有语义对象。
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE object_catalog AS
|
||||||
|
SELECT
|
||||||
|
layer_name,
|
||||||
|
class_name,
|
||||||
|
shape_name,
|
||||||
|
vt_layer,
|
||||||
|
geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM feature_semantic
|
||||||
|
GROUP BY
|
||||||
|
layer_name,
|
||||||
|
class_name,
|
||||||
|
shape_name,
|
||||||
|
vt_layer,
|
||||||
|
geom_type;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
object_catalog
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
得到完整对象目录。
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
魚礁 | p施設 | Point | 24683
|
||||||
|
灯台 | p航路標識群 | Point | 18000
|
||||||
|
等深線 | L等深線 | Line | 440000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 4
|
||||||
|
Geometry Consistency Check
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
检查对象是否使用一致 geometry。
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE geometry_consistency AS
|
||||||
|
SELECT
|
||||||
|
class_name,
|
||||||
|
geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM feature_semantic
|
||||||
|
GROUP BY class_name, geom_type;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
geometry_consistency
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
发现异常对象。
|
||||||
|
|
||||||
|
Example anomaly:
|
||||||
|
|
||||||
|
```
|
||||||
|
灯台 | Polygon
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 5
|
||||||
|
Candidate Object Type Detection
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
自动推导 object_type。
|
||||||
|
|
||||||
|
规则优先级:
|
||||||
|
|
||||||
|
1 class_name
|
||||||
|
2 shape_name
|
||||||
|
3 vt_layer
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE object_type_candidates AS
|
||||||
|
SELECT
|
||||||
|
feature_id,
|
||||||
|
COALESCE(class_name, shape_name, vt_layer) AS object_type,
|
||||||
|
geom_type
|
||||||
|
FROM feature_semantic;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
object_type_candidates
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 6
|
||||||
|
Object Type Statistics
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
统计对象数量。
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE object_type_stats AS
|
||||||
|
SELECT
|
||||||
|
object_type,
|
||||||
|
geom_type,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM object_type_candidates
|
||||||
|
GROUP BY object_type, geom_type;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
object_type_stats
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
识别主要对象。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 7
|
||||||
|
Style Cross Reference
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
分析 style.json。
|
||||||
|
|
||||||
|
Extract:
|
||||||
|
|
||||||
|
```
|
||||||
|
layer_id
|
||||||
|
source-layer
|
||||||
|
icon-image
|
||||||
|
line-color
|
||||||
|
fill-color
|
||||||
|
```
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE style_layers (
|
||||||
|
layer_id VARCHAR(200),
|
||||||
|
source_layer VARCHAR(200),
|
||||||
|
icon VARCHAR(200),
|
||||||
|
line_color VARCHAR(200),
|
||||||
|
fill_color VARCHAR(200)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Join:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE style_mapping AS
|
||||||
|
SELECT
|
||||||
|
s.layer_id,
|
||||||
|
s.icon,
|
||||||
|
o.object_type,
|
||||||
|
o.geom_type
|
||||||
|
FROM style_layers s
|
||||||
|
JOIN object_type_candidates o
|
||||||
|
ON s.source_layer = o.object_type;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
style_mapping
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
确认对象 → 图标关系。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 8
|
||||||
|
Tile Density Analysis
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
统计 tile feature 密度。
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE tile_density AS
|
||||||
|
SELECT
|
||||||
|
z,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
COUNT(*) AS feature_count
|
||||||
|
FROM features
|
||||||
|
GROUP BY z,x,y;
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
tile_density
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 9
|
||||||
|
Tile Density Top 100
|
||||||
|
|
||||||
|
Create Table:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE tile_density_top100 AS
|
||||||
|
SELECT *
|
||||||
|
FROM tile_density
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
LIMIT 100;
|
||||||
|
```
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
识别高密度 tile。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 10
|
||||||
|
Spatial Sanity Checks
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
|
||||||
|
发现明显错误。
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Navigation lights not point:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE anomaly_navigation_geom AS
|
||||||
|
SELECT *
|
||||||
|
FROM feature_semantic
|
||||||
|
WHERE class_name='灯台'
|
||||||
|
AND geom_type!='Point';
|
||||||
|
```
|
||||||
|
|
||||||
|
Reef not point/polygon:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE anomaly_reef_geom AS
|
||||||
|
SELECT *
|
||||||
|
FROM feature_semantic
|
||||||
|
WHERE class_name='魚礁'
|
||||||
|
AND geom_type NOT IN ('Point','Polygon');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK 11
|
||||||
|
NavSea Classification Report
|
||||||
|
|
||||||
|
Generate markdown:
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_classification_report.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Content:
|
||||||
|
|
||||||
|
## Dataset Summary
|
||||||
|
|
||||||
|
Total features:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) FROM features;
|
||||||
|
```
|
||||||
|
|
||||||
|
Total object types:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(DISTINCT object_type)
|
||||||
|
FROM object_type_candidates;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Top Object Types
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT *
|
||||||
|
FROM object_type_stats
|
||||||
|
ORDER BY feature_count DESC
|
||||||
|
LIMIT 50;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geometry Consistency
|
||||||
|
|
||||||
|
```
|
||||||
|
geometry_consistency
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Style Mapping
|
||||||
|
|
||||||
|
```
|
||||||
|
style_mapping
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tile Density
|
||||||
|
|
||||||
|
```
|
||||||
|
tile_density_top100
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Final Deliverables
|
||||||
|
|
||||||
|
Database Tables:
|
||||||
|
|
||||||
|
```
|
||||||
|
at_attributes
|
||||||
|
feature_semantic
|
||||||
|
object_catalog
|
||||||
|
geometry_consistency
|
||||||
|
object_type_candidates
|
||||||
|
object_type_stats
|
||||||
|
style_layers
|
||||||
|
style_mapping
|
||||||
|
tile_density
|
||||||
|
tile_density_top100
|
||||||
|
anomaly_navigation_geom
|
||||||
|
anomaly_reef_geom
|
||||||
|
```
|
||||||
|
|
||||||
|
Final Document:
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_classification_report.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Execution
|
||||||
|
|
||||||
|
Single command:
|
||||||
|
|
||||||
|
```
|
||||||
|
python navsea_audit.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Pipeline:
|
||||||
|
|
||||||
|
```
|
||||||
|
parse_at
|
||||||
|
→ semantic_table
|
||||||
|
→ object_catalog
|
||||||
|
→ classification
|
||||||
|
→ validation
|
||||||
|
→ report
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Success Criteria
|
||||||
|
|
||||||
|
The system must allow answering:
|
||||||
|
|
||||||
|
1 What objects exist
|
||||||
|
2 How many features each object has
|
||||||
|
3 What geometry they use
|
||||||
|
4 How they are styled
|
||||||
|
5 Whether classification is consistent
|
||||||
|
6 Whether tile density is reasonable
|
||||||
151
tasks/firsttest.md
Normal file
151
tasks/firsttest.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# NavSea Weather Server Step-1
|
||||||
|
GRIB 解析环境测试
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
验证服务器可以正确解析 GRIB 文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1 安装系统依赖
|
||||||
|
|
||||||
|
在 Linux 服务器执行:
|
||||||
|
|
||||||
|
apt update
|
||||||
|
|
||||||
|
apt install -y python3 python3-pip
|
||||||
|
|
||||||
|
apt install -y libeccodes0 libeccodes-dev
|
||||||
|
|
||||||
|
apt install -y python3-eccodes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2 安装 Python 库
|
||||||
|
|
||||||
|
pip3 install cfgrib
|
||||||
|
pip3 install xarray
|
||||||
|
pip3 install numpy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3 下载测试 GRIB 文件
|
||||||
|
|
||||||
|
创建测试目录:
|
||||||
|
|
||||||
|
mkdir weather_test
|
||||||
|
cd weather_test
|
||||||
|
|
||||||
|
下载 NOAA GFS 示例:
|
||||||
|
|
||||||
|
wget https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod/gfs.20260312/00/atmos/gfs.t00z.pgrb2.0p25.f003 -O test.grib2
|
||||||
|
|
||||||
|
(如果日期目录不存在,可以换最新日期)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4 创建测试脚本
|
||||||
|
|
||||||
|
创建文件:
|
||||||
|
|
||||||
|
test_grib.py
|
||||||
|
|
||||||
|
内容:
|
||||||
|
|
||||||
|
import xarray as xr
|
||||||
|
|
||||||
|
file = "test.grib2"
|
||||||
|
|
||||||
|
print("Opening GRIB file...")
|
||||||
|
|
||||||
|
ds = xr.open_dataset(file, engine="cfgrib")
|
||||||
|
|
||||||
|
print("Dataset loaded")
|
||||||
|
|
||||||
|
print(ds)
|
||||||
|
|
||||||
|
print("\nVariables:")
|
||||||
|
print(ds.data_vars)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5 运行测试
|
||||||
|
|
||||||
|
python3 test_grib.py
|
||||||
|
|
||||||
|
如果成功,你会看到类似输出:
|
||||||
|
|
||||||
|
Dataset loaded
|
||||||
|
|
||||||
|
Dimensions:
|
||||||
|
latitude: 721
|
||||||
|
longitude: 1440
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
u10
|
||||||
|
v10
|
||||||
|
t2m
|
||||||
|
msl
|
||||||
|
tp
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6 测试读取风数据
|
||||||
|
|
||||||
|
修改脚本:
|
||||||
|
|
||||||
|
import xarray as xr
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
file = "test.grib2"
|
||||||
|
|
||||||
|
ds = xr.open_dataset(file, engine="cfgrib")
|
||||||
|
|
||||||
|
u = ds['u10']
|
||||||
|
v = ds['v10']
|
||||||
|
|
||||||
|
speed = np.sqrt(u**2 + v**2)
|
||||||
|
|
||||||
|
print("Wind sample:")
|
||||||
|
|
||||||
|
print(speed.values[0][0])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7 成功标志
|
||||||
|
|
||||||
|
如果成功运行:
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
GRIB解析正常
|
||||||
|
Python环境正常
|
||||||
|
可以进入 Weather Server 开发
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8 常见错误
|
||||||
|
|
||||||
|
错误:
|
||||||
|
|
||||||
|
eccodes library not found
|
||||||
|
|
||||||
|
解决:
|
||||||
|
|
||||||
|
apt install libeccodes-dev
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9 完成后下一步
|
||||||
|
|
||||||
|
下一步开发:
|
||||||
|
|
||||||
|
GRIB Downloader
|
||||||
|
|
||||||
|
模块:
|
||||||
|
|
||||||
|
weather_server/downloader/gfs_downloader.py
|
||||||
|
|
||||||
|
负责:
|
||||||
|
|
||||||
|
自动下载 GFS GRIB 数据。
|
||||||
413
tasks/pbf/AllPbf.md
Normal file
413
tasks/pbf/AllPbf.md
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
# NavSea Server Codex Tasks v2
|
||||||
|
|
||||||
|
Architecture: NavSea V11 / Codex6
|
||||||
|
Scope: **Server Side Only**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Global Rules(全局强制规则)
|
||||||
|
|
||||||
|
以下规则适用于所有 NavSea Server Tasks。
|
||||||
|
|
||||||
|
## Rule 1 — 必须保留原始 PBF 属性
|
||||||
|
|
||||||
|
语义系统 **不得删除或覆盖任何原始 PBF feature 属性**。
|
||||||
|
|
||||||
|
必须保留包括但不限于:
|
||||||
|
|
||||||
|
* 日文对象名称
|
||||||
|
* 海图原始属性
|
||||||
|
* 灯塔信息
|
||||||
|
* 深度信息
|
||||||
|
* 渔区信息
|
||||||
|
* 海图符号属性
|
||||||
|
* 任何原始 metadata
|
||||||
|
|
||||||
|
语义字段只能 **新增**,不能替换。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
class = 灯
|
||||||
|
shape = 灯標
|
||||||
|
name = 大島灯台
|
||||||
|
```
|
||||||
|
|
||||||
|
新增语义字段:
|
||||||
|
|
||||||
|
```
|
||||||
|
canonical_object_type = navigation_aid_light
|
||||||
|
object_family = navigation_aid
|
||||||
|
detection_class = navigation_mark
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rule 2 — 不得修改 style.json 依赖属性
|
||||||
|
|
||||||
|
任何被 style.json 使用的属性字段 **必须保持不变**。
|
||||||
|
|
||||||
|
典型字段包括:
|
||||||
|
|
||||||
|
```
|
||||||
|
source_layer
|
||||||
|
class
|
||||||
|
shape
|
||||||
|
name
|
||||||
|
symbol
|
||||||
|
depth
|
||||||
|
type
|
||||||
|
category
|
||||||
|
```
|
||||||
|
|
||||||
|
语义系统 **不得重命名或删除这些字段**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rule 3 — 语义字段只能新增
|
||||||
|
|
||||||
|
NavSea Semantic Overlay 只能增加字段,例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
canonical_object_type
|
||||||
|
object_family
|
||||||
|
detection_class
|
||||||
|
capability
|
||||||
|
detection_key
|
||||||
|
```
|
||||||
|
|
||||||
|
不得改变:
|
||||||
|
|
||||||
|
```
|
||||||
|
geometry
|
||||||
|
source_layer
|
||||||
|
original_attributes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 01 — Semantic Overlay Validation System v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
构建验证系统,确保语义重建不会破坏原始 PBF 渲染结构。
|
||||||
|
|
||||||
|
验证内容包括:
|
||||||
|
|
||||||
|
* feature 数量一致
|
||||||
|
* geometry 类型一致
|
||||||
|
* style 渲染一致
|
||||||
|
* 原始属性完整保留
|
||||||
|
|
||||||
|
## 输入表
|
||||||
|
|
||||||
|
Codex 自动检测:
|
||||||
|
|
||||||
|
```
|
||||||
|
canonical_layer_rules
|
||||||
|
pbf_relayer_candidates
|
||||||
|
pbf_render_compatibility
|
||||||
|
pbf_detection_catalog
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出验证表
|
||||||
|
|
||||||
|
```
|
||||||
|
classification_validation
|
||||||
|
geometry_consistency
|
||||||
|
style_render_equivalence
|
||||||
|
attribute_preservation_check
|
||||||
|
semantic_validation_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增验证
|
||||||
|
|
||||||
|
必须新增:
|
||||||
|
|
||||||
|
```
|
||||||
|
attribute_preservation_check
|
||||||
|
```
|
||||||
|
|
||||||
|
用于确认:
|
||||||
|
|
||||||
|
```
|
||||||
|
原始 PBF 属性未丢失
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 02 — Object Taxonomy Builder v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
建立稳定的海图对象分类体系。
|
||||||
|
|
||||||
|
## 输出表
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_object_taxonomy
|
||||||
|
```
|
||||||
|
|
||||||
|
## 字段
|
||||||
|
|
||||||
|
```
|
||||||
|
canonical_object_type
|
||||||
|
object_family
|
||||||
|
detection_class
|
||||||
|
description
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
```
|
||||||
|
rock → hazard → obstacle
|
||||||
|
wreck → hazard → obstacle
|
||||||
|
reef → hazard → obstacle
|
||||||
|
|
||||||
|
light → navigation_aid → navigation_mark
|
||||||
|
```
|
||||||
|
|
||||||
|
## 约束
|
||||||
|
|
||||||
|
Taxonomy 不能改变:
|
||||||
|
|
||||||
|
```
|
||||||
|
source_layer
|
||||||
|
original_attributes
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 03 — Object Capability System v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
定义海图对象在航行中的能力(capability)。
|
||||||
|
|
||||||
|
## 输出表
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_object_capabilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## 字段
|
||||||
|
|
||||||
|
```
|
||||||
|
canonical_object_type
|
||||||
|
capability
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
```
|
||||||
|
rock → collision
|
||||||
|
wreck → collision
|
||||||
|
reef → grounding
|
||||||
|
fishery → entangle
|
||||||
|
light → navigation_mark
|
||||||
|
breakwater → wave_barrier
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目的
|
||||||
|
|
||||||
|
使检测系统可以查询:
|
||||||
|
|
||||||
|
```
|
||||||
|
所有 collision 对象
|
||||||
|
所有 grounding 对象
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 04 — Detection Object View v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
创建统一检测对象视图。
|
||||||
|
|
||||||
|
## 视图
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_detection_objects
|
||||||
|
```
|
||||||
|
|
||||||
|
## 字段
|
||||||
|
|
||||||
|
```
|
||||||
|
feature_id
|
||||||
|
geometry
|
||||||
|
canonical_object_type
|
||||||
|
object_family
|
||||||
|
detection_class
|
||||||
|
capability
|
||||||
|
detection_key
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据来源
|
||||||
|
|
||||||
|
```
|
||||||
|
pbf_relayer_candidates
|
||||||
|
navsea_object_taxonomy
|
||||||
|
navsea_object_capabilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## 约束
|
||||||
|
|
||||||
|
该视图:
|
||||||
|
|
||||||
|
```
|
||||||
|
只读
|
||||||
|
不修改原始数据
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 05 — Spatial Index Builder v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
优化空间查询性能。
|
||||||
|
|
||||||
|
当前数据规模:
|
||||||
|
|
||||||
|
```
|
||||||
|
~6.6 million features
|
||||||
|
```
|
||||||
|
|
||||||
|
## Codex 任务
|
||||||
|
|
||||||
|
自动分析 geometry 字段并创建空间索引。
|
||||||
|
|
||||||
|
优化查询:
|
||||||
|
|
||||||
|
```
|
||||||
|
半径查询
|
||||||
|
航线走廊查询
|
||||||
|
船舶前方查询
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目标对象
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_detection_objects
|
||||||
|
semantic feature tables
|
||||||
|
```
|
||||||
|
|
||||||
|
## 约束
|
||||||
|
|
||||||
|
不得改变表结构。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 08 — Semantic Layer Coverage Report v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
统计语义系统覆盖率。
|
||||||
|
|
||||||
|
检测:
|
||||||
|
|
||||||
|
```
|
||||||
|
未分类对象
|
||||||
|
layer 覆盖率
|
||||||
|
canonical_object 覆盖率
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出表
|
||||||
|
|
||||||
|
```
|
||||||
|
semantic_layer_coverage
|
||||||
|
canonical_object_coverage
|
||||||
|
source_layer_coverage
|
||||||
|
unclassified_features
|
||||||
|
coverage_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用途
|
||||||
|
|
||||||
|
发现:
|
||||||
|
|
||||||
|
```
|
||||||
|
规则遗漏
|
||||||
|
新对象
|
||||||
|
数据异常
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 09 — Vector Tile Semantic Drift Detection v2
|
||||||
|
|
||||||
|
## 任务目标
|
||||||
|
|
||||||
|
检测 PBF 更新导致的语义漂移。
|
||||||
|
|
||||||
|
## 检测内容
|
||||||
|
|
||||||
|
```
|
||||||
|
新对象类型
|
||||||
|
source_layer 变化
|
||||||
|
geometry 类型变化
|
||||||
|
属性结构变化
|
||||||
|
语义规则冲突
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出表
|
||||||
|
|
||||||
|
```
|
||||||
|
semantic_drift_summary
|
||||||
|
new_object_types
|
||||||
|
source_layer_drift
|
||||||
|
geometry_drift
|
||||||
|
attribute_drift
|
||||||
|
rule_conflict_report
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用途
|
||||||
|
|
||||||
|
确保新 PBF 数据不会破坏语义系统。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 推荐执行顺序
|
||||||
|
|
||||||
|
```
|
||||||
|
1 Semantic Overlay Validation
|
||||||
|
2 Semantic Coverage Report
|
||||||
|
3 Object Taxonomy
|
||||||
|
4 Object Capability
|
||||||
|
5 Detection Object View
|
||||||
|
6 Spatial Index Builder
|
||||||
|
7 Drift Detection
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 最终服务器结构
|
||||||
|
|
||||||
|
完成全部任务后服务器语义系统结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
PBF
|
||||||
|
↓
|
||||||
|
Semantic Reconstruction
|
||||||
|
↓
|
||||||
|
Taxonomy
|
||||||
|
↓
|
||||||
|
Capability
|
||||||
|
↓
|
||||||
|
Detection Objects
|
||||||
|
↓
|
||||||
|
Spatial Index
|
||||||
|
↓
|
||||||
|
Validation / Drift Monitoring
|
||||||
|
```
|
||||||
|
|
||||||
|
该结构确保:
|
||||||
|
|
||||||
|
```
|
||||||
|
PBF 稳定
|
||||||
|
原始属性保留
|
||||||
|
style.json 可安全重建
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
107
tasks/pbf/NavSea_Mapping_DB_Registry_v1.md
Normal file
107
tasks/pbf/NavSea_Mapping_DB_Registry_v1.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# NavSea 映射关系数据库固化方案 v1
|
||||||
|
|
||||||
|
版本:v1
|
||||||
|
用途:把“旧版原始字段 / 层名 / 取值”和“新版标准化字段 / 语义 / 渲染语义”的关系正式固化到数据库里,作为后续规则审计、问题排查和构建回放的基础。
|
||||||
|
|
||||||
|
## 1 当前落地位置
|
||||||
|
|
||||||
|
本版已在 `pbf_analysis` 中建立以下正式对象:
|
||||||
|
|
||||||
|
- `navsea_rule_bundle`
|
||||||
|
- `navsea_source_layer_rules`
|
||||||
|
- `navsea_field_name_rules`
|
||||||
|
- `navsea_field_value_rules`
|
||||||
|
- `navsea_taxonomy_rules`
|
||||||
|
- `navsea_render_rules`
|
||||||
|
- `navsea_mapping_registry_vw`
|
||||||
|
- `navsea_legacy_to_new_mapping_vw`
|
||||||
|
- `navsea_new_to_legacy_mapping_vw`
|
||||||
|
|
||||||
|
## 2 规则源文件
|
||||||
|
|
||||||
|
数据库中的规则来自以下版本化文件:
|
||||||
|
|
||||||
|
- [navsea_rule_bundle_v1.yaml](/root/weather/tasks/pbf/mappings/navsea_rule_bundle_v1.yaml)
|
||||||
|
- [navsea_source_layer_rules_v1.yaml](/root/weather/tasks/pbf/mappings/navsea_source_layer_rules_v1.yaml)
|
||||||
|
- [navsea_field_name_rules_v1.yaml](/root/weather/tasks/pbf/mappings/navsea_field_name_rules_v1.yaml)
|
||||||
|
- [navsea_field_value_rules_v1.yaml](/root/weather/tasks/pbf/mappings/navsea_field_value_rules_v1.yaml)
|
||||||
|
- [navsea_taxonomy_rules_v1.yaml](/root/weather/tasks/pbf/mappings/navsea_taxonomy_rules_v1.yaml)
|
||||||
|
- [navsea_render_rules_v1.yaml](/root/weather/tasks/pbf/mappings/navsea_render_rules_v1.yaml)
|
||||||
|
|
||||||
|
## 3 同步脚本
|
||||||
|
|
||||||
|
同步脚本:
|
||||||
|
|
||||||
|
- [navsea_mapping_registry_sync.py](/root/weather/navsea_mapping_registry_sync.py)
|
||||||
|
- [navsea_mapping_registry.py](/root/weather/navsea_mapping_registry.py)
|
||||||
|
|
||||||
|
作用:
|
||||||
|
|
||||||
|
- 建表
|
||||||
|
- 从 YAML 导入 bundle / layer / field / value / taxonomy / render 规则
|
||||||
|
- 生成统一审计视图
|
||||||
|
- 在运行时由 builder 直接读取数据库规则,不再在多个文件中重复维护同一份映射常量
|
||||||
|
|
||||||
|
## 4 双向查询口径
|
||||||
|
|
||||||
|
旧版查新版:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT *
|
||||||
|
FROM navsea_legacy_to_new_mapping_vw
|
||||||
|
WHERE legacy_key = '分類番号' AND legacy_value = '428';
|
||||||
|
```
|
||||||
|
|
||||||
|
新版查旧版:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT *
|
||||||
|
FROM navsea_new_to_legacy_mapping_vw
|
||||||
|
WHERE new_key = 'chart_fill_style' AND new_value = 'fish_reef_area';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5 当前 v1 覆盖内容
|
||||||
|
|
||||||
|
- `source-layer` 日文层名到标准层名
|
||||||
|
- 旧字段名到标准字段名
|
||||||
|
- 关键旧字段取值到标准值
|
||||||
|
- taxonomy 规则
|
||||||
|
- render 规则
|
||||||
|
|
||||||
|
## 6 当前边界
|
||||||
|
|
||||||
|
本版已经把映射关系正式固化到了数据库里,并且 builder 已开始直接消费数据库规则,当前已接入:
|
||||||
|
|
||||||
|
- `source-layer` 标准化
|
||||||
|
- 旧字段值到新字段值的标准化
|
||||||
|
- 首批 render 规则
|
||||||
|
|
||||||
|
但还没有把 builder 的全部 `infer_*` 分支完全改造成“只从数据库执行规则”。
|
||||||
|
当前状态是:
|
||||||
|
|
||||||
|
- 规则资产已经固化在数据库
|
||||||
|
- 代码侧只保留兜底逻辑,数据库规则优先
|
||||||
|
- 后续可以逐步把执行层切到数据库规则驱动
|
||||||
|
|
||||||
|
## 6.1 未决对象处理规则
|
||||||
|
|
||||||
|
本体系中的“兜底”不等于“静默归入一个大筐”。
|
||||||
|
|
||||||
|
当前执行约束是:
|
||||||
|
|
||||||
|
- 如果没有命中特定 render 规则,不允许把 `REN-FALLBACK` 当成正常命中结果
|
||||||
|
- 这类对象会被标记为 `trace_status = needs_review`
|
||||||
|
- builder 会在每次构建后自动输出一份 `mapping_audit` 报告
|
||||||
|
- 报告中会列出未决对象的来源层、对象类型、几何类型、样本 `fid` 和原因
|
||||||
|
|
||||||
|
这条约束的目的是:
|
||||||
|
|
||||||
|
- 不让未覆盖对象悄悄混进“正常渲染”
|
||||||
|
- 不让规则缺口被一个泛化 fallback 掩盖
|
||||||
|
- 让后续补规则时有明确的对象清单
|
||||||
|
|
||||||
|
## 7 建议下一步
|
||||||
|
|
||||||
|
- 增加 `navsea_feature_rule_trace` 的数据库写入
|
||||||
|
- 让 builder 在 engineering 口径记录实际命中的 `taxonomy_rule_id` / `render_rule_id`
|
||||||
|
- 逐步把 `infer_*` 硬编码分支迁移到规则表执行
|
||||||
153
tasks/pbf/NavSea_Object_Taxonomy_Manual_Review_Task.md
Normal file
153
tasks/pbf/NavSea_Object_Taxonomy_Manual_Review_Task.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# NavSea Codex Task
|
||||||
|
|
||||||
|
# Object Taxonomy Manual Review Package Generator
|
||||||
|
|
||||||
|
Architecture: NavSea V11 / Codex6 Scope: Server Side Documentation /
|
||||||
|
Review Preparation
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Task Name
|
||||||
|
|
||||||
|
NavSea_Object_Taxonomy_Manual_Review_Package
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Task Goal
|
||||||
|
|
||||||
|
Generate a manual review package for NavSea object taxonomy definition.
|
||||||
|
|
||||||
|
This task prepares a human-reviewable object classification package so
|
||||||
|
that the user can confirm:
|
||||||
|
|
||||||
|
1. canonical object naming
|
||||||
|
2. object family assignment
|
||||||
|
3. detection class assignment
|
||||||
|
4. whether mixed geometry objects should be unified or split
|
||||||
|
5. whether some source layers are container layers rather than semantic
|
||||||
|
layers
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Inputs
|
||||||
|
|
||||||
|
object_catalog object_type_stats feature_semantic object_type_candidates
|
||||||
|
canonical_layer_rules canonical_object_rules pbf_source_object_stats
|
||||||
|
pbf_detection_catalog
|
||||||
|
|
||||||
|
Optional reference:
|
||||||
|
|
||||||
|
style_layers style_mapping
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Output
|
||||||
|
|
||||||
|
NavSea_Object_Taxonomy_Manual_Review.md
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Required Structure of the Review Document
|
||||||
|
|
||||||
|
## 1 Review Objective
|
||||||
|
|
||||||
|
Explain that this package is for manual confirmation of:
|
||||||
|
|
||||||
|
- canonical object type naming
|
||||||
|
- family grouping
|
||||||
|
- detection grouping
|
||||||
|
- geometry unification or splitting
|
||||||
|
- ambiguous object handling
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 2 Review Priority Groups
|
||||||
|
|
||||||
|
Priority A --- Immediate navigation safety relevance
|
||||||
|
|
||||||
|
Examples: hazards, wrecks, rocks, reefs, dangerous obstacles, navigation
|
||||||
|
marks, channels, anchorage, restricted areas, fishery hazards
|
||||||
|
|
||||||
|
Priority B --- Navigation context / operational relevance
|
||||||
|
|
||||||
|
Examples: boundaries, facilities, subsea cables / pipelines, port
|
||||||
|
structures, land structures relevant to navigation
|
||||||
|
|
||||||
|
Priority C --- Background / rendering objects
|
||||||
|
|
||||||
|
Examples: labels, land background, rendering support layers
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 3 Manual Review Candidate Table
|
||||||
|
|
||||||
|
Required columns:
|
||||||
|
|
||||||
|
review_priority layer_name class_name shape_name vt_layer
|
||||||
|
observed_geom_types total_feature_count suggested_canonical_object_type
|
||||||
|
suggested_canonical_family suggested_detection_class
|
||||||
|
suggested_review_reason needs_manual_review
|
||||||
|
|
||||||
|
needs_manual_review values:
|
||||||
|
|
||||||
|
YES NO
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 4 Ambiguity Categories
|
||||||
|
|
||||||
|
### Multi-Geometry Ambiguity
|
||||||
|
|
||||||
|
Same object appearing as multiple geometry types.
|
||||||
|
|
||||||
|
### Navigation Meaning Ambiguity
|
||||||
|
|
||||||
|
Objects implying collision / grounding / entanglement risk.
|
||||||
|
|
||||||
|
### Source Layer Container Ambiguity
|
||||||
|
|
||||||
|
Layers that may act as container or rendering layers.
|
||||||
|
|
||||||
|
### Regulatory / Boundary Ambiguity
|
||||||
|
|
||||||
|
Objects representing legal or navigational boundaries.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 5 First-Pass Recommended Review Scope
|
||||||
|
|
||||||
|
Focus first on classes similar to:
|
||||||
|
|
||||||
|
航行危険障害物 投錨注意障害物 航路標識点 航路境界等 錨泊地等
|
||||||
|
施設・境界線等 海岸線等深線等 漁具定置箇所
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 6 Proposed Review Decisions
|
||||||
|
|
||||||
|
Each candidate object should include:
|
||||||
|
|
||||||
|
Candidate Object Suggested canonical_object_type Suggested
|
||||||
|
canonical_family Suggested detection_class Reason Manual confirmation
|
||||||
|
required
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 7 Layer Review Appendix
|
||||||
|
|
||||||
|
Columns:
|
||||||
|
|
||||||
|
source_layer semantic_granularity current_rule_guess review_reason
|
||||||
|
needs_manual_review
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 8 Review Outcome Guidance
|
||||||
|
|
||||||
|
The user will confirm:
|
||||||
|
|
||||||
|
1 Confirm canonical object naming 2 Confirm family grouping 3 Confirm
|
||||||
|
detection grouping 4 Confirm geometry unification 5 Confirm container
|
||||||
|
layers
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
36
tasks/pbf/NavSea_PBF_schema_v1.md
Normal file
36
tasks/pbf/NavSea_PBF_schema_v1.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# NavSea PBF Schema v1
|
||||||
|
|
||||||
|
用途:定义 NavSea Vector Tile 的数据结构。
|
||||||
|
|
||||||
|
## 顶层字段
|
||||||
|
|
||||||
|
- identity
|
||||||
|
- chart_semantics
|
||||||
|
- labels
|
||||||
|
- light
|
||||||
|
- hazard
|
||||||
|
- area
|
||||||
|
- source_legacy
|
||||||
|
|
||||||
|
## identity
|
||||||
|
|
||||||
|
- fid
|
||||||
|
- canonical_object_type
|
||||||
|
- canonical_family
|
||||||
|
- detection_key
|
||||||
|
- render_layer
|
||||||
|
|
||||||
|
## chart_semantics
|
||||||
|
|
||||||
|
- chart_render_type
|
||||||
|
- chart_symbol_family
|
||||||
|
- chart_symbol_code
|
||||||
|
- chart_priority
|
||||||
|
- chart_visibility_min
|
||||||
|
- chart_visibility_max
|
||||||
|
|
||||||
|
## labels
|
||||||
|
|
||||||
|
- chart_label_text
|
||||||
|
- chart_label_subtext
|
||||||
|
- chart_label_anchor
|
||||||
226
tasks/pbf/NavSea_PBF_schema_v2.md
Normal file
226
tasks/pbf/NavSea_PBF_schema_v2.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# NavSea PBF Schema v2
|
||||||
|
|
||||||
|
版本:v2 Draft
|
||||||
|
用途:定义 NavSea 交付用 Vector Tile 的字段结构,兼顾渲染、碰撞、检索和旧海图兼容。
|
||||||
|
|
||||||
|
## 1 设计目标
|
||||||
|
|
||||||
|
v2 schema 要同时满足四件事:
|
||||||
|
|
||||||
|
- 保持旧海图渲染内容不明显缩水
|
||||||
|
- 支撑新的语义渲染体系
|
||||||
|
- 支撑碰撞检测和检索
|
||||||
|
- 控制字段职责,避免样式继续直接依赖大量原始编码
|
||||||
|
|
||||||
|
## 2 字段分组
|
||||||
|
|
||||||
|
v2 建议把交付字段分成 8 组:
|
||||||
|
|
||||||
|
- identity
|
||||||
|
- taxonomy
|
||||||
|
- render_semantics
|
||||||
|
- labels
|
||||||
|
- light
|
||||||
|
- hazard_area
|
||||||
|
- normalized_numeric
|
||||||
|
- source_legacy
|
||||||
|
|
||||||
|
## 3 identity
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fid` | string | 是 | NavSea 自有对象标识,使用可逆算法生成的 16 进制字符串 |
|
||||||
|
| `fid_legacy_raw` | integer/string | 否 | 旧版原始 `fid`,建议只保留在 engineering 口径或 SQL trace |
|
||||||
|
| `fid_algo_id` | string | 否 | `fid` 生成算法标识,例如 `feistel32_aes_cyclewalk_v1` |
|
||||||
|
| `fid_key_id` | string | 否 | `fid` 生成时使用的密钥标识 |
|
||||||
|
| `render_layer` | string | 是 | 交付瓦片中的目标图层名 |
|
||||||
|
| `source_layer` | string | 否 | 原始图层名,建议保留到工程版或过渡期交付版 |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 对“只做图片渲染”的产品可以弱化 `fid`
|
||||||
|
- 对当前 NavSea 的交付目标,不建议删除 `fid`
|
||||||
|
- 不建议继续直接暴露旧版原始 `fid`
|
||||||
|
- 建议基于 `tasks/pbf/fiddecode.md` 的可逆算法生成 NavSea 自有 `fid`
|
||||||
|
- 工程实现建议使用 `signed int32` 全域作为 legacy `fid` 的可逆映射范围
|
||||||
|
|
||||||
|
## 4 taxonomy
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `canonical_family` | string | 是 | 大类语义 |
|
||||||
|
| `canonical_object_type` | string | 是 | 细粒度对象类型 |
|
||||||
|
| `detection_key` | string | 是 | 用于检测和规则系统的稳定分类键 |
|
||||||
|
| `semantic_key` | string | 否 | 内部语义归并键,过渡期可保留 |
|
||||||
|
|
||||||
|
## 5 render_semantics
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `chart_render_type` | string | 是 | `symbol` / `line` / `fill` / `label` / `none` |
|
||||||
|
| `chart_symbol_family` | string | 否 | 符号族,例如 `navigation_light`、`hazard`、`depth_mark` |
|
||||||
|
| `chart_symbol_code` | string | 否 | 稳定符号码,例如 `lighthouse`、`rock_awash` |
|
||||||
|
| `chart_line_style` | string | 否 | 线型预设,例如 `boundary_dashed`、`contour_major` |
|
||||||
|
| `chart_fill_style` | string | 否 | 面填充预设,例如 `depth_zone_0_5`、`reef_area` |
|
||||||
|
| `chart_text_style` | string | 否 | 文字样式预设,例如 `place_name_sea`、`light_label` |
|
||||||
|
| `chart_priority` | integer | 是 | 绘制优先级,用于层级和碰撞 |
|
||||||
|
| `chart_visibility_min` | integer | 否 | 最小显示 zoom |
|
||||||
|
| `chart_visibility_max` | integer | 否 | 最大显示 zoom |
|
||||||
|
| `chart_collision_group` | string | 否 | 文本或符号碰撞组,例如 `place_label`、`light_label` |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `chart_render_type` 和 `chart_priority` 建议作为最基础必填渲染字段
|
||||||
|
- 样式层优先读取 `chart_*` 字段,不再优先读取旧编码
|
||||||
|
|
||||||
|
## 6 labels
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `chart_label_text` | string | 否 | 主标注文本 |
|
||||||
|
| `chart_label_subtext` | string | 否 | 次级标注,例如灯略记 |
|
||||||
|
| `chart_label_anchor` | string | 否 | `top` / `bottom` / `left` / `right` / `center` |
|
||||||
|
| `chart_label_dx` | number | 否 | X 偏移 |
|
||||||
|
| `chart_label_dy` | number | 否 | Y 偏移 |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 旧样式中的 `名称`、`名称補助`、`日本語地名`、`英文字地名` 仍可保留
|
||||||
|
- 新样式应优先使用 `chart_label_*`
|
||||||
|
|
||||||
|
## 7 light
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `light_color_code` | string | 否 | `white` / `red` / `green` / `yellow` / `mixed` |
|
||||||
|
| `light_character_code` | string | 否 | `Fl` / `Oc` / `Iso` / `F` / `V-AIS` |
|
||||||
|
| `light_sector_mode` | string | 否 | `sector` / `omni` / `none` |
|
||||||
|
| `light_arc_code` | string | 否 | 灯弧附加表达,是否交付由产品决定 |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 灯弧本身可以不渲染
|
||||||
|
- 但灯质、灯色、略记对检索和标签仍有价值
|
||||||
|
|
||||||
|
## 8 hazard_area
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `hazard_class` | string | 否 | `rock` / `reef` / `wreck` / `obstruction` / `shoal` |
|
||||||
|
| `hazard_severity` | string | 否 | `critical` / `major` / `minor` / `context` |
|
||||||
|
| `area_usage_class` | string | 否 | `anchorage` / `route` / `fishery` / `restricted` / `water` |
|
||||||
|
|
||||||
|
## 9 normalized_numeric
|
||||||
|
|
||||||
|
建议把部分旧数值字段归一化,方便检索和规则判断。
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 来源建议 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `depth_value_m` | number | 否 | 由 `水深値(m)` 归一化 |
|
||||||
|
| `clearance_height_m` | number | 否 | 由 `高さ(m)` 归一化 |
|
||||||
|
| `least_depth_m` | number | 否 | 由 `高さ/深度(m)` 中深度语义归一化 |
|
||||||
|
| `bearing_deg` | number | 否 | 由 `角度` 归一化 |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 这些字段主要服务于检索、检测和统一显示
|
||||||
|
- 原始字段在过渡期可并存
|
||||||
|
|
||||||
|
## 10 source_legacy
|
||||||
|
|
||||||
|
以下字段是根据当前旧 `style.json` 的实际依赖,建议在 v2 过渡期继续保留的旧字段:
|
||||||
|
|
||||||
|
- `分類番号`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `灯色`
|
||||||
|
- `灯略記`
|
||||||
|
- `明弧/分孤`
|
||||||
|
- `表示位置`
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `日本語地名`
|
||||||
|
- `英文字地名`
|
||||||
|
- `水深値(m)`
|
||||||
|
- `高さ(m)`
|
||||||
|
- `高さ/深度(m)`
|
||||||
|
- `角度`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 当前旧样式实际还使用了船舶动态字段 `cog`、`sog`、`hdg`、`length`、`rot`、`delay`
|
||||||
|
- 这些字段来自其他源,不属于 NavSea 静态海图对象 schema 的核心范围
|
||||||
|
|
||||||
|
## 11 v2 最低交付要求
|
||||||
|
|
||||||
|
每个 NavSea feature 至少应满足:
|
||||||
|
|
||||||
|
- `fid`
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `render_layer`
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_priority`
|
||||||
|
|
||||||
|
## 12 工程版与交付版
|
||||||
|
|
||||||
|
建议分两种输出口径:
|
||||||
|
|
||||||
|
### 12.1 delivery schema
|
||||||
|
|
||||||
|
用途:前端渲染、检索、碰撞、正式交付。
|
||||||
|
|
||||||
|
保留:
|
||||||
|
|
||||||
|
- identity
|
||||||
|
- taxonomy
|
||||||
|
- render_semantics
|
||||||
|
- labels
|
||||||
|
- light
|
||||||
|
- hazard_area
|
||||||
|
- normalized_numeric
|
||||||
|
- 关键 `source_legacy`
|
||||||
|
|
||||||
|
### 12.2 engineering schema
|
||||||
|
|
||||||
|
用途:调试、抽查、问题复盘、映射核验。
|
||||||
|
|
||||||
|
在 delivery schema 基础上可额外保留:
|
||||||
|
|
||||||
|
- `fid_legacy_raw`
|
||||||
|
- `fid_algo_id`
|
||||||
|
- `fid_key_id`
|
||||||
|
- 更完整的原始 `at`
|
||||||
|
- 更多 `source_layer` / 原始属性
|
||||||
|
- 临时校验字段
|
||||||
|
|
||||||
|
## 13 v2 与当前 builder 的差距
|
||||||
|
|
||||||
|
当前 `navsea_tile_builder.py` 已经输出:
|
||||||
|
|
||||||
|
- 原始全部属性
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `canonical_family`
|
||||||
|
- `semantic_key`
|
||||||
|
- `detection_key`
|
||||||
|
- `render_layer`
|
||||||
|
|
||||||
|
当前尚未系统输出的 v2 关键字段包括:
|
||||||
|
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_text_style`
|
||||||
|
- `chart_priority`
|
||||||
|
- `chart_visibility_min`
|
||||||
|
- `chart_visibility_max`
|
||||||
|
- `chart_label_*`
|
||||||
|
- `light_*`
|
||||||
|
- `hazard_*`
|
||||||
|
- `area_usage_class`
|
||||||
|
- `normalized_numeric`
|
||||||
|
|
||||||
|
这部分是 builder 下一阶段的主要补齐目标。
|
||||||
331
tasks/pbf/NavSea_PBF_v1_渲染语义契约草案.md
Normal file
331
tasks/pbf/NavSea_PBF_v1_渲染语义契约草案.md
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
# NavSea PBF v1 渲染语义契约草案
|
||||||
|
|
||||||
|
版本:Draft v1
|
||||||
|
用途:用于 NavSea PBF 重构讨论与 Codex 实现参考
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1 重构目标
|
||||||
|
|
||||||
|
NavSea 当前 PBF 渲染依赖大量旧海图编码字段,例如:
|
||||||
|
|
||||||
|
- 分類番号
|
||||||
|
- 形状分類番号
|
||||||
|
- 表示用番号
|
||||||
|
- 灯色
|
||||||
|
- 灯略記
|
||||||
|
- 明弧 / 分孤
|
||||||
|
- 表示位置
|
||||||
|
|
||||||
|
这些字段本质上是 **旧海图绘制编码**,而不是稳定的语义描述。
|
||||||
|
|
||||||
|
当前系统同时存在两种表达:
|
||||||
|
|
||||||
|
1. 旧海图渲染编码
|
||||||
|
2. 新的语义分类字段
|
||||||
|
|
||||||
|
但缺少一层专门表达 **“海图上应该如何表达该对象”** 的语义层。
|
||||||
|
|
||||||
|
因此本重构目标是建立:
|
||||||
|
|
||||||
|
NavSea PBF 的稳定 **渲染语义契约层**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2 三层模型
|
||||||
|
|
||||||
|
NavSea 渲染体系应拆分为三层:
|
||||||
|
|
||||||
|
## A 层:对象语义层
|
||||||
|
|
||||||
|
回答:
|
||||||
|
|
||||||
|
**这个对象是什么**
|
||||||
|
|
||||||
|
示例字段:
|
||||||
|
|
||||||
|
- canonical_object_type
|
||||||
|
- canonical_family
|
||||||
|
- detection_key
|
||||||
|
|
||||||
|
示例值:
|
||||||
|
|
||||||
|
- 港湾灯台
|
||||||
|
- 暗岩
|
||||||
|
- 魚礁
|
||||||
|
- 港則法境界
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B 层:渲染语义层
|
||||||
|
|
||||||
|
回答:
|
||||||
|
|
||||||
|
**在海图上应该如何表达**
|
||||||
|
|
||||||
|
示例字段:
|
||||||
|
|
||||||
|
- chart_render_type
|
||||||
|
- chart_symbol_family
|
||||||
|
- chart_symbol_code
|
||||||
|
- chart_priority
|
||||||
|
- chart_visibility_min
|
||||||
|
- chart_visibility_max
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C 层:样式实现层
|
||||||
|
|
||||||
|
由 MapLibre Style 决定:
|
||||||
|
|
||||||
|
- 颜色
|
||||||
|
- 线型
|
||||||
|
- 图标
|
||||||
|
- 填充
|
||||||
|
- 文字样式
|
||||||
|
|
||||||
|
样式层应尽量只依赖:
|
||||||
|
|
||||||
|
- chart_* 渲染语义字段
|
||||||
|
- 少量 legacy 字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3 建议的核心渲染字段
|
||||||
|
|
||||||
|
## 3.1 主渲染字段
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|-----|------|------|
|
||||||
|
| chart_render_type | string | symbol / line / fill / label / none |
|
||||||
|
| chart_symbol_family | string | 符号族 |
|
||||||
|
| chart_symbol_code | string | 符号代码 |
|
||||||
|
| chart_priority | integer | 渲染优先级 |
|
||||||
|
| chart_visibility_min | integer | 最小显示 zoom |
|
||||||
|
| chart_visibility_max | integer | 最大显示 zoom |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2 标签字段
|
||||||
|
|
||||||
|
| 字段 | 类型 |
|
||||||
|
|-----|------|
|
||||||
|
| chart_label_text | string |
|
||||||
|
| chart_label_subtext | string |
|
||||||
|
| chart_label_anchor | string |
|
||||||
|
| chart_label_dx | number |
|
||||||
|
| chart_label_dy | number |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.3 灯标字段
|
||||||
|
|
||||||
|
| 字段 | 类型 |
|
||||||
|
|-----|------|
|
||||||
|
| light_color_code | string |
|
||||||
|
| light_character_code | string |
|
||||||
|
| light_sector_mode | string |
|
||||||
|
| light_arc_code | string |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
light_color_code = white
|
||||||
|
light_character_code = Fl
|
||||||
|
light_sector_mode = sector
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.4 危险物字段
|
||||||
|
|
||||||
|
| 字段 | 类型 |
|
||||||
|
|-----|------|
|
||||||
|
| hazard_class | string |
|
||||||
|
| hazard_severity | string |
|
||||||
|
|
||||||
|
hazard_class 示例:
|
||||||
|
|
||||||
|
- rock
|
||||||
|
- reef
|
||||||
|
- wreck
|
||||||
|
- obstruction
|
||||||
|
- shoal
|
||||||
|
|
||||||
|
hazard_severity 示例:
|
||||||
|
|
||||||
|
- critical
|
||||||
|
- major
|
||||||
|
- minor
|
||||||
|
- context
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.5 区域字段
|
||||||
|
|
||||||
|
| 字段 | 类型 |
|
||||||
|
|-----|------|
|
||||||
|
| area_usage_class | string |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
- anchorage
|
||||||
|
- route
|
||||||
|
- fishery
|
||||||
|
- restricted
|
||||||
|
- facility
|
||||||
|
- land
|
||||||
|
- water
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4 不建议直接写入 PBF 的字段
|
||||||
|
|
||||||
|
以下字段如果直接写入 PBF,可能会锁死样式:
|
||||||
|
|
||||||
|
- chart_line_style
|
||||||
|
- chart_fill_style
|
||||||
|
- chart_text_style
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
这些字段已经接近 **样式预设名称**,而不是语义。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
depth_zone_0_5
|
||||||
|
boundary_dashed
|
||||||
|
place_name
|
||||||
|
|
||||||
|
未来若更换视觉风格,会产生耦合。
|
||||||
|
|
||||||
|
因此建议 PBF 只表达:
|
||||||
|
|
||||||
|
- line semantic
|
||||||
|
- fill semantic
|
||||||
|
- label role
|
||||||
|
|
||||||
|
具体视觉样式交由 Style 决定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5 建议保留的 legacy 字段
|
||||||
|
|
||||||
|
为了兼容旧样式与历史数据,建议继续保留:
|
||||||
|
分類番号
|
||||||
|
形状分類番号
|
||||||
|
表示用番号
|
||||||
|
灯色
|
||||||
|
灯略記
|
||||||
|
明弧
|
||||||
|
分孤
|
||||||
|
表示位置
|
||||||
|
名称
|
||||||
|
名称補助
|
||||||
|
日本語地名
|
||||||
|
英文字地名
|
||||||
|
|
||||||
|
用途:
|
||||||
|
|
||||||
|
- 样式兜底
|
||||||
|
- 检索
|
||||||
|
- 历史兼容
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6 Feature 示例
|
||||||
|
|
||||||
|
## 灯台
|
||||||
|
{
|
||||||
|
"fid": 12345678,
|
||||||
|
"canonical_object_type": "港湾灯台",
|
||||||
|
"canonical_family": "navigation_aid",
|
||||||
|
"detection_key": "symbol:港湾灯台",
|
||||||
|
|
||||||
|
"chart_render_type": "symbol",
|
||||||
|
"chart_symbol_family": "navigation_light",
|
||||||
|
"chart_symbol_code": "lighthouse",
|
||||||
|
|
||||||
|
"chart_priority": 900,
|
||||||
|
"chart_visibility_min": 7,
|
||||||
|
|
||||||
|
"chart_label_text": "鷹島灯台",
|
||||||
|
"chart_label_subtext": "Fl W 5s",
|
||||||
|
|
||||||
|
"light_color_code": "white",
|
||||||
|
"light_character_code": "Fl"
|
||||||
|
}## 鱼礁危险区
|
||||||
|
{
|
||||||
|
"fid": 23456789,
|
||||||
|
"canonical_object_type": "魚礁",
|
||||||
|
"canonical_family": "hazard",
|
||||||
|
"detection_key": "mixed:魚礁",
|
||||||
|
|
||||||
|
"chart_render_type": "fill",
|
||||||
|
"chart_symbol_family": "hazard",
|
||||||
|
"chart_symbol_code": "reef_area",
|
||||||
|
|
||||||
|
"hazard_class": "reef",
|
||||||
|
"hazard_severity": "major",
|
||||||
|
|
||||||
|
"chart_priority": 850
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7 建议的实现顺序
|
||||||
|
|
||||||
|
阶段 1:
|
||||||
|
|
||||||
|
出瓦片流程新增 chart_* 字段。
|
||||||
|
|
||||||
|
阶段 2:
|
||||||
|
|
||||||
|
PBF 同时包含:
|
||||||
|
|
||||||
|
- 新 taxonomy 字段
|
||||||
|
- 新 chart semantics
|
||||||
|
- legacy 字段
|
||||||
|
|
||||||
|
阶段 3:
|
||||||
|
|
||||||
|
样式优先读取:
|
||||||
|
chart_render_type
|
||||||
|
chart_symbol_family
|
||||||
|
chart_symbol_code
|
||||||
|
|
||||||
|
缺失时回退 legacy 字段。
|
||||||
|
|
||||||
|
阶段 4:
|
||||||
|
|
||||||
|
逐步减少样式对 legacy 编码依赖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8 第一阶段优先实现对象
|
||||||
|
|
||||||
|
建议优先完成四类对象:
|
||||||
|
|
||||||
|
1 灯标 / 航标
|
||||||
|
2 危险点对象
|
||||||
|
3 危险面对象(鱼礁 / 礁盘 / 障碍区)
|
||||||
|
4 边界对象
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
这些对象最依赖旧编码,也是最复杂的渲染类型。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 9 结论
|
||||||
|
|
||||||
|
NavSea PBF 不应继续依赖旧海图编码作为核心渲染逻辑。
|
||||||
|
|
||||||
|
正确方向是:
|
||||||
|
|
||||||
|
在 **原始属性** 与 **样式实现** 之间建立稳定的 **渲染语义层**。
|
||||||
|
|
||||||
|
该层应:
|
||||||
|
|
||||||
|
- 表达稳定语义
|
||||||
|
- 不锁定具体视觉样式
|
||||||
|
- 支持新旧样式共存
|
||||||
|
- 为检测、检索、渲染提供统一数据面
|
||||||
334
tasks/pbf/NavSea_Reversible_Normalization_Audit_Plan_v1.md
Normal file
334
tasks/pbf/NavSea_Reversible_Normalization_Audit_Plan_v1.md
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# NavSea 可逆标准化审计方案 v1
|
||||||
|
|
||||||
|
版本:v1 Draft
|
||||||
|
用途:定义 NavSea 可逆标准化规则的审计方案,用于验证标准化过程是否完整、可逆、稳定、可重复、对渲染无破坏。
|
||||||
|
|
||||||
|
## 1 审计目标
|
||||||
|
|
||||||
|
本审计方案要验证五件事:
|
||||||
|
|
||||||
|
- 原始信息有没有丢
|
||||||
|
- 标准化规则有没有被正确应用
|
||||||
|
- 每个结果能不能回查
|
||||||
|
- 同一规则重复执行是否稳定
|
||||||
|
- 标准化后渲染和业务能力有没有被破坏
|
||||||
|
|
||||||
|
## 2 审计对象
|
||||||
|
|
||||||
|
审计覆盖以下对象:
|
||||||
|
|
||||||
|
- 规则文件
|
||||||
|
- SQL 规则表
|
||||||
|
- feature trace 表
|
||||||
|
- delivery `pbf`
|
||||||
|
- engineering `pbf`
|
||||||
|
- style 消费关系
|
||||||
|
|
||||||
|
## 3 审计维度
|
||||||
|
|
||||||
|
## 3.1 完整性审计
|
||||||
|
|
||||||
|
检查是否所有需要标准化的对象都进入了规则体系。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- 每个实际出现的 `source_layer_jp` 都有标准化规则
|
||||||
|
- 每个实际出现的关键原始字段都有保留策略
|
||||||
|
- 每个 delivery feature 都有对应 trace
|
||||||
|
|
||||||
|
## 3.2 可逆性审计
|
||||||
|
|
||||||
|
检查是否能从标准值回查原始值。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- `source_layer_std` 是否能映射回 `source_layer_jp`
|
||||||
|
- 标准化字段是否有对应规则 ID
|
||||||
|
- feature 是否能查到 bundle 版本
|
||||||
|
|
||||||
|
## 3.3 稳定性审计
|
||||||
|
|
||||||
|
检查相同输入在相同规则版本下是否得到相同输出。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- 同一输入重复运行结果一致
|
||||||
|
- 同一 `rule_id + revision` 输出不漂移
|
||||||
|
|
||||||
|
## 3.4 渲染兼容审计
|
||||||
|
|
||||||
|
检查标准化后是否破坏当前样式表现。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- style 引用的 layer 是否仍然存在
|
||||||
|
- 关键对象是否仍能显示
|
||||||
|
- 旧兼容表现是否没有异常缩水
|
||||||
|
|
||||||
|
## 3.5 业务可用性审计
|
||||||
|
|
||||||
|
检查标准化后是否仍满足检索、碰撞、点击、排障需求。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- `fid` 是否保留
|
||||||
|
- taxonomy 字段是否保留
|
||||||
|
- 关键内容型日文字段是否保留
|
||||||
|
- trace 是否可回查
|
||||||
|
|
||||||
|
## 4 审计表设计
|
||||||
|
|
||||||
|
建议新增以下审计表:
|
||||||
|
|
||||||
|
- `navsea_normalization_audit_run`
|
||||||
|
- `navsea_normalization_coverage_audit`
|
||||||
|
- `navsea_normalization_reversibility_audit`
|
||||||
|
- `navsea_normalization_stability_audit`
|
||||||
|
- `navsea_render_compatibility_audit`
|
||||||
|
|
||||||
|
## 5 每次审计运行应记录
|
||||||
|
|
||||||
|
建议在 `navsea_normalization_audit_run` 中记录:
|
||||||
|
|
||||||
|
- `audit_run_id`
|
||||||
|
- `bundle_id`
|
||||||
|
- `bundle_version`
|
||||||
|
- `source_snapshot`
|
||||||
|
- `target_scope`
|
||||||
|
- `started_at`
|
||||||
|
- `finished_at`
|
||||||
|
- `status`
|
||||||
|
- `notes`
|
||||||
|
|
||||||
|
## 6 审计检查项
|
||||||
|
|
||||||
|
## 6.1 规则覆盖检查
|
||||||
|
|
||||||
|
目标:确认没有“实际数据里出现,但没有规则”的对象。
|
||||||
|
|
||||||
|
建议 SQL:
|
||||||
|
|
||||||
|
- 所有实际出现的 `source_layer_jp`
|
||||||
|
- 左连接 `navsea_source_layer_rules`
|
||||||
|
- 找出未命中项
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- 未命中数量必须为 `0`
|
||||||
|
|
||||||
|
## 6.2 原值保留检查
|
||||||
|
|
||||||
|
目标:确认原始关键字段没有在工程体系中丢失。
|
||||||
|
|
||||||
|
检查字段:
|
||||||
|
|
||||||
|
- `fid`
|
||||||
|
- `fid_legacy_raw`
|
||||||
|
- `source_layer_jp`
|
||||||
|
- `分類番号`
|
||||||
|
- `形状分類番号`
|
||||||
|
- `表示用番号`
|
||||||
|
- `名称`
|
||||||
|
- `日本語地名`
|
||||||
|
- `at_raw`
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- engineering 口径中必须可回查
|
||||||
|
|
||||||
|
## 6.2.1 `fid` 可逆检查
|
||||||
|
|
||||||
|
目标:确认 NavSea 自有 `fid` 不是旧 `fid` 原样外露,且能可逆回查。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- `delivery` / `engineering` 主 `fid` 是否为 16 进制字符串
|
||||||
|
- `fid_legacy_raw` 是否保留于 engineering / trace
|
||||||
|
- `fid_algo_id` 是否存在
|
||||||
|
- `fid_key_id` 是否存在
|
||||||
|
- 是否可通过 `decrypt_number` 从 `fid_navsea_int` 还原 `fid_legacy_raw`
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- 抽样回解正确率必须为 `100%`
|
||||||
|
- 不允许出现“新 `fid` 与旧 `fid` 只是格式改写”的情况
|
||||||
|
|
||||||
|
## 6.3 规则追溯检查
|
||||||
|
|
||||||
|
目标:确认每个标准化结果都有 trace。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- `source_layer_std` 是否有 `source_layer_rule_id`
|
||||||
|
- `canonical_object_type` 是否有 `taxonomy_rule_id`
|
||||||
|
- `chart_*` 是否有 `render_rule_id`
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- 关键标准化字段 trace 覆盖率必须达到 `100%`
|
||||||
|
|
||||||
|
## 6.4 可逆回查检查
|
||||||
|
|
||||||
|
目标:确认能从标准值回查原值。
|
||||||
|
|
||||||
|
抽样要求:
|
||||||
|
|
||||||
|
- 随机抽样 `fid`
|
||||||
|
- 随机抽样 `source_layer_std`
|
||||||
|
- 随机抽样 `canonical_object_type`
|
||||||
|
- 随机抽样 `chart_symbol_code`
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
- 是否能回查 `fid_legacy_raw`
|
||||||
|
- 是否能回查原始 `source_layer_jp`
|
||||||
|
- 是否能回查 `source_fields_used_json`
|
||||||
|
- 是否能回查规则文件版本
|
||||||
|
|
||||||
|
## 6.5 重放一致性检查
|
||||||
|
|
||||||
|
目标:确认相同输入反复执行不会漂移。
|
||||||
|
|
||||||
|
方法:
|
||||||
|
|
||||||
|
1. 使用同一 bundle 版本运行两次
|
||||||
|
2. 比较以下字段:
|
||||||
|
- `source_layer_std`
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- 差异率必须为 `0`
|
||||||
|
|
||||||
|
## 6.6 delivery / engineering 差异检查
|
||||||
|
|
||||||
|
目标:确认 delivery 简化没有破坏回查能力。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- delivery 是否去掉了一部分原值字段
|
||||||
|
- engineering 是否仍保留完整 trace
|
||||||
|
- 是否能通过 `fid` 从 delivery 回查到 engineering / SQL trace
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- 回查链条必须完整
|
||||||
|
|
||||||
|
## 6.7 渲染覆盖检查
|
||||||
|
|
||||||
|
目标:确认标准化后 style 仍消费当前存在的 `source-layer`。
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- 数据侧实际 `source-layer` 集合
|
||||||
|
- 样式侧引用的 `source-layer` 集合
|
||||||
|
- 差集
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
- 若目标是“全覆盖样式”,差集必须为 `0`
|
||||||
|
- 若目标是“有意忽略样式”,必须给出 ignore 清单和理由
|
||||||
|
|
||||||
|
## 7 审计输出
|
||||||
|
|
||||||
|
每次审计至少输出四类结果:
|
||||||
|
|
||||||
|
### 7.1 覆盖报告
|
||||||
|
|
||||||
|
内容:
|
||||||
|
|
||||||
|
- 实际 `source-layer`
|
||||||
|
- 已映射数量
|
||||||
|
- 未映射数量
|
||||||
|
- 未映射列表
|
||||||
|
|
||||||
|
### 7.2 可逆性报告
|
||||||
|
|
||||||
|
内容:
|
||||||
|
|
||||||
|
- 可回查率
|
||||||
|
- 无 trace feature 数量
|
||||||
|
- 缺失规则 ID 数量
|
||||||
|
|
||||||
|
### 7.3 稳定性报告
|
||||||
|
|
||||||
|
内容:
|
||||||
|
|
||||||
|
- 两次重放差异数
|
||||||
|
- 差异字段分布
|
||||||
|
- 受影响 feature 列表
|
||||||
|
|
||||||
|
### 7.4 渲染兼容报告
|
||||||
|
|
||||||
|
内容:
|
||||||
|
|
||||||
|
- style 消费的 `source-layer`
|
||||||
|
- 数据存在但样式未消费的层
|
||||||
|
- 关键对象抽样截图或抽样核验结果
|
||||||
|
|
||||||
|
## 8 审计频率
|
||||||
|
|
||||||
|
建议分三类:
|
||||||
|
|
||||||
|
### 8.1 每次规则变更后
|
||||||
|
|
||||||
|
必须执行:
|
||||||
|
|
||||||
|
- 规则覆盖检查
|
||||||
|
- 规则追溯检查
|
||||||
|
- 可逆回查检查
|
||||||
|
|
||||||
|
### 8.2 每次大批量构建后
|
||||||
|
|
||||||
|
必须执行:
|
||||||
|
|
||||||
|
- delivery / engineering 差异检查
|
||||||
|
- 渲染覆盖检查
|
||||||
|
- 抽样渲染兼容检查
|
||||||
|
|
||||||
|
### 8.3 每次 bundle 升级前
|
||||||
|
|
||||||
|
必须执行:
|
||||||
|
|
||||||
|
- 重放一致性检查
|
||||||
|
- 差异审计
|
||||||
|
|
||||||
|
## 9 不通过条件
|
||||||
|
|
||||||
|
出现以下任一情况,审计应判定不通过:
|
||||||
|
|
||||||
|
- 实际出现的 `source-layer` 无规则
|
||||||
|
- 关键标准化字段无 trace
|
||||||
|
- 无法从标准值回查原值
|
||||||
|
- 同版本重复执行结果漂移
|
||||||
|
- delivery 无法通过 `fid` 回查 engineering / SQL trace
|
||||||
|
- 样式消费覆盖与目标不符且无明确豁免
|
||||||
|
|
||||||
|
## 10 v1 最低可执行方案
|
||||||
|
|
||||||
|
如果要先快速落地,建议第一阶段至少做到:
|
||||||
|
|
||||||
|
1. 建 `source-layer` 标准化规则表
|
||||||
|
2. 建 `fid` 标准化规则文件和 `fid` 可逆回查检查
|
||||||
|
3. 在 trace 表中补 `source_layer_rule_id`
|
||||||
|
4. 建立 `source_layer_jp -> source_layer_std` 覆盖审计
|
||||||
|
5. 建立 `fid -> trace` 回查检查
|
||||||
|
6. 每次样式更新都跑 `source-layer` 覆盖差集
|
||||||
|
|
||||||
|
## 11 结论
|
||||||
|
|
||||||
|
NavSea 的可逆标准化不能只靠规则文档存在,必须配套审计。
|
||||||
|
|
||||||
|
审计的核心不是“看起来转换成功”,而是验证:
|
||||||
|
|
||||||
|
- 原值未丢
|
||||||
|
- 规则有据
|
||||||
|
- 结果可回查
|
||||||
|
- 重放可一致
|
||||||
|
- 渲染不被破坏
|
||||||
|
|
||||||
|
只有规则和审计同时成立,标准化这件事才算真正严谨。
|
||||||
356
tasks/pbf/NavSea_Reversible_Normalization_Rules_v1.md
Normal file
356
tasks/pbf/NavSea_Reversible_Normalization_Rules_v1.md
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
# NavSea 可逆标准化规则 v1
|
||||||
|
|
||||||
|
版本:v1 Draft
|
||||||
|
用途:定义 NavSea 在“日文原始数据 -> 标准化字段/层名/渲染语义”过程中必须遵循的可逆标准化规则,确保转换严谨、可追溯、可回退、可重复执行。
|
||||||
|
|
||||||
|
## 1 目标
|
||||||
|
|
||||||
|
NavSea 的标准化不是“清洗掉旧数据”,而是:
|
||||||
|
|
||||||
|
- 保留原始信息
|
||||||
|
- 新增标准化结果
|
||||||
|
- 记录完整映射过程
|
||||||
|
- 允许从标准化结果反查原始值
|
||||||
|
|
||||||
|
一句话原则:
|
||||||
|
|
||||||
|
> 标准化只能增加表达层,不能破坏原始信息层。
|
||||||
|
|
||||||
|
## 2 适用范围
|
||||||
|
|
||||||
|
本规则适用于以下对象:
|
||||||
|
|
||||||
|
- 日文 `source-layer`
|
||||||
|
- 原始日文字段名
|
||||||
|
- 原始日文字段值
|
||||||
|
- taxonomy 标准化字段
|
||||||
|
- render 语义字段
|
||||||
|
- 输出 `pbf`
|
||||||
|
- SQL 规则表与 trace 表
|
||||||
|
|
||||||
|
## 3 核心原则
|
||||||
|
|
||||||
|
### 3.1 原始值不可丢
|
||||||
|
|
||||||
|
所有参与标准化的关键输入都必须有原始保留位。
|
||||||
|
|
||||||
|
包括但不限于:
|
||||||
|
|
||||||
|
- 原始 `source-layer`
|
||||||
|
- 原始字段名
|
||||||
|
- 原始字段值
|
||||||
|
- 原始 `at`
|
||||||
|
- 原始 `fid`
|
||||||
|
|
||||||
|
### 3.2 标准化结果必须新增,不可覆盖原值
|
||||||
|
|
||||||
|
禁止直接把原值物理改写成标准值后不留痕迹。
|
||||||
|
|
||||||
|
正确做法:
|
||||||
|
|
||||||
|
- 原值保留
|
||||||
|
- 标准值新增
|
||||||
|
- 二者由规则表与 trace 表关联
|
||||||
|
|
||||||
|
### 3.3 每一步标准化都必须有规则来源
|
||||||
|
|
||||||
|
任何标准化结果都必须能回答:
|
||||||
|
|
||||||
|
- 是哪条规则做的
|
||||||
|
- 规则版本是什么
|
||||||
|
- 使用了哪些原始字段
|
||||||
|
|
||||||
|
### 3.4 每一步标准化都必须可逆
|
||||||
|
|
||||||
|
可逆的含义不是“自动完全恢复所有原始结构”,而是:
|
||||||
|
|
||||||
|
- 能从标准值回查原始值
|
||||||
|
- 能从 feature 回查到规则和版本
|
||||||
|
- 能重演同一批输入得到同一批输出
|
||||||
|
|
||||||
|
### 3.5 delivery 可以简化,engineering 不可失真
|
||||||
|
|
||||||
|
delivery `pbf` 可以只暴露必要字段。
|
||||||
|
但 engineering `pbf` / SQL 审计表必须保留足够信息,支持回查。
|
||||||
|
|
||||||
|
## 4 不可做的事
|
||||||
|
|
||||||
|
以下操作在 v1 规则下视为禁止:
|
||||||
|
|
||||||
|
- 直接删除原始 `source-layer`,且不保留 `source_layer_jp`
|
||||||
|
- 直接删除原始字段值,且不保留原值副本
|
||||||
|
- 在 SQL 里手工改结果,不回写规则文件
|
||||||
|
- 在代码里加临时 if 分支,不形成规则 ID 和版本
|
||||||
|
- 只保留标准化结果,不保留 trace
|
||||||
|
|
||||||
|
## 5 字段分层
|
||||||
|
|
||||||
|
NavSea 标准化后,字段应分成三层。
|
||||||
|
|
||||||
|
### 5.1 原始层
|
||||||
|
|
||||||
|
职责:完整保留输入信息。
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
- `source_layer_jp`
|
||||||
|
- `original_properties_json`
|
||||||
|
- `at_raw`
|
||||||
|
- `fid`
|
||||||
|
- 原始 `分類番号`
|
||||||
|
- 原始 `形状分類番号`
|
||||||
|
- 原始 `表示用番号`
|
||||||
|
- 原始 `名称`
|
||||||
|
- 原始 `日本語地名`
|
||||||
|
|
||||||
|
### 5.2 标准化层
|
||||||
|
|
||||||
|
职责:输出结构化、可消费的标准字段。
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
- `source_layer_std`
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_text_style`
|
||||||
|
|
||||||
|
### 5.3 trace 层
|
||||||
|
|
||||||
|
职责:连接“原始层”和“标准化层”。
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
- `normalization_bundle_id`
|
||||||
|
- `source_layer_rule_id`
|
||||||
|
- `taxonomy_rule_id`
|
||||||
|
- `render_rule_id`
|
||||||
|
- `rule_revision`
|
||||||
|
- `source_fields_used_json`
|
||||||
|
- `trace_status`
|
||||||
|
|
||||||
|
## 6 可逆标准化对象
|
||||||
|
|
||||||
|
## 6.1 `source-layer` 标准化
|
||||||
|
|
||||||
|
建议采用双字段:
|
||||||
|
|
||||||
|
- `source_layer_jp`
|
||||||
|
- `source_layer_std`
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
- `source_layer_jp = P錨泊地等`
|
||||||
|
- `source_layer_std = anchorage_area`
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 原始日文层名不可直接丢弃
|
||||||
|
- 标准层名必须来自规则表,不得手工散落在代码里
|
||||||
|
|
||||||
|
## 6.1.1 `fid` 标准化
|
||||||
|
|
||||||
|
NavSea 不应直接对外继续使用旧版原始 `fid` 作为最终对象 ID。
|
||||||
|
|
||||||
|
规则要求:
|
||||||
|
|
||||||
|
- `delivery` / `engineering` 对外主 `fid` 使用 NavSea 自有 `fid`
|
||||||
|
- NavSea 自有 `fid` 由旧版 `fid` 经 `tasks/pbf/fiddecode.md` 中的可逆算法生成
|
||||||
|
- 生成结果使用 16 进制字符串表达
|
||||||
|
|
||||||
|
建议定义:
|
||||||
|
|
||||||
|
- `fid_legacy_raw`:旧版原始整数 `fid`
|
||||||
|
- `fid_navsea_int`:经过可逆加密后的非负整数
|
||||||
|
- `fid`:最终对外使用的 16 进制字符串,例如固定宽度大写 HEX
|
||||||
|
- `fid_algo_id`:例如 `feistel32_aes_cyclewalk_v1`
|
||||||
|
- `fid_key_id`:用于标识采用了哪把密钥
|
||||||
|
|
||||||
|
建议生成规则:
|
||||||
|
|
||||||
|
1. 读取旧版 `fid`
|
||||||
|
2. 使用 `fiddecode.md` 中的 `encrypt_number` 算法生成 `fid_navsea_int`
|
||||||
|
3. 将 `fid_navsea_int` 编码为固定宽度 16 进制字符串
|
||||||
|
4. 在 engineering / trace 中保留 `fid_legacy_raw`
|
||||||
|
|
||||||
|
补充口径:
|
||||||
|
|
||||||
|
- `fid` 可逆算法沿用 `fiddecode.md` 中的 Feistel + AES cycle-walking 方案
|
||||||
|
- 为保证对真实历史数据全覆盖,工程实现应采用 `signed int32` 全域
|
||||||
|
- 不应把示例文档中的局部样本范围误当成生产限制
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- `fid` 必须可逆回查到 `fid_legacy_raw`
|
||||||
|
- 不允许只保留新 `fid`,却丢失旧 `fid`
|
||||||
|
- 不允许直接把旧 `fid` 原样转成 HEX 当作“新 `fid`”
|
||||||
|
|
||||||
|
## 6.2 原始字段名标准化
|
||||||
|
|
||||||
|
对原始字段名建议也采用双字段表达。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- `field_name_jp = 形状分類番号`
|
||||||
|
- `field_name_std = shape_class_code`
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 这里只是定义映射关系
|
||||||
|
- 不代表 delivery `pbf` 必须立刻把所有原字段名换掉
|
||||||
|
|
||||||
|
## 6.3 原始字段值标准化
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- `灯色 = 1` -> `light_color_code = white`
|
||||||
|
- `形状分類番号 = 335` -> `nav_mark_variant = vais`
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 原值保留
|
||||||
|
- 标准值新增
|
||||||
|
- 必须记录规则 ID
|
||||||
|
|
||||||
|
## 7 保留策略
|
||||||
|
|
||||||
|
## 7.1 必须保留的原始日文内容
|
||||||
|
|
||||||
|
以下属于“内容型日文”,不能因为标准化就删除:
|
||||||
|
|
||||||
|
- `名称`
|
||||||
|
- `名称補助`
|
||||||
|
- `日本語地名`
|
||||||
|
- `灯略記`
|
||||||
|
- 其他对展示、检索、业务解释有价值的原始说明文本
|
||||||
|
|
||||||
|
## 7.2 可标准化但需保留原值的结构型日文
|
||||||
|
|
||||||
|
以下属于“结构型日文”,可以标准化,但必须保留原值:
|
||||||
|
|
||||||
|
- 日文 `source-layer`
|
||||||
|
- 原始字段名
|
||||||
|
- `at` 中的结构型键值
|
||||||
|
- 旧样式直接消费的技术性编码字段
|
||||||
|
|
||||||
|
## 7.3 delivery 可隐藏,engineering 必保留
|
||||||
|
|
||||||
|
对结构型日文建议:
|
||||||
|
|
||||||
|
- delivery `pbf` 可以只暴露标准字段
|
||||||
|
- engineering `pbf` / SQL 审计表必须保留原值和映射关系
|
||||||
|
|
||||||
|
## 8 规则文件要求
|
||||||
|
|
||||||
|
为保证可逆与可重复,以下规则必须文件化:
|
||||||
|
|
||||||
|
- `source-layer` 标准化规则
|
||||||
|
- taxonomy 规则
|
||||||
|
- render 规则
|
||||||
|
- 字段名标准化规则
|
||||||
|
- 字段值标准化规则
|
||||||
|
|
||||||
|
建议目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tasks/pbf/mappings/
|
||||||
|
navsea_fid_rules_v1.yaml
|
||||||
|
navsea_source_layer_rules_v1.yaml
|
||||||
|
navsea_taxonomy_rules_v1.yaml
|
||||||
|
navsea_render_rules_v1.yaml
|
||||||
|
navsea_field_name_rules_v1.yaml
|
||||||
|
navsea_field_value_rules_v1.yaml
|
||||||
|
navsea_rule_bundle_v1.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9 SQL 结构要求
|
||||||
|
|
||||||
|
为保证可逆与可查询,建议至少有以下表:
|
||||||
|
|
||||||
|
- `navsea_rule_bundle`
|
||||||
|
- `navsea_source_layer_rules`
|
||||||
|
- `navsea_taxonomy_rules`
|
||||||
|
- `navsea_render_rules`
|
||||||
|
- `navsea_field_name_rules`
|
||||||
|
- `navsea_field_value_rules`
|
||||||
|
- `navsea_feature_rule_trace`
|
||||||
|
|
||||||
|
## 10 feature trace 最低要求
|
||||||
|
|
||||||
|
每个被标准化的 feature 至少应记录:
|
||||||
|
|
||||||
|
- `feature_id`
|
||||||
|
- `fid`
|
||||||
|
- `fid_legacy_raw`
|
||||||
|
- `fid_navsea_int`
|
||||||
|
- `fid_algo_id`
|
||||||
|
- `fid_key_id`
|
||||||
|
- `source_layer_jp`
|
||||||
|
- `source_layer_std`
|
||||||
|
- `normalization_bundle_id`
|
||||||
|
- `source_layer_rule_id`
|
||||||
|
- `taxonomy_rule_id`
|
||||||
|
- `render_rule_id`
|
||||||
|
- `source_fields_used_json`
|
||||||
|
- `trace_status`
|
||||||
|
|
||||||
|
## 11 delivery / engineering 输出要求
|
||||||
|
|
||||||
|
## 11.1 delivery pbf
|
||||||
|
|
||||||
|
目的:给前端渲染和检索使用。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 可适度简化字段
|
||||||
|
- 但不能影响业务使用
|
||||||
|
- 若隐藏原值,必须仍可通过 SQL trace 回查
|
||||||
|
|
||||||
|
## 11.2 engineering pbf
|
||||||
|
|
||||||
|
目的:审计、复盘、问题定位。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 保留原值
|
||||||
|
- 保留标准值
|
||||||
|
- 保留规则 ID
|
||||||
|
- 保留 bundle 版本
|
||||||
|
|
||||||
|
## 12 可逆性验收条件
|
||||||
|
|
||||||
|
只有同时满足以下条件,才可称为“可逆标准化”:
|
||||||
|
|
||||||
|
1. 能从标准值回查原始值
|
||||||
|
2. 能从 feature 回查规则 ID 和版本
|
||||||
|
3. 能重放同一批输入并得到同一批输出
|
||||||
|
4. 原始内容型日文未被误删
|
||||||
|
5. delivery 的简化不影响 engineering 的回查能力
|
||||||
|
|
||||||
|
## 13 v1 落地建议
|
||||||
|
|
||||||
|
第一阶段建议先做到:
|
||||||
|
|
||||||
|
1. 所有日文 `source-layer` 增加 `source_layer_std`
|
||||||
|
2. `fid` 改为 NavSea 自有 16 进制 `fid`
|
||||||
|
3. 新增 `fid` 规则文件和 `fid` trace 字段
|
||||||
|
4. 新增 `source-layer` 规则文件
|
||||||
|
5. 在 trace 表中记录 `source_layer_rule_id`
|
||||||
|
6. 保留原始内容型日文字段
|
||||||
|
7. 严禁在没有 trace 的情况下直接删除原始结构字段
|
||||||
|
|
||||||
|
## 14 结论
|
||||||
|
|
||||||
|
NavSea 的标准化必须是:
|
||||||
|
|
||||||
|
- 可逆的
|
||||||
|
- 可追溯的
|
||||||
|
- 可重复执行的
|
||||||
|
- 区分原始层、标准化层和 trace 层的
|
||||||
|
|
||||||
|
换句话说,标准化不是“替换”,而是“带证据的映射”。
|
||||||
30
tasks/pbf/NavSea_object_render_mapping_table_v1.md
Normal file
30
tasks/pbf/NavSea_object_render_mapping_table_v1.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# NavSea Object → Render Mapping Table v1
|
||||||
|
|
||||||
|
版本:v1 Draft
|
||||||
|
用途:定义对象语义到渲染语义的映射。
|
||||||
|
|
||||||
|
canonical_object_type → chart_render_type / chart_symbol_family / chart_symbol_code
|
||||||
|
|
||||||
|
## 灯标
|
||||||
|
|
||||||
|
| canonical_object_type | render_type | symbol_family | symbol_code |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 港湾灯台 | symbol | navigation_light | lighthouse |
|
||||||
|
| 灯柱 | symbol | navigation_light | light_beacon |
|
||||||
|
| 灯浮標 | symbol | navigation_light | light_buoy |
|
||||||
|
|
||||||
|
## 危险物
|
||||||
|
|
||||||
|
| canonical_object_type | render_type | symbol_family | symbol_code |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 暗岩 | symbol | hazard | rock_awash |
|
||||||
|
| 岩礁 | symbol | hazard | reef |
|
||||||
|
| 沈船 | symbol | hazard | wreck |
|
||||||
|
| 障害物 | symbol | hazard | obstruction |
|
||||||
|
|
||||||
|
## 区域
|
||||||
|
|
||||||
|
| canonical_object_type | render_type | symbol_family | symbol_code |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 魚礁 | fill | hazard | reef_area |
|
||||||
|
| 錨地 | fill | usage_area | anchorage_area |
|
||||||
165
tasks/pbf/NavSea_object_render_mapping_table_v2.md
Normal file
165
tasks/pbf/NavSea_object_render_mapping_table_v2.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# NavSea Object -> Render Mapping Table v2
|
||||||
|
|
||||||
|
版本:v2 Draft
|
||||||
|
用途:定义对象语义到渲染语义的映射规则,作为 `taxonomy -> render_semantics` 的中间规范。
|
||||||
|
|
||||||
|
## 1 设计原则
|
||||||
|
|
||||||
|
- 先按 `canonical_object_type` 映射
|
||||||
|
- 再按几何类型和旧字段细化具体样式
|
||||||
|
- 样式层优先读取 `chart_*` 字段
|
||||||
|
- 旧字段只保留为细节兜底,不再作为主判定入口
|
||||||
|
|
||||||
|
## 2 输出字段
|
||||||
|
|
||||||
|
映射表的目标输出至少包括:
|
||||||
|
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_text_style`
|
||||||
|
- `chart_priority`
|
||||||
|
- `chart_label_text`
|
||||||
|
- `chart_label_subtext`
|
||||||
|
- `light_color_code`
|
||||||
|
- `light_character_code`
|
||||||
|
- `hazard_class`
|
||||||
|
- `hazard_severity`
|
||||||
|
- `area_usage_class`
|
||||||
|
|
||||||
|
## 3 首批强制覆盖对象
|
||||||
|
|
||||||
|
下表是建议优先落地的 v2 核心映射对象。
|
||||||
|
|
||||||
|
| canonical_family | canonical_object_type | geometry | chart_render_type | symbol_family | symbol_or_style | priority | legacy_detail_dependency |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| navigation_aid | 港湾灯台 | Point | symbol | navigation_light | `chart_symbol_code=lighthouse` | 900 | `灯色` `灯略記` `表示用番号` |
|
||||||
|
| navigation_aid | 灯柱 | Point | symbol | navigation_light | `chart_symbol_code=light_beacon` | 890 | `灯色` `灯略記` `形状分類番号` |
|
||||||
|
| navigation_aid | 灯浮標 | Point | symbol | navigation_light | `chart_symbol_code=light_buoy` | 880 | `灯色` `灯略記` `形状分類番号` |
|
||||||
|
| navigation_aid | AIS航标 | Point | symbol | navigation_light | `chart_symbol_code=vais` | 870 | `表示用番号` |
|
||||||
|
| navigation_aid | 浮標 | Point | symbol | navigation_mark | `chart_symbol_code=buoy` | 860 | `形状分類番号` `表示用番号` |
|
||||||
|
| navigation_aid | 立標 | Point | symbol | navigation_mark | `chart_symbol_code=beacon` | 860 | `形状分類番号` `表示用番号` |
|
||||||
|
| hazard | 暗岩 | Point | symbol | hazard | `chart_symbol_code=rock_awash` | 910 | `分類番号` |
|
||||||
|
| hazard | 岩礁 | Point | symbol | hazard | `chart_symbol_code=reef` | 900 | `分類番号` |
|
||||||
|
| hazard | 礁盘 | Polygon | fill | hazard | `chart_fill_style=reef_area` | 860 | `分類番号` |
|
||||||
|
| hazard | 魚礁 | Polygon | fill | hazard | `chart_fill_style=reef_area` | 850 | `分類番号` `表示用番号` |
|
||||||
|
| hazard | 沈船 | Point | symbol | hazard | `chart_symbol_code=wreck` | 905 | `分類番号` |
|
||||||
|
| hazard | 障害物 | Point | symbol | hazard | `chart_symbol_code=obstruction` | 895 | `分類番号` |
|
||||||
|
| hazard | 潜堤 | Line/Polygon | line/fill | hazard | `chart_line_style=submerged_reef` / `chart_fill_style=hazard_area` | 845 | `分類番号` |
|
||||||
|
| depth | 水深点 | Point | label | depth_mark | `chart_text_style=depth_text` | 700 | `水深値(m)` |
|
||||||
|
| depth | 水深区 | Polygon | fill | depth_zone | `chart_fill_style=depth_zone_*` | 650 | `分類番号` |
|
||||||
|
| depth | 等深线 | LineString | line | contour | `chart_line_style=contour_major_or_minor` | 640 | `表示用番号` |
|
||||||
|
| depth | 概略等深线 | LineString | line | contour | `chart_line_style=contour_overview` | 620 | `表示用番号` |
|
||||||
|
| boundary | 危険界 | LineString | line | boundary | `chart_line_style=hazard_boundary` | 800 | `分類番号` |
|
||||||
|
| boundary | 基本线 | LineString | line | boundary | `chart_line_style=baseline` | 780 | `分類番号` |
|
||||||
|
| boundary | 航路境界 | LineString | line | boundary | `chart_line_style=route_boundary` | 770 | `分類番号` |
|
||||||
|
| usage_area | 航路 | Polygon/LineString | fill/line | usage_area | `chart_fill_style=route_area` / `chart_line_style=route_axis` | 760 | `分類番号` |
|
||||||
|
| usage_area | 錨地 | Polygon | fill | usage_area | `chart_fill_style=anchorage_area` | 750 | `分類番号` |
|
||||||
|
| usage_area | 錨泊地 | Polygon | fill | usage_area | `chart_fill_style=anchorage_area` | 750 | `分類番号` |
|
||||||
|
| usage_area | 漁業区 | Polygon | fill | usage_area | `chart_fill_style=fishery_area` | 740 | `分類番号` |
|
||||||
|
| usage_area | パイロットステーション | Point | symbol | usage_area | `chart_symbol_code=pilot_station` | 735 | `表示用番号` |
|
||||||
|
| facility | 防波堤 | LineString/Polygon | line/fill | facility | `chart_line_style=breakwater` | 730 | `分類番号` |
|
||||||
|
| facility | 橋梁 | LineString | line | facility | `chart_line_style=bridge` | 725 | `高さ(m)` |
|
||||||
|
| facility | 海底电缆 | LineString | line | facility | `chart_line_style=subsea_cable` | 720 | `分類番号` |
|
||||||
|
| facility | 高さ制限 | Point/LineString | label/line | facility | `chart_text_style=clearance_label` | 715 | `高さ(m)` `高さ/深度(m)` |
|
||||||
|
| seabed | 底質 | Point | label | seabed | `chart_text_style=seabed_text` | 500 | `名称` |
|
||||||
|
| place | 海上地名 | Point | label | place | `chart_text_style=place_name_sea` | 450 | `日本語地名` `英文字地名` |
|
||||||
|
| place | 陆上地名 | Point | label | place | `chart_text_style=place_name_land` | 440 | `日本語地名` `英文字地名` |
|
||||||
|
|
||||||
|
## 4 细化规则
|
||||||
|
|
||||||
|
### 4.1 灯标对象
|
||||||
|
|
||||||
|
主映射依据:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
|
||||||
|
细化字段:
|
||||||
|
|
||||||
|
- `灯色 -> light_color_code`
|
||||||
|
- `灯略記 -> light_character_code` 与 `chart_label_subtext`
|
||||||
|
- `名称 -> chart_label_text`
|
||||||
|
- `表示用番号` / `形状分類番号` 决定具体 icon 变体
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 灯弧不必作为核心渲染对象单独保留
|
||||||
|
- 但 `明弧/分孤` 可以继续保留,用于特殊产品或工程校验
|
||||||
|
|
||||||
|
### 4.2 危险物
|
||||||
|
|
||||||
|
主映射依据:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
|
||||||
|
细化输出:
|
||||||
|
|
||||||
|
- `hazard_class`
|
||||||
|
- `hazard_severity`
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- `暗岩`、`沈船` 默认 `hazard_severity >= major`
|
||||||
|
- `魚礁` 按区域危险对象处理,但允许叠加 `area_usage_class=fishery`
|
||||||
|
|
||||||
|
### 4.3 水深和等深线
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 水深点统一转成 `chart_render_type=label`
|
||||||
|
- 水深区统一转成 `chart_render_type=fill`
|
||||||
|
- 等深线和概略等深线统一转成 `chart_render_type=line`
|
||||||
|
|
||||||
|
细化:
|
||||||
|
|
||||||
|
- `表示用番号` 或原 line layer 类型决定 `contour_major` / `contour_minor` / `contour_overview`
|
||||||
|
|
||||||
|
### 4.4 边界和区域
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- “区域本体”与“边界线”分别映射
|
||||||
|
- 航路、锚地、鱼业区优先落到 `usage_area`
|
||||||
|
- 基本线、危险界、航路境界优先落到 `boundary`
|
||||||
|
|
||||||
|
## 5 label 映射规则
|
||||||
|
|
||||||
|
| 对象类型 | chart_label_text | chart_label_subtext | chart_text_style |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 港湾灯台 / 灯柱 / 灯浮標 | `名称` | `灯略記` | `light_label` |
|
||||||
|
| 水深点 | 格式化 `depth_value_m` | 空 | `depth_text` |
|
||||||
|
| 海上地名 | `日本語地名` 优先,其次 `英文字地名` | 空 | `place_name_sea` |
|
||||||
|
| 陆上地名 | `日本語地名` 优先,其次 `英文字地名` | 空 | `place_name_land` |
|
||||||
|
| 高さ制限 | 规范化高度文本 | 空 | `clearance_label` |
|
||||||
|
|
||||||
|
## 6 v2 映射必须输出的附加字段
|
||||||
|
|
||||||
|
除了图形类型本身,映射表还应尽量输出:
|
||||||
|
|
||||||
|
- `chart_priority`
|
||||||
|
- `chart_visibility_min`
|
||||||
|
- `chart_visibility_max`
|
||||||
|
- `chart_collision_group`
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 这些字段能把样式层里的很多散乱 zoom/filter 规则收回到数据侧
|
||||||
|
|
||||||
|
## 7 v2 当前缺口
|
||||||
|
|
||||||
|
相较 v1,本表仍需继续扩充以下对象:
|
||||||
|
|
||||||
|
- 导灯
|
||||||
|
- 灯船
|
||||||
|
- 信号所
|
||||||
|
- 制限区域
|
||||||
|
- 未测深区
|
||||||
|
- 干出区
|
||||||
|
- 海底管线
|
||||||
|
- 岸壁 / 栈桥 / 码头
|
||||||
|
- 海底线
|
||||||
|
- 水文现象对象
|
||||||
|
|
||||||
|
这些建议作为 v2.1 的扩展项。
|
||||||
56
tasks/pbf/NavSea_taxonomy_v1.md
Normal file
56
tasks/pbf/NavSea_taxonomy_v1.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# NavSea Taxonomy v1(对象语义分类体系)
|
||||||
|
|
||||||
|
版本:v1 Draft
|
||||||
|
用途:定义 NavSea 的对象语义分类(Object Taxonomy)。
|
||||||
|
|
||||||
|
taxonomy 只回答一个问题:
|
||||||
|
|
||||||
|
> 这个对象是什么
|
||||||
|
|
||||||
|
而不涉及如何绘制。
|
||||||
|
|
||||||
|
## 结构
|
||||||
|
|
||||||
|
canonical_family → canonical_object_type
|
||||||
|
|
||||||
|
## canonical_family
|
||||||
|
|
||||||
|
- navigation_aid 航标
|
||||||
|
- buoy 浮标
|
||||||
|
- hazard 危险物
|
||||||
|
- depth 水深
|
||||||
|
- boundary 边界
|
||||||
|
- facility 港口设施
|
||||||
|
- usage_area 使用区域
|
||||||
|
- landmark 地标
|
||||||
|
- place 地名
|
||||||
|
- seabed 海床
|
||||||
|
- hydro_feature 水文现象
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
navigation_aid
|
||||||
|
|
||||||
|
- 灯台
|
||||||
|
- 港湾灯台
|
||||||
|
- 灯柱
|
||||||
|
- 灯浮標
|
||||||
|
- AIS航标
|
||||||
|
|
||||||
|
hazard
|
||||||
|
|
||||||
|
- 暗岩
|
||||||
|
- 岩礁
|
||||||
|
- 礁盘
|
||||||
|
- 沈船
|
||||||
|
- 障害物
|
||||||
|
- 危険物
|
||||||
|
- 浅滩
|
||||||
|
|
||||||
|
depth
|
||||||
|
|
||||||
|
- 水深点
|
||||||
|
- 水深区
|
||||||
|
- 等深线
|
||||||
|
|
||||||
|
taxonomy 与渲染无关。
|
||||||
306
tasks/pbf/NavSea_taxonomy_v2.md
Normal file
306
tasks/pbf/NavSea_taxonomy_v2.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
# NavSea Taxonomy v2
|
||||||
|
|
||||||
|
版本:v2 Draft
|
||||||
|
用途:定义 NavSea 的对象语义分类体系(Object Taxonomy),为渲染、检索、碰撞检测和统计分析提供稳定的对象身份。
|
||||||
|
|
||||||
|
taxonomy 只回答一个问题:
|
||||||
|
|
||||||
|
> 这个对象是什么
|
||||||
|
|
||||||
|
taxonomy 不直接回答怎么画,也不直接替代旧海图字段。
|
||||||
|
|
||||||
|
## 1 设计原则
|
||||||
|
|
||||||
|
### 1.1 taxonomy 与 render 分层
|
||||||
|
|
||||||
|
- `canonical_family` / `canonical_object_type` 负责语义归类
|
||||||
|
- `chart_*` 字段负责渲染表达
|
||||||
|
- 旧字段如 `分類番号`、`形状分類番号`、`表示用番号` 仍可在过渡期保留,用于兼容旧样式细节
|
||||||
|
|
||||||
|
### 1.2 family 必须互斥
|
||||||
|
|
||||||
|
`canonical_family` 必须尽量稳定、互斥、可统计。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- `buoy` 不再单独作为 family
|
||||||
|
- 灯浮标、灯柱、灯台、AIS 航标统一归到 `navigation_aid`
|
||||||
|
- `place` 只表示地名,不包含设施或标志物
|
||||||
|
|
||||||
|
### 1.3 object_type 可以细分
|
||||||
|
|
||||||
|
`canonical_object_type` 允许保留更细粒度的对象名,用于:
|
||||||
|
|
||||||
|
- 检索
|
||||||
|
- 规则判断
|
||||||
|
- 语义统计
|
||||||
|
- 到渲染语义的映射
|
||||||
|
|
||||||
|
## 2 taxonomy 层级
|
||||||
|
|
||||||
|
结构:
|
||||||
|
|
||||||
|
`canonical_family -> canonical_object_type`
|
||||||
|
|
||||||
|
## 3 canonical_family 定义
|
||||||
|
|
||||||
|
### 3.1 navigation_aid
|
||||||
|
|
||||||
|
定义:用于导航指示、警示、定位或电子助航的对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 灯台
|
||||||
|
- 港湾灯台
|
||||||
|
- 灯柱
|
||||||
|
- 灯浮標
|
||||||
|
- 導灯
|
||||||
|
- 灯船
|
||||||
|
- 浮標
|
||||||
|
- 立標
|
||||||
|
- AIS航标
|
||||||
|
- 信号所
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 原 v1 中的 `buoy` 并入本 family
|
||||||
|
- “浮标是不是灯浮标”属于 object_type 层的问题,不在 family 层拆开
|
||||||
|
|
||||||
|
### 3.2 hazard
|
||||||
|
|
||||||
|
定义:对航行、锚泊或作业形成风险的对象、区域或状态。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 暗岩
|
||||||
|
- 岩礁
|
||||||
|
- 礁盘
|
||||||
|
- 魚礁
|
||||||
|
- 沈船
|
||||||
|
- 障害物
|
||||||
|
- 危険物
|
||||||
|
- 浅滩
|
||||||
|
- 潜堤
|
||||||
|
- 洞穴/穴
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 鱼礁虽然可能带有利用属性,但从航行风险角度优先归到 `hazard`
|
||||||
|
- 若后续需要区分“危险物”和“人工利用构造”,可通过 `hazard_class` 或 `area_usage_class` 细分,而不是拆 family
|
||||||
|
|
||||||
|
### 3.3 depth
|
||||||
|
|
||||||
|
定义:描述水深、水深区、等深线和相关深度表达的对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 水深点
|
||||||
|
- 水深区
|
||||||
|
- 等深线
|
||||||
|
- 概略等深线
|
||||||
|
- 干出区
|
||||||
|
- 未测深区
|
||||||
|
|
||||||
|
### 3.4 boundary
|
||||||
|
|
||||||
|
定义:描述法律、管理、航路或危险范围边界的对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 基本线
|
||||||
|
- 危険界
|
||||||
|
- 航路境界
|
||||||
|
- 港則法による境界
|
||||||
|
- 制限区域境界
|
||||||
|
- 管辖边界
|
||||||
|
|
||||||
|
### 3.5 facility
|
||||||
|
|
||||||
|
定义:人工建造的固定设施、结构物和工程对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 防波堤
|
||||||
|
- 岸壁
|
||||||
|
- 栈桥
|
||||||
|
- 桥梁
|
||||||
|
- 海底电缆
|
||||||
|
- 海底管线
|
||||||
|
- 陆上构造物
|
||||||
|
- 水上设施
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- “设施”与“边界”分离
|
||||||
|
- “设施”与“地标”分离
|
||||||
|
|
||||||
|
### 3.6 usage_area
|
||||||
|
|
||||||
|
定义:表达水域或区域用途、限制或功能属性的对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 錨地
|
||||||
|
- 錨泊地
|
||||||
|
- 漁業区
|
||||||
|
- 航路
|
||||||
|
- パイロットステーション
|
||||||
|
- 禁止区域
|
||||||
|
- 制限区域
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 如果对象主要表达“用途”而不是“实体构造”,优先归到本 family
|
||||||
|
|
||||||
|
### 3.7 landmark
|
||||||
|
|
||||||
|
定义:用于识别、定位或视觉参照的显著对象,但不属于典型助航设施。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 山顶
|
||||||
|
- 烟囱
|
||||||
|
- 塔
|
||||||
|
- 目标物
|
||||||
|
- 显著建筑
|
||||||
|
|
||||||
|
### 3.8 place
|
||||||
|
|
||||||
|
定义:地名和位置名称。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 海上地名
|
||||||
|
- 陆上地名
|
||||||
|
- 岬角地名
|
||||||
|
- 港名
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 只表示名称,不表示实体类别
|
||||||
|
- 与 `facility` 分离,避免“有名字的设施”误归到 place
|
||||||
|
|
||||||
|
### 3.9 seabed
|
||||||
|
|
||||||
|
定义:描述海床性质、底质和海底表面特征的对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 底質
|
||||||
|
- 海底地形
|
||||||
|
- 海底线
|
||||||
|
|
||||||
|
### 3.10 hydro_feature
|
||||||
|
|
||||||
|
定义:描述流、潮、涌浪、水流方向等水文现象的对象。
|
||||||
|
|
||||||
|
典型 object_type:
|
||||||
|
|
||||||
|
- 潮流
|
||||||
|
- 流向
|
||||||
|
- 渦流
|
||||||
|
- 浪向
|
||||||
|
|
||||||
|
## 4 taxonomy 命名规则
|
||||||
|
|
||||||
|
### 4.1 family 命名
|
||||||
|
|
||||||
|
- 使用稳定英文 snake_case
|
||||||
|
- 面向系统处理
|
||||||
|
- 不与具体样式实现耦合
|
||||||
|
|
||||||
|
### 4.2 object_type 命名
|
||||||
|
|
||||||
|
- 允许使用与业务更贴近的中文或日文对象名
|
||||||
|
- 但同义词必须收敛到单一标准写法
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 输出层面统一使用当前已采用的 `canonical_object_type`
|
||||||
|
- 数据治理时维护“原始名 -> 标准对象名”的映射表
|
||||||
|
|
||||||
|
## 5 v2 首批强制覆盖对象
|
||||||
|
|
||||||
|
以下对象建议作为 v2 taxonomy 的首批强制覆盖范围,因为它们同时影响旧样式兼容和后续语义渲染:
|
||||||
|
|
||||||
|
- 港湾灯台
|
||||||
|
- 灯柱
|
||||||
|
- 灯浮標
|
||||||
|
- AIS航标
|
||||||
|
- 暗岩
|
||||||
|
- 岩礁
|
||||||
|
- 礁盘
|
||||||
|
- 魚礁
|
||||||
|
- 沈船
|
||||||
|
- 障害物
|
||||||
|
- 潜堤
|
||||||
|
- 水深点
|
||||||
|
- 水深区
|
||||||
|
- 等深线
|
||||||
|
- 概略等深线
|
||||||
|
- 危険界
|
||||||
|
- 基本线
|
||||||
|
- 航路
|
||||||
|
- 航路境界
|
||||||
|
- 錨地
|
||||||
|
- 錨泊地
|
||||||
|
- 漁業区
|
||||||
|
- 防波堤
|
||||||
|
- 橋梁
|
||||||
|
- 海底电缆
|
||||||
|
- 底質
|
||||||
|
- 海上地名
|
||||||
|
- 陆上地名
|
||||||
|
- 高さ制限
|
||||||
|
|
||||||
|
## 6 taxonomy 与旧字段的关系
|
||||||
|
|
||||||
|
taxonomy 不替代旧字段,但要尽量减少下游直接理解旧字段的负担。
|
||||||
|
|
||||||
|
建议职责划分如下:
|
||||||
|
|
||||||
|
- `canonical_family`:供统计、筛选、检索、碰撞分类使用
|
||||||
|
- `canonical_object_type`:供精细语义判断和渲染映射使用
|
||||||
|
- 旧字段:仅在兼容旧样式细节时保留
|
||||||
|
|
||||||
|
## 7 v2 边界决策
|
||||||
|
|
||||||
|
### 7.1 不再单独使用 buoy family
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 会与 `navigation_aid` 重叠
|
||||||
|
- 对检索和渲染映射帮助有限
|
||||||
|
|
||||||
|
### 7.2 鱼礁优先归到 hazard
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 当前产品目标明确包含碰撞检测
|
||||||
|
- 航行风险语义优先级高于用途语义
|
||||||
|
|
||||||
|
补充表达:
|
||||||
|
|
||||||
|
- 若需要表达作业用途,可通过渲染语义或 `area_usage_class=fishery` 补充
|
||||||
|
|
||||||
|
### 7.3 航路归到 usage_area,航路边界归到 boundary
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 区分“区域本身”和“边界线”
|
||||||
|
- 有利于样式层分别控制 fill 和 line
|
||||||
|
|
||||||
|
## 8 v2 输出要求
|
||||||
|
|
||||||
|
每个交付 feature 至少应满足:
|
||||||
|
|
||||||
|
- 有 `canonical_family`
|
||||||
|
- 有 `canonical_object_type`
|
||||||
|
- 有 `detection_key`
|
||||||
|
|
||||||
|
如果对象未完成 taxonomy 映射,不能直接输出为空,应至少回退到:
|
||||||
|
|
||||||
|
- `canonical_family = unknown`
|
||||||
|
- `canonical_object_type = 未分类对象`
|
||||||
|
|
||||||
|
并进入后续人工补录清单。
|
||||||
21
tasks/pbf/NavSea_tile_generation_pipeline_v1.md
Normal file
21
tasks/pbf/NavSea_tile_generation_pipeline_v1.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# NavSea Tile Generation Pipeline v1
|
||||||
|
|
||||||
|
描述 NavSea 从原始数据到 Vector Tile 的流程。
|
||||||
|
|
||||||
|
raw hydro data
|
||||||
|
↓
|
||||||
|
taxonomy classification
|
||||||
|
↓
|
||||||
|
canonical_object_type
|
||||||
|
↓
|
||||||
|
render mapping table
|
||||||
|
↓
|
||||||
|
chart semantic fields
|
||||||
|
↓
|
||||||
|
vector tile PBF
|
||||||
|
↓
|
||||||
|
MapLibre style rendering
|
||||||
|
|
||||||
|
该流程实现:
|
||||||
|
|
||||||
|
数据 → 语义 → 渲染 → 屏幕。
|
||||||
249
tasks/pbf/NavSea_tile_generation_pipeline_v2.md
Normal file
249
tasks/pbf/NavSea_tile_generation_pipeline_v2.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
# NavSea Tile Generation Pipeline v2
|
||||||
|
|
||||||
|
版本:v2 Draft
|
||||||
|
用途:定义 NavSea 从原始海图数据到交付 Vector Tile 的生产流程和质量要求。
|
||||||
|
|
||||||
|
## 1 目标
|
||||||
|
|
||||||
|
v2 pipeline 不是简单“改字段名”,而是把交付链路明确拆成:
|
||||||
|
|
||||||
|
`原始数据 -> 语义分类 -> 渲染语义 -> 字段物化 -> PBF 交付 -> 样式渲染`
|
||||||
|
|
||||||
|
同时满足:
|
||||||
|
|
||||||
|
- 旧内容兼容
|
||||||
|
- 新样式迁移
|
||||||
|
- 检索和碰撞可用
|
||||||
|
- 工程可验证
|
||||||
|
|
||||||
|
## 2 输入
|
||||||
|
|
||||||
|
### 2.1 原始输入
|
||||||
|
|
||||||
|
- 原始 Vector Tile:`/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
|
||||||
|
- 原始要素属性:包括 `fid`、`分類番号`、`形状分類番号`、`表示用番号`、`名称`、`灯色` 等
|
||||||
|
|
||||||
|
### 2.2 数据库输入
|
||||||
|
|
||||||
|
当前数据库中已具备或部分具备:
|
||||||
|
|
||||||
|
- taxonomy 归类结果
|
||||||
|
- render_layer 归并结果
|
||||||
|
- detection 分类结果
|
||||||
|
|
||||||
|
现有 builder 查询表:
|
||||||
|
|
||||||
|
- `pbf_relayer_candidates`
|
||||||
|
- `properties`
|
||||||
|
|
||||||
|
## 3 输出类型
|
||||||
|
|
||||||
|
建议把输出分成两类:
|
||||||
|
|
||||||
|
### 3.1 delivery tiles
|
||||||
|
|
||||||
|
用途:
|
||||||
|
|
||||||
|
- 正式渲染
|
||||||
|
- 检索
|
||||||
|
- 碰撞检测
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 保留关键旧字段
|
||||||
|
- 输出稳定 taxonomy 字段
|
||||||
|
- 输出新的 render_semantics 字段
|
||||||
|
- 不包含过量调试字段
|
||||||
|
|
||||||
|
### 3.2 engineering tiles
|
||||||
|
|
||||||
|
用途:
|
||||||
|
|
||||||
|
- 抽样校验
|
||||||
|
- 问题定位
|
||||||
|
- 新旧比对
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 可保留更完整的原始字段
|
||||||
|
- 可保留更多校验字段
|
||||||
|
|
||||||
|
## 4 v2 标准流程
|
||||||
|
|
||||||
|
### 步骤 1:读取原始瓦片
|
||||||
|
|
||||||
|
按 `z/x/y` 读取原始 `.pbf`,解码所有 source layer。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 保留每个 source layer 的原始 extent
|
||||||
|
- 不改变 feature geometry
|
||||||
|
|
||||||
|
### 步骤 2:按 fid/source_layer/geom_type 关联数据库记录
|
||||||
|
|
||||||
|
建议匹配键:
|
||||||
|
|
||||||
|
- `fid`
|
||||||
|
- `source_layer`
|
||||||
|
- `geom_type`
|
||||||
|
|
||||||
|
目的:
|
||||||
|
|
||||||
|
- 将原始 feature 与数据库里的 taxonomy / detection / render_layer 结果稳定关联
|
||||||
|
|
||||||
|
### 步骤 3:taxonomy 物化
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `semantic_key`(可选)
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 未命中 taxonomy 的对象要进入 fallback 分类
|
||||||
|
- 不允许静默输出空语义
|
||||||
|
|
||||||
|
### 步骤 4:render mapping 物化
|
||||||
|
|
||||||
|
依据:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- 几何类型
|
||||||
|
- 必要旧字段
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
- `chart_render_type`
|
||||||
|
- `chart_symbol_family`
|
||||||
|
- `chart_symbol_code`
|
||||||
|
- `chart_line_style`
|
||||||
|
- `chart_fill_style`
|
||||||
|
- `chart_text_style`
|
||||||
|
- `chart_priority`
|
||||||
|
- `chart_visibility_min`
|
||||||
|
- `chart_visibility_max`
|
||||||
|
|
||||||
|
### 步骤 5:label / light / numeric 归一化
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
- `chart_label_text`
|
||||||
|
- `chart_label_subtext`
|
||||||
|
- `light_color_code`
|
||||||
|
- `light_character_code`
|
||||||
|
- `depth_value_m`
|
||||||
|
- `clearance_height_m`
|
||||||
|
- `least_depth_m`
|
||||||
|
- `bearing_deg`
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 同类对象统一字段命名
|
||||||
|
- 尽量避免样式和检索继续直接解析原始文本字段
|
||||||
|
|
||||||
|
### 步骤 6:决定输出 layer
|
||||||
|
|
||||||
|
建议优先级:
|
||||||
|
|
||||||
|
1. 使用数据库已确定的 `render_layer`
|
||||||
|
2. 若无 `render_layer`,按渲染语义计算默认层
|
||||||
|
3. 再不行才回退原 `source_layer`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- v2 的长期方向是减少直接依赖旧 source layer 命名
|
||||||
|
- 但过渡期允许回退,保证旧 style 不崩
|
||||||
|
|
||||||
|
### 步骤 7:编码输出 PBF
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 保持原 feature geometry 不变
|
||||||
|
- 保持原 layer extent 不变
|
||||||
|
- 输出写入目标目录 `/home/wwwroot/pbf/{z}/{x}/{y}.pbf`
|
||||||
|
|
||||||
|
## 5 质量门禁
|
||||||
|
|
||||||
|
v2 pipeline 必须增加明确的 QA 检查。
|
||||||
|
|
||||||
|
### 5.1 文件层检查
|
||||||
|
|
||||||
|
- 新旧目标区域 `z/x/y.pbf` 文件名集合一致
|
||||||
|
- 瓦片数一致
|
||||||
|
- 输出层级目录完整
|
||||||
|
|
||||||
|
### 5.2 几何层检查
|
||||||
|
|
||||||
|
- 抽样瓦片 extent 一致
|
||||||
|
- 抽样 feature 数量不异常缩水
|
||||||
|
- 几何类型不发生意外变化
|
||||||
|
|
||||||
|
### 5.3 属性层检查
|
||||||
|
|
||||||
|
- `fid` 保留
|
||||||
|
- taxonomy 字段不为空
|
||||||
|
- 核心 render 字段存在
|
||||||
|
- 关键旧字段未被错误清洗
|
||||||
|
|
||||||
|
### 5.4 渲染层检查
|
||||||
|
|
||||||
|
至少验证以下对象在新样式或兼容样式中可正常显示:
|
||||||
|
|
||||||
|
- 灯标体系
|
||||||
|
- 危险物体系
|
||||||
|
- 鱼礁/鱼业区
|
||||||
|
- 水深区和等深线
|
||||||
|
- 基本线 / 危险界 / 航路境界
|
||||||
|
- 海上地名 / 陆上地名
|
||||||
|
|
||||||
|
## 6 与当前实现的差距
|
||||||
|
|
||||||
|
当前 `navsea_tile_builder.py` 已经完成:
|
||||||
|
|
||||||
|
- 原始瓦片读取
|
||||||
|
- 数据库关联
|
||||||
|
- taxonomy 字段叠加
|
||||||
|
- `render_layer` 重分层
|
||||||
|
- 原始 extent 保持
|
||||||
|
|
||||||
|
当前还缺:
|
||||||
|
|
||||||
|
- 统一的 render mapping 物化
|
||||||
|
- label/light/numeric 归一化
|
||||||
|
- delivery 与 engineering 两种输出口径
|
||||||
|
- 更系统的 QA 报告
|
||||||
|
|
||||||
|
## 7 v2 推荐实施顺序
|
||||||
|
|
||||||
|
### 阶段 1:不破坏现有交付
|
||||||
|
|
||||||
|
- 保持当前 builder 可用
|
||||||
|
- 在 builder 中新增 `chart_*` 字段,不删旧字段
|
||||||
|
|
||||||
|
### 阶段 2:构建混合样式
|
||||||
|
|
||||||
|
- 新样式优先读取 `chart_*`
|
||||||
|
- 旧字段仅用于灯标细节、危险物图案和少数特例
|
||||||
|
|
||||||
|
### 阶段 3:建立 QA 报表
|
||||||
|
|
||||||
|
- 每次构建输出 coverage / feature count / extent 抽样报告
|
||||||
|
- 对 taxonomy 未命中项输出清单
|
||||||
|
|
||||||
|
### 阶段 4:精简交付字段
|
||||||
|
|
||||||
|
- 在新样式和检索稳定后,逐步减少无用旧字段
|
||||||
|
- 但保留 `fid`、taxonomy 和关键兼容字段
|
||||||
|
|
||||||
|
## 8 v2 验收标准
|
||||||
|
|
||||||
|
一版合格的 v2 delivery tiles 至少应满足:
|
||||||
|
|
||||||
|
- 与原始目标区域瓦片文件集合一致
|
||||||
|
- 抽样瓦片 extent 正确
|
||||||
|
- 主要对象类型 taxonomy 完整
|
||||||
|
- `chart_*` 核心字段可用于新样式
|
||||||
|
- 旧兼容样式仍可显示关键对象
|
||||||
|
- 检索和碰撞可通过 `fid + taxonomy + normalized_numeric` 使用
|
||||||
534
tasks/pbf/NavSea_traceable_mapping_architecture_v1.md
Normal file
534
tasks/pbf/NavSea_traceable_mapping_architecture_v1.md
Normal file
@@ -0,0 +1,534 @@
|
|||||||
|
# NavSea 可追溯映射体系设计 v1
|
||||||
|
|
||||||
|
版本:v1 Draft
|
||||||
|
用途:定义 NavSea “旧版原始数据 -> 新版语义字段 / 渲染字段”的可追溯映射体系,确保后续可以稳定定位分类错误、渲染错误和规则变更影响。
|
||||||
|
|
||||||
|
## 1 目标
|
||||||
|
|
||||||
|
这套设计解决的不是“如何分类”本身,而是“分类和渲染映射的过程如何可追溯”。
|
||||||
|
|
||||||
|
必须满足:
|
||||||
|
|
||||||
|
- 能知道某个 feature 命中了哪条 taxonomy 规则
|
||||||
|
- 能知道某个 feature 命中了哪条 render 规则
|
||||||
|
- 能知道规则来自哪个版本
|
||||||
|
- 能知道规则判断时参考了哪些原始字段
|
||||||
|
- 能从最终 `pbf` 或 `fid` 回查到完整映射过程
|
||||||
|
|
||||||
|
一句话概括:
|
||||||
|
|
||||||
|
> 最终结果不是终点,规则命中过程也必须被记录成正式资产。
|
||||||
|
|
||||||
|
## 2 为什么必须做可追溯
|
||||||
|
|
||||||
|
如果只保留结果字段,例如:
|
||||||
|
|
||||||
|
- `canonical_family`
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `detection_key`
|
||||||
|
- `chart_render_type`
|
||||||
|
|
||||||
|
那后续虽然能看到“结果是什么”,但看不到:
|
||||||
|
|
||||||
|
- 为什么会分成这个结果
|
||||||
|
- 是哪条规则起了作用
|
||||||
|
- 如果结果错了,到底该改 taxonomy 还是改 render mapping
|
||||||
|
- 某次规则调整影响了哪些 feature
|
||||||
|
|
||||||
|
而 NavSea 后续一定会反复遇到这些问题:
|
||||||
|
|
||||||
|
- 某个对象分错类
|
||||||
|
- 某类对象在新样式里显示错误
|
||||||
|
- 某次规则升级导致历史区域表现变化
|
||||||
|
- 检索命中和渲染表现不一致
|
||||||
|
|
||||||
|
这时如果没有 trace,就只能人工猜。
|
||||||
|
|
||||||
|
## 3 设计原则
|
||||||
|
|
||||||
|
### 3.1 文件是规则源头
|
||||||
|
|
||||||
|
规则的 source of truth 必须是版本可控文件,而不是代码里的隐式判断,也不是数据库里唯一的一份规则。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- taxonomy 规则放 YAML/CSV 文件
|
||||||
|
- render mapping 规则放 YAML/CSV 文件
|
||||||
|
- 文档文件负责解释,不作为程序执行源
|
||||||
|
|
||||||
|
### 3.2 SQL 是运行时载体
|
||||||
|
|
||||||
|
数据库负责:
|
||||||
|
|
||||||
|
- 导入规则
|
||||||
|
- 物化计算结果
|
||||||
|
- 记录 feature 命中过程
|
||||||
|
- 支撑查询和审计
|
||||||
|
|
||||||
|
但数据库不是唯一规则源头。
|
||||||
|
|
||||||
|
### 3.3 结果和过程都要存
|
||||||
|
|
||||||
|
至少要同时存两类数据:
|
||||||
|
|
||||||
|
- 规则定义
|
||||||
|
- feature 命中结果
|
||||||
|
|
||||||
|
只存最终结果表不够。
|
||||||
|
|
||||||
|
### 3.4 规则必须有稳定 ID
|
||||||
|
|
||||||
|
每条规则必须有稳定 `rule_id`,不能只靠“第几条规则”或描述文本识别。
|
||||||
|
|
||||||
|
### 3.5 规则必须有版本
|
||||||
|
|
||||||
|
每次可影响结果的变更都要有版本号或 bundle 版本。
|
||||||
|
|
||||||
|
建议使用:
|
||||||
|
|
||||||
|
- `bundle_version`
|
||||||
|
- `rule_revision`
|
||||||
|
|
||||||
|
## 4 总体架构
|
||||||
|
|
||||||
|
建议分成 4 层。
|
||||||
|
|
||||||
|
### 4.1 规则源文件层
|
||||||
|
|
||||||
|
建议目录:
|
||||||
|
|
||||||
|
- `tasks/pbf/mappings/navsea_taxonomy_rules_v1.yaml`
|
||||||
|
- `tasks/pbf/mappings/navsea_render_rules_v1.yaml`
|
||||||
|
- `tasks/pbf/mappings/navsea_rule_bundle_v1.yaml`
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- 人可读
|
||||||
|
- 可进入 git
|
||||||
|
- 可 code review
|
||||||
|
- 可回滚
|
||||||
|
|
||||||
|
### 4.2 规则表层
|
||||||
|
|
||||||
|
建议导入 MySQL 生成:
|
||||||
|
|
||||||
|
- `navsea_rule_bundle`
|
||||||
|
- `navsea_taxonomy_rules`
|
||||||
|
- `navsea_render_rules`
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- 运行时查询
|
||||||
|
- 规则生效控制
|
||||||
|
- 按版本执行构建
|
||||||
|
|
||||||
|
### 4.3 结果表层
|
||||||
|
|
||||||
|
建议生成:
|
||||||
|
|
||||||
|
- `navsea_feature_taxonomy_result`
|
||||||
|
- `navsea_feature_render_result`
|
||||||
|
- `navsea_feature_rule_trace`
|
||||||
|
- `navsea_mapping_run`
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- 记录某次构建的命中结果
|
||||||
|
- 记录 feature 与 rule 的关系
|
||||||
|
- 记录本次运行使用的规则版本
|
||||||
|
|
||||||
|
### 4.4 交付层
|
||||||
|
|
||||||
|
交付层包括:
|
||||||
|
|
||||||
|
- delivery pbf
|
||||||
|
- engineering pbf
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- delivery pbf 只保留必要结果字段
|
||||||
|
- engineering pbf 或内部查询表保留 trace 回查能力
|
||||||
|
|
||||||
|
## 5 推荐文件结构
|
||||||
|
|
||||||
|
建议新增目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tasks/pbf/mappings/
|
||||||
|
navsea_rule_bundle_v1.yaml
|
||||||
|
navsea_taxonomy_rules_v1.yaml
|
||||||
|
navsea_render_rules_v1.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
建议新增导入脚本:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scripts/pbf/
|
||||||
|
import_navsea_rules.py
|
||||||
|
run_navsea_mapping.py
|
||||||
|
export_navsea_trace_report.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6 规则文件设计
|
||||||
|
|
||||||
|
## 6.1 bundle 文件
|
||||||
|
|
||||||
|
bundle 文件用于声明:
|
||||||
|
|
||||||
|
- 当前规则集版本
|
||||||
|
- taxonomy 规则文件版本
|
||||||
|
- render 规则文件版本
|
||||||
|
- 适用范围
|
||||||
|
- 是否为当前默认版本
|
||||||
|
|
||||||
|
示例字段:
|
||||||
|
|
||||||
|
- `bundle_id`
|
||||||
|
- `bundle_version`
|
||||||
|
- `status`
|
||||||
|
- `effective_date`
|
||||||
|
- `taxonomy_ruleset`
|
||||||
|
- `render_ruleset`
|
||||||
|
- `notes`
|
||||||
|
|
||||||
|
## 6.2 taxonomy 规则文件
|
||||||
|
|
||||||
|
每条 taxonomy 规则至少包含:
|
||||||
|
|
||||||
|
- `rule_id`
|
||||||
|
- `rule_revision`
|
||||||
|
- `priority`
|
||||||
|
- `enabled`
|
||||||
|
- `match_scope`
|
||||||
|
- `match_expr`
|
||||||
|
- `output.canonical_family`
|
||||||
|
- `output.canonical_object_type`
|
||||||
|
- `output.detection_key_template`
|
||||||
|
- `reason`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `rule_id` 是稳定主键,例如 `TAX-NAV-001`
|
||||||
|
- `rule_revision` 是规则修订号,例如 `3`
|
||||||
|
- `priority` 用于冲突时排序
|
||||||
|
- `match_expr` 描述匹配条件
|
||||||
|
- `reason` 解释为什么有这条规则
|
||||||
|
|
||||||
|
## 6.3 render 规则文件
|
||||||
|
|
||||||
|
每条 render 规则至少包含:
|
||||||
|
|
||||||
|
- `rule_id`
|
||||||
|
- `rule_revision`
|
||||||
|
- `priority`
|
||||||
|
- `enabled`
|
||||||
|
- `match_expr`
|
||||||
|
- `output.chart_render_type`
|
||||||
|
- `output.chart_symbol_family`
|
||||||
|
- `output.chart_symbol_code`
|
||||||
|
- `output.chart_line_style`
|
||||||
|
- `output.chart_fill_style`
|
||||||
|
- `output.chart_text_style`
|
||||||
|
- `output.chart_priority`
|
||||||
|
- `reason`
|
||||||
|
|
||||||
|
render 规则的输入通常包括:
|
||||||
|
|
||||||
|
- `canonical_object_type`
|
||||||
|
- `canonical_family`
|
||||||
|
- geometry type
|
||||||
|
- 必要旧字段
|
||||||
|
|
||||||
|
## 7 match_expr 建议
|
||||||
|
|
||||||
|
不建议把匹配逻辑写成自由文本。
|
||||||
|
|
||||||
|
建议使用结构化表达,例如:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: source_layer
|
||||||
|
op: in
|
||||||
|
value: [p航路標識群, P航行危険障害物]
|
||||||
|
- field: class_name
|
||||||
|
op: contains_any
|
||||||
|
value: [港湾灯台, 灯柱, 灯浮標]
|
||||||
|
- field: geom_type
|
||||||
|
op: eq
|
||||||
|
value: Point
|
||||||
|
```
|
||||||
|
|
||||||
|
支持的运算建议限制在少数几类:
|
||||||
|
|
||||||
|
- `eq`
|
||||||
|
- `neq`
|
||||||
|
- `in`
|
||||||
|
- `contains`
|
||||||
|
- `contains_any`
|
||||||
|
- `is_null`
|
||||||
|
- `not_null`
|
||||||
|
- `regex`
|
||||||
|
|
||||||
|
这样规则可审计、可导入、可执行。
|
||||||
|
|
||||||
|
## 8 SQL 表设计
|
||||||
|
|
||||||
|
## 8.1 `navsea_rule_bundle`
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `bundle_id` | varchar(64) PK | 规则集 ID |
|
||||||
|
| `bundle_version` | varchar(32) | 规则集版本 |
|
||||||
|
| `status` | varchar(20) | `draft` / `active` / `retired` |
|
||||||
|
| `taxonomy_ruleset` | varchar(64) | taxonomy 文件版本 |
|
||||||
|
| `render_ruleset` | varchar(64) | render 文件版本 |
|
||||||
|
| `effective_date` | datetime | 生效时间 |
|
||||||
|
| `created_at` | datetime | 创建时间 |
|
||||||
|
| `notes` | text | 备注 |
|
||||||
|
|
||||||
|
## 8.2 `navsea_taxonomy_rules`
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `rule_id` | varchar(64) PK | 稳定规则 ID |
|
||||||
|
| `bundle_id` | varchar(64) | 所属 bundle |
|
||||||
|
| `rule_revision` | int | 修订号 |
|
||||||
|
| `priority` | int | 优先级,值越小越先匹配 |
|
||||||
|
| `enabled` | tinyint | 是否启用 |
|
||||||
|
| `match_scope` | varchar(32) | `feature` / `layer` / `object` |
|
||||||
|
| `match_expr_json` | json | 结构化匹配表达式 |
|
||||||
|
| `canonical_family` | varchar(100) | 输出 family |
|
||||||
|
| `canonical_object_type` | varchar(191) | 输出 object type |
|
||||||
|
| `detection_key_template` | varchar(191) | 输出 detection 模板 |
|
||||||
|
| `rule_reason` | text | 规则说明 |
|
||||||
|
| `created_at` | datetime | 创建时间 |
|
||||||
|
| `updated_at` | datetime | 更新时间 |
|
||||||
|
|
||||||
|
## 8.3 `navsea_render_rules`
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `rule_id` | varchar(64) PK | 稳定规则 ID |
|
||||||
|
| `bundle_id` | varchar(64) | 所属 bundle |
|
||||||
|
| `rule_revision` | int | 修订号 |
|
||||||
|
| `priority` | int | 优先级 |
|
||||||
|
| `enabled` | tinyint | 是否启用 |
|
||||||
|
| `match_expr_json` | json | 匹配表达式 |
|
||||||
|
| `chart_render_type` | varchar(32) | 输出 |
|
||||||
|
| `chart_symbol_family` | varchar(64) | 输出 |
|
||||||
|
| `chart_symbol_code` | varchar(64) | 输出 |
|
||||||
|
| `chart_line_style` | varchar(64) | 输出 |
|
||||||
|
| `chart_fill_style` | varchar(64) | 输出 |
|
||||||
|
| `chart_text_style` | varchar(64) | 输出 |
|
||||||
|
| `chart_priority` | int | 输出 |
|
||||||
|
| `chart_visibility_min` | int | 输出 |
|
||||||
|
| `chart_visibility_max` | int | 输出 |
|
||||||
|
| `rule_reason` | text | 规则说明 |
|
||||||
|
| `created_at` | datetime | 创建时间 |
|
||||||
|
| `updated_at` | datetime | 更新时间 |
|
||||||
|
|
||||||
|
## 8.4 `navsea_mapping_run`
|
||||||
|
|
||||||
|
记录一次完整构建执行。
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `run_id` | bigint PK | 执行 ID |
|
||||||
|
| `bundle_id` | varchar(64) | 本次使用的规则集 |
|
||||||
|
| `run_type` | varchar(32) | `full` / `aoi` / `qa` |
|
||||||
|
| `tile_scope` | text | 范围说明 |
|
||||||
|
| `started_at` | datetime | 开始时间 |
|
||||||
|
| `finished_at` | datetime | 结束时间 |
|
||||||
|
| `status` | varchar(20) | `running` / `success` / `failed` |
|
||||||
|
| `source_snapshot` | varchar(128) | 源数据快照标识 |
|
||||||
|
| `notes` | text | 备注 |
|
||||||
|
|
||||||
|
## 8.5 `navsea_feature_rule_trace`
|
||||||
|
|
||||||
|
这是最关键的 trace 表。
|
||||||
|
|
||||||
|
建议字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `run_id` | bigint | 所属执行 |
|
||||||
|
| `feature_id` | bigint | 内部 feature 主键 |
|
||||||
|
| `fid` | varchar(191) | 原始对象标识 |
|
||||||
|
| `z` | int | tile z |
|
||||||
|
| `x` | int | tile x |
|
||||||
|
| `y` | int | tile y |
|
||||||
|
| `source_layer` | varchar(100) | 原始 layer |
|
||||||
|
| `geom_type` | varchar(32) | 几何类型 |
|
||||||
|
| `taxonomy_rule_id` | varchar(64) | 命中的 taxonomy 规则 |
|
||||||
|
| `taxonomy_rule_revision` | int | taxonomy 修订号 |
|
||||||
|
| `render_rule_id` | varchar(64) | 命中的 render 规则 |
|
||||||
|
| `render_rule_revision` | int | render 修订号 |
|
||||||
|
| `bundle_id` | varchar(64) | 规则集版本 |
|
||||||
|
| `classification_basis` | varchar(64) | 使用的主要依据 |
|
||||||
|
| `source_fields_used_json` | json | 实际参与判定的字段和值摘要 |
|
||||||
|
| `canonical_family` | varchar(100) | 产出结果 |
|
||||||
|
| `canonical_object_type` | varchar(191) | 产出结果 |
|
||||||
|
| `detection_key` | varchar(191) | 产出结果 |
|
||||||
|
| `chart_render_type` | varchar(32) | 产出结果 |
|
||||||
|
| `chart_symbol_family` | varchar(64) | 产出结果 |
|
||||||
|
| `chart_symbol_code` | varchar(64) | 产出结果 |
|
||||||
|
| `chart_line_style` | varchar(64) | 产出结果 |
|
||||||
|
| `chart_fill_style` | varchar(64) | 产出结果 |
|
||||||
|
| `chart_text_style` | varchar(64) | 产出结果 |
|
||||||
|
| `trace_status` | varchar(20) | `matched` / `fallback` / `manual_review` |
|
||||||
|
| `created_at` | datetime | 记录时间 |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `source_fields_used_json` 不要求保存整份原始属性
|
||||||
|
- 但必须记录本次命中实际使用的关键字段和值摘要
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fid": "123456",
|
||||||
|
"分類番号": "403",
|
||||||
|
"表示用番号": "31135504",
|
||||||
|
"名称": "鷹島灯台",
|
||||||
|
"geom_type": "Point"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9 交付与追溯分离策略
|
||||||
|
|
||||||
|
为了兼顾交付体积和可追溯性,建议分两条线:
|
||||||
|
|
||||||
|
### 9.1 delivery pbf
|
||||||
|
|
||||||
|
保留:
|
||||||
|
|
||||||
|
- 业务必需字段
|
||||||
|
- taxonomy 结果字段
|
||||||
|
- render 结果字段
|
||||||
|
- 必要旧字段
|
||||||
|
|
||||||
|
不强制保留:
|
||||||
|
|
||||||
|
- 完整 trace 细节
|
||||||
|
|
||||||
|
### 9.2 engineering trace
|
||||||
|
|
||||||
|
通过以下方式保留完整追溯能力:
|
||||||
|
|
||||||
|
- SQL trace 表
|
||||||
|
- engineering pbf
|
||||||
|
- 按 `fid` / `feature_id` 回查脚本
|
||||||
|
|
||||||
|
建议 engineering pbf 可额外保留:
|
||||||
|
|
||||||
|
- `taxonomy_rule_id`
|
||||||
|
- `render_rule_id`
|
||||||
|
- `bundle_id`
|
||||||
|
- `trace_status`
|
||||||
|
|
||||||
|
## 10 builder 接入方式
|
||||||
|
|
||||||
|
当前 [navsea_tile_builder.py](/root/weather/navsea_tile_builder.py) 是从 `pbf_relayer_candidates` 读取结果并写入 `pbf`。
|
||||||
|
|
||||||
|
建议改造成两段式:
|
||||||
|
|
||||||
|
### 阶段 A:规则求值阶段
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
- 原始 feature
|
||||||
|
- 规则表
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
- `navsea_feature_taxonomy_result`
|
||||||
|
- `navsea_feature_render_result`
|
||||||
|
- `navsea_feature_rule_trace`
|
||||||
|
|
||||||
|
### 阶段 B:PBF 输出阶段
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
- 已物化结果表
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
- delivery pbf
|
||||||
|
- engineering pbf
|
||||||
|
|
||||||
|
这样做的好处是:
|
||||||
|
|
||||||
|
- “规则判断”与“PBF 编码”解耦
|
||||||
|
- 同一份判定结果可重复出图
|
||||||
|
- QA 和回归检查更容易做
|
||||||
|
|
||||||
|
## 11 问题排查路径
|
||||||
|
|
||||||
|
未来排查一个对象时,建议统一走这条链路:
|
||||||
|
|
||||||
|
1. 先从前端或瓦片里拿到 `fid`
|
||||||
|
2. 用 `fid` 或 `feature_id` 查 `navsea_feature_rule_trace`
|
||||||
|
3. 看命中的 `taxonomy_rule_id` 和 `render_rule_id`
|
||||||
|
4. 回到规则表查看该规则定义
|
||||||
|
5. 必要时回到规则源文件查看变更历史
|
||||||
|
6. 确认问题属于:
|
||||||
|
- 原始数据异常
|
||||||
|
- taxonomy 规则错误
|
||||||
|
- render 规则错误
|
||||||
|
- 样式解释错误
|
||||||
|
|
||||||
|
## 12 变更管理
|
||||||
|
|
||||||
|
建议所有规则变更都遵循:
|
||||||
|
|
||||||
|
- 改规则文件
|
||||||
|
- 提交 git
|
||||||
|
- 生成新 `bundle_version`
|
||||||
|
- 导入 SQL
|
||||||
|
- 执行局部或全量重算
|
||||||
|
- 输出差异报告
|
||||||
|
|
||||||
|
不建议:
|
||||||
|
|
||||||
|
- 直接手改结果表
|
||||||
|
- 直接在 SQL 里无版本地改规则
|
||||||
|
- 直接在代码里插入一条临时 if 判断后不回写规则文件
|
||||||
|
|
||||||
|
## 13 最小可落地版本
|
||||||
|
|
||||||
|
如果要尽快开始,建议第一阶段先做到:
|
||||||
|
|
||||||
|
1. 新增规则源文件目录
|
||||||
|
2. 给 taxonomy 和 render 规则都定义 `rule_id`
|
||||||
|
3. 新增 `navsea_rule_bundle`
|
||||||
|
4. 新增 `navsea_feature_rule_trace`
|
||||||
|
5. builder 在出 engineering 数据时至少写出:
|
||||||
|
- `bundle_id`
|
||||||
|
- `taxonomy_rule_id`
|
||||||
|
- `render_rule_id`
|
||||||
|
- `trace_status`
|
||||||
|
|
||||||
|
这样即使规则体系还不完整,也已经具备“能追”的能力。
|
||||||
|
|
||||||
|
## 14 结论
|
||||||
|
|
||||||
|
NavSea 的“旧版 -> 新版”对应关系,不应只是一套结果字段,也不应只存在于代码里。
|
||||||
|
|
||||||
|
它应该被正式建设成一套可追溯资产,包括:
|
||||||
|
|
||||||
|
- 可版本化的规则文件
|
||||||
|
- 可运行的 SQL 规则表
|
||||||
|
- 可查询的 feature trace 表
|
||||||
|
- 与 delivery / engineering pbf 配套的回查能力
|
||||||
|
|
||||||
|
后续查错、验收、回归、争议复盘,都会依赖这套体系。
|
||||||
344
tasks/pbf/OutPbf.md
Normal file
344
tasks/pbf/OutPbf.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
# NavSea Codex Task
|
||||||
|
# Karatsu AOI Vector Tile Build
|
||||||
|
Architecture: NavSea V11 / Codex6
|
||||||
|
Scope: Server Side Tile Build
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task Name
|
||||||
|
|
||||||
|
NavSea_Karatsu_AOI_PBF_Builder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task Goal
|
||||||
|
|
||||||
|
Generate a NavSea Vector Tile dataset for the area surrounding **Karatsu City (唐津市)**.
|
||||||
|
|
||||||
|
The tile build must cover approximately:
|
||||||
|
|
||||||
|
```
|
||||||
|
10 nautical mile radius
|
||||||
|
centered on Karatsu city
|
||||||
|
```
|
||||||
|
|
||||||
|
The tiles must be generated for **all zoom levels supported by NavSea**.
|
||||||
|
|
||||||
|
The generated tiles will be used to validate:
|
||||||
|
|
||||||
|
- semantic overlay
|
||||||
|
- render compatibility
|
||||||
|
- navigation object detection
|
||||||
|
- tile density behavior
|
||||||
|
- style compatibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Output Location
|
||||||
|
|
||||||
|
Tiles must be written to:
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/wwwroot/pbf
|
||||||
|
```
|
||||||
|
|
||||||
|
Directory structure must follow standard vector tile layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/wwwroot/pbf/{z}/{x}/{y}.pbf
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/wwwroot/pbf/10/864/440.pbf
|
||||||
|
```
|
||||||
|
|
||||||
|
Existing files in the directory may be overwritten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# AOI Definition
|
||||||
|
|
||||||
|
Center location:
|
||||||
|
|
||||||
|
```
|
||||||
|
Karatsu City
|
||||||
|
Japan
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended reference coordinate:
|
||||||
|
|
||||||
|
```
|
||||||
|
Lat: 33.4425
|
||||||
|
Lon: 129.9697
|
||||||
|
```
|
||||||
|
|
||||||
|
Coverage radius:
|
||||||
|
|
||||||
|
```
|
||||||
|
10 nautical miles
|
||||||
|
≈ 18.52 km
|
||||||
|
```
|
||||||
|
|
||||||
|
Codex may implement this AOI using one of the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 center point + radius
|
||||||
|
2 bounding box derived from radius
|
||||||
|
3 tile range approximation
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting tiles must fully cover the requested radius.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Zoom Levels
|
||||||
|
|
||||||
|
Tiles must be generated for:
|
||||||
|
|
||||||
|
```
|
||||||
|
z = 0 → 14
|
||||||
|
```
|
||||||
|
|
||||||
|
(Or the maximum zoom available in the dataset.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Data Sources
|
||||||
|
|
||||||
|
Tile features must be built from the validated NavSea semantic dataset.
|
||||||
|
|
||||||
|
Primary semantic source:
|
||||||
|
|
||||||
|
```
|
||||||
|
pbf_relayer_candidates
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional required tables:
|
||||||
|
|
||||||
|
```
|
||||||
|
features
|
||||||
|
properties
|
||||||
|
at_attributes
|
||||||
|
feature_semantic
|
||||||
|
```
|
||||||
|
|
||||||
|
These tables must be joined to reconstruct the full feature.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature Reconstruction Rules
|
||||||
|
|
||||||
|
Each output tile feature must include:
|
||||||
|
|
||||||
|
## Geometry
|
||||||
|
|
||||||
|
From:
|
||||||
|
|
||||||
|
```
|
||||||
|
features
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
```
|
||||||
|
z
|
||||||
|
x
|
||||||
|
y
|
||||||
|
geom_type
|
||||||
|
```
|
||||||
|
|
||||||
|
The geometry encoding must match the original vector tile geometry.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Original Attributes
|
||||||
|
|
||||||
|
All original attributes from:
|
||||||
|
|
||||||
|
```
|
||||||
|
properties
|
||||||
|
```
|
||||||
|
|
||||||
|
must be preserved.
|
||||||
|
|
||||||
|
Examples include:
|
||||||
|
|
||||||
|
```
|
||||||
|
class
|
||||||
|
shape
|
||||||
|
name
|
||||||
|
symbol
|
||||||
|
depth
|
||||||
|
category
|
||||||
|
```
|
||||||
|
|
||||||
|
These attributes must **not be removed or renamed**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantic Attributes
|
||||||
|
|
||||||
|
Additional semantic fields must be included from:
|
||||||
|
|
||||||
|
```
|
||||||
|
pbf_relayer_candidates
|
||||||
|
```
|
||||||
|
|
||||||
|
Including:
|
||||||
|
|
||||||
|
```
|
||||||
|
canonical_object_type
|
||||||
|
canonical_family
|
||||||
|
semantic_key
|
||||||
|
detection_key
|
||||||
|
render_layer
|
||||||
|
```
|
||||||
|
|
||||||
|
These fields must be **added**, not replacing original fields.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feature Selection
|
||||||
|
|
||||||
|
Features must be selected based on tile coordinates intersecting the AOI.
|
||||||
|
|
||||||
|
Selection process:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 determine tile range covering AOI
|
||||||
|
2 select features matching tile coordinates
|
||||||
|
3 group features by tile
|
||||||
|
4 encode each tile
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tile Encoding
|
||||||
|
|
||||||
|
Each tile must be encoded as a **Mapbox Vector Tile (MVT)**.
|
||||||
|
|
||||||
|
Encoding rules:
|
||||||
|
|
||||||
|
```
|
||||||
|
Layer name = render_layer
|
||||||
|
```
|
||||||
|
|
||||||
|
If `render_layer` is null, fallback to:
|
||||||
|
|
||||||
|
```
|
||||||
|
source_layer
|
||||||
|
```
|
||||||
|
|
||||||
|
Geometry must be encoded according to:
|
||||||
|
|
||||||
|
```
|
||||||
|
geom_type
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Performance Requirements
|
||||||
|
|
||||||
|
The tile builder must:
|
||||||
|
|
||||||
|
```
|
||||||
|
avoid full-table scans where possible
|
||||||
|
use tile coordinate filtering
|
||||||
|
process tiles incrementally
|
||||||
|
```
|
||||||
|
|
||||||
|
The builder must not attempt to load the entire dataset into memory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Build Script
|
||||||
|
|
||||||
|
Codex must implement a script:
|
||||||
|
|
||||||
|
```
|
||||||
|
navsea_tile_builder.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Capabilities required:
|
||||||
|
|
||||||
|
```
|
||||||
|
AOI tile calculation
|
||||||
|
database feature extraction
|
||||||
|
tile grouping
|
||||||
|
vector tile encoding
|
||||||
|
parallel tile generation
|
||||||
|
filesystem output
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Suggested Execution
|
||||||
|
|
||||||
|
The build script should be executable as:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python navsea_tile_builder.py \
|
||||||
|
--center-lat 33.4425 \
|
||||||
|
--center-lon 129.9697 \
|
||||||
|
--radius-nm 10 \
|
||||||
|
--zmin 0 \
|
||||||
|
--zmax 14 \
|
||||||
|
--output /home/wwwroot/pbf
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Expected Result
|
||||||
|
|
||||||
|
After the build completes, the directory should contain:
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/wwwroot/pbf/
|
||||||
|
├─ 0/
|
||||||
|
├─ 1/
|
||||||
|
├─ 2/
|
||||||
|
├─ ...
|
||||||
|
└─ 14/
|
||||||
|
```
|
||||||
|
|
||||||
|
Each containing:
|
||||||
|
|
||||||
|
```
|
||||||
|
{x}/{y}.pbf
|
||||||
|
```
|
||||||
|
|
||||||
|
The tiles must render correctly in MapLibre / OpenCPN / tile viewers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verification
|
||||||
|
|
||||||
|
After build, the following checks should pass:
|
||||||
|
|
||||||
|
```
|
||||||
|
tiles exist for requested AOI
|
||||||
|
tile hierarchy is correct
|
||||||
|
vector tiles decode successfully
|
||||||
|
original attributes preserved
|
||||||
|
semantic fields present
|
||||||
|
```
|
||||||
|
|
||||||
|
Example feature should resemble:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"class": "灯",
|
||||||
|
"shape": "灯標",
|
||||||
|
"name": "唐津灯台",
|
||||||
|
|
||||||
|
"canonical_object_type": "navigation_light",
|
||||||
|
"canonical_family": "navigation_aid",
|
||||||
|
"detection_key": "nav_light"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# End Task
|
||||||
104
tasks/pbf/fiddecode.md
Normal file
104
tasks/pbf/fiddecode.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from Crypto.Cipher import AES
|
||||||
|
import hashlib
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Example implementation updated for the full signed int32 legacy fid domain.
|
||||||
|
# This keeps the algorithm reversible for all observed NavSea legacy fid values.
|
||||||
|
MIN_VAL = -(1 << 31)
|
||||||
|
MAX_VAL = (1 << 31) - 1
|
||||||
|
N = MAX_VAL - MIN_VAL + 1 # 4294967296
|
||||||
|
|
||||||
|
ROUNDS = 10
|
||||||
|
DOMAIN_BITS = 32
|
||||||
|
DOMAIN_SIZE = 1 << DOMAIN_BITS
|
||||||
|
|
||||||
|
|
||||||
|
def _prf(key: bytes, round_no: int, value: int) -> int:
|
||||||
|
"""
|
||||||
|
AES-based PRF, returns 16-bit integer.
|
||||||
|
"""
|
||||||
|
cipher = AES.new(key, AES.MODE_ECB)
|
||||||
|
data = struct.pack(">IQ", round_no, value)
|
||||||
|
block = hashlib.sha256(data).digest()[:16]
|
||||||
|
out = cipher.encrypt(block)
|
||||||
|
return int.from_bytes(out[-2:], "big")
|
||||||
|
|
||||||
|
|
||||||
|
def _feistel_permute(x: int, key: bytes, encrypt: bool) -> int:
|
||||||
|
"""
|
||||||
|
32-bit Feistel permutation.
|
||||||
|
"""
|
||||||
|
if not (0 <= x < DOMAIN_SIZE):
|
||||||
|
raise ValueError("x out of 32-bit domain")
|
||||||
|
|
||||||
|
l = (x >> 16) & 0xFFFF
|
||||||
|
r = x & 0xFFFF
|
||||||
|
|
||||||
|
rounds = range(ROUNDS) if encrypt else reversed(range(ROUNDS))
|
||||||
|
|
||||||
|
for rnd in rounds:
|
||||||
|
if encrypt:
|
||||||
|
f = _prf(key, rnd, r)
|
||||||
|
l, r = r, l ^ f
|
||||||
|
else:
|
||||||
|
f = _prf(key, rnd, l)
|
||||||
|
l, r = r ^ f, l
|
||||||
|
|
||||||
|
return ((l << 16) | r) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_number(x: int, key: bytes) -> int:
|
||||||
|
"""
|
||||||
|
Encrypt x in [-2147483648, 2147483647]
|
||||||
|
Return non-negative integer in [0, N-1]
|
||||||
|
"""
|
||||||
|
if len(key) not in (16, 24, 32):
|
||||||
|
raise ValueError("AES key must be 16, 24, or 32 bytes")
|
||||||
|
if not (MIN_VAL <= x <= MAX_VAL):
|
||||||
|
raise ValueError("input out of range")
|
||||||
|
|
||||||
|
# Map original range to [0, N-1]
|
||||||
|
r = x - MIN_VAL
|
||||||
|
|
||||||
|
# Cycle-walking into [0, N-1]
|
||||||
|
while True:
|
||||||
|
r = _feistel_permute(r, key, True)
|
||||||
|
if r < N:
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_number(y: int, key: bytes) -> int:
|
||||||
|
"""
|
||||||
|
Decrypt non-negative integer y in [0, N-1]
|
||||||
|
Return original integer in [-2147483648, 2147483647]
|
||||||
|
"""
|
||||||
|
if len(key) not in (16, 24, 32):
|
||||||
|
raise ValueError("AES key must be 16, 24, or 32 bytes")
|
||||||
|
if not (0 <= y < N):
|
||||||
|
raise ValueError("encrypted value out of range")
|
||||||
|
|
||||||
|
r = y
|
||||||
|
|
||||||
|
# Reverse cycle-walking
|
||||||
|
while True:
|
||||||
|
r = _feistel_permute(r, key, False)
|
||||||
|
if r < N:
|
||||||
|
return r + MIN_VAL
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
key = b"example-secret!!" # 16 bytes
|
||||||
|
|
||||||
|
samples = [
|
||||||
|
-2147483648,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
12345678,
|
||||||
|
2147483647,
|
||||||
|
]
|
||||||
|
|
||||||
|
for x in samples:
|
||||||
|
enc = encrypt_number(x, key)
|
||||||
|
dec = decrypt_number(enc, key)
|
||||||
|
print(f"x={x}, enc={enc}, dec={dec}, ok={x == dec}")
|
||||||
123
tasks/pbf/mappings/navsea_field_name_rules_v1.yaml
Normal file
123
tasks/pbf/mappings/navsea_field_name_rules_v1.yaml
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
ruleset: navsea_field_name_rules_v1
|
||||||
|
bundle_id: navsea-core
|
||||||
|
version: v1-draft
|
||||||
|
rules:
|
||||||
|
- field_name_jp: fid
|
||||||
|
field_name_std: fid_legacy_raw
|
||||||
|
field_group: identity
|
||||||
|
keep_in_delivery: false
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: reversible_identity
|
||||||
|
notes: Preserve the original legacy fid only in engineering and trace outputs.
|
||||||
|
- field_name_jp: at
|
||||||
|
field_name_std: at_raw
|
||||||
|
field_group: source_legacy
|
||||||
|
keep_in_delivery: false
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: raw_blob
|
||||||
|
notes: Raw attribute pack remains engineering-only by default.
|
||||||
|
- field_name_jp: 分類番号
|
||||||
|
field_name_std: class_code
|
||||||
|
field_group: source_legacy
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: legacy_render_code
|
||||||
|
notes: Legacy class code used during transition and audits.
|
||||||
|
- field_name_jp: 形状分類番号
|
||||||
|
field_name_std: shape_class_code
|
||||||
|
field_group: source_legacy
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: legacy_render_code
|
||||||
|
notes: Legacy shape class code used during transition and audits.
|
||||||
|
- field_name_jp: 表示用番号
|
||||||
|
field_name_std: display_code
|
||||||
|
field_group: source_legacy
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: legacy_render_code
|
||||||
|
notes: Legacy display code retained until all rendering migrates to chart_* fields.
|
||||||
|
- field_name_jp: 灯色
|
||||||
|
field_name_std: light_color_raw_code
|
||||||
|
field_group: light
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: field_value_normalization
|
||||||
|
notes: Legacy raw color code maps into light_color_code.
|
||||||
|
- field_name_jp: 灯略記
|
||||||
|
field_name_std: light_character_remark
|
||||||
|
field_group: light
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: content_text
|
||||||
|
notes: User-visible light remark should remain reversible.
|
||||||
|
- field_name_jp: 明弧/分孤
|
||||||
|
field_name_std: light_sector_remark
|
||||||
|
field_group: light
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: field_value_normalization
|
||||||
|
notes: Legacy light sector remark maps into light_sector_mode and trace fields.
|
||||||
|
- field_name_jp: 表示位置
|
||||||
|
field_name_std: label_position_code_legacy
|
||||||
|
field_group: labels
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: field_value_normalization
|
||||||
|
notes: Legacy label position code maps into chart_label_position_code.
|
||||||
|
- field_name_jp: 名称
|
||||||
|
field_name_std: name_ja
|
||||||
|
field_group: labels
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: content_text
|
||||||
|
notes: Primary Japanese name content must remain available.
|
||||||
|
- field_name_jp: 名称補助
|
||||||
|
field_name_std: name_subtext_ja
|
||||||
|
field_group: labels
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: content_text
|
||||||
|
notes: Secondary Japanese name content must remain available.
|
||||||
|
- field_name_jp: 日本語地名
|
||||||
|
field_name_std: place_name_ja
|
||||||
|
field_group: labels
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: content_text
|
||||||
|
notes: Japanese place name content must remain available.
|
||||||
|
- field_name_jp: 英文字地名
|
||||||
|
field_name_std: place_name_en
|
||||||
|
field_group: labels
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: content_text
|
||||||
|
notes: English place name content remains available for fallback labels.
|
||||||
|
- field_name_jp: 水深値(m)
|
||||||
|
field_name_std: depth_value_m_legacy
|
||||||
|
field_group: normalized_numeric
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: numeric_normalization
|
||||||
|
notes: Legacy depth string is normalized into depth_value_m.
|
||||||
|
- field_name_jp: 高さ(m)
|
||||||
|
field_name_std: clearance_height_m_legacy
|
||||||
|
field_group: normalized_numeric
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: numeric_normalization
|
||||||
|
notes: Legacy height string is normalized into clearance_height_m.
|
||||||
|
- field_name_jp: 高さ/深度(m)
|
||||||
|
field_name_std: height_or_depth_m_legacy
|
||||||
|
field_group: normalized_numeric
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: numeric_normalization
|
||||||
|
notes: Mixed height/depth legacy string maps into least_depth_m or clearance semantics.
|
||||||
|
- field_name_jp: 角度
|
||||||
|
field_name_std: bearing_deg_legacy
|
||||||
|
field_group: normalized_numeric
|
||||||
|
keep_in_delivery: true
|
||||||
|
keep_in_engineering: true
|
||||||
|
normalization_class: numeric_normalization
|
||||||
|
notes: Legacy angle string is normalized into bearing_deg.
|
||||||
148
tasks/pbf/mappings/navsea_field_value_rules_v1.yaml
Normal file
148
tasks/pbf/mappings/navsea_field_value_rules_v1.yaml
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
ruleset: navsea_field_value_rules_v1
|
||||||
|
bundle_id: navsea-core
|
||||||
|
version: v1-draft
|
||||||
|
rules:
|
||||||
|
- rule_id: FVAL-LIGHT-001
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "1"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: green
|
||||||
|
notes: Legacy light color code 1 maps to green in current data.
|
||||||
|
- rule_id: FVAL-LIGHT-002
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "2"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: red
|
||||||
|
notes: Legacy light color code 2 maps to red.
|
||||||
|
- rule_id: FVAL-LIGHT-003
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "3"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: yellow
|
||||||
|
notes: Legacy light color code 3 maps to yellow.
|
||||||
|
- rule_id: FVAL-LIGHT-004
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "4"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: white
|
||||||
|
notes: Legacy light color code 4 maps to white.
|
||||||
|
- rule_id: FVAL-LIGHT-005
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "5"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: blue
|
||||||
|
notes: Legacy light color code 5 maps to blue.
|
||||||
|
- rule_id: FVAL-LIGHT-006
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "6"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: violet
|
||||||
|
notes: Legacy light color code 6 maps to violet.
|
||||||
|
- rule_id: FVAL-LIGHT-007
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "7"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: orange
|
||||||
|
notes: Legacy light color code 7 maps to orange.
|
||||||
|
- rule_id: FVAL-LIGHT-008
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "8"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: amber
|
||||||
|
notes: Legacy light color code 8 maps to amber.
|
||||||
|
- rule_id: FVAL-LIGHT-009
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 灯色
|
||||||
|
legacy_value: "9"
|
||||||
|
standardized_field: light_color_code
|
||||||
|
standardized_value: mixed
|
||||||
|
notes: Legacy light color code 9 maps to mixed.
|
||||||
|
- rule_id: FVAL-LABELPOS-001
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "0"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: tl
|
||||||
|
notes: Legacy label position 0 maps to top-left.
|
||||||
|
- rule_id: FVAL-LABELPOS-002
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "1"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: t
|
||||||
|
notes: Legacy label position 1 maps to top.
|
||||||
|
- rule_id: FVAL-LABELPOS-003
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "2"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: tr
|
||||||
|
notes: Legacy label position 2 maps to top-right.
|
||||||
|
- rule_id: FVAL-LABELPOS-004
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "4"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: l
|
||||||
|
notes: Legacy label position 4 maps to left.
|
||||||
|
- rule_id: FVAL-LABELPOS-005
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "5"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: c
|
||||||
|
notes: Legacy label position 5 maps to center.
|
||||||
|
- rule_id: FVAL-LABELPOS-006
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "6"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: r
|
||||||
|
notes: Legacy label position 6 maps to right.
|
||||||
|
- rule_id: FVAL-LABELPOS-007
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "8"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: bl
|
||||||
|
notes: Legacy label position 8 maps to bottom-left.
|
||||||
|
- rule_id: FVAL-LABELPOS-008
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "9"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: b
|
||||||
|
notes: Legacy label position 9 maps to bottom.
|
||||||
|
- rule_id: FVAL-LABELPOS-009
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 表示位置
|
||||||
|
legacy_value: "10"
|
||||||
|
standardized_field: chart_label_position_code
|
||||||
|
standardized_value: br
|
||||||
|
notes: Legacy label position 10 maps to bottom-right.
|
||||||
|
- rule_id: FVAL-CLASS-001
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 分類番号
|
||||||
|
legacy_value: "428"
|
||||||
|
standardized_field: chart_symbol_code
|
||||||
|
standardized_value: fish_reef
|
||||||
|
geom_scope: Point
|
||||||
|
canonical_object_scope: 魚礁
|
||||||
|
notes: Legacy class 428 point object renders as fish reef symbol.
|
||||||
|
- rule_id: FVAL-CLASS-002
|
||||||
|
rule_revision: 1
|
||||||
|
field_name_jp: 分類番号
|
||||||
|
legacy_value: "428"
|
||||||
|
standardized_field: chart_fill_style
|
||||||
|
standardized_value: fish_reef_area
|
||||||
|
geom_scope: Polygon,MultiPolygon
|
||||||
|
canonical_object_scope: 魚礁
|
||||||
|
notes: Legacy class 428 area object renders as fish reef area pattern.
|
||||||
171
tasks/pbf/mappings/navsea_render_rules_v1.yaml
Normal file
171
tasks/pbf/mappings/navsea_render_rules_v1.yaml
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
ruleset: navsea_render_rules_v1
|
||||||
|
bundle_id: navsea-core
|
||||||
|
version: v1-draft
|
||||||
|
rules:
|
||||||
|
- rule_id: REN-NAV-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 100
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 港湾灯台
|
||||||
|
- field: geom_type
|
||||||
|
op: eq
|
||||||
|
value: Point
|
||||||
|
output:
|
||||||
|
chart_render_type: symbol
|
||||||
|
chart_symbol_family: navigation_light
|
||||||
|
chart_symbol_code: lighthouse
|
||||||
|
chart_priority: 900
|
||||||
|
chart_visibility_min: 7
|
||||||
|
chart_visibility_max: 24
|
||||||
|
reason: 港湾灯台应以高优先级灯标符号渲染。
|
||||||
|
|
||||||
|
- rule_id: REN-HAZ-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 120
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 魚礁
|
||||||
|
- field: geom_type
|
||||||
|
op: eq
|
||||||
|
value: Point
|
||||||
|
output:
|
||||||
|
chart_render_type: symbol
|
||||||
|
chart_symbol_family: hazard
|
||||||
|
chart_symbol_code: fish_reef
|
||||||
|
chart_priority: 860
|
||||||
|
area_usage_class: fishery
|
||||||
|
hazard_class: fish_reef
|
||||||
|
hazard_severity: major
|
||||||
|
reason: 点状鱼礁以鱼礁专用危险物符号表达。
|
||||||
|
|
||||||
|
- rule_id: REN-HAZ-002
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 121
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 魚礁
|
||||||
|
- field: geom_type
|
||||||
|
op: in
|
||||||
|
value: [Polygon, MultiPolygon]
|
||||||
|
output:
|
||||||
|
chart_render_type: fill
|
||||||
|
chart_symbol_family: hazard
|
||||||
|
chart_fill_style: fish_reef_area
|
||||||
|
chart_priority: 850
|
||||||
|
area_usage_class: fishery
|
||||||
|
hazard_class: fish_reef
|
||||||
|
hazard_severity: major
|
||||||
|
reason: 面状鱼礁区以鱼礁专用危险区填充表达,同时保留 fishery 用途语义。
|
||||||
|
|
||||||
|
- rule_id: REN-DEPTH-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 200
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 等深线
|
||||||
|
output:
|
||||||
|
chart_render_type: line
|
||||||
|
chart_symbol_family: contour
|
||||||
|
chart_line_style: contour_minor
|
||||||
|
chart_priority: 640
|
||||||
|
chart_visibility_min: 8
|
||||||
|
chart_visibility_max: 24
|
||||||
|
reason: 等深线统一归入 contour line 体系。
|
||||||
|
|
||||||
|
- rule_id: REN-SEABED-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 210
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 底質
|
||||||
|
output:
|
||||||
|
chart_render_type: label
|
||||||
|
chart_symbol_family: seabed
|
||||||
|
chart_text_style: seabed_text
|
||||||
|
chart_priority: 500
|
||||||
|
chart_visibility_min: 11
|
||||||
|
chart_visibility_max: 24
|
||||||
|
reason: 底质对象以海底属性文字渲染。
|
||||||
|
|
||||||
|
- rule_id: REN-PLACE-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 220
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 海上地名
|
||||||
|
output:
|
||||||
|
chart_render_type: label
|
||||||
|
chart_symbol_family: place
|
||||||
|
chart_text_style: place_name_sea
|
||||||
|
chart_priority: 450
|
||||||
|
chart_visibility_min: 7
|
||||||
|
chart_visibility_max: 24
|
||||||
|
reason: 海上地名使用海上地名字体体系渲染。
|
||||||
|
|
||||||
|
- rule_id: REN-PLACE-002
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 221
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 陆上地名
|
||||||
|
output:
|
||||||
|
chart_render_type: label
|
||||||
|
chart_symbol_family: place
|
||||||
|
chart_text_style: place_name_land
|
||||||
|
chart_priority: 440
|
||||||
|
chart_visibility_min: 7
|
||||||
|
chart_visibility_max: 24
|
||||||
|
reason: 陆上地名使用陆地地名字体体系渲染。
|
||||||
|
|
||||||
|
- rule_id: REN-USAGE-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 230
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: canonical_object_type
|
||||||
|
op: eq
|
||||||
|
value: 錨泊地
|
||||||
|
- field: geom_type
|
||||||
|
op: in
|
||||||
|
value: [Polygon, MultiPolygon]
|
||||||
|
output:
|
||||||
|
chart_render_type: fill
|
||||||
|
chart_symbol_family: usage_area
|
||||||
|
chart_fill_style: anchorage_area
|
||||||
|
chart_priority: 750
|
||||||
|
area_usage_class: anchorage
|
||||||
|
reason: 锚泊地区域以 anchorage_area 语义填充表达。
|
||||||
|
|
||||||
|
- rule_id: REN-FALLBACK-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 9999
|
||||||
|
enabled: true
|
||||||
|
match_expr:
|
||||||
|
all: []
|
||||||
|
output:
|
||||||
|
chart_render_type: none
|
||||||
|
chart_priority: 0
|
||||||
|
reason: 未命中渲染规则的对象默认不进入正式语义渲染,待补录。
|
||||||
12
tasks/pbf/mappings/navsea_rule_bundle_v1.yaml
Normal file
12
tasks/pbf/mappings/navsea_rule_bundle_v1.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
bundle_id: navsea-core
|
||||||
|
bundle_version: v1-draft
|
||||||
|
status: draft
|
||||||
|
effective_date: 2026-03-14
|
||||||
|
taxonomy_ruleset: navsea_taxonomy_rules_v1
|
||||||
|
render_ruleset: navsea_render_rules_v1
|
||||||
|
source_layer_ruleset: navsea_source_layer_rules_v1
|
||||||
|
field_name_ruleset: navsea_field_name_rules_v1
|
||||||
|
field_value_ruleset: navsea_field_value_rules_v1
|
||||||
|
notes: >
|
||||||
|
Draft rule bundle for traceable NavSea taxonomy and render mapping.
|
||||||
|
File is intended as source-of-truth metadata and should be imported into SQL.
|
||||||
151
tasks/pbf/mappings/navsea_source_layer_rules_v1.yaml
Normal file
151
tasks/pbf/mappings/navsea_source_layer_rules_v1.yaml
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
ruleset: navsea_source_layer_rules_v1
|
||||||
|
bundle_id: navsea-core
|
||||||
|
version: v1-draft
|
||||||
|
rules:
|
||||||
|
- source_layer_jp: L700
|
||||||
|
source_layer_std: depth_zone_700
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L702
|
||||||
|
source_layer_std: depth_zone_702
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L725
|
||||||
|
source_layer_std: depth_zone_725
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L739
|
||||||
|
source_layer_std: depth_zone_739
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L740
|
||||||
|
source_layer_std: depth_zone_740
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L741
|
||||||
|
source_layer_std: depth_zone_741
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L748
|
||||||
|
source_layer_std: depth_zone_748
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L749
|
||||||
|
source_layer_std: depth_zone_749
|
||||||
|
notes: Legacy coded depth zone layer kept as stable standardized code.
|
||||||
|
- source_layer_jp: L危険界
|
||||||
|
source_layer_std: hazard_boundary_line
|
||||||
|
notes: Standardized line identity for hazard boundary linework.
|
||||||
|
- source_layer_jp: L基本線
|
||||||
|
source_layer_std: baseline_line
|
||||||
|
notes: Standardized line identity for baseline linework.
|
||||||
|
- source_layer_jp: L概略等深線
|
||||||
|
source_layer_std: depth_contour_overview
|
||||||
|
notes: Overview contour line layer.
|
||||||
|
- source_layer_jp: L海底地形
|
||||||
|
source_layer_std: bathymetry_line
|
||||||
|
notes: Bathymetry support line layer.
|
||||||
|
- source_layer_jp: L海底線
|
||||||
|
source_layer_std: seabed_line
|
||||||
|
notes: Seabed line feature container.
|
||||||
|
- source_layer_jp: L等深線
|
||||||
|
source_layer_std: depth_contour
|
||||||
|
notes: Standard contour line layer.
|
||||||
|
- source_layer_jp: L航路
|
||||||
|
source_layer_std: route_axis_line
|
||||||
|
notes: Route axis or route line layer.
|
||||||
|
- source_layer_jp: L陸上構造物陸
|
||||||
|
source_layer_std: onshore_structure_line
|
||||||
|
notes: Onshore structure line layer.
|
||||||
|
- source_layer_jp: L高さ制限
|
||||||
|
source_layer_std: clearance_limit_line
|
||||||
|
notes: Clearance restriction line layer.
|
||||||
|
- source_layer_jp: P721ククリ
|
||||||
|
source_layer_std: clip_outline_721
|
||||||
|
notes: Stable outline layer preserved by numeric legacy code.
|
||||||
|
- source_layer_jp: P730ククリ
|
||||||
|
source_layer_std: clip_outline_730
|
||||||
|
notes: Stable outline layer preserved by numeric legacy code.
|
||||||
|
- source_layer_jp: P754ククリ
|
||||||
|
source_layer_std: clip_outline_754
|
||||||
|
notes: Stable outline layer preserved by numeric legacy code.
|
||||||
|
- source_layer_jp: pパイロットステーション
|
||||||
|
source_layer_std: pilot_station_point
|
||||||
|
notes: Pilot station point layer.
|
||||||
|
- source_layer_jp: P危険界ククリ
|
||||||
|
source_layer_std: hazard_boundary_outline
|
||||||
|
notes: Hazard boundary outline layer.
|
||||||
|
- source_layer_jp: p地名
|
||||||
|
source_layer_std: place_label_sea
|
||||||
|
notes: Marine place label point layer.
|
||||||
|
- source_layer_jp: p地名陸
|
||||||
|
source_layer_std: place_label_land
|
||||||
|
notes: Land place label point layer.
|
||||||
|
- source_layer_jp: P基本線
|
||||||
|
source_layer_std: baseline_area
|
||||||
|
notes: Baseline area or container layer.
|
||||||
|
- source_layer_jp: P基本線ククリ
|
||||||
|
source_layer_std: baseline_outline
|
||||||
|
notes: Baseline outline layer.
|
||||||
|
- source_layer_jp: p底質
|
||||||
|
source_layer_std: seabed_text_point
|
||||||
|
notes: Seabed label point layer.
|
||||||
|
- source_layer_jp: p投錨注意障害物
|
||||||
|
source_layer_std: anchor_caution_hazard_point
|
||||||
|
notes: Point hazards relevant to anchoring caution.
|
||||||
|
- source_layer_jp: P投錨注意障害物ククリ
|
||||||
|
source_layer_std: anchor_caution_hazard_outline
|
||||||
|
notes: Outline of anchoring caution hazard area.
|
||||||
|
- source_layer_jp: P施設・境界線等
|
||||||
|
source_layer_std: facility_boundary_area
|
||||||
|
notes: Facility and boundary area container.
|
||||||
|
- source_layer_jp: P施設・境界線等ククリ
|
||||||
|
source_layer_std: facility_boundary_outline
|
||||||
|
notes: Facility and boundary outline layer.
|
||||||
|
- source_layer_jp: P施設・境界線等透明
|
||||||
|
source_layer_std: facility_boundary_area_transparent
|
||||||
|
notes: Transparent facility and boundary area layer.
|
||||||
|
- source_layer_jp: P橋りょう等構造物
|
||||||
|
source_layer_std: bridge_structure
|
||||||
|
notes: Bridge and related structure layer.
|
||||||
|
- source_layer_jp: P漁具定置箇所
|
||||||
|
source_layer_std: fixed_fishing_gear_area
|
||||||
|
notes: Fixed fishing gear area layer.
|
||||||
|
- source_layer_jp: P潜堤
|
||||||
|
source_layer_std: submerged_reef_area
|
||||||
|
notes: Submerged reef area layer.
|
||||||
|
- source_layer_jp: P穴
|
||||||
|
source_layer_std: hole_area
|
||||||
|
notes: Hole or depression area layer.
|
||||||
|
- source_layer_jp: p航行危険障害物
|
||||||
|
source_layer_std: navigation_hazard_point
|
||||||
|
notes: Navigation hazard point layer.
|
||||||
|
- source_layer_jp: P航行危険障害物ククリ
|
||||||
|
source_layer_std: navigation_hazard_outline
|
||||||
|
notes: Navigation hazard outline layer.
|
||||||
|
- source_layer_jp: P航路
|
||||||
|
source_layer_std: route_area
|
||||||
|
notes: Route area layer.
|
||||||
|
- source_layer_jp: P航路ククリ
|
||||||
|
source_layer_std: route_outline
|
||||||
|
notes: Route outline layer.
|
||||||
|
- source_layer_jp: p航路境界等
|
||||||
|
source_layer_std: route_boundary_point
|
||||||
|
notes: Route boundary point annotation layer.
|
||||||
|
- source_layer_jp: p航路標識群
|
||||||
|
source_layer_std: navigation_marks
|
||||||
|
notes: Navigation marks point layer.
|
||||||
|
- source_layer_jp: P誘導線ククリ
|
||||||
|
source_layer_std: leading_line_outline
|
||||||
|
notes: Leading line outline layer.
|
||||||
|
- source_layer_jp: p錨泊地等
|
||||||
|
source_layer_std: anchorage_point
|
||||||
|
notes: Anchorage point symbol layer.
|
||||||
|
- source_layer_jp: P錨泊地等ククリ
|
||||||
|
source_layer_std: anchorage_outline
|
||||||
|
notes: Anchorage outline layer.
|
||||||
|
- source_layer_jp: p陸上構造物
|
||||||
|
source_layer_std: onshore_structure_point
|
||||||
|
notes: Onshore structure point layer.
|
||||||
|
- source_layer_jp: P陸上構造物陸
|
||||||
|
source_layer_std: onshore_structure_area
|
||||||
|
notes: Onshore structure area layer.
|
||||||
|
- source_layer_jp: P陸域
|
||||||
|
source_layer_std: land_area
|
||||||
|
notes: Land area polygon layer.
|
||||||
|
- source_layer_jp: p高さ制限
|
||||||
|
source_layer_std: clearance_limit_point
|
||||||
|
notes: Clearance restriction point layer.
|
||||||
134
tasks/pbf/mappings/navsea_taxonomy_rules_v1.yaml
Normal file
134
tasks/pbf/mappings/navsea_taxonomy_rules_v1.yaml
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
ruleset: navsea_taxonomy_rules_v1
|
||||||
|
bundle_id: navsea-core
|
||||||
|
version: v1-draft
|
||||||
|
rules:
|
||||||
|
- rule_id: TAX-NAV-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 100
|
||||||
|
enabled: true
|
||||||
|
match_scope: feature
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: class_name
|
||||||
|
op: contains_any
|
||||||
|
value: [港湾灯台, 灯台]
|
||||||
|
- field: geom_type
|
||||||
|
op: eq
|
||||||
|
value: Point
|
||||||
|
output:
|
||||||
|
canonical_family: navigation_aid
|
||||||
|
canonical_object_type: 港湾灯台
|
||||||
|
detection_key_template: symbol:港湾灯台
|
||||||
|
reason: 港湾灯台属于典型点状助航对象。
|
||||||
|
|
||||||
|
- rule_id: TAX-DEPTH-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 140
|
||||||
|
enabled: true
|
||||||
|
match_scope: layer
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: source_layer
|
||||||
|
op: eq
|
||||||
|
value: L等深線
|
||||||
|
output:
|
||||||
|
canonical_family: depth
|
||||||
|
canonical_object_type: 等深线
|
||||||
|
detection_key_template: line:等深线
|
||||||
|
reason: 等深线来源层具有稳定的深度线语义。
|
||||||
|
|
||||||
|
- rule_id: TAX-SEABED-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 150
|
||||||
|
enabled: true
|
||||||
|
match_scope: layer
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: source_layer
|
||||||
|
op: eq
|
||||||
|
value: p底質
|
||||||
|
output:
|
||||||
|
canonical_family: seabed
|
||||||
|
canonical_object_type: 底質
|
||||||
|
detection_key_template: label:底質
|
||||||
|
reason: 底质标签层具有稳定的海底属性语义。
|
||||||
|
|
||||||
|
- rule_id: TAX-PLACE-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 160
|
||||||
|
enabled: true
|
||||||
|
match_scope: layer
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: source_layer
|
||||||
|
op: eq
|
||||||
|
value: p地名
|
||||||
|
output:
|
||||||
|
canonical_family: place
|
||||||
|
canonical_object_type: 海上地名
|
||||||
|
detection_key_template: label:海上地名
|
||||||
|
reason: 海上地名点层具有稳定的地名语义。
|
||||||
|
|
||||||
|
- rule_id: TAX-PLACE-002
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 161
|
||||||
|
enabled: true
|
||||||
|
match_scope: layer
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: source_layer
|
||||||
|
op: eq
|
||||||
|
value: p地名陸
|
||||||
|
output:
|
||||||
|
canonical_family: place
|
||||||
|
canonical_object_type: 陆上地名
|
||||||
|
detection_key_template: label:陆上地名
|
||||||
|
reason: 陆上地名点层具有稳定的地名语义。
|
||||||
|
|
||||||
|
- rule_id: TAX-USAGE-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 180
|
||||||
|
enabled: true
|
||||||
|
match_scope: feature
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: class_name
|
||||||
|
op: contains_any
|
||||||
|
value: [錨泊, 錨地]
|
||||||
|
output:
|
||||||
|
canonical_family: usage_area
|
||||||
|
canonical_object_type: 錨泊地
|
||||||
|
detection_key_template: mixed:錨泊地
|
||||||
|
reason: 锚泊类对象统一归入 usage_area 体系。
|
||||||
|
|
||||||
|
- rule_id: TAX-HAZ-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 120
|
||||||
|
enabled: true
|
||||||
|
match_scope: feature
|
||||||
|
match_expr:
|
||||||
|
all:
|
||||||
|
- field: class_name
|
||||||
|
op: contains
|
||||||
|
value: 魚礁
|
||||||
|
- field: geom_type
|
||||||
|
op: in
|
||||||
|
value: [Point, Polygon, MultiPolygon]
|
||||||
|
output:
|
||||||
|
canonical_family: hazard
|
||||||
|
canonical_object_type: 魚礁
|
||||||
|
detection_key_template: mixed:魚礁
|
||||||
|
reason: 魚礁在当前业务目标下优先视为危险对象,并允许后续叠加用途语义。
|
||||||
|
|
||||||
|
- rule_id: TAX-FALLBACK-001
|
||||||
|
rule_revision: 1
|
||||||
|
priority: 9999
|
||||||
|
enabled: true
|
||||||
|
match_scope: feature
|
||||||
|
match_expr:
|
||||||
|
all: []
|
||||||
|
output:
|
||||||
|
canonical_family: unknown
|
||||||
|
canonical_object_type: 未分类对象
|
||||||
|
detection_key_template: unknown:{source_layer}
|
||||||
|
reason: 所有未命中 taxonomy 规则的对象统一回退到待补录分类。
|
||||||
114
tasks/pipline.md
Normal file
114
tasks/pipline.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# NavSea Weather Server
|
||||||
|
Task: WeatherServer_Pipeline
|
||||||
|
Architecture: NavSea V11
|
||||||
|
Codex: codex6
|
||||||
|
Status: TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1 任务目标
|
||||||
|
|
||||||
|
创建 Weather Pipeline。
|
||||||
|
|
||||||
|
Pipeline 负责自动执行整个天气数据生产流程:
|
||||||
|
|
||||||
|
Downloader
|
||||||
|
→ Grid Builder
|
||||||
|
→ Vector Tile Generator
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
NOAA GFS
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
/weather/{time}/{z}/{x}/{y}.pbf
|
||||||
|
|
||||||
|
用于 NavSea 客户端加载天气图层。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2 Pipeline 结构
|
||||||
|
|
||||||
|
weather_server/
|
||||||
|
|
||||||
|
downloader/
|
||||||
|
grid/
|
||||||
|
tiles/
|
||||||
|
pipeline/
|
||||||
|
|
||||||
|
创建文件:
|
||||||
|
|
||||||
|
weather_server/pipeline/weather_pipeline.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3 Pipeline 执行流程
|
||||||
|
|
||||||
|
step1 下载 GRIB
|
||||||
|
|
||||||
|
step2 生成 Weather Grid
|
||||||
|
|
||||||
|
step3 生成 Vector Tiles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4 创建 Pipeline 文件
|
||||||
|
|
||||||
|
weather_server/pipeline/weather_pipeline.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5 实现代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
DOWNLOADER = "weather_server/downloader/gfs_downloader.py"
|
||||||
|
GRID_BUILDER = "weather_server/grid/grid_builder_v2.py"
|
||||||
|
TILE_GENERATOR = "weather_server/tiles/vector_tile_generator.py"
|
||||||
|
|
||||||
|
|
||||||
|
def run_step(name, script):
|
||||||
|
|
||||||
|
print("\n==========================")
|
||||||
|
print("Running:", name)
|
||||||
|
print("==========================\n")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
["python", script],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("ERROR:", result.stderr)
|
||||||
|
raise RuntimeError(name + " failed")
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
|
||||||
|
print("\nFinished:", name)
|
||||||
|
print("Time:", round(end-start,2),"seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
print("\nNavSea Weather Pipeline\n")
|
||||||
|
|
||||||
|
run_step("Downloader", DOWNLOADER)
|
||||||
|
|
||||||
|
run_step("Grid Builder", GRID_BUILDER)
|
||||||
|
|
||||||
|
run_step("Vector Tile Generator", TILE_GENERATOR)
|
||||||
|
|
||||||
|
print("\nWeather pipeline completed.\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
83
tasks/test2.md
Normal file
83
tasks/test2.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# NavSea Weather Server
|
||||||
|
Step-2 测试:GFS Downloader
|
||||||
|
|
||||||
|
创建文件:
|
||||||
|
|
||||||
|
gfs_downloader.py
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
BASE_URL = "https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod"
|
||||||
|
|
||||||
|
FORECAST_HOURS = [
|
||||||
|
0,3,6,9,12,15,18,21,24,
|
||||||
|
27,30,33,36,39,42,45,
|
||||||
|
48,51,54,57,60,63,66,
|
||||||
|
69,72
|
||||||
|
]
|
||||||
|
|
||||||
|
OUTPUT_DIR = "data/grib"
|
||||||
|
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cycle():
|
||||||
|
|
||||||
|
now = datetime.utcnow()
|
||||||
|
|
||||||
|
hour = (now.hour // 6) * 6
|
||||||
|
|
||||||
|
cycle = f"{hour:02d}"
|
||||||
|
|
||||||
|
date = now.strftime("%Y%m%d")
|
||||||
|
|
||||||
|
return date, cycle
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(url, path):
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
print("skip", path)
|
||||||
|
return
|
||||||
|
|
||||||
|
print("downloading", url)
|
||||||
|
|
||||||
|
r = requests.get(url, stream=True)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
print("failed", url)
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
for chunk in r.iter_content(1024*1024):
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
date, cycle = get_cycle()
|
||||||
|
|
||||||
|
print("cycle:", date, cycle)
|
||||||
|
|
||||||
|
for fh in FORECAST_HOURS:
|
||||||
|
|
||||||
|
fh_str = f"{fh:03d}"
|
||||||
|
|
||||||
|
filename = f"gfs.t{cycle}z.pgrb2.0p25.f{fh_str}"
|
||||||
|
|
||||||
|
url = f"{BASE_URL}/gfs.{date}/{cycle}/atmos/{filename}"
|
||||||
|
|
||||||
|
output = os.path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
f"{date}_{cycle}_f{fh_str}.grib2"
|
||||||
|
)
|
||||||
|
|
||||||
|
download_file(url, output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
264
tasks/updatedownload.md
Normal file
264
tasks/updatedownload.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# NavSea Weather Server
|
||||||
|
Task: WeatherServer_GFS_Subset_Downloader
|
||||||
|
Architecture: V11
|
||||||
|
Codex: codex6
|
||||||
|
Status: TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 任务目标
|
||||||
|
|
||||||
|
升级现有 gfs_downloader.py
|
||||||
|
|
||||||
|
当前问题:
|
||||||
|
|
||||||
|
直接下载完整 GFS 文件:
|
||||||
|
|
||||||
|
gfs.t00z.pgrb2.0p25.f000
|
||||||
|
|
||||||
|
文件大小:
|
||||||
|
|
||||||
|
≈ 500MB
|
||||||
|
|
||||||
|
下载 72 小时预测需要:
|
||||||
|
|
||||||
|
≈ 12GB
|
||||||
|
|
||||||
|
这是不可接受的。
|
||||||
|
|
||||||
|
解决方案:
|
||||||
|
|
||||||
|
使用 NOAA 提供的 GRIB Subset API:
|
||||||
|
|
||||||
|
filter_gfs_0p25.pl
|
||||||
|
|
||||||
|
只下载:
|
||||||
|
|
||||||
|
1 指定区域
|
||||||
|
2 指定变量
|
||||||
|
3 指定高度层
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
将单个 GRIB 文件缩小到:
|
||||||
|
|
||||||
|
2MB – 10MB
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 下载区域
|
||||||
|
|
||||||
|
NavSea 天气服务器只需要日本附近区域:
|
||||||
|
|
||||||
|
leftlon = 120
|
||||||
|
rightlon = 150
|
||||||
|
toplat = 50
|
||||||
|
bottomlat = 20
|
||||||
|
|
||||||
|
覆盖:
|
||||||
|
|
||||||
|
日本海
|
||||||
|
东海
|
||||||
|
太平洋日本海域
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 下载变量
|
||||||
|
|
||||||
|
Weather System V1 需要以下变量:
|
||||||
|
|
||||||
|
UGRD
|
||||||
|
VGRD
|
||||||
|
APCP
|
||||||
|
PRMSL
|
||||||
|
TMP
|
||||||
|
HTSGW
|
||||||
|
DIRPW
|
||||||
|
PERPW
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
UGRD VGRD → 风
|
||||||
|
APCP → 降水
|
||||||
|
PRMSL → 气压
|
||||||
|
TMP → 温度
|
||||||
|
HTSGW → 浪高
|
||||||
|
DIRPW → 浪方向
|
||||||
|
PERPW → 浪周期
|
||||||
|
|
||||||
|
变量参数来自 NOAA GFS 参数列表。 :contentReference[oaicite:0]{index=0}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 下载高度层
|
||||||
|
|
||||||
|
需要以下层:
|
||||||
|
|
||||||
|
10 m above ground
|
||||||
|
surface
|
||||||
|
mean sea level
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
lev_10_m_above_ground
|
||||||
|
lev_surface
|
||||||
|
lev_mean_sea_level
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# URL 构造规则
|
||||||
|
|
||||||
|
基础地址:
|
||||||
|
|
||||||
|
https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl?
|
||||||
|
file=gfs.t00z.pgrb2.0p25.f003
|
||||||
|
&lev_10_m_above_ground=on
|
||||||
|
&lev_surface=on
|
||||||
|
&lev_mean_sea_level=on
|
||||||
|
&var_UGRD=on
|
||||||
|
&var_VGRD=on
|
||||||
|
&var_APCP=on
|
||||||
|
&var_PRMSL=on
|
||||||
|
&var_TMP=on
|
||||||
|
&var_HTSGW=on
|
||||||
|
&var_DIRPW=on
|
||||||
|
&var_PERPW=on
|
||||||
|
&leftlon=120
|
||||||
|
&rightlon=150
|
||||||
|
&toplat=50
|
||||||
|
&bottomlat=20
|
||||||
|
&dir=%2Fgfs.20260312%2F00%2Fatmos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 修改文件
|
||||||
|
|
||||||
|
weather_server/downloader/gfs_downloader.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 新代码
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
BASE_URL = "https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl"
|
||||||
|
|
||||||
|
OUTPUT_DIR = "data/grib"
|
||||||
|
|
||||||
|
FORECAST_HOURS = [
|
||||||
|
0,3,6,9,12,15,18,21,24,
|
||||||
|
27,30,33,36,39,42,45,
|
||||||
|
48,51,54,57,60,63,66,
|
||||||
|
69,72
|
||||||
|
]
|
||||||
|
|
||||||
|
REGION = {
|
||||||
|
"leftlon":120,
|
||||||
|
"rightlon":150,
|
||||||
|
"toplat":50,
|
||||||
|
"bottomlat":20
|
||||||
|
}
|
||||||
|
|
||||||
|
VARIABLES = [
|
||||||
|
"UGRD",
|
||||||
|
"VGRD",
|
||||||
|
"APCP",
|
||||||
|
"PRMSL",
|
||||||
|
"TMP",
|
||||||
|
"HTSGW",
|
||||||
|
"DIRPW",
|
||||||
|
"PERPW"
|
||||||
|
]
|
||||||
|
|
||||||
|
LEVELS = [
|
||||||
|
"lev_10_m_above_ground",
|
||||||
|
"lev_surface",
|
||||||
|
"lev_mean_sea_level"
|
||||||
|
]
|
||||||
|
|
||||||
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cycle():
|
||||||
|
|
||||||
|
now = datetime.utcnow()
|
||||||
|
|
||||||
|
hour = (now.hour // 6) * 6
|
||||||
|
|
||||||
|
cycle = f"{hour:02d}"
|
||||||
|
|
||||||
|
date = now.strftime("%Y%m%d")
|
||||||
|
|
||||||
|
return date, cycle
|
||||||
|
|
||||||
|
|
||||||
|
def build_url(date, cycle, fh):
|
||||||
|
|
||||||
|
filename = f"gfs.t{cycle}z.pgrb2.0p25.f{fh}"
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"file": filename,
|
||||||
|
"leftlon": REGION["leftlon"],
|
||||||
|
"rightlon": REGION["rightlon"],
|
||||||
|
"toplat": REGION["toplat"],
|
||||||
|
"bottomlat": REGION["bottomlat"],
|
||||||
|
"dir": f"/gfs.{date}/{cycle}/atmos"
|
||||||
|
}
|
||||||
|
|
||||||
|
for v in VARIABLES:
|
||||||
|
params[f"var_{v}"] = "on"
|
||||||
|
|
||||||
|
for l in LEVELS:
|
||||||
|
params[l] = "on"
|
||||||
|
|
||||||
|
return BASE_URL, params
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(url, params, path):
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
print("skip", path)
|
||||||
|
return
|
||||||
|
|
||||||
|
print("downloading", path)
|
||||||
|
|
||||||
|
r = requests.get(url, params=params, stream=True)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
print("failed", r.status_code)
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
for chunk in r.iter_content(1024*1024):
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
date, cycle = get_cycle()
|
||||||
|
|
||||||
|
print("cycle:", date, cycle)
|
||||||
|
|
||||||
|
for fh in FORECAST_HOURS:
|
||||||
|
|
||||||
|
fh_str = f"{fh:03d}"
|
||||||
|
|
||||||
|
url, params = build_url(date, cycle, fh_str)
|
||||||
|
|
||||||
|
output = os.path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
f"{date}_{cycle}_f{fh_str}.grib2"
|
||||||
|
)
|
||||||
|
|
||||||
|
download_file(url, params, output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
56
weather_pipeline.py
Normal file
56
weather_pipeline.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
DOWNLOADER = "gfs_downloader.py"
|
||||||
|
GRID_BUILDER = "grid/grid_builder_v2.py"
|
||||||
|
TILE_GENERATOR = "tiles/vector_tile_generator.py"
|
||||||
|
|
||||||
|
|
||||||
|
def run_step(name, script):
|
||||||
|
print("\n==========================")
|
||||||
|
print("Running:", name)
|
||||||
|
print("==========================\n")
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
["python", script],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("ERROR:", result.stderr)
|
||||||
|
raise RuntimeError(name + " failed")
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
|
||||||
|
print("\nFinished:", name)
|
||||||
|
print("Time:", round(end-start,2),"seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Weather Pipeline Starting...")
|
||||||
|
print("Date:", time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_step("GFS Downloader", DOWNLOADER)
|
||||||
|
run_step("Grid Builder v2", GRID_BUILDER)
|
||||||
|
run_step("Vector Tile Generator", TILE_GENERATOR)
|
||||||
|
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("Weather Pipeline Completed Successfully!")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
except RuntimeError as e:
|
||||||
|
print("\n" + "!"*50)
|
||||||
|
print("Pipeline Failed:", str(e))
|
||||||
|
print("!"*50)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user