基于 HTML5 WebGL 的智慧楼宇三维可视化监控
<h2 id="前言">前言</h2><p>可视化的<strong>智慧楼宇</strong>在 21 世纪是有急迫需求的,中国被世界称为<strong>“基建狂魔”</strong>,全球高层建筑数量位居首位,所以对于楼宇的监控是必不可少。<strong>智慧楼宇</strong>可视化系统更多突出的是管理方面的功能,即如何的全面实现优化控制和管理,节能降耗、高效、舒适、环境安全这样一个目的,可以这样说,判断一个建筑物是否具有智能建筑特点,要看它是否具有 <strong>IBMS</strong> 的系统集成,这是很重要的判定条件。<strong>IBMS</strong> 系统的建立必不可少需要硬件采集信息到后台,随着<strong>工业互联网,物联网</strong>概念的推广应用,让硬件与软件的结合变得系统化,过程化,使得智慧楼宇可视化成为可能。</p>
<p>本文所概述的智慧楼宇是基于一栋真实建筑可视化简化建模之后得到的效果,楼层的轮廓在系统中抽象成线条,整体拼凑起来就形成了一栋简化版的楼宇,当然其中的楼层地板也是抽象成简单的立方体包裹在对应楼层线框的里面。对应的电梯,闸机,扶梯等物体也简化抽象成六面体,线条。抽象的物体全部采用 <strong>HT</strong> 的 <strong>API</strong> 即可创建完成,所以抽象之后具有复用性,同样的方法可以采用到其它所有楼宇。</p>
<h2 id="可视化监控功能">可视化监控功能</h2>
<p>预览地址:基于 HTML5 WebGL 的智慧楼宇三维可视化监控 https://hightopo.com/demo/IBMS/</p>
<ul>
<li><strong>工单监测</strong> -- 统计楼宇收到的工单信息</li>
<li><strong>客流监测</strong> -- 显示当日进出楼宇的人流量信息</li>
<li><strong>消防报警</strong> -- 统计当日楼宇的消防报警信息</li>
<li><strong>重点区域监控</strong> -- 显示重点区域的摄像头信息</li>
<li><strong>停车场车位统计</strong> -- 统计停车场车位信息</li>
<li><strong>楼宇控制</strong> -- 可单独控制不同类型楼层的闸机,电梯,扶梯,警报和客流的显示与隐藏,监控此时电梯,扶梯等物体的运行情况</li>
<li><strong>告警信息</strong> -- 实时滚动显示当前楼宇的告警信息</li>
</ul>
<h2 id="可视化监控预览">可视化监控预览</h2>
<h3 id="楼宇效果">楼宇效果</h3>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235538643-1773063240.gif"></p>
<h3 id="楼层效果">楼层效果</h3>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235552313-1538550847.gif"></p>
<h3 id="楼层控制效果">楼层控制效果</h3>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235604701-2034405981.gif"></p>
<h2 id="可视化实现">可视化实现</h2>
<h3 id="ui-以及模型实现">UI 以及模型实现</h3>
<h4 id="2d-可视化">2D 可视化</h4>
<p>2d 部分的 <strong>UI</strong> 都是采用 <strong>HT</strong> 暴漏的 <strong>API</strong> 进行描绘实现,UI 部分都绘制在一个 <strong>canvas</strong> 节点上,并且全都为<strong>矢量</strong>,所以放大不会失真,两侧的实时数据通过 <strong>数据绑定</strong> 实现实时刷新,<strong>HT</strong> 在处理实时刷新的时候只会刷新当前需要刷新的区域,所以不会出现例如修改一个页面上的某个文字而导致整个 canvas 重绘。</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235616887-49738934.jpg"></p>
<p>上图所示的摄像头监控画面则是通过将 <strong>video</strong> 标签叠加在 2d 对应区域的 UI 上面,HT 对于第三方需要嵌入的 dom 节点可以通过叠加 dom 的方式在对应区域进行叠加来实现。目前摄像头采用的第三方插件多为 <strong>video.js</strong> 或者 <strong>Aliplayer</strong> 来实现嵌入,通过将对应的 <strong>iframe</strong> 嵌入到指定区域来实现,以下为嵌入的 <strong>rtmp 实时视频流</strong>的图标参考代码:</p>
<pre><code class="language-javascript">if (!cache.view) {
// 创建 iframe 节点 并且设置 style 属性
var iframe = cache.htmlView = document.createElement('iframe');
iframe.setAttribute('style', 'margin: 0; padding: 0; border: 0;');
iframe.setAttribute('id', 'cameraIframe');
iframe.src = './rtmp/rtmp.html';
cache.view = iframe;
// 调用 graphView 上的 layoutHTML 布局方法,会自动布局到上面所说的对应区域
cache.htmlView.layoutHTML = function() {
gv.layoutHTML(data, iframe, true)
}
}
// 读取 rtmp 视频流地址
var cameraURL = data.a('cameraURL');
if (cache.cameraURL !== cameraURL) {
cache.cameraURL = cameraURL;
// 获取 iframe 页面暴漏的 playVideoByUrl 方法,来执行对应的播放操作
if (cache.view.contentWindow) {
if (cache.view.contentWindow.playVideoByUrl) {
cache.view.contentWindow.playVideoByUrl(cameraURL, cache);
}
} else {
// 起定时器的目的是为了防止 contentWindow 在第一次还没有加载完成导致 contentWindow 为 null
if (window.loadFrameInterval) {
clearInterval(window.loadFrameInterval);
window.loadFrameInterval = null;
}
window.loadFrameInterval = setInterval(function() {
// 此时 contentWindow 已经加载完成,所以执行 playVideoByUrl 操作
if (cache.view.contentWindow.playVideoByUrl) {
cache.view.contentWindow.playVideoByUrl(cameraURL, cache);
clearInterval(window.loadFrameInterval);
window.loadFrameInterval = null;
}
},
100);
}
}
</code></pre>
<p>以下为 iframe 页面中的部分部分代码:</p>
<pre><code class="language-javascript"> // 记录此时的视频播放器对象
var player = null;
// 记录此时的图标节点 cache 对象
var nodeCache = null;
// 创建视频播放器 dom 节点
function createVideoContainer() {
var div = document.createElement('div');
div.classList.add('prism-player');
div.id = 'player-con';
document.body.appendChild(div);
}
// 通过视频流地址初始化 Aliplayer 播放器
function playVideoByUrl(cameraURL, cache) {
// 如果已经初始化过 player 则需要先销毁
pauseVideo();
// 创建视频播放器 dom 节点
createVideoContainer();
// 初始化 player 播放器
player = new Aliplayer({
"id": "player-con",
"source": cameraURL,
"width": "100%",
"height": "100%",
"autoplay": true,
"isLive": true,
"rePlay": false,
"playsinline": true,
"preload": true,
"controlBarVisibility": "hover",
"useFlashPrism": true,
});
nodeCache = cache;
}
// 销毁 player 对象
function pauseVideo() {
if (player) {
player.dispose();
player = null;
document.body.removeChild(document.getElementById('player-con'));
if (nodeCache) {
nodeCache.cameraURL = null;
nodeCache = null;
}
}
}
</code></pre>
<h4 id="3d-可视化">3D 可视化</h4>
<p>3d 部分系统采用<strong>极简的方式</strong>来表示楼宇,用到了 HT 中的 <strong>ht.Shape</strong> 节点类型,可以参考 <strong>形状手册</strong> ,该节点类型可以指定 <strong>points</strong> 以及 <strong>segments</strong>,所以可以用来动态生成<strong>管道</strong>或者<strong>墙面</strong>,通过简化之后我们的楼宇就可以通过管道和墙面两种类型来构造,并且构造的时候如果有许多层结构相同,或者有规律的从下到上,或从上到下的形状,则只需要构造第一层以及最后一层之后其它所有楼层都可以通过计算的方式动态生成,以下为示意图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235636125-1425141634.jpg"></p>
<p>通过上图可以知道中间所有的楼层的对应点的信息我们都可以通过计算获取,具体的实现的方式在下一节会通过伪代码阐述。</p>
<p>3d 部分还有个电梯的简单上下移动,该部分暂时通过模拟的方式进行移动,没有具体对接真实的电梯运行数据,其中所有的电梯都是对应的电梯线的子节点,所以电梯线部分存储了自己所能到达的楼层,则对应电梯线上的电梯也可以知道自己在垂直空间的运动范围,之后可以通过 <strong>requestAnimationFrame</strong> 来实现动画效果,如下伪代码:</p>
<pre><code class="language-javascript"> // 每帧移动的距离
var moveStep = 5;
function frameFunc() {
// 电梯的移动方向 1 为向上,-1 为向下
var direction = moveNode.a('direction') || 1;
// 电梯下一帧的位置
var nextPosY = moveNode.getElevation() + moveStep * direction;
// 如果下一帧位置大于最高层的位置则改变 direction 的方向
if (nextPosY >= maxElevation) {
direction = -1;
}
// 如果下一帧位置小于最低层的位置则改变 direction 的方向
if (nextPosY <= minElevation) {
direction = 1;
}
moveNode.a('direction', direction);
moveNode.setElevation(nextPosY);
window.requestAnimationFrame(frameFunc);
}
window.requestAnimationFrame(frameFunc);
</code></pre>
<h3 id="楼宇可视化实现">楼宇可视化实现</h3>
<p>为了方便讲解,我们以如下图错开的简单楼层来讲解:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235650151-8601965.jpg"></p>
<p>上图第一层和最后一层都由 6 个点组成,如下图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235702455-1287267501.jpg"></p>
<p>从上图可以看出第一层和最后一层都是由六个点组成,所以第一层和最后一层中间的所有层可以根据这两层点的数据进行计算得出,例如上面图中如果第一层和最后一层中间还有两层的话,则可以计算出对应每层的平面坐标,垂直空间的坐标可以根据第一层和最后一层的垂直距离进行平分得到,描述图如下:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235713763-1732321671.jpg"></p>
<p>若中间有两层则生成的效果图如下:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235725407-625354929.jpg"></p>
<p>通过以上几个可视化的步骤我们可以了解到生成一栋简单的楼层所需要经历的步骤,如下为步骤图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235745917-477292687.png"></p>
<p>通过如上操作我们就可以绘制出某些楼层,如果建筑由不同的楼层结构构成,则同理可以按照如上顺序分开分别绘制,最后拼凑起来。</p>
<p>以下为构造楼宇可视化部分伪代码:</p>
<pre><code class="language-javascript"> // maxFloorIndex 为最高楼层索引号
for (let i = 0; i < maxFloorIndex; i++) {
let percent = i / (maxFloorIndex - 1), // 当前楼层在首层和末层之间的占比
curFloorPoints = []; // 记录当前层的所有点信息
// floorPointLength 记录每一层点的数量
for (let j = 0; j < floorPointLength; j++) {
let v3 = new Vector3();
// firstFloorVector3 指的第一层点的集合
// lastFloorVector3 最后一层点的集合
// lerpVectors 根据 percent 百分比计算出中间的点
v3.lerpVectors(firstFloorVector3, lastFloorVector3, percent);
curFloorPoints.push(v3);
}
// 只需要 xz 平面的点来构成楼层
let points = [],
elevation = curFloorPoints.y;
curFloorPoints.forEach((point) = >{
points.push({
x: point.x,
y: point.z
});
});
// 构造楼层边框的可视化节点
let wireNode = getWire(points, elevation, floorNum);
dm3d.add(wireNode);
// 构造楼层地板的可视化节点
let floorNode = getFloor(points, elevation, floorNum);
dm3d.add(floorNode);
}
</code></pre>
<h2 id="总结">总结</h2>
<p>传统的 <strong>智慧楼宇/楼宇自动化/楼宇安防/智慧园区</strong> 常会采用 <strong>BIM(建筑信息模型 Building information modeling)</strong> 软件,如 <strong>Autodesk</strong> 的 <strong>Revit</strong> 或 <strong>Bentley</strong> 这类建筑和工程软件,但这些 <strong>BIM</strong> 建模模型的数据往往过于庞大臃肿,绝大部分细节信息对楼宇自控意义不大,反而影响拖累了行业 <strong>Web SCADA</strong> 或 <strong>Web</strong> 组态监控的趋势,所以我们采用以 <strong>Hightopo</strong> 的 <strong>HT for Web</strong> 产品轻量化 <strong>HTML5/WebGL</strong> 建模的方案,实现快速建模、运行时轻量化到甚至手机终端浏览器即可 3D 可视化运维的良好效果。</p>
<p>以下为移动端的程序运行截图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/202002/1496396-20200217235801453-1607553735.jpg"></p><br><br>
来源:https://www.cnblogs.com/htdaydayup/p/12324466.html
頁:
[1]