基於 HTML5 WebGL 的高爐鍊鐵廠可視化系統

前言
      在當今  工業4.0 新時代的推進下,不只迎來了  工業互聯網 的發展,還開啓了  5G 時代的新次元。而伴隨着帶寬的提高,網絡信息飛速發展,能源管控上與實時預警在工業互聯網中也佔着舉足輕重的地位,而對於高爐鍊鐵的發展上來看,目前已完成國內260座高爐的數字化和智能化落地,並推進鍊鐵大數據平臺在俄羅斯、越南、伊朗、印尼等「一帶一路」國家鋼鐵企業中應用,充分體現了高爐智能化大屏產業應運而生。咱們將使用  Hightopo(如下簡稱 HT )的  HT for Web 產品上的  web 組態跟你們介紹一下經過 2/3D 融合搭建的高爐鍊鐵廠可視化系統。
      HT 能夠快速實現豐富的  2D 組態和  3D 組態效果,能夠根據需求發揮本身的想象,玩轉不少新奇的功能,而且經過優點互補的做用下,完善出一套完整的可視化系統解決方案。因此在可視化系統的實現上,3D 場景採用以  HT 輕量化 HTML5/WebGL 建模的方案,實現快速建模、運行時輕量化到甚至手機終端瀏覽器便可 3D 可視化運維的良好效果;而在對應的 2D 圖紙上,使用特有的矢量,在各類比例下不失真,加上佈局機制,解決了不一樣屏幕比例下的展現問題。
 
本文將從如下三個方面與你們分享高爐鍊鐵廠在大屏展現上的實現:
      一、 頁面搭建:介紹基礎的 2D 圖紙與 3D 場景融合的項目搭建;
      二、 數據對接:進行面板數據的對接展現;
      三、 動畫實現:鐵水罐車運輸、傳送帶運送以及場景漫遊的實現;
 
界面簡介及效果預覽
      在整個高爐鍊鐵廠可視化系統的 2D 面板上,呈現了昨日曆史與今日實時的一些重要預警數據,在管控上能起到實時監控的做用,也能與歷史數據進行對比,從而使生產與安全達到預期的預警效果;其次 3D 場景經過輕量化的模型呈現出一座高爐鍊鐵廠的基本運做流程以及鐵水罐車運送鋼鐵的動畫,加上環繞的漫遊效果,起到全方位的實時監控狀態的變化。
 
代碼實現
1、頁面搭建
        在內容實現上,採用了 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下的方式實現面板與場景的加載。html

// 將 3D 組件加入到 body 下 g3d.addToDOM(); // 將 2D 組件加入到 3D 組件的根 div 下,父子 DOM 事件會冒泡,這樣不會影響 3D 場景的交互 g2d.addToDOM(g3d.getView());

 

      同時,在交互與呈現上改變了一些實現方式。例如,修改了左右鍵的交互方式,設置左鍵點擊旋轉 3D 場景,右鍵點擊爲 pan 抓圖的場景移動方式。其次,在點擊 2D 有點到圖元像素時,咱們但願不觸發 3D 的交互,例如在對 2D 面板表格中用滾輪滑動的時候,會觸發 3D 場景的縮放,這裏經過監聽 moudedown、touchstart 和 wheel 三種交互來進行控制,對於 wheel 的監聽方式,爲了保證兼容性就經過封裝一個 getWheelEventName() 的方法來獲得事件名。node

// 修改左右鍵交互方式 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'; } }

 

2、數據對接
      在 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 參數經過數學公式來控制動畫的運動的快慢,例如勻速變化、先慢後快等效果。ios

       這裏經過動畫函數封裝了一個差值的動畫效果,參數以下:web

  • 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 }); } }); }); }

  

3、動畫實現
      在靜態的場景以及面板下,很難直觀地去體現一個 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 節點自帶的樣式屬性,其本質意義就是能夠經過指定的方向進行對於整個矢量圖標的裁剪:ajax

 

      一部電影能夠經過各類鏡頭的切換下呈現不盡相同的敘事效果,日劇夕陽下熱血跑的急速切換或者幽暗角落下驚恐的淡入淡出,都是一種敘事的處理手段。在 HT 設定的 3D 場景中一樣地也存在着許許多多敘述的手法,最爲基礎的設定就是經過場景中的主觀眼睛 eye 和場景中心 center 來搭配各類動畫的實現,能夠本身設定值的方法函數來修改,也能夠經過 HT 自身封裝的方法函數來處理,例如 flyTo() 和 moveCamera() 就是最爲基礎的相機動畫,有興趣的話能夠了解一下,本身動手嘗試搭配,確定能最大地發揮 3D 場景的優點所在。json

      漫遊動畫是爲了更好地從不一樣的視角去巡視場景,只要經過設置幾組眼睛視角,運用  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 場景下的講解說明具備完整的故事串聯性。axios

      如下是鐵水罐車裝載與運輸的動畫流程:數組

      在 3D 場景中是用 x, y, z 來分別表示三個軸,經過不斷修改節點的 3D 座標就能夠實現位移效果 car.setPosition3d(x, y, z),而對於鐵水罐車上的裝載標籤則使用吸附的功能,使其吸附在鐵水罐車上就能跟着一塊兒行駛移動,而後在指定的空間座標位置上經過 car.s('3d.visible', true | false) 來控制鐵水罐車的出現與隱藏的效果。promise

 

      而關於傳送帶上煤塊、鐵礦的傳輸和管道氣體流通的指示,經過使用 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
相關文章
相關標籤/搜索