以下是使用 Python 的trimesh库计算 3DTiles 中.b3dm瓦片体积的具体代码示例。核心流程是:解析.b3dm 文件提取 glTF 模型→用 trimesh 加载模型→校验闭合性→计算体积。
步骤说明
解析.b3dm 文件:.b3dm 是 3DTiles 的静态模型瓦片格式,内部封装了 glTF 二进制数据(.glb),需先提取 glTF 内容。
加载模型:用 trimesh 直接加载提取的 glTF 数据。
校验闭合性:只有闭合模型(watertight)才能计算有效体积,需先检查。
计算体积:调用 trimesh 的体积计算接口,输出结果(单位取决于模型坐标单位,通常为立方米)
import os
import struct
import tempfile
import trimesh
def parse_b3dm(b3dm_path):
"""
解析.b3dm文件,提取内部的glTF二进制数据(.glb格式)
:param b3dm_path: .b3dm文件路径
:return: glTF二进制数据(bytes)
"""
with open(b3dm_path, 'rb') as f:
# .b3dm头部结构(共28字节)
# 参考:https://github.com/CesiumGS/3d-tiles/blob/main/specification/TileFormats/B3DM.md
magic = f.read(4).decode('utf-8') # 应为"b3dm"
version = struct.unpack('<I', f.read(4))[0] # 版本号,通常为1
byte_length = struct.unpack('<I', f.read(4))[0] # 文件总字节数
feature_table_json_byte_length = struct.unpack('<I', f.read(4))[0] # 要素表JSON长度
feature_table_binary_byte_length = struct.unpack('<I', f.read(4))[0] # 要素表二进制长度
batch_table_json_byte_length = struct.unpack('<I', f.read(4))[0] # 批处理表JSON长度
batch_table_binary_byte_length = struct.unpack('<I', f.read(4))[0] # 批处理表二进制长度
# 校验文件格式
if magic != 'b3dm' or version != 1:
raise ValueError(f"无效的.b3dm文件:{b3dm_path}")
# 跳过要素表和批处理表,读取glTF数据(从头部结束位置开始)
header_size = 28
feature_table_size = feature_table_json_byte_length + feature_table_binary_byte_length
batch_table_size = batch_table_json_byte_length + batch_table_binary_byte_length
glb_start = header_size + feature_table_size + batch_table_size
f.seek(glb_start)
glb_data = f.read(byte_length - glb_start) # glTF二进制数据
return glb_data
def calculate_b3dm_volume(b3dm_path):
"""
计算单个.b3dm瓦片的体积
:param b3dm_path: .b3dm文件路径
:return: 体积(立方米),若模型不闭合则返回None
"""
try:
# 1. 解析.b3dm,提取glTF数据
glb_data = parse_b3dm(b3dm_path)
# 2. 用临时文件保存glTF数据(trimesh需从文件加载)
with tempfile.NamedTemporaryFile(suffix='.glb', delete=False) as temp_glb:
temp_glb.write(glb_data)
temp_glb_path = temp_glb.name
# 3. 用trimesh加载模型
mesh = trimesh.load(temp_glb_path)
# 4. 校验模型是否闭合(watertight)
if not mesh.is_watertight:
print(f"警告:{b3dm_path} 模型不闭合,无法计算体积")
return None
# 5. 计算体积(单位:取决于模型坐标单位,3DTiles通常为米)
volume = mesh.volume
# 清理临时文件
os.unlink(temp_glb_path)
return volume
except Exception as e:
print(f"处理{b3dm_path}时出错:{str(e)}")
return None
def batch_calculate_volume(b3dm_dir):
"""
批量计算文件夹中所有.b3dm瓦片的体积
:param b3dm_dir: 存放.b3dm文件的文件夹路径
:return: 字典{文件名: 体积}
"""
volume_results = {}
for filename in os.listdir(b3dm_dir):
if filename.endswith('.b3dm'):
b3dm_path = os.path.join(b3dm_dir, filename)
volume = calculate_b3dm_volume(b3dm_path)
if volume is not None:
volume_results[filename] = round(volume, 2) # 保留2位小数
return volume_results
# 示例用法
if __name__ == "__main__":
# 替换为你的.b3dm文件路径或文件夹路径
b3dm_directory = "./b3dm_tiles" # 存放.b3dm瓦片的文件夹
# 或单个文件:b3dm_file = "./tiles/building.b3dm"
# 批量计算文件夹中所有瓦片的体积
results = batch_calculate_volume(b3dm_directory)
# 输出结果
print("体积计算结果(单位:立方米):")
for filename, vol in results.items():
print(f"{filename}: {vol} m³")