前言
      在当今 工业4.0 新时代的推动下,不仅迎来了 工业互联网 的发展,还开启了 5G 时代的新次元。而伴随着带宽的提升,网络信息飞速发展,能源管控上与实时预警在工业互联网中也占着举足轻重的地位,而对于高炉炼铁的发展上来看,目前已完成国内260座高炉的数字化和智能化落地,并推动炼铁大数据平台在俄罗斯、越南、伊朗、印尼等“一带一路”国家钢铁企业中应用,充分体现了高炉智能化大屏产业应运而生。我们将使用 Hightopo(以下简称 HT )的 HT for Web 产品上的 web 组态跟大家介绍一下通过 2/3D 融合搭建的高炉炼铁厂可视化系统。
      HT 可以快速实现丰富的 2D 组态和 3D 组态效果,可以根据需求发挥自己的想象,玩转很多新奇的功能,并且通过优势互补的作用下,完善出一套完整的可视化系统解决方案。所以在可视化系统的实现上,3D 场景采用以 HT 轻量化 HTML5/WebGL 建模的方案,实现快速建模、运行时轻量化到甚至手机终端浏览器即可 3D 可视化运维的良好效果;而在对应的 2D 图纸上,使用特有的矢量,在各种比例下不失真,加上布局机制,解决了不同屏幕比例下的展示问题。
 
本文将从以下三个方面与大家分享高炉炼铁厂在大屏展示上的实现:
      1、页面搭建:介绍基础的 2D 图纸与 3D 场景融合的项目搭建;
      2、数据对接:进行面板数据的对接展示;
      3、动画实现:铁水罐车运输、传送带运送以及场景漫游的实现;
 
界面简介及效果预览
      在整个高炉炼铁厂可视化系统的 2D 面板上,呈现了昨日历史与今日实时的一些重要预警数据,在管控上能起到实时监控的作用,也能与历史数据进行对比,从而使生产与安全达到预期的预警效果;其次 3D 场景通过轻量化的模型呈现出一座高炉炼铁厂的基本运作流程以及铁水罐车运送钢铁的动画,加上环绕的漫游效果,起到全方位的实时监控状态的变化。
 
代码实现
一、页面搭建
        在内容实现上,采用了 HT 轻量化模型以及 web 组态,以 2/3D 结合的方式,通过的 json 反序列化得到 2D 图纸和 3D 场景的完整呈现。首先会通过创建 ht.graph.GraphView 和 ht.graph3d.Graph3dView 来呈现 2D 和 3D 的内容。 2D 视图组件和 3D 视图组件进行 deserialize() 反序列化对应的 url 寄存的 json 呈现出场景与图纸的内容,两者通过对数据模型 DataModel 里的子元素设置标签来进行数据绑定,实现功能上的展示。
// 三维拓扑视图
let g2d = new ht.graph.GraphView();
let g3dDm = g2d.dm();
// 三维拓扑视图
let g3d = new ht.graph3d.Graph3dView();
let g3dDm = g3d.dm();
// 2D 视图组件和 3D 视图组件进行反序列化
g2d.deserialize('displays/index.json');
g3d.deserialize('scenes/index.json');

在内容呈现上还需要将组件加入到 body 下,一般 2/3D 结合的项目上,都会使用 2D 组件加入到 3D 组件的根 div 下,然后 3D 组件再加入到 body下的方式实现面板与场景的加载。

// 将 3D 组件加入到 body 下
g3d.addToDOM();
// 将 2D 组件加入到 3D 组件的根 div 下,父子 DOM 事件会冒泡,这样不会影响 3D 场景的交互
g2d.addToDOM(g3d.getView());

同时,在交互与呈现上改变了一些实现方式。例如,修改了左右键的交互方式,设置左键点击旋转 3D 场景,右键点击为 pan 抓图的场景移动方式。其次,在点击 2D 有点到图元像素时,我们希望不触发 3D 的交互,例如在对 2D 面板表格中用滚轮滑动的时候,会触发 3D 场景的缩放,这里通过监听 moudedown、touchstart 和 wheel 三种交互来进行控制,对于 wheel 的监听方式,为了保证兼容性就通过封装一个 getWheelEventName() 的方法来得到事件名。

// 修改左右键交互方式
let mapInteractor = new ht.graph3d.MapInteractor(this.g3d);
g3d.setInteractors([mapInteractor]);
// 设置修改最大仰角为 PI / 2
mapInteractor.maxPhi = Math.PI / 2; // 避免 2D 与 3D 交互重叠
let div2d = g2d.getView();
const handler = e => {
if (g2d.getDataAt(e)) {
e.stopPropagation();
}
};
div2d.addEventListener('mousedown', handler);
div2d.addEventListener('touchstart', handler);
div2d.addEventListener(getWheelEventName(div2d), handler); // 在一个 HTMLElement 上,可能支持下面三个事件的一种或者两种,但实际回调只会回调一种事件,优先回调标准事件,触发标准事件后,不会触发兼容性事件
function getWheelEventName(element) {
if ('onwheel' in element) {
// 标准事件
return 'wheel';
} else if (document.onmousewheel !== undefined) {
// 通用旧版事件
return 'mousewheel';
} else {
// 旧版 Firefox 事件
return 'DOMMouseScroll';
}
}
二、数据对接
      在 2D 面板的呈现上,会有许多的图表数据信息,我们可以通过访问后台数据接口得到数据,然后在 2D 或者 3D 对应的组件上取得相应的数据模型 dataModel,通过对数据模型里设置唯一的标识 tag 的子节点进行对接数据就可以了。例如现在我们要对 2D 面板的数据进行绑定,我们只需要通过 2D 组件的 g2d 得到数据模型,通过 g2d.dm().getDataByTag(tag) 就可以得到设置有唯一标识的 tag 节点,来对接数据或者设置状态展示了。
      对于数据接口的获取,可以运用主流的 jQuery 框架下的 ajax、基于 promise 的 HTTP 库的 axios  通过轮询调用接口实时获取数据或者使用 HTML5 提供的一种在单个 TCP 连接上进行全双工通讯的协议 WebSocket,可以双向进行数据传输,在选择运用上可以匹配自己的实现需求,而本系统是采用通过 axios 调用接口获取实时数据。
// 昨日利用系数数据对接
axios.get('/yesterdayUse').then(res => {
setBindingDatasWithAnim(dm, res, undefined, v => v.toFixed(2));
});
// 昨日燃料比数据对接
axios.get('/yesterdayFuel').then(res => {
setBindingDatasWithAnim(dm, res, undefined, v => v.toFixed(2));
});
// 昨日入炉品位数据对接
axios.get('/yesterdayIn').then(res => {
setBindingDatasWithAnim(dm, res, undefined, v => v.toFixed(2));
});
// 昨日燃气利用率数据对接
axios.get('/yesterdayCoal').then(res => {
setBindingDatasWithAnim(dm, res, undefined, v => v.toFixed(2));
});
// 实时警报信息面板表格轮询载入数据进行滚动播放
this.addTableRow();
setInterval(() => {
this.addTableRow();
}, 5000);
 
      通过 axios 轮询调用接口,实时获取安全指数和实时数据信息(风量、风温和富氧量):
requestData() {
let dm = this.view.dm();
// 安全指数数据对接并载入圆环动画
axios.get('/levelData').then(res => {
setBindingDatasWithAnim(dm, res, 800, v => Math.round(v));
});
// 实时数据(风量、风温和富氧量)数据对接并载入进度条动画
axios.post('/nature', [
'windNumber', 'windTemp', 'oxygenNumber'
]).then(res => {
setBindingDatasWithAnim(dm, res, 800, v => parseFloat(v.toFixed(1)));
});
}

  

对接数据后,实现一些圆环或者进度条值的增减动画,其本质上是运用 HT 自带的动画函数 ht.Default.startAnim(),通过判断数据绑定的属性后,设定新值与旧值差额的范围动画,然后用户定义函数 easing 参数通过数学公式来控制动画的运动的快慢,例如匀速变化、先慢后快等效果。

这里通过动画函数封装了一个差值的动画效果,参数如下:

  • node:动画处理的节点;
  • name:数据绑定的名称;
  • value:数据绑定的值;
  • format:绑定数据值的格式规范;
  • accesstype:数据绑定的属性从属 ;
  • duration:动画时间;
setValueWithAnimation (node, name, value, format, accesstype = 's', duration = 300) {
let oldValue;
// 判断数据绑定为自定义属性 attr 后根据绑定名字取出旧值
if (accesstype === 'a') {
oldValue = node.a(name);
}
// 判断数据绑定为样式属性 style 后根据绑定名字取出旧值
else if (accesstype === 's') {
oldValue = node.s(name);
}
// 默认通过取值器 getter 得到数据绑定的值
else {
oldValue = node[ht.Default.getter(name)]();
}
// 设置新旧值的差额
let range = value - oldValue;
// 执行动画函数
ht.Default.startAnim({
duration: duration,
easing: function (t) { return 1 - (--t) * t * t * t; },
action: (v, t) => {
// 新值增长的动画范围
let newValue = oldValue + range * v;
// 判断有格式则制定数据格式
if (format) {
newValue = format(newValue);
}
// 判断数据绑定为自定义属性 attr 后设定新值
if (accesstype === 'a') {
node.a(name, newValue);
}
// 判断数据绑定为样式属性 style 后设定新值
else if (accesstype === 's') {
node.s(name, newValue);
}
// 默认通过存值器 setter 设置数据绑定的新值
else {
node[ht.Default.setter(name)]()(node, newValue);
}
}
});
}
      我们时常会在公开的预警场合或者宣传场合看见轮播滚动的数据信息,采用这种方法在公示的同时也不会遗漏掉任何一条数据信息,如果搭配上一些例如淡入淡出的过场效果,更会吸引关注的眼球。而对于实时警报信息的面板表格的实现,也是在添加新数据时,实现了一种过渡的 UI 交互上的沉浸感,主要还是运用了 HT 自带的动画函数 ht.Default.startAnim(),横向通过滚动 100 宽度并数据透明度慢慢浮现,纵向采用向下偏移一行表格行高 54 来添加新的警报信息。
addTableRow() {
// 获取表格节点
let table = this.right3;
// 通过 axios 的 promise 请求接口数据
axios.get('getEvent').then(res => {
// 获取表格节点滚动信息的数据绑定
let tableData = table.a('dataSource');
// 通过向 unshift() 方法可向滚动信息数组的开头添加一个或更多元素
tableData.unshift(res);
// 初始化表格的纵向偏移
table.a('ty', -54);
// 开启表格滚动动画
ht.Default.startAnim({
duration: 600,
// 动画执行函数 action
action: (v, t) => {
table.a({
// 通过添加数据后,横向滚动 100
'firstRowTx': 100 * (1 - v),
// 第一行行高出现的透明度渐变效果
'firstRowOpacity': v,
// 纵向偏移 54 的高度
'ty': (v - 1) * 54
});
}
});
});
}
三、动画实现
      在静态的场景以及面板下,很难直观地去体现一个 2/3D 嵌合的系统的优越性。动画却是赋予生命灵魂的所在,一个恰到好处的 UI 动画设计可以使面板的交互体验鲜活起来,而在 3D 场景中,通过一组简单形象的铁水罐车运输和传送带运送可以让人清晰地明白生产运输的流程,对于模型建筑的管控,利用好视角切入点,我们可以设置全方位的沉浸式漫游巡视。综上,通过轻量模型场景与矢量组件面板的优势叠加,可以呈现出一套灵活的高炉炼铁厂生产预警系统。
      在漫游巡视下,为了更全方位地体现场景,我们通过裁剪的方式来显示和隐藏两侧的面板数据,以下以隐藏面板的裁剪动画为例:
hidePanel() {
// 将左侧数据绑定裁剪的子元素存放进一个数组里
let leftStartClipIndexs = (() => {
let arr = [];
for (let i = 1; i <= 4; i++) arr.push(this['left' + i].s('clip.percentage'));
return arr;
})();
// 将右侧数据绑定裁剪的子元素存放进一个数组里
let rightStartClipIndexs = (() => {
let arr = [];
for (let i = 1; i <= 3; i++) arr.push(this['right' + i].s('clip.percentage'));
return arr;
})();
// 设置面板裁剪的延迟时间,使得视觉上更有层次感
let delayArrays = [400, 800, 1200, 1600];
// 动画执行函数
let action = (index) => {
ht.Default.startAnim({
duration: 700,
easing: Easing.swing,
action: (v, t) => {
this['left' + index].s('clip.percentage', leftStartClipIndexs[index - 1] + (0 - leftStartClipIndexs[index - 1]) * v);
this['right' + index].s('clip.percentage', rightStartClipIndexs[index - 1] + (0 - rightStartClipIndexs[index - 1]) * v);
}
});
};
// 通过判定延迟时间数组的长度,回调 action 动画的执行
for (let i = 0, l = delayArrays.length; i < l; i++) {
ht.Default.callLater(action, this, [i + 1], delayArrays.shift());
}
}

data.s('clip.percentage') 是 HT 节点自带的样式属性,其本质意义就是可以通过指定的方向进行对于整个矢量图标的裁剪:

一部电影可以通过各种镜头的切换下呈现不尽相同的叙事效果,日剧夕阳下热血跑的急速切换或者幽暗角落下惊恐的淡入淡出,都是一种叙事的处理手段。在 HT 设定的 3D 场景中同样地也存在着许许多多叙述的手法,最为基础的设定就是通过场景中的主观眼睛 eye 和场景中心 center 来搭配各种动画的实现,可以自己设定值的方法函数来修改,也可以通过 HT 自身封装的方法函数来处理,例如 flyTo() 和 moveCamera() 就是最为基础的相机动画,有兴趣的话可以了解一下,自己动手尝试搭配,肯定能最大地发挥 3D 场景的优势所在。

      漫游动画是为了更好地从不同的视角去巡视场景,只要通过设置几组眼睛视角,运用 HT 的 moveCamera() 相机视角移动的动画,依次去对应眼睛的视角就可以自动地切换不同视角下场景的效果。
// 默认设置的眼睛视角数组
const ROAM_EYES = [
[1683.6555274005063, 939.9999999999993, 742.6554147474625],
[1717.1004359371925, 512.9256996098727, -1223.5575465999652],
[-181.41773461002046, 245.58303266170844, -2043.6755074222654],
[-1695.7113902533574, 790.0214102589537, -877.645744191523],
[-1848.1700283399357, 1105.522705042774, 1054.1519814237804],
[-108, 940, 1837]
];
// 开启相机移动漫游动画
playRoam() {
// 设置场景眼睛视角
let eye = ROAM_EYES[this.roamIndex];
// 开启相机视角移动动画 moveCamera
this._roamAnim = this.view.moveCamera(eye, [0, 0, 0], {
duration: this.roamIndex ? 3000 : 4000,
easing: Easing.easeOut,
finishFunc: () => {
this.roamIndex ++;
let nextEye = ROAM_EYES[this.roamIndex];
// 判断是否有下一组眼睛视角,有的话继续执行相机视角移动动画,反之则重置漫游动画
if (nextEye) {
this.playRoam();
}
else {
// 事件派发执行显示面板动画
event.fire(EVENT_SHOW_PANEL);
this.resetRoam();
}
}
});
}

如果说场景视角漫游是一种大局整体观的体现,那么铁水罐车装载与运输以及传送带的运送则是一个高炉炼铁流程的拼图。通过一系列动画流程的表达,你会很清晰地发现,特定的 3D 场景下的讲解说明具有完整的故事串联性。

以下是铁水罐车装载与运输的动画流程:

在 3D 场景中是用 x, y, z 来分别表示三个轴,通过不断修改节点的 3D 坐标就可以实现位移效果 car.setPosition3d(x, y, z),而对于铁水罐车上的装载标签则使用吸附的功能,使其吸附在铁水罐车上就能跟着一起行驶移动,然后在指定的空间坐标位置上通过 car.s('3d.visible', true | false) 来控制铁水罐车的出现与隐藏的效果。

而关于传送带上煤块、铁矿的传输和管道气体流通的指示,通过使用 UV 纹理贴图的偏移来实现会方便很多,先来看看效果上的呈现:

  

对于三维模型,有两个重要的坐标系统,就是顶点的位置坐标(X、Y、Z)以及 UV 坐标。形象地说,UV 就是贴图影射到模型表面的依据,U 和 V 分别是图片在显示器水平、垂直方向上的坐标,取值一般都是0~1。而传送带以及管道的指示就是用这种方法实现的,HT 的模型节点自带 uv 值的样式属性,我们只需要不断地控制其偏移变化,就能实现传输的效果:

// 设置初始偏移值
let offset1 = 0, trackOffset = 0;
// 一直调用设置偏移值
setInterval(() => {
flows.each(node => {
node.s({
'top.uv.offset': [-offset1, 0],
'front.uv.offset': [-offset1, 0],
});
});
track.s('shape3d.uv.offset', [0, -trackOffset]);
// 偏移值增加
offset1 += 0.1;
trackOffset += 0.03;
}, 100);
总结
      数字化 和 智能化 大屏管控是 工业互联网 的发展趋势,在很大程度上解放了人力和劳力,在信息飞速传讯的时代,大数据可视化和智能管控的结合,会演绎出许多惊奇的效果碰撞。对实时数据监管下,预警信息也相当重要,保障生产有序进行的同时,我们也要关注安全问题,所以在大屏上呈现的许多内容,都极其具有行业跟上工业互联网的步伐代表性。
      2019 我们也更新了数百个工业互联网 2D/3D 可视化案例集,在这里你能发现许多新奇的实例,也能发掘出不一样的工业互联网:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA
      同时,你也可以查看更多案例及效果:https://www.hightopo.com/demos/index.html

基于 HTML5 WebGL 的高炉炼铁厂可视化系统的更多相关文章

  1. 基于 HTML5 WebGL 的地铁站 3D 可视化系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  2. 基于 HTML5 + WebGL 的地铁 3D 可视化系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  3. 基于 HTML5 WebGL 的智慧楼宇可视化系统

    前言 可视化的智慧楼宇在 21 世纪是有急迫需求的,中国被世界称为"基建狂魔",全球高层建筑数量位居首位,所以对于楼宇的监控是必不可少.智慧楼宇可视化系统更多突出的是管理方面的功能 ...

  4. 基于 HTML5 WebGL 的发动机 3D 可视化系统

    前言     工业机械产品大多体积庞大.运输成本高,在参加行业展会或向海外客户销售时,如果没有实物展示,仅凭静态.简单的图片说明书介绍,无法让客户全面了解产品,不仅工作人员制作麻烦,客户看得也费力.如 ...

  5. 基于 HTML5 WebGL 的 水泥工厂可视化系统

    前言 如今的制造行业,基于数据进行生产策略制定与管理已经成为一种趋势,特别是 工业4.0 的浪潮下,数据战略已经成为很多制造企业的优先战略,而数据可视化以更直观的方式,帮助指导决策,成为数据分析传递信 ...

  6. 基于 HTML5 + WebGL 的无人机 3D 可视化系统

    前言 近年来,无人机的发展越发迅速,既可民用于航拍,又可军用于侦察,涉及行业广泛,也被称为“会飞的照相机”.但作为军事使用,无人机的各项性能要求更加严格.重要.本系统则是通过 Hightopo 的   ...

  7. 基于 HTML5 WebGL 的挖掘机 3D 可视化应用

    前言 在工业互联网以及物联网的影响下,人们对于机械的管理,机械的可视化,机械的操作可视化提出了更高的要求.如何在一个系统中完整的显示机械的运行情况,机械的运行轨迹,或者机械的机械动作显得尤为的重要,因 ...

  8. 基于 HTML5 WebGL 的加油站 3D 可视化监控

    前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ...

  9. 基于 HTML5 + Canvas 实现的 PID 可视化系统

    前言 随着工业物联网和互联网技术的普及和发展,人工填料的方式已经逐渐被机械设备取代.工业厂商减小误操作.提升设备安全以及追求高效率等制造特点对设备的要求愈加高标准.严要求.同时机械生产以后还需遵从整个 ...

随机推荐

  1. GitHub 热点速览 Vol.13:近 40k star 计算机论文项目再霸 GitHub Trending 榜

    作者:HelloGitHub-小鱼干 摘要:"潮流是个轮回",这句话用来形容上周的 GitHub Trending 最贴切不过.无论是已经获得近 40k 的高星项目 Papers ...

  2. [STL] Codeforces 69E Subsegments

    Subsegments time limit per test 1 second memory limit per test 256 megabytes input standard input ou ...

  3. Nginx做负载均衡的几种轮询策略

    集群环境为了解决单点无法支撑高并发的情况,集群采用多台服务器提供服务,一般在集群中使用nginx 将来自客户端的请求转发给服务器端 nginx负载均衡可用提高网站的吞吐量,缓解单台服务器的压力. 一. ...

  4. 重磅!!!一文总结Pytorch的8张思维导图!

    本文以思维导图的形式,为大家介绍了深度学习的核心内容,主要包括:深度学习与Pytorch简介.词向量.用pytorch处理常见的NLP和CV任务.图片风格迁移和GAN.Seq2Seq与Attentio ...

  5. 线程安全,syncronized 用法

    1,为什么有线程安全问题? 当多个线程同时共享同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题.但是做读操作是不会发生数据冲突问题. public class Tra ...

  6. pthread_rwlock_t

    一.读写锁 读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作. 读操作可以共享,写操作是排他的,可以有多个在读(与 CP ...

  7. 一篇漫画故事带你理解透HTTPS(上)

    2020年蝙蝠纪元,二毛一如往常的呆在家中,不敢外出去浪. 为排解心中之闷,二毛抽了一口老烟,熟练的打开了全球最大的同性交友网站,准备假装了解下最近流行的项目... 只听啪的一声回车键,哪知浏览器蹦出 ...

  8. 用c#判断回文数和降序数

    题目:编一个程序,输入一个正整数,判定它是否为回文数和降序数.当输入的数为0时,则退出程序,否则继续循环执行程序. 所谓“降序数”是指一个自然数的低位数字不大于高位数字的数.例如: 64, 55, 3 ...

  9. opentsdb探索之路——部分设计与实现

    opentsdb 概览(overview) opentsdb 存储细节(Writing) rowkey的设计 rowkey的具体实现 压缩(compaction) 追加模式(appends) open ...

  10. K-Folds cross-validator-K折交叉验证实现

    源码: import numpy as np from sklearn.model_selection import KFold X = np.array([[, ], [, ], [, ], [, ...