S-57 数据解剖:把一个 ENC 文件拆给你看

首发于个人博客「航图笔记」:S-57 数据解剖:把一个 ENC 文件拆给你看 · 航图笔记 (笔名:夜航海图,转载请保留本行)

上一篇我们把 S-57 定性成"海图的数据库表结构",很多朋友说不过瘾:文件到底是什么样的?这篇就动真刀。我们拿 IHO S-64 官方测试数据里的一个 ENC 单元,从第一层封装一路拆到单个要素的属性和坐标,最后给出一个最小解析器的骨架。

第一刀:文件根本不是文本

用编辑器打开一个 ENC 文件,你会看到一屏乱码。它不是 JSON,没有花括号,一切信息靠字节位置和描述符 表达。这套封装叫 ISO 8211------一个上世纪定下的自描述交换格式,今天仍然是 S-57 的载体。

ISO 8211 文件的结构只有两层概念:

  • 文件第一条记录是 DDR(数据描述记录),相当于 schema:它声明了后面所有记录里有哪些字段、每个字段什么格式;
  • 之后是若干条 DR(数据记录),相当于数据行。

心智模型:DDR 是表头,DR 是一行行数据。解析器的第一件事永远是读 DDR,否则后面一个字节都读不懂。

第二刀:交换集的全景

单个 ENC 文件不孤立,交付形态是一个交换集(Exchange Set):

复制代码
CATALOG.031        ← 目录文件:清单与更新链
GB500001.000       ← 基础单元(.000)
GB500001.001       ← 第 1 期更新
GB500001.002       ← 第 2 期更新

单元文件名本身有信息量:开头两位是国家码,第三位是航行用途 (Navigational Purpose,1--6),后面是单元序号;末尾三位是更新号,.000 表示基础版。航行用途 1 到 6 覆盖从大洋概图到靠泊图,数字越大比例尺越大------这也直接决定了你渲染引擎的缩放级别该加载哪些单元。

目录文件 CATALOG.031 里每个文件一条 CATD 记录:文件名、类型(基础还是更新)、更新日期。接收端靠它保证更新链完整------缺一环,后面全作废。

第三刀:记录的"四大家族"

剥开 ISO 8211,S-57 的记录就四类。理解了它们的分工,文件在你眼里就从乱码变成了结构:

1. DSID------数据集说明。 全局参数都在这:数据集名、深度单位,还有一个关键角色 COMF (坐标放大因子)。S-57 里坐标是整数,真实经纬度 = 整数值 ÷ COMF,通常 COMF = 10⁷。也就是说你在文件里看到 43200000,实际是东经 4.32°。所有坐标精度问题都从这条换算开始。

2. CATD------目录条目。 上面说过了,只出现在目录文件里。

3. FRID------要素记录(Feature)。 回答"这里是什么"。关键字段:

  • PRIM:几何原型,点 / 线 / 面 / 制图面四选一
  • OBJL:对象类编码,整数。比如 DEPARE(深度区)是 42,全世界按同一本字典解码
  • FOID:全局唯一 ID,机构码(AGEN)+ 两个编号构成三元组
  • ATTF:属性键值对。DEPARE 身上一定挂着 DRVAL1 / DRVAL2------这片水域的水深范围

4. VRID------空间记录(Vector)。 回答"它在哪里"。VRID 标明自己是节点、边还是面;边的几何是 SG2D 字段里的一串坐标点。

两条胶水字段 把两张表缝起来:要素记录里的 FSPT 指向它引用的空间记录;空间记录之间的 VRPT 表达拓扑关系(边的两端节点、面的边界由哪些边组成)。

来拼一个真实的 DEPARE:

复制代码
FRID:  PRIM=A(面)  OBJL=42(DEPARE)
FOID:  AGEN=550   FIDN=1234  FIDS=1
ATTF:  DRVAL1=0   DRVAL2=20        ← 0~20 米的水域
FSPT:  → VRID #88(边)  → #89  → #90  → #91   (面由四条边围成)

VRID #88:  边,SG2D: (315200000, 1206500000) ...  ← 一串 1/10⁷ 度坐标
VRPT:  起节点 #12 → 终节点 #13                 ← 边知道自己接在哪

为什么边要单独存:共边拓扑

看到这里你可能会问:一个面带自己的边界环不就完了,为什么 S-57 要把"边"提升成独立记录?

因为 ENC 要求平面拓扑(planar graph):相邻两个深度区,共享物理上同一条边记录,而不是各存一份。

这件事的直接收益是一致性:边移动,两侧区域同时变形,不可能出现裂缝或重叠。S-58 校验标准里专门有一组检查盯着悬空边和几何缝隙。

而对你这个写渲染器的人来说,代价是加载逻辑复杂了:先把 VRID 表读全、把 FSPT/VRPT 关系建好,才能拼出完整的面。习惯了 GeoJSON 那种"一个 Feature 自带一切"的嵌套模型,转向 S-57 的"两表 + 指针"模型,这是第一个思维弯道。

顺带一提,SOUNDG(水深点组)是个例外配置:它是一组三维点,Z 值直接存水深------这大概是整个 S-57 里离"一个对象一条几何"最近的东西。

渲染器视角的常用对象类

对象目录里 180 多个类,日常打交道的高频的就这十几个:

对象类 含义 渲染时的角色
DEPARE 深度区 按 DRVAL1/DRVAL2 分档填色,浅水蓝、深水白
DEPCNT 等深线 VALDCO 取值,安全等深线加粗加黑
SOUNDG 水深点组 数字注记排版,可按疏密度抽稀
COALNE 海岸线 岸线符号
LNDARE 陆地区 填充底色,上面叠地名注记
LIGHTS 灯标 光弧(SECTR1/2)、节奏(SIGGRP/SIGPER)
BOYLAT 等 浮标系列 按类别/形状挂符号,读颜色属性
WRECKS / OBSTRN / UWTROC 危险物 属性组合决定危险样式
M_COVR / M_QUAL 元要素 不常规渲染,但覆盖范围、数据质量必须特殊处理

M_ 开头的元要素再强调一遍:忽略 M_COVR 和 M_QUAL 是新手最常见的第一个 bug,它们决定你该加载哪些邻接单元、数据精度声明画在哪。

更新机制:.001 文件里是什么

更新文件和基础单元结构完全一样,区别在于每条记录多了更新指令 RUIN

  • 1 = 插入:完整的要素+几何,直接并入
  • 2 = 删除:只带目标要素的 FOID,接收端按号移除
  • 3 = 修改:带新属性或新几何,替换对应记录

三条工程纪律,每条都对应一类线上事故:

  1. 按序应用.001 必须先于 .002,乱序 = 图面错乱
  2. 幂等防重:同一期更新应用两次,结果不可预期,要做好已应用标记
  3. 累积上限:更新号编到 999 会发重制版(reissue),文件管理逻辑要能识别

S-64 测试数据集里有专门的更新用例,验证你的更新逻辑是免费的考卷。

最小解析器骨架

把上面所有内容收拢成一个骨架,你会发现 S-57 解析本质上就是"读 schema → 分发表 → 两张表 → 建关联":

复制代码
读 DDR,拿到字段字典

for 每条数据记录:
    switch 记录里的标识字段:
        DSID → 存全局参数(记住 COMF)
        CATD → 登记文件清单
        FRID → features 表插入一行(OBJL/FOID/ATTF)
        VRID → spatials 表插入一行(类型/坐标)

for f in features:
    按 FSPT 把 f 关联到引用的 spatial
for s in spatials:
    按 VRPT 建边-节点、面-边关系

渲染时:遍历 features,按 OBJL+属性查 S-52 look-up 表

实操建议还是上一篇那句:先用 GDAL 的 S-57 驱动跑通全链路,把 ENC 转成 GeoJSON 对照着看,建立直觉;等你需要性能或特殊控制时,再下到 ISO 8211 字节层自己写------那时你已经知道每张表该长什么样了。

小结

  • ENC = ISO 8211 封装的记录流:DDR 是 schema,DR 是数据
  • 记录四大家族:DSID(全局参数)、CATD(目录)、FRID(是什么)、VRID(在哪里),FSPT/VRPT 负责缝合
  • 坐标是整数 ÷ COMF;平面拓扑要求共边;SOUNDG 三维点是例外
  • 更新 = RUIN 三指令的有序序列,乱序和重复是事故重灾区
  • 学习路径:GDAL 先跑通,字节层后下潜

下一篇按节奏进入 MapLibre 系列:《mbgl 架构总览:线程模型、瓦片调度与渲染管线》------从标准的世界回到代码的世界。


我是夜航海图,做海图与地图渲染开发的工程师。博客「航图笔记」同步更新全部文章,欢迎 RSS 订阅。你的 ENC 解析卡在哪一层,评论区聊聊。

相关推荐
迷茫、Peanut1 小时前
中断的时钟
c++
2402_882893862 小时前
用哈希表封装 unordered_map 和 unordered_set —— 手撕 C++ 哈希容器
c++·哈希桶·unordered_map·unordered_set
熊猫钓鱼>_>2 小时前
用 OHAudioSuite 空间渲染节点搭一座「3D 有声博物馆」——从三种模式到 FreeBuds 头追的实战记录
c++·3d·ai·harmonyos·arkts·audio·ohaudiosuite
千桐科技2 小时前
qData 开源版数据中台 v1.6.2 上线:修复数据连接、元数据与项目管理等多项问题
大数据·开源·数据治理·数据可视化·数据中台·元数据·qdata
_约书亚_2 小时前
chapter 9 高级线程管理 — 归纳总结
c++
图扑软件2 小时前
下篇・换墨|主题/多语言/移动端,一套组件全覆盖
前端·javascript·ui·性能优化·数据可视化
linx2952 小时前
阶段三讲义:类型与对象(Classes & Essential Operations)
c++
飞鸟真人2 小时前
C++ lambda 捕获完整梳理
c++·闭包·lamda
東隅已逝,桑榆非晚2 小时前
c++内存管理
c++·笔记