集成地理信息系统、视频监控系统、交管部门各业务系统数据,对交通路况车流量、事故处理报告等要素进行综合监测,并支持点选查看具体警力、机动目标、交通事件、监控视频等详细信息,帮助管理者实时掌握交通整体运行态势。
支持集成前端视频巡检系统,有效结合视频智能分析、智能定位、智能研判技术,对道路拥堵点位、隐患点位、事故点位等情况进行可视化监测,实现异常事件的实时告警、快速显示,并可智能化调取异常点位周边监控视频,有效提升接处警效率。
支持集成路口信号灯、视频监控等系统数据,对路口交通流量、流速、车辆及道路异常事件、信号灯状态等信息进行实时监测,并可结合专业的模型算法,比对历史最佳通行速度及最佳通行量,对路口交通态势进行可视化分析研判,为信号配时调优和路口交通组织优化提供科学的决策依据,有效提升交通运行效率。
充分整合交管部门现有数据资源,提供多种可视化分析、交互手段,对海量历史违法违章案件数据进行可视化串并分析,深度挖掘案件时空分布规律,为交管部门进行原因分析、主动防范等业务应用提供支持。
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
});
}
});
});
}