基於 HTML5 WebGL 的計量站三維視覺化監控系統 Web 組態工控應用

勤勞的搬運工發表於2019-04-23

前言

得益於 HTML5 WebGL 技術的成熟,從技術上對工控管理的視覺化,資料視覺化變得簡單易行!完成對工控裝置的管理效率,資源管理,風險管理等的大幅度提高,同時也對國家工業4.0計劃作出有力響應!

如本案例所示,是一個基於 HTML5 WebGL 技術實現的計量站三維視覺化監控系統,在本案例中,具體巨集觀的展示一個油田站點的整體場景,然後點選可以進入內景看到油田計量站內景的具體情況,同時可以看到各個裝置的引數的當前狀態。

同樣的其中功能元件具有很高的複用性,所以也會非常方便的應用到其他場景中!

如下所示,便是本案例執行動態圖:

此專案連結:基於 HTML5 WebGL 的計量站三維視覺化監控系統 Web 組態工控應用

(http://www.hightopo.com/demo/metering-station/ )
在這裡插入圖片描述
在這個場景中主要有如下幾個功能:

1、點選來回切換場景;

2、管線流動效果;

3、資料皮膚動態顯示;

程式碼實現

確認功能需求後就可以開始實施實現,動手實現之前要先確認場景有哪些,如下所示主要有油田外景和內景。

外景:
在這裡插入圖片描述
內景:
在這裡插入圖片描述

技能儲備

本 demo 需要掌握 HT for Web 的 2d 和 3d 相關 技術,具體技術參考資料可以去 HT for Web 官網圖撲軟體瞭解。

實現功能

1、 預設視角
在三維場景中,需要先設定一個預設視角,當重新開啟頁面時候直接回到預設視角,通過 setFar, setEye 和 setCenter 方法實現。

gv.setFar(100000);
gv.setEye([1247, 600, 1972]);
gv.setCenter([0, 0, 0]);

2、 視角限制
由於三維場景的特性,如果不作出視角限制,就會出現穿模,翻底等現象,尤其本案例有天空球效果,如果不作出視角限制當使用者無限拉遠後會出現視角跑到天空球外,場景消失問題,這將會非常尷尬!

具體實現是通過 setEye 方法和 setCenter 方法控制場景的 eye 和 center 變數實現,放置到 mp 函式內。

mp(listener, scope, ahead); // 增加自身屬性變化事件監聽器
// 限制 eye
gv.mp(function (e) {
    if (e.property === 'eye') {
        if (gv.getEye()[1] < 90) {
            gv.getEye()[1] = 90;
        }
        if (gv.getEye()[1] > 1500) {
            gv.getEye()[1] = 1500;
        }
        if (gv.getEye()[0] > 2400) {
            gv.getEye()[0] = 2400;
        }
        if (gv.getEye()[0] < -2400) {
            gv.getEye()[0] = -2400;
        }
        if (gv.getEye()[2] > 2500) {
            gv.getEye()[2] = 2500;
        }if (gv.getEye()[2] < -2400) {
            gv.getEye()[2] = -2400;
        }
    }
})

3、 點選切換場景
通過 mi 新增互動事件監聽器為要點機互動模型繫結事件,通過 e.kind 判斷點選事件,然後通過 tag 標籤名獲取要點選互動的模型物件。

首先在點選時候有個拉近效果和周圍模型透明化效果,則是通過 flyTo 實現拉近效果和 setStyle 方法實現拉近後其他模型透明化。

具體程式碼如下:

gv.mi(function (e) {
    if (e.kind === 'clickData') {
        for (var i = 1; i <= 2; i++) {
            if (e.data.getTag() === 'engineRoom' + i) {
                // 點選拉近場景
                gv.flyTo(e.data, {
                    animation: true,
                    distance: 500
                });
                // 選中模型實化
                e.data.setStyle('shape3d.transparent', false);
                e.data.setStyle('shape3d.opacity', 1);
                // 其他模型透明化
                dm.each(data => {
                    if (data.getTag() != 'engineRoom' + i) {
                        data.setStyle('shape3d.transparent', true);
                        data.setStyle('shape3d.opacity', 0.3);
                        data.setStyle('all.transparent', true);
                        data.setStyle('all.opacity', 0.5);
                    }
                })
            }
        }
    }
})

實現效果如下:
在這裡插入圖片描述
然後在完成拉近場景和透明化其他模型後,開始實現場景切換效果。

場景切換的核心是通過 gv.deserialize() 反序列化顯示路徑對應場景,通過輸入場景路徑引數,在回撥函式內完成場景渲染顯示,程式碼如下:

gv.deserialize('scenes/油田.json', function (json, dm, gv, datas) {
    if (json.title) document.title = json.title;
    if (json.a['json.background']) {
        var bgJSON = json.a['json.background'];
        if (bgJSON.indexOf('displays') === 0) {
            var bgGv = new ht.graph.GraphView();
            bgGv.deserialize(bgJSON);
            bgGv.addToDOM();
            graphView.addToDOM(bgGv.getView());
        }
        else if (bgJSON.indexOf('scenes') === 0) {
            var bgG3d = new ht.graph3d.Graph3dView();
            bgG3d.deserialize(bgJSON);
            bgG3d.addToDOM();
            graphView.addToDOM(bgG3d.getView());
        }
        graphView.handleScroll = function () { };
    }
})

但在這之前有一個問題,就是如何處理當前場景和通過反序列化渲染顯示場景的關係,如果不作處理,就會出現當前場景和要切換顯示的場景重合問題,所以在點選切換場景過程中,要先清空當前場景,為後來要切換的場景騰出地方。

所以在前面要先加一行程式碼:

dm.clear();

做完處理後,現在是完成了切換過去效果,但還有要切換回來的功能,這個實現非常簡單,取了個巧,直接 window.location.reload(); 重新整理頁面就好。

最終這部分完整程式碼如下:

function jump(position3d) {
    var timer = setInterval(function () {
        clearInterval(timer)
        var distance = ht.Default.getDistance(gv.getEye(), position3d);
        if (distance <= 501) {
            var home = g2d.dm().getDataByTag('home');
            home.s('2d.visible', true);
            var line = g2d.dm().getDataByTag('line');
            line.s('2d.visible', true);
            dm.clear();
            gv.deserialize('scenes/油田.json', function (json, dm, gv, datas) {
                if (json.title) document.title = json.title;
                if (json.a['json.background']) {
                    var bgJSON = json.a['json.background'];
                    if (bgJSON.indexOf('displays') === 0) {
                        var bgGv = new ht.graph.GraphView();
                        bgGv.deserialize(bgJSON);
                        bgGv.addToDOM();
                        graphView.addToDOM(bgGv.getView());
                    }
                    else if (bgJSON.indexOf('scenes') === 0) {
                        var bgG3d = new ht.graph3d.Graph3dView();
                        bgG3d.deserialize(bgJSON);
                        bgG3d.addToDOM();
                        graphView.addToDOM(bgG3d.getView());
                    }
                    graphView.handleScroll = function () { };
                }
            })
        }
    }, 500)
}

我將它放置到 jump 函式內,然後將 jump 函式放到前面點選事件中呼叫,讓程式碼整體簡潔一些。

實現效果如下圖:
在這裡插入圖片描述
4、 管線流動效果和動態資料皮膚
最後兩個功能實現非常簡單,我就放到一塊來說。

首先效果如下圖所示:
在這裡插入圖片描述
管線流動效果的實現核心就是控制 UV 貼圖偏移,所以通過動畫控制器 startAnim 控制 UV 貼圖偏移量就可以實現,在動畫結束時,在 finishFunc 內回撥函式即可實現動畫迴圈。

pipelineAnim(0.1)
function pipelineAnim(offset1) {
    var anim1 = ht.Default.startAnim({
        duration: 2000,
        action: function () {
            offset1 += 0.015;
            var pipelines = gv.dm().getDataByTag('pipeline');
            pipelines.setStyle('shape3d.uv.offset', [-offset1, 0]);
        },
        finishFunc: function () {
            pipelineAnim(offset1);
        }
    })
}

資料皮膚則是通過定時器在固定間隔時間迴圈執行賦予隨機數即可,在這裡通過隨機數模擬真實資料,在實際當中是通過和後臺對接實現真實資料動態變化,程式碼如下:

setInterval(function () {
    for (var i = 1; i <= 4; i++) {
        var panels = gv.dm().getDataByTag('panel' + i);
        for (var j = 1; j <= 3; j++) {
            if (panels.a('text' + j) != undefined) {
                var num = parseFloat(Math.random() * (100 - 10 + 1) + 10, 10).toFixed(2);
                var textJson = { "引數名": "出口溫度", "引數值": num, "引數單位": panels.a('text' + j)['引數單位'] };
                panels.a('text' + j, textJson);
            }
        }
    }
}, 1000)

結束語

以上便是我今天給大家帶來的工控案例,希望各位看官能夠喜歡本 demo,在本案例中能夠得到一些啟發。

相關文章