Commit 2dc3308f authored by ninglx's avatar ninglx

首页 超限车辆检测 历史播放

parent 2a75496c
syntax = "proto3";
package com.wanji.holo.proto;
option optimize_for = SPEED;
option java_package = "com.wanji.holo.proto";
option java_multiple_files = true;
......@@ -23,7 +21,9 @@ message RealtimeCarInfo {
bool isAggregate = 13; //是否聚合
string aggregatePointNum = 14; //聚合点数量
string dateTime = 15; //车辆时间
map<string, string> extendAttribute = 17;
RoadNet roadNet = 16; //路网数据
map<string, string> extendAttribute = 17; //扩展属性
OverRun overRun = 18;//超限数据
}
//数据类型对象
......@@ -34,15 +34,39 @@ message DataType {
double level= 4;//缩放级别
}
//数据类型对象
message OverRun {
string overType = 1;//超限类型 1超高 2超宽
string timeStamp = 2;//超限预警时间
double vehicleHeight = 3;//车高
double vehicleWidth= 4;//车宽
}
//数据类型对象
message RoadNet {
string crossId = 1;//路口ID
string rid = 2;//路段ID
string laneId = 3;//车道ID
string segmentId= 4;//渠化段ID
}
//数据类型对象
message DetectorState {
string laneId = 1;//车道ID
int32 state = 2;//状态
}
//轨迹信息
message RealtimeCarTrack {
//轨迹
repeated RealtimeCarInfo carInfo = 1;
//数据帧时间
string frameTime = 2;
map<string, int32> detectorState = 3; //检测器状态
}
//分页信息
message Page {
int32 pageNum = 1;//页号
......
<template>
<div
class="loopVideo"
v-loading="videoLoading"
element-loading-text="加载中..."
v-show="videoUrl"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<video
:id="`historyVideoComponent${channel}`"
@canplay="videoCanPlay"
:autoplay="autoplay"
muted
controls
loop
class="videoControl"
ref="loopVideoPlayer"
>
<div class="loopVideo" v-loading="videoLoading" element-loading-text="加载中..."
element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
<video :id="`historyVideoComponent${channel}`" style="object-fit: fill;" @canplay="videoCanPlay" :autoplay="autoplay" muted controls loop
class="videoControl" ref="loopVideoPlayer">
<source :src="videoUrl" type="video/mp4" />
您的浏览器不支持 video 属性。
</video>
......@@ -65,7 +51,7 @@ export default {
.replaceAll(" ", "_")
.replaceAll(":", "_");
},
destroy() {},
destroy() { },
async getVideoStreamUrl() {
if (this.timeModel.startTime && this.timeModel.endTime) {
try {
......@@ -75,11 +61,15 @@ export default {
starttime: this.replaceTimeGap(this.timeModel.startTime),
endtime: this.replaceTimeGap(this.timeModel.endTime),
});
console.log("response", response);
console.log("video response", response);
this.videoUrl = response.content;
setTimeout(() => {
this.$refs.loopVideoPlayer.load();
}, 200);
// this.videoUrl = 'video/111.mp4';
// setTimeout(() => {
// this.$refs.loopVideoPlayer.load();
// }, 200);
} catch (error) {
console.error(`Failed to get video stream url: ${error}`);
}
......@@ -124,5 +114,4 @@ export default {
// left: 30%;
// top: -14px;
// transform: translateX(-50%);
// }
</style>
// }</style>
......@@ -18,6 +18,7 @@ export default {
},
carTypeGlbMap:{
1:'0',
2:'170',
9:'0',
13:'0',
15:'0',
......
......@@ -31,11 +31,11 @@ export function initWs(data) {
// 接收消息
currentSocket.onmessage = (res) => {
// protobuf 解压数据
if (data.name === "callCar") {
if (data.name.includes("callCar")) {
let reader = new FileReader();
reader.onload = (e) => {
let buf = new Uint8Array(e.target.result);
let responseCarTrack = ResponseCarTrack.decode(buf);
let data1 = new Uint8Array(e.target.result);
let responseCarTrack = ResponseCarTrack.decode(data1);
data.callback(responseCarTrack.carInfo);
};
reader.readAsArrayBuffer(res.data);
......
......@@ -20,7 +20,7 @@ import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI, {
size: 'small',
})
window.wsHost = (process.env.NODE_ENV==='development'?'10.102.1.181:9000':location.host)
window.wsHost = (process.env.NODE_ENV==='development'?'10.102.1.182:9000':location.host)
// 暂时挂载到window 以支持项目之前的ELEMENT.Message写法
window.ELEMENT = ElementUI
// 事件总线
......
......@@ -30,6 +30,7 @@ export default {
});
},
mxRefreshLightWs() {
if (this.shouldShowLight) {
let targetPoint = turf.point([window.map.getCenter().lng, window.map.getCenter().lat]);
// 取最近点的crossId 判断哪个路口离当前视野中心点最近 请求对应的灯态websocket
let nearest = turf.nearestPoint(targetPoint, this.crossGeo);
......@@ -51,6 +52,8 @@ export default {
};
});
}
}
},
// 处理灯态
callLight(msg) {
......
......@@ -174,12 +174,14 @@ export default {
radarTimers.push(timer);
},
loadUpdateRadars() {
if (this.shouldShowRadar) {
if (!this.radarShow) {
this.radarShow = true;
this.loadRadars();
} else {
this.updateRadars()
}
}
},
updateRadars() {
this.clearRadarWave();
......
......@@ -112,7 +112,7 @@
</template>
<script>
import { download, getBlob } from "../../../utils/request";
import { getBlob } from "../../../utils/request";
// import fetch from "@/utils/fetch";
import { getReports, getReportsByPage } from "../../../dao/analysis";
import JSZip from "jszip";
......@@ -292,7 +292,7 @@ export default {
link.href = window.URL.createObjectURL(blob);
link.download = report.name;
link.click();
download(url, {}, report.name);
// download(url, {}, report.name);
},
screenReports() {
let filter = this.reportSearchValue;
......
......@@ -107,7 +107,8 @@
<div class="traffics">
<vue-seamless-scroll ref='22' class="warningMsg" :data="overData" :class-option="optionSetting1"
v-show="overData.length">
<li v-for="(item, index) of overData" class="overItem" :key="index">
<li @click="playOverHistoryTrace(item)" v-for="(item, index) of overData" class="overItem"
:key="index">
<div class='topLicense'> <span>预警时间:</span>{{ item.timeStamp }}</div>
<div class='detailMsg'>
<div><span>车牌:</span>{{ item.plateNo }}</div>
......@@ -157,7 +158,7 @@ export default {
dict,
activeName: '11',
overData: [
// { "plateNo": "京A895413", "inDirName": "东向西", "type": "201", "timeStamp": "2024-08-27 15:39:18" },
{ "plateNo": "京A895413", "inDirName": "东向西", "type": "201", "timeStamp": "2024-08-27 15:39:18" },
// { "plateNo": "京A895413", "inDirName": "东向西", "type": "504", "timeStamp": "2024-08-27 15:39:18" },
// { "plateNo": "京A895413", "inDirName": "东向西", "type": "504", "timeStamp": "2024-08-27 15:39:18" },
// { "plateNo": "京A895413", "inDirName": "东向西", "type": "504", "timeStamp": "2024-08-27 15:39:18" },
......@@ -229,13 +230,46 @@ export default {
this.timers.push(timer4);
},
methods: {
timeFormat(date) {
let time = new Date(date);
let year, month, day, hour, min, sec;
year = time.getFullYear();
month =
time.getMonth() + 1 < 10
? "0" + (time.getMonth() + 1)
: time.getMonth() + 1;
day = time.getDate() < 10 ? "0" + time.getDate() : time.getDate();
hour = time.getHours() < 10 ? "0" + time.getHours() : time.getHours();
min =
time.getMinutes() < 10 ? "0" + time.getMinutes() : time.getMinutes();
sec =
time.getSeconds() < 10 ? "0" + time.getSeconds() : time.getSeconds();
let dateTime = `${year}-${month}-${day} ${hour}:${min}:${sec}`; //+year + month + day + hour + min + sec;
return dateTime;
},
playOverHistoryTrace(row) {
let wsSend = {
"dataType": "OVER_RUN", //数据类型【固定值】
"timeStamp": row.timeStamp, //时间
'endTime': this.timeFormat(new Date(new Date(row.timeStamp).getTime() + 1000 * 10)),
"globalId": row.globalId, //车辆ID
"overType": row.type //超限类型
}
console.log('close real time', row, wsSend);
this.$emit("playOverHistoryTrace", wsSend)
},
// 获取超限列表
getOverData() {
getAlarmOverDatas().then(res => {
this.overLoading = false
console.log("超限列表", res.content);
// todo
this.overData = res.content;
if (res.content) {
this.overData = res.content.map(item => {
item.timeStamp = item.timeStamp.replace('T', ' ')
return item
}) || [];
}
})
},
tabClick(e) {
......@@ -642,15 +676,18 @@ export default {
text-overflow: ellipsis;
margin-right: 5px;
}
div:nth-child(1) {
width: 25%;
}
div:nth-child(2) {
width: calc(50% - 10px);
}
div:nth-child(3) {
width: 25%;
margin-right:0
margin-right: 0
}
}
}
......
......@@ -6,7 +6,8 @@
<situation-time v-show="currentCheck === 'first' && timeState" class="situationTime" :time="situationTimeVal" />
<wMap :mapId="'situation-map'" ref="wMap" />
<!--左右图表组件-->
<message-boxes @delWarningPopup="delWarningPopup" @addWarningPopup="addWarningPopup" :show="boxesShow" />
<message-boxes @playOverHistoryTrace="playOverHistoryTrace" @delWarningPopup="delWarningPopup"
@addWarningPopup="addWarningPopup" :show="boxesShow" />
<!--图层切换按钮-->
<layers-switch ref="switch" :show="boxesShow" @changeCheck="changeCheck" @visibleChange="visibleChange"
:class="boxesShow ? 'generalSwitch' : 'rightSwitch'" />
......@@ -20,6 +21,8 @@
<traffic-events-tend @updateHeatMap="refreshEventLayer" v-if="currentCheck === 'third'" />
<!--设备图例控制-->
<equipment-switch @equipmentChange="equipmentChange" v-if="currentCheck === 'fourth'" />
<!--单车超限历史轨迹视频-->
<sigalTraceVideos :key="sigalKey" v-if="showHisVid" :timeDuration="timeDuration"></sigalTraceVideos>
</div>
</template>
......@@ -46,6 +49,7 @@ import draw from "./mixins/draw";
import mixinLightModel from "./mixins/light3d";
import lightMixin from "../../mixins/lightMixin";
import radarMixin from "../../mixins/radarMixin";
import sigalTraceVideos from './sigalTraceVideos/index.vue'
import detetorMixin from "../../mixins/detetorMixin";
import {
eventInfo,
......@@ -123,7 +127,7 @@ export default {
mixinLightModel,
lightMixin, // 实时灯态mixin
radarMixin, // 雷达模型状态mixin
detetorMixin // 实时检测器mixin
detetorMixin, // 实时检测器mixin
],
components: {
TrackSwitcher,
......@@ -135,10 +139,25 @@ export default {
VideoList,
TrafficEventsTend,
EquipmentSwitch,
sigalTraceVideos
},
props: {},
computed: {
shouldShowRadar() {
return this.currentCheck === 'first' && !this.showHisVid
},
shouldShowLight() {
return this.currentCheck === 'first' && !this.showHisVid
}
},
data() {
return {
sigalKey: '',
showHisVid: false,
timeDuration: {
startTime: "",
endTime: "",
},
// Mixin 公用数据
crossGeo: null, //所有路口lnglat geojson集合
sockets: [],
......@@ -179,7 +198,6 @@ export default {
third: ["eventHeat", "eventPoint"],
fourth: ["weather", "camera", "radar", "signal", "milli"],
},
layerShowRenders: true,
rulerStatus: false,
selectVehicleTrackData: null,
highLightCarTracks: {}, // 储存弹窗车辆轨迹数据
......@@ -187,6 +205,7 @@ export default {
cBearing: "",
cLocation: "",
overCarsPoints: {}, // 储存超限车辆动态经纬度
backToRealTimer: null, // 存储回到实时车流倒计时器
};
},
watch: {
......@@ -306,6 +325,62 @@ export default {
this.lastLocation = [];
});
},
playOverHistoryTrace(sendMsg) {
if (this.backToRealTimer) {
clearTimeout(this.backToRealTimer)
this.backToRealTimer = null
}
Promise.all([this.closeWs('callCar'), this.closeWs('callCar1')]).then(() => {
console.log('closed all...');
this.removeAllRadars()
this.radarShow = false
this.clearTrackTimer();
window.tb?.clear(null, true);
window.tb?.update();
this.lastLocation = [];
this.timeDuration = {
startTime: sendMsg.timeStamp,
endTime: sendMsg.endTime,
};
this.$nextTick(() => {
// 播放单车历史ws
let socket = initWs({
name: "callCar1",
url: `ws://${window.wsHost}/holows/subscribe`,
callback: this.callCar,
});
socket.ws.onopen = () => {
if (this.backToRealTimer) {
clearTimeout(this.backToRealTimer)
this.backToRealTimer = null
}
this.backToRealTimer = setTimeout(() => {
this.closeWs('callCar1').then(() => {
this.openRealTime()
this.showHisVid = false
})
this.backToRealTimer = null
}, 1000 * 10)
if (socket.ws.readyState === 1) {
this.sockets.push(socket);
this.showHisVid = true
this.sigalKey = sendMsg.globalId
// socket.ws.send('{ dataType: "1"}');
// socket.ws.send('{"dataType":"OVER_RUN","timeStamp":"2024-09-06 15:01:17.000","endTime":"2024-09-06 15:01:27.000","globalId":"10547041","vehicleHeight":6,"vehicleWidth":2.55,"overType":1}')
socket.ws.send(JSON.stringify(sendMsg));
}
};
// socket.ws.onclose = () => {
// // this.closeRealTime()
// if(this.showHisVid){
// this.showHisVid = false
// this.openRealTime()
// }
// };
})
})
},
openRealTime() {
this.registerListener();
this.switchfirst();
......@@ -587,7 +662,7 @@ export default {
if (this.currentCheck === "first") {
if (map.getZoom() > 18) {
this.mxRefreshLightWs();
this.showCameras = [];
// this.showCameras = [];
}
}
},
......@@ -633,7 +708,6 @@ export default {
}
}
}, 100);
this.layerShowRenders = true;
let arr = this.$store.state.dicts.CrossInfo.map(item => {
[item.longitude, item.latitude] = item.location.split(",")
return item
......@@ -891,6 +965,8 @@ export default {
},
// 车辆详情弹窗内容
addCheckDetail(data) {
console.log('添加弹窗...');
if (vehiclePopups[`popup${data.id}`]) {
if (popupVises[`popup${data.id}`]) {
vehiclePopups[`popup${data.id}`].setLngLat([
......@@ -927,7 +1003,7 @@ export default {
vehiclePopups[`popup${data.id}`] = null;
delete vehiclePopups[`popup${data.id}`];
delete this.highLightCarTracks[data.id]
delete delete this.overCarsPoints[data.id]
delete this.overCarsPoints[data.id]
popupVises[`popup${data.id}`] = false;
});
popupVises[`popup${data.id}`] = true;
......@@ -959,16 +1035,16 @@ export default {
}
licenseLabel = {};
}
if (this.layerShowRenders) {
// 筛选可见区域
for (let i = 0; i < msg.length; i++) {
// 缺失车牌号字段和非机动车类型 把车牌号字段置空
if (!msg[i].picLicense || dict.noVehicleTypes.includes(msg[i].originalType)) {
msg[i].picLicense = "";
}
// if (msg[i].id.toString().includes('120327')) {
// msg[i].overType = '201,202'
// msg[i].timeStamp = '2021'
// if (msg[i].id.toString().includes('120') && new Date().getMinutes() % 2 === 0) {
// msg[i].overRun = {
// overType: '1,2',
// }
// }
let pt = turf.point([msg[i].longitude, msg[i].latitude]);
let poly = turf.polygon(this.bounds);
......@@ -980,10 +1056,10 @@ export default {
if (msg.length > 0) {
// console.log("车辆总数", msg.length);
// 第一帧 || 时间戳不相同 => 放行
if (
this.lastLocation.length === 0 ||
this.lastLocation[0].dateTime !== msg[0].dateTime
) {
if (msg.length === 1) {
console.log('msg', msg);
}
// if (this.lastLocation.length === 0) {
this.situationTimeVal = msg[0].dateTime;
// zoom 大于 18 : 移除圆点和图片图层
if (zoom >= 18) {
......@@ -1019,12 +1095,13 @@ export default {
}
}
// 超限车辆弹窗判断 todo
if (item.overType) {
if (item.overRun?.overType) {
console.log('检测到超限...', item.overRun)
// 如果超限车辆第一次添加 则设置 popupVises[`popup${item.id}`] 为 true
if (item.dill === 'add') {
if (!this.overCarsPoints[item.id]) {
popupVises[`popup${item.id}`] = true
// 右下角弹出超限警告
let overArr = item.overType.split(',')
let overArr = item.overRun.overType.split(',')
let overStr = ''
for (let t of overArr) {
overStr += dict.overCarType[t]
......@@ -1036,7 +1113,7 @@ export default {
message: `
<div>车牌号:${item.picLicense || '--'}</div>
<div>超限类型:${overStr}</div>
<div>预警时间:${item.timeStamp}</div>
<div>预警时间:${item.overRun.timeStamp}</div>
`,
type: 'warning',
position: 'bottom-right',
......@@ -1052,7 +1129,13 @@ export default {
delete this.overCarsPoints[item.id]
vehiclePopups[`popup${item.id}`]?.remove();
}
} else {
if (this.overCarsPoints[item.id]) {
delete this.overCarsPoints[item.id]
vehiclePopups[`popup${item.id}`]?.remove();
}
}
// 超限车辆 end
if (zoom > 18) {
// 新增
if (
......@@ -1125,10 +1208,9 @@ export default {
this.updateSelectVehicleTrack()
this.updateOverCarLnglat()
this.lastLocation = msg;
}
}
// }
}
},
vehicleCircleClick(e) {
const features = map.queryRenderedFeatures(e.point, {
......@@ -1235,7 +1317,6 @@ export default {
});
},
closeAllWs() {
this.layerShowRenders = false;
// console.log("sockets...", this.sockets);
const closePromises = this.sockets.map((socket) => {
return new Promise((resolve) => {
......
......@@ -30,6 +30,12 @@
<div class="detailItem" v-show="!notExceed">
<span class="left">超限类型:</span>{{ overTypeStr }}
</div>
<div class="detailItem" v-show="!notExceed">
<span class="left">宽度:</span>{{ overWidth }}
</div>
<div class="detailItem" v-show="!notExceed">
<span class="left">高度:</span>{{ overHeight }}
</div>
<div class="detailItem">
<span class="left">ID :</span>{{ model.id }}
</div>
......@@ -78,12 +84,21 @@ export default {
},
},
computed: {
overWidth() {
return this.model.overRun?.vehicleWidth || '--'
},
overHeight() {
return this.model.overRun?.vehicleHeight || '--'
},
notExceed() {
return !this.model.overType
if (!this.model.overRun || !this.model.overRun.overType) {
return true
}
return false
},
overTypeStr() {
if (this.model.overType) {
let overArr = this.model.overType.split(',')
if (this.model.overRun?.overType) {
let overArr = this.model.overRun.overType.split(',')
let overStr = ''
for (let t of overArr) {
overStr += dict.overCarType[t]
......@@ -92,7 +107,7 @@ export default {
}
},
overTime() {
return this.model.timeStamp || '--'
return this.model.overRun?.timeStamp || '--'
}
},
beforeDestroy() { },
......
<template>
<div id="holo_historyCameraContainer" class="eventCameraContainer" v-show="true">
<div id="historyCameraContainer" class="historyCameraContainer">
<div class="cameraVideoShow" :key="index" v-for="(item, index) of channels" v-show="true"
:id="`videoVisibles${item}`">
<loop-video class="holo_his" ref="hisVideo" :autoplay="true" :timeModel="timeDuration" :channel="item"></loop-video>
</div>
</div>
</div>
</template>
<script>
import LoopVideo from "../../../components/Standard/loopVideo.vue";
export default {
name: "historyVideos",
components: {
LoopVideo,
},
props: {
channels: {
type: Array,
default() {
return ['2', '9']
}
},
timeDuration: {
type: Object,
default() {
return {
startTime: '',
endTime: ''
}
}
},
},
data() {
return {
}
},
computed: {},
methods: {
// playHisVideos() {
// if (this.$refs.hisVideo) {
// for (let container of this.$refs.hisVideo) {
// container?.startPlay();
// }
// }
// }
},
};
</script>
<style lang="less" scoped>
.eventCameraContainer {
position: absolute;
width: 600px;
height: 200px;
max-width: 600px;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
margin: 0 auto;
border: 1px solid rgba(83, 146, 189, 1);
background: rgba(10, 26, 41, 0.9);
border-radius: 6px;
.holo_his{
border: 1px solid rgba(255,255,255,0.3);
}
.historyCameraContainer {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
height: 100%;
.cameraVideoShow {
width: 50%;
height: 100%;
padding: 4px;
}
.lessWidthVideo {
width: calc(100% - 6px);
}
.normalWidthVideo {
width: 100%;
}
}
.noVideo {
pointer-events: none;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
color: #04396f;
}
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment