基於 HTML5 WebGL 的低碳工業園區監控系統

前言

低碳工業園區的建設與推廣是我國推動工業低碳轉型的重要舉措,低碳工業園區能源與碳排放管控平臺是低碳工業園區建設的關鍵環節。如何對園區內的企業的能源量進行採集、計量、碳排放覈算,如何對能源消耗和碳排放進行實時動態監測等問題,涉及多個技術領域,專業性強。其數據不只要求準確,更要求真實可靠(便可覈查、可溯源)。這是低碳工業園區「管控平臺」建設的核心任務,也是當前我國工業園區建設中須要迫切解決的主要問題之一。html

www.hightopo.com/demo/HTBuil…node

這個 gif 圖中顯示的是一個 2D 3D 結合而成的低碳工業園區的能源監控系統,主要對各個樓宇以及園區總體的水、電等的使用量的實時監控。json

代碼實現

搭建場景

要建立出一個 3D 的低碳工業園區場景並不難,可是如何在同一個界面上同時顯示 2D 和 3D 的場景呢?想要作出炫酷的效果,這種方式在不少狀況下是很是有用的。bash

整個低碳工業園區的場景是搭建在 2D 上的,咱們知道,HTML 給 DOM 元素設置圖片只能用傳統的柵格位圖,可是若是怕圖片被拉伸而致使圖片模糊或者變形等結果,用 json 格式的矢量圖片來實現是最好的,柵格位圖在拉伸放大或縮小時會出現圖形模糊,線條變粗出現鋸齒等問題。 而矢量圖片經過點、線和多邊形來描述圖形,所以在無限放大和縮小圖片的狀況下依然能保持一致的精確度。app

首先我搭建了一個 2D 的場景用來放置咱們的 json 矢量圖,利用 ht.Default.xhrLoad 函數將 json 矢量背景圖反序列化顯示在 gv 上,這個 json 矢量背景圖中除了做爲背景的 node 還有另外兩個節點,以下圖,紅線框起來的比較大的這個節點是用來裝 3D 場景的,而右邊框起來的比較小的節點是用來放置另一個 gv 的(暫時還用不到,後期須要添加相似 form 表單的功能,因此我須要固定位置):dom

ht.Default.xhrLoad('displays/background.json', function(text) {
    dm.deserialize(text);// 反序列化數據到數據模型

    gv.addToDOM();// 將 2D 場景添加到 body 體中
});
複製代碼

這個 2D 場景做爲背景的部分就設置完畢,接下來看看如何在 2D 場景的基礎下放上 3D 場景。函數

2D 中添加 3D 場景

向 2D 中添加 3D 也是很是容易,問題是如何使 3D 場景根據 2D 場景縮放和平移來進行自適應變化,使 3D 場景始終保持在 2D 場景的某個固定的位置?我是經過監聽 gv 的屬性變化事件,監聽到 zoom、translate 等屬性,對 3D 場景進行自動佈局的操做:佈局

var g3dInfo = create3D('g3dNode');
gv.mp(function(e) {// 監聽 gv 的屬性變化事件
    if (e.property === 'zoom' || e.property === 'translateX' || e.property === 'translateY' ) {
        layout(g3dInfo);
    }
});

function layout(info) {
    var rect = info.node.getRect(),// 獲取場景依賴的節點的 矩形區域
        zoom = gv.getZoom(),// 獲取當前 gv 的縮放值
        tx = gv.tx(),// 獲取當前 gv 的水平平移值
        ty = gv.ty();// 獲取當前 gv 的垂直平移值
    
        // 依賴的節點的大小根據 zoom 縮放值來進行縮放
        rect.x *= zoom,
        rect.y *= zoom,
        rect.width *= zoom,
        rect.height *= zoom;

    var x = rect.x + tx,
        y = rect.y + ty;

    // 設置場景自動佈局
    if (info.g3d) info.g3d.layout(x, y, rect.width, rect.height);
}
複製代碼

眼尖的同窗應該已經注意到了,我沒有寫出 create3D 函數的聲明,就展現的效果而言,這個方法只是將場景 json 圖紙反序列化到 3D 場景中,並追加了一個對象 info,將 3D 場景所依賴的 node 和 3D 場景的變量傳進去:性能

function create3D(tag) {
    var g3d = new ht.graph3d.Graph3dView();// 3D 組件
    var dataModel = g3d.dm();// 獲取 3D 場景的數據容器
    gv.getView().appendChild(g3d.getView());// 將 3D 場景添加到 2D 場景中
    ht.Default.xhrLoad('scenes/電雲維.json', function(text) {// 加載 3D 場景的 json 矢量圖紙
        dataModel.deserialize(text);// 反序列化數據到數據模型
    });
    
    // 中止事件的傳播,阻止它被分派到其餘 Document 節點
    g3d.getView().addEventListener('mousedown', function(e) { e.stopPropagation()});
    g3d.getView().addEventListener('mousewheel', function(e) { e.stopPropagation()});
    if (isFirefox=navigator.userAgent.indexOf("Firefox") > 0) {  
        g3d.getView().addEventListener('DOMMouseScroll', function(e) { e.stopPropagation()});
    }

    var info = {
        g3d: g3d,
        node: dm.getDataByTag(tag),
    };
    return info;
}
複製代碼

2D 和 3D 在鼠標事件上有不少相同的點,可是咱們並不但願在操做 3D 場景的同時 2D 場景也跟着變化,因此上面代碼中禁止了鼠標按下和滾輪的事件傳播。動畫

##樓宇信息顯示

低碳工業園區監控系統實現的其中一個功能:點擊樓宇視線移到樓宇顯示到一個比較合適的位置,而且樓宇頂部顯示一個面板用來展現當前樓宇的信息。這裏我直接建立了一個節點,經過設置節點的 shape3d 屬性爲 billboard 便可顯示爲一個「面片」,面板很是好用,首先它只有一個面,在 3D 場景中若是須要大量的顯示數據的節點,推薦用這個 billboard 類型,很是省性能。

// 建立在建築上面的顯示面板
var billboard = new ht.Node();
billboard.setScaleX(2);// 將節點 X 軸上放大 2 倍
billboard.setScaleTall(2);// 將節點 Y 軸上放大 2 倍
billboard.s({
    'shape3d': 'billboard',// 此類型爲一個面片
    'shape3d.image': 'symbols/nodeForm.json',// 設置面片的顯示圖片爲矢量圖片
    'shape3d.autorotate': true,// 始終面向相機
    'shape3d.vector.dynamic': true,// 設置矢量圖形
    '3d.visible': false// 不可見
});
billboard.setTag('billboard');// 設置節點的 tag 惟一屬性
dataModel.add(billboard);// 將節點添加到數據容器中
複製代碼

經過點擊不一樣的樓宇則將信息面板展現在當前點擊的樓宇上方, 並根據不一樣的選中狀況對 billboard 進行顯隱的控制:

dataModel.sm().ms(function(e) {// 監聽選中變化事件
    if (e.kind === 'set' || e.kind === 'append') {// 設置選中 及 追加選中
        billboard.s('3d.visible', true);
        var data = dataModel.sm().ld();// 獲取當前選中的最後一個節點
        if (!data) return;
        billboard.p3(data.getPosition().x, data.getTall() + 200, data.getPosition().y);// 設置 billboard 的位置爲當前選中的節點的上方
    }
    else if (e.kind === 'remove') {// 選中移除
        var data = dataModel.sm().ld();// 獲取當前最後選中的節點
        if (data) {
            billboard.setPosition(data.getPosition().x, data.getPosition().y);
            billboard.setElevation(data.getTall() + 200);
        }
        else billboard.s('3d.visible', false);
    }
    else if (e.kind === 'clear') billboard.s('3d.visible', false);// 清除全部的選中後設置 billboard 不可見
});
複製代碼

(其餘例子參考)

www.hightopo.com/demo/large-…

至於點擊樓宇,從當前視線位置推到節點位置是經過 flyTo 函數,此函數在 6.2.2 版本是有三個參數,參數一爲目標節點,參數二爲是否動畫,參數三爲眼睛跟目標節點中心距離的計算,好比下面代碼設置 0.5,表示眼睛在上述方向上動態計算距離以將目標適配到屏幕 0.5 裏容納。信息面板上方顯示了當前點擊的樓宇的名稱,我是在設計 3D 場景的圖紙時給對應的樓宇設置上 displayName 屬性,當前顯示則根據這個 displayName 來進行顯示。

g3d.mi(function(e) {// 增長交互事件監聽器
    if(e.kind === 'clickData'){
        g3d.flyTo(e.data, true, 0.5);// 將 eye 和 center 從當前位置「飛到」目標節點的位置 第二個參數如果1 則佔滿全屏。 6.2.2 版本以上有此方法
        var name = e.data.getDisplayName();

        // 因爲 3D 中不能將模型組合到一塊兒,因此我用追加選中的方法來解決
        dataModel.each(function(node) {
            if(node.getDisplayName() !== name) return;// 我將同一類型的節點的 displayName 設置相同
            dataModel.sm().appendSelection(node);
        })
    }
});
複製代碼

那麼,只有一個 billboard,咱們如何讓這個 billboard 根據不一樣的樓宇顯示不一樣的信息?這個時候矢量圖標的優點又多了一個,經過對矢量圖標中的某個部分進行數據綁定進行數據的動態變化,這邊我三言兩語也講不完整,我就簡單提一下如何實現,剩下的能夠去官網中的數據綁定手冊中查閱相關資料和具體實現。

前面給 billboard 設置了一個 shape3d.image 屬性,設置的圖片爲 nodeForm.json,這個 json 中有四行文本顯示,頂部的文本用來顯示當前點擊的樓宇的名稱。

根據手冊咱們知道數據綁定的格式分爲兩種,一種是綁定 function 類型,另外一種是綁定 string 類型,以下:

也就是說若是 HT 中沒有定義咱們須要的屬性或者說一個矢量圖上有多個相同的屬性須要更改成不一樣的值,就能夠經過 attr 來自定義屬性,這裏我用的就是這個方法:

"text": {
    "func": "attr@buildingName",
    "value": "賽普健身學院學生宿舍"
}
複製代碼

數據綁定完成後,咱們只須要根據這個綁定數據對當前引用這個 json 矢量圖標的節點的業務屬性變化便可:

// 不一樣的樓宇上顯示的內容不一樣
billboard.a('buildingName', name);
billboard.a('electricUsage', (Math.random()*300).toFixed(2));
billboard.a('waterUsage', (Math.random()*300).toFixed(2));
billboard.a('gasUsage', (Math.random()*300).toFixed(2));
複製代碼

右側數據顯示

3D 場景建立完畢,接下來如何在 3D 上面再加右邊的兩個數據顯示面板?這裏我是在前面 2D json 場景中已排布好位置的節點上添加了另一個 2D 場景,用來顯示總體場景數據。由於這個 gv 上有兩個信息面板,因此我直接在 graphView 上添加了兩個節點,並將節點添加到這個 graphView 的 dataModel 數據容器上,其餘部分我就再也不作解釋了,都是基礎的代碼:

function createGV(tag) {
    var g2d = new ht.graph.GraphView();// 2D 拓撲場景
    var dataModel = g2d.dm();// 獲取當前拓撲場景的數據容器
    gv.getView().appendChild(g2d.getView());// 將此拓撲場景添加到底層背景圖上
    g2d.setInteractors([]);// 清除此組件上的交互

    // 添加兩個節點到拓撲場景上
    var node = new ht.Node();
    node.setImage('symbols/form.json');
    node.setPosition(0, 0);
    dataModel.add(node);

    var node1 = new ht.Node();
    node1.setImage('symbols/form1.json');
    node1.setPosition(0, dm.getDataByTag(tag).getHeight()/3);
    dataModel.add(node1);

    g2d.fitContent();

    setInterval(function() {// form表單數據動態變化
        node.a('electricUse', (Math.random()*300).toFixed(2));
        node.a('waterUse', (Math.random()*300).toFixed(2));
        node.a('gasUse', (Math.random()*300).toFixed(2));
        node.a('tempUse', (RandomNumBoth(10, 40))+'');
        node.a('wetUse', (Math.floor((Math.random()*100)))+'');
    }, 3000);

    var info = {
        g2d: g2d,
        node: dm.getDataByTag(tag)
    }
    return info;
}
複製代碼

以上,整個低碳工業園區監控系統的實現所有結束,有問題的或者建議均可以給我留言,或者直接訪問官網(hightopo.com/)查閱對應的資料。

相關文章
相關標籤/搜索