CM93 MySQL importer validated baseline

This commit is contained in:
kaotei
2026-09-02 09:50:12 +08:00
commit 9d56592cad
62 changed files with 24778 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
# CM93 解析与审计设计 v1
## 1. 原始格式解析
独立 Python 实现依据已公开的 OpenCPN CM93 行为进行互操作核对,但不复制 OpenCPN 的类结构或 GUI/S52 代码。
核心数据顺序:
1. 10-byte prolog
2. 128-byte header
3. vector records
4. 3D point descriptors
5. 2D points
6. feature records
完整性条件:
`word0 + table1_length + table2_length == file_size`
且 `word0 == 138`。
进一步核对:
- vector point 总数 = header `n_vector_record_points`
- 3D point 总数 = header `m_50`
- feature 总数 = header `usn_feature_records`
- attribute payload bytes = header `m_78`
- feature geometry refs = header `m_46 + m_4a`
- related object pointers = header `m_nrelated_object_pointers`
- 最终 cursor = EOF
任何一项不成立,该 cell 解析失败,不允许“尽量导入”。
## 2. 属性
按 `CM93ATTR.DIC/ATTRLUT.DIC`:
- B: 1-byte integer
- W: uint16 / 10.0
- G: int32
- R: float32,标准化后以 Python double/MySQL DOUBLE 保存
- S: NUL-terminated string
- C: 3-byte prefix + NUL-terminated string,prefix 与 text 均保留
- L: count byte + count 个 byte values
raw value bytes 始终另存。
## 3. Geometry
Feature geotype 低 4 bit:
- 1 Point
- 2 Line
- 4 Area
- 8 3D Sounding
Line/Area edge index:低 13 bit 是 edge index,高位是 usage;`usage & 4` 表示反向遍历 edge。
Area 按 edge chain 闭合形成 contour/ring。系统不会调用 `make_valid` 或改变原始顶点坐标。
## 4. 坐标
CM93 raw X/Y:0..65535。
Cell 变换:
`easting = raw_x * x_rate + easting_min`
`northing = raw_y * y_rate + northing_min`
CM93 半长轴:`6378388.0 m`。
若 coverage 给出 `_wgsox/_wgsoy`,标准化母数据采用:
`easting -= wgsox`
`northing -= wgsoy`
然后执行 CM93 Mercator → lon/lat。
Coverage polygon 自身保留未偏移坐标,用于判断 feature 属于哪个 coverage;offset 元数据独立保存。
## 5. Sounding
CM93 Z:
- `z >= 12000` → `depth = z - 12000`
- 否则 → `depth = z / 10.0`
每个 sounding point 同时保存 raw_x/raw_y/raw_z、depth 和 WGS84 Point。
## 6. Raw preservation
每个 cell 保存 decoded:
- prolog
- header
- vector table
- point3d table
- point2d table
- feature table
较大的 table 按 512 KiB 分块写入 `chart_raw_record`,避免 MySQL `max_allowed_packet` 造成单大包问题。
审计要求所有 raw chunk `OCTET_LENGTH` 总和 = 原 cell 文件字节数。
## 7. 审计分层
### Source parser audit
重新读取原 CM93,验证所有结构计数和 EOF。
### Database completeness audit
逐 cell 对比源数据与:
- chart_feature
- chart_feature_attribute
- point/line/area geometry
- chart_coverage
- chart_sounding_point
### Raw preservation audit
比较 raw chunk 总字节数与原文件长度。
### Hash audit
比较数据库 `source_sha256` 与当前原文件。
### Precision audit
从 Z/A~G 分层抽样,重新从 CM93 raw geometry 生成 WKB,然后从 MySQL `ST_AsWKB()` 回读,使用 `equals_exact` 比较。
默认 tolerance:`1e-12 degree`。
该标准比最初讨论的 `1e-9 degree` 更严格。
## 8. 不做的事情
第一阶段不做:
- PBF
- zoom selection
- simplify
- topology repair
- scale 去重
- cartographic style 映射
这些都属于第二阶段。

50
docs/Win11环境安装.md Normal file
View File

@@ -0,0 +1,50 @@
# Windows 11 + pyenv 环境安装
已知机器:
- pyenv 已安装
- Python 3.9.2 当前全局默认
- Python 3.12.10 已安装
本项目不要修改全局默认版本,推荐只设置项目 local:
```powershell
cd <项目目录>
pyenv local 3.12.10
python --version
```
必须为:
```text
Python 3.12.10
```
建立虚拟环境:
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
pip install -e .
```
或者直接:
```powershell
.\scripts\setup_env.ps1
```
数据库配置:
```powershell
Copy-Item .env.example .env
notepad .env
```
然后:
```powershell
cm93db env-check
cm93db db-init --create-database
cm93db db-check
```

25
docs/外部参考.md Normal file
View File

@@ -0,0 +1,25 @@
# 外部参考
本项目独立实现 CM93 importer,不复制 OpenCPN GPL 源码。
用于核对行为和 MySQL 兼容性的公开参考:
- OpenCPN CM93 implementation:
https://github.com/OpenCPN/OpenCPN/blob/master/gui/src/cm93.cpp
- MySQL 5.7 Spatial Data Types:
https://dev.mysql.com/doc/refman/5.7/en/spatial-type-overview.html
- MySQL 5.7 Geometry Class:
https://dev.mysql.com/doc/refman/5.7/en/gis-class-geometry.html
- MySQL 5.7 Spatial Index:
https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html
注意:MySQL 5.7 Geometry 坐标本身为 double precision,但对实际 SRID 的地理计算能力不能按 MySQL 8 的行为假设。
## v0.2.0 互操作说明
CM93 binary 行为用于互操作核对的核心公开实现是 OpenCPN `cm93.cpp` 的 decode/header/table/feature/attribute/Transform 逻辑。
本工程采用独立 Python 数据模型、数据库模型和实现结构。CM93 substitution mapping 属于解码互操作所需的固定映射;如未来公开分发或商业分发本工具,应自行进行许可证/数据授权审查。

151
docs/导入与审计.md Normal file
View File

@@ -0,0 +1,151 @@
# CM93 导入与审计操作说明
## 1. 适用环境
- Windows 11
- Python 3.12.10
- MySQL 5.7.34
- 已完成 `cm93db db-init --create-database`
## 2. 正式导入
如果 `.env` 已配置:
```text
CM93_SOURCE_DIR=D:\Ship\CM93V2\CM93_2014
```
可直接运行:
```powershell
& ".\.venv\Scripts\cm93db.exe" import
```
也可显式给路径:
```powershell
& ".\.venv\Scripts\cm93db.exe" import D:\Ship\CM93V2\CM93_2014
```
默认行为:
- 递归扫描 Z/A/B/C/D/E/F/G;
- 每个 cell 独立事务;
- DONE + 文件 SHA256 相同自动跳过;
- FAILED cell 下次自动重试;
- 保存 typed attributes;
- 保存标准化 WGS84 geometry;
- 保存 decoded CM93 原始 table block;
- `_wgsox/_wgsoy` 默认按 `embedded` policy 应用;
- 不 simplify / snap / quantize / round;
- 不自动 `make_valid`。
### 先小批验证
推荐正式 30,097 cell 前先跑:
```powershell
& ".\.venv\Scripts\cm93db.exe" import --scale G --limit-cells 5
```
然后:
```powershell
& ".\.venv\Scripts\cm93db.exe" audit --sample-cells 5
```
通过后再跑完整导入。
### 只导某些尺度
```powershell
& ".\.venv\Scripts\cm93db.exe" import --scale G --scale F
```
### 强制重导
```powershell
& ".\.venv\Scripts\cm93db.exe" import --force
```
## 3. 审计
### 快速审计
```powershell
& ".\.venv\Scripts\cm93db.exe" audit --sample-cells 20
```
包括:
1. 数据库 cell / feature / attribute 计数;
2. 每个 DONE cell 的 source count 与数据库 count 对账;
3. Point / Line / Area geometry presence;
4. 随机重新解析源 CM93 cell;
5. 源 feature / attribute count 对比;
6. 从 MySQL `ST_AsWKB` 读回 geometry;
7. 与重新计算的 double precision geometry 做逐顶点比较;
8. sounding raw X/Y/Z、depth、lon/lat 比较。
默认坐标容差:
```text
1e-12 degree
```
这个容差用于发现数据库序列化/写入造成的精度损失,不是让 importer 人为 round 到该精度。
### 全源最终审计
完整导入后:
```powershell
& ".\.venv\Scripts\cm93db.exe" audit --all-source
```
这会重新解析所有 DONE cell,耗时明显高于随机抽样,但适合作为母数据库最终验收。
报告自动写入:
```text
logs/audit-YYYYMMDD-HHMMSS.json
```
## 4. offset policy
正式母库默认:
```text
--offset-policy embedded
```
含义:
1. `_m_sor` coverage 自身使用未偏移 cell 坐标建立;
2. 保存 `_wgsox/_wgsoy` 原始值;
3. 对能够匹配 coverage 的普通几何,在 CM93 Mercator 阶段减去对应 offset;
4. 然后使用 CM93 半长轴 `6378388.0` 转换 WGS84 lon/lat;
5. feature JSON 中记录实际应用的 offset 和 coverage match method。
为研究/对照可运行:
```powershell
--offset-policy none
```
但不要把同一个 dataset 混用两种 policy。改变 policy 时应 `--force` 全部重导,并重新执行全源审计。
## 5. Geometry issue
源 CM93 中可能存在自交 polygon 等问题。
规则:
- raw record 永久保留;
- `geometry_issue` 记录 Shapely validity / ring assembly 问题;
- `geometry_repaired=0`;
- 当前版本不自动修复;
- 可以保存结构上可编码的 invalid WKB;
- 无法形成 geometry 时不伪造 geometry row。
第二阶段 PBF 如需要显示修复,应在发布层制定独立策略,不能修改母数据库事实。

0
docs/报告/.gitkeep Normal file
View File

View File

@@ -0,0 +1,69 @@
# CM93 Importer v0.2.0 离线验证报告
## 范围
本报告验证 CM93 parser、geometry 标准化和 MySQL 写入批次结构。
由于 ChatGPT 执行环境无法访问用户本机 MySQL,本报告不声称已经替用户执行 MySQL 实际插入;真实数据库验收由 `import --limit-cells 5` + `audit` 完成。
## 固定真实样本
用户提供的 147 个 A~G CM93 cell。
结果:
```text
cells 147
parse failures 0
features 100621
attributes 112746
```
与前期独立解析计数一致。
## Geometry + 写入结构回归
对 147 个 cell 全部执行:
```text
parse
→ coverage build
→ Point/Line/Area/Sounding normalization
→ embedded offset matching
→ WKB generation
→ 模拟 MySQL insert tuple / dependent-id mapping
```
结果:
```text
cells 147
features 100621
attributes 112746
geometry issues 36
unmatched coverage 4
```
36 个 geometry issue 主要为源 polygon self-intersection;v0.2.0 不自动 make-valid。
4 个 unmatched coverage 为仍有可保存几何、但 representative point 未落入 `_m_sor` coverage 的 cartographic/text 类对象;按 policy 保存 0 offset 并在 `_cm93.coverage_match_method` 中记录 `unmatched`,不静默猜测最近 coverage。
## 精度规则
- Python float / C double
- MySQL Geometry double
- CM93 Mercator `6378388.0`
- 不 round
- 不 simplify
- 不 quantize
- sounding 原始 X/Y/Z 同时保存
- raw feature / raw attribute / decoded CM93 table block 保存
## 下一验证门槛
用户机器:
```powershell
cm93db import --scale G --limit-cells 5
cm93db audit --sample-cells 5
```
只有数据库实际写入和 WKB 读回精度审计 PASS 后,再启动 30,097 cell 全量导入。

View File

@@ -0,0 +1,69 @@
# CM93 导入器开发验证报告 v0.2.0
## 结论
v0.2.0 已完成 CM93 binary parser、标准化 geometry、MySQL 5.7 writer、断点续导和 audit 代码。
## 实际样本回归
输入:用户上传 `04501020.zip` + `CM93OBJ.DIC` + `CM93ATTR.DIC`。
结果:
- Cell: 147
- Parser PASS: 147
- Parser FAIL: 0
- Feature: 100,621
- Attribute: 112,746
- 未知 object ID: 0
- 未知 attribute ID: 0
- Sounding points: 76,126
- `_m_sor` coverage: 258
- 非零 `_wgsox/_wgsoy` coverage: 34
结构级验证:
- prolog/header/file-size formula: PASS
- vector point header count: PASS
- 3D point header count: PASS
- feature header count: PASS
- attribute payload byte header count: PASS
- geometry-reference header count: PASS
- related-pointer header count: PASS
- EOF exact: PASS
- decoded raw table 总字节 == 原文件字节: PASS
## Geometry
样本中所有应产生 Point/Line/Area 的 source feature 均生成了 geometry;没有 geometry missing。
部分 CM93 Area 在严格拓扑检查下存在自相交/边界问题。v0.2.0 不自动修复,只在 `--validate-geometry` 时报告;这符合母数据库不改变原数据顶点的原则。
## MySQL
目标:MySQL 5.7.34-log。
用户已在前一版本实际验证:
- 数据库创建成功
- 14 张表初始化成功
- Schema contract PASS
v0.2.0 未修改表数量或需要迁移的核心 Schema,因此现有数据库可继续使用。
v0.2.0 新增的实际 feature/geometry bulk write 尚需在用户 Windows/MySQL 上执行 10-cell pilot;README 和 Codex 接手文档已固定 pilot → audit → full import 顺序。
## 当前正式数据目录
`D:\Ship\CM93V2\CM93JAPAN`
不再使用旧的 `CM93_2014` 全目录作为当前正式导入目标。
## 下一验收点
```powershell
cm93db import --scale G --limit 10
cm93db audit --scale G --limit 10 --precision-samples 32
```
只有审计 `RESULT: PASS` 后才继续全量日本数据。

50
docs/数据库设计.md Normal file
View File

@@ -0,0 +1,50 @@
# MySQL 5.7.34 数据库设计
## 核心表
- `chart_dataset`:一个 CM93 数据集根目录
- `chart_dictionary_object`:对象字典快照
- `chart_dictionary_attribute`:属性字典快照
- `chart_cell`:CM93 cell 元数据
- `chart_coverage`:`_m_sor` coverage
- `chart_feature`:公共 feature 元数据
- `chart_feature_attribute`:规范化属性
- `chart_point_geometry`
- `chart_line_geometry`
- `chart_area_geometry`
- `chart_sounding_point`
- `chart_raw_record`
- `chart_import_job`
- `chart_import_event`
## Geometry
MySQL 5.7 不使用 MySQL 8 的 SRID 列限制。
所有标准化 geometry 写入时显式:
`ST_GeomFromWKB(%s, 4326)`
并在 `chart_feature.normalized_srid` 记录 4326。
## 原始值
CM93 的:
- header bounds
- source scale
- raw geotype
- raw feature bytes
- raw attribute bytes
- coverage offset
必须保存。
## 数值属性
`chart_feature_attribute` 同时提供:
- `value_int`
- `value_real`
- `value_text`
- `value_json`
- `raw_value_blob`
这样既保留 typed value,也允许审计原始数据。

55
docs/架构设计.md Normal file
View File

@@ -0,0 +1,55 @@
# 架构设计
## 数据流
CM93 原始目录
→ SourceScanner
→ DictionaryLoader
→ CellBinaryReader
→ FeatureDecoder
→ CoverageResolver
→ PrecisionProjection
→ ChartFeature Model
→ MySQLBatchWriter
→ MySQL 母数据库
## 分层
### `cm93`
只负责格式解析和坐标/几何语义,不知道 MySQL。
### `model`
定义稳定中间模型。
### `database`
负责 MySQL 5.7 写入、事务、批量提交和恢复。
### `cli`
只做命令组织。
## 数据库与 CM93 边界
写入数据库以后:
- geometry 已标准化为 WGS84 经纬度;
- 原始 CM93 元数据仍保留;
- 后续 PBF builder 不需要理解 CM93 二进制。
## Cell 事务
每个 cell 为独立事务边界:
DISCOVERED → IMPORTING → DONE
失败:
IMPORTING → FAILED
再次运行时:
- DONE 默认跳过;
- FAILED 可重试;
- 强制参数可重新导入指定 cell。
## Geometry
母数据库保留标准化 geometry,但不依赖 geometry 作为唯一事实来源。
原始记录和 source index 是审计入口。

55
docs/精度规范.md Normal file
View File

@@ -0,0 +1,55 @@
# CM93 母数据库精度规范 v2
## 固定常量
- CM93 raw grid: 0..65535
- CM93 Mercator semi-major axis: **6378388.0 m**
- 标准化母库:WGS84 经纬度语义 / SRID 4326
- Python 坐标计算:CPython float(C double)
- WKB X/Y:IEEE-754 8-byte double
## 禁止
- numpy.float32 作为坐标
- MySQL FLOAT 保存最终经纬度
- importer 阶段 round
- simplify
- snap
- quantize
- 自动 make_valid 后替换原 geometry
## 转换
1. 读取 raw uint16 X/Y。
2. 使用 header 的 easting/northing bounds 恢复 CM93 Mercator。
3. 若 feature 所属 `_m_sor` coverage 有 `_wgsox/_wgsoy`:在 Mercator 米坐标中减去 offset。
4. 使用 6378388.0 执行 CM93 Mercator → lon/lat。
5. WKB 写 MySQL,第二参数显式 SRID=4326。
## 数据可追踪
必须同时保存:
- source cell path
- SHA-256
- header
- raw decoded tables
- raw feature record
- raw attribute payload/value
- `_wgsox/_wgsoy`
- normalized geometry
因此标准化公式未来如需修订,不必重新取得原 CM93 光盘/目录即可重新解释数据库 raw 数据。
## 审计门槛
默认:
- parser 结构计数全部一致
- EOF 精确一致
- source SHA-256 一致
- raw chunk 总字节 = source file bytes
- source/database Feature/Attribute/Geometry/Sounding 计数一致
- WKB round-trip precision tolerance = **1e-12 degree**
`--validate-geometry` 的拓扑 validity 是额外信息,不允许通过自动修复掩盖原数据结构。