M3D – 一个想突破又还有很多包袱的新生三维数据格式杂谈
都说 “一流企业定标准,二流企业卖品牌,三流企业卖产品,四流企业做项目”,在面向类 WebGL 技术的三维浏览器端渲染的大规模流式数据加载技术中,等到了 MapGIS 主导的 m3d 标准。
1 门派
这一份浓浓的武汉味似乎是想在继 Cesium 3d-Tiles
、Esri i3s
、超图 S3M
后再在三维市场分一杯羹。
在前言中,吴老爷子带路,洋洋洒洒的一队人。
2 几个术语简析
- m3d:
Model of 3D
,在原文第 4 节有指出,但是翻译过来不太对的样子。 - glbx:
glTF
数据规范中,对于 glb 格式的一种变种文件。原文指的是 “gltf数据的扩展格式”,我不清楚 m3d 标准对 gltf 扩展了什么顶层属性,或者是没有好好研究glTF
规范,因为glTF
规范本身在几乎所有的属性中都有extensions
和extras
的预留属性,用于扩展各种不同逻辑的业务功能或渲染特征。
3 结构简析
结构的设计与 Esri i3s
略相类似,使用 Node
充当空间剖分的术语。
M3DDataInfo.mcj
:一种 JSON 文本文件,后缀名是 mcj,且此文件的文件名可任意设置,用于描述整份 m3d 数据RootNodeInfo.json
、NodeInfo<i>.json
:对 node 的描述文件,文件名可以自定义*.m3d
:m3d 文件,一种压缩文件,可选压缩格式有:,后缀名是m3d
,文件名可以自定义
每份 m3d 数据,其根目录下有
- 一个
mcj
文件描述整份数据 - 一个
Shared
目录存放Shared.m3d
文件 - 一个 根节点(RootNode)json 描述文件
- 一个
Data
目录用于存放子一级的节点目录- 每个节点目录下仍能拥有更下一级的节点目录,可递归
- 每个节点目录下有一个对当前 node 的 json 描述文件
- 每个节点目录下可拥有 n 个 m3d 文件,这些被称作 “瓦片”,用于管理不同行业的数据(点云.m3d、倾斜摄影模型.m3d、bim.m3d、地质体.m3d)
- 每个 m3d 文件下压缩了三个目录:
Geometry
、Texture
、Attribute
,分别存放*.glb/glbx
、*.jpg/png/webp
、*.json/bin
文件用于存储几何、纹理、属性数据
- 每个 m3d 文件下压缩了三个目录:
3.1 空间剖分方式
规范中的术语是 “数据树形结构”,个人认为使用 空间剖分方式 更好,直观。
使用了三种常规三维数据结构,即空间八叉树、四叉树、KD树。
3.2 空间范围表示方式
BoundingBox
BoundingSphere
以上两种常见空间范围体二选一。
其中,BoundingBox
使用的定义是 6 元素的 AABB 包围盒,即经纬高三轴的最值。
4 M3DDataInfo.mcj
类定义
描述整个 m3d 文件的 json 文件,它拥有如下顶层属性:
- asset: string,基本信息,简单的字符串
- version: string,这个令我十分无语,UML 图中是
string
,在文下是float
,我认为string
更合适。 - dataName: string,数据名
- guid: string,数据唯一标识符
- compressType: string,压缩类型,可选
"zip"
、"7z"
、"rar"
- spatialReference: string,空间参考,可选
"WGS84"
、"CGCS2000"
- treeType: string,空间剖分结构,可选
"QuadTree"
、"OCTree"
、"K-DTree"
、"RTree"
,这点我也有点无语,在前文中并未提及 R树,虽然 R树在空间搜索中还是很常见的。 - lodType: string,层次细节类型,可选
"ADD"
、"REPLACE"
- boundingVolume: BoundingVolume
- position: Point
- rootNode: Uri
- fieldInfo: Array<Field>
其中,BoundingVolume
在上文已提及,连同 Point
、Field
,在此简单使用 Rust
结构体定义:
struct BoundingVolume {
boundingBox: BoundingBox,
boundingSphere: BoundingSphere,
}
struct BoundingBox {
left: f64,
top: f64,
right: f64,
bottom: f64,
minHeight: f64,
maxHeight: f64,
}
struct BoundingSphere {
center: Point,
radius: f64,
}
struct Point {
x: f64,
y: f64,
z: f64,
}
struct Field {
name: String,
type: String,
alias: String,
size: i32,
}
其中,Field
的 type 字段可选值为 "bool"
、"int16"
、"uint16"
、"int32"
、"uint32"
、"int64"
、"uint64"
、"float"
、"double"
、"wchar"
、"text"
、"date"
、"time"
、"timestamp"
。
5 NodeInfo.json
类定义
每个节点(node)的描述文件是一个 json 文件,它的顶层属性有:
- name: string
- lodLevel: string,用字符串是一个留余量的设计,因为不一定 level 必须是数字
- boundingVolume: BoundingVolume
- lodMode: string,可选值
"distance"
、"pixel"
- lodType: string,可选值
"ADD"
、"REPLACE"
- lodError: string,切换误差值,单位根据
lodMode
决定 - transform: float[16],转换矩阵
- parentNode: NodeInfo
- childrenNode: Array<NodeInfo>
- uri: string
- shared: Uri
- tileDataInfoIndex: int,M3D 瓦片索引
- tileDataInfoList: Array<TileDataInfo>,瓦片数据列表
其中,TileDataInfo
的类定义用 rust 表示可为:
struct TileDataInfo {
tileData: Uri,
geometry: Geometry,
attribute: Attribute,
texture: Uri,
dataType: string, // 可选值 "Vector"、"TiltPhotography"、"Model"、"BIM"、"PointCloud"、"PipeLine"、"GeoModel"、"GeoGrid"、"GeoDrill"、"GeoSection",分别表示:矢量、倾斜、模型、BIM、点云、管线、地质体、地质体网格、地址钻孔、地质剖面
}
struct Geometry {
blobType: string, // 可选值 "glb"、"glbx"
url: string,
geoCompressType: string, // 只能是 "draco"
geometryType: string, // 可选值 "Point"、"Line"、"Polygon"、"Surface"、"Entity"
}
struct Attribute {
attType: string, // 可选值 "json"、"bin"
uri: string,
}
6 *.m3d
文件
文中对 m3d 的介绍在 7.4 节,就不复述了,主要提一点:
- glbx:基于 glb 数据扩展了单体化信息及地质体数据信息的几何要素文件,后缀名为.glbx,文件名可自定义。
我觉得这个格式的提出是多余的,槽点最后再谈。
7. 地质模型几何结构定义
由于本部分(原文位于7.4.3节)在原文中较为详细,只捡几个点提一提。
7.1 钻孔模型几何结构定义
略。
7.2 地质剖面模型几何结构定义
剖面使用的是一个点列表表示的简单封闭三维线,这是有缺点的,因为不能表达多剖面的情况。
7.3 地质体模型几何结构定义
地质体在此处使用了多个索引三角面来定义,其实可以完全使用 glTF 来定义,只不过这里简化成了索引+顶点的形式。
7.4 网格模型几何结构
这部分的设计略显冗杂,有重复设计的嫌疑,没有优化。
8 属性文件的设计
属性文件的类型在 M3DDataInfo 对象中定义,见本文第 4 部分。
它使用 “属性记录(Record)” 来描述一个属性,又分了下列两个类定义:
struct Record {
oid: long,
rcdValues: Vec<RcdValue>,
}
struct RcdValue<T> {
fldName: String,
rcdValue: T
}
关于此部分的吐槽留到最后。
9 单体化的定义
m3d 标准使用一个叫 oidTable
的表,记录每个 “物件” 的单体信息。
具体做法是,对每个顶点进行设定值,例如房屋A设定每个顶点的值是1,那么就记录在 oidTable 中即可。
并由此扩展 glb 文件,构成 glbx 文件。
单开一小节吐槽
在 Cesium 提出的 glTF 扩展中,有一个更为接近此设计的扩充:glTF 中 Primitive 的定义增加一个 _BATCHID
,使得顶点属性新增一个,与 POSITION
、COLOR
等同级别,使用此 _BATCHID
来区分顶点归属于哪个 batch。
利用 glTF 强大的扩展能力完全没必要 “扩展 glb 文件成 glbx”。
另外,单体化也是一个伪命题。因为倾斜摄影模型仅仅是有外观的三角网模型,不具备精细模型和 GIS 要素的独立性,所以才提出 “单体化” 这种词。
在 GIS 数据、BIM 数据中就很少听说过这词,因为这两种数据本身就有着完备的物件独立信息。
如果模型建模时,有完整的地理要素的定义,其实就具备了单体化的信息,使用 GIS 中的 “要素” 完全可以区分开不同的物体。
10 Shared
文件夹
关于此文件夹没有过多的描述,只有不到 1/4 页纸。
它似乎是设计来存放公共数据的,用来提供 instance
能力。
11 RESTful 接口设计
11.1 数据信息获取服务:M3DData
url模板:<base-url>/services/<service-name>/M3dServer
例:
//igserver.mapgis.com/igs/rest/services/wuhan-3d/M3dServer
调取方法是 HTTP GET
,返回类型是 application/json
11.2 公共资源获取服务:M3DSharedResources
url模板:<m3d-data-url>/shared-resources
例:
//igserver.mapgis.com/igs/rest/services/wuhan-3d/M3dServer/shared-resource
调取方法是 HTTP GET
,返回类型是 application/octet-stream
11.3 根节点信息获取服务:M3DRootNodeInfo
略
11.4 其他服务
为什么写两个就不写了?
因为原文定义已经很详细了,无需复述。
12 优点
- 几何部分使用
glTF
定义 - 能使用
webp
作为纹理贴图 - 容纳了各行各与的业务数据
- 规范定义较为完备,文档废话不多,但是不失详尽性
- 参考了i3s的结构定义,设计上把几何、纹理、属性数据解耦
- 在几何部分能重视 draco 压缩算法
13 缺点
① 对 glTF 规范认知不全
没有充分阅读 glTF 规范
- glTF 本身就含有纹理贴图、采样器、材质的定义,此处再将纹理贴图单独定义,但未指出究竟是几何部分的 glb/glbx 独立存储几何 + 纹理单独存放在纹理目录,亦或者是混用,这一点十分疑惑。
没有充分利用 glTF 强大的扩展能力
- glTF 本身就带了十几种世界各大组织提供的扩展能力,包括但不限于纹理压缩、几何压缩、材质扩展
如无必要,勿增实体。glbx 是一种多余的设计。
② 文档定义缺陷
定义歧义
对于路径的定义,在原文见过 Uri
、url
、string
三种写法。
对于空间剖分的定义,在文件组织上,使用 Node
文件夹来组织,但是其下的各部分数据又使用 Tile
来定义,在原文第 3.2 节还专门说明了瓦片数据,却不见 Node
的详细定义;
在图1和图2中,对 Node 和 m3d 文件的组织十分困惑,Node<i>
目录应该是一个 Node
,下面能挂载 N 个 m3d 文件;但是在原文 3.3 节中,每个瓦片下面挂 0 或 1 个 m3d 文件,却不见瓦片的定义,只见 tile data(瓦片数据)
的定义。
asset
属性按理说可以囊括 name
、version
、guid
、dataName
等属性的,却将他们分开,不知道设计上是否经过足够的考虑。
类型错误
在 M3DDataInfo 的类定义中,对于 version
字段的类型,表格里是 float
,表格上面的 UML 类图又是 string
,这种低级错误应该在校对的时候修正。
UML 描述不够完整
属性的默认值、属性的可选性未指出。
属性拼写不规范
例如 geoCompressType
,此属性是 Geometry
类下的,按理说就不需要 geo
前缀,只需保留 compressType
即可。
还有 attType
,该简写的地方简写,此处就不该简写,它是 Attribute
对象的一个属性,所以它应该是 type
。
RcdValue
对象应该完整的命名为 RecordValue
,且其 rcdValue
应简单命名为 value
,它的类型还出现了模糊不清的定义,因为没有人知道 value
是什么值类型。
驼峰命名法、下划线命名法混用就不说了。
③ 标准里太多 “专有” 词汇
- 单体化:这个就不适用于全体行业数据,在本文第 9 节已详尽提及。
- 其他见 本文 14.②
④ 对一些属性未完全解释
lodType
:对"ADD"
、"REPLACE"
未完全解释lodError
:未解释其详细的值含义
14 槽点
① ArcGIS Server REST接口风格味道太重
在原文的第 8 部分体现的十分明显,RESTful
风格其实完全没有必要设计如此冗杂的 URL 模板。
② 对数据的行业层级和结构层级混淆
数据的结构层级是与行业无关的,例如 “矢量” 是一种概念,矢量数据可以囊括二维图形、三维图形数据,完全可以描述几何部分。
行业层级必须基于结构层级去定义,才有物质基础,这一点马克思老爷子早就总结好了。如果一个行业的数据对其底层的结构不清楚,例如单提 “BIM数据”,不提是什么格式,不提它的结构,那也只能是谈判桌上的词,落实不下来,谈不了什么自主创新。
③ 使用商业压缩格式 rar,使用过多的压缩格式
若为开放标准,而使用半封闭的 rar 格式,则必须在服务端多准备一个 rar 解压的程序。
zip、7z 作为存储时能有效减少体积,但是文章中并未提及这两种压缩方式的压缩等级、压缩与解压缩的性能开销,如果因为这些延长了文件 IO 的时间,那是得不偿失的。
这一点是 i3s 的资源 gzip 法则占优,因为浏览器天生支持 gzip,并且开销不算很大。
④ 扩展性不佳
若将来要新增气象数据或时态数据,没有留有冗余的设计。
15 个人总结
定义这种类型的数据规范,不仅要有足够充足的行业一线实践经历,还要在 Web 可视化领域有足够的底层技术积累。
从技术和数据的视角做定义,才具备完整的可实施性和一般适配性。
M3D 的提出丰富了我国对三维数据规范的标准库,但是现在缺实现,生态不足,推广几乎没有,如何让各行各业买账,还需要走相当长的路,务必脚踏实地,方可行百里路。