全视传媒 發表於 2020-4-12 23:50:00

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

<h2>前言</h2>
<p>&nbsp;&nbsp;&nbsp; 如今的制造行业,基于数据进行生产策略制定与管理已经成为一种趋势,特别是&nbsp;<strong>工业4.0</strong>&nbsp;的浪潮下,数据战略已经成为很多制造企业的优先战略,而数据可视化以更直观的方式,帮助指导决策,成为数据分析传递信息的重要工具。通过数据可视化系统助力实现数据驱动的工业世界,为&nbsp;<strong>工业4.0</strong>&nbsp;提供更加灵活、敏捷、高效、个性化的数据支撑。今天就给大家带来一个采用&nbsp;<strong>Hightopo</strong>&nbsp;的&nbsp;<strong>HT for Web</strong>&nbsp;产品实现了一个水泥工厂可视化系统。</p>
<h3 align="left">系统预览</h3>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200411173958932-2101980099.png"></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200411174528357-302781882.png"></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200411174558059-1748488108.png"></p>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200410233603732-1716353546.png"></p>
<p>本案例共有七个子系统:</p>
<ul>
<li><strong>数据概况</strong>&nbsp;-- 展示全厂年月时间单位的各项数据概况</li>
<li><strong>窑系统运行</strong>&nbsp;-- 用窑工艺流程动画展示窑系统实时运行状态</li>
<li><strong>系统运行情况</strong>&nbsp;-- 用动画流程图展示整个系统运行情况</li>
<li><strong>生料质量控制</strong>&nbsp;-- 用图表和流程图展示各种生料的配比情况</li>
<li><strong>熟料质量控制</strong>&nbsp;-- 用动画流程图展示各种熟料的配比情况</li>
<li><strong>煤粉质量控制</strong>&nbsp;-- 用图表和流程图对煤粉质量进行监控</li>
<li><strong>智能物流</strong>&nbsp;-- 通过 3D 场景实时监控进出厂车辆,和各项原料运输情</li>
</ul>
<h3>子系统页面切换</h3>
<p><img src="https://img2018.cnblogs.com/i-beta/1496396/202003/1496396-20200302014540315-1796835532.png"></p>
<p>&nbsp; &nbsp; &nbsp; 切换不同子系统时,左侧菜单和顶部标题是不需要切换的,所以我们把需要切换的内容部分别放在不同的 Block 中,Block&nbsp;类型,本身不绘制任何内容,用于作为其它节点的父节点,可以与子节点同步大小,当它隐藏或显示时,所有子节点都会跟着隐藏或显示。所以当我们切换子系统时只需要控制对应的 Block 显示隐藏,而不需要去加载切换多张图纸。</p>
<h3>流向地图</h3>
<p>&nbsp;<img src="https://img2018.cnblogs.com/i-beta/1496396/202003/1496396-20200302031059781-1478316475.gif"></p>
<p>&nbsp; &nbsp; 在数据概况页面中,流向地图展示年度水泥向各地区的销售情况,这里我们用&nbsp;Shape&nbsp;类型绘制线段来连接源地和汇地,用流动效果表示销售关系。流动效果只需引入 HT 的&nbsp;ht-flow.js&nbsp;插件,即可通过简单的属性设置实现,代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 获取线段的父节点
<span data-mce-="">this.flowParent = dm.getDataByTag('saleFlowParent'<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> 遍历得到所有线段
<span data-mce-="">this.flowParent.eachChild(child =&gt;<span data-mce-=""> {
    <span data-mce-="">//<span data-mce-=""> 开启流动,设置流动样式
<span data-mce-="">    child.s({
      <span data-mce-="">//<span data-mce-=""> 开启流动
      'flow': <span data-mce-="">true<span data-mce-="">,
      <span data-mce-="">//<span data-mce-=""> 设置流动组中最大元素的尺寸
      'flow.element.max': 4<span data-mce-="">,
      <span data-mce-="">//<span data-mce-=""> 设置流动组中的元素的渐变阴影中心颜色
      'flow.element.shadow.begincolor': '#49e5fe'<span data-mce-="">,
      <span data-mce-="">//<span data-mce-=""> 设置流动组中的最大元素的渐变阴影尺寸
      'flow.element.shadow.max': 16<span data-mce-="">,
      <span data-mce-="">//<span data-mce-=""> 设置流动组中的元素的渐变阴影边缘颜色
      'flow.element.shadow.endcolor': 'rgba(73, 229, 254, 0)'<span data-mce-="">,
    });
});</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<h3>&nbsp;窑系统动画</h3>
<p><img src="https://img2018.cnblogs.com/i-beta/1496396/202003/1496396-20200302040528963-1160319592.gif"></p>
<p>&nbsp; &nbsp; 在窑系统运行页面中,窑工艺流程动画很直观的展示了窑系统实时运行状态。画面中火焰、水和熟料在传送带上运输的动画效果,为了在性能较差的设备上也能流畅运行,我通过切换不同矢量图形的方式实现。这里用到了 HT 矢量中状态机制,先绘制多个不同的矢量组件,每个组件都可以定义状态来决定自己在哪个状态下显示,只要通过 data.s('state') 修改节点状态就可以实现如下效果:</p>
<p><img src="https://img2018.cnblogs.com/i-beta/1496396/202003/1496396-20200302045120015-2135933274.gif"></p>
<p>&nbsp; &nbsp; 使用一个定时器,不断地改变节点的状态值,相关代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">this._stateTimer = setInterval(() =&gt;<span data-mce-=""> {
    stateNodes.forEach(node =&gt;<span data-mce-=""> {
      <span data-mce-="">this<span data-mce-="">.stateAnimation(node);
    });
}, 180<span data-mce-="">);
<span data-mce-="">//<span data-mce-="">切换状态
<span data-mce-="">stateAnimation(node) {
    let stateIndex = (node.a('stateIndex') || 0) %<span data-mce-=""> stateEnum.length,
      state =<span data-mce-=""> stateEnum.value;
    node.s('state'<span data-mce-="">, state);
    node.a('stateIndex', ++<span data-mce-="">stateIndex);
}</span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>&nbsp;</p>
<h3>流程图动画</h3>
<p><img src="https://img2018.cnblogs.com/i-beta/1496396/202003/1496396-20200302071742417-1090212036.gif"></p>
<p>&nbsp; &nbsp; 流程图中流动线同样是使用&nbsp;ht-flow.js&nbsp;插件实现。由于图纸上的线段比较多,我把不同的线段分组放在不同的 Block 下,遍历其子节点设置样式,代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-="">设置流动属性
<span data-mce-="">setNodeFlow (data, value) {
    <span data-mce-="">if (data <span data-mce-="">instanceof<span data-mce-=""> ht.Block) {
      data.eachChild(child =&gt;<span data-mce-=""> {
            <span data-mce-="">this<span data-mce-="">.setNodeFlow(child, value);
      });
    }
    <span data-mce-="">else <span data-mce-="">if (data.getDisplayName() === 'line'<span data-mce-="">){
      data.s({
            'flow'<span data-mce-="">: value,
            'flow.element.max': 4<span data-mce-="">,
            'flow.element.count': 1<span data-mce-="">,
            'flow.count': 5<span data-mce-="">,
            'flow.step': 10<span data-mce-="">
      });
    }
}
<span data-mce-="">//<span data-mce-="">设置虚线流动属性
<span data-mce-="">setNodeDashFlow(data, value) {
    <span data-mce-="">if (data <span data-mce-="">instanceof<span data-mce-=""> ht.Block) {
      data.eachChild(child =&gt;<span data-mce-=""> {
            <span data-mce-="">this<span data-mce-="">.setNodeDashFlow(child, value);
      });
    }
    <span data-mce-="">else <span data-mce-="">if (data.getDisplayName() === 'border'<span data-mce-="">){
      <span data-mce-="">if<span data-mce-=""> (value) {
            data.s({
                'shape.dash.flow': <span data-mce-="">true<span data-mce-="">,
                'shape.dash': <span data-mce-="">true<span data-mce-="">
            });
      }
      <span data-mce-="">else<span data-mce-=""> {
            data.s({
                'shape.dash.flow': <span data-mce-="">false<span data-mce-="">,
                'shape.dash': <span data-mce-="">false<span data-mce-="">
            });
      }
    }
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>&nbsp; &nbsp; 为了使动画看起来更顺畅,我给一些节点加上透明度动画,设置节点透明度的代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-="">设置节点透明度
setNodeOpacity (data, value = 0.5<span data-mce-="">) {
    <span data-mce-="">if (data <span data-mce-="">instanceof<span data-mce-=""> ht.Block) {
      data.eachChild(child =&gt;<span data-mce-=""> {
            <span data-mce-="">this<span data-mce-="">.setNodeOpacity(child, value);
      });
    }
    <span data-mce-="">else<span data-mce-=""> {
      data.s('opacity'<span data-mce-="">, value);
    }
}</span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>&nbsp; &nbsp; 接下来只需要依次执行动画:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-="">开始流程图动画
<span data-mce-="">start() {
    let {eo, eoInput, eoLine1, eoKind, eoCalu} = <span data-mce-="">this<span data-mce-="">;
    <span data-mce-="">//<span data-mce-="">工况输入透明度动画
    <span data-mce-="">this.gv.enableFlow(30<span data-mce-="">);
    <span data-mce-="">this<span data-mce-="">.setNodeOpacity(eo);
    <span data-mce-="">this.setNodeFlow(eo, <span data-mce-="">false<span data-mce-="">);
    (<span data-mce-="">new Promise((resolve, reject) =&gt;<span data-mce-=""> {
      <span data-mce-="">this.animtion =<span data-mce-=""> startAnim({
            frames: 16<span data-mce-="">,
            interval: 5<span data-mce-="">,
            finishFunc: () =&gt;<span data-mce-=""> {resolve()},
            action: (v, t) =&gt;<span data-mce-=""> {
                <span data-mce-="">this.setNodeOpacity(eoInput, 0.5 + 0.5 *<span data-mce-=""> v);
            }
      });
    })).then(() =&gt;<span data-mce-=""> {
      <span data-mce-="">//<span data-mce-="">连线连线透明动画,流动
      <span data-mce-="">return <span data-mce-="">new Promise((resolve, reject) =&gt;<span data-mce-=""> {
            <span data-mce-="">this.animtion =<span data-mce-=""> startAnim({
                frames: 12<span data-mce-="">,
                interval: 10<span data-mce-="">,
                finishFunc: () =&gt;<span data-mce-=""> {
                  <span data-mce-="">this.setNodeFlow(eoLine1, <span data-mce-="">true<span data-mce-="">);
                  <span data-mce-="">this.timer = setTimeout(() =&gt; {resolve()}, 1500<span data-mce-="">);
                },
                action: (v, t) =&gt;<span data-mce-=""> {
                  <span data-mce-="">this.setNodeOpacity(eoLine1, 0.5 + 0.5 *<span data-mce-=""> v);
                }
            });
      })
    }).then(() =&gt;<span data-mce-=""> {
      <span data-mce-="">//<span data-mce-="">软计算透明动画
      <span data-mce-="">return <span data-mce-="">new Promise(resolve =&gt;<span data-mce-=""> {
            <span data-mce-="">this.animtion =<span data-mce-=""> startAnim({
                frames: 16<span data-mce-="">,
                interval: 5<span data-mce-="">,
                finishFunc: () =&gt;<span data-mce-=""> {resolve()},
                action: (v, t) =&gt;<span data-mce-=""> {
                  <span data-mce-="">this.setNodeOpacity(eoKind, 0.5 + 0.5 *<span data-mce-=""> v);
                  <span data-mce-="">this.setNodeOpacity(eoCalu, 0.5 + 0.5 *<span data-mce-=""> v);
                }
            });
      });
    }).then(() =&gt;<span data-mce-=""> {
      <span data-mce-="">//<span data-mce-="">软计算透明虚线流动
      <span data-mce-="">return <span data-mce-="">new Promise(resolve =&gt;<span data-mce-=""> {
            <span data-mce-="">this.setNodeDashFlow(eoKind, <span data-mce-="">true<span data-mce-="">);
            <span data-mce-="">this.setNodeDashFlow(eoCalu, <span data-mce-="">true<span data-mce-="">);
            <span data-mce-="">this.timer = setTimeout(() =&gt;<span data-mce-=""> {
                <span data-mce-="">this.setNodeDashFlow(eoKind, <span data-mce-="">false<span data-mce-="">);
                <span data-mce-="">this.setNodeDashFlow(eoCalu, <span data-mce-="">false<span data-mce-="">);
                resolve();
            }, 3000<span data-mce-="">);
      });
    }).then(() =&gt;<span data-mce-=""> {
      ......
    })
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>&nbsp;</p>
<h3>智能物流</h3>
<p>&nbsp; &nbsp; 前面六个子系统均为 2D 界面,而智能物流页面则是嵌入了一个 3D 场景。实现方式是通过定义 HT 矢量 JSON 的&nbsp;renderHTML&nbsp;函数属性,可实现在 GraphView 拓扑图上,嵌入任意第三方 HTML DOM 元素。不过这里也要注意一点,HT 的图纸是 Canvas 实现的,renderHTML 的 DOM 元素一定在 Canvas 之上,使用 renderHTML 的 DOM 与常规 Canvas 上绘制的图元不可能有层级控制可能性。下面展示一下 renderHTML 函数属性里的代码:</p>
<div class="cnblogs_code">
<pre>renderHTML : <span data-mce-="">function<span data-mce-=""> (data, gv, cache) {
    <span data-mce-="">//<span data-mce-=""> 避免重复创建g3d
    <span data-mce-="">if (!<span data-mce-="">cache.g3d) {
      <span data-mce-="">//<span data-mce-=""> 创建 3D 视图组件
      <span data-mce-="">var g3d = cache.g3d = <span data-mce-="">new<span data-mce-=""> ht.graph3d.Graph3dView();
      <span data-mce-="">//<span data-mce-=""> 布局函数,根据图元的位置信息摆放HTML元素
      g3d.layoutHTML = <span data-mce-="">function<span data-mce-=""> () {
            gv.layoutHTML(data, g3d, <span data-mce-="">true<span data-mce-="">);
      };
      <span data-mce-="">//<span data-mce-=""> 阻止事件冒泡
      g3d.getView().addEventListener('mousedown', <span data-mce-="">function<span data-mce-=""> (event) {
            event.stopPropagation();
      });
      g3d.getView().addEventListener('touchstart', <span data-mce-="">function<span data-mce-=""> (event) {
            event.stopPropagation();
      });
    }
    <span data-mce-="">//<span data-mce-=""> 获取图元自定义属性sceneURL的值
    <span data-mce-="">var sceneURL = data.a('sceneURL'<span data-mce-="">);
    <span data-mce-="">//<span data-mce-=""> 获取图元自定义属性onPostDeserialize的值
    <span data-mce-="">var onPostDeserialize = data.a('onPostDeserialize'<span data-mce-="">);
    <span data-mce-="">//<span data-mce-=""> 当图元自定义属性sceneURL改变时,清除旧dataModel,反序列化新的sceneURL
    <span data-mce-="">if (cache.g3d.sceneURL !==<span data-mce-=""> sceneURL) {
      cache.g3d.dm().clear();
      cache.g3d.sceneURL =<span data-mce-=""> sceneURL;
      <span data-mce-="">if<span data-mce-=""> (sceneURL) {
            cache.g3d.deserialize(sceneURL, <span data-mce-="">function<span data-mce-=""> (json, dm, g3d, datas) {
                <span data-mce-="">//<span data-mce-=""> 在反序列化后的回调函数中,执行onPostDeserialize函数
                onPostDeserialize &amp;&amp;<span data-mce-=""> onPostDeserialize(json, dm, g3d, datas);
            });
      }
    }
    <span data-mce-="">return<span data-mce-=""> cache.g3d;
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200410231103152-1429543537.gif"></p>
<p>&nbsp; &nbsp; &nbsp;3D场景嵌入后,接下来实现水泥厂内的车辆动画。根据后台传来车辆进入工厂的数据,我们创建运载不同原料的车辆模型,让它们沿着不同的路径抵达对应的厂房。同样是用 Shape 类型事先绘制好路径,根据 Shape 的 Points 和 Segments 信息,实现车辆沿着路径行驶动画。相关代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-=""> carAnimation(car, path, duration) {
      <span data-mce-="">//<span data-mce-=""> 车辆行驶动画
<span data-mce-="">      ht.Default.startAnim({
            duration: duration,
            easing: Easing.easeNone,
            action: <span data-mce-="">function<span data-mce-=""> (v, t) {
                <span data-mce-="">//<span data-mce-=""> 设置偏移量
                let offset = Math.floor(v * 100<span data-mce-="">);
                <span data-mce-="">//<span data-mce-=""> 根据偏移量得到在路径上的点坐标
                let position =<span data-mce-=""> ht.Default.getPercentPositionOnPoints(path.getPoints(), path.getSegments(), offset);
                <span data-mce-="">//<span data-mce-=""> 根据偏移量得到在路径上的点于路径切线角度
                let angle =<span data-mce-=""> ht.Default.getPercentAngle(path.getPoints(), path.getSegments(), offset);
                <span data-mce-="">//<span data-mce-=""> 设置车辆位置坐标及旋转角度
<span data-mce-="">                car.setX(position.x);
                car.setY(position.y);
                car.setRotationY(Math.PI / 2 -<span data-mce-=""> angle);
            },
      });
    }</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<h2>总结</h2>
<p>&nbsp; &nbsp; 工业互联网是工业发展的必经之路,我们国家是一个工业大国,正处在工业转型升级的关键时刻,面临着人工成本上升、原材料价格波动、贸易竞争日益加剧等问题,迫切需要提高效率、降低生产成本。只有坚定不移地推动工业互联网落地,加快更多企业的数字化转型和智能化改造,才有能让在全球化竞争中立于不败之地。可视化作为智能化数字化的最后一环,让复杂抽象的数据变得真正可知可感,帮助决策者发现规律,洞悉未来,为企业提速增效。</p>
<p>&nbsp; &nbsp; 还有更多的可视化案例可以参考:https://www.hightopo.com/demos/index.html</p><br><br>
来源:https://www.cnblogs.com/xhload3d/p/12688752.html
頁: [1]
查看完整版本: 基于 HTML5 WebGL 的 水泥工厂可视化系统