BIMFACE中矩形空间拆分与合并

应用场景

BIM运维场景中,空间同设备一样,作为一种资产被纳入运维管理体系,典型的应用场景例如商铺、防火分区等,这就涉及到空间的拆分和合并,在bimface中,已经实现了空间的动态调整,但是距离自定义的,较为直观的空间拆分与合并,目前的处理方式还不能够满足业务场景的需求,于是自行完成了基于bimface的矩形空间的拆分与合并的实现过程。

空间拆分与合并拆分空间监听模型单击事件1清除原有空间点击计数根据两点绘制直线2任意两点决定直线走向计算直线在坐标系中的斜率A和截距b计算直线与各边界交点3通过直线方程计算与各边的交点剔除不符合条件的交点判别拆分类型4切点邻边计算公共点计算交点邻接点点集排序切点对边区分交点与原始点集点分组排序切点对角线直接按对角线进行点排序计算拆分后的边界对象5根据排序后的点集生成可识别的边界对象绘制空间6合并空间边界数据整理1验证数据合法性(可选)2结构转换存储3筛选极值4构造边界对象5绘制空间6

先说合并

合并矩形空间的前提条件,是有两个及以上的且相邻的矩形空间,如果两个空间不相邻,也就失去了合并的意义,即使合并也不能够表达出真实物理世界的空间结构。空间合并相对来说比较简单,每个空间都是有一系列的有序的点围起来的二维封闭平面,这一系列的点集暂且称之为边界信息点集,加上高度参数就形成了三维立体空间。相邻的矩形空间必然会有近似重合的点,如下图的黄圈部分,如果把这些点去掉,只保留最外围的点(极值点,如下图的白圈部分),就形成了一个新的有序的点集,构成了新的边界信息,再加上合理的高度,被合并的空间就产生了。

以下是空间合并的核心代码:

/**
* 空间合并处理管道,适用于多个规则且相邻的矩形空间
* @param {Array} boundaryArray 空间边界数据数组(必填)
* @param {String} id 空间唯一标识(非必填)
* @param {Number} height 空间高度(非必填)
* @param {Glodon.Web.Graphics.Color} faceColor 空间表面颜色(非必填)
* @param {Glodon.Web.Graphics.Color} frameColor 空间轮廓颜色(非必填)
* @returns {Object} 新构造的空间边界
* @requires WebUtils
* @public
*/
mergeBoundaryPipeline: function (boundaryArray, id, height, faceColor, frameColor) {
if (!boundaryArray || !boundaryArray.length) {
console.warn("boundaryArray is empty!");
return;
} const vertical = 1;
for (let n = 0, len = boundaryArray.length; n < len; n++) {
//第一步:整理数据,去除小数部分
let cleanData = this.cleanBoundaryData(boundaryArray[n]);
//第二步:将所有的点数据存储至一维数组
this.storePointArray(cleanData);
} //第三步:筛选极值点
let extremum = this.extremumBoundaryPoint(this.pointCollection, vertical);
//第四步:根据极值点构造新边界
let newBoundary = this.buildBoundary(extremum);
this.viewer.createRoom(newBoundary, height || 5500, id || webUtils.guid(), faceColor || webUtils.fromHexColor('#ff0000', 0.25), frameColor || webUtils.fromHexColor('#ff0000'));
return newBoundary;
}, /**
* 通过顶点集合获取极值点,以便构造新的空间边界
* @param {Array} pointCollection 被合并前的多个空间的顶点集合
* @param {Number} direction 原空间的分隔方向 1:纵向 2:横向
* @returns {Array} 从一系列顶点中筛选出的顶点集合
*/
extremumBoundaryPoint: function (pointCollection, direction) {
const vertical = 1, horizontal = 2;
let extremumPoint = [];
minX = maxX = pointCollection[0].x;
minY = maxY = pointCollection[0].y;
for (let n = 1, len = pointCollection.length; n < len; n++) {
pointCollection[n].x > maxX ? maxX = pointCollection[n].x : null;
pointCollection[n].x < minX ? minX = pointCollection[n].x : null;
pointCollection[n].y > maxY ? maxY = pointCollection[n].y : null;
pointCollection[n].y < minY ? minY = pointCollection[n].y : null;
} for (let k = 0, len = pointCollection.length; k < len; k++) {
let currentPoint = pointCollection[k];
if (direction === 1) {
if (!(currentPoint.x > minX && currentPoint.x < maxX)) {
let exist = extremumPoint.some(item => {
if (item.x == currentPoint.x && item.y == currentPoint.y) {
return true;
}
return false;
}) if (!exist) {
extremumPoint.push(currentPoint);
} } else {
// console.log("分割方向:纵向");
}
}
if (direction === 2) {
if (!(currentPoint.y > minY && currentPoint.y < maxY)) {
let exist = extremumPoint.some(item => {
if (item.x == currentPoint.x && item.y == currentPoint.y) {
return true;
}
return false;
}) if (!exist) {
extremumPoint.push(currentPoint);
}
}
}
}
//对符合条件的点集进行顺时针排序,思路是找到最大和最小占1、3索引,剩余的两个点随机
return extremumPoint;
}

蓝色代表原始的分离的空间,红色代表合并后的空间效果

再说拆分

空间的拆分相对于合并就比较麻烦,因为合并只有一种方式,单拆分却有很多种。例如,沿着相对于空间水平方向或者垂直方向切割、沿着对角线切割、斜方向切割等,要考虑多种可能性。大体的思路是,首先监听鼠标单击事件,获取单击的两个点位置作为参数,可以计算出过该两点的直线,有了直线方程,再分别与空间边界的四条边计算交点,如果交点不在边界信息围成的区域内则丢弃,只保留在边界信息内的交点,如果与矩形区域相交,必然是两个交点(与矩形顶点相交没有意义,排除一个交点的可能),再按照拆分的类型分别计算拆分后的点集并排序,计算出两个新的边界点集,最终绘制出两个新的空间。



空间拆分的核心算法如下:

/**
* 根据二维坐标点集和求解二元一次方程直线
* @param {Array} pointArray 二维坐标点集合 [{x:100,y:200},{x:200,y:400}]
* @returns {Object} 返回直线【Y = Ax + b】的斜率【A】和截距【b】
*/
resolveEquation: function (pointArray) {
let result = {
A: 0, b: 0
};
if (!pointArray || !pointArray.length) {
console.warn("parameter pointArray invalidate!");
return;
} //解方程 Y = Ax + b 核心算法,此处考虑要不要四舍五入
let A, b
//不存在斜率
if (Math.round(pointArray[1].y) === Math.round(pointArray[0].y)) {
A = 0;
b = pointArray[0].y;
console.log("点集" + JSON.stringify(pointArray) + "对应的二元一次方程为:Y = " + b);
} else if (Math.round(pointArray[0].x) === Math.round(pointArray[1].x)) {
A = 0;
b = pointArray[0].x;
console.log("点集" + JSON.stringify(pointArray) + "对应的二元一次方程为:X = " + b);
}
//存在斜率
else {
A = (pointArray[1].y - pointArray[0].y) / (pointArray[1].x - pointArray[0].x);
b = pointArray[0].y - pointArray[0].x * (pointArray[0].y - pointArray[1].y) / (pointArray[0].x - pointArray[1].x);
console.log("点集" + JSON.stringify(pointArray) + "对应的二元一次方程为:Y = " + A + "*x + " + b);
}
result.A = A;
result.b = b;
return result;
}, /**
* 根据点集合与边界计算交点
* @param {Object} boundary 空间边界数据
* @param {Array} pointArray 分割点集合
* @param {Number} height 高度
* @requires RoomUtils
* @returns {Array} crossPointArray 直线与边界交点集合
*/
findCrossPoint: function (boundary, pointArray, height) {
let roomUtils = new RoomUtils();
//整理边界数据
boundary = roomUtils.cleanBoundaryData(boundary);
//计算分割点集所在的直线方程 Y = Ax + b
let { A, b } = this.resolveEquation(pointArray);
let pointList = boundary.loops[0];
//直线与边界的交点集合,N条边N个点,最终会保留两个交点
let pointCollection = [];
let crossObjectArray = [];
for (let n = 0, len = pointList.length; n < len; n++) {
//item => 标识线段的两端点集合 [{x:x,y:y},{x:x,y:y}]
let item = pointList[n];
let roundX0 = Math.round(item[0].x), roundX1 = Math.round(item[1].x);
let roundY0 = Math.round(item[0].y), roundY1 = Math.round(item[1].y);
let crossObject = { item: item, cross: false, crossBy: undefined };
//当边界线是垂直直线
if (roundX0 === roundX1) {
let y = this.calculateCoordinate(A, b, item[0].x, 0);
let point = { x: item[0].x, y: y, z: height };
//如果交点Y坐标在线段两端之间则加入到集合
if (Math.min(item[0].y, item[1].y) < y && Math.max(item[0].y, item[1].y) > y) {
pointCollection.push(new THREE.Vector3(point.x, point.y, point.z));
crossObject.cross = true;
crossObject.crossBy = new THREE.Vector3(point.x, point.y, point.z);
}
} //当边界线是水平直线
if (roundY0 === roundY1) {
let x = this.calculateCoordinate(A, b, 0, item[0].y);
let point = { x: x, y: item[0].y, z: height };
//如果交点X坐标在线段两端之间则加入到集合
if (Math.min(item[0].x, item[1].x) < x && Math.max(item[0].x, item[1].x) > x) {
pointCollection.push(new THREE.Vector3(point.x, point.y, point.z));
crossObject.cross = true;
crossObject.crossBy = new THREE.Vector3(point.x, point.y, point.z);
}
}
crossObjectArray.push(crossObject);
//其他情形暂不考虑,先验证可行性与准确性
}
return { pointCollection: pointCollection, crossObjectArray: crossObjectArray };
}, /**
* 创建拆分后的空间
* @param {Array} crossObjectArray 用于拆分空间的点集合
* @requires WebUtils
* @requires ModelHelper
* @returns {Array} 拆分后的空间边界集合
*/
buildSplitAreas: function (crossObjectArray) {
if (!crossObjectArray) return;
console.log(crossObjectArray); var webUtils = new WebUtils();
var modelHelper = new ModelHelper();
//标识切割边是否相邻
let isAdjacent = false;
let boundaryCollection = [];
//区分邻边还是对边
for (let i = 0, len = crossObjectArray.length; i < len; i++) {
if (i !== len - 1 && crossObjectArray[i].cross && crossObjectArray[i + 1].cross) {
isAdjacent = true;
}
};
//首尾相接时
if (crossObjectArray[0].cross && crossObjectArray[crossObjectArray.length - 1].cross) {
isAdjacent = true;
} console.log(isAdjacent);
//如果交点相邻
if (isAdjacent) {
//找到切割点的公共点作为中间点构件边界
let boundaryPoints = [];
let boundary = crossObjectArray.filter(p => { return p.cross }); //找到公共点,如果不是首尾相接,取中间,否则取两边
let commonPoint = webUtils.isObjectEqual(boundary[0].item[0], boundary[1].item[1]) ? boundary[0].item[0] : boundary[0].item[1]; //寻找相交线中非公共点
let leftPoint = [];
webUtils.isObjectEqual(boundary[0].item[0], boundary[1].item[1]) ? leftPoint.push(boundary[0].item[1], boundary[1].item[0]) : leftPoint.push(boundary[0].item[0], boundary[1].item[1]); for (let k = 0, len = boundary.length; k < len; k++) {
boundary[k].crossBy.z = 0;
boundaryPoints.push(boundary[k].crossBy);
}
boundaryPoints.splice(1, 0, commonPoint); //获取三角侧边界对象
var boundarys = modelHelper.buildAreaBoundary(boundaryPoints);
boundaryCollection.push(boundarys); //开始寻找另一侧点集
let oppositeBoundary = crossObjectArray.filter(p => { return !p.cross });
let oppositePoint = webUtils.isObjectEqual(oppositeBoundary[0].item[0], oppositeBoundary[1].item[1]) ? oppositeBoundary[0].item[0] : oppositeBoundary[0].item[1]; //组装另一侧空间边界
leftPoint.splice(1, 0, oppositePoint); //点集排序
if (leftPoint[0].x === boundary[0].crossBy.x || leftPoint[0].y === boundary[0].crossBy.y) {
leftPoint.splice(0, 0, boundary[0].crossBy);
leftPoint.splice(leftPoint.length, 0, boundary[1].crossBy);
} else {
leftPoint.splice(0, 0, boundary[1].crossBy);
leftPoint.splice(leftPoint.length, 0, boundary[0].crossBy);
} //获取非三角侧边界对象
console.log("leftPoint", leftPoint);
var boundarys2 = modelHelper.buildAreaBoundary(leftPoint);
boundaryCollection.push(boundarys2); } else {
let points = [];
//如果交点非相邻(对边)
if (crossObjectArray[0].cross) {
crossObjectArray[0].crossBy.z = crossObjectArray[2].crossBy.z = 0;
points.push(crossObjectArray[3].item[0], crossObjectArray[3].item[1], crossObjectArray[0].crossBy, crossObjectArray[2].crossBy);
boundaryCollection.push(modelHelper.buildAreaBoundary(points));
points = [];
points.push(crossObjectArray[0].crossBy, crossObjectArray[1].item[0], crossObjectArray[1].item[1], crossObjectArray[2].crossBy);
boundaryCollection.push(modelHelper.buildAreaBoundary(points));
} else {
crossObjectArray[1].crossBy.z = crossObjectArray[3].crossBy.z = 0;
points.push(crossObjectArray[0].item[0], crossObjectArray[0].item[1], crossObjectArray[1].crossBy, crossObjectArray[3].crossBy);
boundaryCollection.push(modelHelper.buildAreaBoundary(points));
points = [];
points.push(crossObjectArray[1].crossBy, crossObjectArray[2].item[0], crossObjectArray[2].item[1], crossObjectArray[3].crossBy);
boundaryCollection.push(modelHelper.buildAreaBoundary(points));
}
}
return boundaryCollection; }

总体效果



目前的空间拆分仅限于矩形空间,因为矩形的空间在BIM运维中相对来说是比较多的,而且算法相对简单一些,后续我们会逐渐探索非矩形空间,甚至是不规则多边形的空间拆分与合并算法,并应用到空间资产管理与运维场景中。

作者:悠扬的牧笛
地址:https://www.cnblogs.com/xhb-bky-blog/p/13500295.html
声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。

【BIM】基于BIMFACE的空间拆分与合并的更多相关文章

  1. NDK学习笔记-文件的拆分与合并

    文件的拆分与合并在开发中经常会用到,上传或是下载的时候都有这样的运用 文件拆分的思路 将文件大小拆分为n个文件 那么,每个文件的大小就是等大小的 如果文件大小被n除不尽,那么就使用n+1个文件来拆分 ...

  2. (数据科学学习手札84)基于geopandas的空间数据分析——空间计算篇(上)

    本文示例代码.数据及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在本系列之前的文章中我们主要讨论了g ...

  3. (数据科学学习手札88)基于geopandas的空间数据分析——空间计算篇(下)

    本文示例代码及数据已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在基于geopandas的空间数据分析系列 ...

  4. 基于Solr的空间搜索

    如果需要对带经纬度的数据进行检索,比如查找当前所在位置附近1000米的酒店,一种简单的方法就是:获取数据库中的所有酒店数据,按经纬度计算距离,返回距离小于1000米的数据. 这种方式在数据量小的时候比 ...

  5. pdf拆分与合并

    1.引用iTextSharp,用于拆分和合并pdf文件 using iTextSharp.text; using iTextSharp.text.pdf; 2.合并pdf //outMergeFile ...

  6. fasta文件拆分与合并

    Linux中fasta文件的拆分与合并 FASTA文件的拆分: (1)如果从一个文件a提取第11至20个序列存到另一个文件b: awk -v RS='>' 'NR>1{i++}i>= ...

  7. Goldengate进程的拆分与合并

    Goldengate的拆分与合并分类: ORACLE GoldenGate 2013-10-10 15:22 721人阅读 评论(0) 收藏 举报在使用Goldengate作为复制解决方案时,随着负载 ...

  8. 基于AutoCAD的空间数据共享平台雏形

    好久没有更新博客了,今天先透露一个新的产品——AutoMap.我自己对于这个产品的定位是“基于AutoCAD的空间数据共享平台”.用一句话来概括AutoMap的功能:为用户提供一个在AutoCAD下访 ...

  9. C#文件的拆分与合并操作示例

    C#文件的拆分与合并操作示例代码. 全局变量定义 ;//文件大小 //拆分.合并的文件数 int count; FileInfo splitFile; string splitFliePath; Fi ...

随机推荐

  1. 我搭的神经网络不work该怎么办!看看这11条新手最容易犯的错误

    1. 忘了数据规范化 2. 没有检查结果 3. 忘了数据预处理 4. 忘了正则化 5. 设置了过大的批次大小 6. 使用了不适当的学习率 7. 在最后一层使用了错误的激活函数 8. 网络含有不良梯度 ...

  2. Linux探测工具BCC(网络)

    Linux探测工具BCC(网络) 承接上文,本节以ICMP和TCP为例介绍与网络相关的部分内容. 目录 Linux探测工具BCC(网络) Icmp的探测 TCP的探测 Icmp的探测 首先看下促使我学 ...

  3. Nginx安装与运行配置总结

    Nginx安装与运行配置总结 1. 去官网下载对应的nginx包,推荐使用稳定版本 2. 上传nginx到linux系统 3. 安装依赖环境 (1)安装gcc环境 yun install gcc-c+ ...

  4. ReentrantLock与synchronized 源码解析

    一.概念及执行原理   在 JDK 1.5 之前共享对象的协调机制只有 synchronized 和 volatile,在 JDK 1.5 中增加了新的机制 ReentrantLock,该机制的诞生并 ...

  5. vue项目打包配置多个测试环境与生产环境,用npm命令打出不同的资源包。

    1.找到package.json文件,找到script节点.再新增一个新的脚本命令 test 2.修改prod.env.js配置文件,npm_lifecycle_event代表返回当前执行的脚本名称, ...

  6. Sqlalchemy 事件监听与初始化

    sqlalchemy不仅仅能自动创建数据库,更提供了其他更强大的功能,今天要介绍的就是sqlalchemy中的事件监听,并将其应用到数据库的初始化中. 需求:当插入设置password字段时,自动加密 ...

  7. Centos7 yum安装Python3.6环境,超简单

    原文链接:https://blog.51cto.com/wenguonideshou/2083301 配置好Python3.6和pip3安装EPEL和IUS软件源 yum install epel-r ...

  8. Linux时间同步服务chrony

    1.简介 Chrony是一个开源的自由软件,像CentOS 7或基于RHEL 7操作系统,已经是默认服务,默认配置文件在 /etc/chrony.conf .它能保持系统时间与时间服务器(NTP)同步 ...

  9. 关于H标签 DL DT DD标签的一个小故事

    看了一篇关于SEO论坛的论文,大概故事内容是:一个专业的销售公司,里面SEO  技术多多,可就是销售网站的SEO的情况极为恼火.这天,老板又招到了一个SEO,直接聘为SEO主管全权负责网站的SEO,并 ...

  10. MPI实现Jacobi

    一.Jacobi迭代 #include<stdio.h> #include<mpi.h> #include<stdlib.h> #define totalsize ...