S-64:IHO 官方测试数据集,渲染器的陪练

第一篇入门文里给过一条最便宜的上手路线:OpenCPN + IHO S-64 官方测试数据 + GDAL,一周建立"数据→显示"的直觉。前半句和后半句都好理解,中间这个 S-64 一直没展开。一句话定位:它是 IHO 出的 ECDIS 测试数据集,专门回答"你的渲染器显示得对不对"。

标准文本(S-52、S-57)写的是"应该怎样";S-64 给的是现成的数据加标准答案——一份精心构造的 ENC 数据,配套一批"加载后你应该看到什么"的用例说明与预期显示效果。自研渲染器最痛苦的一步是手里没有"对的参照物",S-64 就是那个参照物。

三类人,三种用法

  • ECDIS 合规视角:显示与更新是否符合 S-52/S-57 的表现要求,是型式认可审查的实质内容之一——正式测试以 IEC 61174 为纲,S-64 数据集就是它的核心输入。按用例逐条过,差异即疑点
  • 开源/自研渲染器视角:OpenCPN 这类实现和各家自研引擎,拿它当对照系——同一份数据,你画的、别人画的、预期效果,三者对齐说明理解没跑偏
  • 入门者视角:初学最难的不是代码,是"不知道自己画得对不对"。S-64 每条用例附带现象说明,等于自带答案册

怎么拿到手:IHO 官网的标准与规范页免费下载,现行版本在 Ed 3.0 一线(IHO 的"Standards in Force"页面可查现行版次)。顺手提一句它的地位:连 S-100 安全方案的官方测试数据,都是拿 S-64 这套改出来的。

一条更新用例的完整走法

拿最能暴露问题的"更新链路"用例举例(各版本 S-64 的用例组织不同,以随包说明为准),走法固定五步:

五步工作流 + 像素 diff 循环

  1. 装底座:OpenCPN 正常安装,把 S-64 数据集所在目录加入海图集合,加载基础数据,截图记下初始画面
  2. 逐份应用更新:数据集内的增量更新文件逐份导入。要盯的不是一个静态结果,而是每一次应用后的画面变化:浮标移位、沉船新增、等深线改值导致周边填色重排——每一处变化都对应标准里的一条表现要求
  3. 对照预期:用例说明写明了预期现象,逐条打钩
  4. 数据侧核对:有疑问时用 GDAL 把同一份数据的要素、属性、几何 dump 出来,先分清"数据理解错了"还是"画错了"——这两类错的排查方向完全相反
  5. 记错题本:把翻车用例记下来。它们就是你私有的 S-64,未来回归测试的种子

前三步手点一遍大约半天,价值在于理解每条用例在防什么错。手点第二遍开始就是在浪费生命——所以有第四步之后的自动化。

三大翻车点对照表

大量排查时间其实花在同一类问题上。按出现频率排个序:

现象根因第一排查动作
更新应用了,画面没变某层缓存的失效没接到这次更新关缓存直渲染复现,先定性再定位
物标画了,符号不对look-up 表查错:条件项、状态档位或专用符号匹配打印该要素命中的 look-up 条目,逐项核对
颜色"差不多但不全对"token 用错色板,或硬编码了十六进制值从渲染代码里捞出该像素的 token 名,回色板表对照

查错顺序也值得写死:先查 look-up 和配置,再查绘制代码——前者错得最多,后者背锅最多。

从人眼到像素 diff

把"预期效果 vs 实际渲染"自动化,只需要三固定:固定输入、固定渲染、固定比对。

  • 固定输入:同一份数据文件、同一套 look-up 配置、同一个视口;涉及时间的要素(灯标光韵律这类)固定时钟
  • 固定渲染:渲染一帧,截图保存为基准图(baseline);此后每次代码变动,重画同一帧
  • 固定比对:逐像素做通道差分,容差以内(抗锯齿边缘的灰度抖动)忽略;超差的连通区域聚类、框出、高亮——一份差异报告就是本次改动的影响清单

比对函数本身只有几十行,核心逻辑一看就懂:

// diff.js:基准图 vs 当前帧(RGBA,逐像素比较)
function diff(baseline, current, tol = 8) {
  const bad = [];
  for (let i = 0; i < baseline.length; i += 4) {
    if (Math.abs(baseline[i]   - current[i])   > tol ||
        Math.abs(baseline[i+1] - current[i+1]) > tol ||
        Math.abs(baseline[i+2] - current[i+2]) > tol) {
      bad.push(i / 4);                   // 记下超差像素的坐标
    }
  }
  return cluster(bad, baseline.width);   // 相邻超差点聚成矩形框
}

两个工程细节决定这玩意儿是"真好用"还是"天天狼来了":

  • 可容忍区域做成掩膜:状态栏时钟、天然渐变的水面纹理这类必然变化的区域,从比对中剔除——否则每天第一次运行都是假警报
  • 固定输入要真的固定:涉及时间的要素(灯标光韵律、潮汐时刻)给渲染器喂固定 UTC,随机数喂固定种子

这条 diff 线的成本是一个截图脚本加一个比对函数,收益是每一次升级依赖、移植算法、调整 look-up 都有保险。配套还要立一条纪律:diff 报红只有两种合法处理——修 bug,或者确认是"有意变更"并评审后更新基准图。没有第三种,基准图悄悄换掉的那天,回归线就名存实亡。

我们的回归线跑起来后抓到的第一个问题,是一个只在特定符号密度下出现的注记错位,手测根本撞不见。这种案子攒多了你会发现:S-64 的价值不在"通过",而在把"显示得对不对"从一道主观题变成一道判断题。

S-100 时代:接棒的是 S-164

S-64 服务的还是 S-57/S-52 体系。S-100 时代的接棒者叫 S-164——《IHO Test Data Sets in ECDIS》(Ed 1.0.0,2023 年 3 月),定位与 S-64 一脉相承:测试数据集加一本使用手册(TIM),首批覆盖的是 S-101 新一代 ENC 的显示与更新用例,数据集连增量更新和 XML 版本都备齐了。最初的仓库说明写得很直白:初始内容就是 S-64 测试数据集的 S-101 等价物。

更值得点赞的是发布方式:数据集和手册直接开源在 IHO 官方的 GitHub 组织里(iho-ohi/S-164-Sub-Group),任何人可以下载、比对、提 issue——"标准配套测试数据"这件事,从纸质附录变成了活的公开项目。覆盖面也在实打实地扩:从 S-101 起步,已经把内河 ENC(S-401)和 S-102、S-111 这类辅助数据纳了进来。双标准并存的年代意味着两套都得用:存量系统跑 S-64,S-101 项目从第一天起就该把 S-164 纳入回归。

两个提醒

S-64 覆盖的是典型与疑难场景的抽样,不是全量验收——通过它不代表合规达标,不通过一定有问题。另外它随版本演进、用例集持续增补(S-164 那边也一样,覆盖面在逐版扩员),跟着当前版本的随包说明走,别背旧版目录。

下一篇回到数据侧:S-125 航标(AtoN)——灯标"灭没"这种短周期变更,标准里是怎么建模的。


我是夜航海图,做海图与地图渲染开发的工程师。博客「航图笔记」同步更新全部文章,欢迎 RSS 订阅;如果内容帮到了你,也欢迎在爱发电请我喝杯咖啡。

♥ 这篇对你有用?在爱发电支持夜航海图——请我喝杯咖啡,工具和文章都会继续更新。

← 返回目录

评论

评论基于 GitHub Discussions,需要 GitHub 账号;没有也欢迎通过 RSS 阅读器或各平台评论区交流。