新基建下,智慧交通發展新規劃:智慧隧道監控可視化系統

前言 隨着當代經濟的發展,交通環境日益緊張,加上山區地區的交通運輸的需求,隧道的交通建設開發方興未艾。
隧道交通的規劃越來越完備,而對於隧道內監控管理維護卻顯得有些不足。而
工業4.0的崛起,逐步進入了智能化的新時代,伴隨着
工業互聯網的新興力量,工控可視化系統應運而生,不僅能起到日常的監控管理維護,在發現事故或險情時能第一時間採取
應急預案;還能通過實時數據的採集反饋,遠程操控設備運行以及預測設備的優良性能,從而達到更立體更全面的工控系統的運行。
HT for Web
 不止自主研發了強大的基於 HTML5 的 2D、3D 渲染引擎,為可視化提供了豐富的展示效果。介於 2D 組態和 3D 組態上,Hightopo(以下簡稱 HT )的 HT for Web 產品上的有着豐富的組態化可供選擇,本文將介紹如何運用 HT 豐富的 2/3D 組態搭建出一個隧道監控可視化系統的解決方案 監控隧道內的車道堵塞情況、隧道內的車禍現場,在隧道中显示當前車禍位置並在隧道口給予提示等功能都是非常有必要的。這個隧道監控可視化系統的主要內容包括:照明、風機、車道指示燈、交通信號燈、情報板、消防、火災報警、車行橫洞、風向儀、微波車檢、隧道緊急逃生出口的控制以及事故模擬等等。  
界面簡介及效果預覽  

預覽鏈接:http://www.hightopo.com/demo/tunnel2/index.html

上圖中的各種設備都可以雙擊,此時 camera 的位置會從當前位置移動到雙擊的設備的正前方;隧道入口的展示牌會自動輪播,出現事故時會展示牌中的內容會由“限速80,請開車燈”變為“超車道兩車追尾,請減速慢行”;兩隧道中間的逃生通道上方的指示牌是可以點擊的,點擊切換為藍綠色激活狀態,兩旁的逃生通道門也會打開,再單擊指示牌變為灰色,門關閉;還有一個事故現場模擬,雙擊兩旁變壓器中其中一個,在隧道內會出現一個“事故現場圖標”,單擊此圖標,出現彈出框显示事故等等等等。

 
代碼實現
一、場景搭建 整個隧道都是基於 3D 場景上繪製的,先來看看怎麼搭建 3D 場景:

// 數據容器 dm = new ht.DataModel(); // 3d 場景 g3d = new ht.graph3d.Graph3dView(dm); // 將場景添加到 body 中 g3d.addToDOM();

上面代碼中的 addToDOM 函數,是一個將組件添加到 body 體中的函數的封裝,定義如下:

addToDOM = function(){ var self = this, // 獲取組件的底層 div view = self.getView(), style = view.style; // 將組件底層div添加進body中  document.body.appendChild(view); // ht 默認將所有的組件的position都設置為absolute絕對定位 style.left = '0'; style.right = '0'; style.top = '0'; style.bottom = '0'; // 窗口大小改變事件,調用刷新函數 window.addEventListener('resize', function () { self.iv(); }, false); }

 
二、JSON反序列化 整個場景是由名為 隧道1.json 的文件導出而成的,我只需要用代碼將 json 文件中的內容轉換為我需要的部分即可:

// xhrLoad 函數是一個異步加載文件的函數 ht.Default.xhrLoad('./scenes/隧道1.json', function(text) { // 將 json 文件中的文本轉為我們需要的 json 格式的內容 var json = ht.Default.parse(text); // 反序列化數據容器,解析用於生成對應的Data對象並添加到數據容器 這裏相當於把 json 文件中生成的 ht.Node 節點反序列化到數據容器中,這樣數據容器中就有這個節點了  dm.deserialize(json); });

由於 xhrLoad 函數是一個異步加載函數,所以如果 dm 數據容器反序列化未完成就直接調用了其中的節點,那麼會造成數據獲取不到的結果,所以一般來說我是將一些邏輯代碼寫在這個函數內部,或者給邏輯代碼設置 timeout 錯開時間差。

首先,由於數據都是存儲在 dm 數據容器中的(通過 dm.add(node) 添加的),所以我們要獲取數據除了可以通過 id、tag 等獨立的方式,還可以通過遍曆數據容器來獲取多個元素。由於這個場景比較複雜,模型的面也比較多,鑒於設備配置,我將能 Batch 批量的元素都進行了批量。

批量是 HT 實現下的一種特有的機制,批量能提高性能的原理在於,當圖元一個個獨立繪製模型時性能較差,而當一批圖元聚合成一個大模型進行一次性的繪製時, 則會極大提高 WebGL 刷新性能,執行代碼如下

dm.each(function(data) { // 對“電話”進行批量 if (data.s('front.image') === 'assets/sos電話.png'){ data.s('batch', 'sosBatch'); } // 逃生通道批量(透明度也會影響性能) else if (data.s('all.color') === 'rgba(222,222,222,0.18)') { data.s('batch', 'emergencyBatch'); } else if (data.s('shape3d') === 'models/隧道/攝像頭.json' || data.s('shape3d') === 'models/隧道/橫洞.json' || data.s('shape3d') === 'models/隧道/捲簾門.json') { // 個別攝像頭染色了 不做批量 if(!data.s('shape3d.blend')) // 基礎批量什麼也不做 data.s('batch', 'basicBatch'); } else if (data.s('shape3d') === 'models/大型變壓器/變壓器.json') { data.s('batch', 'tileBatch'); data.setToolTip('單擊漫遊,雙擊車禍地點出現圖標'); } else if (data.getDisplayName() === '地面') { // 設置隧道“地面”不可選中 data.s('3d.selectable', false); } else if (data.s('shape3d') === 'models/隧道/排風.json') { // 排風扇的模型比較複雜,所以做批量 data.s('batch', 'fanBatch'); } else if (data.getDisplayName() === 'arrow') { // 隧道兩旁的箭頭路標 if (data.getTag() === 'arrowLeft') data.s('shape3d.image', 'displays/abc.png'); else data.s('shape3d.image', 'displays/abc2.png'); data.s({ 'shape3d': 'billboard', // 緩存,設置了 cache 的代價是需要設置 invalidateShape3dCachedImage 'shape3d.image.cache': true, // 設置這個值,圖片上的鋸齒就不會太明顯了(若圖片類型為 json,則設置 shape3d.dynamic.transparent) 'shape3d.transparent': true }); g3d.invalidateShape3dCachedImage(data); } // 隧道入口處的情報板 else if (data.getTag() === 'board' || data.getTag() === 'board1') { // 業務屬性,用來控制文本的位置[x,y,width,height] data.a('textRect', [0, 2, 244, 46]); // 業務屬性,設置文本內容 data.a('limitText', '限速80,請開車燈'); var min = -245; var name = 'board' + data.getId(); window[name] = setInterval(function() { // 設置情報板中的文字向左滾動,並且當文字全部显示時重複閃爍三次  circleFunc(data, window[name], min); }, 100); } //給逃生通道上方的指示板 動態設置顏色 var infos = ['人行橫洞1', '人行橫洞2', '人行橫洞3', '人行橫洞4', '車行橫洞1', '車行橫洞2', '車行橫洞3']; infos.forEach(function(info) { if(data.getDisplayName() === info) { data.a('emergencyColor', 'rgb(138, 138, 138)'); } }); infos = ['車道指示器', '車道指示器1', '車道指示器2', '車道指示器3']; infos.forEach(function(info) { if (data.getDisplayName() === info) { // 考慮到性能問題 將六面體變換為 billboard 類型元素 createBillboard(data, 'assets/車道信號-過.png', 'assets/車道信號-過.png', info); } }); });

上面有一處設置了 tooltip 文字提示信息,在 3d 中,要显示這個文字提示信息,就需要設置 g3d.enableToolTip() 函數,默認 3d 組件是關閉這個功能的。  
三、邏輯代碼
情報板滾動條 我就直接按照上面代碼中提到的方法進行解釋,首先是 circleFunc 情報板文字循環移動的函數,在這個函數中我們用到了業務屬性 limitText 設置情報板中的文字屬性以及 textRect 設置情報板中文字的移動位置屬性:

// 設置情報板中的文字向左滾動,並且當文字全部显示時重複閃爍三次 function circleFunc(data, timer, min) { // 獲取當前業務屬性 limitText 的內容 var text = data.a('limitText'); // 設置業務屬性 textRect 文本框的坐標和大小 data.a('textRect', [data.a('textRect')[0]-5, 2, 244, 46]); if (parseInt(data.a('textRect')) <= parseInt(min)) { data.a('textRect', [255, 2, 244, 46]); } else if (data.a('textRect')[0] === 0) { clearInterval(timer); var index = 0; // 設置多個 timer 是因為能夠進入這個函數中的不止一個 data,如果在同一時間多個 data 設置同一個 timer,那肯定只會對最後一個節點進行動畫。後面還有很多這種陷阱,要注意 var testName = 'testTimer' + data.getId(); window[testName] = setInterval(function() { index++; // 如果情報板中文本內容為空 if(data.a('limitText') === '') { setTimeout(function() { // 設置為傳入的 text 值 data.a('limitText', text); }, 100); } else { setTimeout(function() { // 若情報板中的文本內容不為空,則設置為空 data.a('limitText', ''); }, 100); } // 重複三次 if(index === 11) { clearInterval(window[testName]); data.a('limitText', text); } }, 100); setTimeout(function() { timer = setInterval(function() { // 回調函數  circleFunc(data, timer, min); }, 100); }, 1500); } } 

由於 WebGL 對瀏覽器的要求不低,為了能盡量多的適應各大瀏覽器,我們將所有的“道路指示器” ht.Node 類型的六面體全部換成 billboard 類型的節點,性能能提升不少。

http://www.hightopo.com 設置 billboard 的方法很簡單,獲取當前的六面體節點,然後給這些節點設置:

node.s({
    'shape3d': 'billboard', 'shape3d.image': imageUrl, 'shape3d.image.cache': true }); // 還記得用 shape3d.image.cache 的代價么? g3d.invalidateShape3dCachedImage(node); 

當然,因為 billboard 不能雙面显示不同的圖片,只是一個“面”,所以我們還得在這個節點的位置創建另一個節點,在這個節點的“背面”显示圖片,並且跟這個節點的配置一模一樣,不過位置要稍稍偏移一點。  
Camera 緩慢偏移 其他動畫部分比較簡單,我就不在這裏多說了,這裡有一個雙擊節點能將視線從當前 camera 位置移動到雙擊節點正前方的位置的動畫我提一下。我封裝了兩個函數 setEye 和 setCenter,分別用來設置 camera 的位置和目標位置的:

// 設置“目標”位置 function setCenter(center, finish) { // 獲取當前“目標”位置,為一個數組,而 getCenter 數組會在視線移動的過程中不斷變化,所以我們先拷貝一份 var c = g3d.getCenter().slice(0), // 當前x軸位置和目標位置的差值 dx = center[0] - c[0], dy = center[1] - c[1], dz = center[2] - c[2]; // 啟動 500 毫秒的動畫過度  ht.Default.startAnim({ duration: 500, action: function(v, t) { // 將“目標”位置緩慢從當前位置移動到設置的位置處  g3d.setCenter([ c[0] + dx * v, c[1] + dy * v, c[2] + dz * v ]); } }); }; // 設置“眼睛”位置 function setEye(eye, finish) { // 獲取當前“眼睛”位置,為一個數組,而 getEye 數組會在視線移動的過程中不斷變化,所以我們先拷貝一份 var e = g3d.getEye().slice(0), dx = eye[0] - e[0], dy = eye[1] - e[1], dz = eye[2] - e[2]; // 啟動 500 毫秒的動畫過度  ht.Default.startAnim({ duration: 500, // 將 Camera 位置緩慢地從當前位置移動到設置的位置 action: function(v, t) { g3d.setEye([ e[0] + dx * v, e[1] + dy * v, e[2] + dz * v ]); } }); };

後期我們要設置的時候就直接調用這兩個函數,並設置參數為我們目標的位置即可。比如我這個場景中的各個模型,由於不同視角對應的各個模型的旋轉角度也不同,我只能找幾個比較有代表性的 0°,90°,180°以及360° 這四種比較典型的角度了。所以繪製 3D 場景的時候,我也盡量設置節點的旋轉角度為這四个中的一種(而且對於我們這個場景來說,基本上只在 y 軸上旋轉了):

// 獲取事件對象的三維坐標 var p3 = e.data.p3(), // 獲取事件對象的三維尺寸 s3 = e.data.s3(), // 獲取事件對象的三維旋轉值 r3 = e.data.r3(); // 設置“目標”位置為當前事件對象的三維坐標值 setCenter(p3); // 如果節點的 y 軸旋轉值 不為 0 if (r3[1] !== 0) { // 浮點負數得做轉換才能進行比值 if (parseFloat(r3[1].toFixed(5)) === parseFloat(-3.14159)) { // 設置camera 的目標位置 setEye([p3[0], p3[1]+s3[1], p3[2] * Math.abs(r3[1]*2.3/6)]);  } else if (parseFloat(r3[1].toFixed(4)) === parseFloat(-1.5708)) { setEye([p3[0] * Math.abs(r3[1]/1.8), p3[1]+s3[1], p3[2]]);  } else { setEye([p3[0] *r3[1], p3[1]+s3[1], p3[2]]); } } else { setEye([p3[0], p3[1]+s3[1]*2, p3[2]+1000]); }

 
事故模擬現場 最後來說說模擬的事故現場吧,這段還是比較接近實際項目的。操作流程如下:雙擊“變壓器”–>隧道中間某個部分會出現一個“事故現場”圖標–>單擊圖標,彈出對話框,显示當前事故信息–>點擊確定,則事故現場之前的燈都显示為紅色×,並且隧道入口的情報板上的文字显示為“超車道兩車追尾,請減速慢行”–>再雙擊一次“變壓器”,場景恢復事故之前的狀態。 在 HT 中,可通過 Graph3dView#addInteractorListener(簡寫為 mi)來監聽交互過程:

g3d.addInteractorListener(function(e) { if(e.kind === 'doubleClickData') { // 有“事故”圖標節點存在 if (e.data.getTag() === 'jam') return; // 如果雙擊對象是變壓器 if (e.data.s('shape3d') === 'models/大型變壓器/變壓器.json') { index++; // 通過唯一標識 tag 標籤獲取“事故”圖標節點對象 var jam = dm.getDataByTag('jam'); if(index === 1){ var jam = dm.getDataByTag('jam'); jam.s({ // 設置節點在 3d 上可見 '3d.visible': true, // 設置節點為 billboard 類型 'shape3d': 'billboard', // 設置 billboard 的显示圖片 'shape3d.image': 'assets/車禍.png', // 設置 billboard 圖片是否緩存 'shape3d.image.cache': true, // 是否始終面向鏡頭 'shape3d.autorotate': true, // 默認保持圖片原本大小,設置為數組模式則可以設置圖片显示在界面上的大小 'shape3d.fixSizeOnScreen': [30, 30], }); // cache 的代價是節點需要設置這個函數  g3d.invalidateShape3dCachedImage(jam); } else { jam.s({ // 第二次雙擊變壓器就將所有一切恢復“事故”之前的狀態 '3d.visible': false }); dm.each(function(data) { var p3 = data.p3(); if ((p3[2] < jam.p3()[2]) && data.getDisplayName() === '車道指示器1') { data.s('shape3d.image', 'assets/車道信號-過.png'); } if(data.getTag() === 'board1') { data.a('limitText', '限速80,請開車燈'); } }); index = 0; } } } });

既然“事故”節點圖標出現了,接着點擊圖標出現“事故信息彈出框”,監聽事件同樣是在 mi(addInteractorListener)中,但是這次監聽的是單擊事件,我們知道,監聽雙擊事件時會觸發一次單擊事件,為了避免這種情況,我在單擊事件裏面做了演示:

// 點擊圖元 else if (e.kind === 'clickData'){ timer = setTimeout(function() { clearTimeout(timer); // 如果是“事故”圖標節點 if (e.data.getTag() === 'jam') { // 創建一個對話框  createDialog(e.data); } }, 200); }

在上面的雙擊事件中我沒有 clearTimeout,怕順序問題給大家造成困擾,要記得加一下。 彈出框如下: 這個彈出框是由兩個 ht.widget.FormPane 表單構成的,左邊的表單隻有一行,行高為 140,右邊的表單是由 5 行構成的,點擊確定,則“事故”圖標節點之前的道路指示燈都換成紅色×的圖標:

// 彈出框右邊的表單 function createForm4(node, dialog) { // 表單組件 var form = new ht.widget.FormPane(); // 設置表單組件的寬 form.setWidth(200); // 設置表單組件的高 form.setHeight(200); // 獲取表單組件的底層 div var view = form.getView(); // 將表單組件添加到 body 中  document.body.appendChild(view); var infos = [ '編輯框內容為:2輛', '編輯框內容為:客車-客車', '編輯框內容為:無起火', '編輯框內容為:超車道' ]; infos.forEach(function(info) { // 向表單中添加行  form.addRow([ info // 第二個參數為行寬度,小於1的值為相對值 ], [0.1]); }); form.addRow([ { // 添加一行的“確認”按鈕  button: { label: '確認', // 按鈕點擊事件觸發 onClicked: function() { // 隱藏對話框  dialog.hide(); dm.each(function(data) { var p3 = data.p3(); // 改變“車道指示器”的显示圖片為紅色×,這裏我是根據“事故”圖標節點的坐標來判斷“車道显示器”是在前還是在後的 if ((p3[2] < node.p3()[2]) && data.getDisplayName() === '車道指示器1') { data.s('shape3d.image', 'assets/車道信號-禁止.png'); } // 將隧道口的情報板上的文字替換 if(data.getTag() === 'board1') { data.a('limitText', '超車道兩車追尾,請減速慢行'); } }); } } } ], [0.1]); return form; }

 
總結 伴隨着新基建的建設興起,是以新發展理念為引領,以技術創新為驅動,以信息網絡為基礎,面向高質量發展需要,提供数字轉型、智能升級、融合創新等服務的基礎設施體系的完備,國家正邁入新時代的建設,也迎來了新時代的挑戰與機遇。隧道交通的監控可以歸納為工控管理與智慧交通建設的產物,同樣具有極為重要的意義。在眾多行業上所積累的經驗,HT 已經實現了許多不同領域建設的案例,例如 路口監管可視化系統,有興趣的話也可以了解一下!   2019 我們也更新了數百個工業互聯網 2D/3D 可視化案例集,在這裏你能發現許多新奇的實例,也能發掘出不一樣的工業互聯網: https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA 同時,你也可以查看更多案例及效果: https://www.hightopo.com/demos/index.html 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案