智慧城市交通的要素:路口監管可視化系統的解決方案

集成地理資訊系統、影片監控系統、交管部門各業務系統數據,對交通路況車流量、事故處理報告等要素進行綜合監測,並支援點選查看具體警力、機動目標、交通事件、監控影片等詳細資訊,幫助管理者實時掌握交通整體運行態勢。


支援集成前端影片巡檢系統,有效結合影片智慧分析、智慧定位、智慧研判技術,對道路擁堵點位、隱患點位、事故點位等情況進行可視化監測,實現異常事件的實時告警、快速顯示,並可智慧化調取異常點位周邊監控影片,有效提升接處警效率。

支援集成路口訊號燈、影片監控等系統數據,對路口交通流量、流速、車輛及道路異常事件、訊號燈狀態等資訊進行實時監測,並可結合專業的模型演算法,比對歷史最佳通行速度及最佳通行量,對路口交通態勢進行可視化分析研判,為訊號配時調優和路口交通組織優化提供科學的決策依據,有效提升交通運行效率。

充分整合交管部門現有數據資源,提供多種可視化分析、交互手段,對海量歷史違法違章案件數據進行可視化串並分析,深度挖掘案件時空分布規律,為交管部門進行原因分析、主動防範等業務應用提供支援。

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


