BIM审核项目中 在建筑模型数据入库后 经常有模型构件进行投影的场景 模型投影调用超图Supermap iObjects Java提供的GeoModel3D.converToRegion()接口。而当对某些复杂的模型构件 节点数目过多 进行投影操作时 运算效率较低 有时超过30分钟。经过项目组的优化 有一定的优化效果 但是并没有彻底改善这个问题。
如下图所示的来源于某桥梁模型的构件 以三角形网格表达 该模型共有173926个节点和346459个面。
成功运行构件投影测试脚本 在windows虚拟机上运行时间大约为16分钟 合960s。
二、问题分析2.1现象观察从这个图像中 我们有下述两点观察
①真正对投影面积有较大影响的底面结构比较简单 可以清晰地看到网格。
②所占数据较大的 其实是图中的圆柱体结构。如果利用超图接口进行计算 输入数据是进行过坐标转换到WGS84的三维坐标数据。耗时的方法是GeoModel3D.convertToRegion()方法。该方法执行时间超过16分钟 通过该方法计算后并转换回投影坐标系中计算出来的面积为5339370351平方毫米。
针对这一问题 首先可以尝试寻找超图接口的替代方法 除此之外 也可尝试探索对原有网格进行简化后再进行算法的方法。
经过多方资料查询 尝试从网格简化和shapely库地理计算的两个角度出发解决该问题。
2.2.1基于Shapely的自行实现经过讨论 技术团队认为本问题可以抽象为两个步骤
①第一步将三维模型中的所有节点都投影至XY平面 并完整的保留三角形网格 此时三角形网格会在同一个XY坐标发生重叠。
②第二步针对超多数量的三角形网格进行一元聚合(unary_union) 即在三角形重合的地方进行合并。合并之后即可以调用聚合后的二维图形的面积接口 计算出投影面积。
具体实现上 第一步比较简单 直接忽略z轴坐标 并按照三角形网格的相同接口组成polygon即可以 第二步利用到的则是python shapely库的unary_union接口 该接口是GEOS库中UnaryUnionOp类的简单封装。
面积的计算结果为5340510605平方毫米 与超图的计算结果仅有1.14平方米的误差。鉴于超图的计算过程中从原始数据进行了坐标转换后又转换回来 怀疑是这个过程中有精度损失。
在同一台机器的相同的计算环境下 计算时间则大幅度减少到158s 仅仅是原来的16.5%。
2.2.2网格简化算法经过调研 学术界在网格简化问题上影响力比较大的是卡内基梅隆大学在20世纪末提出的基于Quadric Error Metrics的算法。16年发布的开源软件MeshLab基于VCG Library对该算法有一个实现(功能名称为Quadric Edge Collapse Decimation) 因此可以借助该软件对简化后再进行计算的思路进行验证。而且MeshLib提供了cmdline的接口 从而使得该算法能力能够简单地被嵌入系统 或者可以直接包装VCG Library的相关接口 接入该算法能力。
利用该算法将原有34万 面的三维模型分别简化为16万、8万、4万、2万、1万个面 从图像上的直观上看几乎没有区别。技术团队也针对简化后的模型计算了投影面积 并测量了计算时间 总结如下表
顶点数量 ???面数量 ???面积/平方毫米 ?计算时长/秒 ?
原始数据?????173926 ????346459 ? ?5340510605 ? ? 158 ? ?????已验证
1/2简化??????80630 ????159992 ? ?5340510605 ? ? 119 ?? ?????已验证
1/4简化?? ? ??40584 ?????80000 ? ?5340510605 ? ? ?64 ???????已验证
1/8简化?? ? ??20518 ?????39999 ? ?5340510604 ? ? ?24 ???????已验证
1/16简化?? ??10391 ?????20000 ? ?5340510885 ? ? ?10 ????????已验证
1/32简化?? ? ??5289 ??????9999 ? ?5340510877 ? ???4 ?? ?? ????已验证
从结果中可以看出 即使简化为1万个面 计算出来的面积与原始数据的偏差只有200平方毫米。
2.2.3初步结论①针对该问题 通过采用比较成熟的开源计算方案 计算效率可以得到大幅度提高 从16分钟缩短到3分钟之内。
②网格简化算法能进一步加速计算 最快4s可以得到结果 误差微乎其微。
三、接口设计思路希望以新接口替代超图的GeoModel3D.convertToRegion() 所以设计入参 GeoModel3D return GeoRegion
实现路径 GeoModel3D-- 骨架(顶点串及序列)-- meshlab 接口接受的数
据结构(off)-- 简化网格-- 节点降维-- 重合处三角合并-- 转换GeoJSON ??-- GeoRegion
三、代码实现3.1 获取三维模型构件点集获取超图 Model 对象的顶点集合与序列并输出 off 格式文件的方法参考以下代码
/**
* 将 Model 对象转换为 off 格式并在指定位置输出
*
* param model Model 对象
* param originalOffPath ?off 输出路径
*/
private void outputOffFile(Model model, String originalOffPath) {
List Double verticesList new ArrayList ();
List Integer vertexIndexesList new ArrayList ();
//获取 GeoModel3D 顶点点串与序列
for (int index 0; index model.getSkeletonCount(-1); index ) {
SkeletonID id new SkeletonID(-1, index);
Skeleton skeleton model.getSkeleton(id);
double[] vertices skeleton.getVertices();
List Double tempVerticeList Doubles.asList(vertices);
verticesList.addAll(tempVerticeList);
int[] vertexIndexes skeleton.getVertexIndexes();
List Integer tempIndexlist Ints.asList(vertexIndexes);
vertexIndexesList.addAll(tempIndexlist);
}
//输出 originalOff.off
writeOff(verticesList, vertexIndexesList, originalOffPath);
}
注意 此处的代码 getSkeletonCount()和 SkeletonId()方法中的 LOD 层级取值-1 代表当前 LOD 层级 此种取值是约定俗成的。
3.2 Windows环境调用meshlabserver方法我们应用meshlab的Quadric Edge Collapse Decimation方法对模型骨架转化得到的off文件进行模型简化。为将这部分工作做代码实现 我们通过Meshlab的.mlx脚本 保存对原数据的操作 然后通过调用meshlabserver进行批量处理。
3.2.1 meshlab方法参数研究在 做 三 维 模 型 简 化 时 主 要 使 用MeshLab的Quadric Edge Collapse Decimation方法 其参数较多 经过试验 建议以下参数配比
其中 需要注意的参数有以下几个
①Target number of faces 目标面数。简化目标面的数量 该参数决定了简化的程度 软件中针对特定模型进行手动操作尚可 代码实现需要频繁改变脚本参数 不适用。
②Percentage reduction (0..1) 简化百分比。默认为0 当设置不为0时 简化面数是以该参数为主 替代Target Number of Faces 如此可以跳过动态编辑mlx脚本 减少运行时间。且根据经验 最好设置为0.5的整数次幂。
③Preserve Topology 保留拓扑关系。因为模型可能存在复杂的拓扑关系 简化后的模型进行投影后 希望保留其应有的岛洞关系 因此此处勾选。
其余参数 默认值即可。
--
3.2.2 meshlabserver命令行执行如图为meshlabserver在Windows上的cmd调用实现
cmd 语法为
meshlabserver 路径 -i 原始 off 路径 -o 简化输出 off 路径 -m vc vn -s mlx
脚本路径
如此得到简化off后 python读取其中的顶点位置与序列信息 进行降维与投影合并。
3.3 python库导入为实现三维骨架点串的节点降维、重合处三角合并、聚合后二维图形面积获取等阶段性目标 需要导入若干python库 首先安装python 3.8版本即可。
导入shapely库 使用unary_union()做投影面合并 导入geojson库 实现
shapely.Polygon转化为GeoJSON文件。使用pip install命令安装以上库即可。
①?shapely
②?geojson
3.4 off的降维合并与输出简化后的off python读取其点串信息 降维构件二维面 并调用shapely
库的unary_union()方法实现面的合并 代码如下
?
def get_projection_area(vertices, triangles):
polygons []
for i1, i2, i3 in triangles:
p1 vertices[i1]
p2 vertices[i2]
p3 vertices[i3]
poly Polygon(((p1[0], p1[1]), (p2[0], p2[1]), (p3[0], p3[1])))
if not poly.is_valid:
continue
polygons.append(poly)
print( polygon contructed )
unioned unary_union(polygons)
print( unioned )
print( Area: %f % unioned.area)
return unioned
求得合并投影面后 转换为GeoJSON格式 以供构建超图GeoRegion使用。
代码如下
def write_geojson(polygon, output_path):
#p wkt.loads((str)(polygon))
# using geojson module to convert from WKT back into GeoJSON format
geojson_out geojson.Feature(geometry polygon, properties {})
with open(output_path, w ) as outfile:
json.dump(geojson_out, outfile, indent 3)
outfile.close()
3.5 超图GeoRegion构建与坐标转换由于3.1中得到Model及骨架中的点集不含坐标系信息 所以在此基础上进行一系列计算得到的结果GeoRegion也是原点在o处的模型的投影面。因此合并投影面GeoJSON在转换回超图的GeoRegion对象后 需要根据其原始的空间矩阵做矩阵转换 恢复位置、比例、姿态 赋予空间信息。矩阵转换代码如下
/**
?* 对Model骨架点串简化投影合并得到的GeoRegion进行转化 使其恢复位置、比例、姿态
?*
?* param geoRegion ???????网格简化与unary_union()后获得的结果GeoRegion对象
?* param transformParams 矩阵转换所需的参数
?* return ??????????????????转换后的GeoRegion
?*/
private GeoRegion transformGeoRegion(GeoRegion geoRegion, TransformParams transformParams) {
????GeoRegion resGeoRegion new GeoRegion();
????if (null geoRegion || geoRegion.isEmpty()) {
????????return resGeoRegion;
????}
????try {
????????for (int i 0; i geoRegion.getPartCount(); i ) {
????????????Point2Ds point2Ds geoRegion.getPart(i);
????????????Point2Ds point2DsNew new Point2Ds();
????????????for (int j 0; j point2Ds.getCount(); j ) {
????????????????Point2D point2D point2Ds.getItem(j);
????????????????Double x point2D.getX();
????????????????Double y point2D.getY();
????????????????// 1 缩放
????????????????Double xRes1 x * transformParams.getScaleX();
????????????????Double yRes1 y * transformParams.getScaleY();
????????????????// 2 旋转
????????????????//绕x轴
????????????????Double yRes2 yRes1 * Math.cos(transformParams.getRotateX());
????????????????//绕y轴
????????????????Double xRes2 xRes1 * Math.cos(transformParams.getRotateY());
????????????????//绕z轴
????????????????Double xRes3 xRes2 * Math.cos(transformParams.getRotateZ()) - yRes2 * Math.sin(transformParams.getRotateZ());
????????????????Double yRes3 yRes2 * Math.cos(transformParams.getRotateZ()) - xRes2 * Math.sin(transformParams.getRotateZ());
????????????????// 3 平移
????????????????Double xRes4 xRes3 transformParams.getOffsetX();
????????????????Double yRes4 yRes3 transformParams.getOffsetY();
????????????????Point2D point2dNew new Point2D(xRes4, yRes4);
????????????????point2DsNew.add(point2dNew);
????????????}
????????????resGeoRegion.insertPart(i, point2DsNew);
????????}
????} catch (Exception e) {
????????e.getMessage();
????}
????return resGeoRegion;
}
将目前的方法与超图convertToRegion()方法计算结果对比 在表中取形状复杂的构件进行测试 分别比较各自投影面的面积相似度与空间覆盖率 结果较为理想。
测试结果
最终结果与超图convertToRegion()方法获取的GeoRegion比对不仅面积值
相差小 且基本重合 可知思路正确。但可供测试的超大顶点模型构件数量有限
且测试构件复杂度不足 仍需获取更多复杂构件进行进一步测试并优化方案。
图片来自 Pexels 我 8 点半左右到家,立马上线入会...... 重启 我入会的时候,已...
上次我们讲了一下钉钉专属存储的三种部署架构,除了架构外,在钉钉专属存储部署...
本文转载自网络,原文链接:https://mp.weixin.qq.com/s/vqRfJtXcXg6fS_aeX0x8jQ...
本文介绍了阿里云资源编排服务ROS如何帮助OA实现自动化部署,大大提升效率。 公...
2018年1月,百度智能云授权TOP云(zuntop.com)科技公司为河南的服务中心,TOP云...
正泰新能源是正泰集团旗下集清洁能源开发、建设、运营、管理于一体的能源解决方...
案例背景 梅城镇位于建德市东南部,距杭州市中心110公里,建德市区35公里。为响...
物联网已历经十余年的发展期,尤其是近几年,物联网的发展动能不断丰富,市场潜...
说明 JetBrains 全系列产品永久激活教程 适用于 JetBrains 全系列产品 2018、201...
TOP云 (west.cn)10月17日消息,双11快到了,各大商家又在摩拳擦掌了,而快递却...