@@ -163,3 +163,33 @@ export function getQuestions(params) { | |||
params | |||
}) | |||
} | |||
/** | |||
* 控制无人机 | |||
* @param { } params | |||
* @returns | |||
*/ | |||
export function controlAir(params) { | |||
return request({ | |||
url: `/airport/drone/control`, | |||
method: 'POST', | |||
params | |||
}) | |||
} | |||
// 根据应急任务ID查询预警记录 | |||
export function emergencyRecord(params) { | |||
return request({ | |||
url: `/warning/record/one/by/emergencyMissionId`, | |||
method: 'GET', | |||
params | |||
}) | |||
} | |||
// 获取无人机数据 | |||
export function uavInfo(id) { | |||
return request({ | |||
url: `/airport/drone/data/${id}`, | |||
method: 'GET' | |||
}) | |||
} |
@@ -32,7 +32,7 @@ | |||
<span>飞行高度:</span> | |||
<n-slider v-model:value="flyHeight" :default-value="100" :format-tooltip="formatHeight" :max="600" @update:value="limitMin" /> | |||
</p> | |||
<p class="execute-btn" @click="test">立即执行</p> | |||
<p class="execute-btn" @click="executeNow">立即执行</p> | |||
<p class="alarm-title">任务记录</p> | |||
<div class="task-log"> | |||
<ul> | |||
@@ -94,6 +94,7 @@ | |||
import { reactive, toRefs, watch } from 'vue' | |||
import { EARLY_SOURCE, TASK_STATUS } from '@/utils/dictionary.js' | |||
import { getWarningRecord, getWarningInfo, ignoreWarning, confirmWarning, pointflight } from '@/api/dashboard/index.js' | |||
import { useInspectionStore } from '@/store/modules/inspection.js' | |||
// turf 用于简单的空间计算 | |||
import * as turf from '@turf/turf' | |||
export default { | |||
@@ -111,6 +112,7 @@ export default { | |||
}, | |||
emits: ['start'], | |||
setup(props, { emit }) { | |||
const inspectionStore = useInspectionStore() | |||
const data = reactive({ | |||
airpotOptions: [], | |||
airport: {}, | |||
@@ -242,7 +244,7 @@ export default { | |||
data.warningShow = false | |||
} | |||
const test = async() => { | |||
const executeNow = async() => { | |||
var airportName = '' | |||
data.airpotOptions?.map((item) => { | |||
if (item.value === data.airportId) { | |||
@@ -261,7 +263,7 @@ export default { | |||
if (res.code === 0) { | |||
getRecord(parseInt(data.warningInfo?.id)) | |||
handleExecute() | |||
handleExecute(res.data?.emergencyMissionId) | |||
} | |||
} | |||
@@ -290,6 +292,7 @@ export default { | |||
return value + 'm' | |||
} | |||
// 立即执行 传入应急任务id | |||
const handleExecute = (id) => { | |||
emit('start', id) | |||
} | |||
@@ -299,7 +302,7 @@ export default { | |||
formatHeight, | |||
checkAirport, | |||
closeAirport, | |||
test, | |||
executeNow, | |||
closeWarning, | |||
ignore, | |||
confirm, |
@@ -54,7 +54,7 @@ import { Point } from 'ol/geom' | |||
import { | |||
airportList, getWarning | |||
} from '@/api/dashboard/index.js' | |||
import { getQuestions } from '@/api/task/index.js' | |||
import { getTaskDetail } from '@/api/task/index.js' | |||
import { gcj02towgs84 } from '@/utils/coordinate-util.js' | |||
import AirInfo from './AirInfo.vue' | |||
import ProblemInfo from './ProblemInfo.vue' | |||
@@ -678,8 +678,15 @@ export default { | |||
monitorVideo.value?.disposeVideo() | |||
} | |||
const handleExecute = () => { | |||
data.drawerShow = true | |||
const handleExecute = async(value) => { | |||
const res = await getTaskDetail(value) | |||
if (res.code === 0) { | |||
inspectionStore.setList(res.data) | |||
} | |||
} | |||
const refreshRecord = () => { | |||
} | |||
watch(() => inspectionStore.getList, (val) => { | |||
@@ -716,7 +723,8 @@ export default { | |||
hideProblemInfo, | |||
getMaxZOverlay, | |||
Warning, | |||
handleExecute | |||
handleExecute, | |||
refreshRecord | |||
} | |||
} | |||
} |
@@ -14,6 +14,7 @@ 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 * as control from 'ol/control' | |||
import { styleList } from '@/utils/style.js' | |||
import { getTrackList } from '@/api/task/index.js' | |||
@@ -31,38 +32,22 @@ export default { | |||
const inspectionStore = useInspectionStore() | |||
const data = reactive({ | |||
map: null, | |||
view: null, | |||
drawerShow: false, | |||
trackLayer: null, | |||
trackInfo: null, | |||
// 轨迹数据 | |||
trackList: [] | |||
trackList: [], | |||
socket: null, | |||
liveTrackLayer: null | |||
}) | |||
console.log(inspectionStore.getList) | |||
const getMapOptions = computed(() => { | |||
return { | |||
id: props.id | |||
} | |||
}) | |||
// watch(() => props.idd, (value) => { | |||
// if (value) { | |||
// console.log('hhh', value) | |||
// } else { | |||
// console.log('hhh', value) | |||
// } | |||
// }) | |||
// if (props.ttest) { | |||
// getTrackData(props.ttest.missionId).then(res => { | |||
// if (res.trackList.length > 0) { | |||
// initTrack(formatTradeList(res.trackList), 'route') | |||
// } | |||
// }) | |||
// } | |||
/** | |||
* 初始化地图 | |||
*/ | |||
@@ -80,14 +65,16 @@ export default { | |||
params: { LAYERS: 'jiangning:town' } | |||
}) | |||
}) | |||
data.view = new View({ | |||
center: transform([118.773136, 31.828065], 'EPSG:4326', 'EPSG:3857'), | |||
zoom: 11, | |||
maxZoom: 17 | |||
}) | |||
data.map = new Map({ | |||
// 地图容器 | |||
target: props.id, | |||
view: new View({ | |||
center: transform([118.773136, 31.828065], 'EPSG:4326', 'EPSG:3857'), | |||
zoom: 11, | |||
maxZoom: 17 | |||
}), | |||
view: data.view, | |||
layers: [tdtImgMap], | |||
controls: control.defaults({ | |||
attribution: false, | |||
@@ -100,72 +87,123 @@ export default { | |||
} | |||
/* 获取轨迹数据 */ | |||
const getTrackData = async function(id) { | |||
const res = await getTrackList(id) | |||
const getTrackData = async function() { | |||
const res = await getTrackList(inspectionStore.getList.id) | |||
const trackList = res.data | |||
data.trackList = trackList | |||
// const trackList = data.trackList | |||
return Promise.resolve({ | |||
trackList | |||
}) | |||
} | |||
/* 处理轨迹列表 */ | |||
const formatTradeList = function(trackList) { | |||
return trackList.map((item) => | |||
fromLonLat([parseFloat(item.lng), parseFloat(item.lat)]) | |||
) | |||
if (inspectionStore.getList.id) { | |||
if (!data.map) { | |||
getTrackData().then(({ trackList }) => { | |||
setLiveTrack(formatTradeList(trackList)) | |||
}) | |||
} | |||
} | |||
/* 加载实时轨迹 */ | |||
const setLiveTrack = function(hisTrackList) { | |||
data.animating = false | |||
data.disdance = 0 | |||
var changeTrack | |||
if (hisTrackList.length > 1) { | |||
data.trackInfo = initTrack(hisTrackList, 'liveTrackLayer', 'route') | |||
if (data.trackInfo.vectorLayer) { | |||
data.liveTrackLayer.on('change', changeTrack) | |||
} | |||
} | |||
/* 轨迹改变 */ | |||
changeTrack = (event) => { | |||
try { | |||
const vectorContext = getVectorContext(event) | |||
vectorContext.setStyle(styleList['geoMarker']) | |||
VectorContext.drawGeometry(data.trackInfo.position) | |||
vectorContext.drawGeometry(data.trackInfo.route) | |||
data.map.render() | |||
} catch { | |||
console.log(event) | |||
} | |||
} | |||
// 两秒钟获取一次数据 | |||
data.socket = setInterval(() => { | |||
getTrackData().then(({ trackList }) => { | |||
const len = trackList.length | |||
const obj = trackList[len - 1] | |||
const coordinate = fromLonLat([obj?.lng, obj?.lat]) | |||
if (data.trackInfo) { | |||
data.trackInfo.route.appendCoordinate(coordinate) | |||
data.trackInfo.vectorLayer.getSource().changed() | |||
data.trackInfo.position.setCoordinates(coordinate) | |||
} | |||
}) | |||
}, 2000) | |||
} | |||
/* 初始化轨迹 */ | |||
const initTrack = function(coordinate, routeType) { | |||
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({ | |||
const endMarker = new Feature({ | |||
type: 'icon', | |||
geometry: new Point(route.getFirstCoordinate()) | |||
geometry: new Point(route.getLastCoordinate()) | |||
}) | |||
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')] | |||
} | |||
const position = endMarker.getGeometry().clone() | |||
if (routeType === 'route') { // 轨迹数据 | |||
const geoMarker = new Feature({ | |||
type: 'geoMarker', | |||
geometry: position | |||
}) | |||
geoMarker.setProperties({ type: 'geoMarker' }, false) | |||
if (data.liveTrackLayer) { | |||
data.liveTrackLayer.setSource( | |||
new VectorSource({ | |||
features: [geoMarker, routeFeature] | |||
}) | |||
) | |||
vectorLayer = data.liveTrackLayer | |||
} else { | |||
vectorLayer = new VectorLayer({ | |||
source: new VectorSource({ | |||
features: [geoMarker, routeFeature] | |||
}), | |||
style: function(feature) { | |||
return styleList[feature.get('type')] | |||
} | |||
}) | |||
} | |||
data[layer] = vectorLayer | |||
data.map.addLayer(data[layer]) | |||
data.view.fit(route, { padding: [50, 50, 50, 50] }) | |||
return { | |||
vectorLayer, | |||
geoMarker, | |||
route, | |||
position | |||
} | |||
} | |||
data.mapData.addLayer(vectorLayer) | |||
data.mapView.fit(route, { padding: [50, 50, 50, 50] }) | |||
vectorLayer.on('postrender', moveFeature.bind()) | |||
function moveFeature(event) { | |||
/* 计算飞行的百分比 */ | |||
const percent = data.videoInfo.currentTime / data.videoInfo.duration | |||
const currentCoordinate = route.getCoordinateAt(percent) | |||
position.setCoordinates(currentCoordinate) | |||
geoMarker.setGeometry(null) | |||
const vectorContext = getVectorContext(event) | |||
vectorContext.setStyle(styleList['geoMarker']) | |||
vectorContext.drawGeometry(position) | |||
data.mapData?.render() | |||
} | |||
} | |||
/* 处理轨迹列表 */ | |||
const formatTradeList = function(trackList) { | |||
return trackList.map((item) => | |||
fromLonLat([parseFloat(item.lng), parseFloat(item.lat)]) | |||
) | |||
} | |||
// 结束直播飞行 | |||
const endLiveFly = () => { | |||
clearInterval(data.socket) | |||
data.socket = null | |||
} | |||
onMounted(() => { | |||
@@ -173,11 +211,15 @@ export default { | |||
}) | |||
onBeforeUnmount(() => { | |||
clearInterval(data.socket) | |||
data.socket = null | |||
}) | |||
return { | |||
...toRefs(data), | |||
getMapOptions | |||
getMapOptions, | |||
endLiveFly, | |||
initTrack | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
<template> | |||
<n-drawer v-bind="getDrawerOptions" @update:show="handleDrawerColse"> | |||
<n-drawer-content closable title="报告详情"> | |||
<n-drawer-content closable title="机场调度"> | |||
<div ref="containerRef" class="warning__container"> | |||
<div class="warning__side"> | |||
@@ -48,32 +48,38 @@ | |||
</div> | |||
<div class="control__panel"> | |||
<SpeedChart :data="chartData" /> | |||
<ControlPanel v-if="showControl" mode="camera" @start="startOrder" @reset="endOrder" /> | |||
<ControlPanel v-if="showControl" mode="locus" @start="startOrder" @reset="endOrder" /> | |||
<!-- <SpeedChart :data="chartData" /> --> | |||
<ControlPanel v-if="showControl" mode="camera" @instruct="sendControl" /> | |||
<ControlPanel v-if="showControl" mode="locus" @instruct="sendControl" /> | |||
</div> | |||
</div> | |||
<div ref="mapRef" class="warn__back"> | |||
<Underlay /> | |||
<Underlay ref="baseMap" /> | |||
</div> | |||
<div ref="videoRef" class="inner"> | |||
111111111111 | |||
<VideoPlayer | |||
id="emergency-video" | |||
ref="emergencyVideo" | |||
style="position: absolute;left: 0;top: 0" | |||
/> | |||
</div> | |||
</n-drawer-content> | |||
</n-drawer> | |||
</template> | |||
<script> | |||
import { defineComponent, ref, reactive, toRefs, computed, watch, nextTick } from 'vue' | |||
import { defineComponent, ref, reactive, toRefs, computed, watch, nextTick, onBeforeUnmount } from 'vue' | |||
import Underlay from './Underlay.vue' | |||
import ControlPanel from './ControlPanel.vue' | |||
import SpeedChart from './SpeedChart.vue' | |||
import { useInspectionStore } from '@/store/modules/inspection.js' | |||
import VideoPlayer from '@/components/VideoPlayer/index.vue' | |||
import { controlAir, emergencyRecord, uavInfo } from '@/api/task/index.js' | |||
export default defineComponent({ | |||
name: 'WarningDrawer', | |||
components: { Underlay, ControlPanel, SpeedChart }, | |||
components: { Underlay, ControlPanel, SpeedChart, VideoPlayer }, | |||
props: { | |||
/* 可见 */ | |||
visible: { | |||
@@ -84,17 +90,24 @@ export default defineComponent({ | |||
data: { | |||
type: Object, | |||
default: () => {} | |||
}, | |||
id: { | |||
type: Number, | |||
default: 0 | |||
} | |||
}, | |||
emits: { | |||
'update:visible': null | |||
'update:visible': null, | |||
'refresh:id': null | |||
}, | |||
setup(props, { emit }) { | |||
const inspectionStore = useInspectionStore() | |||
const containerRef = ref() | |||
const sideRef = ref() | |||
const mapRef = ref() | |||
const baseMap = ref() | |||
const videoRef = ref() | |||
const emergencyVideo = ref() | |||
const data = reactive({ | |||
hasChanged: false, | |||
showControl: false, | |||
@@ -114,7 +127,23 @@ export default defineComponent({ | |||
/* 关闭抽屉 */ | |||
function handleDrawerColse() { | |||
emit('update:visible', false) | |||
inspectionStore.resetList() | |||
getEmergencyRecord(inspectionStore.getList.id) | |||
// inspectionStore.resetList() | |||
// 结束直播飞行 | |||
// baseMap.value?.endLiveFly() | |||
// 关闭视频播放 | |||
emergencyVideo.value?.disposeVideo() | |||
// emit('refresh:id') | |||
} | |||
const getEmergencyRecord = async(id) => { | |||
const res = await emergencyRecord({ | |||
emergencyMissionId: id | |||
}) | |||
if (res.code === 0) { | |||
console.log(res.data) | |||
} | |||
} | |||
const handleChange = () => { | |||
@@ -140,11 +169,7 @@ export default defineComponent({ | |||
} | |||
} | |||
const startOrder = (params) => { | |||
console.log(params) | |||
} | |||
const endOrder = (params) => { | |||
const sendControl = (params) => { | |||
console.log(params) | |||
} | |||
@@ -161,6 +186,25 @@ export default defineComponent({ | |||
} | |||
}) | |||
watch(() => inspectionStore.getList, (value) => { | |||
if (value?.aiplayUrl) { | |||
setTimeout(() => { | |||
initOriginPlayer(value) | |||
}, 3000) | |||
} | |||
}) | |||
/* 初始化播放器 */ | |||
function initOriginPlayer(value) { | |||
const origin = { | |||
width: '100%', | |||
height: '100%', | |||
source: value.aiplayUrl, | |||
isLive: true | |||
} | |||
emergencyVideo.value?.init(origin) | |||
} | |||
const handleOperate = () => { | |||
data.operate = data.operate === '悬停' ? '继续飞行' : '悬停' | |||
} | |||
@@ -178,12 +222,34 @@ export default defineComponent({ | |||
showIcon: false, | |||
content: `是否返航`, | |||
confirm: () => { | |||
emit('update:visible', false) | |||
airBack() | |||
} | |||
} | |||
) | |||
} | |||
/** | |||
* 飞机返仓 zhilin03 | |||
*/ | |||
const airBack = async() => { | |||
const res = await controlAir({ | |||
value: { | |||
'zhilin': '03', | |||
'taskId': inspectionStore.getList?.id, | |||
'airportId': inspectionStore.getList?.airportId, | |||
'msg': '飞行任务' | |||
} | |||
}) | |||
if (res.code === 0) { | |||
console.log('返航成功') | |||
} | |||
} | |||
onBeforeUnmount(() => { | |||
emergencyVideo.value?.disposeVideo() | |||
}) | |||
return { | |||
containerRef, | |||
sideRef, | |||
@@ -197,8 +263,9 @@ export default defineComponent({ | |||
handleControl, | |||
handleBack, | |||
fanhang, | |||
startOrder, | |||
endOrder | |||
sendControl, | |||
baseMap, | |||
emergencyVideo | |||
} | |||
} | |||
}) | |||
@@ -296,7 +363,6 @@ export default defineComponent({ | |||
position: absolute; | |||
width: calc(100% - 40px); | |||
height: calc(100% - 20px); | |||
background: blue; | |||
} | |||
.container__back{ |