Compare commits
13 Commits
4f96d7be91
...
69b4f961a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69b4f961a6 | ||
|
|
e52328b3f5 | ||
|
|
35d713993f | ||
|
|
d678851f21 | ||
|
|
2513dd56f8 | ||
|
|
15994b5c7e | ||
|
|
7105c2f405 | ||
|
|
924df2edca | ||
|
|
bc31552437 | ||
|
|
08a00a86c7 | ||
|
|
6d72340f36 | ||
|
|
b0f68dbe39 | ||
|
|
d50b127eae |
290
NavSea_全国三层数据生成与查看说明.md
Normal file
290
NavSea_全国三层数据生成与查看说明.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# NavSea 全国四脚本一页面数据生成与查看说明
|
||||
|
||||
本文说明全国版三层数据的重算、导出、整体密度图生成和页面查看方式。
|
||||
|
||||
当前统一数据库:
|
||||
|
||||
- `navsea_japan_coast_grid`
|
||||
|
||||
当前全国三层:
|
||||
|
||||
- `coast_200m`:全国海岸 200x200
|
||||
- `fish_port_20m`:全国渔港 20x20
|
||||
- `hazard_50m`:全国海上障碍 50x50
|
||||
|
||||
当前整体密度图:
|
||||
|
||||
- `density_overview`:三档密度整体格子图
|
||||
|
||||
## 1. 生成顺序
|
||||
|
||||
建议按下面顺序重算:
|
||||
|
||||
1. 先生成 `200x200`
|
||||
2. 再生成 `20x20`
|
||||
3. 最后生成 `50x50`
|
||||
4. 再导出全国静态 JSON 资产
|
||||
5. 最后查看整体密度图
|
||||
|
||||
这样可以先把底盘准备好,再做上层数据。
|
||||
|
||||
## 2. 200x200 全国海岸重算
|
||||
|
||||
脚本:
|
||||
|
||||
- [`coastline/build_japan_coast_grid_mysql.py`](/root/sourceserver/pbf/coastline/build_japan_coast_grid_mysql.py)
|
||||
|
||||
默认会写入:
|
||||
|
||||
- 数据库:`navsea_japan_coast_grid`
|
||||
- 图层:`coast_200m`
|
||||
- 导出目录:`out/coastline/japan_coast_grid_mysql`
|
||||
|
||||
执行命令:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/build_japan_coast_grid_mysql.py
|
||||
```
|
||||
|
||||
## 3. 20x20 全国渔港重算
|
||||
|
||||
脚本:
|
||||
|
||||
- [`coastline/build_fish_port_20m_full_mysql_resume.py`](/root/sourceserver/pbf/coastline/build_fish_port_20m_full_mysql_resume.py)
|
||||
|
||||
默认会写入:
|
||||
|
||||
- 数据库:`navsea_japan_coast_grid`
|
||||
- 图层:`fish_port_20m`
|
||||
|
||||
推荐执行命令:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/build_fish_port_20m_full_mysql_resume.py \
|
||||
--resume \
|
||||
--max-scan-cells 200000
|
||||
```
|
||||
|
||||
如果只想重算某个 `PRC`,可以显式指定,例如:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/build_fish_port_20m_full_mysql_resume.py \
|
||||
--prc 02 \
|
||||
--max-scan-cells 200000
|
||||
```
|
||||
|
||||
如果只想重算某个具体渔港,可以直接用 `FPC`,例如:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/build_fish_port_20m_full_mysql_resume.py \
|
||||
--fpc 1210100 \
|
||||
--max-scan-cells 200000
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `PRC` 需要按两位字符串理解,`2` 和 `02` 等价
|
||||
- `FPC` 是单个渔港的港码,脚本会先反查它所属的 `PRC`,再只重算这个港口
|
||||
- 单港口模式会在日志里同时输出:
|
||||
- 命中的 `200x200` 粗格数量
|
||||
- 最终写入的 `20x20` 格子数量
|
||||
- 默认先按渔港 bbox 找同区域 `coast_200m` 粗格,再把粗格切成 `20x20`
|
||||
- 默认不会再允许无边界整片扫描
|
||||
- 如果确实要回退旧行为,需要手动加脚本里提供的相应开关
|
||||
|
||||
## 4. 根据港名查 FPC
|
||||
|
||||
脚本:
|
||||
|
||||
- [`find_fpc_by_port_name.py`](/root/sourceserver/pbf/find_fpc_by_port_name.py)
|
||||
|
||||
用途:
|
||||
|
||||
- 已知渔港名字时,先查出可能的 `FPC`
|
||||
- 这个脚本会扫描 `coastline/C09-06.zip` 里的渔港要素
|
||||
- 支持模糊匹配和精确匹配
|
||||
|
||||
推荐用法:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python find_fpc_by_port_name.py 無垢島
|
||||
```
|
||||
|
||||
如果你想只看精确命中:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python find_fpc_by_port_name.py 無垢島 --exact
|
||||
```
|
||||
|
||||
如果你想要 JSON 结果:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python find_fpc_by_port_name.py 無垢島 --json
|
||||
```
|
||||
|
||||
如果原始包里没有港名字段,建议改用港名对照表:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python find_fpc_by_port_name.py 無垢島 --catalog out/port_name_catalog.csv
|
||||
```
|
||||
|
||||
对照表最低需要这些列里的任意几列:
|
||||
|
||||
- `name` / `港名` / `名称`
|
||||
- `FPC`
|
||||
- 可选 `PRC`
|
||||
|
||||
说明:
|
||||
|
||||
- 这份脚本主要查 `C09-06.zip` 里的渔港名称字段
|
||||
- 默认会把 `NA2`、`NA4`、`FCF` 等候选字段一起拿来比对
|
||||
- 输出里会给出:
|
||||
- `FPC`
|
||||
- `PRC`
|
||||
- 主名称
|
||||
- 辅助名称
|
||||
- 源记录 id
|
||||
- 如果原始包里没有这条名字,脚本会提示你改用 `--catalog`
|
||||
## 5. 50x50 全国障碍重算
|
||||
|
||||
脚本:
|
||||
|
||||
- [`coastline/build_japan_hazard_50m_mysql.py`](/root/sourceserver/pbf/coastline/build_japan_hazard_50m_mysql.py)
|
||||
|
||||
默认会写入:
|
||||
|
||||
- 数据库:`navsea_japan_coast_grid`
|
||||
- 图层:`hazard_50m`
|
||||
- PBF 根目录:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
|
||||
|
||||
执行命令:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/build_japan_hazard_50m_mysql.py
|
||||
```
|
||||
|
||||
这个脚本当前会纳入:
|
||||
|
||||
- `navigation_hazard_area`
|
||||
- `fixed_fishing_gear_area`
|
||||
- `anchor_caution_hazard_area`
|
||||
- `navigation_hazard_point`
|
||||
- `anchor_caution_hazard_point`
|
||||
- `navigation_marks`
|
||||
- `baseline_area` 中 `canonical_object_type=breakwater`
|
||||
|
||||
## 6. 全国三层静态 JSON 导出
|
||||
|
||||
脚本:
|
||||
|
||||
- [`coastline/export_navgrid_mysql_assets.py`](/root/sourceserver/pbf/coastline/export_navgrid_mysql_assets.py)
|
||||
|
||||
默认导出到:
|
||||
|
||||
- `src/pbf/coastline-mysql/japan_national/`
|
||||
|
||||
执行命令:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/export_navgrid_mysql_assets.py \
|
||||
--db-name navsea_japan_coast_grid \
|
||||
--out-dir src/pbf/coastline-mysql/japan_national
|
||||
```
|
||||
|
||||
导出结果包括:
|
||||
|
||||
- `coast_200m_grid.geojson`
|
||||
- `fish_port_20m_grid.geojson`
|
||||
- `hazard_50m_grid.geojson`
|
||||
- `density_overview_grid.geojson`
|
||||
- `manifest.json`
|
||||
|
||||
说明:
|
||||
|
||||
- `density_overview_grid.geojson` 是按密度优先级合并后的整体格子图
|
||||
- 同一区域如果存在更高密度格子,就优先保留更高密度
|
||||
- 当前密度优先级:
|
||||
- `20x20` = 高密度,淡绿色
|
||||
- `50x50` = 中密度,黄色
|
||||
- `200x200` = 低密度,淡红色
|
||||
|
||||
## 7. HTML 预览页面
|
||||
|
||||
当前全国预览页面:
|
||||
|
||||
- `http://192.168.200.184/newpec/navsea-coastline-fukuoka-saga-200m.html`
|
||||
|
||||
说明:
|
||||
|
||||
- 页面标题已经改成全国口径
|
||||
- 页面内有四个按钮:
|
||||
- `200x200`
|
||||
- `20x20`
|
||||
- `50x50`
|
||||
- `整体密度`
|
||||
|
||||
页面对应的本地文件:
|
||||
|
||||
- [`src/pbf/navsea-coastline-fukuoka-saga-200m.html`](/root/sourceserver/pbf/src/pbf/navsea-coastline-fukuoka-saga-200m.html)
|
||||
|
||||
页面加载的数据源:
|
||||
|
||||
- `./coastline-mysql/japan_coast_200m/manifest.json`
|
||||
- `./coastline-mysql/japan_coast_200m/coast_200m_grid.geojson`
|
||||
- `./coastline-mysql/japan_national/manifest.json`
|
||||
- `./coastline-mysql/japan_national/fish_port_20m_grid.geojson`
|
||||
- `./coastline-mysql/japan_national/hazard_50m_grid.geojson`
|
||||
- `./coastline-mysql/japan_national/density_overview_grid.geojson`
|
||||
|
||||
## 8. 页面查看方式
|
||||
|
||||
如果只是看全国 200x200:
|
||||
|
||||
1. 打开页面
|
||||
2. 默认就是 `200x200`
|
||||
3. 页面会自动定位到全国底盘范围
|
||||
|
||||
如果想切换看渔港或障碍:
|
||||
|
||||
1. 点击 `20x20`
|
||||
2. 点击 `50x50`
|
||||
3. 点击 `整体密度`
|
||||
|
||||
如果想强制刷新:
|
||||
|
||||
1. 点击 `重新加载`
|
||||
|
||||
## 9. 线下同步到网页目录
|
||||
|
||||
如果你重新生成了静态资产,想让线上页面立即看到新内容,通常需要把生成结果同步到网页目录:
|
||||
|
||||
```bash
|
||||
cp -r src/pbf/coastline-mysql/japan_national /mnt/sda1/www/newpec/
|
||||
cp src/pbf/navsea-coastline-fukuoka-saga-200m.html /mnt/sda1/www/newpec/navsea-coastline-fukuoka-saga-200m.html
|
||||
```
|
||||
|
||||
## 10. 一句话版
|
||||
|
||||
最常用的整套命令是:
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
./.venv/bin/python coastline/build_japan_coast_grid_mysql.py
|
||||
./.venv/bin/python coastline/build_fish_port_20m_full_mysql_resume.py --resume --max-scan-cells 200000
|
||||
./.venv/bin/python coastline/build_japan_hazard_50m_mysql.py
|
||||
./.venv/bin/python coastline/export_navgrid_mysql_assets.py --db-name navsea_japan_coast_grid --out-dir src/pbf/coastline-mysql/japan_national
|
||||
```
|
||||
|
||||
然后打开:
|
||||
|
||||
- `http://192.168.200.184/newpec/navsea-coastline-fukuoka-saga-200m.html`
|
||||
480
NavSea_全国完整PBF重建流水线说明.md
Normal file
480
NavSea_全国完整PBF重建流水线说明.md
Normal file
@@ -0,0 +1,480 @@
|
||||
# NavSea 全国 Full PBF 重建流水线说明
|
||||
|
||||
最后确认:`2026-06-11`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本文固化当前全国 Full PBF 基线的可重复重建节点:
|
||||
|
||||
- 当前基线目录:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
|
||||
- 原始海图来源:全国 `newpec` PBF
|
||||
- 语义来源:MySQL `pbf_analysis`
|
||||
- 固定规则包:`navsea-core`
|
||||
- 固定代码节点:Git commit `6d72340`
|
||||
|
||||
本文用于以后重新生成与 `pbf-delivery-full-20260418-rebuild` 业务内容等价的全国 Full PBF。
|
||||
|
||||
这里的“业务内容等价”是指:
|
||||
|
||||
- 瓦片范围一致
|
||||
- source layer、对象和属性语义一致
|
||||
- 几何与 extent 一致
|
||||
- 样式消费结果和人工视觉判断一致
|
||||
|
||||
不强制要求每个 PBF 文件的二进制 SHA256 完全一致。编码库版本、属性顺序或 feature 编码顺序变化,都可能造成字节不同。
|
||||
|
||||
## 2. 当前确认条件
|
||||
|
||||
### 2.1 数据库
|
||||
|
||||
当前已确认数据库内容没有被修改。
|
||||
|
||||
数据库配置由 `navsea_tile_builder.py` 的 `DbConfig` 提供:
|
||||
|
||||
- database:`pbf_analysis`
|
||||
- unix socket:`/tmp/mysql.sock`
|
||||
|
||||
当前关键表数量:
|
||||
|
||||
| 表 | 行数 |
|
||||
| --- | ---: |
|
||||
| `features` | `6685117` |
|
||||
| `properties` | `23659423` |
|
||||
| `pbf_relayer_candidates` | `6685117` |
|
||||
|
||||
规则包:
|
||||
|
||||
- `bundle_id = navsea-core`
|
||||
- 当前状态:`v1-draft`
|
||||
|
||||
### 2.2 原始 PBF
|
||||
|
||||
原始几何与原始属性来源:
|
||||
|
||||
```text
|
||||
/home/wwwroot/newpec/exported_auto/
|
||||
tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles
|
||||
```
|
||||
|
||||
本文后续命令将其写成单行路径。
|
||||
|
||||
### 2.3 Reference Footprint
|
||||
|
||||
全国瓦片生成范围使用:
|
||||
|
||||
```text
|
||||
/home/wwwroot/pbf-delivery-full-20260415
|
||||
```
|
||||
|
||||
`--reference-tile-root` 只决定需要生成哪些 `z/x/y` 瓦片。
|
||||
|
||||
实际瓦片几何仍读取原始 `newpec` PBF,语义数据仍读取 MySQL。不要把 `20260415` 理解成新包的内容来源。
|
||||
|
||||
### 2.4 固定代码版本
|
||||
|
||||
2026-04-18 全国分片重建时,固定使用的是当时的 HEAD 版本。对应可追溯节点:
|
||||
|
||||
```text
|
||||
6d72340 guard high-extent tile reencoding
|
||||
```
|
||||
|
||||
当前工作区的 `navsea_tile_builder.py` 已经包含后续修改,不能用于严格复建该历史基线。应使用独立 Git worktree,同时固定:
|
||||
|
||||
- `navsea_tile_builder.py`
|
||||
- `navsea_mapping_registry.py`
|
||||
- `tasks/pbf/mappings/` 下的规则文件
|
||||
|
||||
## 3. 流水线总览
|
||||
|
||||
```text
|
||||
原始 newpec 全国 PBF
|
||||
+
|
||||
MySQL pbf_analysis 语义表
|
||||
+
|
||||
navsea-core 映射规则
|
||||
↓
|
||||
固定 6d72340 代码节点
|
||||
↓
|
||||
按 z0-8 / z9-10 / z11 / z12 分片重建
|
||||
↓
|
||||
合并全国候选根目录
|
||||
↓
|
||||
瓦片数量与 mapping audit
|
||||
↓
|
||||
对象保真与 geometry / extent audit
|
||||
↓
|
||||
全国固定 AOI 视觉审计
|
||||
↓
|
||||
人工确认后切换 style 和正式目录
|
||||
```
|
||||
|
||||
## 4. 重建前准备
|
||||
|
||||
### 4.1 禁止直接覆盖当前基线
|
||||
|
||||
不要直接写入:
|
||||
|
||||
```text
|
||||
/home/wwwroot/pbf-delivery-full-20260418-rebuild
|
||||
```
|
||||
|
||||
先生成候选目录,审计通过后再切换。本文示例候选目录:
|
||||
|
||||
```text
|
||||
/home/wwwroot/pbf-delivery-full-rebuild-candidate
|
||||
```
|
||||
|
||||
### 4.2 建立固定代码 worktree
|
||||
|
||||
```bash
|
||||
cd /root/sourceserver/pbf
|
||||
|
||||
git worktree add --detach /tmp/navsea-full-rebuild-code 6d72340
|
||||
```
|
||||
|
||||
检查固定版本:
|
||||
|
||||
```bash
|
||||
git -C /tmp/navsea-full-rebuild-code rev-parse HEAD
|
||||
```
|
||||
|
||||
期望以 `6d72340` 开头。
|
||||
|
||||
### 4.3 设置路径变量
|
||||
|
||||
```bash
|
||||
export REPO=/root/sourceserver/pbf
|
||||
export CODE=/tmp/navsea-full-rebuild-code
|
||||
export PY=/root/sourceserver/pbf/.venv/bin/python
|
||||
|
||||
export SOURCE=/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles
|
||||
export REFERENCE=/home/wwwroot/pbf-delivery-full-20260415
|
||||
|
||||
export PARTS=/home/wwwroot/pbf-delivery-full-rebuild-candidate.parts
|
||||
export CANDIDATE=/home/wwwroot/pbf-delivery-full-rebuild-candidate
|
||||
```
|
||||
|
||||
### 4.4 输入预检
|
||||
|
||||
```bash
|
||||
test -d "$SOURCE"
|
||||
test -d "$REFERENCE"
|
||||
test -x "$PY"
|
||||
test -f "$CODE/navsea_tile_builder.py"
|
||||
```
|
||||
|
||||
检查 reference 瓦片总数:
|
||||
|
||||
```bash
|
||||
find "$REFERENCE" -type f -name '*.pbf' | wc -l
|
||||
```
|
||||
|
||||
当前期望:
|
||||
|
||||
```text
|
||||
65148
|
||||
```
|
||||
|
||||
检查数据库连接和关键表:
|
||||
|
||||
```bash
|
||||
cd "$REPO"
|
||||
|
||||
$PY - <<'PY'
|
||||
import pymysql
|
||||
from navsea_tile_builder import DbConfig
|
||||
|
||||
config = DbConfig()
|
||||
connection = pymysql.connect(
|
||||
host=config.host,
|
||||
port=config.port,
|
||||
user=config.user,
|
||||
password=config.password,
|
||||
database=config.database,
|
||||
unix_socket=config.unix_socket,
|
||||
)
|
||||
|
||||
with connection:
|
||||
with connection.cursor() as cursor:
|
||||
for table in ("features", "properties", "pbf_relayer_candidates"):
|
||||
cursor.execute(f"SELECT COUNT(*) FROM `{table}`")
|
||||
print(table, cursor.fetchone()[0])
|
||||
PY
|
||||
```
|
||||
|
||||
## 5. 全国分片重建
|
||||
|
||||
### 5.1 清理候选输出
|
||||
|
||||
候选目录只能在确认它不是正式目录后删除:
|
||||
|
||||
```bash
|
||||
rm -rf "$PARTS" "$CANDIDATE"
|
||||
mkdir -p "$PARTS"
|
||||
```
|
||||
|
||||
### 5.2 通用构建参数
|
||||
|
||||
四个分片必须使用相同业务参数:
|
||||
|
||||
```text
|
||||
--strip-legacy-japanese-delivery
|
||||
--release-minimal
|
||||
--fid-key 'thisMyWorld@2026'
|
||||
--bundle-id navsea-core
|
||||
```
|
||||
|
||||
机器实际只有 4 核。建议四个分片各使用 `workers=1` 并行执行,避免沿用历史上的过度并发参数。
|
||||
|
||||
### 5.3 z0 至 z8
|
||||
|
||||
```bash
|
||||
$PY "$CODE/navsea_tile_builder.py" \
|
||||
--source-root "$SOURCE" \
|
||||
--reference-tile-root "$REFERENCE" \
|
||||
--output "$PARTS/z0_8" \
|
||||
--zmin 0 \
|
||||
--zmax 8 \
|
||||
--workers 1 \
|
||||
--strip-legacy-japanese-delivery \
|
||||
--release-minimal \
|
||||
--fid-key 'thisMyWorld@2026' \
|
||||
--bundle-id navsea-core
|
||||
```
|
||||
|
||||
### 5.4 z9 至 z10
|
||||
|
||||
```bash
|
||||
$PY "$CODE/navsea_tile_builder.py" \
|
||||
--source-root "$SOURCE" \
|
||||
--reference-tile-root "$REFERENCE" \
|
||||
--output "$PARTS/z9_10" \
|
||||
--zmin 9 \
|
||||
--zmax 10 \
|
||||
--workers 1 \
|
||||
--strip-legacy-japanese-delivery \
|
||||
--release-minimal \
|
||||
--fid-key 'thisMyWorld@2026' \
|
||||
--bundle-id navsea-core
|
||||
```
|
||||
|
||||
### 5.5 z11
|
||||
|
||||
```bash
|
||||
$PY "$CODE/navsea_tile_builder.py" \
|
||||
--source-root "$SOURCE" \
|
||||
--reference-tile-root "$REFERENCE" \
|
||||
--output "$PARTS/z11" \
|
||||
--zmin 11 \
|
||||
--zmax 11 \
|
||||
--workers 1 \
|
||||
--strip-legacy-japanese-delivery \
|
||||
--release-minimal \
|
||||
--fid-key 'thisMyWorld@2026' \
|
||||
--bundle-id navsea-core
|
||||
```
|
||||
|
||||
### 5.6 z12
|
||||
|
||||
```bash
|
||||
$PY "$CODE/navsea_tile_builder.py" \
|
||||
--source-root "$SOURCE" \
|
||||
--reference-tile-root "$REFERENCE" \
|
||||
--output "$PARTS/z12" \
|
||||
--zmin 12 \
|
||||
--zmax 12 \
|
||||
--workers 1 \
|
||||
--strip-legacy-japanese-delivery \
|
||||
--release-minimal \
|
||||
--fid-key 'thisMyWorld@2026' \
|
||||
--bundle-id navsea-core
|
||||
```
|
||||
|
||||
如需四段并行,应分别保存日志并检查每个命令的退出状态。不能仅根据目录里已经出现 PBF 就判断任务完成。
|
||||
|
||||
## 6. 分片验收与合并
|
||||
|
||||
### 6.1 检查 mapping audit
|
||||
|
||||
每个分片会在其父目录生成对应的 mapping audit 文件。必须检查:
|
||||
|
||||
- 构建进程正常退出
|
||||
- `written` 数量符合对应 zoom 范围
|
||||
- 没有影响交付字段的 unresolved mapping
|
||||
- 没有异常大批量 skipped tile
|
||||
|
||||
### 6.2 合并候选根目录
|
||||
|
||||
```bash
|
||||
mkdir -p "$CANDIDATE"
|
||||
|
||||
cp -a "$PARTS/z0_8/." "$CANDIDATE/"
|
||||
cp -a "$PARTS/z9_10/." "$CANDIDATE/"
|
||||
cp -a "$PARTS/z11/." "$CANDIDATE/"
|
||||
cp -a "$PARTS/z12/." "$CANDIDATE/"
|
||||
```
|
||||
|
||||
这些分片的 zoom 范围互不重叠,合并时不应出现同路径覆盖冲突。
|
||||
|
||||
### 6.3 检查瓦片数量
|
||||
|
||||
```bash
|
||||
find "$CANDIDATE" -type f -name '*.pbf' -printf '%P\n' |
|
||||
awk -F/ '{count[$1]++} END {for (z in count) print "z" z "=" count[z]}' |
|
||||
sort -V
|
||||
```
|
||||
|
||||
当前基线期望:
|
||||
|
||||
| Zoom | 瓦片数 |
|
||||
| ---: | ---: |
|
||||
| `0` | `1` |
|
||||
| `5` | `10` |
|
||||
| `6` | `27` |
|
||||
| `7` | `76` |
|
||||
| `8` | `230` |
|
||||
| `9` | `813` |
|
||||
| `10` | `3156` |
|
||||
| `11` | `12279` |
|
||||
| `12` | `48556` |
|
||||
| 合计 | `65148` |
|
||||
|
||||
当前数据库没有 `z1-z4` 对应的受支持输出,因此最终目录中没有这些 zoom 的 PBF。
|
||||
|
||||
## 7. 交付审计
|
||||
|
||||
审计顺序必须保持:
|
||||
|
||||
1. 对象保真审计
|
||||
2. 渲染与 geometry / extent 审计
|
||||
3. AOI / 人工视觉审计
|
||||
|
||||
不能因为画面看起来接近,就跳过对象和几何门禁。
|
||||
|
||||
### 7.1 Geometry / Extent 审计
|
||||
|
||||
```bash
|
||||
cd "$REPO"
|
||||
|
||||
$PY navsea_geometry_native_audit.py \
|
||||
--target-root "$CANDIDATE" \
|
||||
--label full-rebuild-candidate \
|
||||
--output-dir report/full_rebuild_candidate_geometry
|
||||
```
|
||||
|
||||
重点检查:
|
||||
|
||||
- `source_extent_mismatch_tiles = 0`
|
||||
- 不出现历史上的 `extent=4096` 错退
|
||||
- 高精度 tile 继续保持原始 source 的 `extent=1048576`
|
||||
- 不出现旧坏片式的大范围几何错移
|
||||
|
||||
坐标超 extent 本身不一定是错误。必须结合 source extent 和原始 tile 对照判断。
|
||||
|
||||
### 7.2 与当前基线做结构比较
|
||||
|
||||
不要只比较 SHA256。推荐逐瓦片解码后比较:
|
||||
|
||||
- layer 集合
|
||||
- layer extent
|
||||
- feature 数量
|
||||
- feature geometry
|
||||
- 标准化后的 properties
|
||||
- NavSea FID
|
||||
|
||||
对结构差异必须输出具体 `z/x/y`、layer 和 feature 锚点,不能只给一个全局差异数量。
|
||||
|
||||
### 7.3 全国 AOI 视觉审计
|
||||
|
||||
候选目录需要可通过 HTTP 访问,例如:
|
||||
|
||||
```text
|
||||
http://192.168.200.184/pbf-delivery-full-rebuild-candidate/{z}/{x}/{y}.pbf
|
||||
```
|
||||
|
||||
通过 compare 页的临时覆盖参数审计候选包:
|
||||
|
||||
```bash
|
||||
$PY navsea_full_aoi_visual_audit.py \
|
||||
--compare-url 'http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-rebuild-candidate%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf' \
|
||||
--variant full \
|
||||
--output-dir report/full_rebuild_candidate_visual
|
||||
```
|
||||
|
||||
人工复核至少覆盖:
|
||||
|
||||
- 博多港
|
||||
- 唐津
|
||||
- 东京湾
|
||||
- 大阪湾
|
||||
- 冲绳
|
||||
- `z10`
|
||||
- `z12`
|
||||
|
||||
重点确认:
|
||||
|
||||
- 没有大片空白
|
||||
- 岛屿、岸线和危险物没有错位
|
||||
- 安全图标和关键文字没有缺失
|
||||
- 点击拾取仍能定位到正确对象
|
||||
|
||||
## 8. 发布切换
|
||||
|
||||
所有审计通过前,不修改正式 style,不覆盖当前基线。
|
||||
|
||||
推荐发布方式:
|
||||
|
||||
1. 保留当前 `/home/wwwroot/pbf-delivery-full-20260418-rebuild`
|
||||
2. 将候选目录部署为带新日期或版本号的正式目录
|
||||
3. 更新 extentfix style 的 tile URL
|
||||
4. 更新全国 compare 页可见版本字符串
|
||||
5. 部署 style 和 compare 页
|
||||
6. 清缓存后再次完成固定 AOI 人工确认
|
||||
7. 在 `STEP_RECORD.md` 记录新目录、版本、审计报告和切换时间
|
||||
|
||||
只有明确要求原路径替换时,才对 `pbf-delivery-full-20260418-rebuild` 做原子目录切换。切换前必须保留可快速回退的旧目录。
|
||||
|
||||
## 9. 历史脚本的职责边界
|
||||
|
||||
### `navsea_tile_builder.py`
|
||||
|
||||
全国 Full PBF 的核心生成器。
|
||||
|
||||
负责:
|
||||
|
||||
- 从原始 PBF 读取几何与原始 feature
|
||||
- 从 MySQL 读取语义投影
|
||||
- 应用 NavSea mapping registry
|
||||
- 输出 delivery layer、标准字段和 NavSea FID
|
||||
|
||||
### `navsea_rebuild_hotspot_tiles.py`
|
||||
|
||||
只用于固定 AOI 热点重建和问题定位。
|
||||
|
||||
它不是全国 Full PBF 的正式生成 pipeline,不能用它代替本文的全国分片重建。
|
||||
|
||||
### `navsea_geometry_native_audit.py`
|
||||
|
||||
负责 geometry、extent 和 native 风险审计。
|
||||
|
||||
### `navsea_full_aoi_visual_audit.py`
|
||||
|
||||
负责全国固定 AOI 的浏览器截图和视觉 diff。
|
||||
|
||||
## 10. 节点结论
|
||||
|
||||
以后需要重新生成全国 Full PBF 时,以本文作为入口节点。
|
||||
|
||||
固定重建要素为:
|
||||
|
||||
```text
|
||||
代码节点:6d72340
|
||||
数据库:pbf_analysis 当前确认未修改的数据
|
||||
规则包:navsea-core
|
||||
原始 PBF:newpec-mvt-20260106
|
||||
reference footprint:pbf-delivery-full-20260415
|
||||
生成方式:四段分片重建后合并
|
||||
审计顺序:对象保真 → 渲染/几何 → AOI 视觉
|
||||
```
|
||||
|
||||
如果上述任一输入发生变化,生成结果应视为新的全国版本,不能继续沿用 `20260418-rebuild` 的版本含义。
|
||||
2988
STEP_RECORD.md
2988
STEP_RECORD.md
File diff suppressed because it is too large
Load Diff
354
build_semantic_delivery_assets.py
Normal file
354
build_semantic_delivery_assets.py
Normal file
@@ -0,0 +1,354 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import mapbox_vector_tile
|
||||
from PIL import Image
|
||||
|
||||
from navsea_tile_reencode_guard import (
|
||||
assert_reencoded_tile_safe,
|
||||
decode_tile_with_extents,
|
||||
encode_layers_preserving_extents,
|
||||
)
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent
|
||||
BASE_AUDIT_CSV = REPO_ROOT / "tasks/pbf/NavSea_Sprite_Audit_newpec_style_with_pbf_2026-04-16.csv"
|
||||
T_OVERRIDE_CSV = REPO_ROOT / "tasks/pbf/NavSea_Sprite_Basis_T_建议修正表_2026-04-16.csv"
|
||||
MAPPING_JSON = REPO_ROOT / "src/pbf/semantic_sprite_key_map_2026-04-16.json"
|
||||
|
||||
SOURCE_SPRITE_JSON_2X = Path("/mnt/sda1/www/newpec/sprite/sprite@2x.json")
|
||||
SOURCE_SPRITE_PNG_2X = Path("/mnt/sda1/www/newpec/sprite/sprite@2x.png")
|
||||
OUTPUT_SPRITE_DIR = Path("/mnt/sda1/www/newpec/sprite-semantic")
|
||||
|
||||
SOURCE_STYLE = REPO_ROOT / "src/pbf/style.navsea-delivery-full.json"
|
||||
OUTPUT_STYLE = REPO_ROOT / "src/pbf/style.navsea-delivery-full-semantic.json"
|
||||
DEPLOYED_STYLE = Path("/mnt/sda1/www/newpec/domain/style.navsea-delivery-full-semantic.json")
|
||||
|
||||
SOURCE_PBF_ROOT = Path("/home/wwwroot/pbf-delivery-full-20260415")
|
||||
OUTPUT_PBF_ROOT = Path("/home/wwwroot/pbf-delivery-full-semantic-20260416")
|
||||
|
||||
SEMANTIC_SPRITE_BASE_URL = "http://192.168.200.184/newpec/sprite-semantic/sprite"
|
||||
SEMANTIC_PBF_TILE_URL = "http://192.168.200.184/pbf-delivery-full-semantic-20260416/{z}/{x}/{y}.pbf"
|
||||
STYLE_VERSION = "full-semantic-style-r1-20260416-1628"
|
||||
PBF_VERSION = "full-semantic-pbf-r1-20260416-1628"
|
||||
SPRITE_VERSION = "full-semantic-sprite-r1-20260416-1628"
|
||||
|
||||
PBF_PROPERTY_FIELDS = ("chart_icon_image", "chart_fill_pattern")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AuditRow:
|
||||
old_sprite_key: str
|
||||
used_in_newpec_style: int
|
||||
used_in_pbf: int
|
||||
new_sprite_key: str
|
||||
|
||||
|
||||
def normalize_key(value: str) -> str:
|
||||
value = re.sub(r"\s*\[[^\]]+\]\s*", "", value.strip())
|
||||
value = value.replace("-", "_").replace(" ", "_").lower()
|
||||
value = (
|
||||
value.replace("easte", "east")
|
||||
.replace("sourth", "south")
|
||||
.replace("varian", "variant")
|
||||
.replace("cardianal", "cardinal")
|
||||
)
|
||||
value = re.sub(r"_+", "_", value).strip("_")
|
||||
return value
|
||||
|
||||
|
||||
def load_audit_rows() -> list[AuditRow]:
|
||||
overrides: dict[str, str] = {}
|
||||
with T_OVERRIDE_CSV.open(encoding="utf-8-sig") as handle:
|
||||
for row in csv.DictReader(handle):
|
||||
overrides[row["old_sprite_key"].strip()] = row["suggested_new_sprite_key"].strip()
|
||||
|
||||
rows: list[AuditRow] = []
|
||||
with BASE_AUDIT_CSV.open(encoding="utf-8-sig") as handle:
|
||||
for row in csv.DictReader(handle):
|
||||
old_key = row["old_sprite_key"].strip()
|
||||
new_key = overrides.get(old_key) or row["new_sprite_key"].strip()
|
||||
rows.append(
|
||||
AuditRow(
|
||||
old_sprite_key=old_key,
|
||||
used_in_newpec_style=int(row["used_in_newpec_style"] or 0),
|
||||
used_in_pbf=int(row["used_in_pbf"] or 0),
|
||||
new_sprite_key=normalize_key(new_key) if new_key else "",
|
||||
)
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def build_mapping(rows: list[AuditRow]) -> dict[str, str]:
|
||||
mapping: dict[str, str] = {}
|
||||
for row in rows:
|
||||
if row.new_sprite_key:
|
||||
mapping[row.old_sprite_key] = row.new_sprite_key
|
||||
return mapping
|
||||
|
||||
|
||||
def build_used_sprite_entries(rows: list[AuditRow], mapping: dict[str, str]) -> list[tuple[str, str]]:
|
||||
used: list[tuple[str, str]] = []
|
||||
for row in rows:
|
||||
if row.used_in_newpec_style <= 0 and row.used_in_pbf <= 0:
|
||||
continue
|
||||
used.append((row.old_sprite_key, mapping.get(row.old_sprite_key, row.old_sprite_key)))
|
||||
return used
|
||||
|
||||
|
||||
def write_mapping_json(mapping: dict[str, str]) -> None:
|
||||
MAPPING_JSON.write_text(
|
||||
json.dumps(dict(sorted(mapping.items())), ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def pack_entries(
|
||||
entries: list[tuple[str, str]],
|
||||
source_meta: dict[str, dict],
|
||||
scale: float,
|
||||
max_width: int,
|
||||
) -> tuple[Image.Image, dict[str, dict]]:
|
||||
positions: dict[str, dict] = {}
|
||||
cursor_x = 0
|
||||
cursor_y = 0
|
||||
row_height = 0
|
||||
packed: list[tuple[str, tuple[int, int, int, int]]] = []
|
||||
|
||||
for old_key, new_key in entries:
|
||||
meta = source_meta[old_key]
|
||||
width = max(1, round(meta["width"] * scale))
|
||||
height = max(1, round(meta["height"] * scale))
|
||||
if cursor_x and cursor_x + width > max_width:
|
||||
cursor_x = 0
|
||||
cursor_y += row_height
|
||||
row_height = 0
|
||||
packed.append((old_key, (cursor_x, cursor_y, width, height)))
|
||||
record = {
|
||||
"x": cursor_x,
|
||||
"y": cursor_y,
|
||||
"width": width,
|
||||
"height": height,
|
||||
"pixelRatio": 1 if scale == 0.5 else 2,
|
||||
}
|
||||
positions[new_key] = record
|
||||
if old_key != new_key:
|
||||
positions[old_key] = dict(record)
|
||||
cursor_x += width
|
||||
row_height = max(row_height, height)
|
||||
|
||||
atlas_height = cursor_y + row_height
|
||||
image = Image.new("RGBA", (max_width, atlas_height), (0, 0, 0, 0))
|
||||
source_image = Image.open(SOURCE_SPRITE_PNG_2X).convert("RGBA")
|
||||
|
||||
for old_key, (x, y, width, height) in packed:
|
||||
meta = source_meta[old_key]
|
||||
crop = source_image.crop(
|
||||
(
|
||||
meta["x"],
|
||||
meta["y"],
|
||||
meta["x"] + meta["width"],
|
||||
meta["y"] + meta["height"],
|
||||
)
|
||||
)
|
||||
if scale != 1.0:
|
||||
crop = crop.resize((width, height), Image.LANCZOS)
|
||||
image.paste(crop, (x, y))
|
||||
|
||||
return image, positions
|
||||
|
||||
|
||||
def build_semantic_sprite(entries: list[tuple[str, str]]) -> None:
|
||||
OUTPUT_SPRITE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
source_meta = json.loads(SOURCE_SPRITE_JSON_2X.read_text(encoding="utf-8"))
|
||||
|
||||
atlas_2x, meta_2x = pack_entries(entries, source_meta, scale=1.0, max_width=2048)
|
||||
atlas_1x, meta_1x = pack_entries(entries, source_meta, scale=0.5, max_width=1024)
|
||||
|
||||
atlas_2x.save(OUTPUT_SPRITE_DIR / "sprite@2x.png")
|
||||
atlas_1x.save(OUTPUT_SPRITE_DIR / "sprite.png")
|
||||
(OUTPUT_SPRITE_DIR / "sprite@2x.json").write_text(
|
||||
json.dumps(meta_2x, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
(OUTPUT_SPRITE_DIR / "sprite.json").write_text(
|
||||
json.dumps(meta_1x, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def replace_sprite_keys(value, mapping: dict[str, str]):
|
||||
if isinstance(value, dict):
|
||||
return {k: replace_sprite_keys(v, mapping) for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [replace_sprite_keys(item, mapping) for item in value]
|
||||
if isinstance(value, str):
|
||||
return mapping.get(value, value)
|
||||
return value
|
||||
|
||||
|
||||
def build_semantic_style(mapping: dict[str, str]) -> None:
|
||||
style = json.loads(SOURCE_STYLE.read_text(encoding="utf-8"))
|
||||
style = replace_sprite_keys(style, mapping)
|
||||
style["name"] = "NavSea Delivery Full Semantic"
|
||||
style["sprite"] = SEMANTIC_SPRITE_BASE_URL
|
||||
if style.get("sources", {}).get("navsea_delivery"):
|
||||
style["sources"]["navsea_delivery"]["tiles"] = [SEMANTIC_PBF_TILE_URL]
|
||||
OUTPUT_STYLE.write_text(json.dumps(style, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
DEPLOYED_STYLE.write_text(json.dumps(style, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def hardlink_or_copy(src: Path, dst: Path) -> None:
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dst.exists():
|
||||
dst.unlink()
|
||||
try:
|
||||
os.link(src, dst)
|
||||
except OSError:
|
||||
dst.write_bytes(src.read_bytes())
|
||||
|
||||
|
||||
def remap_tile(src: Path, dst: Path, value_map: dict[str, str], needles: list[bytes]) -> tuple[bool, int]:
|
||||
raw = src.read_bytes()
|
||||
if not any(needle in raw for needle in needles):
|
||||
hardlink_or_copy(src, dst)
|
||||
return False, 0
|
||||
|
||||
decoded, source_extents = decode_tile_with_extents(raw)
|
||||
changed_features = 0
|
||||
changed = False
|
||||
layers = []
|
||||
|
||||
for layer_name, layer in decoded.items():
|
||||
features = []
|
||||
for feature in layer["features"]:
|
||||
props = feature.get("properties", {})
|
||||
feature_changed = False
|
||||
for field in PBF_PROPERTY_FIELDS:
|
||||
value = props.get(field)
|
||||
if isinstance(value, str) and value in value_map:
|
||||
props[field] = value_map[value]
|
||||
feature_changed = True
|
||||
if feature_changed:
|
||||
changed_features += 1
|
||||
changed = True
|
||||
output_feature = {
|
||||
"geometry": feature["geometry"],
|
||||
"properties": props,
|
||||
}
|
||||
if feature.get("id") is not None:
|
||||
output_feature["id"] = feature["id"]
|
||||
features.append(output_feature)
|
||||
layers.append({"name": layer_name, "features": features})
|
||||
|
||||
if changed:
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dst.exists():
|
||||
dst.unlink()
|
||||
dst.write_bytes(encode_layers_preserving_extents(layers, source_extents))
|
||||
assert_reencoded_tile_safe(src, dst)
|
||||
else:
|
||||
hardlink_or_copy(src, dst)
|
||||
return changed, changed_features
|
||||
|
||||
|
||||
def remap_tile_job(args: tuple[str, str, dict[str, str], list[bytes]]) -> tuple[bool, int]:
|
||||
src_str, dst_str, value_map, needles = args
|
||||
return remap_tile(Path(src_str), Path(dst_str), value_map, needles)
|
||||
|
||||
|
||||
def backfill_missing_tiles(src_root: Path, dst_root: Path) -> int:
|
||||
missing = 0
|
||||
for src in src_root.rglob("*.pbf"):
|
||||
dst = dst_root / src.relative_to(src_root)
|
||||
if dst.exists():
|
||||
continue
|
||||
hardlink_or_copy(src, dst)
|
||||
missing += 1
|
||||
return missing
|
||||
|
||||
|
||||
def build_semantic_pbf(rows: list[AuditRow], mapping: dict[str, str]) -> dict[str, int]:
|
||||
value_map = {
|
||||
row.old_sprite_key: mapping[row.old_sprite_key]
|
||||
for row in rows
|
||||
if row.used_in_pbf > 0 and row.old_sprite_key in mapping
|
||||
}
|
||||
needles = [old.encode("utf-8") for old in value_map]
|
||||
if OUTPUT_PBF_ROOT.exists():
|
||||
shutil.rmtree(OUTPUT_PBF_ROOT)
|
||||
OUTPUT_PBF_ROOT.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(
|
||||
["cp", "-al", f"{SOURCE_PBF_ROOT}/.", str(OUTPUT_PBF_ROOT)],
|
||||
check=True,
|
||||
)
|
||||
|
||||
rg_cmd = ["rg", "-a", "-l", "-F"]
|
||||
for old_key in value_map:
|
||||
rg_cmd.extend(["-e", old_key])
|
||||
rg_cmd.append(str(SOURCE_PBF_ROOT))
|
||||
result = subprocess.run(rg_cmd, check=True, capture_output=True, text=True)
|
||||
tile_paths = [Path(line) for line in result.stdout.splitlines() if line.strip()]
|
||||
|
||||
stats = {
|
||||
"tiles_total": len(list(SOURCE_PBF_ROOT.rglob("*.pbf"))),
|
||||
"tiles_changed": 0,
|
||||
"features_changed": 0,
|
||||
"tiles_scanned_for_rewrite": len(tile_paths),
|
||||
}
|
||||
|
||||
jobs = [
|
||||
(
|
||||
str(src),
|
||||
str(OUTPUT_PBF_ROOT / src.relative_to(SOURCE_PBF_ROOT)),
|
||||
value_map,
|
||||
needles,
|
||||
)
|
||||
for src in tile_paths
|
||||
]
|
||||
|
||||
with ProcessPoolExecutor(max_workers=min(os.cpu_count() or 4, 12)) as executor:
|
||||
for changed, feature_count in executor.map(remap_tile_job, jobs, chunksize=128):
|
||||
if changed:
|
||||
stats["tiles_changed"] += 1
|
||||
stats["features_changed"] += feature_count
|
||||
|
||||
stats["tiles_backfilled"] = backfill_missing_tiles(SOURCE_PBF_ROOT, OUTPUT_PBF_ROOT)
|
||||
stats["tiles_total_after_backfill"] = len(list(OUTPUT_PBF_ROOT.rglob("*.pbf")))
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def main() -> None:
|
||||
rows = load_audit_rows()
|
||||
mapping = build_mapping(rows)
|
||||
used_entries = build_used_sprite_entries(rows, mapping)
|
||||
|
||||
write_mapping_json(mapping)
|
||||
build_semantic_sprite(used_entries)
|
||||
build_semantic_style(mapping)
|
||||
pbf_stats = build_semantic_pbf(rows, mapping)
|
||||
|
||||
summary = {
|
||||
"mapping_count": len(mapping),
|
||||
"used_sprite_entries": len(used_entries),
|
||||
"style_output": str(OUTPUT_STYLE),
|
||||
"deployed_style": str(DEPLOYED_STYLE),
|
||||
"sprite_output_dir": str(OUTPUT_SPRITE_DIR),
|
||||
"pbf_output_dir": str(OUTPUT_PBF_ROOT),
|
||||
**pbf_stats,
|
||||
"style_version": STYLE_VERSION,
|
||||
"pbf_version": PBF_VERSION,
|
||||
"sprite_version": SPRITE_VERSION,
|
||||
}
|
||||
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2250
coastline/build_fish_port_20m_full_mysql_resume.py
Normal file
2250
coastline/build_fish_port_20m_full_mysql_resume.py
Normal file
File diff suppressed because it is too large
Load Diff
565
coastline/build_japan_coast_grid_mysql.py
Normal file
565
coastline/build_japan_coast_grid_mysql.py
Normal file
@@ -0,0 +1,565 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import glob
|
||||
import json
|
||||
import math
|
||||
import zipfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pymysql
|
||||
from shapely.geometry import LineString, MultiLineString, box, mapping
|
||||
from shapely.prepared import prep
|
||||
|
||||
|
||||
DB_NAME = "navsea_japan_coast_grid"
|
||||
DB_USER = "root"
|
||||
DB_PASSWORD = "2chi9ks2"
|
||||
DB_HOST = "localhost"
|
||||
DB_SOCKET = "/tmp/mysql.sock"
|
||||
|
||||
DEFAULT_INPUT_GLOB = "coastline/C23-06_*_GML.zip"
|
||||
DEFAULT_OUT_DIR = "out/coastline/japan_coast_grid_mysql"
|
||||
LAYER_NAME = "coast_200m"
|
||||
CELL_SIZE_M = 200.0
|
||||
|
||||
RADIUS = 6378137.0
|
||||
MAX_MERCATOR_LAT = 85.0511287798066
|
||||
NS = {"gml": "http://www.opengis.net/gml/3.2"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SourcePackage:
|
||||
path: Path
|
||||
prefecture: str
|
||||
code: str
|
||||
|
||||
|
||||
def mercator_x(lon: float) -> float:
|
||||
return RADIUS * math.radians(lon)
|
||||
|
||||
|
||||
def mercator_y(lat: float) -> float:
|
||||
lat = max(min(lat, MAX_MERCATOR_LAT), -MAX_MERCATOR_LAT)
|
||||
return RADIUS * math.log(math.tan(math.pi / 4.0 + math.radians(lat) / 2.0))
|
||||
|
||||
|
||||
def lon_from_mercator(x: float) -> float:
|
||||
return math.degrees(x / RADIUS)
|
||||
|
||||
|
||||
def lat_from_mercator(y: float) -> float:
|
||||
return math.degrees(2.0 * math.atan(math.exp(y / RADIUS)) - math.pi / 2.0)
|
||||
|
||||
|
||||
def mercator_bbox_to_lonlat(minx: float, miny: float, maxx: float, maxy: float) -> tuple[float, float, float, float]:
|
||||
return (
|
||||
lon_from_mercator(minx),
|
||||
lat_from_mercator(miny),
|
||||
lon_from_mercator(maxx),
|
||||
lat_from_mercator(maxy),
|
||||
)
|
||||
|
||||
|
||||
def align_floor(value: float, step: float) -> float:
|
||||
return math.floor(value / step) * step
|
||||
|
||||
|
||||
def align_ceil(value: float, step: float) -> float:
|
||||
return math.ceil(value / step) * step
|
||||
|
||||
|
||||
def parse_text_list(text: str) -> list[float]:
|
||||
return [float(part) for part in text.split() if part]
|
||||
|
||||
|
||||
def detect_prefecture(meta_xml: str) -> str:
|
||||
title_start = meta_xml.find("<title>")
|
||||
if title_start == -1:
|
||||
return "unknown"
|
||||
title_end = meta_xml.find("</title>", title_start)
|
||||
if title_end == -1:
|
||||
return "unknown"
|
||||
return meta_xml[title_start + 7:title_end].strip()
|
||||
|
||||
|
||||
def load_package(path: Path) -> SourcePackage:
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
meta_name = next(name for name in zf.namelist() if "META" in name and name.endswith(".xml"))
|
||||
meta_xml = zf.read(meta_name).decode("shift_jis", errors="replace")
|
||||
prefecture = detect_prefecture(meta_xml)
|
||||
code = path.stem.replace("_GML", "")
|
||||
return SourcePackage(path=path, prefecture=prefecture, code=code)
|
||||
|
||||
|
||||
def iter_coastline_lines(zip_path: Path) -> Iterable[LineString]:
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
xml_name = next(name for name in zf.namelist() if name.endswith(".xml") and "META" not in name)
|
||||
root = ET.fromstring(zf.read(xml_name))
|
||||
|
||||
for curve in root.findall(".//gml:Curve", NS):
|
||||
coords: list[tuple[float, float]] = []
|
||||
for pos_list in curve.findall(".//gml:posList", NS):
|
||||
if not pos_list.text:
|
||||
continue
|
||||
values = parse_text_list(pos_list.text)
|
||||
if len(values) < 4 or len(values) % 2 != 0:
|
||||
continue
|
||||
for i in range(0, len(values), 2):
|
||||
lat = values[i]
|
||||
lon = values[i + 1]
|
||||
coords.append((mercator_x(lon), mercator_y(lat)))
|
||||
if len(coords) >= 2:
|
||||
yield LineString(coords)
|
||||
|
||||
|
||||
def mysql_connect(database: str | None = None):
|
||||
kwargs = {
|
||||
"host": DB_HOST,
|
||||
"user": DB_USER,
|
||||
"password": DB_PASSWORD,
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"cursorclass": pymysql.cursors.Cursor,
|
||||
}
|
||||
if DB_SOCKET:
|
||||
kwargs["unix_socket"] = DB_SOCKET
|
||||
if database:
|
||||
kwargs["database"] = database
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def ensure_database() -> None:
|
||||
conn = mysql_connect()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"CREATE DATABASE IF NOT EXISTS `{DB_NAME}` "
|
||||
"DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def ensure_schema(conn) -> None:
|
||||
ddl = [
|
||||
"DROP TABLE IF EXISTS navsea_grid_cell",
|
||||
"DROP TABLE IF EXISTS navsea_grid_package_stat",
|
||||
"DROP TABLE IF EXISTS navsea_grid_layer_meta",
|
||||
"""
|
||||
CREATE TABLE navsea_grid_layer_meta (
|
||||
layer_name VARCHAR(32) NOT NULL PRIMARY KEY,
|
||||
description VARCHAR(255) NOT NULL,
|
||||
source_desc TEXT NOT NULL,
|
||||
cell_size_m DOUBLE NOT NULL,
|
||||
feature_count BIGINT NOT NULL,
|
||||
bbox_min_lon DOUBLE NOT NULL,
|
||||
bbox_min_lat DOUBLE NOT NULL,
|
||||
bbox_max_lon DOUBLE NOT NULL,
|
||||
bbox_max_lat DOUBLE NOT NULL,
|
||||
export_file VARCHAR(255) DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE navsea_grid_package_stat (
|
||||
source_code VARCHAR(64) NOT NULL,
|
||||
prefecture VARCHAR(128) NOT NULL,
|
||||
zip_path TEXT NOT NULL,
|
||||
line_count BIGINT NOT NULL,
|
||||
point_count BIGINT NOT NULL,
|
||||
bbox_min_lon DOUBLE NOT NULL,
|
||||
bbox_min_lat DOUBLE NOT NULL,
|
||||
bbox_max_lon DOUBLE NOT NULL,
|
||||
bbox_max_lat DOUBLE NOT NULL,
|
||||
grid_min_lon DOUBLE NOT NULL,
|
||||
grid_min_lat DOUBLE NOT NULL,
|
||||
grid_max_lon DOUBLE NOT NULL,
|
||||
grid_max_lat DOUBLE NOT NULL,
|
||||
blocked_count BIGINT NOT NULL,
|
||||
candidate_count BIGINT NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE navsea_grid_cell (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
layer_name VARCHAR(32) NOT NULL,
|
||||
cell_id VARCHAR(64) NOT NULL,
|
||||
row_idx INT NOT NULL,
|
||||
col_idx INT NOT NULL,
|
||||
cell_size_m DOUBLE NOT NULL,
|
||||
state_name VARCHAR(32) NOT NULL,
|
||||
class_name VARCHAR(32) NOT NULL,
|
||||
source_name VARCHAR(191) NOT NULL,
|
||||
min_lon DOUBLE NOT NULL,
|
||||
min_lat DOUBLE NOT NULL,
|
||||
max_lon DOUBLE NOT NULL,
|
||||
max_lat DOUBLE NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uniq_layer_cell (layer_name, cell_id),
|
||||
KEY idx_layer_state (layer_name, state_name),
|
||||
KEY idx_layer_rowcol (layer_name, row_idx, col_idx)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""",
|
||||
]
|
||||
with conn.cursor() as cur:
|
||||
for stmt in ddl:
|
||||
cur.execute(stmt)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def insert_cells(cur, rows: list[tuple]) -> None:
|
||||
cur.executemany(
|
||||
"""
|
||||
INSERT INTO navsea_grid_cell
|
||||
(layer_name, cell_id, row_idx, col_idx, cell_size_m, state_name, class_name, source_name,
|
||||
min_lon, min_lat, max_lon, max_lat)
|
||||
VALUES
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
row_idx = row_idx,
|
||||
col_idx = col_idx,
|
||||
cell_size_m = cell_size_m,
|
||||
state_name = IF(VALUES(state_name) = 'HARD_BLOCKED' AND state_name <> 'HARD_BLOCKED', VALUES(state_name), state_name),
|
||||
class_name = IF(VALUES(state_name) = 'HARD_BLOCKED' AND state_name <> 'HARD_BLOCKED', VALUES(class_name), class_name),
|
||||
source_name = IF(VALUES(state_name) = 'HARD_BLOCKED' AND state_name <> 'HARD_BLOCKED', VALUES(source_name), source_name),
|
||||
min_lon = min_lon,
|
||||
min_lat = min_lat,
|
||||
max_lon = max_lon,
|
||||
max_lat = max_lat
|
||||
""",
|
||||
rows,
|
||||
)
|
||||
|
||||
|
||||
def upsert_meta(
|
||||
cur,
|
||||
*,
|
||||
layer_name: str,
|
||||
description: str,
|
||||
source_desc: str,
|
||||
cell_size_m: float,
|
||||
feature_count: int,
|
||||
bbox: tuple[float, float, float, float],
|
||||
export_file: str | None,
|
||||
) -> None:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO navsea_grid_layer_meta
|
||||
(layer_name, description, source_desc, cell_size_m, feature_count,
|
||||
bbox_min_lon, bbox_min_lat, bbox_max_lon, bbox_max_lat, export_file)
|
||||
VALUES
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
description=VALUES(description),
|
||||
source_desc=VALUES(source_desc),
|
||||
cell_size_m=VALUES(cell_size_m),
|
||||
feature_count=VALUES(feature_count),
|
||||
bbox_min_lon=VALUES(bbox_min_lon),
|
||||
bbox_min_lat=VALUES(bbox_min_lat),
|
||||
bbox_max_lon=VALUES(bbox_max_lon),
|
||||
bbox_max_lat=VALUES(bbox_max_lat),
|
||||
export_file=VALUES(export_file)
|
||||
""",
|
||||
(
|
||||
layer_name,
|
||||
description,
|
||||
source_desc,
|
||||
cell_size_m,
|
||||
feature_count,
|
||||
bbox[0],
|
||||
bbox[1],
|
||||
bbox[2],
|
||||
bbox[3],
|
||||
export_file,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Build Japan coastline 200m grid directly into MySQL")
|
||||
parser.add_argument(
|
||||
"--input",
|
||||
dest="inputs",
|
||||
action="append",
|
||||
default=[],
|
||||
help="海岸线 GML zip。可重复指定;默认自动扫描 coastline/C23-06_*_GML.zip",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--db-name",
|
||||
default=DB_NAME,
|
||||
help="MySQL 数据库名",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
default=DEFAULT_OUT_DIR,
|
||||
help="输出目录,用于可选 GeoJSON 和元数据",
|
||||
)
|
||||
parser.add_argument("--buffer-m", type=float, default=50.0, help="海岸线缓冲距离,默认 50m")
|
||||
parser.add_argument(
|
||||
"--margin-cells",
|
||||
type=int,
|
||||
default=2,
|
||||
help="输出范围外扩的格子数,默认 2 个格子",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--write-geojson",
|
||||
action="store_true",
|
||||
help="同时输出 coastline.geojson 和 grid.geojson",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
project_root = Path(__file__).resolve().parent.parent
|
||||
if args.inputs:
|
||||
input_paths: list[Path] = []
|
||||
for item in args.inputs:
|
||||
if any(ch in item for ch in "*?[]"):
|
||||
input_paths.extend(Path(path) for path in sorted(glob.glob(item)))
|
||||
else:
|
||||
input_paths.append(Path(item))
|
||||
else:
|
||||
input_paths = [Path(path) for path in sorted(glob.glob(str(project_root / DEFAULT_INPUT_GLOB)))]
|
||||
|
||||
if not input_paths:
|
||||
raise SystemExit("no coastline packages found")
|
||||
|
||||
packages: list[SourcePackage] = []
|
||||
for path in input_paths:
|
||||
if not path.is_absolute():
|
||||
path = project_root / path
|
||||
if not path.exists():
|
||||
raise SystemExit(f"missing input: {path}")
|
||||
packages.append(load_package(path))
|
||||
|
||||
out_dir = Path(args.out_dir)
|
||||
if not out_dir.is_absolute():
|
||||
out_dir = project_root / out_dir
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
ensure_database()
|
||||
conn = mysql_connect(args.db_name)
|
||||
try:
|
||||
print(f"数据库 {args.db_name} 已连接,准备初始化表结构 ...")
|
||||
ensure_schema(conn)
|
||||
|
||||
total_lines = 0
|
||||
total_points = 0
|
||||
package_stats: list[dict] = []
|
||||
|
||||
for package in packages:
|
||||
print(f"[{package.code}] 开始处理 {package.path.name}")
|
||||
lines = list(iter_coastline_lines(package.path))
|
||||
if not lines:
|
||||
print(f"[{package.code}] 没有可用海岸线,跳过")
|
||||
continue
|
||||
|
||||
coastline_geom = MultiLineString([list(line.coords) for line in lines])
|
||||
coastline_buffer = coastline_geom.buffer(args.buffer_m, cap_style=2, join_style=2)
|
||||
prepared_buffer = prep(coastline_buffer)
|
||||
coast_bounds = coastline_geom.bounds
|
||||
|
||||
minx = align_floor(coast_bounds[0] - args.buffer_m - args.margin_cells * CELL_SIZE_M, CELL_SIZE_M)
|
||||
miny = align_floor(coast_bounds[1] - args.buffer_m - args.margin_cells * CELL_SIZE_M, CELL_SIZE_M)
|
||||
maxx = align_ceil(coast_bounds[2] + args.buffer_m + args.margin_cells * CELL_SIZE_M, CELL_SIZE_M)
|
||||
maxy = align_ceil(coast_bounds[3] + args.buffer_m + args.margin_cells * CELL_SIZE_M, CELL_SIZE_M)
|
||||
|
||||
line_count = len(lines)
|
||||
point_count = sum(len(line.coords) for line in lines)
|
||||
total_lines += line_count
|
||||
total_points += point_count
|
||||
|
||||
package_blocked = 0
|
||||
package_candidate = 0
|
||||
package_rows: list[tuple] = []
|
||||
|
||||
ix0 = int(round(minx / CELL_SIZE_M))
|
||||
iy0 = int(round(miny / CELL_SIZE_M))
|
||||
ix1 = int(round(maxx / CELL_SIZE_M))
|
||||
iy1 = int(round(maxy / CELL_SIZE_M))
|
||||
|
||||
for ix in range(ix0, ix1):
|
||||
cell_minx = ix * CELL_SIZE_M
|
||||
cell_maxx = cell_minx + CELL_SIZE_M
|
||||
for iy in range(iy0, iy1):
|
||||
cell_miny = iy * CELL_SIZE_M
|
||||
cell_maxy = cell_miny + CELL_SIZE_M
|
||||
cell = box(cell_minx, cell_miny, cell_maxx, cell_maxy)
|
||||
intersects = prepared_buffer.intersects(cell)
|
||||
if intersects:
|
||||
cell_id = f"{ix}:{iy}"
|
||||
package_rows.append(
|
||||
(
|
||||
LAYER_NAME,
|
||||
cell_id,
|
||||
iy,
|
||||
ix,
|
||||
CELL_SIZE_M,
|
||||
"HARD_BLOCKED",
|
||||
"HARD_BLOCKED",
|
||||
package.code,
|
||||
cell_minx,
|
||||
cell_miny,
|
||||
cell_maxx,
|
||||
cell_maxy,
|
||||
)
|
||||
)
|
||||
package_blocked += 1
|
||||
else:
|
||||
package_candidate += 1
|
||||
|
||||
if len(package_rows) >= 5000:
|
||||
with conn.cursor() as cur:
|
||||
insert_cells(cur, package_rows)
|
||||
conn.commit()
|
||||
print(
|
||||
f"[{package.code}] 已写入 {len(package_rows)} 行,"
|
||||
f"blocked={package_blocked} candidate={package_candidate}"
|
||||
)
|
||||
package_rows.clear()
|
||||
|
||||
if package_rows:
|
||||
with conn.cursor() as cur:
|
||||
insert_cells(cur, package_rows)
|
||||
conn.commit()
|
||||
|
||||
package_stats.append(
|
||||
{
|
||||
"source_code": package.code,
|
||||
"prefecture": package.prefecture,
|
||||
"zip_path": str(package.path),
|
||||
"line_count": line_count,
|
||||
"point_count": point_count,
|
||||
"bbox_mercator": [coast_bounds[0], coast_bounds[1], coast_bounds[2], coast_bounds[3]],
|
||||
"grid_bounds_mercator": [minx, miny, maxx, maxy],
|
||||
"blocked_count": package_blocked,
|
||||
"candidate_count": package_candidate,
|
||||
}
|
||||
)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO navsea_grid_package_stat(
|
||||
source_code, prefecture, zip_path, line_count, point_count,
|
||||
bbox_min_lon, bbox_min_lat, bbox_max_lon, bbox_max_lat,
|
||||
grid_min_lon, grid_min_lat, grid_max_lon, grid_max_lat,
|
||||
blocked_count, candidate_count
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
|
||||
)
|
||||
""",
|
||||
(
|
||||
package.code,
|
||||
package.prefecture,
|
||||
str(package.path),
|
||||
line_count,
|
||||
point_count,
|
||||
coast_bounds[0],
|
||||
coast_bounds[1],
|
||||
coast_bounds[2],
|
||||
coast_bounds[3],
|
||||
minx,
|
||||
miny,
|
||||
maxx,
|
||||
maxy,
|
||||
package_blocked,
|
||||
package_candidate,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
print(
|
||||
f"[{package.code}] 完成 line={line_count} point={point_count} "
|
||||
f"blocked={package_blocked} candidate={package_candidate}"
|
||||
)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("ANALYZE TABLE navsea_grid_cell")
|
||||
cur.execute("ANALYZE TABLE navsea_grid_package_stat")
|
||||
cur.execute("ANALYZE TABLE navsea_grid_layer_meta")
|
||||
conn.commit()
|
||||
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"SELECT COUNT(*) FROM navsea_grid_cell WHERE layer_name=%s", (LAYER_NAME,))
|
||||
final_total = int(cur.fetchone()[0] or 0)
|
||||
cur.execute(
|
||||
f"SELECT COUNT(*) FROM navsea_grid_cell WHERE layer_name=%s AND state_name=%s",
|
||||
(LAYER_NAME, "HARD_BLOCKED"),
|
||||
)
|
||||
blocked_count = int(cur.fetchone()[0] or 0)
|
||||
cur.execute(
|
||||
f"SELECT COUNT(*) FROM navsea_grid_cell WHERE layer_name=%s AND state_name=%s",
|
||||
(LAYER_NAME, "NAVIGABLE_CANDIDATE"),
|
||||
)
|
||||
candidate_count = int(cur.fetchone()[0] or 0)
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT MIN(min_lon), MIN(min_lat), MAX(max_lon), MAX(max_lat)
|
||||
FROM navsea_grid_cell
|
||||
WHERE layer_name=%s
|
||||
""",
|
||||
(LAYER_NAME,),
|
||||
)
|
||||
bbox_row = cur.fetchone()
|
||||
|
||||
grid_bbox = (
|
||||
float(bbox_row[0]) if bbox_row and bbox_row[0] is not None else float("inf"),
|
||||
float(bbox_row[1]) if bbox_row and bbox_row[1] is not None else float("inf"),
|
||||
float(bbox_row[2]) if bbox_row and bbox_row[2] is not None else float("-inf"),
|
||||
float(bbox_row[3]) if bbox_row and bbox_row[3] is not None else float("-inf"),
|
||||
)
|
||||
|
||||
build_time = dt.datetime.now().isoformat(timespec="seconds")
|
||||
grid_bbox_lonlat = mercator_bbox_to_lonlat(*grid_bbox)
|
||||
|
||||
manifest = {
|
||||
"build_time": build_time,
|
||||
"db_name": args.db_name,
|
||||
"source_scope": "japan",
|
||||
"source_code": "C23-06_*",
|
||||
"source_count": len(packages),
|
||||
"source_packages": [str(pkg.path) for pkg in packages],
|
||||
"coastline_line_count": total_lines,
|
||||
"coastline_point_count": total_points,
|
||||
"grid_cell_count": final_total,
|
||||
"blocked_count": blocked_count,
|
||||
"candidate_count": candidate_count,
|
||||
"grid_bounds_lonlat": list(grid_bbox_lonlat),
|
||||
"package_stats": package_stats,
|
||||
"notes": "全国海岸线 200m 硬阻塞栅格,直接写入 MySQL",
|
||||
}
|
||||
manifest_path = out_dir / "japan_coast_grid_mysql.manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
with conn.cursor() as cur:
|
||||
upsert_meta(
|
||||
cur,
|
||||
layer_name=LAYER_NAME,
|
||||
description="全国海岸线 200m 硬阻塞格",
|
||||
source_desc="coastline/C23-06_*_GML.zip",
|
||||
cell_size_m=CELL_SIZE_M,
|
||||
feature_count=final_total,
|
||||
bbox=grid_bbox_lonlat,
|
||||
export_file=str(manifest_path.relative_to(project_root)),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
print("完成")
|
||||
print(f" MySQL: {args.db_name}")
|
||||
print(f" packages={len(packages)}")
|
||||
print(f" coastline_lines={total_lines}")
|
||||
print(f" grid_cells={final_total}")
|
||||
print(f" blocked={blocked_count}")
|
||||
print(f" candidate={candidate_count}")
|
||||
print(f" manifest: {manifest_path}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
516
coastline/build_japan_hazard_50m_mysql.py
Normal file
516
coastline/build_japan_hazard_50m_mysql.py
Normal file
@@ -0,0 +1,516 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
import mapbox_vector_tile
|
||||
import pymysql
|
||||
from shapely.geometry import box, shape
|
||||
from shapely.prepared import prep
|
||||
|
||||
|
||||
DB_NAME = "navsea_japan_coast_grid"
|
||||
DB_USER = "root"
|
||||
DB_PASSWORD = "2chi9ks2"
|
||||
DB_HOST = "localhost"
|
||||
DB_SOCKET = "/tmp/mysql.sock"
|
||||
|
||||
DEFAULT_TILE_ROOT = Path("/home/wwwroot/pbf-delivery-full-20260418-rebuild")
|
||||
DEFAULT_TILE_Z = 12
|
||||
DEFAULT_OUT_DIR = "src/pbf/coastline-mysql/japan_national"
|
||||
DEFAULT_SOURCE_DESC = "pbf-delivery-full-20260418-rebuild z12"
|
||||
|
||||
HAZARD_CELL_M = 50.0
|
||||
HAZARD_LAYER_NAME = "hazard_50m"
|
||||
HAZARD_DESCRIPTION = "全国 PBF 海上障碍 50m 黄格"
|
||||
HAZARD_LAYERS = (
|
||||
"navigation_hazard_area",
|
||||
"fixed_fishing_gear_area",
|
||||
"anchor_caution_hazard_area",
|
||||
"navigation_hazard_point",
|
||||
"anchor_caution_hazard_point",
|
||||
"navigation_marks",
|
||||
)
|
||||
BREAKWATER_LAYER = "baseline_area"
|
||||
POINT_LAYERS = {
|
||||
"navigation_hazard_point",
|
||||
"anchor_caution_hazard_point",
|
||||
"navigation_marks",
|
||||
}
|
||||
HAZARD_CANONICAL_OBJECT_TYPES = {
|
||||
"breakwater",
|
||||
}
|
||||
|
||||
RADIUS = 6378137.0
|
||||
MAX_MERCATOR_LAT = 85.0511287798066
|
||||
|
||||
|
||||
def mercator_x(lon: float) -> float:
|
||||
return RADIUS * math.radians(lon)
|
||||
|
||||
|
||||
def mercator_y(lat: float) -> float:
|
||||
lat = max(min(lat, MAX_MERCATOR_LAT), -MAX_MERCATOR_LAT)
|
||||
return RADIUS * math.log(math.tan(math.pi / 4.0 + math.radians(lat) / 2.0))
|
||||
|
||||
|
||||
def lon_from_mercator(x: float) -> float:
|
||||
return math.degrees(x / RADIUS)
|
||||
|
||||
|
||||
def lat_from_mercator(y: float) -> float:
|
||||
return math.degrees(2.0 * math.atan(math.exp(y / RADIUS)) - math.pi / 2.0)
|
||||
|
||||
|
||||
def rect_geojson(min_lon: float, min_lat: float, max_lon: float, max_lat: float) -> dict:
|
||||
return {
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[min_lon, min_lat],
|
||||
[max_lon, min_lat],
|
||||
[max_lon, max_lat],
|
||||
[min_lon, max_lat],
|
||||
[min_lon, min_lat],
|
||||
]
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def local_name(tag: str) -> str:
|
||||
return tag.split("}", 1)[-1]
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def stream_geojson(path: Path, features: Iterable[dict]) -> int:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
count = 0
|
||||
with path.open("w", encoding="utf-8") as fh:
|
||||
fh.write('{"type":"FeatureCollection","features":[\n')
|
||||
first = True
|
||||
for feature in features:
|
||||
if not first:
|
||||
fh.write(",\n")
|
||||
fh.write(json.dumps(feature, ensure_ascii=False))
|
||||
first = False
|
||||
count += 1
|
||||
fh.write("\n]}\n")
|
||||
return count
|
||||
|
||||
|
||||
def mysql_connect(database: str | None = None):
|
||||
kwargs = {
|
||||
"host": DB_HOST,
|
||||
"user": DB_USER,
|
||||
"password": DB_PASSWORD,
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"cursorclass": pymysql.cursors.Cursor,
|
||||
}
|
||||
if DB_SOCKET:
|
||||
kwargs["unix_socket"] = DB_SOCKET
|
||||
if database:
|
||||
kwargs["database"] = database
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def ensure_database(name: str) -> None:
|
||||
conn = mysql_connect()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
f"CREATE DATABASE IF NOT EXISTS `{name}` "
|
||||
"DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def ensure_schema(conn) -> None:
|
||||
stmts = [
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS navsea_grid_layer_meta (
|
||||
layer_name VARCHAR(32) NOT NULL PRIMARY KEY,
|
||||
description VARCHAR(255) NOT NULL,
|
||||
source_desc TEXT NOT NULL,
|
||||
cell_size_m DOUBLE NOT NULL,
|
||||
feature_count BIGINT NOT NULL,
|
||||
bbox_min_lon DOUBLE NOT NULL,
|
||||
bbox_min_lat DOUBLE NOT NULL,
|
||||
bbox_max_lon DOUBLE NOT NULL,
|
||||
bbox_max_lat DOUBLE NOT NULL,
|
||||
export_file VARCHAR(255) DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS navsea_grid_cell (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
layer_name VARCHAR(32) NOT NULL,
|
||||
cell_id VARCHAR(64) NOT NULL,
|
||||
row_idx INT NOT NULL,
|
||||
col_idx INT NOT NULL,
|
||||
cell_size_m DOUBLE NOT NULL,
|
||||
state_name VARCHAR(32) NOT NULL,
|
||||
class_name VARCHAR(32) NOT NULL,
|
||||
source_name VARCHAR(191) NOT NULL,
|
||||
min_lon DOUBLE NOT NULL,
|
||||
min_lat DOUBLE NOT NULL,
|
||||
max_lon DOUBLE NOT NULL,
|
||||
max_lat DOUBLE NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uniq_layer_cell (layer_name, cell_id),
|
||||
KEY idx_layer_state (layer_name, state_name),
|
||||
KEY idx_layer_rowcol (layer_name, row_idx, col_idx)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""",
|
||||
]
|
||||
with conn.cursor() as cur:
|
||||
for stmt in stmts:
|
||||
cur.execute(stmt)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def replace_layer_rows(conn, layer_name: str) -> None:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("DELETE FROM navsea_grid_cell WHERE layer_name=%s", (layer_name,))
|
||||
cur.execute("DELETE FROM navsea_grid_layer_meta WHERE layer_name=%s", (layer_name,))
|
||||
conn.commit()
|
||||
|
||||
|
||||
def insert_cells(cur, rows: list[tuple]) -> None:
|
||||
cur.executemany(
|
||||
"""
|
||||
INSERT INTO navsea_grid_cell
|
||||
(layer_name, cell_id, row_idx, col_idx, cell_size_m, state_name, class_name, source_name,
|
||||
min_lon, min_lat, max_lon, max_lat)
|
||||
VALUES
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
""",
|
||||
rows,
|
||||
)
|
||||
|
||||
|
||||
def upsert_meta(
|
||||
cur,
|
||||
*,
|
||||
layer_name: str,
|
||||
description: str,
|
||||
source_desc: str,
|
||||
cell_size_m: float,
|
||||
feature_count: int,
|
||||
bbox: tuple[float, float, float, float],
|
||||
export_file: str,
|
||||
) -> None:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO navsea_grid_layer_meta
|
||||
(layer_name, description, source_desc, cell_size_m, feature_count,
|
||||
bbox_min_lon, bbox_min_lat, bbox_max_lon, bbox_max_lat, export_file)
|
||||
VALUES
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
description=VALUES(description),
|
||||
source_desc=VALUES(source_desc),
|
||||
cell_size_m=VALUES(cell_size_m),
|
||||
feature_count=VALUES(feature_count),
|
||||
bbox_min_lon=VALUES(bbox_min_lon),
|
||||
bbox_min_lat=VALUES(bbox_min_lat),
|
||||
bbox_max_lon=VALUES(bbox_max_lon),
|
||||
bbox_max_lat=VALUES(bbox_max_lat),
|
||||
export_file=VALUES(export_file)
|
||||
""",
|
||||
(
|
||||
layer_name,
|
||||
description,
|
||||
source_desc,
|
||||
cell_size_m,
|
||||
feature_count,
|
||||
bbox[0],
|
||||
bbox[1],
|
||||
bbox[2],
|
||||
bbox[3],
|
||||
export_file,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def tile_mercator_bounds(z: int, x: int, y: int) -> tuple[float, float, float, float]:
|
||||
n = 2**z
|
||||
lon_left = x / n * 360.0 - 180.0
|
||||
lon_right = (x + 1) / n * 360.0 - 180.0
|
||||
lat_top = math.degrees(math.atan(math.sinh(math.pi * (1 - 2 * y / n))))
|
||||
lat_bottom = math.degrees(math.atan(math.sinh(math.pi * (1 - 2 * (y + 1) / n))))
|
||||
return mercator_x(lon_left), mercator_y(lat_bottom), mercator_x(lon_right), mercator_y(lat_top)
|
||||
|
||||
|
||||
def transform_geom_from_tile(geom, bounds_m: tuple[float, float, float, float], extent: int):
|
||||
minx, miny, maxx, maxy = bounds_m
|
||||
dx = maxx - minx
|
||||
dy = maxy - miny
|
||||
|
||||
def coord(x: float, y: float):
|
||||
mx = minx + (x / extent) * dx
|
||||
my = miny + (y / extent) * dy
|
||||
return mx, my
|
||||
|
||||
def walk(obj):
|
||||
if isinstance(obj[0], (int, float)):
|
||||
return coord(obj[0], obj[1])
|
||||
return [walk(item) for item in obj]
|
||||
|
||||
return walk(geom)
|
||||
|
||||
|
||||
def iter_source_tiles(tile_root: Path, zoom: int) -> list[Path]:
|
||||
zoom_root = tile_root / str(zoom)
|
||||
if not zoom_root.exists():
|
||||
raise SystemExit(f"missing tile zoom root: {zoom_root}")
|
||||
return sorted(zoom_root.glob("*/*.pbf"))
|
||||
|
||||
|
||||
def iter_hazard_cells(tile_root: Path, zoom: int) -> Iterable[tuple[int, int]]:
|
||||
hazard_cells: set[tuple[int, int]] = set()
|
||||
tiles = iter_source_tiles(tile_root, zoom)
|
||||
for index, p in enumerate(tiles, start=1):
|
||||
try:
|
||||
tx = int(p.parent.name)
|
||||
ty = int(p.stem)
|
||||
except ValueError:
|
||||
continue
|
||||
try:
|
||||
tile = mapbox_vector_tile.decode(p.read_bytes())
|
||||
except Exception as exc:
|
||||
print(f"[hazard_50m] 跳过无法解析的 tile: {p} ({exc.__class__.__name__})")
|
||||
continue
|
||||
|
||||
if index % 5000 == 0:
|
||||
print(f"[hazard_50m] 已扫描 tile {index}/{len(tiles)}: {p.parent.parent.name}/{tx}/{ty}.pbf")
|
||||
|
||||
for layer_name in HAZARD_LAYERS + (BREAKWATER_LAYER,):
|
||||
layer = tile.get(layer_name)
|
||||
if not layer:
|
||||
continue
|
||||
extent = int(layer.get("extent") or 1048576)
|
||||
minx, miny, maxx, maxy = tile_mercator_bounds(zoom, tx, ty)
|
||||
for feature in layer.get("features", []):
|
||||
props = feature.get("properties") or {}
|
||||
if layer_name == BREAKWATER_LAYER and props.get("canonical_object_type") not in HAZARD_CANONICAL_OBJECT_TYPES:
|
||||
continue
|
||||
if (
|
||||
props.get("canonical_object_type") == "fish_reef"
|
||||
or props.get("chart_symbol_code") == "fish_reef"
|
||||
or props.get("class_name") == "魚礁"
|
||||
):
|
||||
continue
|
||||
geom = feature.get("geometry") or {}
|
||||
coords = geom.get("coordinates")
|
||||
if not coords:
|
||||
continue
|
||||
shp_coords = transform_geom_from_tile(coords, (minx, miny, maxx, maxy), extent)
|
||||
shp = shape({"type": geom.get("type"), "coordinates": shp_coords})
|
||||
if shp.is_empty:
|
||||
continue
|
||||
if layer_name in POINT_LAYERS:
|
||||
shp = shp.buffer(25.0)
|
||||
minx2, miny2, maxx2, maxy2 = shp.bounds
|
||||
start_x = math.floor(minx2 / HAZARD_CELL_M) * HAZARD_CELL_M
|
||||
start_y = math.floor(miny2 / HAZARD_CELL_M) * HAZARD_CELL_M
|
||||
end_x = math.ceil(maxx2 / HAZARD_CELL_M) * HAZARD_CELL_M
|
||||
end_y = math.ceil(maxy2 / HAZARD_CELL_M) * HAZARD_CELL_M
|
||||
prep_geom = prep(shp)
|
||||
cy = start_y
|
||||
while cy < end_y:
|
||||
cx = start_x
|
||||
while cx < end_x:
|
||||
cell = box(cx, cy, cx + HAZARD_CELL_M, cy + HAZARD_CELL_M)
|
||||
if prep_geom.intersects(cell):
|
||||
hazard_cells.add((int(round(cx / HAZARD_CELL_M)), int(round(cy / HAZARD_CELL_M))))
|
||||
cx += HAZARD_CELL_M
|
||||
cy += HAZARD_CELL_M
|
||||
return sorted(hazard_cells, key=lambda item: (item[1], item[0]))
|
||||
|
||||
|
||||
def cell_bbox_from_mercator(ix: int, iy: int, cell_m: float) -> tuple[float, float, float, float]:
|
||||
minx = ix * cell_m
|
||||
miny = iy * cell_m
|
||||
maxx = minx + cell_m
|
||||
maxy = miny + cell_m
|
||||
return (
|
||||
lon_from_mercator(minx),
|
||||
lat_from_mercator(miny),
|
||||
lon_from_mercator(maxx),
|
||||
lat_from_mercator(maxy),
|
||||
)
|
||||
|
||||
|
||||
def import_hazard_layer(
|
||||
conn,
|
||||
*,
|
||||
layer_name: str,
|
||||
description: str,
|
||||
source_desc: str,
|
||||
cell_size_m: float,
|
||||
tile_root: Path,
|
||||
zoom: int,
|
||||
export_path: Path,
|
||||
) -> tuple[int, tuple[float, float, float, float]]:
|
||||
cells = iter_hazard_cells(tile_root, zoom)
|
||||
batch: list[tuple] = []
|
||||
count = 0
|
||||
export_bbox = [float("inf"), float("inf"), float("-inf"), float("-inf")]
|
||||
|
||||
with conn.cursor() as cur:
|
||||
print(f"[{layer_name}] 开始扫描全国 PBF 危险层:{tile_root} / z{zoom}")
|
||||
with export_path.open("w", encoding="utf-8") as fh:
|
||||
fh.write('{"type":"FeatureCollection","features":[\n')
|
||||
first = True
|
||||
for ix, iy in cells:
|
||||
min_lon, min_lat, max_lon, max_lat = cell_bbox_from_mercator(ix, iy, cell_size_m)
|
||||
export_bbox[0] = min(export_bbox[0], min_lon)
|
||||
export_bbox[1] = min(export_bbox[1], min_lat)
|
||||
export_bbox[2] = max(export_bbox[2], max_lon)
|
||||
export_bbox[3] = max(export_bbox[3], max_lat)
|
||||
cell_id = f"{ix}:{iy}"
|
||||
batch.append(
|
||||
(
|
||||
layer_name,
|
||||
cell_id,
|
||||
iy,
|
||||
ix,
|
||||
cell_size_m,
|
||||
"HAZARD_50M",
|
||||
"HAZARD",
|
||||
source_desc,
|
||||
min_lon,
|
||||
min_lat,
|
||||
max_lon,
|
||||
max_lat,
|
||||
)
|
||||
)
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"layer_name": layer_name,
|
||||
"cell_id": cell_id,
|
||||
"row": iy,
|
||||
"col": ix,
|
||||
"cell_size_m": cell_size_m,
|
||||
"state_name": "HAZARD_50M",
|
||||
"class_name": "HAZARD",
|
||||
"source_name": source_desc,
|
||||
},
|
||||
"geometry": rect_geojson(min_lon, min_lat, max_lon, max_lat)["geometry"],
|
||||
}
|
||||
if not first:
|
||||
fh.write(",\n")
|
||||
fh.write(json.dumps(feature, ensure_ascii=False))
|
||||
first = False
|
||||
count += 1
|
||||
if len(batch) >= 5000:
|
||||
insert_cells(cur, batch)
|
||||
conn.commit()
|
||||
batch.clear()
|
||||
if count % 20000 == 0:
|
||||
print(f"[{layer_name}] 已处理 {count} 个格子 ...")
|
||||
if batch:
|
||||
insert_cells(cur, batch)
|
||||
conn.commit()
|
||||
fh.write("\n]}\n")
|
||||
|
||||
if count == 0:
|
||||
raise RuntimeError(f"{layer_name} 扫描结果为空,请检查 tile_root={tile_root} zoom={zoom}")
|
||||
|
||||
print(f"[{layer_name}] 导入完成:{count} 条,导出 {export_path}")
|
||||
return count, (export_bbox[0], export_bbox[1], export_bbox[2], export_bbox[3])
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="重算全国 50x50 海上障碍格并写入统一 MySQL 库。")
|
||||
parser.add_argument("--db-name", default=DB_NAME, help="MySQL 数据库名")
|
||||
parser.add_argument("--tile-root", type=Path, default=DEFAULT_TILE_ROOT, help="全国 PBF 根目录")
|
||||
parser.add_argument("--zoom", type=int, default=DEFAULT_TILE_Z, help="PBF 瓦片 zoom,默认 12")
|
||||
parser.add_argument("--out-dir", default=DEFAULT_OUT_DIR, help="GeoJSON 输出目录")
|
||||
parser.add_argument("--source-desc", default=DEFAULT_SOURCE_DESC, help="写入元数据的来源说明")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
project_root = Path(__file__).resolve().parent.parent
|
||||
out_dir = Path(args.out_dir)
|
||||
if not out_dir.is_absolute():
|
||||
out_dir = project_root / out_dir
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
ensure_database(args.db_name)
|
||||
conn = mysql_connect(args.db_name)
|
||||
try:
|
||||
print(f"数据库 {args.db_name} 已连接,准备初始化表结构 ...")
|
||||
ensure_schema(conn)
|
||||
replace_layer_rows(conn, HAZARD_LAYER_NAME)
|
||||
|
||||
hazard_export = out_dir / "hazard_50m_grid.geojson"
|
||||
hazard_count, hazard_bbox = import_hazard_layer(
|
||||
conn,
|
||||
layer_name=HAZARD_LAYER_NAME,
|
||||
description=HAZARD_DESCRIPTION,
|
||||
source_desc=args.source_desc,
|
||||
cell_size_m=HAZARD_CELL_M,
|
||||
tile_root=args.tile_root,
|
||||
zoom=args.zoom,
|
||||
export_path=hazard_export,
|
||||
)
|
||||
|
||||
with conn.cursor() as cur:
|
||||
upsert_meta(
|
||||
cur,
|
||||
layer_name=HAZARD_LAYER_NAME,
|
||||
description=HAZARD_DESCRIPTION,
|
||||
source_desc=args.source_desc,
|
||||
cell_size_m=HAZARD_CELL_M,
|
||||
feature_count=hazard_count,
|
||||
bbox=hazard_bbox,
|
||||
export_file=str(hazard_export.relative_to(project_root)),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
manifest = {
|
||||
"database": args.db_name,
|
||||
"generated_at": dt.datetime.now().isoformat(timespec="seconds"),
|
||||
"layer": {
|
||||
"name": HAZARD_LAYER_NAME,
|
||||
"count": hazard_count,
|
||||
"bbox_lonlat": list(hazard_bbox),
|
||||
"cell_size_m": HAZARD_CELL_M,
|
||||
"export_file": str(hazard_export.relative_to(project_root)),
|
||||
"source_desc": args.source_desc,
|
||||
"tile_root": str(args.tile_root),
|
||||
"zoom": args.zoom,
|
||||
},
|
||||
}
|
||||
manifest_path = out_dir / "manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
print("已导入 MySQL 数据库:", args.db_name)
|
||||
print(" - hazard_50m:", hazard_count, hazard_export)
|
||||
print(" - manifest:", manifest_path)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
485
coastline/export_navgrid_mysql_assets.py
Normal file
485
coastline/export_navgrid_mysql_assets.py
Normal file
@@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import pymysql
|
||||
|
||||
|
||||
DB_NAME = "navsea_japan_coast_grid"
|
||||
DB_USER = "root"
|
||||
DB_PASSWORD = "2chi9ks2"
|
||||
DB_HOST = "localhost"
|
||||
DB_SOCKET = "/tmp/mysql.sock"
|
||||
|
||||
DEFAULT_OUT_DIR = "src/pbf/coastline-mysql/japan_national"
|
||||
|
||||
DENSITY_OVERVIEW_FILE = "density_overview_grid.geojson"
|
||||
CELL_SIZES = {
|
||||
"coast_200m": 200.0,
|
||||
"fish_port_20m": 20.0,
|
||||
"hazard_50m": 50.0,
|
||||
}
|
||||
|
||||
RADIUS = 6378137.0
|
||||
MAX_MERCATOR_LAT = 85.0511287798066
|
||||
|
||||
|
||||
def mysql_connect(database: str, *, cursorclass=pymysql.cursors.Cursor):
|
||||
kwargs = {
|
||||
"host": DB_HOST,
|
||||
"user": DB_USER,
|
||||
"password": DB_PASSWORD,
|
||||
"database": database,
|
||||
"charset": "utf8mb4",
|
||||
"cursorclass": cursorclass,
|
||||
}
|
||||
if DB_SOCKET:
|
||||
kwargs["unix_socket"] = DB_SOCKET
|
||||
return pymysql.connect(**kwargs)
|
||||
|
||||
|
||||
def rect_geometry(min_lon: float, min_lat: float, max_lon: float, max_lat: float) -> dict:
|
||||
return {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[min_lon, min_lat],
|
||||
[max_lon, min_lat],
|
||||
[max_lon, max_lat],
|
||||
[min_lon, max_lat],
|
||||
[min_lon, min_lat],
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def lon_from_mercator(x: float) -> float:
|
||||
return math.degrees(x / RADIUS)
|
||||
|
||||
|
||||
def lat_from_mercator(y: float) -> float:
|
||||
return math.degrees(2.0 * math.atan(math.exp(y / RADIUS)) - math.pi / 2.0)
|
||||
|
||||
|
||||
def mercator_bbox_to_lonlat(minx: float, miny: float, maxx: float, maxy: float) -> tuple[float, float, float, float]:
|
||||
return (
|
||||
lon_from_mercator(minx),
|
||||
lat_from_mercator(miny),
|
||||
lon_from_mercator(maxx),
|
||||
lat_from_mercator(maxy),
|
||||
)
|
||||
|
||||
|
||||
def normalize_bbox(layer_name: str, min_lon: float, min_lat: float, max_lon: float, max_lat: float) -> tuple[float, float, float, float]:
|
||||
if layer_name == "coast_200m" and (
|
||||
abs(min_lon) > 180.0 or abs(max_lon) > 180.0 or abs(min_lat) > 90.0 or abs(max_lat) > 90.0
|
||||
):
|
||||
return mercator_bbox_to_lonlat(min_lon, min_lat, max_lon, max_lat)
|
||||
return min_lon, min_lat, max_lon, max_lat
|
||||
|
||||
|
||||
def fetch_stats(conn, where_sql: str, params: tuple) -> tuple[int, list[float] | None]:
|
||||
sql = f"""
|
||||
SELECT COUNT(*), MIN(min_lon), MIN(min_lat), MAX(max_lon), MAX(max_lat)
|
||||
FROM navsea_grid_cell
|
||||
WHERE {where_sql}
|
||||
"""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql, params)
|
||||
row = cur.fetchone()
|
||||
count = int(row[0] or 0)
|
||||
if count == 0:
|
||||
return 0, None
|
||||
min_lon, min_lat, max_lon, max_lat = normalize_bbox(
|
||||
params[0] if params else "",
|
||||
float(row[1]),
|
||||
float(row[2]),
|
||||
float(row[3]),
|
||||
float(row[4]),
|
||||
)
|
||||
return count, [min_lon, min_lat, max_lon, max_lat]
|
||||
|
||||
|
||||
def export_layer(
|
||||
conn,
|
||||
*,
|
||||
path: Path,
|
||||
where_sql: str,
|
||||
params: tuple,
|
||||
) -> int:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
sql = f"""
|
||||
SELECT layer_name, cell_id, row_idx, col_idx, cell_size_m, state_name, class_name,
|
||||
source_name, min_lon, min_lat, max_lon, max_lat
|
||||
FROM navsea_grid_cell
|
||||
WHERE {where_sql}
|
||||
ORDER BY row_idx, col_idx
|
||||
"""
|
||||
count = 0
|
||||
with conn.cursor(pymysql.cursors.SSCursor) as cur, path.open("w", encoding="utf-8") as fh:
|
||||
cur.execute(sql, params)
|
||||
fh.write('{"type":"FeatureCollection","features":[\n')
|
||||
first = True
|
||||
for row in cur:
|
||||
(
|
||||
layer_name,
|
||||
cell_id,
|
||||
row_idx,
|
||||
col_idx,
|
||||
cell_size_m,
|
||||
state_name,
|
||||
class_name,
|
||||
source_name,
|
||||
min_lon,
|
||||
min_lat,
|
||||
max_lon,
|
||||
max_lat,
|
||||
) = row
|
||||
min_lon, min_lat, max_lon, max_lat = normalize_bbox(
|
||||
row[0],
|
||||
float(min_lon),
|
||||
float(min_lat),
|
||||
float(max_lon),
|
||||
float(max_lat),
|
||||
)
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"layer_name": layer_name,
|
||||
"cell_id": cell_id,
|
||||
"row": int(row_idx),
|
||||
"col": int(col_idx),
|
||||
"cell_size_m": float(cell_size_m),
|
||||
"state_name": state_name,
|
||||
"class_name": class_name,
|
||||
"source_name": source_name,
|
||||
},
|
||||
"geometry": rect_geometry(min_lon, min_lat, max_lon, max_lat),
|
||||
}
|
||||
if not first:
|
||||
fh.write(",\n")
|
||||
fh.write(json.dumps(feature, ensure_ascii=False))
|
||||
first = False
|
||||
count += 1
|
||||
fh.write("\n]}\n")
|
||||
return count
|
||||
|
||||
|
||||
def _range_for_overlap(min_value: float, max_value: float, fine_size_m: float) -> tuple[int, int]:
|
||||
start = int(math.floor(min_value / fine_size_m))
|
||||
end = int(math.floor((max_value - 1e-9) / fine_size_m))
|
||||
return start, end
|
||||
|
||||
|
||||
def _overlaps_finer_cells(row_idx: int, col_idx: int, coarse_size_m: float, fine_size_m: float, fine_keys: set[tuple[int, int]]) -> bool:
|
||||
min_x = col_idx * coarse_size_m
|
||||
min_y = row_idx * coarse_size_m
|
||||
max_x = min_x + coarse_size_m
|
||||
max_y = min_y + coarse_size_m
|
||||
row_start, row_end = _range_for_overlap(min_y, max_y, fine_size_m)
|
||||
col_start, col_end = _range_for_overlap(min_x, max_x, fine_size_m)
|
||||
for fine_row in range(row_start, row_end + 1):
|
||||
for fine_col in range(col_start, col_end + 1):
|
||||
if (fine_row, fine_col) in fine_keys:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def export_density_overview(conn, *, path: Path) -> tuple[int, dict]:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
kept_counts = {
|
||||
"20": 0,
|
||||
"50": 0,
|
||||
"200": 0,
|
||||
}
|
||||
kept_bbox = [float("inf"), float("inf"), float("-inf"), float("-inf")]
|
||||
keys_20: set[tuple[int, int]] = set()
|
||||
keys_50: set[tuple[int, int]] = set()
|
||||
|
||||
def write_feature(fh, feature: dict, first: bool) -> bool:
|
||||
if not first:
|
||||
fh.write(",\n")
|
||||
fh.write(json.dumps(feature, ensure_ascii=False))
|
||||
return False
|
||||
|
||||
with conn.cursor(pymysql.cursors.SSCursor) as cur, path.open("w", encoding="utf-8") as fh:
|
||||
fh.write('{"type":"FeatureCollection","features":[\n')
|
||||
first = True
|
||||
|
||||
# 20m: always keep
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT layer_name, cell_id, row_idx, col_idx, cell_size_m, state_name, class_name,
|
||||
source_name, min_lon, min_lat, max_lon, max_lat
|
||||
FROM navsea_grid_cell
|
||||
WHERE layer_name=%s
|
||||
ORDER BY row_idx, col_idx
|
||||
""",
|
||||
("fish_port_20m",),
|
||||
)
|
||||
for row in cur:
|
||||
(
|
||||
layer_name,
|
||||
cell_id,
|
||||
row_idx,
|
||||
col_idx,
|
||||
cell_size_m,
|
||||
state_name,
|
||||
class_name,
|
||||
source_name,
|
||||
min_lon,
|
||||
min_lat,
|
||||
max_lon,
|
||||
max_lat,
|
||||
) = row
|
||||
keys_20.add((int(row_idx), int(col_idx)))
|
||||
kept_counts["20"] += 1
|
||||
min_lon, min_lat, max_lon, max_lat = normalize_bbox(
|
||||
layer_name,
|
||||
float(min_lon),
|
||||
float(min_lat),
|
||||
float(max_lon),
|
||||
float(max_lat),
|
||||
)
|
||||
kept_bbox[0] = min(kept_bbox[0], min_lon)
|
||||
kept_bbox[1] = min(kept_bbox[1], min_lat)
|
||||
kept_bbox[2] = max(kept_bbox[2], max_lon)
|
||||
kept_bbox[3] = max(kept_bbox[3], max_lat)
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"layer_name": layer_name,
|
||||
"cell_id": cell_id,
|
||||
"row": int(row_idx),
|
||||
"col": int(col_idx),
|
||||
"cell_size_m": float(cell_size_m),
|
||||
"state_name": state_name,
|
||||
"class_name": class_name,
|
||||
"source_name": source_name,
|
||||
"density_key": "20",
|
||||
"density_level": 3,
|
||||
"density_name": "高密度",
|
||||
},
|
||||
"geometry": rect_geometry(min_lon, min_lat, max_lon, max_lat),
|
||||
}
|
||||
first = write_feature(fh, feature, first)
|
||||
|
||||
# 50m: keep only when no 20m cell overlaps this area
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT layer_name, cell_id, row_idx, col_idx, cell_size_m, state_name, class_name,
|
||||
source_name, min_lon, min_lat, max_lon, max_lat
|
||||
FROM navsea_grid_cell
|
||||
WHERE layer_name=%s
|
||||
ORDER BY row_idx, col_idx
|
||||
""",
|
||||
("hazard_50m",),
|
||||
)
|
||||
for row in cur:
|
||||
(
|
||||
layer_name,
|
||||
cell_id,
|
||||
row_idx,
|
||||
col_idx,
|
||||
cell_size_m,
|
||||
state_name,
|
||||
class_name,
|
||||
source_name,
|
||||
min_lon,
|
||||
min_lat,
|
||||
max_lon,
|
||||
max_lat,
|
||||
) = row
|
||||
row_idx_i = int(row_idx)
|
||||
col_idx_i = int(col_idx)
|
||||
if _overlaps_finer_cells(row_idx_i, col_idx_i, CELL_SIZES["hazard_50m"], CELL_SIZES["fish_port_20m"], keys_20):
|
||||
continue
|
||||
keys_50.add((row_idx_i, col_idx_i))
|
||||
kept_counts["50"] += 1
|
||||
min_lon, min_lat, max_lon, max_lat = normalize_bbox(
|
||||
layer_name,
|
||||
float(min_lon),
|
||||
float(min_lat),
|
||||
float(max_lon),
|
||||
float(max_lat),
|
||||
)
|
||||
kept_bbox[0] = min(kept_bbox[0], min_lon)
|
||||
kept_bbox[1] = min(kept_bbox[1], min_lat)
|
||||
kept_bbox[2] = max(kept_bbox[2], max_lon)
|
||||
kept_bbox[3] = max(kept_bbox[3], max_lat)
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"layer_name": layer_name,
|
||||
"cell_id": cell_id,
|
||||
"row": row_idx_i,
|
||||
"col": col_idx_i,
|
||||
"cell_size_m": float(cell_size_m),
|
||||
"state_name": state_name,
|
||||
"class_name": class_name,
|
||||
"source_name": source_name,
|
||||
"density_key": "50",
|
||||
"density_level": 2,
|
||||
"density_name": "中密度",
|
||||
},
|
||||
"geometry": rect_geometry(min_lon, min_lat, max_lon, max_lat),
|
||||
}
|
||||
first = write_feature(fh, feature, first)
|
||||
|
||||
# 200m: keep only when neither 20m nor 50m cell overlaps this area
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT layer_name, cell_id, row_idx, col_idx, cell_size_m, state_name, class_name,
|
||||
source_name, min_lon, min_lat, max_lon, max_lat
|
||||
FROM navsea_grid_cell
|
||||
WHERE layer_name=%s
|
||||
ORDER BY row_idx, col_idx
|
||||
""",
|
||||
("coast_200m",),
|
||||
)
|
||||
for row in cur:
|
||||
(
|
||||
layer_name,
|
||||
cell_id,
|
||||
row_idx,
|
||||
col_idx,
|
||||
cell_size_m,
|
||||
state_name,
|
||||
class_name,
|
||||
source_name,
|
||||
min_lon,
|
||||
min_lat,
|
||||
max_lon,
|
||||
max_lat,
|
||||
) = row
|
||||
row_idx_i = int(row_idx)
|
||||
col_idx_i = int(col_idx)
|
||||
if _overlaps_finer_cells(row_idx_i, col_idx_i, CELL_SIZES["coast_200m"], CELL_SIZES["fish_port_20m"], keys_20):
|
||||
continue
|
||||
if _overlaps_finer_cells(row_idx_i, col_idx_i, CELL_SIZES["coast_200m"], CELL_SIZES["hazard_50m"], keys_50):
|
||||
continue
|
||||
kept_counts["200"] += 1
|
||||
min_lon, min_lat, max_lon, max_lat = normalize_bbox(
|
||||
layer_name,
|
||||
float(min_lon),
|
||||
float(min_lat),
|
||||
float(max_lon),
|
||||
float(max_lat),
|
||||
)
|
||||
kept_bbox[0] = min(kept_bbox[0], min_lon)
|
||||
kept_bbox[1] = min(kept_bbox[1], min_lat)
|
||||
kept_bbox[2] = max(kept_bbox[2], max_lon)
|
||||
kept_bbox[3] = max(kept_bbox[3], max_lat)
|
||||
feature = {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"layer_name": layer_name,
|
||||
"cell_id": cell_id,
|
||||
"row": row_idx_i,
|
||||
"col": col_idx_i,
|
||||
"cell_size_m": float(cell_size_m),
|
||||
"state_name": state_name,
|
||||
"class_name": class_name,
|
||||
"source_name": source_name,
|
||||
"density_key": "200",
|
||||
"density_level": 1,
|
||||
"density_name": "低密度",
|
||||
},
|
||||
"geometry": rect_geometry(min_lon, min_lat, max_lon, max_lat),
|
||||
}
|
||||
first = write_feature(fh, feature, first)
|
||||
|
||||
fh.write("\n]}\n")
|
||||
|
||||
total = kept_counts["20"] + kept_counts["50"] + kept_counts["200"]
|
||||
if total == 0:
|
||||
raise RuntimeError("density overview export produced no features")
|
||||
summary = {
|
||||
"count": total,
|
||||
"by_density": kept_counts,
|
||||
"bbox": None if kept_bbox[0] == float("inf") else kept_bbox,
|
||||
}
|
||||
return total, summary
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Export NavSea MySQL grid layers to static GeoJSON assets")
|
||||
parser.add_argument("--db-name", default=DB_NAME)
|
||||
parser.add_argument("--out-dir", default=DEFAULT_OUT_DIR)
|
||||
args = parser.parse_args()
|
||||
|
||||
project_root = Path(__file__).resolve().parent.parent
|
||||
out_dir = Path(args.out_dir)
|
||||
if not out_dir.is_absolute():
|
||||
out_dir = project_root / out_dir
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
layers = {
|
||||
"coast_200m": {
|
||||
"file": "coast_200m_grid.geojson",
|
||||
"where": "layer_name=%s",
|
||||
"params": ("coast_200m",),
|
||||
"cell_size_m": 200.0,
|
||||
},
|
||||
"fish_port_20m": {
|
||||
"file": "fish_port_20m_grid.geojson",
|
||||
"where": "layer_name=%s",
|
||||
"params": ("fish_port_20m",),
|
||||
"cell_size_m": 20.0,
|
||||
},
|
||||
"hazard_50m": {
|
||||
"file": "hazard_50m_grid.geojson",
|
||||
"where": "layer_name=%s",
|
||||
"params": ("hazard_50m",),
|
||||
"cell_size_m": 50.0,
|
||||
},
|
||||
}
|
||||
|
||||
manifest_layers: dict[str, dict] = {}
|
||||
conn = mysql_connect(args.db_name)
|
||||
try:
|
||||
for layer_name, spec in layers.items():
|
||||
out_path = out_dir / spec["file"]
|
||||
count = export_layer(conn, path=out_path, where_sql=spec["where"], params=spec["params"])
|
||||
stat_count, bbox = fetch_stats(conn, spec["where"], spec["params"])
|
||||
if count != stat_count:
|
||||
raise RuntimeError(f"{layer_name} export count mismatch: {count} != {stat_count}")
|
||||
manifest_layers[layer_name] = {
|
||||
"count": count,
|
||||
"bbox_lonlat": bbox,
|
||||
"cell_size_m": spec["cell_size_m"],
|
||||
"export_file": str(out_path.relative_to(project_root)),
|
||||
}
|
||||
print(f"{layer_name}: {count} -> {out_path}")
|
||||
|
||||
density_path = out_dir / DENSITY_OVERVIEW_FILE
|
||||
density_count, density_summary = export_density_overview(conn, path=density_path)
|
||||
manifest_layers["density_overview"] = {
|
||||
"count": density_count,
|
||||
"bbox_lonlat": density_summary["bbox"],
|
||||
"cell_size_m": None,
|
||||
"export_file": str(density_path.relative_to(project_root)),
|
||||
"by_density": density_summary["by_density"],
|
||||
}
|
||||
print(f"density_overview: {density_count} -> {density_path}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
manifest = {
|
||||
"database": args.db_name,
|
||||
"generated_at": dt.datetime.now().isoformat(timespec="seconds"),
|
||||
"layers": manifest_layers,
|
||||
}
|
||||
manifest_path = out_dir / "manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"manifest: {manifest_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
282
find_fpc_by_port_name.py
Normal file
282
find_fpc_by_port_name.py
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
FISH_SRC_DEFAULT = "coastline/C09-06.zip"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MatchRow:
|
||||
fpc: str
|
||||
prc: str | None
|
||||
primary_name: str | None
|
||||
secondary_name: str | None
|
||||
source_id: str | None
|
||||
score: tuple[int, int, str]
|
||||
|
||||
|
||||
def local_name(tag: str) -> str:
|
||||
return tag.split("}", 1)[-1]
|
||||
|
||||
|
||||
def normalize_text(value: str) -> str:
|
||||
value = value.strip()
|
||||
value = value.replace(" ", " ")
|
||||
value = re.sub(r"\s+", "", value)
|
||||
return value.lower()
|
||||
|
||||
|
||||
def load_xml(zip_path: Path) -> ET.Element:
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
xml_name = next(name for name in zf.namelist() if name.endswith(".xml") and "META" not in name)
|
||||
return ET.fromstring(zf.read(xml_name))
|
||||
|
||||
|
||||
def get_text(feature: ET.Element, key: str) -> str | None:
|
||||
el = feature.find(f"{{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}}{key}")
|
||||
if el is None or not (el.text or "").strip():
|
||||
return None
|
||||
return el.text.strip()
|
||||
|
||||
|
||||
def feature_name_tokens(feature: ET.Element) -> list[str]:
|
||||
tokens: list[str] = []
|
||||
for key in ("NA2", "NA4", "FCF", "AAC", "CFP", "FPA"):
|
||||
value = get_text(feature, key)
|
||||
if value:
|
||||
tokens.append(value)
|
||||
return tokens
|
||||
|
||||
|
||||
def feature_display_name(feature: ET.Element) -> tuple[str | None, str | None]:
|
||||
na2 = get_text(feature, "NA2")
|
||||
na4 = get_text(feature, "NA4")
|
||||
fcf = get_text(feature, "FCF")
|
||||
primary = na2 or na4 or fcf
|
||||
secondary = None
|
||||
if primary == na2:
|
||||
secondary = fcf or na4
|
||||
elif primary == na4:
|
||||
secondary = na2 or fcf
|
||||
else:
|
||||
secondary = na2 or na4
|
||||
return primary, secondary
|
||||
|
||||
|
||||
def catalog_display_name(row: dict[str, str]) -> str | None:
|
||||
for key in ("name", "name_ja", "port_name", "港名", "漁港名", "名称"):
|
||||
value = row.get(key)
|
||||
if value and value.strip():
|
||||
return value.strip()
|
||||
return None
|
||||
|
||||
|
||||
def catalog_fpc(row: dict[str, str]) -> str | None:
|
||||
for key in ("fpc", "FPC"):
|
||||
value = row.get(key)
|
||||
if value and value.strip():
|
||||
return value.strip()
|
||||
return None
|
||||
|
||||
|
||||
def catalog_prc(row: dict[str, str]) -> str | None:
|
||||
for key in ("prc", "PRC"):
|
||||
value = row.get(key)
|
||||
if value and value.strip():
|
||||
return value.strip()
|
||||
return None
|
||||
|
||||
|
||||
def score_match(query: str, tokens: list[str], primary: str | None) -> tuple[int, int, str] | None:
|
||||
q = normalize_text(query)
|
||||
token_norms = [normalize_text(token) for token in tokens if token]
|
||||
primary_norm = normalize_text(primary) if primary else ""
|
||||
|
||||
if primary_norm == q:
|
||||
return (0, 0, primary_norm)
|
||||
if q in token_norms:
|
||||
return (1, 0, primary_norm)
|
||||
if primary_norm and q in primary_norm:
|
||||
return (2, len(primary_norm), primary_norm)
|
||||
for token in token_norms:
|
||||
if q in token:
|
||||
return (3, len(token), primary_norm or token)
|
||||
return None
|
||||
|
||||
|
||||
def load_catalog_matches(catalog_path: Path, query: str) -> list[MatchRow]:
|
||||
suffix = catalog_path.suffix.lower()
|
||||
rows: list[dict[str, str]] = []
|
||||
if suffix in (".csv", ".tsv"):
|
||||
delimiter = "\t" if suffix == ".tsv" else ","
|
||||
with catalog_path.open("r", encoding="utf-8-sig", newline="") as fh:
|
||||
reader = csv.DictReader(fh, delimiter=delimiter)
|
||||
rows.extend(dict(row) for row in reader)
|
||||
elif suffix in (".json", ".jsonl"):
|
||||
text = catalog_path.read_text(encoding="utf-8")
|
||||
if suffix == ".jsonl":
|
||||
rows.extend(json.loads(line) for line in text.splitlines() if line.strip())
|
||||
else:
|
||||
payload = json.loads(text)
|
||||
if isinstance(payload, list):
|
||||
rows.extend(dict(row) for row in payload)
|
||||
elif isinstance(payload, dict) and isinstance(payload.get("items"), list):
|
||||
rows.extend(dict(row) for row in payload["items"])
|
||||
else:
|
||||
raise SystemExit("catalog json must be a list or {items:[...]}")
|
||||
else:
|
||||
raise SystemExit("catalog must be .csv, .tsv, .json or .jsonl")
|
||||
|
||||
matches: list[MatchRow] = []
|
||||
for row in rows:
|
||||
name = catalog_display_name(row)
|
||||
fpc = catalog_fpc(row)
|
||||
if not name or not fpc:
|
||||
continue
|
||||
score = score_match(query, [name], name)
|
||||
if score is None:
|
||||
continue
|
||||
matches.append(
|
||||
MatchRow(
|
||||
fpc=fpc,
|
||||
prc=catalog_prc(row),
|
||||
primary_name=name,
|
||||
secondary_name=None,
|
||||
source_id=row.get("source_id") or row.get("id") or row.get("sid"),
|
||||
score=score,
|
||||
)
|
||||
)
|
||||
matches.sort(key=lambda row: (row.score, row.fpc))
|
||||
return matches
|
||||
|
||||
|
||||
def collect_matches(zip_path: Path, query: str) -> list[MatchRow]:
|
||||
root = load_xml(zip_path)
|
||||
obj = root.find(".//{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}OBJ")
|
||||
if obj is None:
|
||||
raise SystemExit("OBJ block missing in input package")
|
||||
|
||||
matches: list[MatchRow] = []
|
||||
for feature in obj.findall(".//{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}CB03"):
|
||||
fpc = get_text(feature, "FPC")
|
||||
prc = get_text(feature, "PRC")
|
||||
if not fpc:
|
||||
continue
|
||||
tokens = feature_name_tokens(feature)
|
||||
primary, secondary = feature_display_name(feature)
|
||||
score = score_match(query, tokens, primary)
|
||||
if score is None:
|
||||
continue
|
||||
source_id = None
|
||||
loc = feature.find("{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}LOC")
|
||||
if loc is not None:
|
||||
source_id = loc.attrib.get("idref")
|
||||
matches.append(
|
||||
MatchRow(
|
||||
fpc=fpc,
|
||||
prc=prc,
|
||||
primary_name=primary,
|
||||
secondary_name=secondary,
|
||||
source_id=source_id,
|
||||
score=score,
|
||||
)
|
||||
)
|
||||
|
||||
matches.sort(key=lambda row: (row.score, row.fpc))
|
||||
return matches
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="按渔港名字查找 FPC")
|
||||
parser.add_argument("name", help="渔港名字,支持包含匹配")
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
default=FISH_SRC_DEFAULT,
|
||||
help="输入渔港原始包,默认 coastline/C09-06.zip",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--catalog",
|
||||
default=None,
|
||||
help="可选的港名对照表(CSV/TSV/JSON/JSONL);如果原始包里找不到名字,建议用它",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exact",
|
||||
action="store_true",
|
||||
help="只保留精确匹配",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="以 JSON 输出",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-results",
|
||||
type=int,
|
||||
default=20,
|
||||
help="最多输出多少条候选结果",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
source_path = Path(args.source)
|
||||
if not source_path.is_absolute():
|
||||
source_path = Path(__file__).resolve().parent / source_path
|
||||
if not source_path.exists():
|
||||
raise SystemExit(f"missing input: {source_path}")
|
||||
|
||||
matches: list[MatchRow] = []
|
||||
if args.catalog:
|
||||
catalog_path = Path(args.catalog)
|
||||
if not catalog_path.is_absolute():
|
||||
catalog_path = Path(__file__).resolve().parent / catalog_path
|
||||
if not catalog_path.exists():
|
||||
raise SystemExit(f"missing catalog: {catalog_path}")
|
||||
matches = load_catalog_matches(catalog_path, args.name)
|
||||
|
||||
if not matches:
|
||||
matches = collect_matches(source_path, args.name)
|
||||
if args.exact:
|
||||
q = normalize_text(args.name)
|
||||
matches = [row for row in matches if normalize_text(row.primary_name or "") == q]
|
||||
|
||||
matches = matches[: args.max_results]
|
||||
if not matches:
|
||||
if args.catalog:
|
||||
raise SystemExit(
|
||||
f"no FPC matched for name={args.name!r} in catalog={args.catalog!r}"
|
||||
)
|
||||
raise SystemExit(
|
||||
f"no FPC matched for name={args.name!r}; "
|
||||
"this source package may not contain human-readable port names, "
|
||||
"try --catalog with a port-name mapping file"
|
||||
)
|
||||
|
||||
if args.json:
|
||||
print(
|
||||
json.dumps(
|
||||
[asdict(row) | {"score": list(row.score)} for row in matches],
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
for idx, row in enumerate(matches, start=1):
|
||||
name_bits = [bit for bit in [row.primary_name, row.secondary_name] if bit]
|
||||
print(
|
||||
f"{idx}. FPC={row.fpc} PRC={row.prc or '-'} "
|
||||
f"name={' / '.join(name_bits) if name_bits else '-'} "
|
||||
f"source_id={row.source_id or '-'}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
163
navsea_audit_kyushu_semantic_icon_test.py
Normal file
163
navsea_audit_kyushu_semantic_icon_test.py
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""审计九州语义图标测试版是否丢失对象、几何或非图标属性。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import Counter
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import mapbox_vector_tile
|
||||
|
||||
|
||||
EXPECTED_FIELDS = {"chart_icon_image", "icon_id", "arc_id"}
|
||||
|
||||
|
||||
def stable(value: Any) -> str:
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
def normalize_expected_properties(layer_name: str, properties: dict[str, Any]) -> None:
|
||||
if (
|
||||
layer_name == "anchor_caution_hazard_area"
|
||||
and str(properties.get("class_code")) == "420"
|
||||
and properties.get("chart_fill_pattern") == "fill-daytime-405"
|
||||
):
|
||||
properties["chart_fill_pattern"] = "fill-daytime-420"
|
||||
if (
|
||||
layer_name == "navigation_hazard_area"
|
||||
and str(properties.get("class_code")) == "427"
|
||||
and properties.get("chart_fill_pattern") == "fill-daytime-405"
|
||||
):
|
||||
properties["chart_fill_pattern"] = "fill-daytime-427"
|
||||
|
||||
|
||||
def feature_signature(layer_name: str, feature: dict[str, Any]) -> str:
|
||||
properties = dict(feature.get("properties") or {})
|
||||
for field in EXPECTED_FIELDS:
|
||||
properties.pop(field, None)
|
||||
normalize_expected_properties(layer_name, properties)
|
||||
return stable({
|
||||
"id": feature.get("id"),
|
||||
"geometry": feature.get("geometry"),
|
||||
"properties": properties,
|
||||
})
|
||||
|
||||
|
||||
def audit_tile(args: tuple[str, str]) -> dict[str, Any]:
|
||||
source_path, target_path = args
|
||||
source = mapbox_vector_tile.decode(Path(source_path).read_bytes())
|
||||
target = mapbox_vector_tile.decode(Path(target_path).read_bytes())
|
||||
errors: list[dict[str, Any]] = []
|
||||
source_layers = set(source)
|
||||
target_layers = set(target)
|
||||
if source_layers != target_layers:
|
||||
errors.append({"kind": "layer_set", "source": sorted(source_layers), "target": sorted(target_layers)})
|
||||
|
||||
source_counts = Counter()
|
||||
target_counts = Counter()
|
||||
icon_counts = Counter()
|
||||
old_prefix_counts = Counter()
|
||||
for layer_name in sorted(source_layers | target_layers):
|
||||
source_features = source.get(layer_name, {}).get("features", [])
|
||||
target_features = target.get(layer_name, {}).get("features", [])
|
||||
source_counts[layer_name] = len(source_features)
|
||||
target_counts[layer_name] = len(target_features)
|
||||
source_signatures = Counter(feature_signature(layer_name, feature) for feature in source_features)
|
||||
target_signatures = Counter(feature_signature(layer_name, feature) for feature in target_features)
|
||||
if source_signatures != target_signatures:
|
||||
missing = source_signatures - target_signatures
|
||||
extra = target_signatures - source_signatures
|
||||
errors.append({
|
||||
"kind": "feature_or_geometry_or_property",
|
||||
"layer": layer_name,
|
||||
"source_count": len(source_features),
|
||||
"target_count": len(target_features),
|
||||
"missing_count": sum(missing.values()),
|
||||
"extra_count": sum(extra.values()),
|
||||
})
|
||||
for feature in target_features:
|
||||
properties = feature.get("properties") or {}
|
||||
for field in ("icon_id", "arc_id"):
|
||||
value = properties.get(field)
|
||||
if isinstance(value, str):
|
||||
icon_counts[f"{field}:{value}"] += 1
|
||||
for value in properties.values():
|
||||
if isinstance(value, str) and (value.startswith("symbol-daytime-") or value.startswith("arc-daytime-")):
|
||||
old_prefix_counts[value] += 1
|
||||
return {
|
||||
"tile": source_path,
|
||||
"errors": errors,
|
||||
"source_counts": dict(source_counts),
|
||||
"target_counts": dict(target_counts),
|
||||
"icon_counts": dict(icon_counts),
|
||||
"old_prefix_counts": dict(old_prefix_counts),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source-root", type=Path, required=True)
|
||||
parser.add_argument("--target-root", type=Path, required=True)
|
||||
parser.add_argument("--report-dir", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
pairs = []
|
||||
for target in sorted(args.target_root.glob("*/*/*.pbf")):
|
||||
source = args.source_root / target.relative_to(args.target_root)
|
||||
if source.exists():
|
||||
pairs.append((str(source), str(target)))
|
||||
|
||||
results: list[dict[str, Any]] = []
|
||||
with ProcessPoolExecutor() as executor:
|
||||
for result in executor.map(audit_tile, pairs, chunksize=16):
|
||||
results.append(result)
|
||||
|
||||
error_rows = [error | {"tile": result["tile"]} for result in results for error in result["errors"]]
|
||||
icon_counts = Counter()
|
||||
old_prefix_counts = Counter()
|
||||
source_features = Counter()
|
||||
target_features = Counter()
|
||||
for result in results:
|
||||
icon_counts.update(result["icon_counts"])
|
||||
old_prefix_counts.update(result["old_prefix_counts"])
|
||||
source_features.update(result["source_counts"])
|
||||
target_features.update(result["target_counts"])
|
||||
|
||||
payload = {
|
||||
"source_root": str(args.source_root),
|
||||
"target_root": str(args.target_root),
|
||||
"tiles_target": len(list(args.target_root.glob("*/*/*.pbf"))),
|
||||
"tiles_compared": len(results),
|
||||
"tiles_with_errors": len({row["tile"] for row in error_rows}),
|
||||
"errors": error_rows[:200],
|
||||
"source_features": dict(source_features),
|
||||
"target_features": dict(target_features),
|
||||
"icon_counts": dict(icon_counts),
|
||||
"old_prefix_counts": dict(old_prefix_counts),
|
||||
"verdict": "PASS" if not error_rows and not old_prefix_counts else "FAIL",
|
||||
}
|
||||
args.report_dir.mkdir(parents=True, exist_ok=True)
|
||||
(args.report_dir / "object_geometry_icon_audit.json").write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
lines = [
|
||||
"# 九州语义图标测试版对象 / 几何 / 图标审计",
|
||||
"",
|
||||
f"- 来源:`{args.source_root}`",
|
||||
f"- 目标:`{args.target_root}`",
|
||||
f"- 目标瓦片:`{payload['tiles_target']}`,实际比较:`{payload['tiles_compared']}`",
|
||||
f"- 有错误瓦片:`{payload['tiles_with_errors']}`",
|
||||
f"- 旧图标前缀残留:`{sum(old_prefix_counts.values())}`",
|
||||
f"- 结论:`{payload['verdict']}`",
|
||||
"",
|
||||
"本审计忽略预期的 `chart_icon_image`、`icon_id`、`arc_id` 字段变化,逐图层比较对象数量、ID、几何和其余属性。",
|
||||
]
|
||||
(args.report_dir / "object_geometry_icon_audit.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
494
navsea_build_kyushu_semantic_icon_test.py
Normal file
494
navsea_build_kyushu_semantic_icon_test.py
Normal file
@@ -0,0 +1,494 @@
|
||||
#!/usr/bin/env python3
|
||||
"""从当前全国 Full 基线生成九州语义图标替换测试包。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from collections import Counter
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import mercantile
|
||||
|
||||
from navsea_tile_reencode_guard import (
|
||||
assert_reencoded_tile_safe,
|
||||
decode_tile_with_extents,
|
||||
encode_layers_preserving_extents,
|
||||
)
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent
|
||||
CONFIG_PATH = REPO_ROOT / "tasks/pbf/navsea_semantic_icon_map_v1.json"
|
||||
SOURCE_ROOT = Path("/home/wwwroot/pbf-delivery-full-20260418-rebuild")
|
||||
TARGET_ROOT = Path("/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806")
|
||||
TARGET_TILE_URL = "http://192.168.200.184/pbf-delivery-kyushu-semantic-icon-v1-20260806/{z}/{x}/{y}.pbf"
|
||||
KYUSHU_BBOX = (128.0, 30.0, 132.5, 34.9)
|
||||
ZOOMS = tuple(range(5, 13))
|
||||
ASSET_VERSION = "kyushu-semantic-icon-v1-20260806-r2"
|
||||
|
||||
SPRITE_SOURCE = Path("/mnt/sda1/www/newpec/sprite-semantic")
|
||||
SPRITE_TARGET = Path(f"/mnt/sda1/www/newpec/sprite-{ASSET_VERSION}")
|
||||
SPRITE_URL = f"http://192.168.200.184/newpec/sprite-{ASSET_VERSION}/sprite"
|
||||
|
||||
STYLE_SOURCE = REPO_ROOT / "src/pbf/style.navsea-delivery-full-semantic-extentfix.json"
|
||||
STYLE_OUTPUT = REPO_ROOT / "src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json"
|
||||
STYLE_DEPLOY = Path("/mnt/sda1/www/newpec/domain/style.navsea-delivery-kyushu-semantic-icon-v1.json")
|
||||
|
||||
NEWPEC_STYLE_SOURCE = REPO_ROOT / "src/pbf/style.json"
|
||||
NEWPEC_STYLE_OUTPUT = REPO_ROOT / "src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json"
|
||||
NEWPEC_STYLE_DEPLOY = Path("/mnt/sda1/www/newpec/domain/style.navsea-newpec-kyushu-icon-test-v1.json")
|
||||
|
||||
REPORT_DIR = REPO_ROOT / "report/kyushu_semantic_icon_v1_2026-08-06"
|
||||
|
||||
SIMPLE_ICON_LAYERS = {
|
||||
"hazard-points",
|
||||
"anchorage-symbols",
|
||||
"anchor-hazard-points",
|
||||
"anchor-hazard-points-428",
|
||||
"facility-points",
|
||||
"landmark-points",
|
||||
}
|
||||
|
||||
STYLE_EXTRA_MAP = {
|
||||
"light_flare_green": "fl_G",
|
||||
"light_flare_red": "fl_R",
|
||||
"light_flare_yellow_white": "fl_YW",
|
||||
}
|
||||
|
||||
CANONICAL_ICON_MAP = {
|
||||
"coastal_lighthouse_over_15m": "lt_cst",
|
||||
"harbor_lighthouse": "lt_hbr",
|
||||
"breakwater_lighthouse": "lt_bkw",
|
||||
"minor_light": "lt_min",
|
||||
"light_beacon": "lt_bcn",
|
||||
"light_buoy": "bu_lit",
|
||||
"lattice_buoy": "bu_lat",
|
||||
"pillar_buoy": "bu_pil",
|
||||
"can_buoy": "bu_can",
|
||||
"buoy_generic": "bu_gen",
|
||||
"nav_mark_vais": "mk_vais",
|
||||
}
|
||||
|
||||
|
||||
def load_config() -> dict[str, Any]:
|
||||
return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def collect_tiles() -> list[tuple[int, int, int, Path]]:
|
||||
tiles: list[tuple[int, int, int, Path]] = []
|
||||
west, south, east, north = KYUSHU_BBOX
|
||||
for z in ZOOMS:
|
||||
for tile in mercantile.tiles(west, south, east, north, [z]):
|
||||
rel = Path(str(tile.z)) / str(tile.x) / f"{tile.y}.pbf"
|
||||
source = SOURCE_ROOT / rel
|
||||
if source.exists():
|
||||
tiles.append((tile.z, tile.x, tile.y, rel))
|
||||
return sorted(tiles)
|
||||
|
||||
|
||||
def copy_link(source: Path, target: Path) -> None:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
if target.exists():
|
||||
target.unlink()
|
||||
try:
|
||||
target.hardlink_to(source)
|
||||
except OSError:
|
||||
shutil.copy2(source, target)
|
||||
|
||||
|
||||
def class_icon(layer_name: str, class_code: object, config: dict[str, Any]) -> str | None:
|
||||
code = str(class_code)
|
||||
if layer_name in {"navigation_hazard_point", "anchor_caution_hazard_point"}:
|
||||
return config["hazard_class_map"].get(code)
|
||||
if layer_name == "onshore_structure_point":
|
||||
return config["landmark_class_map"].get(code)
|
||||
if layer_name == "anchorage_point":
|
||||
return config["anchorage_class_map"].get(code)
|
||||
if layer_name == "facility_boundary_point":
|
||||
return config["facility_class_map"].get(code)
|
||||
return None
|
||||
|
||||
|
||||
def navigation_icon(properties: dict[str, Any], config: dict[str, Any]) -> str | None:
|
||||
legacy = properties.get("chart_icon_image")
|
||||
if isinstance(legacy, str):
|
||||
mapped = config["sprite_key_map"].get(legacy)
|
||||
if mapped:
|
||||
return mapped
|
||||
display_code = properties.get("display_code")
|
||||
if display_code is not None:
|
||||
mapped = config["display_code_map"].get(str(display_code))
|
||||
if mapped:
|
||||
return mapped
|
||||
canonical = properties.get("canonical_object_type")
|
||||
if isinstance(canonical, str):
|
||||
return CANONICAL_ICON_MAP.get(canonical)
|
||||
return None
|
||||
|
||||
|
||||
def arc_icon(properties: dict[str, Any], config: dict[str, Any]) -> str | None:
|
||||
if properties.get("chart_symbol_code") != "lighthouse":
|
||||
return None
|
||||
if not properties.get("light_color_code"):
|
||||
return None
|
||||
if (
|
||||
properties.get("canonical_object_type") == "coastal_lighthouse_over_15m"
|
||||
and properties.get("light_sector_mode") == "sector"
|
||||
):
|
||||
return config["arc_rules"]["sector"]
|
||||
color = str(properties.get("light_color_code"))
|
||||
return config["arc_rules"].get(color, config["arc_rules"]["other"])
|
||||
|
||||
|
||||
def normalize_pattern_properties(layer_name: str, properties: dict[str, Any]) -> None:
|
||||
if (
|
||||
layer_name == "anchor_caution_hazard_area"
|
||||
and str(properties.get("class_code")) == "420"
|
||||
and properties.get("chart_fill_pattern") == "fill-daytime-405"
|
||||
):
|
||||
properties["chart_fill_pattern"] = "fill-daytime-420"
|
||||
if (
|
||||
layer_name == "navigation_hazard_area"
|
||||
and str(properties.get("class_code")) == "427"
|
||||
and properties.get("chart_fill_pattern") == "fill-daytime-405"
|
||||
):
|
||||
properties["chart_fill_pattern"] = "fill-daytime-427"
|
||||
|
||||
|
||||
def rewrite_tile(
|
||||
source: Path,
|
||||
target: Path,
|
||||
config: dict[str, Any],
|
||||
*,
|
||||
verify_geometry: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
decoded, extents = decode_tile_with_extents(source.read_bytes())
|
||||
changed_features = 0
|
||||
icon_counts: Counter[str] = Counter()
|
||||
arc_counts: Counter[str] = Counter()
|
||||
old_values: Counter[str] = Counter()
|
||||
unknown_old_values: Counter[str] = Counter()
|
||||
output_layers: list[dict[str, Any]] = []
|
||||
|
||||
for layer_name, layer in decoded.items():
|
||||
output_features = []
|
||||
for feature in layer["features"]:
|
||||
properties = dict(feature.get("properties") or {})
|
||||
before = dict(properties)
|
||||
normalize_pattern_properties(layer_name, properties)
|
||||
legacy = properties.get("chart_icon_image")
|
||||
if isinstance(legacy, str) and (
|
||||
legacy.startswith("symbol-daytime-") or legacy.startswith("arc-daytime-")
|
||||
):
|
||||
old_values[legacy] += 1
|
||||
|
||||
icon_id = class_icon(layer_name, properties.get("class_code"), config)
|
||||
if layer_name == "navigation_marks":
|
||||
icon_id = icon_id or navigation_icon(properties, config)
|
||||
if icon_id is None and isinstance(legacy, str):
|
||||
icon_id = config["sprite_key_map"].get(legacy)
|
||||
|
||||
if icon_id:
|
||||
properties["icon_id"] = icon_id
|
||||
icon_counts[icon_id] += 1
|
||||
properties.pop("chart_icon_image", None)
|
||||
|
||||
if layer_name == "navigation_marks":
|
||||
arc_id = arc_icon(properties, config)
|
||||
if arc_id:
|
||||
properties["arc_id"] = arc_id
|
||||
arc_counts[arc_id] += 1
|
||||
|
||||
for value in properties.values():
|
||||
if isinstance(value, str) and (
|
||||
value.startswith("symbol-daytime-") or value.startswith("arc-daytime-")
|
||||
):
|
||||
unknown_old_values[value] += 1
|
||||
|
||||
if properties != before:
|
||||
changed_features += 1
|
||||
output_feature = {
|
||||
"geometry": feature["geometry"],
|
||||
"properties": properties,
|
||||
}
|
||||
if feature.get("id") is not None:
|
||||
output_feature["id"] = feature["id"]
|
||||
output_features.append(output_feature)
|
||||
output_layers.append({"name": layer_name, "features": output_features})
|
||||
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(encode_layers_preserving_extents(output_layers, extents))
|
||||
if verify_geometry:
|
||||
assert_reencoded_tile_safe(source, target)
|
||||
return {
|
||||
"changed_features": changed_features,
|
||||
"icon_counts": dict(icon_counts),
|
||||
"arc_counts": dict(arc_counts),
|
||||
"old_values": dict(old_values),
|
||||
"unknown_old_values": dict(unknown_old_values),
|
||||
}
|
||||
|
||||
|
||||
def rewrite_tile_job(args: tuple[str, str, dict[str, Any], bool]) -> dict[str, Any]:
|
||||
source, target, config, verify_geometry = args
|
||||
result = rewrite_tile(
|
||||
Path(source), Path(target), config, verify_geometry=verify_geometry
|
||||
)
|
||||
result["source"] = source
|
||||
return result
|
||||
|
||||
|
||||
def transform_style(config: dict[str, Any]) -> dict[str, Any]:
|
||||
style = json.loads(STYLE_SOURCE.read_text(encoding="utf-8"))
|
||||
key_map = dict(config["sprite_key_map"])
|
||||
key_map.update(STYLE_EXTRA_MAP)
|
||||
|
||||
def transform(value: Any) -> Any:
|
||||
if isinstance(value, dict):
|
||||
return {key: transform(item) for key, item in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [transform(item) for item in value]
|
||||
if isinstance(value, str):
|
||||
if value == "chart_icon_image":
|
||||
return "icon_id"
|
||||
return key_map.get(value, value)
|
||||
return value
|
||||
|
||||
style = transform(style)
|
||||
style["name"] = "NavSea Kyushu Semantic Icon Test v1"
|
||||
style["sprite"] = SPRITE_URL
|
||||
style["metadata"] = {
|
||||
"navsea_test_version": "kyushu-semantic-icon-v1-20260806",
|
||||
"source_baseline": str(SOURCE_ROOT),
|
||||
"icon_map": str(CONFIG_PATH),
|
||||
}
|
||||
style["sources"]["navsea_delivery"]["tiles"] = [TARGET_TILE_URL]
|
||||
|
||||
for layer in style["layers"]:
|
||||
layer_id = layer.get("id")
|
||||
layout = layer.get("layout") or {}
|
||||
if layer_id in SIMPLE_ICON_LAYERS:
|
||||
layout["icon-image"] = ["coalesce", ["get", "icon_id"], ""]
|
||||
layer["layout"] = layout
|
||||
elif layer_id == "nav-light-arc":
|
||||
layout["icon-image"] = ["coalesce", ["get", "arc_id"], ""]
|
||||
layer["layout"] = layout
|
||||
return style
|
||||
|
||||
|
||||
def write_style(style: dict[str, Any], path: Path, deploy_path: Path) -> None:
|
||||
payload = json.dumps(style, ensure_ascii=False, indent=2) + "\n"
|
||||
path.write_text(payload, encoding="utf-8")
|
||||
deploy_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
deploy_path.write_text(payload, encoding="utf-8")
|
||||
|
||||
|
||||
def write_newpec_style() -> None:
|
||||
style = json.loads(NEWPEC_STYLE_SOURCE.read_text(encoding="utf-8"))
|
||||
style["name"] = "NavSea Newpec Kyushu Icon Test v1"
|
||||
style["metadata"] = {
|
||||
"navsea_test_version": "kyushu-semantic-icon-v1-20260806",
|
||||
"role": "left_original_newpec_reference",
|
||||
}
|
||||
write_style(style, NEWPEC_STYLE_OUTPUT, NEWPEC_STYLE_DEPLOY)
|
||||
|
||||
|
||||
def collect_literal_sprite_refs(style: dict[str, Any]) -> set[str]:
|
||||
refs: set[str] = set()
|
||||
|
||||
def walk(value: Any) -> None:
|
||||
if isinstance(value, dict):
|
||||
for child in value.values():
|
||||
walk(child)
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
walk(child)
|
||||
elif isinstance(value, str):
|
||||
if (
|
||||
value.startswith("pattern_fill_")
|
||||
or value.startswith("fill-daytime-")
|
||||
or value.startswith("fill_")
|
||||
or value.startswith("special_pattern_")
|
||||
or value in {"tide_spot"}
|
||||
):
|
||||
refs.add(value)
|
||||
|
||||
for layer in style.get("layers", []):
|
||||
layout = layer.get("layout") or {}
|
||||
paint = layer.get("paint") or {}
|
||||
for field in ("icon-image",):
|
||||
if field in layout:
|
||||
walk(layout[field])
|
||||
for field in ("fill-pattern", "line-pattern"):
|
||||
if field in paint:
|
||||
walk(paint[field])
|
||||
return refs
|
||||
|
||||
|
||||
def build_sprite(config: dict[str, Any], style: dict[str, Any]) -> dict[str, Any]:
|
||||
if SPRITE_TARGET.exists():
|
||||
shutil.rmtree(SPRITE_TARGET)
|
||||
SPRITE_TARGET.mkdir(parents=True)
|
||||
source_meta: dict[str, dict[str, dict[str, Any]]] = {}
|
||||
for filename in ("sprite.json", "sprite@2x.json"):
|
||||
source_meta[filename] = json.loads((SPRITE_SOURCE / filename).read_text(encoding="utf-8"))
|
||||
|
||||
key_map = dict(config["sprite_key_map"])
|
||||
key_map.update(STYLE_EXTRA_MAP)
|
||||
fallback_sources = {"lm_cus": "symbol-daytime-660"}
|
||||
passthrough_keys = collect_literal_sprite_refs(style)
|
||||
added: dict[str, str] = {}
|
||||
for filename, meta in source_meta.items():
|
||||
# 测试包只改名图标/灯弧;fill/line pattern 等非图标纹理必须按 style 原名保留。
|
||||
output: dict[str, dict[str, Any]] = {}
|
||||
data_driven_pattern_keys = {
|
||||
key
|
||||
for key in meta
|
||||
if (
|
||||
key.startswith("pattern_fill_")
|
||||
or key.startswith("fill-daytime-")
|
||||
or key.startswith("fill_")
|
||||
or key.startswith("special_pattern_")
|
||||
)
|
||||
}
|
||||
for key in sorted(passthrough_keys | data_driven_pattern_keys):
|
||||
if key in meta:
|
||||
output[key] = dict(meta[key])
|
||||
added[key] = key
|
||||
for old_key, new_key in key_map.items():
|
||||
source_key = old_key if old_key in meta else None
|
||||
if source_key is None:
|
||||
for candidate, candidate_new in key_map.items():
|
||||
if candidate_new == new_key and candidate in meta:
|
||||
source_key = candidate
|
||||
break
|
||||
if source_key is None:
|
||||
source_key = fallback_sources.get(new_key)
|
||||
if source_key and source_key in meta:
|
||||
output[new_key] = dict(meta[source_key])
|
||||
added[new_key] = source_key
|
||||
(SPRITE_TARGET / filename).write_text(
|
||||
json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
for filename in ("sprite.png", "sprite@2x.png"):
|
||||
shutil.copy2(SPRITE_SOURCE / filename, SPRITE_TARGET / filename)
|
||||
return {
|
||||
"added": added,
|
||||
"passthrough_keys": sorted(passthrough_keys),
|
||||
"fallbacks": {"lm_cus": fallback_sources["lm_cus"]},
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config = load_config()
|
||||
tiles = collect_tiles()
|
||||
if TARGET_ROOT.exists():
|
||||
raise RuntimeError(f"refusing to overwrite existing PBF target: {TARGET_ROOT}")
|
||||
TARGET_ROOT.mkdir(parents=True)
|
||||
|
||||
totals: Counter[str] = Counter()
|
||||
old_values: Counter[str] = Counter()
|
||||
unknown_old_values: Counter[str] = Counter()
|
||||
icon_counts: Counter[str] = Counter()
|
||||
arc_counts: Counter[str] = Counter()
|
||||
rewritten = 0
|
||||
candidate_needles = [
|
||||
key.encode("utf-8") for key in config["sprite_key_map"]
|
||||
] + [
|
||||
b"navigation_marks",
|
||||
b"navigation_hazard_point",
|
||||
b"anchor_caution_hazard_point",
|
||||
b"anchorage_point",
|
||||
b"facility_boundary_point",
|
||||
b"onshore_structure_point",
|
||||
]
|
||||
rewrite_jobs: list[tuple[str, str, dict[str, Any], bool]] = []
|
||||
candidate_count = 0
|
||||
for z, x, y, rel in tiles:
|
||||
source = SOURCE_ROOT / rel
|
||||
target = TARGET_ROOT / rel
|
||||
raw = source.read_bytes()
|
||||
if not any(needle in raw for needle in candidate_needles):
|
||||
copy_link(source, target)
|
||||
continue
|
||||
rewrite_jobs.append(
|
||||
(str(source), str(target), config, candidate_count < 3)
|
||||
)
|
||||
candidate_count += 1
|
||||
|
||||
worker_count = min(os.cpu_count() or 4, 12)
|
||||
with ProcessPoolExecutor(max_workers=worker_count) as executor:
|
||||
for result in executor.map(rewrite_tile_job, rewrite_jobs, chunksize=8):
|
||||
if result["changed_features"]:
|
||||
rewritten += 1
|
||||
totals["features_changed"] += result["changed_features"]
|
||||
old_values.update(result["old_values"])
|
||||
unknown_old_values.update(result["unknown_old_values"])
|
||||
icon_counts.update(result["icon_counts"])
|
||||
arc_counts.update(result["arc_counts"])
|
||||
|
||||
style = transform_style(config)
|
||||
style["metadata"]["navsea_test_version"] = ASSET_VERSION
|
||||
style["sprite"] = SPRITE_URL
|
||||
sprite = build_sprite(config, style)
|
||||
write_style(style, STYLE_OUTPUT, STYLE_DEPLOY)
|
||||
write_newpec_style()
|
||||
|
||||
REPORT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
summary = {
|
||||
"version": "kyushu-semantic-icon-v1-20260806",
|
||||
"source_root": str(SOURCE_ROOT),
|
||||
"target_root": str(TARGET_ROOT),
|
||||
"bbox": KYUSHU_BBOX,
|
||||
"zooms": list(ZOOMS),
|
||||
"tiles": len(tiles),
|
||||
"tiles_rewritten": rewritten,
|
||||
"candidate_tiles": candidate_count,
|
||||
**totals,
|
||||
"old_values": dict(old_values),
|
||||
"unknown_old_values_after_rewrite": dict(unknown_old_values),
|
||||
"icon_counts": dict(icon_counts),
|
||||
"arc_counts": dict(arc_counts),
|
||||
"sprite": sprite,
|
||||
"style": str(STYLE_OUTPUT),
|
||||
"newpec_style": str(NEWPEC_STYLE_OUTPUT),
|
||||
}
|
||||
(REPORT_DIR / "kyushu_semantic_icon_v1.json").write_text(
|
||||
json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
lines = [
|
||||
"# 九州语义图标替换测试版 v1",
|
||||
"",
|
||||
f"- PBF 来源:`{SOURCE_ROOT}`",
|
||||
f"- PBF 输出:`{TARGET_ROOT}`",
|
||||
f"- 范围:`{KYUSHU_BBOX}`",
|
||||
f"- zoom:`{ZOOMS[0]}-{ZOOMS[-1]}`",
|
||||
f"- 瓦片数:`{len(tiles)}`",
|
||||
f"- 重写瓦片:`{rewritten}`",
|
||||
f"- 改动 feature:`{totals['features_changed']}`",
|
||||
f"- sprite:`{SPRITE_TARGET}`",
|
||||
f"- delivery style:`{STYLE_OUTPUT}`",
|
||||
f"- newpec style:`{NEWPEC_STYLE_OUTPUT}`",
|
||||
"",
|
||||
"## 旧图标值",
|
||||
"",
|
||||
"```json",
|
||||
json.dumps(dict(old_values), ensure_ascii=False, indent=2),
|
||||
"```",
|
||||
"",
|
||||
"## 审计关注",
|
||||
"",
|
||||
"- `unknown_old_values_after_rewrite` 必须为空。",
|
||||
"- `lm_cus` 当前使用 `symbol-daytime-660` 的临时视觉回退,需人工确认。",
|
||||
"- 几何使用原 tile extent 重编码,并执行 extent 安全检查。",
|
||||
]
|
||||
(REPORT_DIR / "kyushu_semantic_icon_v1.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
82
navsea_extent_shift_audit.py
Normal file
82
navsea_extent_shift_audit.py
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from navsea_tile_reencode_guard import validate_reencoded_tile
|
||||
|
||||
|
||||
DEFAULT_SOURCE_ROOT = Path(
|
||||
"/home/wwwroot/newpec/exported_auto/"
|
||||
"tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles"
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="审计 PBF 是否存在高 extent 被 4096 口径重编码的几何错移。")
|
||||
parser.add_argument("--target-root", type=Path, required=True)
|
||||
parser.add_argument("--source-root", type=Path, default=DEFAULT_SOURCE_ROOT)
|
||||
parser.add_argument("--output-json", type=Path)
|
||||
parser.add_argument("--zoom", action="append", type=int)
|
||||
parser.add_argument("--sample-limit", type=int, default=50)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def should_include(rel: Path, zoom_filter: set[int] | None) -> bool:
|
||||
if not zoom_filter:
|
||||
return True
|
||||
try:
|
||||
return int(rel.parts[0]) in zoom_filter
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
zoom_filter = set(args.zoom or [])
|
||||
suspicious_tiles: list[dict[str, Any]] = []
|
||||
checked = 0
|
||||
|
||||
for tile_path in sorted(args.target_root.glob("*/*/*.pbf")):
|
||||
rel = tile_path.relative_to(args.target_root)
|
||||
if not should_include(rel, zoom_filter or None):
|
||||
continue
|
||||
source_tile = args.source_root / rel
|
||||
if not source_tile.exists():
|
||||
continue
|
||||
checked += 1
|
||||
verdict = validate_reencoded_tile(source_tile, tile_path)
|
||||
if verdict["suspicious"]:
|
||||
suspicious_tiles.append(
|
||||
{
|
||||
"tile": str(rel),
|
||||
**verdict,
|
||||
}
|
||||
)
|
||||
|
||||
suspicious_tiles.sort(
|
||||
key=lambda item: (
|
||||
item["output_max_overflow"] - item["source_max_overflow"],
|
||||
item["tile"],
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
summary = {
|
||||
"target_root": str(args.target_root),
|
||||
"source_root": str(args.source_root),
|
||||
"zoom_filter": sorted(zoom_filter),
|
||||
"tiles_checked": checked,
|
||||
"suspicious_tile_count": len(suspicious_tiles),
|
||||
"samples": suspicious_tiles[: args.sample_limit],
|
||||
}
|
||||
if args.output_json:
|
||||
args.output_json.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
227
navsea_full_aoi_visual_audit.py
Normal file
227
navsea_full_aoi_visual_audit.py
Normal file
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from PIL import Image, ImageChops, ImageStat
|
||||
|
||||
|
||||
DEFAULT_COMPARE_URL = "http://192.168.200.184/newpec/navsea-compare-full-audit.html"
|
||||
DEFAULT_AOI_JSON = Path("/root/sourceserver/pbf/tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json")
|
||||
DEFAULT_OUTPUT_DIR = Path("/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="对全国 full / semantic 做 AOI 截图 + 图像 diff 审计。")
|
||||
parser.add_argument("--compare-url", default=DEFAULT_COMPARE_URL)
|
||||
parser.add_argument("--aoi-json", type=Path, default=DEFAULT_AOI_JSON)
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
|
||||
parser.add_argument("--width", type=int, default=1280)
|
||||
parser.add_argument("--height", type=int, default=720)
|
||||
parser.add_argument("--toolbar-height", type=int, default=120)
|
||||
parser.add_argument("--bottom-trim", type=int, default=30)
|
||||
parser.add_argument("--timeout-sec", type=int, default=120)
|
||||
parser.add_argument("--virtual-time-budget-ms", type=int, default=30000)
|
||||
parser.add_argument("--variant", action="append", choices=("full", "semantic"), help="只跑指定 variant,可重复传入")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run_browser_screenshot(
|
||||
compare_url: str,
|
||||
output_png: Path,
|
||||
width: int,
|
||||
height: int,
|
||||
timeout_sec: int,
|
||||
virtual_time_budget_ms: int,
|
||||
) -> None:
|
||||
cmd = [
|
||||
"timeout",
|
||||
f"{timeout_sec}s",
|
||||
"google-chrome",
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--enable-unsafe-swiftshader",
|
||||
"--no-sandbox",
|
||||
f"--virtual-time-budget={virtual_time_budget_ms}",
|
||||
f"--window-size={width},{height}",
|
||||
f"--screenshot={output_png}",
|
||||
compare_url,
|
||||
]
|
||||
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||
|
||||
|
||||
def compute_visual_metrics(left_img: Image.Image, right_img: Image.Image) -> dict[str, Any]:
|
||||
diff = ImageChops.difference(left_img, right_img)
|
||||
diff_rgb = diff.convert("RGB")
|
||||
diff_gray = diff.convert("L")
|
||||
stat = ImageStat.Stat(diff_rgb)
|
||||
mean_abs = [round(v, 4) for v in stat.mean]
|
||||
rms = [round(v, 4) for v in stat.rms]
|
||||
bbox = diff_gray.getbbox()
|
||||
nonzero_pixels = 0
|
||||
if bbox:
|
||||
mask = diff_gray.point(lambda value: 255 if value else 0)
|
||||
histogram = mask.histogram()
|
||||
nonzero_pixels = histogram[255] if len(histogram) > 255 else 0
|
||||
total_pixels = left_img.size[0] * left_img.size[1]
|
||||
changed_ratio = round(nonzero_pixels / total_pixels, 6) if total_pixels else 0.0
|
||||
return {
|
||||
"changed_pixels": nonzero_pixels,
|
||||
"total_pixels": total_pixels,
|
||||
"changed_ratio": changed_ratio,
|
||||
"mean_abs_rgb": mean_abs,
|
||||
"rms_rgb": rms,
|
||||
"diff_bbox": list(bbox) if bbox else None,
|
||||
"diff_image": diff,
|
||||
}
|
||||
|
||||
|
||||
def crop_compare_panes(full_png: Path, left_png: Path, right_png: Path, diff_png: Path, toolbar_height: int, bottom_trim: int) -> dict[str, Any]:
|
||||
image = Image.open(full_png).convert("RGBA")
|
||||
usable_top = toolbar_height
|
||||
usable_bottom = max(usable_top + 1, image.height - bottom_trim)
|
||||
mid_x = image.width // 2
|
||||
left = image.crop((0, usable_top, mid_x, usable_bottom))
|
||||
right = image.crop((mid_x, usable_top, image.width, usable_bottom))
|
||||
left.save(left_png)
|
||||
right.save(right_png)
|
||||
|
||||
metrics = compute_visual_metrics(left, right)
|
||||
diff_image = metrics.pop("diff_image")
|
||||
diff_image.convert("RGB").point(lambda value: min(255, value * 4)).save(diff_png)
|
||||
metrics["left_crop"] = [0, usable_top, mid_x, usable_bottom]
|
||||
metrics["right_crop"] = [mid_x, usable_top, image.width, usable_bottom]
|
||||
return metrics
|
||||
|
||||
|
||||
def build_case_url(compare_url: str, variant: str, center: list[float], zoom: int | float) -> str:
|
||||
query = urlencode(
|
||||
{
|
||||
"variant": variant,
|
||||
"audit": "1",
|
||||
"center": f"{center[0]},{center[1]}",
|
||||
"zoom": zoom,
|
||||
}
|
||||
)
|
||||
separator = "&" if "?" in compare_url else "?"
|
||||
return f"{compare_url}{separator}{query}"
|
||||
|
||||
|
||||
def write_report(output_dir: Path, summary: dict[str, Any]) -> tuple[Path, Path]:
|
||||
report_json = output_dir / "full_aoi_visual_audit.json"
|
||||
report_md = output_dir / "full_aoi_visual_audit.md"
|
||||
report_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
lines = [
|
||||
"# 全国 AOI 截图对比审计",
|
||||
"",
|
||||
"## 总览",
|
||||
"",
|
||||
f"- compare page: `{summary['compare_url']}`",
|
||||
f"- cases: `{len(summary['cases'])}`",
|
||||
"",
|
||||
"## 差异排序",
|
||||
"",
|
||||
]
|
||||
for case in summary["cases"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"### {case['label']} · {case['variant']} · z{case['zoom']}",
|
||||
"",
|
||||
f"- changed ratio: `{case['changed_ratio']}`",
|
||||
f"- changed pixels: `{case['changed_pixels']}` / `{case['total_pixels']}`",
|
||||
f"- mean abs rgb: `{case['mean_abs_rgb']}`",
|
||||
f"- compare url: `{case['compare_url']}`",
|
||||
f"- left image: `{case['left_image']}`",
|
||||
f"- right image: `{case['right_image']}`",
|
||||
f"- diff image: `{case['diff_image']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
report_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return report_md, report_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
output_dir = args.output_dir
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
aois = json.loads(args.aoi_json.read_text(encoding="utf-8"))
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
variants = args.variant or ["full", "semantic"]
|
||||
for variant in variants:
|
||||
for aoi in aois:
|
||||
for zoom in aoi["zoom_levels"]:
|
||||
case_id = f"{aoi['id']}_{variant}_z{zoom}"
|
||||
case_dir = output_dir / case_id
|
||||
case_dir.mkdir(parents=True, exist_ok=True)
|
||||
compare_url = build_case_url(args.compare_url, variant, aoi["center"], zoom)
|
||||
full_png = case_dir / "compare_full.png"
|
||||
left_png = case_dir / "compare_left.png"
|
||||
right_png = case_dir / "compare_right.png"
|
||||
diff_png = case_dir / "compare_diff.png"
|
||||
|
||||
run_browser_screenshot(
|
||||
compare_url=compare_url,
|
||||
output_png=full_png,
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
timeout_sec=args.timeout_sec,
|
||||
virtual_time_budget_ms=args.virtual_time_budget_ms,
|
||||
)
|
||||
metrics = crop_compare_panes(
|
||||
full_png=full_png,
|
||||
left_png=left_png,
|
||||
right_png=right_png,
|
||||
diff_png=diff_png,
|
||||
toolbar_height=args.toolbar_height,
|
||||
bottom_trim=args.bottom_trim,
|
||||
)
|
||||
cases.append(
|
||||
{
|
||||
"id": case_id,
|
||||
"label": aoi["label"],
|
||||
"variant": variant,
|
||||
"zoom": zoom,
|
||||
"center": aoi["center"],
|
||||
"radius_nm": aoi["radius_nm"],
|
||||
"compare_url": compare_url,
|
||||
"changed_pixels": metrics["changed_pixels"],
|
||||
"total_pixels": metrics["total_pixels"],
|
||||
"changed_ratio": metrics["changed_ratio"],
|
||||
"mean_abs_rgb": metrics["mean_abs_rgb"],
|
||||
"rms_rgb": metrics["rms_rgb"],
|
||||
"diff_bbox": metrics["diff_bbox"],
|
||||
"left_image": str(left_png),
|
||||
"right_image": str(right_png),
|
||||
"diff_image": str(diff_png),
|
||||
}
|
||||
)
|
||||
|
||||
cases.sort(key=lambda item: item["changed_ratio"], reverse=True)
|
||||
summary = {
|
||||
"compare_url": args.compare_url,
|
||||
"cases": cases,
|
||||
}
|
||||
report_md, report_json = write_report(output_dir, summary)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"report_md": str(report_md),
|
||||
"report_json": str(report_json),
|
||||
"top_cases": cases[:5],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
368
navsea_geometry_native_audit.py
Normal file
368
navsea_geometry_native_audit.py
Normal file
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from collections import Counter, defaultdict
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from mapbox_vector_tile.Mapbox import vector_tile_pb2
|
||||
|
||||
|
||||
DEFAULT_SOURCE_ROOT = Path(
|
||||
"/home/wwwroot/newpec/exported_auto/"
|
||||
"tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LayerGeometryStats:
|
||||
point_count: int = 0
|
||||
min_x: int = 0
|
||||
max_x: int = 0
|
||||
min_y: int = 0
|
||||
max_y: int = 0
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="审计并按需修复 NavSea PBF 的几何 / extent / native 风险。")
|
||||
parser.add_argument("--target-root", type=Path, required=True)
|
||||
parser.add_argument("--label", required=True)
|
||||
parser.add_argument("--output-dir", type=Path, required=True)
|
||||
parser.add_argument("--source-root", type=Path, default=DEFAULT_SOURCE_ROOT)
|
||||
parser.add_argument("--apply-fix", action="store_true", help="若检测到 extent 与源瓦片不一致,则按源瓦片 extent 原地修复。")
|
||||
parser.add_argument("--sample-limit", type=int, default=30)
|
||||
parser.add_argument("--zoom", action="append", type=int, help="只审指定 zoom,可重复传入多次。")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def zigzag_decode(value: int) -> int:
|
||||
return (value >> 1) ^ (-(value & 1))
|
||||
|
||||
|
||||
def decode_feature_geometry(geometry: list[int]) -> LayerGeometryStats:
|
||||
stats = LayerGeometryStats()
|
||||
cursor_x = 0
|
||||
cursor_y = 0
|
||||
initialized = False
|
||||
idx = 0
|
||||
|
||||
while idx < len(geometry):
|
||||
command = geometry[idx]
|
||||
idx += 1
|
||||
command_id = command & 0x7
|
||||
command_count = command >> 3
|
||||
|
||||
if command_id in (1, 2): # MoveTo, LineTo
|
||||
for _ in range(command_count):
|
||||
if idx + 1 >= len(geometry):
|
||||
raise ValueError("geometry command truncated")
|
||||
cursor_x += zigzag_decode(geometry[idx])
|
||||
cursor_y += zigzag_decode(geometry[idx + 1])
|
||||
idx += 2
|
||||
|
||||
if not initialized:
|
||||
stats.min_x = stats.max_x = cursor_x
|
||||
stats.min_y = stats.max_y = cursor_y
|
||||
initialized = True
|
||||
else:
|
||||
stats.min_x = min(stats.min_x, cursor_x)
|
||||
stats.max_x = max(stats.max_x, cursor_x)
|
||||
stats.min_y = min(stats.min_y, cursor_y)
|
||||
stats.max_y = max(stats.max_y, cursor_y)
|
||||
stats.point_count += 1
|
||||
elif command_id == 7: # ClosePath
|
||||
continue
|
||||
else:
|
||||
raise ValueError(f"unsupported geometry command id={command_id}")
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def merge_layer_stats(existing: LayerGeometryStats | None, incoming: LayerGeometryStats) -> LayerGeometryStats:
|
||||
if existing is None or existing.point_count == 0:
|
||||
return incoming
|
||||
if incoming.point_count == 0:
|
||||
return existing
|
||||
return LayerGeometryStats(
|
||||
point_count=existing.point_count + incoming.point_count,
|
||||
min_x=min(existing.min_x, incoming.min_x),
|
||||
max_x=max(existing.max_x, incoming.max_x),
|
||||
min_y=min(existing.min_y, incoming.min_y),
|
||||
max_y=max(existing.max_y, incoming.max_y),
|
||||
)
|
||||
|
||||
|
||||
def analyze_tile(tile_path: Path) -> dict[str, Any]:
|
||||
tile = vector_tile_pb2.tile()
|
||||
tile.ParseFromString(tile_path.read_bytes())
|
||||
|
||||
layer_summaries: list[dict[str, Any]] = []
|
||||
extent_values: set[int] = set()
|
||||
tile_outside_extent = False
|
||||
tile_max_overflow = 0
|
||||
|
||||
for layer in tile.layers:
|
||||
extent = int(layer.extent or 4096)
|
||||
extent_values.add(extent)
|
||||
aggregate: LayerGeometryStats | None = None
|
||||
for feature in layer.features:
|
||||
geom_stats = decode_feature_geometry(list(feature.geometry))
|
||||
aggregate = merge_layer_stats(aggregate, geom_stats)
|
||||
|
||||
if aggregate is None:
|
||||
aggregate = LayerGeometryStats()
|
||||
|
||||
outside_extent = False
|
||||
overflow = 0
|
||||
if aggregate.point_count:
|
||||
overflow = max(
|
||||
0,
|
||||
-aggregate.min_x,
|
||||
-aggregate.min_y,
|
||||
aggregate.max_x - extent,
|
||||
aggregate.max_y - extent,
|
||||
)
|
||||
outside_extent = overflow > 0
|
||||
|
||||
tile_outside_extent = tile_outside_extent or outside_extent
|
||||
tile_max_overflow = max(tile_max_overflow, overflow)
|
||||
layer_summaries.append(
|
||||
{
|
||||
"name": layer.name,
|
||||
"extent": extent,
|
||||
"point_count": aggregate.point_count,
|
||||
"min_x": aggregate.min_x,
|
||||
"max_x": aggregate.max_x,
|
||||
"min_y": aggregate.min_y,
|
||||
"max_y": aggregate.max_y,
|
||||
"outside_extent": outside_extent,
|
||||
"overflow": overflow,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"extent_values": sorted(extent_values),
|
||||
"outside_extent": tile_outside_extent,
|
||||
"max_overflow": tile_max_overflow,
|
||||
"layers": layer_summaries,
|
||||
}
|
||||
|
||||
|
||||
def desired_extent_from_source(source_path: Path) -> int | None:
|
||||
if not source_path.exists():
|
||||
return None
|
||||
tile = vector_tile_pb2.tile()
|
||||
tile.ParseFromString(source_path.read_bytes())
|
||||
extents = sorted({int(layer.extent or 4096) for layer in tile.layers})
|
||||
if not extents:
|
||||
return None
|
||||
if len(extents) == 1:
|
||||
return extents[0]
|
||||
return max(extents)
|
||||
|
||||
|
||||
def repair_tile_extents(tile_path: Path, desired_extent: int) -> bool:
|
||||
tile = vector_tile_pb2.tile()
|
||||
raw = tile_path.read_bytes()
|
||||
tile.ParseFromString(raw)
|
||||
changed = False
|
||||
for layer in tile.layers:
|
||||
current = int(layer.extent or 4096)
|
||||
if current != desired_extent:
|
||||
layer.extent = desired_extent
|
||||
changed = True
|
||||
if changed:
|
||||
tile_path.write_bytes(tile.SerializeToString())
|
||||
return changed
|
||||
|
||||
|
||||
def hardlink_copy_tree(src_root: Path, dst_root: Path) -> None:
|
||||
if dst_root.exists():
|
||||
raise SystemExit(f"backup root already exists: {dst_root}")
|
||||
dst_root.mkdir(parents=True, exist_ok=True)
|
||||
os.system(f"cp -al {src_root}/. {dst_root}/")
|
||||
|
||||
|
||||
def write_report(
|
||||
output_dir: Path,
|
||||
label: str,
|
||||
target_root: Path,
|
||||
source_root: Path,
|
||||
summary: dict[str, Any],
|
||||
) -> tuple[Path, Path]:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
report_json = output_dir / "geometry_native_audit.json"
|
||||
report_md = output_dir / "geometry_native_audit.md"
|
||||
report_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
lines = [
|
||||
f"# {label} 几何 / Extent / Native 风险审计",
|
||||
"",
|
||||
"## 范围",
|
||||
"",
|
||||
f"- target root: `{target_root}`",
|
||||
f"- source root: `{source_root}`",
|
||||
f"- tiles: `{summary['tile_count']}`",
|
||||
f"- repaired tiles: `{summary['repaired_tiles']}`",
|
||||
"",
|
||||
"## 总体结论",
|
||||
"",
|
||||
f"- 坐标超当前 extent 的瓦片数:`{summary['outside_extent_tiles']}`",
|
||||
f"- 与源瓦片 extent 不一致的瓦片数:`{summary['source_extent_mismatch_tiles']}`",
|
||||
f"- 可直接按源 extent 修复的瓦片数:`{summary['fixable_from_source_tiles']}`",
|
||||
"",
|
||||
"## Zoom 汇总",
|
||||
"",
|
||||
]
|
||||
|
||||
for zoom in sorted(summary["zoom_summary"], key=int):
|
||||
item = summary["zoom_summary"][zoom]
|
||||
lines.extend(
|
||||
[
|
||||
f"### z{zoom}",
|
||||
"",
|
||||
f"- tiles: `{item['tile_count']}`",
|
||||
f"- outside extent: `{item['outside_extent_tiles']}`",
|
||||
f"- source mismatch: `{item['source_extent_mismatch_tiles']}`",
|
||||
f"- extent counts: `{item['extent_counts']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
lines.extend(["## 代表问题样本", ""])
|
||||
for item in summary["sample_tiles"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"### `{item['tile']}`",
|
||||
"",
|
||||
f"- extent values: `{item['extent_values']}`",
|
||||
f"- source extent: `{item['source_extent']}`",
|
||||
f"- outside extent: `{item['outside_extent']}`",
|
||||
f"- max overflow: `{item['max_overflow']}`",
|
||||
f"- fixable from source: `{item['fixable_from_source']}`",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
report_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return report_md, report_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
target_root = args.target_root
|
||||
source_root = args.source_root
|
||||
output_dir = args.output_dir
|
||||
|
||||
if not target_root.exists():
|
||||
raise SystemExit(f"target root not found: {target_root}")
|
||||
if not source_root.exists():
|
||||
raise SystemExit(f"source root not found: {source_root}")
|
||||
|
||||
zoom_summary: dict[str, dict[str, Any]] = defaultdict(
|
||||
lambda: {
|
||||
"tile_count": 0,
|
||||
"outside_extent_tiles": 0,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": Counter(),
|
||||
}
|
||||
)
|
||||
sample_tiles: list[dict[str, Any]] = []
|
||||
repaired_tiles = 0
|
||||
outside_extent_tiles = 0
|
||||
source_extent_mismatch_tiles = 0
|
||||
fixable_from_source_tiles = 0
|
||||
|
||||
tile_paths = sorted(target_root.glob("*/*/*.pbf"))
|
||||
if args.zoom:
|
||||
zoom_filter = {str(item) for item in args.zoom}
|
||||
tile_paths = [path for path in tile_paths if path.parent.parent.name in zoom_filter]
|
||||
for tile_path in tile_paths:
|
||||
rel = tile_path.relative_to(target_root)
|
||||
zoom = rel.parts[0]
|
||||
analysis = analyze_tile(tile_path)
|
||||
source_extent = desired_extent_from_source(source_root / rel)
|
||||
current_extent = max(analysis["extent_values"]) if analysis["extent_values"] else None
|
||||
mismatch = source_extent is not None and current_extent is not None and source_extent != current_extent
|
||||
fixable = bool(
|
||||
mismatch
|
||||
and source_extent is not None
|
||||
and analysis["outside_extent"]
|
||||
and analysis["max_overflow"] <= max(0, source_extent - (current_extent or 0))
|
||||
)
|
||||
|
||||
zoom_item = zoom_summary[zoom]
|
||||
zoom_item["tile_count"] += 1
|
||||
for extent in analysis["extent_values"]:
|
||||
zoom_item["extent_counts"][extent] += 1
|
||||
if analysis["outside_extent"]:
|
||||
outside_extent_tiles += 1
|
||||
zoom_item["outside_extent_tiles"] += 1
|
||||
if mismatch:
|
||||
source_extent_mismatch_tiles += 1
|
||||
zoom_item["source_extent_mismatch_tiles"] += 1
|
||||
if fixable:
|
||||
fixable_from_source_tiles += 1
|
||||
|
||||
if args.apply_fix and fixable and source_extent is not None:
|
||||
if repair_tile_extents(tile_path, source_extent):
|
||||
repaired_tiles += 1
|
||||
analysis = analyze_tile(tile_path)
|
||||
|
||||
if len(sample_tiles) < args.sample_limit and (analysis["outside_extent"] or mismatch):
|
||||
sample_tiles.append(
|
||||
{
|
||||
"tile": str(rel),
|
||||
"extent_values": analysis["extent_values"],
|
||||
"source_extent": source_extent,
|
||||
"outside_extent": analysis["outside_extent"],
|
||||
"max_overflow": analysis["max_overflow"],
|
||||
"fixable_from_source": fixable,
|
||||
}
|
||||
)
|
||||
|
||||
normalized_zoom_summary = {
|
||||
zoom: {
|
||||
"tile_count": item["tile_count"],
|
||||
"outside_extent_tiles": item["outside_extent_tiles"],
|
||||
"source_extent_mismatch_tiles": item["source_extent_mismatch_tiles"],
|
||||
"extent_counts": dict(sorted(item["extent_counts"].items())),
|
||||
}
|
||||
for zoom, item in sorted(zoom_summary.items(), key=lambda kv: int(kv[0]))
|
||||
}
|
||||
|
||||
summary = {
|
||||
"label": args.label,
|
||||
"target_root": str(target_root),
|
||||
"source_root": str(source_root),
|
||||
"tile_count": len(tile_paths),
|
||||
"outside_extent_tiles": outside_extent_tiles,
|
||||
"source_extent_mismatch_tiles": source_extent_mismatch_tiles,
|
||||
"fixable_from_source_tiles": fixable_from_source_tiles,
|
||||
"repaired_tiles": repaired_tiles,
|
||||
"zoom_summary": normalized_zoom_summary,
|
||||
"sample_tiles": sample_tiles,
|
||||
}
|
||||
report_md, report_json = write_report(output_dir, args.label, target_root, source_root, summary)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"report_md": str(report_md),
|
||||
"report_json": str(report_json),
|
||||
"outside_extent_tiles": outside_extent_tiles,
|
||||
"source_extent_mismatch_tiles": source_extent_mismatch_tiles,
|
||||
"fixable_from_source_tiles": fixable_from_source_tiles,
|
||||
"repaired_tiles": repaired_tiles,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
277
navsea_rebuild_hotspot_tiles.py
Normal file
277
navsea_rebuild_hotspot_tiles.py
Normal file
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import mercantile
|
||||
|
||||
from build_semantic_delivery_assets import build_mapping, load_audit_rows, remap_tile
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent
|
||||
SOURCE_TILE_ROOT = Path(
|
||||
"/home/wwwroot/newpec/exported_auto/"
|
||||
"tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles"
|
||||
)
|
||||
FULL_ROOT = Path("/home/wwwroot/pbf-delivery-full-20260415")
|
||||
SEMANTIC_ROOT = Path("/home/wwwroot/pbf-delivery-full-semantic-20260416")
|
||||
DEFAULT_AOI_JSON = REPO_ROOT / "tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json"
|
||||
DEFAULT_OUTPUT_DIR = REPO_ROOT / "report/full_hotspot_rebuild_2026-04-18"
|
||||
DEFAULT_TMP_ROOT = Path("/tmp/navsea_hotspot_rebuild")
|
||||
FID_KEY = "thisMyWorld@2026"
|
||||
BUNDLE_ID = "navsea-core"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="重建全国固定 AOI 热点 tile,修复旧坏几何。")
|
||||
parser.add_argument("--aoi-json", type=Path, default=DEFAULT_AOI_JSON)
|
||||
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
|
||||
parser.add_argument("--tmp-root", type=Path, default=DEFAULT_TMP_ROOT)
|
||||
parser.add_argument("--zoom", action="append", type=int, help="只修指定 zoom,可重复传入")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def aoi_bbox(center: list[float], radius_nm: float) -> tuple[float, float, float, float]:
|
||||
lng, lat = center
|
||||
radius_km = radius_nm * 1.852
|
||||
lat_delta = radius_km / 111.32
|
||||
lon_delta = radius_km / (111.32 * math.cos(math.radians(lat)))
|
||||
return (
|
||||
lng - lon_delta,
|
||||
lat - lat_delta,
|
||||
lng + lon_delta,
|
||||
lat + lat_delta,
|
||||
)
|
||||
|
||||
|
||||
def hardlink_or_copy(src: Path, dst: Path) -> None:
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if dst.exists():
|
||||
dst.unlink()
|
||||
try:
|
||||
dst.hardlink_to(src)
|
||||
except OSError:
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
|
||||
def collect_tiles(aoi_json: Path, zoom_filter: set[int] | None) -> tuple[list[dict], list[str]]:
|
||||
aois = json.loads(aoi_json.read_text(encoding="utf-8"))
|
||||
tile_map: dict[str, dict] = {}
|
||||
labels: list[str] = []
|
||||
for aoi in aois:
|
||||
labels.append(aoi["label"])
|
||||
west, south, east, north = aoi_bbox(aoi["center"], aoi["radius_nm"])
|
||||
for zoom in aoi["zoom_levels"]:
|
||||
if zoom_filter and zoom not in zoom_filter:
|
||||
continue
|
||||
for tile in mercantile.tiles(west, south, east, north, [zoom]):
|
||||
rel = Path(str(tile.z)) / str(tile.x) / f"{tile.y}.pbf"
|
||||
tile_map[str(rel)] = {
|
||||
"z": tile.z,
|
||||
"x": tile.x,
|
||||
"y": tile.y,
|
||||
"relative_path": str(rel),
|
||||
}
|
||||
tiles = sorted(tile_map.values(), key=lambda item: (item["z"], item["x"], item["y"]))
|
||||
return tiles, labels
|
||||
|
||||
|
||||
def prepare_reference_root(reference_root: Path, tiles: list[dict]) -> int:
|
||||
if reference_root.exists():
|
||||
shutil.rmtree(reference_root)
|
||||
prepared = 0
|
||||
for tile in tiles:
|
||||
rel = Path(tile["relative_path"])
|
||||
src = SOURCE_TILE_ROOT / rel
|
||||
if not src.exists():
|
||||
continue
|
||||
dst = reference_root / rel
|
||||
hardlink_or_copy(src, dst)
|
||||
prepared += 1
|
||||
return prepared
|
||||
|
||||
|
||||
def backup_existing_tiles(backup_root: Path, target_root: Path, tiles: list[dict]) -> int:
|
||||
if backup_root.exists():
|
||||
shutil.rmtree(backup_root)
|
||||
backed_up = 0
|
||||
for tile in tiles:
|
||||
rel = Path(tile["relative_path"])
|
||||
src = target_root / rel
|
||||
if not src.exists():
|
||||
continue
|
||||
dst = backup_root / rel
|
||||
hardlink_or_copy(src, dst)
|
||||
backed_up += 1
|
||||
return backed_up
|
||||
|
||||
|
||||
def run_full_rebuild(reference_root: Path, rebuild_root: Path, zoom_filter: set[int] | None) -> None:
|
||||
if rebuild_root.exists():
|
||||
shutil.rmtree(rebuild_root)
|
||||
cmd = [
|
||||
str(REPO_ROOT / ".venv/bin/python"),
|
||||
str(REPO_ROOT / "navsea_tile_builder.py"),
|
||||
"--reference-tile-root",
|
||||
str(reference_root),
|
||||
"--output",
|
||||
str(rebuild_root),
|
||||
"--workers",
|
||||
"1",
|
||||
"--strip-legacy-japanese-delivery",
|
||||
"--release-minimal",
|
||||
"--fid-key",
|
||||
FID_KEY,
|
||||
"--bundle-id",
|
||||
BUNDLE_ID,
|
||||
]
|
||||
if zoom_filter:
|
||||
cmd.extend(["--zmin", str(min(zoom_filter)), "--zmax", str(max(zoom_filter))])
|
||||
subprocess.run(cmd, check=True, cwd=REPO_ROOT)
|
||||
|
||||
|
||||
def deploy_full_tiles(rebuild_root: Path, target_root: Path, tiles: list[dict]) -> int:
|
||||
deployed = 0
|
||||
for tile in tiles:
|
||||
rel = Path(tile["relative_path"])
|
||||
src = rebuild_root / rel
|
||||
if not src.exists():
|
||||
continue
|
||||
hardlink_or_copy(src, target_root / rel)
|
||||
deployed += 1
|
||||
return deployed
|
||||
|
||||
|
||||
def deploy_semantic_tiles(rebuild_root: Path, target_root: Path, tiles: list[dict]) -> dict[str, int]:
|
||||
rows = load_audit_rows()
|
||||
mapping = build_mapping(rows)
|
||||
value_map = {
|
||||
row.old_sprite_key: mapping[row.old_sprite_key]
|
||||
for row in rows
|
||||
if row.used_in_pbf > 0 and row.old_sprite_key in mapping
|
||||
}
|
||||
needles = [old.encode("utf-8") for old in value_map]
|
||||
|
||||
stats = {
|
||||
"tiles_total": 0,
|
||||
"tiles_written": 0,
|
||||
"tiles_semantic_rewritten": 0,
|
||||
"features_semantic_changed": 0,
|
||||
}
|
||||
for tile in tiles:
|
||||
rel = Path(tile["relative_path"])
|
||||
src = rebuild_root / rel
|
||||
if not src.exists():
|
||||
continue
|
||||
dst = target_root / rel
|
||||
changed, feature_count = remap_tile(src, dst, value_map, needles)
|
||||
stats["tiles_total"] += 1
|
||||
stats["tiles_written"] += 1
|
||||
if changed:
|
||||
stats["tiles_semantic_rewritten"] += 1
|
||||
stats["features_semantic_changed"] += feature_count
|
||||
return stats
|
||||
|
||||
|
||||
def write_report(output_dir: Path, summary: dict) -> tuple[Path, Path]:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
report_json = output_dir / "full_hotspot_rebuild.json"
|
||||
report_md = output_dir / "full_hotspot_rebuild.md"
|
||||
report_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
lines = [
|
||||
"# 全国热点 AOI 旧坏 Tile 重建修复",
|
||||
"",
|
||||
"## 范围",
|
||||
"",
|
||||
f"- AOI 配置:`{summary['aoi_json']}`",
|
||||
f"- AOI 标签:`{', '.join(summary['aoi_labels'])}`",
|
||||
f"- zoom:`{summary['zoom_filter'] or '按 AOI 原配置'}`",
|
||||
f"- 参考源:`{summary['source_root']}`",
|
||||
f"- full 目标:`{summary['full_root']}`",
|
||||
f"- semantic 目标:`{summary['semantic_root']}`",
|
||||
"",
|
||||
"## 数量",
|
||||
"",
|
||||
f"- 热点 tile 数:`{summary['tile_count']}`",
|
||||
f"- 参考 tile 准备:`{summary['prepared_reference_tiles']}`",
|
||||
f"- full 备份:`{summary['full_backup_tiles']}`",
|
||||
f"- semantic 备份:`{summary['semantic_backup_tiles']}`",
|
||||
f"- full 部署:`{summary['full_deployed_tiles']}`",
|
||||
f"- semantic 部署:`{summary['semantic_stats']['tiles_written']}`",
|
||||
f"- semantic 重写:`{summary['semantic_stats']['tiles_semantic_rewritten']}`",
|
||||
f"- semantic 改动 feature:`{summary['semantic_stats']['features_semantic_changed']}`",
|
||||
"",
|
||||
"## 备份目录",
|
||||
"",
|
||||
f"- full backup: `{summary['full_backup_root']}`",
|
||||
f"- semantic backup: `{summary['semantic_backup_root']}`",
|
||||
"",
|
||||
"## 说明",
|
||||
"",
|
||||
"- 这轮不是单纯回写 extent,而是用当前 builder 对固定热点 tile 重新生成 full 包。",
|
||||
"- semantic 包再以重建后的 full tile 为底稿,按当前语义 sprite 映射规则重写相关属性。",
|
||||
"- 目标是先消除全国审计热点里的旧坏几何与大片空白问题。",
|
||||
"",
|
||||
]
|
||||
report_md.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return report_md, report_json
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
zoom_filter = set(args.zoom or [])
|
||||
tiles, aoi_labels = collect_tiles(args.aoi_json, zoom_filter or None)
|
||||
|
||||
tmp_root = args.tmp_root
|
||||
reference_root = tmp_root / "reference_tiles"
|
||||
rebuild_root = tmp_root / "rebuild_full"
|
||||
full_backup_root = tmp_root / "backup_full"
|
||||
semantic_backup_root = tmp_root / "backup_semantic"
|
||||
|
||||
prepared_reference_tiles = prepare_reference_root(reference_root, tiles)
|
||||
full_backup_tiles = backup_existing_tiles(full_backup_root, FULL_ROOT, tiles)
|
||||
semantic_backup_tiles = backup_existing_tiles(semantic_backup_root, SEMANTIC_ROOT, tiles)
|
||||
run_full_rebuild(reference_root, rebuild_root, zoom_filter or None)
|
||||
full_deployed_tiles = deploy_full_tiles(rebuild_root, FULL_ROOT, tiles)
|
||||
semantic_stats = deploy_semantic_tiles(rebuild_root, SEMANTIC_ROOT, tiles)
|
||||
|
||||
summary = {
|
||||
"aoi_json": str(args.aoi_json),
|
||||
"aoi_labels": aoi_labels,
|
||||
"zoom_filter": sorted(zoom_filter),
|
||||
"source_root": str(SOURCE_TILE_ROOT),
|
||||
"full_root": str(FULL_ROOT),
|
||||
"semantic_root": str(SEMANTIC_ROOT),
|
||||
"tile_count": len(tiles),
|
||||
"prepared_reference_tiles": prepared_reference_tiles,
|
||||
"full_backup_root": str(full_backup_root),
|
||||
"semantic_backup_root": str(semantic_backup_root),
|
||||
"full_backup_tiles": full_backup_tiles,
|
||||
"semantic_backup_tiles": semantic_backup_tiles,
|
||||
"full_deployed_tiles": full_deployed_tiles,
|
||||
"semantic_stats": semantic_stats,
|
||||
}
|
||||
report_md, report_json = write_report(args.output_dir, summary)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"report_md": str(report_md),
|
||||
"report_json": str(report_json),
|
||||
"tile_count": len(tiles),
|
||||
"full_deployed_tiles": full_deployed_tiles,
|
||||
"semantic_stats": semantic_stats,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
77
navsea_tile_reencode_guard.py
Normal file
77
navsea_tile_reencode_guard.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import mapbox_vector_tile
|
||||
|
||||
from navsea_geometry_native_audit import analyze_tile
|
||||
|
||||
|
||||
def decode_tile_with_extents(raw: bytes) -> tuple[dict[str, Any], dict[str, int]]:
|
||||
decoded = mapbox_vector_tile.decode(raw)
|
||||
extents = {
|
||||
layer_name: int(layer.get("extent") or 4096)
|
||||
for layer_name, layer in decoded.items()
|
||||
}
|
||||
return decoded, extents
|
||||
|
||||
|
||||
def encode_layers_preserving_extents(
|
||||
encoded_layers: list[dict[str, Any]],
|
||||
layer_extents: dict[str, int],
|
||||
) -> bytes:
|
||||
per_layer_options = {
|
||||
layer["name"]: {"extents": int(layer_extents[layer["name"]])}
|
||||
for layer in encoded_layers
|
||||
}
|
||||
return mapbox_vector_tile.encode(encoded_layers, per_layer_options=per_layer_options)
|
||||
|
||||
|
||||
def validate_reencoded_tile(
|
||||
source_tile_path: Path,
|
||||
output_tile_path: Path,
|
||||
*,
|
||||
tolerance: int = 4096,
|
||||
) -> dict[str, Any]:
|
||||
source_analysis = analyze_tile(source_tile_path)
|
||||
output_analysis = analyze_tile(output_tile_path)
|
||||
|
||||
source_extent = max(source_analysis["extent_values"]) if source_analysis["extent_values"] else 4096
|
||||
output_extent = max(output_analysis["extent_values"]) if output_analysis["extent_values"] else 4096
|
||||
source_overflow = int(source_analysis["max_overflow"])
|
||||
output_overflow = int(output_analysis["max_overflow"])
|
||||
suspicious_extent_shift = (
|
||||
output_extent > 4096
|
||||
and output_overflow >= source_overflow + (output_extent - 4096) - tolerance
|
||||
)
|
||||
suspicious = output_overflow > source_overflow + tolerance or suspicious_extent_shift
|
||||
|
||||
return {
|
||||
"source_extent_values": source_analysis["extent_values"],
|
||||
"output_extent_values": output_analysis["extent_values"],
|
||||
"source_max_overflow": source_overflow,
|
||||
"output_max_overflow": output_overflow,
|
||||
"suspicious_extent_shift": suspicious_extent_shift,
|
||||
"suspicious": suspicious,
|
||||
}
|
||||
|
||||
|
||||
def assert_reencoded_tile_safe(
|
||||
source_tile_path: Path,
|
||||
output_tile_path: Path,
|
||||
*,
|
||||
tolerance: int = 4096,
|
||||
) -> None:
|
||||
verdict = validate_reencoded_tile(source_tile_path, output_tile_path, tolerance=tolerance)
|
||||
if verdict["suspicious"]:
|
||||
raise RuntimeError(
|
||||
"reencoded tile geometry drift detected: "
|
||||
f"source={source_tile_path} output={output_tile_path} "
|
||||
f"source_extent={verdict['source_extent_values']} "
|
||||
f"output_extent={verdict['output_extent_values']} "
|
||||
f"source_overflow={verdict['source_max_overflow']} "
|
||||
f"output_overflow={verdict['output_max_overflow']} "
|
||||
f"suspicious_extent_shift={verdict['suspicious_extent_shift']}"
|
||||
)
|
||||
95
report/REPORT_INDEX_2026-04-18.md
Normal file
95
report/REPORT_INDEX_2026-04-18.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 审计报告索引
|
||||
|
||||
最后更新:`2026-04-18`
|
||||
|
||||
## 最近 3 天最重要的结论
|
||||
|
||||
### 1. 根因报告
|
||||
|
||||
- [`report/full_extent_shift_root_cause_2026-04-18.md`](/root/sourceserver/pbf/report/full_extent_shift_root_cause_2026-04-18.md)
|
||||
- 结论:
|
||||
- 全国 full / semantic 的大块空白、岛礁错位,根因不是图标样式本身
|
||||
- 真正问题是高 `extent = 1048576` tile 被按 `4096` 口径重编码,导致几何整体错移
|
||||
|
||||
### 2. extent / native 修复
|
||||
|
||||
- [`report/full_geometry_native_extent_fix_2026-04-18.md`](/root/sourceserver/pbf/report/full_geometry_native_extent_fix_2026-04-18.md)
|
||||
- 结论:
|
||||
- 全国 `z12` 中存在一批 `extent = 4096` 错退片
|
||||
- 已按 source tile 口径回写到 `1048576`
|
||||
- 解决的是明确的几何 / metadata 风险,不等于视觉完全一致
|
||||
|
||||
### 3. 热点重建
|
||||
|
||||
- [`report/full_hotspot_rebuild_2026-04-18/full_hotspot_rebuild.md`](/root/sourceserver/pbf/report/full_hotspot_rebuild_2026-04-18/full_hotspot_rebuild.md)
|
||||
- 结论:
|
||||
- 固定 AOI 热点共重建 `154` 张 tile
|
||||
- full 重建后,再以 full 为底稿刷新 semantic
|
||||
- 这一步先把历史坏 tile 从热点审计面上剥离出去
|
||||
|
||||
### 4. 热点视觉验证
|
||||
|
||||
- [`report/karatsu_hotspot_verify_2026-04-18/full_aoi_visual_audit.md`](/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/full_aoi_visual_audit.md)
|
||||
- 结论:
|
||||
- 唐津热点的极端空白问题已经明显收敛
|
||||
- 但 full / semantic 相对原始 newpec 仍存在较大整体视觉差异
|
||||
|
||||
### 5. 全国 AOI 截图对比
|
||||
|
||||
- [`report/full_aoi_visual_audit_2026-04-18_r1/full_aoi_visual_audit.md`](/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/full_aoi_visual_audit.md)
|
||||
- [`report/full_aoi_visual_audit_2026-04-18_r2_subset/full_aoi_visual_audit.md`](/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r2_subset/full_aoi_visual_audit.md)
|
||||
- 结论:
|
||||
- 固定 AOI 为:
|
||||
- 博多港中心 10 海里
|
||||
- 东京湾 10 海里
|
||||
- 大阪 10 海里
|
||||
- 冲绳 10 海里
|
||||
- 唐津 5 海里
|
||||
- 重点 zoom:
|
||||
- `10`
|
||||
- `12`
|
||||
- 当前高 diff 主要集中在 z12
|
||||
|
||||
### 6. 过程型补充报告
|
||||
|
||||
- [`report/full_rebuild_staging_okinawa_z12_2026-04-18/full_aoi_visual_audit.md`](/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/full_aoi_visual_audit.md)
|
||||
- [`report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/full_aoi_visual_audit.md`](/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/full_aoi_visual_audit.md)
|
||||
- [`report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/full_aoi_visual_audit.md`](/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/full_aoi_visual_audit.md)
|
||||
- 结论:
|
||||
- staging 版先证明了新 rebuild 的 z12 几何不再炸成大片空白
|
||||
- 剩余差异更偏向整体视觉表达,而不是旧坏片继续扩散
|
||||
|
||||
## 审计方法总表
|
||||
|
||||
### 1. 物体保全审计
|
||||
|
||||
- 脚本:
|
||||
- [`navsea_object_preservation_audit.py`](/root/sourceserver/pbf/navsea_object_preservation_audit.py)
|
||||
- 关键词:
|
||||
- `--fid-key 'thisMyWorld@2026'`
|
||||
- `--match-on-fid-only`
|
||||
|
||||
### 2. render 审计
|
||||
|
||||
- 脚本:
|
||||
- [`navsea_render_audit.py`](/root/sourceserver/pbf/navsea_render_audit.py)
|
||||
|
||||
### 3. AOI 截图对比审计
|
||||
|
||||
- 脚本:
|
||||
- [`navsea_full_aoi_visual_audit.py`](/root/sourceserver/pbf/navsea_full_aoi_visual_audit.py)
|
||||
- 页面:
|
||||
- [`src/pbf/navsea-compare-full-audit.html`](/root/sourceserver/pbf/src/pbf/navsea-compare-full-audit.html)
|
||||
|
||||
### 4. extent / native 风险审计
|
||||
|
||||
- 脚本:
|
||||
- [`navsea_geometry_native_audit.py`](/root/sourceserver/pbf/navsea_geometry_native_audit.py)
|
||||
- [`navsea_extent_shift_audit.py`](/root/sourceserver/pbf/navsea_extent_shift_audit.py)
|
||||
- [`navsea_tile_reencode_guard.py`](/root/sourceserver/pbf/navsea_tile_reencode_guard.py)
|
||||
|
||||
### 5. 热点重建审计
|
||||
|
||||
- 脚本:
|
||||
- [`navsea_rebuild_hotspot_tiles.py`](/root/sourceserver/pbf/navsea_rebuild_hotspot_tiles.py)
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html",
|
||||
"cases": [
|
||||
{
|
||||
"id": "okinawa_10nm_full_z12",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=12",
|
||||
"changed_pixels": 314441,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.861954,
|
||||
"mean_abs_rgb": [
|
||||
40.364,
|
||||
41.3728,
|
||||
55.3433
|
||||
],
|
||||
"rms_rgb": [
|
||||
67.4457,
|
||||
66.8189,
|
||||
83.1026
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_full_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 313371,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.859021,
|
||||
"mean_abs_rgb": [
|
||||
32.3459,
|
||||
33.3448,
|
||||
50.9619
|
||||
],
|
||||
"rms_rgb": [
|
||||
53.1806,
|
||||
51.4741,
|
||||
72.4363
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm_semantic_z12",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=12",
|
||||
"changed_pixels": 310142,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.85017,
|
||||
"mean_abs_rgb": [
|
||||
40.3146,
|
||||
41.3705,
|
||||
55.2069
|
||||
],
|
||||
"rms_rgb": [
|
||||
67.4717,
|
||||
66.8981,
|
||||
83.0146
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_semantic_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 309067,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.847223,
|
||||
"mean_abs_rgb": [
|
||||
32.2753,
|
||||
33.2837,
|
||||
50.6801
|
||||
],
|
||||
"rms_rgb": [
|
||||
53.1618,
|
||||
51.4769,
|
||||
72.184
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_full_z10",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=10",
|
||||
"changed_pixels": 267772,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.734024,
|
||||
"mean_abs_rgb": [
|
||||
25.3191,
|
||||
24.6696,
|
||||
50.8948
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.688,
|
||||
39.9594,
|
||||
68.802
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_semantic_z10",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=10",
|
||||
"changed_pixels": 263392,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.722018,
|
||||
"mean_abs_rgb": [
|
||||
24.5744,
|
||||
23.762,
|
||||
48.4161
|
||||
],
|
||||
"rms_rgb": [
|
||||
42.989,
|
||||
38.9809,
|
||||
66.1666
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_full_z12",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=12",
|
||||
"changed_pixels": 242549,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.664882,
|
||||
"mean_abs_rgb": [
|
||||
21.7351,
|
||||
25.7701,
|
||||
23.1903
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.1671,
|
||||
46.1494,
|
||||
50.8333
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_semantic_z12",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=12",
|
||||
"changed_pixels": 235968,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.646842,
|
||||
"mean_abs_rgb": [
|
||||
21.5178,
|
||||
25.5206,
|
||||
22.6922
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.901,
|
||||
45.7039,
|
||||
50.198
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_full_z10",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=10",
|
||||
"changed_pixels": 233090,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.638953,
|
||||
"mean_abs_rgb": [
|
||||
22.602,
|
||||
21.9769,
|
||||
43.8265
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.1938,
|
||||
39.2381,
|
||||
64.4699
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_semantic_z10",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=10",
|
||||
"changed_pixels": 226222,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.620126,
|
||||
"mean_abs_rgb": [
|
||||
22.2492,
|
||||
21.4661,
|
||||
42.4354
|
||||
],
|
||||
"rms_rgb": [
|
||||
42.9444,
|
||||
38.7677,
|
||||
62.857
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_full_z10",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=10",
|
||||
"changed_pixels": 208628,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.571897,
|
||||
"mean_abs_rgb": [
|
||||
21.5068,
|
||||
21.8381,
|
||||
37.6819
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.9016,
|
||||
42.4239,
|
||||
60.7869
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm_semantic_z10",
|
||||
"label": "东京湾 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
139.85,
|
||||
35.42
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=10",
|
||||
"changed_pixels": 208306,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.571014,
|
||||
"mean_abs_rgb": [
|
||||
21.3378,
|
||||
21.3679,
|
||||
37.1671
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.6894,
|
||||
41.6284,
|
||||
60.1436
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_full_z12",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=12",
|
||||
"changed_pixels": 194689,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.533687,
|
||||
"mean_abs_rgb": [
|
||||
20.7021,
|
||||
21.5285,
|
||||
26.4928
|
||||
],
|
||||
"rms_rgb": [
|
||||
45.2912,
|
||||
45.6054,
|
||||
50.3669
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_full_z10",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=10",
|
||||
"changed_pixels": 194451,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.533035,
|
||||
"mean_abs_rgb": [
|
||||
21.0579,
|
||||
20.6348,
|
||||
35.3722
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.3815,
|
||||
41.2946,
|
||||
58.6624
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_full_z12",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=12",
|
||||
"changed_pixels": 194138,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.532177,
|
||||
"mean_abs_rgb": [
|
||||
23.4548,
|
||||
25.0792,
|
||||
21.5488
|
||||
],
|
||||
"rms_rgb": [
|
||||
49.0486,
|
||||
49.6832,
|
||||
54.94
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_semantic_z12",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=12",
|
||||
"changed_pixels": 193860,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.531414,
|
||||
"mean_abs_rgb": [
|
||||
20.125,
|
||||
20.7366,
|
||||
25.1796
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.4182,
|
||||
44.2282,
|
||||
48.4008
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm_full_z10",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=10",
|
||||
"changed_pixels": 192396,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.527401,
|
||||
"mean_abs_rgb": [
|
||||
19.8231,
|
||||
18.7544,
|
||||
25.9994
|
||||
],
|
||||
"rms_rgb": [
|
||||
43.3599,
|
||||
39.8588,
|
||||
51.1734
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_semantic_z10",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=10",
|
||||
"changed_pixels": 191439,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.524778,
|
||||
"mean_abs_rgb": [
|
||||
20.8376,
|
||||
20.016,
|
||||
34.5041
|
||||
],
|
||||
"rms_rgb": [
|
||||
44.2009,
|
||||
40.5148,
|
||||
57.712
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm_semantic_z12",
|
||||
"label": "大阪 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
135.35,
|
||||
34.61
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=12",
|
||||
"changed_pixels": 187180,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.513103,
|
||||
"mean_abs_rgb": [
|
||||
23.438,
|
||||
24.8296,
|
||||
21.3988
|
||||
],
|
||||
"rms_rgb": [
|
||||
49.1511,
|
||||
49.5348,
|
||||
54.9121
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm_semantic_z10",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=10",
|
||||
"changed_pixels": 186366,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.510872,
|
||||
"mean_abs_rgb": [
|
||||
19.3335,
|
||||
17.9048,
|
||||
24.0201
|
||||
],
|
||||
"rms_rgb": [
|
||||
42.9399,
|
||||
38.9411,
|
||||
48.1207
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-full-audit.html`
|
||||
- cases: `20`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 冲绳 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.861954`
|
||||
- changed pixels: `314441` / `364800`
|
||||
- mean abs rgb: `[40.364, 41.3728, 55.3433]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.859021`
|
||||
- changed pixels: `313371` / `364800`
|
||||
- mean abs rgb: `[32.3459, 33.3448, 50.9619]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z12/compare_diff.png`
|
||||
|
||||
### 冲绳 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.85017`
|
||||
- changed pixels: `310142` / `364800`
|
||||
- mean abs rgb: `[40.3146, 41.3705, 55.2069]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.847223`
|
||||
- changed pixels: `309067` / `364800`
|
||||
- mean abs rgb: `[32.2753, 33.2837, 50.6801]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.734024`
|
||||
- changed pixels: `267772` / `364800`
|
||||
- mean abs rgb: `[25.3191, 24.6696, 50.8948]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_full_z10/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.722018`
|
||||
- changed pixels: `263392` / `364800`
|
||||
- mean abs rgb: `[24.5744, 23.762, 48.4161]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/karatsu_5nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.664882`
|
||||
- changed pixels: `242549` / `364800`
|
||||
- mean abs rgb: `[21.7351, 25.7701, 23.1903]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.646842`
|
||||
- changed pixels: `235968` / `364800`
|
||||
- mean abs rgb: `[21.5178, 25.5206, 22.6922]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.638953`
|
||||
- changed pixels: `233090` / `364800`
|
||||
- mean abs rgb: `[22.602, 21.9769, 43.8265]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.620126`
|
||||
- changed pixels: `226222` / `364800`
|
||||
- mean abs rgb: `[22.2492, 21.4661, 42.4354]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.571897`
|
||||
- changed pixels: `208628` / `364800`
|
||||
- mean abs rgb: `[21.5068, 21.8381, 37.6819]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=139.85%2C35.42&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 东京湾 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.571014`
|
||||
- changed pixels: `208306` / `364800`
|
||||
- mean abs rgb: `[21.3378, 21.3679, 37.1671]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=139.85%2C35.42&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/tokyo_bay_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.533687`
|
||||
- changed pixels: `194689` / `364800`
|
||||
- mean abs rgb: `[20.7021, 21.5285, 26.4928]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=130.335%2C33.6385&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.533035`
|
||||
- changed pixels: `194451` / `364800`
|
||||
- mean abs rgb: `[21.0579, 20.6348, 35.3722]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.532177`
|
||||
- changed pixels: `194138` / `364800`
|
||||
- mean abs rgb: `[23.4548, 25.0792, 21.5488]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=135.35%2C34.61&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_full_z12/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.531414`
|
||||
- changed pixels: `193860` / `364800`
|
||||
- mean abs rgb: `[20.125, 20.7366, 25.1796]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/hakata_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 冲绳 10 海里 · full · z10
|
||||
|
||||
- changed ratio: `0.527401`
|
||||
- changed pixels: `192396` / `364800`
|
||||
- mean abs rgb: `[19.8231, 18.7544, 25.9994]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=127.67%2C26.21&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_full_z10/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.524778`
|
||||
- changed pixels: `191439` / `364800`
|
||||
- mean abs rgb: `[20.8376, 20.016, 34.5041]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
### 大阪 10 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.513103`
|
||||
- changed pixels: `187180` / `364800`
|
||||
- mean abs rgb: `[23.438, 24.8296, 21.3988]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=135.35%2C34.61&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/osaka_bay_10nm_semantic_z12/compare_diff.png`
|
||||
|
||||
### 冲绳 10 海里 · semantic · z10
|
||||
|
||||
- changed ratio: `0.510872`
|
||||
- changed pixels: `186366` / `364800`
|
||||
- mean abs rgb: `[19.3335, 17.9048, 24.0201]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=127.67%2C26.21&zoom=10`
|
||||
- left image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_aoi_visual_audit_2026-04-18_r1/okinawa_10nm_semantic_z10/compare_diff.png`
|
||||
|
||||
109
report/full_extent_shift_root_cause_2026-04-18.md
Normal file
109
report/full_extent_shift_root_cause_2026-04-18.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# 全国 full / semantic 高 extent 几何错移根因报告
|
||||
|
||||
## 1. 结论
|
||||
|
||||
- 当前全国 `full / semantic` 中反复出现的“大块空白 / 岛礁附近整块打穿 / 右侧大片缺失”问题
|
||||
- 根因不是 `style` 改图标本身
|
||||
- 真正根因是:
|
||||
- 某条会重写 `PBF` 的生产链路
|
||||
- 在处理原始 `extent = 1048576` 的高精度 tile 时
|
||||
- 用了 `4096` 口径重编码几何
|
||||
- 结果会把 tile 的几何整体错移,尤其是 `Y` 坐标整张 tile 下移一个:
|
||||
- `1048576 - 4096 = 1044480`
|
||||
量级的偏移
|
||||
|
||||
## 2. 证据
|
||||
|
||||
### 2.1 生产坏 tile 的数值签名
|
||||
|
||||
- 代表坏 tile:
|
||||
- `/tmp/navsea_hotspot_rebuild/backup_full/12/3526/1643.pbf`
|
||||
- `/tmp/navsea_hotspot_rebuild/backup_semantic/12/3526/1643.pbf`
|
||||
- 对比 source tile:
|
||||
- `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles/12/3526/1643.pbf`
|
||||
- source 正常 overflow:
|
||||
- `20480`
|
||||
- 坏 tile overflow:
|
||||
- `1064960`
|
||||
|
||||
### 2.2 复现实验
|
||||
|
||||
- 已直接拿 source tile 做重编码实验
|
||||
- 当把原始 `extent = 1048576` tile 重新编码时,故意把 layer `extent` 改成 `4096`
|
||||
- 可以稳定复现出与生产坏 tile 一模一样的几何签名:
|
||||
- `overflow = 1064960`
|
||||
- 这说明:
|
||||
- 当前线上坏 tile 不是随机损坏
|
||||
- 而是非常明确的“高 extent tile 被按 4096 口径重编码”的产物
|
||||
|
||||
### 2.3 热点备份审计结果
|
||||
|
||||
- 对修前热点 full 备份:
|
||||
- [`hotspot_backup_full_shift_audit_2026-04-18.json`](/root/sourceserver/pbf/report/hotspot_backup_full_shift_audit_2026-04-18.json)
|
||||
- 对修前热点 semantic 备份:
|
||||
- [`hotspot_backup_semantic_shift_audit_2026-04-18.json`](/root/sourceserver/pbf/report/hotspot_backup_semantic_shift_audit_2026-04-18.json)
|
||||
- 结果:
|
||||
- `backup_full`:`154` 张里有 `42` 张命中该签名
|
||||
- `backup_semantic`:`154` 张里有 `43` 张命中该签名
|
||||
|
||||
## 3. 为什么会让人误以为是“改图标导致的”
|
||||
|
||||
- 图标 / style 变更本身不会把海底等深线、海岸线、陆域几何整块打空
|
||||
- 但“图标改名 / 属性改写”这类需求往往会触发:
|
||||
- `decode pbf`
|
||||
- `改属性`
|
||||
- `encode pbf`
|
||||
这条重写链
|
||||
- 一旦这条链在高 extent tile 上没有保回原始 extent
|
||||
- 几何就会被整体写坏
|
||||
- 所以用户看到“像是改 style 图标之后坏了”
|
||||
- 现象上是对的
|
||||
- 但根因不是 style
|
||||
- 而是“被图标/属性变更触发的 PBF 重编码链”出了错
|
||||
|
||||
## 4. 本轮代码修复
|
||||
|
||||
### 4.1 已新增重编码守卫
|
||||
|
||||
- [`navsea_tile_reencode_guard.py`](/root/sourceserver/pbf/navsea_tile_reencode_guard.py)
|
||||
|
||||
用途:
|
||||
|
||||
- 统一保存原 layer `extent`
|
||||
- 重编码后立即和 source tile 做几何签名比对
|
||||
- 一旦出现:
|
||||
- overflow 异常增大
|
||||
- 或命中 `1048576 -> 4096` 错重编码签名
|
||||
直接报错,不允许静默产出坏 tile
|
||||
|
||||
### 4.2 已新增根因审计脚本
|
||||
|
||||
- [`navsea_extent_shift_audit.py`](/root/sourceserver/pbf/navsea_extent_shift_audit.py)
|
||||
|
||||
用途:
|
||||
|
||||
- 审计目标目录里是否存在:
|
||||
- 高 extent tile 被按 4096 口径错重编码
|
||||
的几何签名
|
||||
|
||||
### 4.3 已加固语义版 PBF 重写链
|
||||
|
||||
- 已修改:
|
||||
- [`build_semantic_delivery_assets.py`](/root/sourceserver/pbf/build_semantic_delivery_assets.py)
|
||||
|
||||
当前要求:
|
||||
|
||||
- decode 时显式带出原始 layer extent
|
||||
- encode 时必须按原 layer extent 写回
|
||||
- 每张被重写的 tile 写完后立即做几何守卫校验
|
||||
|
||||
## 5. 当前对线上问题的影响
|
||||
|
||||
- 热点 AOI 中已修过的 tile:
|
||||
- 已通过重建替换为当前正确几何
|
||||
- 但全国目录里仍可能存在更多尚未重建的历史坏 tile
|
||||
- 因此要“彻底解决”全国问题,最终仍需要:
|
||||
- 用当前受保护的生产链路重建全国 `full`
|
||||
- 再基于新的 full 重建 `semantic`
|
||||
- 并在部署前跑一轮 `extent shift audit`
|
||||
|
||||
45
report/full_geometry_native_extent_fix_2026-04-18.json
Normal file
45
report/full_geometry_native_extent_fix_2026-04-18.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"scope": {
|
||||
"zoom": 12,
|
||||
"full_root": "/home/wwwroot/pbf-delivery-full-20260415",
|
||||
"semantic_root": "/home/wwwroot/pbf-delivery-full-semantic-20260416"
|
||||
},
|
||||
"pre_fix": {
|
||||
"full": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 47366,
|
||||
"4096": 1190
|
||||
}
|
||||
},
|
||||
"semantic": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 47296,
|
||||
"4096": 1260
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup_roots": [
|
||||
"/home/wwwroot/pbf-delivery-full-20260415-backup-20260418-preextentfix",
|
||||
"/home/wwwroot/pbf-delivery-full-semantic-20260416-backup-20260418-preextentfix"
|
||||
],
|
||||
"repaired_tiles": {
|
||||
"full": 1183,
|
||||
"semantic": 1215
|
||||
},
|
||||
"post_fix": {
|
||||
"full": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 48556
|
||||
}
|
||||
},
|
||||
"semantic": {
|
||||
"tile_count": 48556,
|
||||
"extent_counts": {
|
||||
"1048576": 48556
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
report/full_geometry_native_extent_fix_2026-04-18.md
Normal file
50
report/full_geometry_native_extent_fix_2026-04-18.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 全国 full / semantic z12 extent 逻辑审计与修复
|
||||
|
||||
## 范围
|
||||
|
||||
- `/home/wwwroot/pbf-delivery-full-20260415`
|
||||
- `/home/wwwroot/pbf-delivery-full-semantic-20260416`
|
||||
- 聚焦 zoom `12`
|
||||
|
||||
## 修前统计
|
||||
|
||||
- `full z12`
|
||||
- 总瓦片:`48556`
|
||||
- `extent = 1048576`:`47366`
|
||||
- `extent = 4096`:`1190`
|
||||
- `semantic z12`
|
||||
- 总瓦片:`48556`
|
||||
- `extent = 1048576`:`47296`
|
||||
- `extent = 4096`:`1260`
|
||||
|
||||
## 修复动作
|
||||
|
||||
- 已先做硬链接备份:
|
||||
- `/home/wwwroot/pbf-delivery-full-20260415-backup-20260418-preextentfix`
|
||||
- `/home/wwwroot/pbf-delivery-full-semantic-20260416-backup-20260418-preextentfix`
|
||||
- 对当前 `extent = 4096` 的 z12 错片:
|
||||
- 逐张读取原始 `newpec` 同路径 source tile
|
||||
- 若 source extent 为 `1048576`
|
||||
- 则把当前 delivery / semantic tile 的全部 layer extent 回写为 `1048576`
|
||||
|
||||
## 修复结果
|
||||
|
||||
- `full repaired = 1183`
|
||||
- `semantic repaired = 1215`
|
||||
|
||||
## 修后复核
|
||||
|
||||
- `full z12`
|
||||
- `48556 / 48556` 全部为 `1048576`
|
||||
- `semantic z12`
|
||||
- `48556 / 48556` 全部为 `1048576`
|
||||
|
||||
## 当前结论
|
||||
|
||||
- 这轮已确认全国 `full / semantic` 的 `z12` 中确实存在一批 extent 错退片
|
||||
- 当前这批错片已经按 source tile 口径修回
|
||||
- 这一步解决的是:
|
||||
- 几何 / extent / native 风险中的明确 metadata 错片问题
|
||||
- 这不等于:
|
||||
- 全国视觉等价性已经通过
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"aoi_json": "/root/sourceserver/pbf/tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json",
|
||||
"aoi_labels": [
|
||||
"博多港中心 10 海里",
|
||||
"东京湾 10 海里",
|
||||
"大阪 10 海里",
|
||||
"冲绳 10 海里",
|
||||
"唐津 5 海里"
|
||||
],
|
||||
"zoom_filter": [],
|
||||
"source_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
|
||||
"full_root": "/home/wwwroot/pbf-delivery-full-20260415",
|
||||
"semantic_root": "/home/wwwroot/pbf-delivery-full-semantic-20260416",
|
||||
"tile_count": 154,
|
||||
"prepared_reference_tiles": 154,
|
||||
"full_backup_root": "/tmp/navsea_hotspot_rebuild/backup_full",
|
||||
"semantic_backup_root": "/tmp/navsea_hotspot_rebuild/backup_semantic",
|
||||
"full_backup_tiles": 154,
|
||||
"semantic_backup_tiles": 154,
|
||||
"full_deployed_tiles": 154,
|
||||
"semantic_stats": {
|
||||
"tiles_total": 154,
|
||||
"tiles_written": 154,
|
||||
"tiles_semantic_rewritten": 0,
|
||||
"features_semantic_changed": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# 全国热点 AOI 旧坏 Tile 重建修复
|
||||
|
||||
## 范围
|
||||
|
||||
- AOI 配置:`/root/sourceserver/pbf/tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json`
|
||||
- AOI 标签:`博多港中心 10 海里, 东京湾 10 海里, 大阪 10 海里, 冲绳 10 海里, 唐津 5 海里`
|
||||
- zoom:`按 AOI 原配置`
|
||||
- 参考源:`/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
|
||||
- full 目标:`/home/wwwroot/pbf-delivery-full-20260415`
|
||||
- semantic 目标:`/home/wwwroot/pbf-delivery-full-semantic-20260416`
|
||||
|
||||
## 数量
|
||||
|
||||
- 热点 tile 数:`154`
|
||||
- 参考 tile 准备:`154`
|
||||
- full 备份:`154`
|
||||
- semantic 备份:`154`
|
||||
- full 部署:`154`
|
||||
- semantic 部署:`154`
|
||||
- semantic 重写:`0`
|
||||
- semantic 改动 feature:`0`
|
||||
|
||||
## 备份目录
|
||||
|
||||
- full backup: `/tmp/navsea_hotspot_rebuild/backup_full`
|
||||
- semantic backup: `/tmp/navsea_hotspot_rebuild/backup_semantic`
|
||||
|
||||
## 说明
|
||||
|
||||
- 这轮不是单纯回写 extent,而是用当前 builder 对固定热点 tile 重新生成 full 包。
|
||||
- semantic 包再以重建后的 full tile 为底稿,按当前语义 sprite 映射规则重写相关属性。
|
||||
- 目标是先消除全国审计热点里的旧坏几何与大片空白问题。
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf",
|
||||
"cases": [
|
||||
{
|
||||
"id": "karatsu_5nm_full_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 276326,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.757473,
|
||||
"mean_abs_rgb": [
|
||||
28.6339,
|
||||
29.7368,
|
||||
58.0848
|
||||
],
|
||||
"rms_rgb": [
|
||||
48.2024,
|
||||
47.0653,
|
||||
75.8149
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/karatsu_5nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/karatsu_5nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/karatsu_5nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_10nm_full_z12",
|
||||
"label": "博多港中心 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=130.335%2C33.6385&zoom=12",
|
||||
"changed_pixels": 193301,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.529882,
|
||||
"mean_abs_rgb": [
|
||||
20.6516,
|
||||
21.4763,
|
||||
26.4402
|
||||
],
|
||||
"rms_rgb": [
|
||||
45.2293,
|
||||
45.5602,
|
||||
50.335
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/hakata_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/hakata_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/hakata_10nm_full_z12/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf`
|
||||
- cases: `2`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 唐津 5 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.757473`
|
||||
- changed pixels: `276326` / `364800`
|
||||
- mean abs rgb: `[28.6339, 29.7368, 58.0848]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/karatsu_5nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/karatsu_5nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/karatsu_5nm_full_z12/compare_diff.png`
|
||||
|
||||
### 博多港中心 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.529882`
|
||||
- changed pixels: `193301` / `364800`
|
||||
- mean abs rgb: `[20.6516, 21.4763, 26.4402]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=130.335%2C33.6385&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/hakata_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/hakata_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_rebuild_staging_hakata_karatsu_z12_2026-04-18/hakata_10nm_full_z12/compare_diff.png`
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf",
|
||||
"cases": [
|
||||
{
|
||||
"id": "okinawa_10nm_full_z12",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=127.67%2C26.21&zoom=12",
|
||||
"changed_pixels": 286800,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.786184,
|
||||
"mean_abs_rgb": [
|
||||
35.1541,
|
||||
37.5348,
|
||||
57.2885
|
||||
],
|
||||
"rms_rgb": [
|
||||
57.997,
|
||||
59.3287,
|
||||
80.8913
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/okinawa_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/okinawa_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/okinawa_10nm_full_z12/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf`
|
||||
- cases: `1`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 冲绳 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.786184`
|
||||
- changed pixels: `286800` / `364800`
|
||||
- mean abs rgb: `[35.1541, 37.5348, 57.2885]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=127.67%2C26.21&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/okinawa_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/okinawa_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_2026-04-18/okinawa_10nm_full_z12/compare_diff.png`
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf",
|
||||
"cases": [
|
||||
{
|
||||
"id": "okinawa_10nm_full_z12",
|
||||
"label": "冲绳 10 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
127.67,
|
||||
26.21
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=127.67%2C26.21&zoom=12",
|
||||
"changed_pixels": 286800,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.786184,
|
||||
"mean_abs_rgb": [
|
||||
35.1541,
|
||||
37.5348,
|
||||
57.2885
|
||||
],
|
||||
"rms_rgb": [
|
||||
57.997,
|
||||
59.3287,
|
||||
80.8913
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/okinawa_10nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/okinawa_10nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/okinawa_10nm_full_z12/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf`
|
||||
- cases: `1`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 冲绳 10 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.786184`
|
||||
- changed pixels: `286800` / `364800`
|
||||
- mean abs rgb: `[35.1541, 37.5348, 57.2885]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?deliveryTileUrl=http%3A%2F%2F192.168.200.184%2Fpbf-delivery-full-20260418-staging-z12%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.pbf&variant=full&audit=1¢er=127.67%2C26.21&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/okinawa_10nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/okinawa_10nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/full_rebuild_staging_okinawa_z12_r2_2026-04-18/okinawa_10nm_full_z12/compare_diff.png`
|
||||
|
||||
555
report/hotspot_backup_full_shift_audit_2026-04-18.json
Normal file
555
report/hotspot_backup_full_shift_audit_2026-04-18.json
Normal file
@@ -0,0 +1,555 @@
|
||||
{
|
||||
"target_root": "/tmp/navsea_hotspot_rebuild/backup_full",
|
||||
"source_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
|
||||
"zoom_filter": [],
|
||||
"tiles_checked": 154,
|
||||
"suspicious_tile_count": 42,
|
||||
"samples": [
|
||||
{
|
||||
"tile": "12/3641/1615.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3641/1614.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3640/1616.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3640/1615.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3640/1614.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1618.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1617.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1616.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1615.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1614.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1629.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1628.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1627.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1626.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1630.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1629.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1628.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1627.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1626.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1630.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1629.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1628.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1627.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3527/1643.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3527/1642.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3526/1643.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3526/1642.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3525/1644.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3525/1643.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3525/1642.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1740.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1739.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1738.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1737.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1736.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1740.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1739.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1738.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1737.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1736.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3499/1739.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3499/1738.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
}
|
||||
]
|
||||
}
|
||||
568
report/hotspot_backup_semantic_shift_audit_2026-04-18.json
Normal file
568
report/hotspot_backup_semantic_shift_audit_2026-04-18.json
Normal file
@@ -0,0 +1,568 @@
|
||||
{
|
||||
"target_root": "/tmp/navsea_hotspot_rebuild/backup_semantic",
|
||||
"source_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
|
||||
"zoom_filter": [],
|
||||
"tiles_checked": 154,
|
||||
"suspicious_tile_count": 43,
|
||||
"samples": [
|
||||
{
|
||||
"tile": "12/3641/1615.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3641/1614.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3640/1616.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3640/1615.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3640/1614.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1618.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1617.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1616.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1615.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3639/1614.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1629.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1628.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1627.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3588/1626.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1630.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1629.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1628.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1627.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3586/1626.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1630.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1629.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1628.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3585/1627.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3527/1643.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3527/1642.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3526/1643.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3526/1642.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3525/1644.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3525/1643.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3525/1642.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1740.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1739.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1738.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1737.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3501/1736.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1740.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1739.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1738.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1737.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3500/1736.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3499/1740.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3499/1739.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
},
|
||||
{
|
||||
"tile": "12/3499/1738.pbf",
|
||||
"source_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"output_extent_values": [
|
||||
1048576
|
||||
],
|
||||
"source_max_overflow": 20480,
|
||||
"output_max_overflow": 1064960,
|
||||
"suspicious_extent_shift": true,
|
||||
"suspicious": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html",
|
||||
"cases": [
|
||||
{
|
||||
"id": "karatsu_5nm_full_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "full",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 277714,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.761277,
|
||||
"mean_abs_rgb": [
|
||||
28.6843,
|
||||
29.789,
|
||||
58.1373
|
||||
],
|
||||
"rms_rgb": [
|
||||
48.2605,
|
||||
47.109,
|
||||
75.836
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_full_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_full_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_full_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm_semantic_z12",
|
||||
"label": "唐津 5 海里",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 274149,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.751505,
|
||||
"mean_abs_rgb": [
|
||||
28.5041,
|
||||
29.6077,
|
||||
57.8853
|
||||
],
|
||||
"rms_rgb": [
|
||||
48.0973,
|
||||
46.9345,
|
||||
75.7303
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_semantic_z12/compare_left.png",
|
||||
"right_image": "/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_semantic_z12/compare_right.png",
|
||||
"diff_image": "/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_semantic_z12/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-full-audit.html`
|
||||
- cases: `2`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 唐津 5 海里 · full · z12
|
||||
|
||||
- changed ratio: `0.761277`
|
||||
- changed pixels: `277714` / `364800`
|
||||
- mean abs rgb: `[28.6843, 29.789, 58.1373]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=full&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_full_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_full_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_full_z12/compare_diff.png`
|
||||
|
||||
### 唐津 5 海里 · semantic · z12
|
||||
|
||||
- changed ratio: `0.751505`
|
||||
- changed pixels: `274149` / `364800`
|
||||
- mean abs rgb: `[28.5041, 29.6077, 57.8853]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-full-audit.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_semantic_z12/compare_left.png`
|
||||
- right image: `/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_semantic_z12/compare_right.png`
|
||||
- diff image: `/root/sourceserver/pbf/report/karatsu_hotspot_verify_2026-04-18/karatsu_5nm_semantic_z12/compare_diff.png`
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
{
|
||||
"label": "kyushu-semantic-icon-v1",
|
||||
"target_root": "/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806",
|
||||
"source_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
|
||||
"tile_count": 4707,
|
||||
"outside_extent_tiles": 4707,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"fixable_from_source_tiles": 0,
|
||||
"repaired_tiles": 0,
|
||||
"zoom_summary": {
|
||||
"5": {
|
||||
"tile_count": 2,
|
||||
"outside_extent_tiles": 2,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 2
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"tile_count": 4,
|
||||
"outside_extent_tiles": 4,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 4
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"tile_count": 9,
|
||||
"outside_extent_tiles": 9,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 9
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"tile_count": 20,
|
||||
"outside_extent_tiles": 20,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 20
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"tile_count": 70,
|
||||
"outside_extent_tiles": 70,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 70
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"tile_count": 234,
|
||||
"outside_extent_tiles": 234,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 234
|
||||
}
|
||||
},
|
||||
"11": {
|
||||
"tile_count": 884,
|
||||
"outside_extent_tiles": 884,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"4096": 884
|
||||
}
|
||||
},
|
||||
"12": {
|
||||
"tile_count": 3484,
|
||||
"outside_extent_tiles": 3484,
|
||||
"source_extent_mismatch_tiles": 0,
|
||||
"extent_counts": {
|
||||
"1048576": 3484
|
||||
}
|
||||
}
|
||||
},
|
||||
"sample_tiles": [
|
||||
{
|
||||
"tile": "10/876/405.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/406.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/407.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/408.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/409.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/410.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/411.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/412.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/413.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/414.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/415.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/416.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/417.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/418.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/419.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/420.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/421.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/876/422.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/405.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/406.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/407.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/408.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/409.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/410.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/411.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/412.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/413.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/414.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/415.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
},
|
||||
{
|
||||
"tile": "10/877/416.pbf",
|
||||
"extent_values": [
|
||||
4096
|
||||
],
|
||||
"source_extent": 4096,
|
||||
"outside_extent": true,
|
||||
"max_overflow": 80,
|
||||
"fixable_from_source": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
# kyushu-semantic-icon-v1 几何 / Extent / Native 风险审计
|
||||
|
||||
## 范围
|
||||
|
||||
- target root: `/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806`
|
||||
- source root: `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
|
||||
- tiles: `4707`
|
||||
- repaired tiles: `0`
|
||||
|
||||
## 总体结论
|
||||
|
||||
- 坐标超当前 extent 的瓦片数:`4707`
|
||||
- 与源瓦片 extent 不一致的瓦片数:`0`
|
||||
- 可直接按源 extent 修复的瓦片数:`0`
|
||||
|
||||
## Zoom 汇总
|
||||
|
||||
### z5
|
||||
|
||||
- tiles: `2`
|
||||
- outside extent: `2`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 2}`
|
||||
|
||||
### z6
|
||||
|
||||
- tiles: `4`
|
||||
- outside extent: `4`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 4}`
|
||||
|
||||
### z7
|
||||
|
||||
- tiles: `9`
|
||||
- outside extent: `9`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 9}`
|
||||
|
||||
### z8
|
||||
|
||||
- tiles: `20`
|
||||
- outside extent: `20`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 20}`
|
||||
|
||||
### z9
|
||||
|
||||
- tiles: `70`
|
||||
- outside extent: `70`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 70}`
|
||||
|
||||
### z10
|
||||
|
||||
- tiles: `234`
|
||||
- outside extent: `234`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 234}`
|
||||
|
||||
### z11
|
||||
|
||||
- tiles: `884`
|
||||
- outside extent: `884`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{4096: 884}`
|
||||
|
||||
### z12
|
||||
|
||||
- tiles: `3484`
|
||||
- outside extent: `3484`
|
||||
- source mismatch: `0`
|
||||
- extent counts: `{1048576: 3484}`
|
||||
|
||||
## 代表问题样本
|
||||
|
||||
### `10/876/405.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/406.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/407.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/408.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/409.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/410.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/411.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/412.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/413.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/414.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/415.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/416.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/417.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/418.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/419.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/420.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/421.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/876/422.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/405.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/406.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/407.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/408.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/409.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/410.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/411.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/412.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/413.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/414.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/415.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
|
||||
### `10/877/416.pbf`
|
||||
|
||||
- extent values: `[4096]`
|
||||
- source extent: `4096`
|
||||
- outside extent: `True`
|
||||
- max overflow: `80`
|
||||
- fixable from source: `False`
|
||||
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"version": "kyushu-semantic-icon-v1-20260806",
|
||||
"source_root": "/home/wwwroot/pbf-delivery-full-20260418-rebuild",
|
||||
"target_root": "/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806",
|
||||
"bbox": [
|
||||
128.0,
|
||||
30.0,
|
||||
132.5,
|
||||
34.9
|
||||
],
|
||||
"zooms": [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12
|
||||
],
|
||||
"tiles": 4707,
|
||||
"tiles_rewritten": 1340,
|
||||
"candidate_tiles": 1343,
|
||||
"features_changed": 40131,
|
||||
"old_values": {
|
||||
"symbol-daytime-301": 2536,
|
||||
"symbol-daytime-308": 33,
|
||||
"symbol-daytime-428": 13416,
|
||||
"symbol-daytime-30500003": 165,
|
||||
"symbol-daytime-303": 4993,
|
||||
"symbol-daytime-327": 626,
|
||||
"symbol-daytime-310": 1833,
|
||||
"symbol-daytime-30500001": 25,
|
||||
"symbol-daytime-320": 959,
|
||||
"symbol-daytime-321": 290,
|
||||
"symbol-daytime-429": 256,
|
||||
"symbol-daytime-413": 187,
|
||||
"symbol-daytime-325": 38,
|
||||
"symbol-daytime-323": 77,
|
||||
"symbol-daytime-30700003": 24,
|
||||
"symbol-daytime-30500002": 16,
|
||||
"symbol-daytime-335359": 12,
|
||||
"symbol-daytime-405": 5329,
|
||||
"symbol-daytime-412": 50,
|
||||
"symbol-daytime-719": 54,
|
||||
"symbol-daytime-410": 10
|
||||
},
|
||||
"unknown_old_values_after_rewrite": {},
|
||||
"icon_counts": {
|
||||
"lt_hbr": 2536,
|
||||
"mk_lead_v": 33,
|
||||
"hz_foul": 3598,
|
||||
"hz_reef": 6659,
|
||||
"lt_lead_c": 165,
|
||||
"lm_mtn": 2059,
|
||||
"hz_overfall": 912,
|
||||
"lt_min": 4993,
|
||||
"bu_lat": 626,
|
||||
"lt_bcn": 1833,
|
||||
"lm_chim": 1907,
|
||||
"lm_twr": 2527,
|
||||
"hz_seaweed": 470,
|
||||
"lt_lead_a": 25,
|
||||
"bu_lit": 959,
|
||||
"lm_mar": 121,
|
||||
"lm_fish": 846,
|
||||
"lm_cus": 85,
|
||||
"lm_mon": 42,
|
||||
"bu_gen": 290,
|
||||
"hz_wreck_survey": 432,
|
||||
"hz_sandwave": 662,
|
||||
"lm_ctrl": 28,
|
||||
"bu_can": 38,
|
||||
"hz_outfall": 174,
|
||||
"hz_danger_clear": 26,
|
||||
"bu_pil": 77,
|
||||
"lm_prom": 8,
|
||||
"hz_whirl": 27,
|
||||
"mk_lead_c": 24,
|
||||
"hz_reef_danger": 25,
|
||||
"lt_lead_b": 16,
|
||||
"mk_vais": 12,
|
||||
"fac_fishport": 1164,
|
||||
"hz_rock_awash": 1397,
|
||||
"hz_rock_sub": 3932,
|
||||
"hz_danger_iso": 212,
|
||||
"fac_port": 261,
|
||||
"hz_tower": 57,
|
||||
"hz_wreck_sub": 50,
|
||||
"fac_seastation": 48,
|
||||
"hz_dolphin": 205,
|
||||
"fac_fisherina": 11,
|
||||
"fac_marina": 95,
|
||||
"an_quar": 33,
|
||||
"hz_wreck_hull": 10,
|
||||
"hz_pile": 340,
|
||||
"hz_obst": 60,
|
||||
"an_restrict": 2,
|
||||
"an_no_anchor": 4,
|
||||
"an_desig": 15
|
||||
},
|
||||
"arc_counts": {
|
||||
"arc_open_YW": 204,
|
||||
"arc_YW": 977,
|
||||
"arc_G": 610,
|
||||
"arc_R": 769
|
||||
},
|
||||
"sprite": {
|
||||
"added": {
|
||||
"lt_cst": "coastal_lighthouse",
|
||||
"lt_hbr": "harbor_lighthouse",
|
||||
"lt_bkw": "breakwater_lighthouse",
|
||||
"lt_min": "light_minor",
|
||||
"lt_lead_a": "leading_light_variantt_30500001",
|
||||
"lt_lead_b": "leading_light_variantt_30500002",
|
||||
"lt_lead_c": "leading_light_variantt_30500003",
|
||||
"lt_up_G": "symbol-daytime-30700001",
|
||||
"lt_up_R": "symbol-daytime-30700002",
|
||||
"mk_lead_c": "leading_mark_variantt_30700003",
|
||||
"mk_lead_v": "leading_mark_variantt_308",
|
||||
"lt_bcn": "light_beacon",
|
||||
"bu_lit": "buoy_light",
|
||||
"bu_gen": "buoy_generic",
|
||||
"bu_pil": "buoy_pillar",
|
||||
"bu_can": "buoy_can",
|
||||
"bu_lat": "buoy_lattice",
|
||||
"mk_vais": "nav_mark_vais",
|
||||
"hz_rock_awash": "rock_exposed_drying_awash_group",
|
||||
"hz_rock_sub": "sunken_rock",
|
||||
"hz_danger_clear": "cleared_danger_object",
|
||||
"hz_danger_iso": "isolated_danger",
|
||||
"hz_wreck_hull": "wreck_hull_exposed",
|
||||
"hz_wreck_sub": "wreck_fully_submerged_dangerous",
|
||||
"hz_wreck_survey": "wreck_surveyed",
|
||||
"hz_foul": "foul_ground",
|
||||
"hz_sandwave": "sand_wave",
|
||||
"hz_overfall": "tide_rips_overfalls",
|
||||
"hz_whirl": "eddy_whirlpool",
|
||||
"hz_seaweed": "seaweed",
|
||||
"hz_obst": "obstruction",
|
||||
"hz_reef": "fish_reef",
|
||||
"hz_reef_danger": "fish_reef_dangerous",
|
||||
"hz_tower": "tower_yagura_observation_platform",
|
||||
"hz_pile": "bollard_pile_stake",
|
||||
"hz_dolphin": "dolphin_structure",
|
||||
"hz_outfall": "subsea_installation_outfall_intake",
|
||||
"lm_chim": "symbol-daytime-640",
|
||||
"lm_twr": "symbol-daytime-650",
|
||||
"lm_mar": "symbol-daytime-660",
|
||||
"lm_fish": "symbol-daytime-661",
|
||||
"lm_cus": "symbol-daytime-660",
|
||||
"lm_prom": "symbol-daytime-680",
|
||||
"lm_mtn": "symbol-daytime-681",
|
||||
"lm_ctrl": "symbol-daytime-682",
|
||||
"lm_mon": "symbol-daytime-698",
|
||||
"an_quar": "anchorage_quarantine",
|
||||
"an_desig": "anchorage_designated",
|
||||
"an_restrict": "restriction_area_group",
|
||||
"an_no_anchor": "anchoring_prohibited",
|
||||
"fac_port": "port_general_small_harbor_group",
|
||||
"fac_fishport": "fishing_port",
|
||||
"fac_marina": "marina",
|
||||
"fac_fisherina": "fisherina",
|
||||
"fac_seastation": "sea_station_umi_no_eki",
|
||||
"fac_pilot": "pilot_station",
|
||||
"tide_spot": "tidespot-daytime",
|
||||
"fl_G": "light_flare_green",
|
||||
"fl_R": "light_flare_red",
|
||||
"fl_YW": "light_flare_yellow_white"
|
||||
},
|
||||
"fallbacks": {
|
||||
"lm_cus": "symbol-daytime-660"
|
||||
}
|
||||
},
|
||||
"style": "/root/sourceserver/pbf/src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json",
|
||||
"newpec_style": "/root/sourceserver/pbf/src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# 九州语义图标替换测试版 v1
|
||||
|
||||
- PBF 来源:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
|
||||
- PBF 输出:`/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806`
|
||||
- 范围:`(128.0, 30.0, 132.5, 34.9)`
|
||||
- zoom:`5-12`
|
||||
- 瓦片数:`4707`
|
||||
- 重写瓦片:`1340`
|
||||
- 改动 feature:`40131`
|
||||
- sprite:`/mnt/sda1/www/newpec/sprite-kyushu-semantic-icon-v1-20260806`
|
||||
- delivery style:`/root/sourceserver/pbf/src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json`
|
||||
- newpec style:`/root/sourceserver/pbf/src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json`
|
||||
|
||||
## 旧图标值
|
||||
|
||||
```json
|
||||
{
|
||||
"symbol-daytime-301": 2536,
|
||||
"symbol-daytime-308": 33,
|
||||
"symbol-daytime-428": 13416,
|
||||
"symbol-daytime-30500003": 165,
|
||||
"symbol-daytime-303": 4993,
|
||||
"symbol-daytime-327": 626,
|
||||
"symbol-daytime-310": 1833,
|
||||
"symbol-daytime-30500001": 25,
|
||||
"symbol-daytime-320": 959,
|
||||
"symbol-daytime-321": 290,
|
||||
"symbol-daytime-429": 256,
|
||||
"symbol-daytime-413": 187,
|
||||
"symbol-daytime-325": 38,
|
||||
"symbol-daytime-323": 77,
|
||||
"symbol-daytime-30700003": 24,
|
||||
"symbol-daytime-30500002": 16,
|
||||
"symbol-daytime-335359": 12,
|
||||
"symbol-daytime-405": 5329,
|
||||
"symbol-daytime-412": 50,
|
||||
"symbol-daytime-719": 54,
|
||||
"symbol-daytime-410": 10
|
||||
}
|
||||
```
|
||||
|
||||
## 审计关注
|
||||
|
||||
- `unknown_old_values_after_rewrite` 必须为空。
|
||||
- `lm_cus` 当前使用 `symbol-daytime-660` 的临时视觉回退,需人工确认。
|
||||
- 几何使用原 tile extent 重编码,并执行 extent 安全检查。
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"source_root": "/home/wwwroot/pbf-delivery-full-20260418-rebuild",
|
||||
"target_root": "/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806",
|
||||
"tiles_target": 4707,
|
||||
"tiles_compared": 4707,
|
||||
"tiles_with_errors": 0,
|
||||
"errors": [],
|
||||
"source_features": {
|
||||
"baseline_area": 225324,
|
||||
"baseline_outline": 401949,
|
||||
"hole_area": 110994,
|
||||
"land_area": 65455,
|
||||
"bathymetry_line": 413730,
|
||||
"depth_contour": 171277,
|
||||
"navigation_marks": 11627,
|
||||
"bridge_structure": 7224,
|
||||
"hazard_boundary_outline": 20151,
|
||||
"place_label_land": 10983,
|
||||
"submerged_reef_area": 770,
|
||||
"place_label_sea": 12092,
|
||||
"anchor_caution_hazard_point": 12932,
|
||||
"baseline_line": 14640,
|
||||
"fixed_fishing_gear_area": 9044,
|
||||
"onshore_structure_line": 114122,
|
||||
"onshore_structure_point": 7623,
|
||||
"anchor_caution_hazard_area": 1809,
|
||||
"anchor_caution_hazard_outline": 1815,
|
||||
"clearance_limit_line": 325,
|
||||
"hazard_boundary_line": 37,
|
||||
"onshore_structure_area": 3640,
|
||||
"depth_contour_overview": 2154,
|
||||
"depth_zone_739": 181,
|
||||
"route_boundary_point": 78,
|
||||
"route_area": 51,
|
||||
"route_outline": 161,
|
||||
"depth_zone_740": 9,
|
||||
"route_axis_line": 14,
|
||||
"depth_zone_700": 8,
|
||||
"depth_zone_725": 4,
|
||||
"seabed_line": 2106,
|
||||
"seabed_text_point": 33376,
|
||||
"facility_boundary_point": 1579,
|
||||
"navigation_hazard_point": 6316,
|
||||
"depth_zone_741": 497,
|
||||
"depth_zone_748": 45,
|
||||
"depth_zone_749": 43,
|
||||
"clearance_limit_point": 261,
|
||||
"clip_outline_754": 223,
|
||||
"facility_boundary_area": 449,
|
||||
"navigation_hazard_outline": 48,
|
||||
"anchorage_area": 55,
|
||||
"anchorage_outline": 55,
|
||||
"anchorage_point": 54,
|
||||
"facility_boundary_outline": 233,
|
||||
"pilot_station_point": 16,
|
||||
"navigation_hazard_area": 24,
|
||||
"leading_line_outline": 16,
|
||||
"clip_outline_730": 4,
|
||||
"facility_boundary_area_transparent": 4
|
||||
},
|
||||
"target_features": {
|
||||
"baseline_area": 225324,
|
||||
"baseline_outline": 401949,
|
||||
"hole_area": 110994,
|
||||
"land_area": 65455,
|
||||
"bathymetry_line": 413730,
|
||||
"depth_contour": 171277,
|
||||
"navigation_marks": 11627,
|
||||
"bridge_structure": 7224,
|
||||
"hazard_boundary_outline": 20151,
|
||||
"place_label_land": 10983,
|
||||
"submerged_reef_area": 770,
|
||||
"place_label_sea": 12092,
|
||||
"anchor_caution_hazard_point": 12932,
|
||||
"baseline_line": 14640,
|
||||
"fixed_fishing_gear_area": 9044,
|
||||
"onshore_structure_line": 114122,
|
||||
"onshore_structure_point": 7623,
|
||||
"anchor_caution_hazard_area": 1809,
|
||||
"anchor_caution_hazard_outline": 1815,
|
||||
"clearance_limit_line": 325,
|
||||
"hazard_boundary_line": 37,
|
||||
"onshore_structure_area": 3640,
|
||||
"depth_contour_overview": 2154,
|
||||
"depth_zone_739": 181,
|
||||
"route_boundary_point": 78,
|
||||
"route_area": 51,
|
||||
"route_outline": 161,
|
||||
"depth_zone_740": 9,
|
||||
"route_axis_line": 14,
|
||||
"depth_zone_700": 8,
|
||||
"depth_zone_725": 4,
|
||||
"seabed_line": 2106,
|
||||
"seabed_text_point": 33376,
|
||||
"facility_boundary_point": 1579,
|
||||
"navigation_hazard_point": 6316,
|
||||
"depth_zone_741": 497,
|
||||
"depth_zone_748": 45,
|
||||
"depth_zone_749": 43,
|
||||
"clearance_limit_point": 261,
|
||||
"clip_outline_754": 223,
|
||||
"facility_boundary_area": 449,
|
||||
"navigation_hazard_outline": 48,
|
||||
"anchorage_area": 55,
|
||||
"anchorage_outline": 55,
|
||||
"anchorage_point": 54,
|
||||
"facility_boundary_outline": 233,
|
||||
"pilot_station_point": 16,
|
||||
"navigation_hazard_area": 24,
|
||||
"leading_line_outline": 16,
|
||||
"clip_outline_730": 4,
|
||||
"facility_boundary_area_transparent": 4
|
||||
},
|
||||
"icon_counts": {
|
||||
"icon_id:lt_hbr": 2536,
|
||||
"arc_id:arc_open_YW": 204,
|
||||
"arc_id:arc_YW": 977,
|
||||
"arc_id:arc_G": 610,
|
||||
"arc_id:arc_R": 769,
|
||||
"icon_id:mk_lead_v": 33,
|
||||
"icon_id:hz_foul": 3598,
|
||||
"icon_id:hz_reef": 6659,
|
||||
"icon_id:lt_lead_c": 165,
|
||||
"icon_id:lm_mtn": 2059,
|
||||
"icon_id:hz_overfall": 912,
|
||||
"icon_id:lt_min": 4993,
|
||||
"icon_id:bu_lat": 626,
|
||||
"icon_id:lt_bcn": 1833,
|
||||
"icon_id:lm_chim": 1907,
|
||||
"icon_id:lm_twr": 2527,
|
||||
"icon_id:hz_seaweed": 470,
|
||||
"icon_id:lt_lead_a": 25,
|
||||
"icon_id:bu_lit": 959,
|
||||
"icon_id:lm_mar": 121,
|
||||
"icon_id:lm_fish": 846,
|
||||
"icon_id:lm_cus": 85,
|
||||
"icon_id:lm_mon": 42,
|
||||
"icon_id:bu_gen": 290,
|
||||
"icon_id:hz_wreck_survey": 432,
|
||||
"icon_id:hz_sandwave": 662,
|
||||
"icon_id:lm_ctrl": 28,
|
||||
"icon_id:bu_can": 38,
|
||||
"icon_id:hz_outfall": 174,
|
||||
"icon_id:hz_danger_clear": 26,
|
||||
"icon_id:bu_pil": 77,
|
||||
"icon_id:lm_prom": 8,
|
||||
"icon_id:hz_whirl": 27,
|
||||
"icon_id:mk_lead_c": 24,
|
||||
"icon_id:hz_reef_danger": 25,
|
||||
"icon_id:lt_lead_b": 16,
|
||||
"icon_id:mk_vais": 12,
|
||||
"icon_id:fac_fishport": 1164,
|
||||
"icon_id:hz_rock_awash": 1397,
|
||||
"icon_id:hz_rock_sub": 3932,
|
||||
"icon_id:hz_danger_iso": 212,
|
||||
"icon_id:fac_port": 261,
|
||||
"icon_id:hz_tower": 57,
|
||||
"icon_id:hz_wreck_sub": 50,
|
||||
"icon_id:fac_seastation": 48,
|
||||
"icon_id:hz_dolphin": 205,
|
||||
"icon_id:fac_fisherina": 11,
|
||||
"icon_id:fac_marina": 95,
|
||||
"icon_id:an_quar": 33,
|
||||
"icon_id:hz_wreck_hull": 10,
|
||||
"icon_id:hz_pile": 340,
|
||||
"icon_id:hz_obst": 60,
|
||||
"icon_id:an_restrict": 2,
|
||||
"icon_id:an_no_anchor": 4,
|
||||
"icon_id:an_desig": 15
|
||||
},
|
||||
"old_prefix_counts": {},
|
||||
"verdict": "PASS"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# 九州语义图标测试版对象 / 几何 / 图标审计
|
||||
|
||||
- 来源:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
|
||||
- 目标:`/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806`
|
||||
- 目标瓦片:`4707`,实际比较:`4707`
|
||||
- 有错误瓦片:`0`
|
||||
- 旧图标前缀残留:`0`
|
||||
- 结论:`PASS`
|
||||
|
||||
本审计忽略预期的 `chart_icon_image`、`icon_id`、`arc_id` 字段变化,并把两个不存在的纹理名规范化后再比较:
|
||||
|
||||
- `anchor_caution_hazard_area class_code=420`:`fill-daytime-405` -> `fill-daytime-420`
|
||||
- `navigation_hazard_area class_code=427`:`fill-daytime-405` -> `fill-daytime-427`
|
||||
|
||||
其余内容逐图层比较对象数量、ID、几何和属性。
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"asset_version": "kyushu-semantic-icon-v1-20260806-r2",
|
||||
"scanned_tiles": 4707,
|
||||
"used_keys": [
|
||||
"an_desig",
|
||||
"an_no_anchor",
|
||||
"an_quar",
|
||||
"an_restrict",
|
||||
"arc_G",
|
||||
"arc_R",
|
||||
"arc_YW",
|
||||
"arc_open_YW",
|
||||
"bu_can",
|
||||
"bu_gen",
|
||||
"bu_lat",
|
||||
"bu_lit",
|
||||
"bu_pil",
|
||||
"fac_fisherina",
|
||||
"fac_fishport",
|
||||
"fac_marina",
|
||||
"fac_port",
|
||||
"fac_seastation",
|
||||
"fill-daytime-420",
|
||||
"fill-daytime-427",
|
||||
"fill-daytime-428",
|
||||
"fill-daytime-754",
|
||||
"hz_danger_clear",
|
||||
"hz_danger_iso",
|
||||
"hz_dolphin",
|
||||
"hz_foul",
|
||||
"hz_obst",
|
||||
"hz_outfall",
|
||||
"hz_overfall",
|
||||
"hz_pile",
|
||||
"hz_reef",
|
||||
"hz_reef_danger",
|
||||
"hz_rock_awash",
|
||||
"hz_rock_sub",
|
||||
"hz_sandwave",
|
||||
"hz_seaweed",
|
||||
"hz_tower",
|
||||
"hz_whirl",
|
||||
"hz_wreck_hull",
|
||||
"hz_wreck_sub",
|
||||
"hz_wreck_survey",
|
||||
"lm_chim",
|
||||
"lm_ctrl",
|
||||
"lm_cus",
|
||||
"lm_fish",
|
||||
"lm_mar",
|
||||
"lm_mon",
|
||||
"lm_mtn",
|
||||
"lm_prom",
|
||||
"lm_twr",
|
||||
"lt_bcn",
|
||||
"lt_hbr",
|
||||
"lt_lead_a",
|
||||
"lt_lead_b",
|
||||
"lt_lead_c",
|
||||
"lt_min",
|
||||
"mk_lead_c",
|
||||
"mk_lead_v",
|
||||
"mk_vais",
|
||||
"pattern_fill_fishery_or_fish_reef_area",
|
||||
"special_pattern_line_754",
|
||||
"tide_spot"
|
||||
],
|
||||
"used_key_count": 62,
|
||||
"sprite_key_count": 98,
|
||||
"missing_keys": [],
|
||||
"legacy_sprite_keys": [],
|
||||
"verdict": "PASS"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# 九州测试版 Sprite 图标与纹理闭合审计
|
||||
|
||||
- 资产版本:`kyushu-semantic-icon-v1-20260806-r2`
|
||||
- 扫描瓦片:`4707`
|
||||
- 使用中的 icon_id/arc_id/纹理 key:`62`
|
||||
- sprite 键数:`98`
|
||||
- 缺失键:`0`
|
||||
- 旧前缀键:`0`
|
||||
- 结论:`PASS`
|
||||
|
||||
本审计同时检查 `icon_id`、`arc_id`、`chart_fill_pattern`、`chart_fill_style` 映射纹理,以及 style 中直接写死的 `fill-pattern` / `line-pattern` / `icon-image` 依赖。
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html",
|
||||
"cases": [
|
||||
{
|
||||
"id": "karatsu_kyushu_icon_v1_semantic_z12",
|
||||
"label": "唐津九州语义图标测试",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12",
|
||||
"changed_pixels": 287577,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.788314,
|
||||
"mean_abs_rgb": [
|
||||
39.3424,
|
||||
42.4767,
|
||||
74.167
|
||||
],
|
||||
"rms_rgb": [
|
||||
61.2913,
|
||||
62.6326,
|
||||
97.1048
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_left.png",
|
||||
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_right.png",
|
||||
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "karatsu_kyushu_icon_v1_semantic_z10",
|
||||
"label": "唐津九州语义图标测试",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
129.9697,
|
||||
33.4425
|
||||
],
|
||||
"radius_nm": 5,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=10",
|
||||
"changed_pixels": 274296,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.751908,
|
||||
"mean_abs_rgb": [
|
||||
33.6012,
|
||||
36.7804,
|
||||
64.1346
|
||||
],
|
||||
"rms_rgb": [
|
||||
56.4791,
|
||||
56.9124,
|
||||
89.4452
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_left.png",
|
||||
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_right.png",
|
||||
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_kyushu_icon_v1_semantic_z12",
|
||||
"label": "博多港九州语义图标测试",
|
||||
"variant": "semantic",
|
||||
"zoom": 12,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=12",
|
||||
"changed_pixels": 237978,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.652352,
|
||||
"mean_abs_rgb": [
|
||||
30.9085,
|
||||
30.6867,
|
||||
39.4968
|
||||
],
|
||||
"rms_rgb": [
|
||||
55.1912,
|
||||
58.1514,
|
||||
67.7886
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_left.png",
|
||||
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_right.png",
|
||||
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_diff.png"
|
||||
},
|
||||
{
|
||||
"id": "hakata_kyushu_icon_v1_semantic_z10",
|
||||
"label": "博多港九州语义图标测试",
|
||||
"variant": "semantic",
|
||||
"zoom": 10,
|
||||
"center": [
|
||||
130.335,
|
||||
33.6385
|
||||
],
|
||||
"radius_nm": 10,
|
||||
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=10",
|
||||
"changed_pixels": 236727,
|
||||
"total_pixels": 364800,
|
||||
"changed_ratio": 0.648923,
|
||||
"mean_abs_rgb": [
|
||||
31.9821,
|
||||
32.9781,
|
||||
55.3087
|
||||
],
|
||||
"rms_rgb": [
|
||||
57.9478,
|
||||
56.188,
|
||||
83.9112
|
||||
],
|
||||
"diff_bbox": [
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
513
|
||||
],
|
||||
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_left.png",
|
||||
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_right.png",
|
||||
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_diff.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# 全国 AOI 截图对比审计
|
||||
|
||||
## 总览
|
||||
|
||||
- compare page: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html`
|
||||
- cases: `4`
|
||||
|
||||
## 差异排序
|
||||
|
||||
### 唐津九州语义图标测试 · semantic · z12
|
||||
|
||||
- changed ratio: `0.788314`
|
||||
- changed pixels: `287577` / `364800`
|
||||
- mean abs rgb: `[39.3424, 42.4767, 74.167]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=12`
|
||||
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_left.png`
|
||||
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_right.png`
|
||||
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_diff.png`
|
||||
|
||||
### 唐津九州语义图标测试 · semantic · z10
|
||||
|
||||
- changed ratio: `0.751908`
|
||||
- changed pixels: `274296` / `364800`
|
||||
- mean abs rgb: `[33.6012, 36.7804, 64.1346]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=129.9697%2C33.4425&zoom=10`
|
||||
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_left.png`
|
||||
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_right.png`
|
||||
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_diff.png`
|
||||
|
||||
### 博多港九州语义图标测试 · semantic · z12
|
||||
|
||||
- changed ratio: `0.652352`
|
||||
- changed pixels: `237978` / `364800`
|
||||
- mean abs rgb: `[30.9085, 30.6867, 39.4968]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=12`
|
||||
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_left.png`
|
||||
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_right.png`
|
||||
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_diff.png`
|
||||
|
||||
### 博多港九州语义图标测试 · semantic · z10
|
||||
|
||||
- changed ratio: `0.648923`
|
||||
- changed pixels: `236727` / `364800`
|
||||
- mean abs rgb: `[31.9821, 32.9781, 55.3087]`
|
||||
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1¢er=130.335%2C33.6385&zoom=10`
|
||||
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_left.png`
|
||||
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_right.png`
|
||||
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_diff.png`
|
||||
459
src/pbf/navsea-coastline-fukuoka-saga-200m.html
Normal file
459
src/pbf/navsea-coastline-fukuoka-saga-200m.html
Normal file
@@ -0,0 +1,459 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>NavSea 全国海岸 / 渔港 / 障碍红格预览 v2.1</title>
|
||||
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--panel-bg: rgba(14, 18, 22, 0.88);
|
||||
--panel-border: rgba(255, 255, 255, 0.12);
|
||||
--text: #f3efe7;
|
||||
--muted: #c7c0b4;
|
||||
--accent: #ff808a;
|
||||
--accent-2: #ff4d57;
|
||||
--base: #071018;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--base);
|
||||
color: var(--text);
|
||||
font-family: "Noto Sans SC", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
z-index: 2;
|
||||
width: min(460px, calc(100vw - 32px));
|
||||
background: var(--panel-bg);
|
||||
border: 1px solid var(--panel-border);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 14px 16px 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 6px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
appearance: none;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: linear-gradient(180deg, rgba(255, 128, 138, 0.26), rgba(255, 77, 87, 0.1));
|
||||
color: var(--text);
|
||||
border-radius: 999px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.active {
|
||||
border-color: rgba(255, 200, 204, 0.88);
|
||||
background: linear-gradient(180deg, rgba(255, 128, 138, 0.42), rgba(255, 77, 87, 0.2));
|
||||
box-shadow: 0 0 0 1px rgba(255, 128, 138, 0.24) inset;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: rgba(244, 154, 160, 0.55);
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: grid;
|
||||
grid-template-columns: 18px 1fr;
|
||||
gap: 8px 10px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.swatch-red {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 77, 87, 0.38);
|
||||
border: 1px solid rgba(255, 77, 87, 0.95);
|
||||
}
|
||||
|
||||
.swatch-line {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background: #1e1e1e;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #ffd8db;
|
||||
min-height: 1.4em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
z-index: 2;
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
color: #1e120f;
|
||||
background: rgba(255, 238, 236, 0.84);
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.maplibregl-ctrl-bottom-right .maplibregl-ctrl {
|
||||
margin: 0 16px 16px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<div class="panel">
|
||||
<div class="title">NavSea 全国海岸 / 渔港 / 障碍红格预览</div>
|
||||
<div class="meta" id="meta">
|
||||
版本:v2.1<br />
|
||||
数据:全国统一库导出<br />
|
||||
底盘:navsea_japan_coast_grid<br />
|
||||
默认视图:全国 200x200<br />
|
||||
底图:GSI 标准地图(mapple)
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button id="reloadBtn">重新加载</button>
|
||||
<button id="btn200" class="active">200x200</button>
|
||||
<button id="btn20">20x20</button>
|
||||
<button id="btn50">50x50</button>
|
||||
<button id="btnDensity">整体密度</button>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<div class="swatch-red"></div><div>当前图层采用红色填充和深色边线显示</div>
|
||||
</div>
|
||||
<div class="status" id="status">等待加载中...</div>
|
||||
</div>
|
||||
<div class="hint" id="hint">当前加载:200x200 全国海岸格</div>
|
||||
|
||||
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
||||
<script>
|
||||
const VERSION = "v2.1";
|
||||
const LAYERS = {
|
||||
"200": {
|
||||
key: "coast_200m",
|
||||
label: "200x200 全国海岸格",
|
||||
manifestUrl: "./coastline-mysql/japan_coast_200m/manifest.json",
|
||||
gridUrl: "./coastline-mysql/japan_coast_200m/coast_200m_grid.geojson",
|
||||
center: [138.5, 36.4],
|
||||
zoom: 4.8
|
||||
},
|
||||
"20": {
|
||||
key: "fish_port_20m",
|
||||
label: "20x20 全国渔港格",
|
||||
manifestUrl: "./coastline-mysql/japan_national/manifest.json",
|
||||
gridUrl: "./coastline-mysql/japan_national/fish_port_20m_grid.geojson",
|
||||
center: [130.55, 33.55],
|
||||
zoom: 6.0
|
||||
},
|
||||
"50": {
|
||||
key: "hazard_50m",
|
||||
label: "50x50 全国海上障碍格",
|
||||
manifestUrl: "./coastline-mysql/japan_national/manifest.json",
|
||||
gridUrl: "./coastline-mysql/japan_national/hazard_50m_grid.geojson",
|
||||
center: [131.0, 33.55],
|
||||
zoom: 5.8
|
||||
},
|
||||
"density": {
|
||||
key: "density_overview",
|
||||
label: "整体密度图",
|
||||
manifestUrl: "./coastline-mysql/japan_national/manifest.json",
|
||||
gridUrl: "./coastline-mysql/japan_national/density_overview_grid.geojson",
|
||||
center: [138.5, 36.4],
|
||||
zoom: 4.8,
|
||||
mode: "density"
|
||||
}
|
||||
};
|
||||
|
||||
const map = new maplibregl.Map({
|
||||
container: "map",
|
||||
style: {
|
||||
version: 8,
|
||||
sources: {
|
||||
mapple: {
|
||||
type: "raster",
|
||||
minzoom: 0,
|
||||
maxzoom: 19,
|
||||
tileSize: 256,
|
||||
tiles: [
|
||||
"https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"
|
||||
],
|
||||
attribution: "© 昭文社"
|
||||
}
|
||||
},
|
||||
layers: [
|
||||
{ id: "mapple", type: "raster", source: "mapple" }
|
||||
]
|
||||
},
|
||||
center: LAYERS["200"].center,
|
||||
zoom: LAYERS["200"].zoom,
|
||||
pitch: 0,
|
||||
bearing: 0,
|
||||
hash: true
|
||||
});
|
||||
|
||||
map.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||
map.addControl(new maplibregl.ScaleControl({ maxWidth: 140, unit: "metric" }), "bottom-left");
|
||||
|
||||
let manifest = null;
|
||||
let currentLayerKey = "200";
|
||||
let currentMode = "regular";
|
||||
|
||||
function setStatus(text) {
|
||||
document.getElementById("status").textContent = text;
|
||||
}
|
||||
|
||||
function setHint(text) {
|
||||
document.getElementById("hint").textContent = text;
|
||||
}
|
||||
|
||||
function setActiveButton(key) {
|
||||
for (const id of ["btn200", "btn20", "btn50", "btnDensity"]) {
|
||||
const button = document.getElementById(id);
|
||||
button.classList.toggle("active", id === `btn${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadJson(url) {
|
||||
const resp = await fetch(url, { cache: "no-store" });
|
||||
if (!resp.ok) {
|
||||
throw new Error(`${url} -> ${resp.status}`);
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
function layerStats(manifestObj, layerKey) {
|
||||
if (!manifestObj) return { count: 0, bbox: null };
|
||||
if (manifestObj.layers && manifestObj.layers[layerKey]) {
|
||||
const item = manifestObj.layers[layerKey];
|
||||
return { count: item.count || 0, bbox: item.bbox_lonlat || null };
|
||||
}
|
||||
if (manifestObj.layer === layerKey) {
|
||||
return {
|
||||
count: manifestObj.count || 0,
|
||||
bbox: manifestObj.grid_bounds_lonlat || null
|
||||
};
|
||||
}
|
||||
return { count: 0, bbox: null };
|
||||
}
|
||||
|
||||
function validLonLatBounds(bounds) {
|
||||
if (!Array.isArray(bounds) || bounds.length !== 4) return false;
|
||||
const [west, south, east, north] = bounds.map(Number);
|
||||
if (![west, south, east, north].every(Number.isFinite)) return false;
|
||||
if (Math.abs(west) > 180 || Math.abs(east) > 180) return false;
|
||||
if (Math.abs(south) > 90 || Math.abs(north) > 90) return false;
|
||||
return west < east && south < north;
|
||||
}
|
||||
|
||||
function applyGridStyle(mode) {
|
||||
currentMode = mode;
|
||||
const isDensity = mode === "density";
|
||||
const fillColor = isDensity
|
||||
? [
|
||||
"match",
|
||||
["get", "density_key"],
|
||||
"20", "#bfeec2",
|
||||
"50", "#f2e17b",
|
||||
"200", "#f5b6ba",
|
||||
"#ff2b35"
|
||||
]
|
||||
: "#ff2b35";
|
||||
const outlineColor = isDensity
|
||||
? [
|
||||
"match",
|
||||
["get", "density_key"],
|
||||
"20", "#67a96c",
|
||||
"50", "#b8a228",
|
||||
"200", "#cd7b82",
|
||||
"#a40008"
|
||||
]
|
||||
: "#a40008";
|
||||
|
||||
if (map.getLayer("navsea-grid-fill")) {
|
||||
map.setPaintProperty("navsea-grid-fill", "fill-color", fillColor);
|
||||
map.setPaintProperty("navsea-grid-fill", "fill-opacity", 0.82);
|
||||
map.setPaintProperty("navsea-grid-fill", "fill-outline-color", "#111111");
|
||||
}
|
||||
if (map.getLayer("navsea-grid-halo")) {
|
||||
map.setPaintProperty("navsea-grid-halo", "line-color", "#ffffff");
|
||||
map.setPaintProperty("navsea-grid-halo", "line-width", 5);
|
||||
map.setPaintProperty("navsea-grid-halo", "line-opacity", 0.96);
|
||||
}
|
||||
if (map.getLayer("navsea-grid-line")) {
|
||||
map.setPaintProperty("navsea-grid-line", "line-color", outlineColor);
|
||||
map.setPaintProperty("navsea-grid-line", "line-width", isDensity ? 3.25 : 3.5);
|
||||
map.setPaintProperty("navsea-grid-line", "line-opacity", 1.0);
|
||||
}
|
||||
const legend = document.querySelector(".legend");
|
||||
if (legend) {
|
||||
legend.innerHTML = isDensity
|
||||
? `
|
||||
<div class="swatch-red" style="background: rgba(191, 238, 194, 0.95); border-color: rgba(103, 169, 108, 1);"></div><div>高密度:20x20,淡绿色</div>
|
||||
<div class="swatch-red" style="background: rgba(242, 225, 123, 0.95); border-color: rgba(184, 162, 40, 1);"></div><div>中密度:50x50,黄色</div>
|
||||
<div class="swatch-red" style="background: rgba(245, 182, 186, 0.95); border-color: rgba(205, 123, 130, 1);"></div><div>低密度:200x200,淡红色</div>
|
||||
`
|
||||
: '<div class="swatch-red"></div><div>当前图层采用红色填充和深色边线显示</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLayer(key, forceFit = true) {
|
||||
const spec = LAYERS[key];
|
||||
currentLayerKey = key;
|
||||
setActiveButton(key);
|
||||
setStatus(`加载${spec.label}中...`);
|
||||
setHint(`当前加载:${spec.label}`);
|
||||
manifest = await loadJson(spec.manifestUrl);
|
||||
const grid = await loadJson(spec.gridUrl);
|
||||
const mode = spec.mode || "regular";
|
||||
|
||||
if (map.getSource("navsea-grid")) {
|
||||
map.getSource("navsea-grid").setData(grid);
|
||||
} else {
|
||||
map.addSource("navsea-grid", { type: "geojson", data: grid });
|
||||
map.addLayer({
|
||||
id: "navsea-grid-fill",
|
||||
type: "fill",
|
||||
source: "navsea-grid",
|
||||
paint: {
|
||||
"fill-color": "#ff2b35",
|
||||
"fill-opacity": 0.82,
|
||||
"fill-outline-color": "#111111"
|
||||
}
|
||||
});
|
||||
map.addLayer({
|
||||
id: "navsea-grid-halo",
|
||||
type: "line",
|
||||
source: "navsea-grid",
|
||||
paint: {
|
||||
"line-color": "#ffffff",
|
||||
"line-width": 5,
|
||||
"line-opacity": 0.96
|
||||
}
|
||||
});
|
||||
map.addLayer({
|
||||
id: "navsea-grid-line",
|
||||
type: "line",
|
||||
source: "navsea-grid",
|
||||
paint: {
|
||||
"line-color": "#a40008",
|
||||
"line-width": 3.5,
|
||||
"line-opacity": 1.0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyGridStyle(mode);
|
||||
|
||||
const stats = layerStats(manifest, spec.key);
|
||||
setStatus(`已加载:${spec.label},共 ${stats.count || 0} 个格子。`);
|
||||
|
||||
const bounds = stats.bbox || manifest.grid_bounds_lonlat || manifest.layers?.[spec.key]?.bbox_lonlat;
|
||||
if (forceFit && validLonLatBounds(bounds)) {
|
||||
map.fitBounds([[bounds[0], bounds[1]], [bounds[2], bounds[3]]], {
|
||||
padding: 50,
|
||||
duration: 900
|
||||
});
|
||||
} else {
|
||||
map.easeTo({
|
||||
center: spec.center,
|
||||
zoom: spec.zoom,
|
||||
duration: 700
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
map.on("load", async () => {
|
||||
try {
|
||||
await loadLayer("200", true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus(`加载失败:${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("reloadBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
setStatus(`重新加载中... ${VERSION}`);
|
||||
await loadLayer(currentLayerKey, true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus(`重载失败:${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btn200").addEventListener("click", async () => {
|
||||
try {
|
||||
await loadLayer("200", true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus(`加载失败:${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btn20").addEventListener("click", async () => {
|
||||
try {
|
||||
await loadLayer("20", true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus(`加载失败:${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btn50").addEventListener("click", async () => {
|
||||
try {
|
||||
await loadLayer("50", true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus(`加载失败:${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btnDensity").addEventListener("click", async () => {
|
||||
try {
|
||||
await loadLayer("density", true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus(`加载失败:${err.message}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
645
src/pbf/navsea-compare-full-audit.html
Normal file
645
src/pbf/navsea-compare-full-audit.html
Normal file
@@ -0,0 +1,645 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>NavSea Full Audit Compare</title>
|
||||
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #eef2ed;
|
||||
--panel: rgba(250, 248, 242, 0.94);
|
||||
--ink: #17232b;
|
||||
--muted: #667780;
|
||||
--line: rgba(23, 35, 43, 0.12);
|
||||
--shadow: 0 14px 28px rgba(23, 35, 43, 0.14);
|
||||
--left: #305f72;
|
||||
--right: #8b5e34;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(48, 95, 114, 0.12), transparent 28%),
|
||||
radial-gradient(circle at bottom right, rgba(139, 94, 52, 0.1), transparent 24%),
|
||||
var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: "Avenir Next", "Segoe UI", "PingFang SC", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
align-items: flex-start;
|
||||
padding: 14px 18px 12px;
|
||||
background: linear-gradient(180deg, rgba(250, 248, 242, 0.98), rgba(250, 248, 242, 0.9));
|
||||
border-bottom: 1px solid var(--line);
|
||||
box-shadow: var(--shadow);
|
||||
z-index: 20;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 4px;
|
||||
max-width: 920px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.version-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(23, 35, 43, 0.16);
|
||||
background: rgba(250, 248, 242, 0.9);
|
||||
color: var(--ink);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 10px 22px rgba(23, 35, 43, 0.08);
|
||||
}
|
||||
|
||||
button {
|
||||
height: 40px;
|
||||
padding: 0 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(23, 35, 43, 0.16);
|
||||
background: linear-gradient(180deg, #234a59, #17333f);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 22px rgba(23, 35, 43, 0.18);
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pane {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
border-right: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.pane:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.map {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.pane-tag {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
z-index: 5;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: var(--panel);
|
||||
border: 1px solid rgba(23, 35, 43, 0.08);
|
||||
box-shadow: 0 12px 24px rgba(23, 35, 43, 0.12);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.pane-tag strong {
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pane-tag span {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.inspect-panel {
|
||||
position: fixed;
|
||||
right: 14px;
|
||||
bottom: 14px;
|
||||
z-index: 30;
|
||||
width: min(380px, calc(100vw - 28px));
|
||||
max-height: 42vh;
|
||||
overflow: auto;
|
||||
padding: 11px 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(250, 248, 242, 0.95);
|
||||
border: 1px solid rgba(23, 35, 43, 0.12);
|
||||
box-shadow: var(--shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.inspect-panel h3 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.inspect-hint {
|
||||
margin: 0 0 8px;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.inspect-meta {
|
||||
display: grid;
|
||||
grid-template-columns: 96px 1fr;
|
||||
gap: 4px 8px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.inspect-meta dt {
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.inspect-meta dd {
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.inspect-json {
|
||||
margin: 0;
|
||||
padding: 9px 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(23, 35, 43, 0.93);
|
||||
color: #eef4f7;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 14px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 30;
|
||||
max-width: calc(100% - 420px);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(23, 35, 43, 0.84);
|
||||
color: #f7f9fb;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
body.audit-mode .pane-tag,
|
||||
body.audit-mode .inspect-panel,
|
||||
body.audit-mode .status,
|
||||
body.audit-mode .maplibregl-ctrl-top-right,
|
||||
body.audit-mode .maplibregl-ctrl-bottom-right,
|
||||
body.audit-mode .maplibregl-ctrl-bottom-left {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.pane {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.pane:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.inspect-panel {
|
||||
width: min(360px, calc(100vw - 24px));
|
||||
max-height: 30vh;
|
||||
right: 12px;
|
||||
bottom: 54px;
|
||||
}
|
||||
|
||||
.status {
|
||||
max-width: calc(100% - 24px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="toolbar">
|
||||
<div>
|
||||
<div id="page-title" class="title">全国人工审计对比页</div>
|
||||
<div id="page-subtitle" class="subtitle">左侧固定加载原始 <code>style.json</code> + 原始 newpec 瓦片;右侧固定加载全国 delivery 的 <code>style.navsea-delivery-full.json</code> + 全国 delivery PBF + 当前 sprite。两边联动同步,适合直接审 style / pbf / sprite 的整体视觉差异。</div>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<div id="html-chip" class="version-chip">HTML: 加载中</div>
|
||||
<div id="style-chip" class="version-chip">Style: 加载中</div>
|
||||
<div id="pbf-chip" class="version-chip">PBF: 加载中</div>
|
||||
<div id="sprite-chip" class="version-chip">Sprite: 加载中</div>
|
||||
<button id="reload-btn" type="button">重新加载</button>
|
||||
<button id="home-btn" type="button">回到全国</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
<section class="pane">
|
||||
<div class="pane-tag">
|
||||
<strong style="color: var(--left);">左侧 · 原始版</strong>
|
||||
<span>style.json + 原始 newpec 瓦片</span>
|
||||
</div>
|
||||
<div id="map-original" class="map"></div>
|
||||
</section>
|
||||
<section class="pane">
|
||||
<div class="pane-tag">
|
||||
<strong style="color: var(--right);">右侧 · Delivery</strong>
|
||||
<span id="delivery-pane-label">全国 delivery style + 全国 delivery PBF + 当前 sprite</span>
|
||||
</div>
|
||||
<div id="map-delivery" class="map"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<aside class="inspect-panel">
|
||||
<h3>点击对比</h3>
|
||||
<p id="inspect-hint" class="inspect-hint">点击左右任一地图对象,查看当前命中的 source、source-layer、render layer 和属性。</p>
|
||||
<dl class="inspect-meta">
|
||||
<dt>面板</dt>
|
||||
<dd id="inspect-side">-</dd>
|
||||
<dt>点击坐标</dt>
|
||||
<dd id="inspect-lnglat">-</dd>
|
||||
<dt>source</dt>
|
||||
<dd id="inspect-source">-</dd>
|
||||
<dt>source-layer</dt>
|
||||
<dd id="inspect-layer">-</dd>
|
||||
<dt>geometry</dt>
|
||||
<dd id="inspect-geometry">-</dd>
|
||||
<dt>render layer</dt>
|
||||
<dd id="inspect-render-layer">-</dd>
|
||||
</dl>
|
||||
<pre id="inspect-json" class="inspect-json">{
|
||||
"message": "等待点击对象"
|
||||
}</pre>
|
||||
</aside>
|
||||
|
||||
<div id="status" class="status">等待加载对比样式…</div>
|
||||
|
||||
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
||||
<script>
|
||||
const HTML_VERSION = "full-audit-r3-20260418-1546";
|
||||
const ORIGINAL_STYLE_URL = "./style.json";
|
||||
const ORIGINAL_TILE_URL = "http://192.168.200.184/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles/{z}/{x}/{y}.pbf";
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const auditMode = searchParams.get("audit") === "1";
|
||||
const variantParam = (searchParams.get("variant") || "semantic").trim().toLowerCase();
|
||||
const deliveryTileUrlOverride = (searchParams.get("deliveryTileUrl") || "").trim();
|
||||
const PROFILES = {
|
||||
full: {
|
||||
label: "全国",
|
||||
pageTitle: "全国人工审计对比页",
|
||||
subtitle: "左侧固定加载原始 <code>style.json</code> + 原始 newpec 瓦片;右侧固定加载全国 delivery 版 <code>style.navsea-delivery-full.json</code> + 全国 delivery PBF。两边联动同步,适合直接做原始版与全国 delivery 的截图对比审计。",
|
||||
deliveryPaneLabel: "全国 delivery style + 全国 delivery PBF",
|
||||
deliveryStyleUrl: "./domain/style.navsea-delivery-full.json",
|
||||
deliveryTileUrl: "http://192.168.200.184/pbf-delivery-full-20260415/{z}/{x}/{y}.pbf",
|
||||
deliveryStyleVersion: "full-delivery-style-current",
|
||||
deliveryPbfVersion: "full-delivery-pbf-20260415",
|
||||
deliverySpriteVersion: "newpec-sprite-current",
|
||||
deliveryStyleFile: "style.navsea-delivery-full.json",
|
||||
deliveryPbfName: "pbf-delivery-full-20260415",
|
||||
deliverySpriteName: "newpec/sprite/sprite",
|
||||
initialView: {
|
||||
center: [136.5, 35.8],
|
||||
zoom: 4.8,
|
||||
bearing: 0,
|
||||
pitch: 0
|
||||
},
|
||||
homeLabel: "回到全国"
|
||||
},
|
||||
semantic: {
|
||||
label: "全国",
|
||||
pageTitle: "全国人工审计对比页",
|
||||
subtitle: "左侧固定加载原始 <code>style.json</code> + 原始 newpec 瓦片;右侧固定加载全国 delivery 的语义版 <code>style.navsea-delivery-full-semantic.json</code> + 语义版全国 delivery PBF + 语义版 sprite atlas。两边联动同步,适合直接做原始版与全国语义版的截图对比审计。",
|
||||
deliveryPaneLabel: "全国语义版 delivery style + 语义版全国 delivery PBF + 语义版 sprite",
|
||||
deliveryStyleUrl: "./domain/style.navsea-delivery-full-semantic.json",
|
||||
deliveryTileUrl: "http://192.168.200.184/pbf-delivery-full-semantic-20260416/{z}/{x}/{y}.pbf",
|
||||
deliveryStyleVersion: "full-semantic-style-r3-20260416-2210",
|
||||
deliveryPbfVersion: "full-semantic-pbf-r2-20260416-2248-hazardfix",
|
||||
deliverySpriteVersion: "full-semantic-sprite-r1-20260416-1628",
|
||||
deliveryStyleFile: "style.navsea-delivery-full-semantic.json",
|
||||
deliveryPbfName: "pbf-delivery-full-semantic-20260416",
|
||||
deliverySpriteName: "newpec/sprite-semantic/sprite",
|
||||
initialView: {
|
||||
center: [136.5, 35.8],
|
||||
zoom: 4.8,
|
||||
bearing: 0,
|
||||
pitch: 0
|
||||
},
|
||||
homeLabel: "回到全国"
|
||||
}
|
||||
};
|
||||
const PROFILE = PROFILES[variantParam] || PROFILES.semantic;
|
||||
|
||||
const reloadBtn = document.getElementById("reload-btn");
|
||||
const homeBtn = document.getElementById("home-btn");
|
||||
const pageTitleEl = document.getElementById("page-title");
|
||||
const pageSubtitleEl = document.getElementById("page-subtitle");
|
||||
const deliveryPaneLabelEl = document.getElementById("delivery-pane-label");
|
||||
const htmlChip = document.getElementById("html-chip");
|
||||
const styleChip = document.getElementById("style-chip");
|
||||
const pbfChip = document.getElementById("pbf-chip");
|
||||
const spriteChip = document.getElementById("sprite-chip");
|
||||
const statusEl = document.getElementById("status");
|
||||
const inspectHintEl = document.getElementById("inspect-hint");
|
||||
const inspectSideEl = document.getElementById("inspect-side");
|
||||
const inspectLngLatEl = document.getElementById("inspect-lnglat");
|
||||
const inspectSourceEl = document.getElementById("inspect-source");
|
||||
const inspectLayerEl = document.getElementById("inspect-layer");
|
||||
const inspectGeometryEl = document.getElementById("inspect-geometry");
|
||||
const inspectRenderLayerEl = document.getElementById("inspect-render-layer");
|
||||
const inspectJsonEl = document.getElementById("inspect-json");
|
||||
|
||||
let originalMap;
|
||||
let deliveryMap;
|
||||
let syncLock = false;
|
||||
let styleVersion = `${HTML_VERSION}-${Date.now()}`;
|
||||
|
||||
htmlChip.textContent = `HTML: ${HTML_VERSION}`;
|
||||
if (auditMode) document.body.classList.add("audit-mode");
|
||||
|
||||
function parseNumberParam(value) {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function getRequestedCamera() {
|
||||
const centerValue = searchParams.get("center");
|
||||
const zoomValue = parseNumberParam(searchParams.get("zoom"));
|
||||
const bearingValue = parseNumberParam(searchParams.get("bearing"));
|
||||
const pitchValue = parseNumberParam(searchParams.get("pitch"));
|
||||
|
||||
if (!centerValue && zoomValue === null && bearingValue === null && pitchValue === null) {
|
||||
return {
|
||||
center: PROFILE.initialView.center,
|
||||
zoom: PROFILE.initialView.zoom,
|
||||
bearing: PROFILE.initialView.bearing,
|
||||
pitch: PROFILE.initialView.pitch
|
||||
};
|
||||
}
|
||||
|
||||
let center = PROFILE.initialView.center;
|
||||
if (centerValue) {
|
||||
const parts = centerValue.split(",").map((item) => Number(item.trim()));
|
||||
if (parts.length === 2 && parts.every(Number.isFinite)) {
|
||||
center = [parts[0], parts[1]];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
center,
|
||||
zoom: zoomValue ?? PROFILE.initialView.zoom,
|
||||
bearing: bearingValue ?? PROFILE.initialView.bearing,
|
||||
pitch: pitchValue ?? PROFILE.initialView.pitch
|
||||
};
|
||||
}
|
||||
|
||||
function applyProfileUi() {
|
||||
pageTitleEl.textContent = PROFILE.pageTitle;
|
||||
pageSubtitleEl.innerHTML = PROFILE.subtitle;
|
||||
deliveryPaneLabelEl.textContent = PROFILE.deliveryPaneLabel;
|
||||
homeBtn.textContent = PROFILE.homeLabel;
|
||||
styleChip.textContent = `Style: ${PROFILE.deliveryStyleVersion} (${PROFILE.deliveryStyleFile})`;
|
||||
if (deliveryTileUrlOverride) {
|
||||
pbfChip.textContent = `PBF: override (${deliveryTileUrlOverride})`;
|
||||
} else {
|
||||
pbfChip.textContent = `PBF: ${PROFILE.deliveryPbfVersion} (${PROFILE.deliveryPbfName})`;
|
||||
}
|
||||
spriteChip.textContent = `Sprite: ${PROFILE.deliverySpriteVersion} (${PROFILE.deliverySpriteName})`;
|
||||
document.title = `NavSea Compare · ${PROFILE.label}`;
|
||||
}
|
||||
|
||||
function setStatus(text) {
|
||||
statusEl.textContent = text;
|
||||
}
|
||||
|
||||
function withCacheBuster(url, version) {
|
||||
const separator = url.includes("?") ? "&" : "?";
|
||||
return `${url}${separator}v=${version}`;
|
||||
}
|
||||
|
||||
async function fetchStyle(url) {
|
||||
const response = await fetch(withCacheBuster(url, styleVersion), { cache: "no-store" });
|
||||
if (!response.ok) {
|
||||
throw new Error(`加载样式失败: ${url}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function touchStyleAssets(style) {
|
||||
if (style.sprite) style.sprite = withCacheBuster(style.sprite, styleVersion);
|
||||
if (style.glyphs) style.glyphs = withCacheBuster(style.glyphs, styleVersion);
|
||||
Object.values(style.sources || {}).forEach((source) => {
|
||||
if (Array.isArray(source.tiles)) {
|
||||
source.tiles = source.tiles.map((tileUrl) => withCacheBuster(tileUrl, styleVersion));
|
||||
}
|
||||
if (typeof source.url === "string") {
|
||||
source.url = withCacheBuster(source.url, styleVersion);
|
||||
}
|
||||
});
|
||||
return style;
|
||||
}
|
||||
|
||||
function buildOriginalStyle(style) {
|
||||
const next = structuredClone(style);
|
||||
if (next.sources?.newpec) {
|
||||
next.sources.newpec.tiles = [withCacheBuster(ORIGINAL_TILE_URL, styleVersion)];
|
||||
}
|
||||
return touchStyleAssets(next);
|
||||
}
|
||||
|
||||
function buildDeliveryStyle(style) {
|
||||
const next = structuredClone(style);
|
||||
if (next.sources?.navsea_delivery) {
|
||||
const tileUrl = deliveryTileUrlOverride || PROFILE.deliveryTileUrl;
|
||||
next.sources.navsea_delivery.tiles = [withCacheBuster(tileUrl, styleVersion)];
|
||||
}
|
||||
return touchStyleAssets(next);
|
||||
}
|
||||
|
||||
function resetInspectPanel(message) {
|
||||
inspectHintEl.textContent = message;
|
||||
inspectSideEl.textContent = "-";
|
||||
inspectLngLatEl.textContent = "-";
|
||||
inspectSourceEl.textContent = "-";
|
||||
inspectLayerEl.textContent = "-";
|
||||
inspectGeometryEl.textContent = "-";
|
||||
inspectRenderLayerEl.textContent = "-";
|
||||
inspectJsonEl.textContent = JSON.stringify({ message }, null, 2);
|
||||
}
|
||||
|
||||
function formatLngLat(lngLat) {
|
||||
return `${lngLat.lng.toFixed(6)}, ${lngLat.lat.toFixed(6)}`;
|
||||
}
|
||||
|
||||
function updateInspectPanel(side, event, feature) {
|
||||
inspectHintEl.textContent = "已选中对象。继续点击可对照两侧差异。";
|
||||
inspectSideEl.textContent = side;
|
||||
inspectLngLatEl.textContent = formatLngLat(event.lngLat);
|
||||
inspectSourceEl.textContent = feature.source || "-";
|
||||
inspectLayerEl.textContent = feature.sourceLayer || feature.layer?.["source-layer"] || "-";
|
||||
inspectGeometryEl.textContent = feature.geometry?.type || "-";
|
||||
inspectRenderLayerEl.textContent = feature.layer?.id || "-";
|
||||
inspectJsonEl.textContent = JSON.stringify(feature.properties || {}, null, 2);
|
||||
}
|
||||
|
||||
function bindInspect(map, sideLabel) {
|
||||
map.on("click", (event) => {
|
||||
const features = map.queryRenderedFeatures(event.point);
|
||||
if (!features.length) {
|
||||
resetInspectPanel(`${sideLabel}点击位置没有命中对象。点击坐标: ${formatLngLat(event.lngLat)}`);
|
||||
return;
|
||||
}
|
||||
updateInspectPanel(sideLabel, event, features[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function syncMaps(primary, secondary) {
|
||||
primary.on("move", () => {
|
||||
if (syncLock) return;
|
||||
syncLock = true;
|
||||
secondary.jumpTo({
|
||||
center: primary.getCenter(),
|
||||
zoom: primary.getZoom(),
|
||||
bearing: primary.getBearing(),
|
||||
pitch: primary.getPitch()
|
||||
});
|
||||
syncLock = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMaps() {
|
||||
const requestedCamera = getRequestedCamera();
|
||||
styleVersion = `${HTML_VERSION}-${Date.now()}`;
|
||||
reloadBtn.disabled = true;
|
||||
homeBtn.disabled = true;
|
||||
setStatus(`正在加载全国原始版和 delivery 版样式… HTML ${HTML_VERSION}`);
|
||||
|
||||
try {
|
||||
const [originalStyleRaw, deliveryStyleRaw] = await Promise.all([
|
||||
fetchStyle(ORIGINAL_STYLE_URL),
|
||||
fetchStyle(PROFILE.deliveryStyleUrl)
|
||||
]);
|
||||
const originalStyle = buildOriginalStyle(originalStyleRaw);
|
||||
const deliveryStyle = buildDeliveryStyle(deliveryStyleRaw);
|
||||
|
||||
if (!originalMap && !deliveryMap) {
|
||||
originalMap = new maplibregl.Map({
|
||||
container: "map-original",
|
||||
style: originalStyle,
|
||||
center: requestedCamera.center,
|
||||
zoom: requestedCamera.zoom,
|
||||
bearing: requestedCamera.bearing,
|
||||
pitch: requestedCamera.pitch
|
||||
});
|
||||
deliveryMap = new maplibregl.Map({
|
||||
container: "map-delivery",
|
||||
style: deliveryStyle,
|
||||
center: requestedCamera.center,
|
||||
zoom: requestedCamera.zoom,
|
||||
bearing: requestedCamera.bearing,
|
||||
pitch: requestedCamera.pitch
|
||||
});
|
||||
|
||||
originalMap.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||
deliveryMap.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||
bindInspect(originalMap, "左侧原始版");
|
||||
bindInspect(deliveryMap, "右侧Delivery");
|
||||
syncMaps(originalMap, deliveryMap);
|
||||
syncMaps(deliveryMap, originalMap);
|
||||
|
||||
let loaded = 0;
|
||||
function markLoaded() {
|
||||
loaded += 1;
|
||||
if (loaded >= 2) {
|
||||
setStatus(`全国双屏已加载,可直接拖动、缩放、点击对比。HTML ${HTML_VERSION}`);
|
||||
}
|
||||
}
|
||||
originalMap.on("load", markLoaded);
|
||||
deliveryMap.on("load", markLoaded);
|
||||
} else {
|
||||
const camera = requestedCamera;
|
||||
let loaded = 0;
|
||||
function markLoaded() {
|
||||
loaded += 1;
|
||||
if (loaded >= 2) {
|
||||
originalMap.jumpTo(camera);
|
||||
deliveryMap.jumpTo(camera);
|
||||
setStatus(`全国双屏样式已重新加载。HTML ${HTML_VERSION}`);
|
||||
}
|
||||
}
|
||||
originalMap.once("style.load", markLoaded);
|
||||
deliveryMap.once("style.load", markLoaded);
|
||||
originalMap.setStyle(originalStyle, { diff: false });
|
||||
deliveryMap.setStyle(deliveryStyle, { diff: false });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setStatus(error.message || "加载失败");
|
||||
} finally {
|
||||
reloadBtn.disabled = false;
|
||||
homeBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
reloadBtn.addEventListener("click", loadMaps);
|
||||
homeBtn.addEventListener("click", () => {
|
||||
if (!originalMap || !deliveryMap) return;
|
||||
const camera = {
|
||||
center: PROFILE.initialView.center,
|
||||
zoom: PROFILE.initialView.zoom,
|
||||
bearing: PROFILE.initialView.bearing,
|
||||
pitch: PROFILE.initialView.pitch
|
||||
};
|
||||
window.history.replaceState({}, "", window.location.pathname);
|
||||
originalMap.jumpTo(camera);
|
||||
deliveryMap.jumpTo(camera);
|
||||
setStatus("已回到全国初始视角。");
|
||||
});
|
||||
|
||||
applyProfileUi();
|
||||
resetInspectPanel("点击左右任一地图对象,查看当前命中的 source、source-layer、render layer 和属性。");
|
||||
loadMaps();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
101
src/pbf/navsea-compare-kyushu-semantic-icon-v1.html
Normal file
101
src/pbf/navsea-compare-kyushu-semantic-icon-v1.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>NavSea 九州语义图标替换测试 v1</title>
|
||||
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<style>
|
||||
:root { --bg:#eef2ed; --panel:rgba(250,248,242,.95); --ink:#17232b; --muted:#667780; --line:rgba(23,35,43,.14); }
|
||||
* { box-sizing:border-box; }
|
||||
html,body { margin:0; width:100%; height:100%; overflow:hidden; background:var(--bg); color:var(--ink); font-family:"Avenir Next","Segoe UI","PingFang SC","Noto Sans SC",sans-serif; }
|
||||
body { display:grid; grid-template-rows:auto 1fr; }
|
||||
.toolbar { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:8px 12px; background:var(--panel); border-bottom:1px solid var(--line); box-shadow:0 8px 20px rgba(23,35,43,.12); z-index:10; }
|
||||
.title { font-size:16px; font-weight:700; }
|
||||
.subtitle { margin-top:2px; color:var(--muted); font-size:11px; line-height:1.35; }
|
||||
.actions { display:flex; align-items:center; justify-content:flex-end; gap:7px; flex-wrap:wrap; }
|
||||
.chip { max-width:260px; padding:7px 9px; border:1px solid var(--line); border-radius:999px; background:#fffdf8; font-size:10px; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
button { height:31px; padding:0 11px; border:1px solid rgba(23,35,43,.16); border-radius:8px; background:#17333f; color:#fff; font:inherit; font-size:11px; font-weight:700; cursor:pointer; }
|
||||
button:disabled { opacity:.55; cursor:wait; }
|
||||
.layout { min-height:0; display:grid; grid-template-columns:1fr 1fr; }
|
||||
.pane { position:relative; min-width:0; border-right:1px solid var(--line); }
|
||||
.pane:last-child { border-right:0; }
|
||||
.map { position:absolute; inset:0; }
|
||||
.tag { position:absolute; top:10px; left:10px; z-index:2; padding:8px 10px; border:1px solid var(--line); border-radius:10px; background:var(--panel); box-shadow:0 8px 18px rgba(23,35,43,.12); font-size:11px; }
|
||||
.tag strong { display:block; margin-bottom:2px; font-size:12px; }
|
||||
.tag span { color:var(--muted); }
|
||||
.inspect { position:fixed; right:12px; bottom:12px; z-index:5; width:min(390px,calc(100vw - 24px)); max-height:42vh; overflow:auto; padding:10px 12px; border:1px solid var(--line); border-radius:12px; background:var(--panel); box-shadow:0 12px 26px rgba(23,35,43,.16); }
|
||||
.inspect h3 { margin:0 0 5px; font-size:12px; }
|
||||
.inspect p { margin:0 0 7px; color:var(--muted); font-size:10px; }
|
||||
.inspect dl { display:grid; grid-template-columns:84px 1fr; gap:3px 7px; margin:0 0 7px; font-size:10px; }
|
||||
.inspect dt { color:var(--muted); font-weight:700; }
|
||||
.inspect dd { margin:0; word-break:break-word; }
|
||||
.inspect pre { margin:0; padding:7px; border-radius:8px; background:#17232b; color:#eef4f7; font-size:10px; white-space:pre-wrap; word-break:break-word; }
|
||||
#status { position:fixed; left:50%; bottom:12px; z-index:4; transform:translateX(-50%); max-width:calc(100vw - 430px); padding:7px 11px; border-radius:999px; background:rgba(23,35,43,.86); color:#fff; font-size:10px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
@media (max-width:900px) { .toolbar { align-items:flex-start; flex-direction:column; } .actions { justify-content:flex-start; } #status { max-width:calc(100vw - 24px); bottom:6px; } .inspect { bottom:38px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="toolbar">
|
||||
<div>
|
||||
<div class="title">NavSea 九州语义图标替换测试 v1</div>
|
||||
<div class="subtitle">左侧:原始 newpec;右侧:当前 Full 基线生成的九州语义图标测试版。两侧同步平移、缩放,可点击对象查看属性。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<span id="version" class="chip">HTML: kyushu-semantic-icon-v1-20260806-r2</span>
|
||||
<span id="style" class="chip">Style: 加载中</span>
|
||||
<button id="reload" type="button">重新加载</button>
|
||||
<button id="home" type="button">回到九州</button>
|
||||
</div>
|
||||
</header>
|
||||
<main class="layout">
|
||||
<section class="pane"><div class="tag"><strong style="color:#305f72">左侧 · 原始 newpec</strong><span>原始瓦片 + 原始 sprite</span></div><div id="original" class="map"></div></section>
|
||||
<section class="pane"><div class="tag"><strong style="color:#8b5e34">右侧 · 语义图标测试版</strong><span>Kyushu semantic icon v1</span></div><div id="delivery" class="map"></div></section>
|
||||
</main>
|
||||
<aside class="inspect"><h3>点击对比</h3><p id="hint">点击左右任一地图对象,查看 source-layer、render layer 和属性。</p><dl><dt>面板</dt><dd id="side">-</dd><dt>坐标</dt><dd id="lnglat">-</dd><dt>source-layer</dt><dd id="layer">-</dd><dt>render layer</dt><dd id="render">-</dd></dl><pre id="json">{\n "message": "等待点击对象"\n}</pre></aside>
|
||||
<div id="status">等待加载样式…</div>
|
||||
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
||||
<script>
|
||||
const VERSION = "kyushu-semantic-icon-v1-20260806-r2";
|
||||
const ORIGINAL_STYLE = "./domain/style.navsea-newpec-kyushu-icon-test-v1.json";
|
||||
const DELIVERY_STYLE = "./domain/style.navsea-delivery-kyushu-semantic-icon-v1.json";
|
||||
const CENTER = [130.4, 32.5], ZOOM = 7.5;
|
||||
const q = new URLSearchParams(location.search);
|
||||
const requestedCenter = (q.get("center") || "").split(",").map(Number);
|
||||
const camera = { center: requestedCenter.length === 2 && requestedCenter.every(Number.isFinite) ? requestedCenter : CENTER, zoom: Number(q.get("zoom")) || ZOOM, bearing: Number(q.get("bearing")) || 0, pitch: Number(q.get("pitch")) || 0 };
|
||||
let originalMap, deliveryMap, lock = false, cacheVersion = `${VERSION}-${Date.now()}`;
|
||||
const $ = (id) => document.getElementById(id);
|
||||
function bust(url) { const join = url.includes("?") ? "&" : "?"; return `${url}${join}v=${encodeURIComponent(cacheVersion)}`; }
|
||||
function touch(style) {
|
||||
const next = structuredClone(style);
|
||||
if (next.sprite) next.sprite = bust(next.sprite);
|
||||
if (next.glyphs) next.glyphs = bust(next.glyphs);
|
||||
for (const source of Object.values(next.sources || {})) {
|
||||
if (source.tiles) source.tiles = source.tiles.map(bust);
|
||||
if (source.url) source.url = bust(source.url);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
async function loadStyle(url) { const response = await fetch(bust(url), { cache:"no-store" }); if (!response.ok) throw new Error(`加载样式失败: ${url}`); return touch(await response.json()); }
|
||||
function sync(primary, secondary) { primary.on("move", () => { if (lock) return; lock = true; secondary.jumpTo({ center:primary.getCenter(), zoom:primary.getZoom(), bearing:primary.getBearing(), pitch:primary.getPitch() }); lock = false; }); }
|
||||
function inspect(map, label) { map.on("click", (event) => { const feature = map.queryRenderedFeatures(event.point)[0]; if (!feature) { $("hint").textContent = `${label}:点击位置未命中对象。`; return; } $("hint").textContent = "已选中对象。继续点击可查看另一侧。"; $("side").textContent = label; $("lnglat").textContent = `${event.lngLat.lng.toFixed(6)}, ${event.lngLat.lat.toFixed(6)}`; $("layer").textContent = feature.sourceLayer || "-"; $("render").textContent = feature.layer?.id || "-"; $("json").textContent = JSON.stringify(feature.properties || {}, null, 2); }); }
|
||||
async function start() {
|
||||
$("reload").disabled = true; $("status").textContent = `正在加载九州双屏… ${VERSION}`; cacheVersion = `${VERSION}-${Date.now()}`;
|
||||
try {
|
||||
const [leftStyle, rightStyle] = await Promise.all([loadStyle(ORIGINAL_STYLE), loadStyle(DELIVERY_STYLE)]);
|
||||
$("style").textContent = "Style: newpec 原始 / delivery semantic v1";
|
||||
if (!originalMap) {
|
||||
originalMap = new maplibregl.Map({ container:"original", style:leftStyle, ...camera });
|
||||
deliveryMap = new maplibregl.Map({ container:"delivery", style:rightStyle, ...camera });
|
||||
originalMap.addControl(new maplibregl.NavigationControl(), "top-right"); deliveryMap.addControl(new maplibregl.NavigationControl(), "top-right");
|
||||
sync(originalMap, deliveryMap); sync(deliveryMap, originalMap); inspect(originalMap, "左侧原始版"); inspect(deliveryMap, "右侧语义版");
|
||||
let loaded = 0; const mark = () => { loaded += 1; if (loaded === 2) $("status").textContent = `九州双屏已加载,可拖动、缩放、点击审计。${VERSION}`; }; originalMap.once("load", mark); deliveryMap.once("load", mark);
|
||||
} else {
|
||||
let loaded = 0; const mark = () => { loaded += 1; if (loaded === 2) { originalMap.jumpTo(camera); deliveryMap.jumpTo(camera); $("status").textContent = `九州双屏已重新加载。${VERSION}`; } }; originalMap.once("style.load", mark); deliveryMap.once("style.load", mark); originalMap.setStyle(leftStyle, { diff:false }); deliveryMap.setStyle(rightStyle, { diff:false });
|
||||
}
|
||||
} catch (error) { console.error(error); $("status").textContent = error.message || "加载失败"; } finally { $("reload").disabled = false; }
|
||||
}
|
||||
$("reload").addEventListener("click", start); $("home").addEventListener("click", () => { if (originalMap && deliveryMap) { originalMap.jumpTo(camera); deliveryMap.jumpTo(camera); } }); start();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
101
src/pbf/semantic_sprite_key_map_2026-04-16.json
Normal file
101
src/pbf/semantic_sprite_key_map_2026-04-16.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"fill-daytime-175": "fill_x",
|
||||
"fill-daytime-420": "fill_nasi",
|
||||
"fill-daytime-427": "fill_net_red",
|
||||
"fill-daytime-428": "pattern_fill_obstruction_or_fish_reef",
|
||||
"fill-daytime-745": "fill_mark_r",
|
||||
"fill-daytime-746": "fill_mark_y",
|
||||
"fill-daytime-747": "fill_mark",
|
||||
"fill-daytime-750": "fill_point",
|
||||
"fill-daytime-753": "fill_y",
|
||||
"fill-daytime-754": "pattern_fill_facility_area",
|
||||
"fill-daytime-755": "fill_a",
|
||||
"fill-daytime-900": "fill_box",
|
||||
"fill-daytime-910": "pattern_fill_fishery_or_fish_reef_area",
|
||||
"light-daytime-1": "light_flare_green",
|
||||
"light-daytime-2": "light_flare_red",
|
||||
"light-daytime-3": "light_flare_yellow_white",
|
||||
"line-daytime-712": "line_a",
|
||||
"line-daytime-721": "line_b",
|
||||
"line-daytime-730": "line_c",
|
||||
"line-daytime-748": "line_d",
|
||||
"line-daytime-754": "special_pattern_line_754",
|
||||
"symbol-daytime-300": "coastal_lighthouse",
|
||||
"symbol-daytime-301": "harbor_lighthouse",
|
||||
"symbol-daytime-302": "breakwater_lighthouse",
|
||||
"symbol-daytime-303": "light_minor",
|
||||
"symbol-daytime-30500001": "leading_light_variantt_30500001",
|
||||
"symbol-daytime-30500002": "leading_light_variantt_30500002",
|
||||
"symbol-daytime-30500003": "leading_light_variantt_30500003",
|
||||
"symbol-daytime-30700001": "light_up_green",
|
||||
"symbol-daytime-30700002": "light_up_red",
|
||||
"symbol-daytime-30700003": "leading_mark_variantt_30700003",
|
||||
"symbol-daytime-308": "leading_mark_variantt_308",
|
||||
"symbol-daytime-310": "light_beacon",
|
||||
"symbol-daytime-311350": "port_hand_mark_topmark",
|
||||
"symbol-daytime-311351": "starboard_hand_mark_topmark",
|
||||
"symbol-daytime-311354": "light_beacon_north_cardinal_variantt",
|
||||
"symbol-daytime-311355": "light_beacon_east_cardinal_variantt",
|
||||
"symbol-daytime-311356": "light_beacon_north_cardinal_variantt_311356",
|
||||
"symbol-daytime-311357": "light_beacon_south_cardinal_variantt",
|
||||
"symbol-daytime-311358": "light_beacon_west_cardinal_variantt_311358",
|
||||
"symbol-daytime-311359": "light_beacon_isolated_danger_variantt_311359",
|
||||
"symbol-daytime-311360": "light_beacon_safe_water_variantt_311360",
|
||||
"symbol-daytime-312350": "buoy_port_lateral_variantt_312350",
|
||||
"symbol-daytime-312351": "buoy_starboard_lateral_variantt_312351",
|
||||
"symbol-daytime-312354": "buoy_north_cardinal",
|
||||
"symbol-daytime-312355": "buoy_east_cardinal",
|
||||
"symbol-daytime-312356": "buoy_west_cardinal",
|
||||
"symbol-daytime-312357": "buoy_south_cardinal",
|
||||
"symbol-daytime-312358": "buoy_isolated_danger",
|
||||
"symbol-daytime-312359": "buoy_safe_water",
|
||||
"symbol-daytime-312360": "buoy_special_mark",
|
||||
"symbol-daytime-313362": "mooring_buoy",
|
||||
"symbol-daytime-314362": "safe_water_mark",
|
||||
"symbol-daytime-316": "mark_box_box",
|
||||
"symbol-daytime-317": "mark_with_topmark",
|
||||
"symbol-daytime-320": "buoy_light",
|
||||
"symbol-daytime-321": "buoy_generic",
|
||||
"symbol-daytime-323": "buoy_pillar",
|
||||
"symbol-daytime-324350": "port_hand_mark",
|
||||
"symbol-daytime-324351": "starboard_hand_mark",
|
||||
"symbol-daytime-324360": "special_mark",
|
||||
"symbol-daytime-325": "buoy_can",
|
||||
"symbol-daytime-327": "buoy_lattice",
|
||||
"symbol-daytime-335359": "nav_mark_vais",
|
||||
"symbol-daytime-401": "rock_exposed_drying_awash_group",
|
||||
"symbol-daytime-404": "sunken_rock",
|
||||
"symbol-daytime-405": "cleared_danger_object",
|
||||
"symbol-daytime-407": "coral_red",
|
||||
"symbol-daytime-408": "coral_black",
|
||||
"symbol-daytime-409": "isolated_danger",
|
||||
"symbol-daytime-410": "wreck_hull_exposed",
|
||||
"symbol-daytime-412": "wreck_fully_submerged_dangerous",
|
||||
"symbol-daytime-413": "wreck_surveyed",
|
||||
"symbol-daytime-414": "unknown_tu_b",
|
||||
"symbol-daytime-420": "foul_ground",
|
||||
"symbol-daytime-421": "sand_wave",
|
||||
"symbol-daytime-422": "tide_rips_overfalls",
|
||||
"symbol-daytime-424": "eddy_whirlpool",
|
||||
"symbol-daytime-425": "seaweed",
|
||||
"symbol-daytime-426": "unknown_t_object",
|
||||
"symbol-daytime-427": "obstruction",
|
||||
"symbol-daytime-428": "fish_reef",
|
||||
"symbol-daytime-429": "fish_reef_dangerous",
|
||||
"symbol-daytime-430": "fill_box_point",
|
||||
"symbol-daytime-431": "tower_yagura_observation_platform",
|
||||
"symbol-daytime-432": "bollard_pile_stake",
|
||||
"symbol-daytime-433": "dolphin_structure",
|
||||
"symbol-daytime-434": "subsea_installation_outfall_intake",
|
||||
"symbol-daytime-498": "fill_box_black",
|
||||
"symbol-daytime-500": "pilot_station",
|
||||
"symbol-daytime-505": "port_general_small_harbor_group",
|
||||
"symbol-daytime-520": "fishing_port",
|
||||
"symbol-daytime-530": "marina",
|
||||
"symbol-daytime-540": "fisherina",
|
||||
"symbol-daytime-550": "sea_station_umi_no_eki",
|
||||
"symbol-daytime-719": "anchorage_quarantine",
|
||||
"symbol-daytime-720": "anchorage_designated",
|
||||
"symbol-daytime-721": "restriction_area_group",
|
||||
"symbol-daytime-724": "anchoring_prohibited"
|
||||
}
|
||||
2932
src/pbf/style.navsea-delivery-full-semantic-extentfix.json
Normal file
2932
src/pbf/style.navsea-delivery-full-semantic-extentfix.json
Normal file
File diff suppressed because it is too large
Load Diff
2680
src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json
Normal file
2680
src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json
Normal file
File diff suppressed because it is too large
Load Diff
5656
src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json
Normal file
5656
src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json
Normal file
File diff suppressed because it is too large
Load Diff
37
tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json
Normal file
37
tasks/pbf/NavSea_Full_AOI_Visual_Audit_2026-04-18.json
Normal file
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"id": "hakata_10nm",
|
||||
"label": "博多港中心 10 海里",
|
||||
"center": [130.335, 33.6385],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "tokyo_bay_10nm",
|
||||
"label": "东京湾 10 海里",
|
||||
"center": [139.85, 35.42],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "osaka_bay_10nm",
|
||||
"label": "大阪 10 海里",
|
||||
"center": [135.35, 34.61],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "okinawa_10nm",
|
||||
"label": "冲绳 10 海里",
|
||||
"center": [127.67, 26.21],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "karatsu_5nm",
|
||||
"label": "唐津 5 海里",
|
||||
"center": [129.9697, 33.4425],
|
||||
"radius_nm": 5,
|
||||
"zoom_levels": [10, 12]
|
||||
}
|
||||
]
|
||||
16
tasks/pbf/NavSea_Kyushu_语义图标视觉审计范围-v1.json
Normal file
16
tasks/pbf/NavSea_Kyushu_语义图标视觉审计范围-v1.json
Normal file
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id": "hakata_kyushu_icon_v1",
|
||||
"label": "博多港九州语义图标测试",
|
||||
"center": [130.335, 33.6385],
|
||||
"radius_nm": 10,
|
||||
"zoom_levels": [10, 12]
|
||||
},
|
||||
{
|
||||
"id": "karatsu_kyushu_icon_v1",
|
||||
"label": "唐津九州语义图标测试",
|
||||
"center": [129.9697, 33.4425],
|
||||
"radius_nm": 5,
|
||||
"zoom_levels": [10, 12]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,477 @@
|
||||
old_sprite_key,used_in_newpec_style,used_in_pbf,usage_note,manual_label_ja,new_sprite_key,basis,source_page
|
||||
symbol-daytime-428,2,3322,style_and_pbf,魚礁,fish_reef [manual],manual,guide115_08
|
||||
symbol-daytime-301,11,2830,style_and_pbf,港湾灯台,harbor_lighthouse,manual,guide115_02
|
||||
symbol-daytime-303,9,1581,style_and_pbf,,light_minor,,
|
||||
symbol-daytime-405,2,1286,style_and_pbf,掃海済みの危険物,cleared_danger_object [manual],manual,guide115_07_20260120
|
||||
symbol-daytime-310,18,927,style_and_pbf,,light_beacon,,
|
||||
fill-daytime-428,2,899,style_and_pbf,,pattern_fill_obstruction_or_fish_reef,,
|
||||
symbol-daytime-320,12,810,style_and_pbf,,buoy_light,,
|
||||
symbol-daytime-327,18,573,style_and_pbf,,buoy_lattice,,
|
||||
symbol-daytime-429,2,554,style_and_pbf,魚礁(危険なもの),fish_reef_dangerous [manual],manual,guide115_08
|
||||
fill-daytime-754,1,446,style_and_pbf,,pattern_fill_facility_area,,
|
||||
symbol-daytime-30500003,7,328,style_and_pbf,,leading_light_variant_30500003,,
|
||||
symbol-daytime-413,4,319,style_and_pbf,測定済みの沈船,wreck_surveyed [manual],manual,guide115_08
|
||||
symbol-daytime-321,5,311,style_and_pbf,,buoy_generic,,
|
||||
symbol-daytime-323,8,238,style_and_pbf,,buoy_pillar,,
|
||||
symbol-daytime-719,1,150,style_and_pbf,検疫錨地,anchorage_quarantine [manual],manual,guide115_01
|
||||
symbol-daytime-412,2,111,style_and_pbf,危険全沈没船,wreck_fully_submerged_dangerous [manual],manual,guide115_08
|
||||
symbol-daytime-308,1,110,style_and_pbf,,leading_mark_variant_308,,
|
||||
symbol-daytime-30700003,4,87,style_and_pbf,,leading_mark_variant_30700003,,
|
||||
symbol-daytime-325,7,76,style_and_pbf,,buoy_can,,
|
||||
symbol-daytime-30500001,1,50,style_and_pbf,,leading_light_variant_30500001,,
|
||||
symbol-daytime-30500002,2,42,style_and_pbf,,leading_light_variant_30500002,,
|
||||
symbol-daytime-335359,2,40,style_and_pbf,,nav_mark_vais,,
|
||||
symbol-daytime-410,4,34,style_and_pbf,船体露出沈船,wreck_hull_exposed,manual,guide115_08
|
||||
symbol-daytime-300,13,0,style_only,沿岸灯台,coastal_lighthouse,manual,guide115_02
|
||||
fill-daytime-421,12,0,style_only,,,,
|
||||
symbol-daytime-302,11,0,style_only,防波堤灯台,breakwater_lighthouse,manual,guide115_02
|
||||
symbol-daytime-314,8,0,style_only,,,,
|
||||
symbol-daytime-401,6,0,style_only,水上岩・干出岩・洗岩(共通),rock_exposed_drying_awash_group [manual],manual,guide115_07_20260120
|
||||
fill-daytime-407,4,0,style_only,,,,
|
||||
symbol-daytime-333,4,0,style_only,,,,
|
||||
fill-daytime-401,3,0,style_only,,,,
|
||||
fill-daytime-756,3,0,style_only,,,,
|
||||
symbol-daytime-312356,3,0,style_only,西方位浮標,buoy_west_cardinal [manual],manual,guide115_04
|
||||
symbol-daytime-312357,3,0,style_only,南方位浮標,buoy_south_cardinal [manual],manual,guide115_04
|
||||
symbol-daytime-312360,3,0,style_only,特殊標識浮標,buoy_special_mark [manual],manual,guide115_04
|
||||
symbol-daytime-313362,3,0,style_only,係船浮標,mooring_buoy [manual],manual,guide115_04
|
||||
symbol-daytime-314362,3,0,style_only,,,,
|
||||
symbol-daytime-317,3,0,style_only,,,,
|
||||
symbol-daytime-721,3,0,style_only,制限区域・航路横断等禁止区域・航泊禁止区域(共通),restriction_area_group [manual],manual,guide115_02
|
||||
fill-daytime-420,2,0,style_only,,,,
|
||||
fill-daytime-427,2,0,style_only,,,,
|
||||
fill-daytime-429,2,0,style_only,,,,
|
||||
line-daytime-710,2,0,style_only,,,,
|
||||
line-daytime-713,2,0,style_only,,,,
|
||||
symbol-daytime-311354,2,0,style_only,左舷標識灯標(推定),light_beacon_port_lateral_variant_311354 [manual?],manual?,guide115_03
|
||||
symbol-daytime-311355,2,0,style_only,右舷標識灯標(推定),light_beacon_starboard_lateral_variant_311355 [manual?],manual?,guide115_03
|
||||
symbol-daytime-311357,2,0,style_only,東方位標識灯標(推定),light_beacon_east_cardinal_variant_311357 [manual?],manual?,guide115_03
|
||||
symbol-daytime-311358,2,0,style_only,西方位標識灯標(推定),light_beacon_west_cardinal_variant_311358 [manual?],manual?,guide115_03
|
||||
symbol-daytime-311359,2,0,style_only,孤立障害標識灯標(推定),light_beacon_isolated_danger_variant_311359 [manual?],manual?,guide115_03
|
||||
symbol-daytime-311360,2,0,style_only,安全水域標識灯標(推定),light_beacon_safe_water_variant_311360 [manual?],manual?,guide115_03
|
||||
symbol-daytime-312350,2,0,style_only,左舷浮標(推定),buoy_port_lateral_variant_312350 [manual?],manual?,guide115_04
|
||||
symbol-daytime-312354,2,0,style_only,北方位浮標,buoy_north_cardinal [manual],manual,guide115_04
|
||||
symbol-daytime-312358,2,0,style_only,孤立障害浮標,buoy_isolated_danger [manual],manual,guide115_04
|
||||
symbol-daytime-316,2,0,style_only,,,,
|
||||
symbol-daytime-324360,2,0,style_only,,,,
|
||||
symbol-daytime-404,2,0,style_only,暗岩,sunken_rock [manual],manual,guide115_07_20260120
|
||||
symbol-daytime-406,2,0,style_only,,,,
|
||||
symbol-daytime-407,2,0,style_only,,,,
|
||||
symbol-daytime-408,2,0,style_only,,,,
|
||||
symbol-daytime-409,2,0,style_only,孤立危険物,isolated_danger [manual],manual,guide115_08
|
||||
symbol-daytime-414,2,0,style_only,,,,
|
||||
symbol-daytime-420,2,0,style_only,険悪物,foul_ground [manual],manual,guide115_08
|
||||
symbol-daytime-421,2,0,style_only,サンドウェーブ,sand_wave [manual],manual,guide115_08
|
||||
symbol-daytime-422,2,0,style_only,急潮・波紋・激潮,tide_rips_overfalls [manual],manual,guide115_08
|
||||
symbol-daytime-424,2,0,style_only,渦流,eddy_whirlpool [manual],manual,guide115_08
|
||||
symbol-daytime-425,2,0,style_only,海草,seaweed [manual],manual,guide115_08
|
||||
symbol-daytime-426,2,0,style_only,,,,
|
||||
symbol-daytime-427,2,0,style_only,障害物,obstruction [manual],manual,guide115_08
|
||||
symbol-daytime-430,2,0,style_only,,,,
|
||||
symbol-daytime-431,2,0,style_only,塔、やぐら、測台,tower_yagura_observation_platform [manual],manual,guide115_08
|
||||
symbol-daytime-432,2,0,style_only,ボッシーン、パイル、杭,bollard_pile_stake [manual],manual,guide115_08
|
||||
symbol-daytime-433,2,0,style_only,ドルフィン,dolphin_structure [manual],manual,guide115_08
|
||||
symbol-daytime-434,2,0,style_only,海底設置物、放水口、取水口,subsea_installation_outfall_intake [manual],manual,guide115_08
|
||||
symbol-daytime-498,2,0,style_only,,,,
|
||||
symbol-daytime-505,2,0,style_only,一般港湾・小港湾(共通),port_general_small_harbor_group [manual],manual,guide115_01
|
||||
arrow-daytime-0,1,0,style_only,,,,
|
||||
arrow-daytime-1,1,0,style_only,,,,
|
||||
arrow-daytime-2,1,0,style_only,,,,
|
||||
arrow-daytime-3,1,0,style_only,,,,
|
||||
arrow-daytime-4,1,0,style_only,,,,
|
||||
fill-daytime-175,1,0,style_only,,,,
|
||||
fill-daytime-745,1,0,style_only,,,,
|
||||
fill-daytime-746,1,0,style_only,,,,
|
||||
fill-daytime-747,1,0,style_only,,,,
|
||||
fill-daytime-750,1,0,style_only,,,,
|
||||
fill-daytime-753,1,0,style_only,,,,
|
||||
fill-daytime-755,1,0,style_only,,,,
|
||||
fill-daytime-900,1,0,style_only,,,,
|
||||
fill-daytime-910,1,0,style_only,漁業区・魚礁エリア塗り,pattern_fill_fishery_or_fish_reef_area [style],style,style.navsea-delivery-full.json
|
||||
light-daytime-1,1,0,style_only,灯光(緑),light_flare_green [manual],manual,guide115_02
|
||||
light-daytime-2,1,0,style_only,灯光(赤),light_flare_red [manual],manual,guide115_02
|
||||
light-daytime-3,1,0,style_only,灯光(黄・白),light_flare_yellow_white [manual],manual,guide115_02
|
||||
line-daytime-712,1,0,style_only,,,,
|
||||
line-daytime-721,1,0,style_only,,,,
|
||||
line-daytime-730,1,0,style_only,,,,
|
||||
line-daytime-748,1,0,style_only,,,,
|
||||
line-daytime-754,1,0,style_only,特殊パターン線 754,special_pattern_line_754 [style],style,style.navsea-delivery-full.json
|
||||
symbol-daytime-30700001,1,0,style_only,,,,
|
||||
symbol-daytime-30700002,1,0,style_only,,,,
|
||||
symbol-daytime-311350,1,0,style_only,,,,
|
||||
symbol-daytime-311351,1,0,style_only,,,,
|
||||
symbol-daytime-311356,1,0,style_only,北方位標識灯標(推定),light_beacon_north_cardinal_variant_311356 [manual?],manual?,guide115_03
|
||||
symbol-daytime-312351,1,0,style_only,右舷浮標(推定),buoy_starboard_lateral_variant_312351 [manual?],manual?,guide115_04
|
||||
symbol-daytime-312355,1,0,style_only,東方位浮標,buoy_east_cardinal [manual],manual,guide115_04
|
||||
symbol-daytime-312359,1,0,style_only,安全水域浮標,buoy_safe_water [manual],manual,guide115_04
|
||||
symbol-daytime-324350,1,0,style_only,,,,
|
||||
symbol-daytime-324351,1,0,style_only,,,,
|
||||
symbol-daytime-335350,1,0,style_only,,,,
|
||||
symbol-daytime-335351,1,0,style_only,,,,
|
||||
symbol-daytime-500,1,0,style_only,パイロットステーション,pilot_station [manual],manual,guide115_01
|
||||
symbol-daytime-520,1,0,style_only,漁港,fishing_port [manual],manual,guide115_01
|
||||
symbol-daytime-530,1,0,style_only,マリーナ,marina [manual],manual,guide115_01
|
||||
symbol-daytime-540,1,0,style_only,フィッシャリーナ,fisherina [manual],manual,guide115_01
|
||||
symbol-daytime-550,1,0,style_only,海の駅,sea_station_umi_no_eki [manual],manual,guide115_01
|
||||
symbol-daytime-720,1,0,style_only,錨泊(指定)地,anchorage_designated [manual],manual,guide115_01
|
||||
symbol-daytime-724,1,0,style_only,錨泊禁止区域,anchoring_prohibited [manual],manual,guide115_02
|
||||
symbol-daytime-759,1,0,style_only,,,,
|
||||
symbol-daytime-760,1,0,style_only,,,,
|
||||
symbol-daytime-761,1,0,style_only,,,,
|
||||
tidespot-daytime,1,0,style_only,,,,
|
||||
arc-daytime-0105306901192310,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-01180035,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-01293219,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0200103104031001,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-02002252,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0201102104021011,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-02048301,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0206408002180297,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-02113325,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0222231004310222,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0223726704267237,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0225529904299089,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-02257145,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-02300210,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-02317310,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04002244,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04004246,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04006315,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04007277,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04008243,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04010293,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04014213,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04016181,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04016291,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04018198,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04018258,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04020236,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04020243,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04021213,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04021253,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04022202,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04022307,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04024258,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04025273,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04027289,0,0,unused_in_current_full,灯弧(黄・白系),,,
|
||||
arc-daytime-04028287,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04029268,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04029280,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04030275,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04031330,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04033249,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04035209,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04035221042430270202703502221243,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04035283,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04036216,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04040253,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04043223,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04045135,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04048213,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04051314,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-040522800208109201052073,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04052305,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04053279,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04054226,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04057308,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04060257,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04060319,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0406126602079097,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04063251042640410204106302251264,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04068298,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04070270,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04070314,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04070336,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04073309,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04077286,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04079310,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04079335,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04081293,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04082332,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0408402102021084,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0408626602266086,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04091357,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04092292,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04093033,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04093340,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04094000,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-040951040203509502104170,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04095312,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04097050,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04097335,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04098000,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04099318,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04102142,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04102355,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04104012,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04105013,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04105020,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0410533902339015,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04106344,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04107043,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04108347,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04109033,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04109316,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04115052,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04116064,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0411609202092116,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04118329,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-041201250413031202125130,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04121318,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04124324,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04125351,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04132318,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04133030,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04135320,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04138018,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0413935602106139,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04140012,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04140341,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04152017,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04155005,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04156008,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04156335,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04159010,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04160006,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04160329,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04162032,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04166358,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04168018,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04169065,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-041702050421505102205215,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04172080,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04173082,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04175017,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04177071,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04178066,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04180008,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04180060,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04180062,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0418112902129181,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04182090,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04185055,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04185095,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04187142,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04190057,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04190085,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04194029,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04195021,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04197017,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04198097,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04198358,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04199019,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0419907902043079,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04199343,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04201091,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04203099,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04204043,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04204099,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04205045,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04205097,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04207038,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0420704802048070,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04209116,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04210091,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04213013,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04215162,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04216098,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04217119,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04221052,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04221104,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04222055,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04222063,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04223137,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04229073,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04230050,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04230076,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-042310350405610002035056,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0423116704125167,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04232050,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0423222602226232,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04233110,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-042352850429530504320170,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04237100,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04240170,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0424122702227241,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04242150,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04245092,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0424628604296140,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04251115,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0425408002080101,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04254169,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04255075,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04257132,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04257134,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-042572820429532304010148022172570228229502323010,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04259113,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04261114,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04263083,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04265214,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0426524202242265,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04266162,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04269146,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04270080,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0427021502215270,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04271113,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04271115,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04272211,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-0427410502250274,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04274157,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04275131,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04278130,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04279098,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04285147,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-042900770207709004090189,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04295180,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04297154,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04297190,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04297254,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04300156,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04301172,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04301175,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04306184,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04307120,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04311219,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04313145,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04314107,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04314201,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04318146,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04323171,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04323233,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04325262,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04326162,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04326164,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04327228,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04331151,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04334203,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04336243,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04343296,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04346177,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04350210,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04352232,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04356186,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-04358167,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-05177027,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-06172050,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-06191091,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-07207029,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-07207075,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-07357229,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08007250,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08024245,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08034279,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08057336,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08068287,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08189045,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08208134,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08241098,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08255101,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-08350232,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-l3,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-m1,0,0,unused_in_current_full,灯弧(緑),,,
|
||||
arc-daytime-m2,0,0,unused_in_current_full,灯弧(赤),,,
|
||||
arc-daytime-m3,0,0,unused_in_current_full,灯弧(黄・白),,,
|
||||
arc-daytime-s1,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-s2,0,0,unused_in_current_full,,,,
|
||||
arc-daytime-s3,0,0,unused_in_current_full,,,,
|
||||
shipfinder-hull-blue,0,0,unused_in_current_full,,,,
|
||||
shipfinder-hull-gray,0,0,unused_in_current_full,,,,
|
||||
shipfinder-hull-green,0,0,unused_in_current_full,,,,
|
||||
shipfinder-hull-orange,0,0,unused_in_current_full,,,,
|
||||
shipfinder-hull-red,0,0,unused_in_current_full,,,,
|
||||
shipfinder-hull-yellow,0,0,unused_in_current_full,,,,
|
||||
shipfinder-left,0,0,unused_in_current_full,,,,
|
||||
shipfinder-lost,0,0,unused_in_current_full,,,,
|
||||
shipfinder-right,0,0,unused_in_current_full,,,,
|
||||
shipfinder-sog1,0,0,unused_in_current_full,,,,
|
||||
shipfinder-sog2,0,0,unused_in_current_full,,,,
|
||||
shipfinder-sog3,0,0,unused_in_current_full,,,,
|
||||
shipfinder-sog4,0,0,unused_in_current_full,,,,
|
||||
shipfinder-sog5,0,0,unused_in_current_full,,,,
|
||||
shipfinder-sog6,0,0,unused_in_current_full,,,,
|
||||
shipfinder-straight,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-311362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314350,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314351,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-314360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316350,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316351,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-316362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318350,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318351,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-318362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321350,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321351,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-321362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-324362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-331,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332350,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332351,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-332362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333350,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333351,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333359,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-333362,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335354,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335355,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335356,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335357,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335358,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335360,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-335374,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-590,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-640,0,0,unused_in_current_full,煙突,,,
|
||||
symbol-daytime-650,0,0,unused_in_current_full,塔、やぐら、風車,,,
|
||||
symbol-daytime-660,0,0,unused_in_current_full,海事関係署,,,
|
||||
symbol-daytime-661,0,0,unused_in_current_full,漁業協同組合,,,
|
||||
symbol-daytime-670,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-680,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-681,0,0,unused_in_current_full,山頂,,,
|
||||
symbol-daytime-682,0,0,unused_in_current_full,,,,
|
||||
symbol-daytime-698,0,0,unused_in_current_full,その他(記念碑等),,,
|
||||
|
34
tasks/pbf/NavSea_Sprite_Basis_T_建议修正表_2026-04-16.csv
Normal file
34
tasks/pbf/NavSea_Sprite_Basis_T_建议修正表_2026-04-16.csv
Normal file
@@ -0,0 +1,34 @@
|
||||
row,old_sprite_key,current_new_sprite_key,suggested_new_sprite_key,reason,used_in_newpec_style,used_in_pbf,manual_label_ja,source_page
|
||||
38,symbol-daytime-314362,safe water mark,safe_water_mark,去空格,统一为下划线命名。,3,0,,
|
||||
39,symbol-daytime-317,mark_bang,mark_with_topmark,原 token `bang` 语义不清,先改成可读的占位语义。,3,0,,
|
||||
41,fill-daytime-420,fille_nasi,fill_nasi,`fille` 疑似拼写误差;建议统一 fill 前缀。,2,0,,
|
||||
42,fill-daytime-427,fille_net_red,fill_net_red,`fille` 疑似拼写误差;建议统一 fill 前缀。,2,0,,
|
||||
46,symbol-daytime-311354,light_beacon_north_cardinal_varian,light_beacon_north_cardinal_variant,修正 `varian` 拼写。,2,0,,guide115_03
|
||||
47,symbol-daytime-311355,light_beacon_easte_cardinal_varian,light_beacon_east_cardinal_variant,修正 `easte/varian` 拼写。,2,0,,guide115_03
|
||||
48,symbol-daytime-311357,light_beacon_sourth_cardinal_varian,light_beacon_south_cardinal_variant,修正 `sourth/varian` 拼写。,2,0,,guide115_03
|
||||
55,symbol-daytime-316,mark_box_box,mark_box_box,格式可接受,先保留。,2,0,,
|
||||
56,symbol-daytime-324360,special_mark,special_mark,格式可接受,先保留。,2,0,special mark,
|
||||
59,symbol-daytime-407,coral_red,coral_red,格式可接受,先保留。,2,0,,
|
||||
60,symbol-daytime-408,coral_black,coral_black,格式可接受,先保留。,2,0,,
|
||||
62,symbol-daytime-414,tu_b,unknown_tu_b,`tu_b` 暂无法直读,建议保留 unknown 前缀等待确认。,2,0,,
|
||||
68,symbol-daytime-426,t_object,unknown_t_object,`t_object` 暂无法直读,建议保留 unknown 前缀等待确认。,2,0,,
|
||||
70,symbol-daytime-430,fille_box_point,fill_box_point,`fille` 疑似拼写误差;建议统一 fill 前缀。,2,0,,
|
||||
75,symbol-daytime-498,fille_box_black,fill_box_black,`fille` 疑似拼写误差;建议统一 fill 前缀。,2,0,,
|
||||
82,fill-daytime-175,fille_X,fill_x,`fille` 疑似拼写误差;字母建议统一小写。,1,0,,
|
||||
83,fill-daytime-745,fille_mark_r,fill_mark_r,`fille` 疑似拼写误差。,1,0,,
|
||||
84,fill-daytime-746,fille_mark_y,fill_mark_y,`fille` 疑似拼写误差。,1,0,,
|
||||
85,fill-daytime-747,fille_mark,fill_mark,`fille` 疑似拼写误差。,1,0,,
|
||||
86,fill-daytime-750,fille_point,fill_point,`fille` 疑似拼写误差。,1,0,,
|
||||
87,fill-daytime-753,fille_y,fill_y,`fille` 疑似拼写误差;字母建议统一小写。,1,0,,
|
||||
88,fill-daytime-755,fille_A,fill_a,`fille` 疑似拼写误差;字母建议统一小写。,1,0,,
|
||||
89,fill-daytime-900,fille_box,fill_box,`fille` 疑似拼写误差。,1,0,,
|
||||
94,line-daytime-712,line_a,line_a,格式可接受,先保留。,1,0,,
|
||||
95,line-daytime-721,line_b,line_b,格式可接受,先保留。,1,0,,
|
||||
96,line-daytime-730,line_c,line_c,格式可接受,先保留。,1,0,,
|
||||
97,line-daytime-748,line_d,line_d,格式可接受,先保留。,1,0,,
|
||||
99,symbol-daytime-30700001,light_up_green,light_up_green,格式可接受,先保留。,1,0,,
|
||||
100,symbol-daytime-30700002,light_up_red,light_up_red,格式可接受,先保留。,1,0,,
|
||||
101,symbol-daytime-311350,port-hand mark_bang,port_hand_mark_topmark,去空格与连字符,`bang` 改为更可读的 `topmark`。,1,0,左舷標識,
|
||||
102,symbol-daytime-311351,starboard-hand mark_bang,starboard_hand_mark_topmark,去空格与连字符,`bang` 改为更可读的 `topmark`。,1,0,右舷標識,
|
||||
107,symbol-daytime-324350,port-hand mark,port_hand_mark,去空格与连字符,统一下划线命名。,1,0,左舷標識,
|
||||
108,symbol-daytime-324351,starboard-hand mark,starboard_hand_mark,去空格与连字符,统一下划线命名。,1,0,右舷標識,
|
||||
|
156
tasks/pbf/NavSea_语义图标替换表-v1.md
Normal file
156
tasks/pbf/NavSea_语义图标替换表-v1.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# NavSea 语义图标替换表-v1
|
||||
|
||||
状态:设计稿,尚未写入 PBF、style 或 sprite。
|
||||
|
||||
基线:
|
||||
|
||||
- PBF:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
|
||||
- style:`src/pbf/style.navsea-delivery-full-semantic-extentfix.json`
|
||||
- sprite:`/mnt/sda1/www/newpec/sprite-semantic/sprite`
|
||||
|
||||
## 1. 统一规则
|
||||
|
||||
PBF、style 和 `sprite.json` 使用同一个短语 ID。PBF 中建议使用:
|
||||
|
||||
```text
|
||||
icon_id:普通点状图标
|
||||
arc_id:灯弧图标
|
||||
```
|
||||
|
||||
`class_code` 继续保留用于对象追溯,但不再被 style 用来拼接图标名称。
|
||||
|
||||
颜色缩写:
|
||||
|
||||
| 缩写 | 含义 |
|
||||
|---|---|
|
||||
| `G` | 绿色 |
|
||||
| `R` | 红色 |
|
||||
| `Y` | 黄色 |
|
||||
| `W` | 白色 |
|
||||
| `B` | 蓝色 |
|
||||
| `YW` | 黄/白组合 |
|
||||
|
||||
## 2. 灯弧
|
||||
|
||||
| 当前 key | 新 ID | 条件/含义 |
|
||||
|---|---|---|
|
||||
| `arc-daytime-m1` | `arc_G` | 绿色闭合灯弧 |
|
||||
| `arc-daytime-m2` | `arc_R` | 红色闭合灯弧 |
|
||||
| `arc-daytime-m3` | `arc_YW` | 黄/白闭合灯弧 |
|
||||
| `arc-daytime-04027289` | `arc_open_YW` | `coastal_lighthouse_over_15m` 且 `light_sector_mode=sector` |
|
||||
|
||||
普通灯弧按 `light_color_code` 选择 `arc_G`、`arc_R` 或 `arc_YW`;扇弧条件优先级最高。
|
||||
|
||||
## 3. 航标、灯塔、浮标
|
||||
|
||||
| 当前 key | 新 ID | 含义 |
|
||||
|---|---|---|
|
||||
| `symbol-daytime-300` | `lt_cst` | 沿岸灯台 |
|
||||
| `symbol-daytime-301` | `lt_hbr` | 港湾灯台 |
|
||||
| `symbol-daytime-302` | `lt_bkw` | 防波堤灯台 |
|
||||
| `symbol-daytime-303` | `lt_min` | 小型灯 |
|
||||
| `symbol-daytime-30500001` | `lt_lead_a` | 导标变体 A |
|
||||
| `symbol-daytime-30500002` | `lt_lead_b` | 导标变体 B |
|
||||
| `symbol-daytime-30500003` | `lt_lead_c` | 导标变体 C |
|
||||
| `symbol-daytime-30700001` | `lt_up_G` | 绿色上向灯 |
|
||||
| `symbol-daytime-30700002` | `lt_up_R` | 红色上向灯 |
|
||||
| `symbol-daytime-30700003` | `mk_lead_c` | 导标变体 |
|
||||
| `symbol-daytime-308` | `mk_lead_v` | 导标变体 |
|
||||
| `symbol-daytime-310` | `lt_bcn` | 灯标 |
|
||||
| `symbol-daytime-320` | `bu_lit` | 灯浮标 |
|
||||
| `symbol-daytime-321` | `bu_gen` | 普通浮标 |
|
||||
| `symbol-daytime-323` | `bu_pil` | 圆柱/柱型浮标 |
|
||||
| `symbol-daytime-325` | `bu_can` | 圆筒型浮标 |
|
||||
| `symbol-daytime-327` | `bu_lat` | 桁架型浮标 |
|
||||
| `symbol-daytime-335359` | `mk_vais` | V-AIS 航标 |
|
||||
| `symbol-daytime-719` | `an_quar` | 检疫锚地 |
|
||||
|
||||
现有 style 中的别名也统一归并:
|
||||
|
||||
| 当前 key | 新 ID |
|
||||
|---|---|
|
||||
| `harbor_lighthouse` | `lt_hbr` |
|
||||
| `breakwater_lighthouse` | `lt_bkw` |
|
||||
|
||||
## 4. 危险物与鱼礁
|
||||
|
||||
| 当前 class_code / key | 新 ID | 含义 |
|
||||
|---|---|---|
|
||||
| `401`、`402`、`403` / `symbol-daytime-401` | `hz_rock_awash` | 水上岩、干出岩、洗岩组 |
|
||||
| `404` / `symbol-daytime-404` | `hz_rock_sub` | 暗岩 |
|
||||
| `405` / `symbol-daytime-405` | `hz_danger_clear` | 扫海后的危险物 |
|
||||
| `409` / `symbol-daytime-409` | `hz_danger_iso` | 孤立危险物 |
|
||||
| `410` / `symbol-daytime-410` | `hz_wreck_hull` | 船体露出沉船 |
|
||||
| `412` / `symbol-daytime-412` | `hz_wreck_sub` | 危险全沉没船 |
|
||||
| `413`、`415` / `symbol-daytime-413` | `hz_wreck_survey` | 测量/调查后的沉船 |
|
||||
| `420` / `symbol-daytime-420` | `hz_foul` | 险恶物 |
|
||||
| `421` / `symbol-daytime-421` | `hz_sandwave` | 沙波 |
|
||||
| `422` / `symbol-daytime-422` | `hz_overfall` | 急潮、波纹、激潮 |
|
||||
| `424` / `symbol-daytime-424` | `hz_whirl` | 渦流 |
|
||||
| `425` / `symbol-daytime-425` | `hz_seaweed` | 海草 |
|
||||
| `427` / `symbol-daytime-427` | `hz_obst` | 障碍物 |
|
||||
| `428` / `symbol-daytime-428` | `hz_reef` | 鱼礁 |
|
||||
| `429` / `symbol-daytime-429` | `hz_reef_danger` | 危险鱼礁 |
|
||||
| `431` / `symbol-daytime-431` | `hz_tower` | 塔、橹、测台 |
|
||||
| `432` / `symbol-daytime-432` | `hz_pile` | 墩柱、桩、杭 |
|
||||
| `433` / `symbol-daytime-433` | `hz_dolphin` | 系缆墩 |
|
||||
| `434` / `symbol-daytime-434` | `hz_outfall` | 海底设施、排水口、取水口 |
|
||||
|
||||
## 5. 陆上地标
|
||||
|
||||
这些图标由 `onshore_structure_point` 的 `class_code` 生成,必须在重建 PBF 时直接写入 `icon_id`,不能继续由 style 动态拼接编号。
|
||||
|
||||
| class_code | canonical_object_type | 新 ID |
|
||||
|---:|---|---|
|
||||
| 640 | `chimney` | `lm_chim` |
|
||||
| 650 | `tower_yagura_windmill` | `lm_twr` |
|
||||
| 660 | `maritime_office` | `lm_mar` |
|
||||
| 661 | `fishing_cooperative` | `lm_fish` |
|
||||
| 662 | `customs_office` | `lm_cus` |
|
||||
| 680 | 陆上显著物(观览车等) | `lm_prom` |
|
||||
| 681 | `mountain_top` | `lm_mtn` |
|
||||
| 682 | 管制信号所 | `lm_ctrl` |
|
||||
| 698 | `other_landmark_monument` | `lm_mon` |
|
||||
|
||||
## 6. 当前 PBF 中的直接图标值
|
||||
|
||||
当前全国 Full PBF 实际出现的 `chart_icon_image` 值,均应落入本表:
|
||||
|
||||
```text
|
||||
symbol-daytime-301
|
||||
symbol-daytime-303
|
||||
symbol-daytime-30500001
|
||||
symbol-daytime-30500002
|
||||
symbol-daytime-30500003
|
||||
symbol-daytime-30700003
|
||||
symbol-daytime-308
|
||||
symbol-daytime-310
|
||||
symbol-daytime-320
|
||||
symbol-daytime-321
|
||||
symbol-daytime-323
|
||||
symbol-daytime-325
|
||||
symbol-daytime-327
|
||||
symbol-daytime-335359
|
||||
symbol-daytime-405
|
||||
symbol-daytime-410
|
||||
symbol-daytime-412
|
||||
symbol-daytime-413
|
||||
symbol-daytime-428
|
||||
symbol-daytime-429
|
||||
symbol-daytime-719
|
||||
```
|
||||
|
||||
其中 `symbol-daytime-719` 当前语义建议为:
|
||||
|
||||
```text
|
||||
an_quar = quarantine anchorage
|
||||
```
|
||||
|
||||
## 7. 实施约束
|
||||
|
||||
- 本表确认后,统一重建全部 Full PBF,不在现有 PBF 上做局部替换。
|
||||
- style 的 `icon-image` 统一读取 `icon_id` 或 `arc_id`,删除 `class_code` 拼接逻辑。
|
||||
- sprite JSON key 与新 ID 完全一致。
|
||||
- `class_code`、FID 和 source-layer 继续保留,用于反向追溯和对象审计。
|
||||
- `symbol-daytime-*`、`arc-daytime-*` 只允许出现在转换审计日志或历史映射表中,不能出现在正式 PBF、正式 style 和正式 sprite JSON 中。
|
||||
- `symbol-daytime-662` 对应 `lm_cus`,需要补上海关图标素材。
|
||||
224
tasks/pbf/navsea_semantic_icon_map_v1.json
Normal file
224
tasks/pbf/navsea_semantic_icon_map_v1.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"version": "semantic-icon-v1",
|
||||
"baseline": "full-semantic-extentfix",
|
||||
"fields": {
|
||||
"point_icon": "icon_id",
|
||||
"light_arc": "arc_id",
|
||||
"legacy_point_icon": "chart_icon_image"
|
||||
},
|
||||
"color_codes": {
|
||||
"green": "G",
|
||||
"red": "R",
|
||||
"yellow": "Y",
|
||||
"white": "W",
|
||||
"blue": "B",
|
||||
"yellow_white": "YW"
|
||||
},
|
||||
"sprite_key_map": {
|
||||
"symbol-daytime-300": "lt_cst",
|
||||
"symbol-daytime-301": "lt_hbr",
|
||||
"symbol-daytime-302": "lt_bkw",
|
||||
"symbol-daytime-303": "lt_min",
|
||||
"symbol-daytime-30500001": "lt_lead_a",
|
||||
"symbol-daytime-30500002": "lt_lead_b",
|
||||
"symbol-daytime-30500003": "lt_lead_c",
|
||||
"symbol-daytime-30700001": "lt_up_G",
|
||||
"symbol-daytime-30700002": "lt_up_R",
|
||||
"symbol-daytime-30700003": "mk_lead_c",
|
||||
"symbol-daytime-308": "mk_lead_v",
|
||||
"symbol-daytime-310": "lt_bcn",
|
||||
"symbol-daytime-320": "bu_lit",
|
||||
"symbol-daytime-321": "bu_gen",
|
||||
"symbol-daytime-323": "bu_pil",
|
||||
"symbol-daytime-325": "bu_can",
|
||||
"symbol-daytime-327": "bu_lat",
|
||||
"symbol-daytime-335359": "mk_vais",
|
||||
"symbol-daytime-401": "hz_rock_awash",
|
||||
"symbol-daytime-404": "hz_rock_sub",
|
||||
"symbol-daytime-405": "hz_danger_clear",
|
||||
"symbol-daytime-409": "hz_danger_iso",
|
||||
"symbol-daytime-410": "hz_wreck_hull",
|
||||
"symbol-daytime-412": "hz_wreck_sub",
|
||||
"symbol-daytime-413": "hz_wreck_survey",
|
||||
"symbol-daytime-420": "hz_foul",
|
||||
"symbol-daytime-421": "hz_sandwave",
|
||||
"symbol-daytime-422": "hz_overfall",
|
||||
"symbol-daytime-424": "hz_whirl",
|
||||
"symbol-daytime-425": "hz_seaweed",
|
||||
"symbol-daytime-427": "hz_obst",
|
||||
"symbol-daytime-428": "hz_reef",
|
||||
"symbol-daytime-429": "hz_reef_danger",
|
||||
"symbol-daytime-431": "hz_tower",
|
||||
"symbol-daytime-432": "hz_pile",
|
||||
"symbol-daytime-433": "hz_dolphin",
|
||||
"symbol-daytime-434": "hz_outfall",
|
||||
"symbol-daytime-640": "lm_chim",
|
||||
"symbol-daytime-650": "lm_twr",
|
||||
"symbol-daytime-660": "lm_mar",
|
||||
"symbol-daytime-661": "lm_fish",
|
||||
"symbol-daytime-662": "lm_cus",
|
||||
"symbol-daytime-680": "lm_prom",
|
||||
"symbol-daytime-681": "lm_mtn",
|
||||
"symbol-daytime-682": "lm_ctrl",
|
||||
"symbol-daytime-698": "lm_mon",
|
||||
"symbol-daytime-719": "an_quar",
|
||||
"symbol-daytime-720": "an_desig",
|
||||
"symbol-daytime-721": "an_restrict",
|
||||
"symbol-daytime-724": "an_no_anchor",
|
||||
"symbol-daytime-505": "fac_port",
|
||||
"symbol-daytime-520": "fac_fishport",
|
||||
"symbol-daytime-530": "fac_marina",
|
||||
"symbol-daytime-540": "fac_fisherina",
|
||||
"symbol-daytime-550": "fac_seastation",
|
||||
"harbor_lighthouse": "lt_hbr",
|
||||
"breakwater_lighthouse": "lt_bkw",
|
||||
"coastal_lighthouse": "lt_cst",
|
||||
"light_minor": "lt_min",
|
||||
"leading_light_variant_30500001": "lt_lead_a",
|
||||
"leading_light_variantt_30500001": "lt_lead_a",
|
||||
"leading_light_variant_30500002": "lt_lead_b",
|
||||
"leading_light_variantt_30500002": "lt_lead_b",
|
||||
"leading_light_variant_30500003": "lt_lead_c",
|
||||
"leading_light_variantt_30500003": "lt_lead_c",
|
||||
"leading_mark_variant_30700003": "mk_lead_c",
|
||||
"leading_mark_variantt_30700003": "mk_lead_c",
|
||||
"leading_mark_variant_308": "mk_lead_v",
|
||||
"leading_mark_variantt_308": "mk_lead_v",
|
||||
"light_beacon": "lt_bcn",
|
||||
"buoy_light": "bu_lit",
|
||||
"buoy_generic": "bu_gen",
|
||||
"buoy_pillar": "bu_pil",
|
||||
"buoy_can": "bu_can",
|
||||
"buoy_lattice": "bu_lat",
|
||||
"nav_mark_vais": "mk_vais",
|
||||
"pilot_station": "fac_pilot",
|
||||
"tidespot-daytime": "tide_spot",
|
||||
"light-daytime-1": "fl_G",
|
||||
"light-daytime-2": "fl_R",
|
||||
"light-daytime-3": "fl_YW",
|
||||
"arc-daytime-04027289": "arc_open_YW",
|
||||
"arc-daytime-m1": "arc_G",
|
||||
"arc-daytime-m2": "arc_R",
|
||||
"arc-daytime-m3": "arc_YW",
|
||||
"rock_exposed_drying_awash_group": "hz_rock_awash",
|
||||
"sunken_rock": "hz_rock_sub",
|
||||
"cleared_danger_object": "hz_danger_clear",
|
||||
"isolated_danger": "hz_danger_iso",
|
||||
"wreck_hull_exposed": "hz_wreck_hull",
|
||||
"wreck_fully_submerged_dangerous": "hz_wreck_sub",
|
||||
"wreck_surveyed": "hz_wreck_survey",
|
||||
"foul_ground": "hz_foul",
|
||||
"sand_wave": "hz_sandwave",
|
||||
"tide_rips_overfalls": "hz_overfall",
|
||||
"eddy_whirlpool": "hz_whirl",
|
||||
"seaweed": "hz_seaweed",
|
||||
"obstruction": "hz_obst",
|
||||
"fish_reef": "hz_reef",
|
||||
"fish_reef_dangerous": "hz_reef_danger",
|
||||
"tower_yagura_observation_platform": "hz_tower",
|
||||
"bollard_pile_stake": "hz_pile",
|
||||
"dolphin_structure": "hz_dolphin",
|
||||
"subsea_installation_outfall_intake": "hz_outfall",
|
||||
"anchorage_quarantine": "an_quar",
|
||||
"anchorage_designated": "an_desig",
|
||||
"restriction_area_group": "an_restrict",
|
||||
"anchoring_prohibited": "an_no_anchor",
|
||||
"port_general_small_harbor_group": "fac_port",
|
||||
"fishing_port": "fac_fishport",
|
||||
"marina": "fac_marina",
|
||||
"fisherina": "fac_fisherina",
|
||||
"sea_station_umi_no_eki": "fac_seastation"
|
||||
},
|
||||
"hazard_class_map": {
|
||||
"401": "hz_rock_awash",
|
||||
"402": "hz_rock_awash",
|
||||
"403": "hz_rock_awash",
|
||||
"404": "hz_rock_sub",
|
||||
"405": "hz_danger_clear",
|
||||
"409": "hz_danger_iso",
|
||||
"410": "hz_wreck_hull",
|
||||
"412": "hz_wreck_sub",
|
||||
"413": "hz_wreck_survey",
|
||||
"415": "hz_wreck_survey",
|
||||
"420": "hz_foul",
|
||||
"421": "hz_sandwave",
|
||||
"422": "hz_overfall",
|
||||
"424": "hz_whirl",
|
||||
"425": "hz_seaweed",
|
||||
"427": "hz_obst",
|
||||
"428": "hz_reef",
|
||||
"429": "hz_reef_danger",
|
||||
"431": "hz_tower",
|
||||
"432": "hz_pile",
|
||||
"433": "hz_dolphin",
|
||||
"434": "hz_outfall"
|
||||
},
|
||||
"landmark_class_map": {
|
||||
"640": "lm_chim",
|
||||
"650": "lm_twr",
|
||||
"660": "lm_mar",
|
||||
"661": "lm_fish",
|
||||
"662": "lm_cus",
|
||||
"680": "lm_prom",
|
||||
"681": "lm_mtn",
|
||||
"682": "lm_ctrl",
|
||||
"698": "lm_mon"
|
||||
},
|
||||
"anchorage_class_map": {
|
||||
"719": "an_quar",
|
||||
"720": "an_desig",
|
||||
"721": "an_restrict",
|
||||
"722": "an_restrict",
|
||||
"723": "an_restrict",
|
||||
"724": "an_no_anchor"
|
||||
},
|
||||
"facility_class_map": {
|
||||
"505": "fac_port",
|
||||
"510": "fac_port",
|
||||
"520": "fac_fishport",
|
||||
"530": "fac_marina",
|
||||
"540": "fac_fisherina",
|
||||
"550": "fac_seastation"
|
||||
},
|
||||
"display_code_map": {
|
||||
"30300000": "lt_min",
|
||||
"30300002": "lt_min",
|
||||
"30300004": "lt_min",
|
||||
"30500001": "lt_lead_a",
|
||||
"30500002": "lt_lead_b",
|
||||
"30500003": "lt_lead_c",
|
||||
"30500004": "lt_lead_c",
|
||||
"30500010": "lt_lead_c",
|
||||
"30600000": "lt_min",
|
||||
"30600001": "lt_min",
|
||||
"30600002": "lt_min",
|
||||
"30600003": "lt_min",
|
||||
"30600004": "lt_min",
|
||||
"30600006": "lt_min",
|
||||
"30700000": "mk_lead_c",
|
||||
"30700001": "lt_up_G",
|
||||
"30700002": "lt_up_R",
|
||||
"30700003": "mk_lead_c",
|
||||
"30700004": "mk_lead_c",
|
||||
"30700007": "mk_lead_c",
|
||||
"30800000": "mk_lead_v",
|
||||
"30900000": "lt_lead_c",
|
||||
"30900002": "lt_lead_b",
|
||||
"30900004": "lt_lead_c",
|
||||
"30900009": "lt_lead_c",
|
||||
"30900011": "lt_lead_c"
|
||||
},
|
||||
"arc_rules": {
|
||||
"sector": "arc_open_YW",
|
||||
"green": "arc_G",
|
||||
"red": "arc_R",
|
||||
"other": "arc_YW"
|
||||
},
|
||||
"fallbacks": {
|
||||
"lm_cus": {
|
||||
"source_sprite_key": "symbol-daytime-660",
|
||||
"status": "temporary_visual_fallback",
|
||||
"reason": "当前 sprite 没有 symbol-daytime-662 原图"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user