在我最近的项目中,我开发一个 移动端离线地下管网系统,目的是通过 Cesium 来渲染地下管网的 3D 模型,并实现属性查询功能。由于这是一个离线系统,不依赖后端服务器来查询属性,因此我选择使用 Cesium 提供的接口,直接从 3D Tiles 数据中提取属性名并进行属性查询。
在这个过程中,我的管网模型被切分成了 3D Tiles 格式,瓦片为 b3dm 文件,并且包含 Batch Table,用于存储每个模型实例的属性信息。在这篇博客中,我将分享如何利用 Cesium 的接口,通过图层名动态获取属性名,并进一步基于属性名和属性值查询管网信息。
在使用 Cesium 渲染 3D Tiles 数据时,文件结构至关重要。通过你提供的截图可以看出,我的 3D Tiles 数据包括以下几个关键文件:
- tileset.json:描述 3D Tiles 数据结构的元数据文件,包含每个瓦片的位置、层次以及引用的具体数据文件。
- grid_0_0.b3dm:一个 b3dm 文件,存储了模型的几何数据(如 glTF 数据)。
- grid_0_0_batchTable.json:一个 Batch Table 文件,存储模型的属性信息,用于描述每个模型实例(如名称、编号、材质等)。
离线系统意味着所有数据都需要在本地运行,包括三维模型的渲染和属性查询。这带来了以下挑战和需求:
- 离线数据存储:模型切片为 3D Tiles 格式,瓦片数据保存在本地。
- 属性存储方式:属性信息通过 Batch Table 存储在每个 b3dm 瓦片中。
- 离线高效查询:在没有后端服务支持的情况下,需要直接从 3D Tiles 中提取属性并查询。
通过 Cesium 的 API,我们可以直接从瓦片的 Batch Table 中读取属性,无需额外开发后端逻辑。
核心代码实现
1. 获取属性名
在 Cesium 中,属性存储在每个 b3dm
瓦片的 Batch Table 中。我们可以递归遍历瓦片树,从每个瓦片中提取属性名。
async getPipelineFieldsFromFeatures(layerKey) {
// 获取指定的图层
const layer = this.pipelineLayers[layerKey.value];
if (!layer || !(layer instanceof Cesium.Cesium3DTileset)) {
console.error(
`无法找到图层或图层不是有效的 Cesium3DTileset 实例: ${layerKey}`
);
return [];
}
try {
// 确保瓦片加载完成
await layer.readyPromise;
// 使用 Set 确保属性字段唯一
const uniqueFields = new Set();
// 遍历图层中的所有瓦片
const traverseTiles = (tile) => {
if (tile.content && tile.content.featuresLength > 0) {
for (let i = 0; i < tile.content.featuresLength; i++) {
// 获取瓦片中的每个 Feature(模型实例)
const feature = tile.content.getFeature(i);
// 获取 Feature 的所有属性名
const propertyIds = feature.getPropertyIds();
propertyIds.forEach((prop) => uniqueFields.add(prop));
}
}
// 递归遍历子瓦片
tile.children.forEach(traverseTiles);
};
// 从根瓦片开始递归
traverseTiles(layer._root);
// 格式化输出属性名
return Array.from(uniqueFields).map((field) => ({
label: field, // 属性名的显示值
value: field, // 属性名的真实值
}));
} catch (error) {
console.error(`从图层 "${layerKey}" 提取字段失败:`, error);
return [];
}
}
用户选择图层后,会动态加载该图层的所有属性字段,供用户选择查询条件。管网系统中可能包括以下属性:
- 起点编号
- 终点编号
- 材质
- 埋深
- 规格
2. 基于属性名查询属性值
获取属性名后,我们可以通过用户指定的条件(属性名和属性值)查询模型实例。
async queryFeaturesByProperty(layerKey, fieldName, fieldValue) {
// 获取指定的图层
const layer = this.pipelineLayers[layerKey.value];
if (!layer || !(layer instanceof Cesium.Cesium3DTileset)) {
console.error(
`无法找到图层或图层不是有效的 Cesium3DTileset 实例: ${layerKey}`
);
return [];
}
try {
// 确保瓦片加载完成
await layer.readyPromise;
const matchingFeatures = [];
// 遍历瓦片
const traverseTiles = (tile) => {
if (tile.content && tile.content.featuresLength > 0) {
for (let i = 0; i < tile.content.featuresLength; i++) {
const feature = tile.content.getFeature(i);
// 根据属性值匹配实例
if (feature.getProperty(fieldName) === fieldValue) {
matchingFeatures.push(feature);
}
}
}
tile.children.forEach(traverseTiles);
};
traverseTiles(layer._root);
return matchingFeatures;
} catch (error) {
console.error(`查询图层 "${layerKey}" 的属性失败:`, error);
return [];
}
}