下一代 3DTiles(Next)-瓦片属性数据组织扩展3DTILES_metadata
3D Tiles Next 是一组针对下一代 3D Tiles 的新功能,分6个章节对3D Tiles Next进行详解。
3D Tiles Next 是一组针对下一代 3D Tiles 的新功能(或者说 3D Tiles 扩展)。
这些新功能目前以 3D Tiles 1.0 的扩展草案呈现,将来可能会合并到 3D Tiles 2.0 中。
3D模型在线预览提供多种低代码平台3D模型在线预览解决方案,实现了将多种3D模型格式无缝集成到低代码业务表单中。这意味着用户可以在不离开低代码平台的情况下,直接查看和操作3D模型,极大地提升了数据可视化的效果和用户交互体验。
依赖于
3D Tiles 1.0
可选与必需
如果用到了属性元数据,那么这项扩展必须同时出现在 extensionsUsed
和 extensionsRequired
数组中,即“必需的”。
概述
此扩展定义了一种在 3D Tiles 中存储结构化的元数据的机制,这些元数据一般又称作 属性数据,来源广泛,可用于数据检查、分析、样式化 3D Tiles 或者其他用途。
这些属性数据要根据模板来记录,这种模板被称作“Schema(模式)”,使得属性数据有章可循。而且,不同层级的对象(最大的对象是 Tileset,依次向下为 Tile 等)有不同的关联方式。
下列说明不同层级的对象的元数据(属性)的细节:
- 对于 Tileset 级别,属性元数据记录的是整个数据集级别的信息,比如数据发布年份等
- 对于 Tile 级别,属性元数据可以记录更细粒度的信息,例如瓦片数据内容的最大高度等
- 对于 Tile Content Groups 级别,可以将分组后的瓦片数据内容的属性元数据也分组来存储
下图展示了 3D Tiles 中各个级别的对象(tileset、tiles、content、group)之间的关系及属性元数据的实例:
属性元数据有什么用
- 直接查看:在界面上鼠标滑过或点击瓦片时,展示属性元数据
- 分组:瓦片的数据内容可以分组,每个组可以有自己的属性元数据,这样就可以根据每个组自己的属性元数据来控制显示隐藏等组内瓦片全部一致的操作
- 支持复杂格式的数据:此扩展支持 Schema 嵌入在 JSON 内,也支持外部引用,这样各个领域的专家就可以把行业数据编入其中
- 渲染优化:属性元数据可能会包含一些性能优化相关的参数,这样可以在遍历等有需要的时候优化相关算法
属性元数据
概述
Property 描述某个实体(实体指 Tileset、Tile 或者 TileGroup)的信息。
Schema 为 Class 定义数据类型等元数据信息。
每个实体,都是 Class 的具体实例,有着 Class 内记录的数据值。
此外,Statistics 可把特定 Class 内的统计值抽取出来单独存储;Semantic 则用于定义特定 Property 的用法和含义。
schema
一个 schema
一般由 classes
和 enum
这两个描述数据存储格式的属性组成,其余描述性的属性有 id
、name
、description
、version
、extensions
和 extras
。
classes 由若干个 key-value 的 class 构成。
class
每个 class 抽象了一类元数据,名称即 key。
有具体的 properties 描述由什么属性构成。
举例:
"building"
即一个 class,它有 3 个 "property":"name"、"id"、"height",即下面一小节要介绍的 class property。
class property
每一个 class 都有 properties
属性对象,它的每一个 key-value 即一个 property
所有的 property 用来形容该 class 的数据构成。
用于形容一个 property 的属性,用 componentType、type、semantic 等字段来量度。
① property.componentType
属性的 componentType
即组分类型,有如下几种:
"BOOLEAN"
,布尔类型"STRING"
,字符串类型"ENUM"
,枚举类型,参考 2.2.3 小节"INT8"
、"INT16"
、"INT32"
、"INT64"
,有符号整型,从 8 ~ 64 位"UINT8"
、"UINT16"
、"UINT32"
、"UINT64"
,无符号整型,从 8 ~ 64 位"FLOAT32"
、"FLOAT64"
,浮点型,32 和 64 位两种
② property.type
有四类,均为字符串:
"SINGLE"
,默认的,单值类型"ARRAY"
,数组类型"VEC2"
、"VEC3"
、"VEC4"
,向量类型"MAT2"
、"MAT3"
、"MAT4"
,矩(方)阵类型
其中,"SINGLE" 和 "ARRAY" 类型的 property,其元素可以是任何数据类型;"VECN" 和 "MATN" 类型的 property,其元素类型只能是数值。
③ property.semantic
可以为 property 打上特殊含义,即使用 property.semantic
属性,来标记前端所需要的额外的信息。它的值可以是任意的字符串,也可以是由规范内置的 semantic
,见 三维元数据语义参考(3D Metadata Semantic Reference)
举例说明,以 "building" 这个 property 为例:
可见它有 3 个 property:
"name"、"id" 和 "height",
它们的 "semantic"
分别是:
"NAME"、"ID" 和 "_HEIGHT"。
其中,"NAME" 和 "ID" 是规范内置的,"_HEIGHT" 是自定义的。
enum
enum,即枚举,上一小节的 property.componentType 中允许有 "ENUM"
类型,即 class 的 property 的值,允许是枚举类型的。
举例:
enums
与 classes
是同级别属性,此处定义了一个枚举类型 "qualityEnum"
,有个可用于显示的 "name"
属性 "Quality",可见它的枚举值以数组形式给出:
其中,"name"
是枚举的值的名称,"value"
是枚举值的数值。
注意,此处 Unspecified
枚举值是特殊且是可选的,可用来识别缺失的数据。
外部 schema
若 "3DTILES_metadata"
不提供 "schema"
属性,则由外部 json 提供模式定义:
statistics
统计信息用于记录各个 class 的统计学变量,例如数值的最大最小值、平均数,枚举的频次等。
代码中可以用这些统计信息来进行数据分析、显示等操作,例如使用 [声明式样式语言]()。
统计信息是按 class 提供的。基于整个 tileset 提供样式化或者有关上下文之后,程序代码只需管理好瓦片的下载和处理即可。
举例:
这里只有一个 class,单独摘取其统计信息如下:
容易看出,数值属性 height
记录了最大最小值,分别是 341.7 和 3.9;枚举属性 buildingType
中三个枚举值的频率分别为 50000、40950、50。
自定义统计值
规范允许添加自定义的统计信息,为了不与官方统计变量名称冲突,只需在自定义的统计变量名前加一个下划线,例如上面例子中 height 属性的自定义统计变量 _mode
,其值是 5.0。
官方内置统计变量
名称 | 描述 | 适用类型 |
---|---|---|
minimum | 最小值 | SINGLE、ARRAY、VECN、MATN |
maximum | 最大值 | 同上 |
mean | 算术平均值 | 同上 |
median | 中位数 | 同上 |
standardDeviation | 标准差 | 同上 |
variance | 方差 | 同上 |
sum | 和 | 同上 |
frequencies | 频次 | ENUM |
其中,frequencies 以对象形式出现,其键是枚举的 name,值是频次;其余统计变量的值均为数值。见上面例子。
元数据的分配
classes
只是定义了属性元数据的数据类型和含义,3D Tiles 中的实际元素并未真正具备实际的数据。
每个 3D Tiles 实际元素的属性元数据都会包含它属于哪个 class
,用 class 的 name 来标识,而且属性元数据的具体属性字段都与 class 中规定的字段对应(名称一致,数据类型一致),如果某个属性字段在它的 class 中没有 required: true
的规定,那么它可以不出现在 JSON 中。
大多数属性元数据直接写在 JSON 中,但是隐式瓦片分割扩展的 tileset 中的隐式瓦片的属性元数据是例外,它用二进制的方式存储属性元数据。
分级:
- Tileset 级别的属性元数据
- Tile 级别的属性元数据(包括隐式瓦片的属性元数据)
- ContentGroup 级别的属性元数据
- ContentFeature 级别的属性元数据
接下来由粗到细地看属性元数据在各个层级的元素上的具体例子。
Tileset 层级的属性元数据
规范参考 tileset.schema.json 和 metadataEntity.schema.json
作用在整个 tileset 级别的属性元数据,常见的属性可能有:年份信息、数据生产者详细信息、tileset 的一般性上下文等。
例子:
这个例子中的 extensions
是整个 tileset.json 的顶级属性,tileset
属性和 schema
是同级别的,均为 extensions.3DTILES_metadata
的成分。
tileset
属性指定了这个 tileset 最顶级元素,即瓦片数据集本身的属性元数据。它的 class 是 "city"
,可以在 schema 中找到,例子中将 city 这个 class 的三条 required: true
属性分配到了,即 "name"
的 "Philadelphia"
,"dateFounded"
的 "October 27, 1682"
和 "population"
的 1579000
。
Tile 层级的属性元数据
规范参考 [tile.3DTILES_metadata.schema.json]() 和 [metadataEntity.schema.json]()
每个瓦片允许有自己的属性元数据,例如添加一些空间信息以方便空间索引算法的遍历。
下面的例子会使用内置的 semantic TILE_MAXIMUM_HEIGHT
,具体参考 (3D Metadata Semantic Reference)[]
很容易看到这是一个传统的 tileset,没有使用隐式瓦片分割扩展。
属性元数据作用在 root 瓦片的 extensions.3DTILES_metadata 属性上,指明了属性元数据遵循 tile 这个 class,它记录了 tile 这个 class 规定的两项属性字段,其中 maximumHeight
字段拥有内置的语义(semantic)—— "TILE_MAXIMUM_HEIGHT",其值指定为 4418
;countries
字段则是一个常规的字符串数组,其值为 ["United States", "Canada", "Mexico"]
。
隐式瓦片的属性元数据
规范参考 [subtree.3DTILES_metadata.schema.json]() 和 [metadataEntity.schema.json]()
此部分需要有 3DTILES_implicit_tiling 的先导基础。
若某个 tileset 启用了隐式瓦片分割扩展,那么 tileset.json 中就没有每个 tile 的 JSON 定义了,此时每个 tile 的属性元数据就需要记录在别的地方,那就是 subtree 文件中的 JSON 和 binary 中,也就是说 3DTILES_implicit_tiling 扩展可以与 3DTILES_metadata 扩展兼容使用。
既然 subtree 文件是二进制文件,那干脆就把属性元数据也编码进二进制中,JSON 部分只留读取二进制中属性元数据的字节存储描述信息。
二进制编码对大数据集是有好处的。
注意,瓦片的属性元数据仅对可见的瓦片有效,并且对于每个可见的隐式瓦片,就算没有某个属性字段,那这个属性的值也需要使用对应的 noData 符号占位。
下列是一个 subtree 文件中 JSON 的部分(不包含注释):
瓦片可见性、瓦片内容可见性、孩子子树可见性数据被编码在 0、1、2 这三个 bufferView 所指向的二进制数据中,但是这个 subtree 的 buffer 却有 7 个 bufferView,其中 3、4、5、6 号 bufferView 正是给 3DTILES_metadata 用的。
3DTILES_implicit_tiling 规定了瓦片的分割方案、子树的可见性内容编码,这样在 tileset.json 中就无需显示指定每一层每一个瓦片对象的信息了,一切都可以根据 level 和瓦片坐标计算而来。
与上一小节的 root 瓦片类似,在这一簇 子树中,每一个隐式瓦片的属性元数据都实现自 tile 这个 class,只不过具体的属性字段信息不再是 JSON,而是编码成了二进制,所以在 properties 中看到的数据指向是 bufferView 的编号。
例如,horizonOcclusionPoint 属性字段的数据存储在 bufferViews[3],countries 属性字段因为是不定长字符串数组类型,所以有三个数值需要存储,即数据本身 bufferView[4],存储 arrayOffset 的 bufferView[5],和存储 stringOffset 的 bufferView[6]。
具体二进制是如何存储这些类型各异的元数据的可以参考 三维元数据规范 - 存储格式 中 BinaryTableFormat 的部分,在 Array part 的第二个例子中有图示,简单的说就是数据本身是字符串的各个字符的字节信息,stringOffset 是每个字符串的起始长度,arrayOffset 则是每一个不定长字符串数组的起始 stringOffset 索引号。
Content Group 的属性元数据
规范参考 [group.schema.json]()、[tileset.3DTILES_metadata.schema.json]() 和 [metadataEntity.schema.json]()
一个 Tile 可能会包含超过一个数据内容(参考 3DTiles Next 扩展 3DTILES_multiple_contents),或者说多个瓦片数据内容是可以共享一个元数据的。本扩展可以与多重瓦片内容的瓦片一起搭配使用。
对属性元数据进行分组是有意义的,比如瓦片内只有一部分数据内容是有元数据的,这样只需记录有元数据的那部分瓦片内容的属性元数据;或者像“图层”一样使用多重瓦片内容的瓦片,可以使用属性元数据来控制瓦片的数据内容的可视化或者样式化。
瓦片数据内容是如何分配分组的属性元数据的呢?在 tile.content 的 JSON 中,记录其 extensions.3DTILES_metadata 属性,指派 group
属性即可。每个 tile.content 只能对应一个 group,但是一个 group 可以赋予给任意个 tile.content。
下面为官方例子:
这个例子有一个 layer
class,与 schema 同级别的有一个 groups
属性,意味着属性有两个分组,一个是 buildings
属性,一个是 trees
属性,这两个的属性均为 layer 这个 class 的具象化,而落实到具体的瓦片数据内容上,就是 root 瓦片的两个复合数据内容所指派的信息了,譬如 content[0]
,它的 uri 是 "buildings.glb"
,它的属性元数据指派为 buildings
这个 group。
更深层次的要素级属性元数据
3D Tiles 只记录瓦片的结构拓扑,但是每个瓦片的数据内容仍然可以细分为不同的层级。
这部分不属于 3D Tiles 中 “tile” 的定义了,需要到 glTF 的扩展中补充扩展定义,即 EXT_mesh_features 扩展,这与 3DTILES_metadata 并不冲突,都是 [三维元数据规范(3D Metadata Specification)]() 的内容,只不过上述四类属于瓦片层级,再往下探则能细化元数据到 glTF 的 primitive、mesh、node 等元素里了。
下一代 3DTiles(Next)- 索引
下一代 3DTiles(Next)-瓦片数据扩展3DTILES_content_gltf
下一代 3DTiles(Next)-瓦片数据扩展3DTILES_multiple_content
下一代 3DTiles(Next)-瓦片组织扩展3DTILES_implicit_tiling