整合地理資訊系統、視訊監控系統、交管部門各業務系統資料,對交通路況車流量、事故處理報告等要素進行綜合監測,並支援點選檢視具體警力、機動目標、交通事件、監控視訊等詳細資訊,幫助管理者實時掌握交通整體執行態勢。
支援整合前端視訊巡檢系統,有效結合視訊智慧分析、智慧定位、智慧研判技術,對道路擁堵點位、隱患點位、事故點位等情況進行視覺化監測,實現異常事件的實時告警、快速顯示,並可智慧化調取異常點位周邊監控視訊,有效提升接處警效率。
支援整合路口訊號燈、視訊監控等系統資料,對路口交通流量、流速、車輛及道路異常事件、訊號燈狀態等資訊進行實時監測,並可結合專業的模型演算法,比對歷史最佳通行速度及最佳通行量,對路口交通態勢進行視覺化分析研判,為訊號配時調優和路口交通組織優化提供科學的決策依據,有效提升交通執行效率。
充分整合交管部門現有資料資源,提供多種視覺化分析、互動手段,對海量歷史違法違章案件資料進行視覺化串並分析,深度挖掘案件時空分佈規律,為交管部門進行原因分析、主動防範等業務應用提供支援。
loadCar(type) { // 建立車輛新節點 let car = new ht.Node(); // 根據車輛型別建立載入對應車輛模型 switch (type) { case 'familyCar': car.s('shape3d', 'models/HT模型庫/交通/車輛/家用車.json'); break; case 'truck': car.s('shape3d', 'models/HT模型庫/交通/車輛/卡車.json'); break; case 'jeep': car.s('shape3d', 'models/HT模型庫/交通/車輛/吉普車.json'); break; ... default: console.log('NO THIS TYPE CAR!'); break; } // 設定車輛不可選擇和不可移動 car.s({ '3d.selectable': false, '3d.movable': false }); // 設定錨點 --- 車的頭部 car.setAnchor3d(1, 0, 0.5); // 設定初始位置 car.setPosition3d(0, 100000, 0); let typeIndex = 1; // 判斷是否此前生成了這種型別的車輛 this.g3dDm.each(data => { if (data.getTag() === type + typeIndex) { typeIndex++; } }) // 設定車輛節點標籤 car.setTag(type + typeIndex); // 設定車輛節點的名字 car.setDisplayName(type); // 將車輛節點新增到資料模型中 this.g3dDm.add(car); }
而關於管道動畫的實現上,基於 ht.Default.startAnim() 封裝了一個 move 的動畫函式是節點沿著路徑平滑移動的封裝函式,主要引數為:
- node:動畫節點;
- path:執行路徑;
- duration:動畫執行排程時間;
- animParams:動畫引數;
通過繪製一條執行路線的管道,ht.Default.getLineCacheInfo() 得到這條管道的點位和分割資訊 cache,然後管道資訊通過 ht.Default.getLineLength() 得到管道的長度,並且通過 ht.Default.getLineOffset() 來獲取連線或者管道指定比例的偏移資訊,從而達到移動的效果,是為了通過 node.lookAtX() 來獲取節點下一個面對的朝向的位置資訊,並設定節點此時的位置,從而達到節點沿著路徑平滑移動的效果。
move(node, path, duration = 20000, animParams) { // path._cache_ 裡面存著管道的節點資訊 let cache = path._cache_; // 如果沒有快取資訊,則獲取 path._cache_ 裡面存著管道的節點資訊 if (!cache) { cache = path._cache_ = ht.Default.getLineCacheInfo(path.getPoints(), path.getSegments()); } // 獲取管道快取資訊的長度 const len = ht.Default.getLineLength(cache); // 設定動畫物件初始化 animParams = animParams || {}; // 設定 action 為 animParams 的動畫執行函式 const action = animParams.action; // 動畫執行部分 animParams.action = (v, t) => { // 獲取管道運動的偏移資訊 const offset = ht.Default.getLineOffset(cache, len * v); // 獲取偏移位置上的點 const point = offset.point; // 設定節點看向的下一個位置 node.lookAtX([point.x, point.y, point.z], "forward "); // 設定節點的位置 node.p3(point.x, point.y, point.z); // 判斷動畫是否執行完 if (action) action(); }; // 迴圈呼叫動畫執行函式 return loop(animParams.action, duration); } // 迴圈動畫函式 loop(action, duration) { return ht.Default.startAnim({ duration: duration, action: action }); }
在互動實現上,通過點選選中攝像頭後,使這個攝像頭的錐形區域變為直線,表示為選中狀態同時標記選中的攝像頭的選中前後順序,並且通過派發事件驅使 2D 圖紙上顯示攝像頭彈窗,在彈窗顯示的同時,通過計算得到實時變動的中心點位置資訊(center),只要實時通過全域性派發事件把位置資訊傳輸到攝像頭彈窗場景,就能起到攝像頭場景視角與主場景中所點選攝像頭的視角同步;取消彈窗顯示的互動方式是通過雙擊場景背景,恢復攝像頭錐形區域並且派發事件去隱藏 2D圖紙上的攝像頭彈窗:
// 全域性事件派發器 var G = {} window.G = G; G.event = new ht.Notifier(); handleInteractive(e) { const {kind, data} = e; if(kind === 'clickData') { // 判斷點選節點是否帶有標籤,沒有標籤則 return let tag = data.getTag(); if(!tag) return; // 判斷標籤名為攝像頭 if(tag.indexOf('camera') >= 0) { // 設定指定上一個點選的攝像頭和當前點選的攝像頭 this.lastClickCamera = this.nowClickCamera; this.nowClickCamera = data; // 如果之前有點選攝像頭,則初始化攝像頭錐體的大小 if (this.lastClickCamera !== null) { let clickRangeNode = this.lastClickCamera.getChildren()._as[0]; clickRangeNode.s3(300, 150, 500); } // 如果有點選攝像頭,則設定所點選攝像頭錐體的大小 if (this.nowClickCamera !== null) { let clickRangeNode = this.nowClickCamera.getChildren()._as[0]; clickRangeNode.s3(5, 5, 500); } // 獲取點選攝像頭的位置資訊 var cameraP3 = nowClickCamera.p3(); // 獲取點選攝像頭的旋轉資訊 var cameraR3 = nowClickCamera.r3(); // 獲取點選攝像頭的大小資訊 var cameraS3 = nowClickCamera.s3(); // 當前錐體起始位置 var realP3 = [cameraP3[0], cameraP3[1] + cameraS3[1] / 2, cameraP3[2] + cameraS3[2] / 2]; // 將當前眼睛位置繞著攝像頭起始位置旋轉得到正確眼睛位置 var realEye = getCenter(cameraP3, realP3, cameraR3); // 全域性事件派發至攝像頭場景改變視角的眼睛 eye 和中心點 center G.event.fire({ type: 'videoCreated', eye: realEye, center: getCenter(realEye, [realEye[0], realEye[1] ,realEye[2] + 5], cameraR3) }); // 視訊彈窗顯示派發 event.fire(SHOW_VIDEO, {g3dDm: this.g3dDm, cameraName:tag}); } } // 雙擊背景隱藏攝像頭場景視窗,並初始化攝像頭錐體的大小 if(kind === 'doubleClickBackground') { // 視訊彈窗隱藏派發 event.fire(HIDE_VIDEO); // 如果之前有點選攝像頭,則初始化攝像頭錐體的大小 if (this.nowClickCamera !== null) { let clickRangeNode = this.nowClickCamera.getChildren()._as[0]; clickRangeNode.s3(300, 150, 500) } // 設定當前點選攝像頭為空 this.nowClickCamera = null; } }
以上所涉及到方法 getCenter(),實際上是通過去獲取每個攝像頭節點在場景中對應的旋轉角度,簡化理解就是一個點 A 圍繞著另外一個點 B 旋轉,即中心點位置(center)圍繞著眼睛位置(eye)旋轉,而我們則需要去計算點 A 的位置(中心點位置 center),這裡通過封裝一個 getCenter 方法用於獲取 3d 場景中點 A 繞著點 B 旋轉 angle 角度之後得到的點 A 在 3d 場景中的位置,方法中採用了 HT 封裝的 ht.Math 下面的方法,以下為實現的程式碼:
實現程式碼如下:
// pointA 為 pointB 圍繞的旋轉點 // pointB 為需要旋轉的點 // r3 為旋轉的角度陣列 [xAngle, yAngle, zAngle] 為繞著 x, y, z 軸分別旋轉的角度 const getCenter = function(pointA, pointB, r3) { const mtrx = new ht.Math.Matrix4(); const euler = new ht.Math.Euler(); const v1 = new ht.Math.Vector3(); const v2 = new ht.Math.Vector3(); mtrx.makeRotationFromEuler(euler.set(r3[0], r3[1], r3[2])); v1.fromArray(pointB).sub(v2.fromArray(pointA)); v2.copy(v1).applyMatrix4(mtrx); v2.sub(v1); return [pointB[0] + v2.x, pointB[1] + v2.y, pointB[2] + v2.z]; };
2.2 實景攝像頭的實現原理
- RTMP (Real Time Messaging Protocol):實時訊息傳輸協議,RTMP 協議中,視訊必須是 H264 編碼,音訊必須是 AAC 或 MP3 編碼,且多以 flv 格式封包。因為 RTMP 協議傳輸的基本是 FLV 格式的流檔案,必須使用 flash 播放器才能播放。
- RTSP (Real-Time Stream Protocol):RTSP 實時效果非常好,適合視訊聊天、視訊監控等方向。
- HLS(Http Live Streaming):由 Apple 公司定義的基於 HTTP 的流媒體實時傳輸協議。傳輸內容包括兩部分:1.M3U8 描述檔案,2.TS 媒體檔案。TS 媒體檔案中的視訊必須是H264編碼,音訊必須是 AAC 或 MP3 編碼。資料通過 HTTP 協議傳輸。目前 video.js 庫支援該格式檔案的播放。
- HTTP-FLV:本協議就是 http+flv,將音視訊資料封裝成FLV格式,然後通過http協議傳輸到客戶端,這個協議大大方便了瀏覽器客戶端播放直播視訊流.目前 flv.js 庫支援該格式的檔案播放。
例如通過一個簡單的 RTMP 視訊流的對接就可以明白其實現的原理。對於的視訊的載入,需要用到 video.js 的外掛進行展示,所以先引入外掛,然後對接視訊流後,也是同樣通過全域性事件派發到 HT 的渲染元素 renderHTML 將視訊流渲染到場景圖紙中,以下是實現的虛擬碼:
// 引入 video.js 外掛 <script src="./js/video.js"></script> // 通過全域性事件派發到渲染元素 renderHTML 去渲染視訊到場景圖紙中 G.event.add(function(e){ if(e.type==='videoCreated'){ var div=e.div; div.innerHTML='<video id="video" class="video-js vjs-default-skin"><source src="rtmp://10.10.70.57/live/test" type="rtmp/flv"></video>'; window.player = videojs('video'); } });
- ajax:使用 JavaScript 向伺服器提出請求並處理響應而不阻塞使用者核心物件 XMLHttpRequest;
- axios:基於 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中;
- WebSocket:HTML5 提供的一種在單個 TCP 連線上進行全雙工通訊的協議;
ajax 和 axios 要實時獲取介面資料得通過輪詢呼叫介面的形式進行傳輸,而 WebSocket 可以雙向進行資料傳輸,在選擇運用上可以匹配自己的實現需求。本系統是採用通過 axios 呼叫介面獲取實時資料。
示例中的柱狀圖和折線圖,是通過 HT 裡的機制下去使用 eEcharts 上一些圖表進行自定義配置而實現的,繼而通過對 axios 介面輪詢呼叫載入資料,展現了實時的路口監控資料資訊:
loadData() { // 獲取圖紙的資料模型 let dm = this.g2d.dm(); // 獲取車流量介面的資料 axios.get('/traffic').then(res => { // 接入日車流量折線圖的資料 this.lineChart1.a({ 'seriesData1': res.lineChartData1, 'axisData' : res.axisData }); // 接入車輛執行高峰折線圖的資料 this.lineChart2.a({ 'seriesData1': res.lineChartData2, 'axisData' : res.axisData }) // 採用數字跳動的方式載入一些資料內容 setBindingDatasWithAnim(dm, res, 800, v => Math.round(v)); // 接入執行峰值的時刻 this.peakTime.s('text', res.peakTime); }); // 載入裝置執行狀態的資料 axios.get('/equipmentStatus').then(res => { setBindingDatasWithAnim(dm, res, 800, v => Math.round(v)); }); // 載入事故統計的資料 axios.get('/accident').then(res => { setBindingDatasWithAnim(dm, res, 800, v => Math.round(v)); // 接入每月事故柱狀圖的資料 this.accidentBar.a({ axisData: res.axisData, seriesData1: res.seriesData1 }) }); }
addTableRow() { // 獲取表格節點 let table = this.table; // 通過 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 }); } }); }); }