|
|
@@ -1,18 +1,67 @@ |
|
|
|
<template> |
|
|
|
<n-drawer v-bind="getDrawerOptions" @update:show="handleDrawerColse"> |
|
|
|
<n-drawer-content closable title="轨迹回放"> |
|
|
|
<BaseMap /> |
|
|
|
<div class="main_container"> |
|
|
|
<div id="history-map" /> |
|
|
|
<!-- 视频播放 --> |
|
|
|
<div class="videobox"> |
|
|
|
<!-- 视频开关 --> |
|
|
|
<div |
|
|
|
class="flagbox" |
|
|
|
:class="videoShow == 'back' ? 'flagbox_open' : 'flaxbox_back'" |
|
|
|
> |
|
|
|
<span class="videotitle">巡检视频</span> |
|
|
|
<img |
|
|
|
v-if="videoShow == 'back'" |
|
|
|
src="@/assets/img/back.png" |
|
|
|
alt="" |
|
|
|
class="closedetail imageicon" |
|
|
|
@click="showVideo('open')" |
|
|
|
> |
|
|
|
<img |
|
|
|
v-else |
|
|
|
src="@/assets/img/open.png" |
|
|
|
alt="" |
|
|
|
class="closedetail imageicon" |
|
|
|
@click="showVideo('back')" |
|
|
|
> |
|
|
|
</div> |
|
|
|
<!-- 直播视频 --> |
|
|
|
<!-- <div |
|
|
|
class="video_content" |
|
|
|
:class="videoShow == 'back' ? 'video_show' : 'video_hidden'" |
|
|
|
> |
|
|
|
<VideoPlayer :ref="videoRef" :options="getVideoOptions" @time-update="handleOriginTime" @video-status="handleOriginStatus" /> |
|
|
|
<VideoPlayer :ref="aiVideoRef" :options="getVideoAiOptions" @time-update="handleAiTime" @video-status="(data)=>videoStatus.aiStatus = data.status" /> |
|
|
|
|
|
|
|
</div> --> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</n-drawer-content> |
|
|
|
</n-drawer> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import BaseMap from './BaseMap.vue' |
|
|
|
import { h, defineComponent, computed, reactive, toRefs } from 'vue' |
|
|
|
import { defineComponent, computed, reactive, toRefs, onMounted, ref, watch } from 'vue' |
|
|
|
import { Map, View, Feature } from 'ol' |
|
|
|
import 'ol/ol.css' |
|
|
|
import { Tile, Vector as VectorLayer } from 'ol/layer' |
|
|
|
import LineString from 'ol/geom/LineString' |
|
|
|
import Point from 'ol/geom/Point' |
|
|
|
import { XYZ, Vector as VectorSource } from 'ol/source' |
|
|
|
import { transform, fromLonLat } from 'ol/proj' |
|
|
|
import { getVectorContext } from 'ol/render' |
|
|
|
import VectorContext from 'ol/render/VectorContext' |
|
|
|
import { Draw } from 'ol/interaction' |
|
|
|
import { toRaw } from '@vue/reactivity' |
|
|
|
import * as control from 'ol/control' |
|
|
|
import { styleList } from '../tools/style.js' |
|
|
|
|
|
|
|
// 视频组件 |
|
|
|
import VideoPlayer from '@/components/VideoPlayer/index.vue' |
|
|
|
export default defineComponent({ |
|
|
|
name: 'LiveDrawer', |
|
|
|
components: { BaseMap }, |
|
|
|
components: { VideoPlayer }, |
|
|
|
props: { |
|
|
|
/* 可见 */ |
|
|
|
visible: { |
|
|
@@ -30,7 +79,39 @@ export default defineComponent({ |
|
|
|
}, |
|
|
|
setup(props, { emit }) { |
|
|
|
const data = reactive({ |
|
|
|
|
|
|
|
mapData: null, |
|
|
|
view: null, |
|
|
|
trackLayer: null, |
|
|
|
trackInfo: null, |
|
|
|
// 轨迹数据 |
|
|
|
trackList: [ |
|
|
|
{ lng: '118.765591', lat: '32.050993' }, |
|
|
|
{ lng: '118.665591', lat: '32.150993' }, |
|
|
|
{ lng: '118.565591', lat: '32.070993' }, |
|
|
|
{ lng: '118.465591', lat: '32.020993' }, |
|
|
|
{ lng: '118.365591', lat: '32.110993' }, |
|
|
|
{ lng: '118.265591', lat: '32.080993' }, |
|
|
|
{ lng: '118.165591', lat: '32.150993' } |
|
|
|
], |
|
|
|
disdance: null, |
|
|
|
animating: null, |
|
|
|
detailData: { |
|
|
|
videoUrl: '', |
|
|
|
aiVideoUrl: '' |
|
|
|
}, |
|
|
|
videoShow: 'back', // 视频展示开关 |
|
|
|
videoInfo: { |
|
|
|
currentTime: 0, |
|
|
|
duration: 100 |
|
|
|
}, |
|
|
|
aiVideoInfo: { |
|
|
|
currentTime: 0, |
|
|
|
duration: 100 |
|
|
|
}, |
|
|
|
videoStatus: { |
|
|
|
status: 'init', |
|
|
|
aiStatus: 'init' |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
/* 获取抽屉的信息 */ |
|
|
@@ -41,13 +122,173 @@ export default defineComponent({ |
|
|
|
placement: 'right' |
|
|
|
} |
|
|
|
}) |
|
|
|
/* 获取原视频地址 */ |
|
|
|
const getVideoOptions = computed(() => { |
|
|
|
return { |
|
|
|
id: 'palyer-on', |
|
|
|
width: '400px', |
|
|
|
height: '324px', |
|
|
|
autoplay: false, |
|
|
|
source: data.detailData.videoUrl |
|
|
|
} |
|
|
|
}) |
|
|
|
/* 获取AI视频地址 */ |
|
|
|
const getVideoLiveOptions = computed(() => { |
|
|
|
return { |
|
|
|
id: 'palyer-ai-on', |
|
|
|
width: '400px', |
|
|
|
height: '324px', |
|
|
|
autoplay: false, |
|
|
|
source: data.detailData.aiVideoUrl |
|
|
|
} |
|
|
|
}) |
|
|
|
const handleOriginStatus = function(data) { |
|
|
|
data.videoStatus.status = data.status |
|
|
|
data.videoInfo = { |
|
|
|
...data |
|
|
|
} |
|
|
|
} |
|
|
|
const videoRef = ref(null) |
|
|
|
const aiVideoRef = ref(null) |
|
|
|
const handleOriginTime = function(data) { |
|
|
|
data.videoInfo = { |
|
|
|
...data |
|
|
|
} |
|
|
|
const len = Math.abs(data.currentTime - data.aiVideoInfo.currentTime) |
|
|
|
if (data.status === 'skip' || len > 3) { |
|
|
|
aiVideoRef.value.seekTime(data.currentTime) |
|
|
|
} |
|
|
|
} |
|
|
|
const handleAiTime = function(data) { |
|
|
|
data.aiVideoInfo = { |
|
|
|
...data |
|
|
|
} |
|
|
|
const len = Math.abs(data.currentTime - data.videoInfo.currentTime) |
|
|
|
if (data.status === 'skip' || len > 3) { |
|
|
|
videoRef.value.seekTime(data.currentTime) |
|
|
|
} |
|
|
|
} |
|
|
|
/* 播放视频 */ |
|
|
|
const startAllVideo = function() { |
|
|
|
videoRef.value.playVideo() |
|
|
|
aiVideoRef.value.playVideo() |
|
|
|
} |
|
|
|
|
|
|
|
watch(() => data.videoStatus, (value) => { |
|
|
|
if (value.status === 'ready' && value.aiStatus === 'ready') { |
|
|
|
startAllVideo() |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
initMap() |
|
|
|
// 加载航线 |
|
|
|
initTrack(formatTradeList(data.trackList), 'trackLayer', 'route') |
|
|
|
}) |
|
|
|
|
|
|
|
/* 初始化地图 */ |
|
|
|
const initMap = function() { |
|
|
|
const layers = [ |
|
|
|
new Tile({ |
|
|
|
visible: true, |
|
|
|
source: new XYZ({ |
|
|
|
url: 'https://tianditu.t-aaron.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=f82bb2fd8115c7fb576a8b2fcf738523' |
|
|
|
}) |
|
|
|
}) |
|
|
|
] |
|
|
|
|
|
|
|
data.view = new View({ |
|
|
|
maxZoom: 18, |
|
|
|
zoom: 5, |
|
|
|
center: transform([118.837886, 32.057175], 'EPSG:4326', 'EPSG:3857') |
|
|
|
}) |
|
|
|
data.mapData = new Map({ |
|
|
|
layers: layers, |
|
|
|
target: 'history-map', |
|
|
|
view: data.view, |
|
|
|
controls: control.defaults({ |
|
|
|
attribution: false, |
|
|
|
rotate: false, |
|
|
|
zoom: false |
|
|
|
}) |
|
|
|
}) |
|
|
|
data.mapData.render() |
|
|
|
} |
|
|
|
|
|
|
|
/* 处理轨迹列表 */ |
|
|
|
const formatTradeList = function(trackList) { |
|
|
|
return trackList.map((item) => |
|
|
|
fromLonLat([parseFloat(item.lng), parseFloat(item.lat)]) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
/* 初始化轨迹 */ |
|
|
|
const initTrack = function(coordinate, layer, routeType) { |
|
|
|
let vectorLayer = null |
|
|
|
const route = new LineString(coordinate) |
|
|
|
const routeFeature = new Feature({ |
|
|
|
type: routeType, |
|
|
|
geometry: route |
|
|
|
}) |
|
|
|
const startMarker = new Feature({ |
|
|
|
type: 'icon', |
|
|
|
geometry: new Point(route.getFirstCoordinate()) |
|
|
|
}) |
|
|
|
const position = startMarker.getGeometry().clone() |
|
|
|
const geoMarker = new Feature({ |
|
|
|
type: 'geoMarker', |
|
|
|
geometry: position |
|
|
|
}) |
|
|
|
geoMarker.setProperties({ type: 'geoMarker' }, false) |
|
|
|
if (data.trackLayer) { |
|
|
|
data.trackLayer.setSource( |
|
|
|
new VectorSource({ |
|
|
|
features: [geoMarker, routeFeature] |
|
|
|
}) |
|
|
|
) |
|
|
|
vectorLayer = data.trackLayer |
|
|
|
} else { |
|
|
|
vectorLayer = new VectorLayer({ |
|
|
|
source: new VectorSource({ |
|
|
|
features: [geoMarker, routeFeature] |
|
|
|
}), |
|
|
|
style: function(feature) { |
|
|
|
return styleList[feature.get('type')] |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
data[layer] = vectorLayer |
|
|
|
data.mapData.addLayer(data[layer]) |
|
|
|
data.view.fit(route, { padding: [50, 50, 50, 50] }) |
|
|
|
data[layer].on('postrender', moveFeature.bind()) |
|
|
|
|
|
|
|
function moveFeature(event) { |
|
|
|
/* 计算飞行的百分比 */ |
|
|
|
var distance = data.videoInfo.currentTime / data.videoInfo.duration |
|
|
|
const currentCoordinate = route.getCoordinateAt(distance) |
|
|
|
position.setCoordinates(currentCoordinate) |
|
|
|
geoMarker.setGeometry(null) |
|
|
|
const vectorContext = getVectorContext(event) |
|
|
|
vectorContext.setStyle(styleList['geoMarker']) |
|
|
|
vectorContext.drawGeometry(position) |
|
|
|
data.mapData.render() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function handleDrawerColse() { |
|
|
|
emit('update:visible', false) |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...toRefs(data), |
|
|
|
videoRef, |
|
|
|
aiVideoRef, |
|
|
|
getDrawerOptions, |
|
|
|
handleOriginStatus, |
|
|
|
handleOriginTime, |
|
|
|
handleAiTime, |
|
|
|
getVideoOptions, |
|
|
|
getVideoLiveOptions, |
|
|
|
handleDrawerColse |
|
|
|
} |
|
|
|
} |
|
|
@@ -58,4 +299,86 @@ export default defineComponent({ |
|
|
|
.n-button+.n-button{ |
|
|
|
margin-left: 30px; |
|
|
|
} |
|
|
|
.main_container { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
position: relative; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
#history-map { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
} |
|
|
|
/* 视频 */ |
|
|
|
.videobox { |
|
|
|
width: 800px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
justify-content: flex-start; |
|
|
|
align-items: flex-end; |
|
|
|
position: absolute; |
|
|
|
top: 10px; |
|
|
|
right: 10px; |
|
|
|
} |
|
|
|
.flagbox { |
|
|
|
width: 800px; |
|
|
|
height: 27px; |
|
|
|
background: #fff; |
|
|
|
padding: 0 10px; |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
.flagbox_open { |
|
|
|
width: 800px; |
|
|
|
animation: openbox 1s; |
|
|
|
} |
|
|
|
.flaxbox_back { |
|
|
|
width: 116px; |
|
|
|
animation: backbox 1s; |
|
|
|
} |
|
|
|
@keyframes backbox { |
|
|
|
from { |
|
|
|
width: 800px; |
|
|
|
} |
|
|
|
to { |
|
|
|
width: 116px; |
|
|
|
} |
|
|
|
} |
|
|
|
@keyframes openbox { |
|
|
|
from { |
|
|
|
width: 116px; |
|
|
|
} |
|
|
|
to { |
|
|
|
width: 800px; |
|
|
|
} |
|
|
|
} |
|
|
|
.video_content { |
|
|
|
width: 800px; |
|
|
|
display: flex; |
|
|
|
} |
|
|
|
.video_show { |
|
|
|
opacity: 1; |
|
|
|
animation: showIt 1s; |
|
|
|
} |
|
|
|
@keyframes showIt { |
|
|
|
from { |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
to { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
} |
|
|
|
.video_hidden { |
|
|
|
opacity: 0; |
|
|
|
animation: hiddenIt 1s; |
|
|
|
} |
|
|
|
@keyframes hiddenIt { |
|
|
|
from { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
to { |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |