|
|
@ -45,6 +45,7 @@ |
|
|
@copy-route="handleCopyRouteFromMenu" |
|
|
@copy-route="handleCopyRouteFromMenu" |
|
|
@edit-platform="openEditPlatformDialog" |
|
|
@edit-platform="openEditPlatformDialog" |
|
|
@power-zone="openPowerZoneDialog" |
|
|
@power-zone="openPowerZoneDialog" |
|
|
|
|
|
@launch-missile="openLaunchMissileDialog" |
|
|
/> |
|
|
/> |
|
|
|
|
|
|
|
|
<!-- 定位弹窗 --> |
|
|
<!-- 定位弹窗 --> |
|
|
@ -142,6 +143,43 @@ |
|
|
</span> |
|
|
</span> |
|
|
</el-dialog> |
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 导弹发射参数弹窗(可拖动) --> |
|
|
|
|
|
<el-dialog |
|
|
|
|
|
ref="missileDialog" |
|
|
|
|
|
title="导弹发射参数" |
|
|
|
|
|
:visible.sync="missileDialogVisible" |
|
|
|
|
|
:modal="false" |
|
|
|
|
|
width="300px" |
|
|
|
|
|
custom-class="missile-params-dialog dialog-draggable" |
|
|
|
|
|
@open="onMissileDialogOpen" |
|
|
|
|
|
> |
|
|
|
|
|
<el-form :model="missileForm" label-width="80px" size="small"> |
|
|
|
|
|
<el-form-item label="角度(°)"> |
|
|
|
|
|
<el-input-number |
|
|
|
|
|
v-model="missileForm.angle" |
|
|
|
|
|
:min="-180" |
|
|
|
|
|
:max="180" |
|
|
|
|
|
controls-position="right" |
|
|
|
|
|
style="width: 100%;" |
|
|
|
|
|
@change="updateMissilePreview" |
|
|
|
|
|
/> |
|
|
|
|
|
</el-form-item> |
|
|
|
|
|
<el-form-item label="距离(km)"> |
|
|
|
|
|
<el-input-number |
|
|
|
|
|
v-model="missileForm.distance" |
|
|
|
|
|
:min="1" |
|
|
|
|
|
controls-position="right" |
|
|
|
|
|
style="width: 100%;" |
|
|
|
|
|
@change="updateMissilePreview" |
|
|
|
|
|
/> |
|
|
|
|
|
</el-form-item> |
|
|
|
|
|
</el-form> |
|
|
|
|
|
<span slot="footer" class="dialog-footer"> |
|
|
|
|
|
<el-button @click="missileDialogVisible = false">取 消</el-button> |
|
|
|
|
|
<el-button type="primary" @click="launchMissile">确 定</el-button> |
|
|
|
|
|
</span> |
|
|
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
<!-- 地图右下角:比例尺 + 经纬度 --> |
|
|
<!-- 地图右下角:比例尺 + 经纬度 --> |
|
|
<div class="map-info-panel"> |
|
|
<div class="map-info-panel"> |
|
|
<div class="scale-bar" @click="handleScaleClick"> |
|
|
<div class="scale-bar" @click="handleScaleClick"> |
|
|
@ -169,7 +207,7 @@ import RadiusDialog from '../dialogs/RadiusDialog.vue' |
|
|
import axios from 'axios' |
|
|
import axios from 'axios' |
|
|
import request from '@/utils/request' |
|
|
import request from '@/utils/request' |
|
|
import { getToken } from '@/utils/auth' |
|
|
import { getToken } from '@/utils/auth' |
|
|
import { savePlatformStyle, getRoutes, getPlatformStyle } from '@/api/system/routes' |
|
|
import { savePlatformStyle, getRoutes, getPlatformStyle, getMissileParams, saveMissileParams } from '@/api/system/routes' |
|
|
|
|
|
|
|
|
export default { |
|
|
export default { |
|
|
name: 'CesiumMap', |
|
|
name: 'CesiumMap', |
|
|
@ -207,6 +245,16 @@ export default { |
|
|
routeLocked: { |
|
|
routeLocked: { |
|
|
type: Object, |
|
|
type: Object, |
|
|
default: () => ({}) |
|
|
default: () => ({}) |
|
|
|
|
|
}, |
|
|
|
|
|
/** 推演时间轴:相对 K 的分钟数(用于导弹按时间轴显示/隐藏与位置插值) */ |
|
|
|
|
|
deductionTimeMinutes: { |
|
|
|
|
|
type: Number, |
|
|
|
|
|
default: 0 |
|
|
|
|
|
}, |
|
|
|
|
|
/** 当前房间 ID(用于 Redis 存储导弹参数 key:房间+航线+平台) */ |
|
|
|
|
|
roomId: { |
|
|
|
|
|
type: [String, Number], |
|
|
|
|
|
default: null |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
watch: { |
|
|
watch: { |
|
|
@ -231,6 +279,21 @@ export default { |
|
|
handler(newVal) { |
|
|
handler(newVal) { |
|
|
this.updateCoordinatesDisplay() |
|
|
this.updateCoordinatesDisplay() |
|
|
} |
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
deductionTimeMinutes: { |
|
|
|
|
|
immediate: true, |
|
|
|
|
|
handler(val) { |
|
|
|
|
|
this.currentDeductionMinutes = (val != null && Number.isFinite(Number(val))) ? Number(val) : 0 |
|
|
|
|
|
this.updateMissilesByTime() |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
missileDialogVisible(val) { |
|
|
|
|
|
if (!val) { |
|
|
|
|
|
this.clearMissilePreview() |
|
|
|
|
|
if (this._missileDialogDragCleanup) { |
|
|
|
|
|
this._missileDialogDragCleanup() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
data() { |
|
|
data() { |
|
|
@ -288,6 +351,19 @@ export default { |
|
|
radius: 1000, |
|
|
radius: 1000, |
|
|
color: 'rgba(255, 0, 0, 0.3)' |
|
|
color: 'rgba(255, 0, 0, 0.3)' |
|
|
}, |
|
|
}, |
|
|
|
|
|
missileDialogVisible: false, |
|
|
|
|
|
missileForm: { |
|
|
|
|
|
angle: 0, |
|
|
|
|
|
distance: 100, |
|
|
|
|
|
platformHeadingDeg: 0 |
|
|
|
|
|
}, |
|
|
|
|
|
/** 导弹预览:弹窗打开时显示的轨迹线与朝向图标,关闭时移除 */ |
|
|
|
|
|
missilePreviewLine: null, |
|
|
|
|
|
missilePreviewIcon: null, |
|
|
|
|
|
/** 导弹实体列表:每项 { entity, launchKMinutes, endKMinutes, startPoint, endPoint },用于按推演时间轴显示/隐藏与插值位置 */ |
|
|
|
|
|
missileEntities: [], |
|
|
|
|
|
/** 当前推演时间(相对K的分钟数),从 prop 同步,供导弹 CallbackProperty 读取确保拿到最新值 */ |
|
|
|
|
|
currentDeductionMinutes: 0, |
|
|
// 航线飞机标牌显示状态:routeId -> true 显示 / false 隐藏,不设则默认显示 |
|
|
// 航线飞机标牌显示状态:routeId -> true 显示 / false 隐藏,不设则默认显示 |
|
|
routeLabelVisible: {}, |
|
|
routeLabelVisible: {}, |
|
|
// 航线飞机标牌样式:routeId -> { fontSize, fontColor } |
|
|
// 航线飞机标牌样式:routeId -> { fontSize, fontColor } |
|
|
@ -2434,6 +2510,334 @@ export default { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
openLaunchMissileDialog() { |
|
|
|
|
|
this.contextMenu.visible = false |
|
|
|
|
|
const entityData = this.contextMenu.entityData |
|
|
|
|
|
if (entityData && entityData.entity) { |
|
|
|
|
|
if (entityData.lat == null || entityData.lng == null) { |
|
|
|
|
|
const pos = entityData.entity.position && entityData.entity.position.getValue |
|
|
|
|
|
? entityData.entity.position.getValue(Cesium.JulianDate.now()) |
|
|
|
|
|
: null |
|
|
|
|
|
if (pos) { |
|
|
|
|
|
const ll = this.cartesianToLatLng(pos) |
|
|
|
|
|
if (ll) { |
|
|
|
|
|
entityData.lat = ll.lat |
|
|
|
|
|
entityData.lng = ll.lng |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
const rot = entityData.entity.billboard && entityData.entity.billboard.rotation |
|
|
|
|
|
const rotVal = rot && rot.getValue ? rot.getValue(Cesium.JulianDate.now()) : rot |
|
|
|
|
|
if (rotVal != null && Number.isFinite(Number(rotVal))) { |
|
|
|
|
|
const headingRad = Math.PI / 2 - Number(rotVal) |
|
|
|
|
|
this.missileForm.platformHeadingDeg = ((headingRad * 180 / Math.PI) % 360 + 360) % 360 |
|
|
|
|
|
} else { |
|
|
|
|
|
this.missileForm.platformHeadingDeg = 0 |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
this.missileForm.platformHeadingDeg = 0 |
|
|
|
|
|
} |
|
|
|
|
|
this.missileDialogVisible = true |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
this.updateMissilePreview() |
|
|
|
|
|
this.fetchMissileParamsFromRedis() |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 从 Redis 拉取当前房间+航线+平台的导弹参数并回填表单(支持返回数组时取最后一条作为默认) */ |
|
|
|
|
|
fetchMissileParamsFromRedis() { |
|
|
|
|
|
if (!this.roomId || !this.contextMenu.entityData || this.contextMenu.entityData.type !== 'routePlatform') return |
|
|
|
|
|
const routeId = this.contextMenu.entityData.routeId |
|
|
|
|
|
const platformId = this.contextMenu.entityData.platformId != null ? this.contextMenu.entityData.platformId : 0 |
|
|
|
|
|
getMissileParams({ roomId: this.roomId, routeId, platformId }).then(res => { |
|
|
|
|
|
let data = res.data |
|
|
|
|
|
if (Array.isArray(data) && data.length > 0) data = data[data.length - 1] |
|
|
|
|
|
if (data && (data.angle != null || data.distance != null)) { |
|
|
|
|
|
if (data.angle != null) this.missileForm.angle = Number(data.angle) |
|
|
|
|
|
if (data.distance != null) this.missileForm.distance = Number(data.distance) |
|
|
|
|
|
this.updateMissilePreview() |
|
|
|
|
|
} |
|
|
|
|
|
}).catch(() => {}) |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 导弹参数弹窗打开后绑定标题栏拖动 */ |
|
|
|
|
|
onMissileDialogOpen() { |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
const dialog = document.querySelector('.missile-params-dialog') |
|
|
|
|
|
const wrapper = dialog && dialog.closest('.el-dialog__wrapper') |
|
|
|
|
|
if (!wrapper || !dialog) return |
|
|
|
|
|
const header = wrapper.querySelector('.el-dialog__header') |
|
|
|
|
|
if (!header) return |
|
|
|
|
|
header.style.cursor = 'move' |
|
|
|
|
|
let startX, startY, startLeft, startTop |
|
|
|
|
|
const onMouseDown = (e) => { |
|
|
|
|
|
if (e.button !== 0) return |
|
|
|
|
|
startX = e.clientX |
|
|
|
|
|
startY = e.clientY |
|
|
|
|
|
const rect = wrapper.getBoundingClientRect() |
|
|
|
|
|
startLeft = rect.left |
|
|
|
|
|
startTop = rect.top |
|
|
|
|
|
wrapper.style.position = 'fixed' |
|
|
|
|
|
wrapper.style.left = startLeft + 'px' |
|
|
|
|
|
wrapper.style.top = startTop + 'px' |
|
|
|
|
|
wrapper.style.margin = '0' |
|
|
|
|
|
document.addEventListener('mousemove', onMouseMove) |
|
|
|
|
|
document.addEventListener('mouseup', onMouseUp) |
|
|
|
|
|
} |
|
|
|
|
|
const onMouseMove = (e) => { |
|
|
|
|
|
const dx = e.clientX - startX |
|
|
|
|
|
const dy = e.clientY - startY |
|
|
|
|
|
wrapper.style.left = (startLeft + dx) + 'px' |
|
|
|
|
|
wrapper.style.top = (startTop + dy) + 'px' |
|
|
|
|
|
startLeft += dx |
|
|
|
|
|
startTop += dy |
|
|
|
|
|
startX = e.clientX |
|
|
|
|
|
startY = e.clientY |
|
|
|
|
|
} |
|
|
|
|
|
const onMouseUp = () => { |
|
|
|
|
|
document.removeEventListener('mousemove', onMouseMove) |
|
|
|
|
|
document.removeEventListener('mouseup', onMouseUp) |
|
|
|
|
|
} |
|
|
|
|
|
header.addEventListener('mousedown', onMouseDown) |
|
|
|
|
|
this._missileDialogDragCleanup = () => { |
|
|
|
|
|
header.removeEventListener('mousedown', onMouseDown) |
|
|
|
|
|
header.style.cursor = '' |
|
|
|
|
|
delete this._missileDialogDragCleanup |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 清除导弹预览(轨迹线 + 朝向图标) */ |
|
|
|
|
|
clearMissilePreview() { |
|
|
|
|
|
if (this.viewer && this.viewer.entities) { |
|
|
|
|
|
if (this.missilePreviewLine) { |
|
|
|
|
|
this.viewer.entities.remove(this.missilePreviewLine) |
|
|
|
|
|
this.missilePreviewLine = null |
|
|
|
|
|
} |
|
|
|
|
|
if (this.missilePreviewIcon) { |
|
|
|
|
|
this.viewer.entities.remove(this.missilePreviewIcon) |
|
|
|
|
|
this.missilePreviewIcon = null |
|
|
|
|
|
} |
|
|
|
|
|
if (this.viewer.scene) this.viewer.scene.requestRender() |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 根据当前角度、距离与平台位置/航向更新导弹预览(弹窗打开时显示) */ |
|
|
|
|
|
updateMissilePreview() { |
|
|
|
|
|
if (!this.missileDialogVisible || !this.viewer || !this.viewer.entities) { |
|
|
|
|
|
this.clearMissilePreview() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
const entityData = this.contextMenu.entityData |
|
|
|
|
|
if (!entityData || entityData.lat == null || entityData.lng == null) { |
|
|
|
|
|
this.clearMissilePreview() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
const platformHeadingDeg = Number(this.missileForm.platformHeadingDeg) || 0 |
|
|
|
|
|
const angle = Number(this.missileForm.angle) || 0 |
|
|
|
|
|
const distance = Number(this.missileForm.distance) || 100 |
|
|
|
|
|
const actualBearingDeg = ((platformHeadingDeg + angle) % 360 + 360) % 360 |
|
|
|
|
|
const brng = Cesium.Math.toRadians(actualBearingDeg) |
|
|
|
|
|
const R = 6371000 |
|
|
|
|
|
const d = distance * 1000 |
|
|
|
|
|
const lat1 = Cesium.Math.toRadians(Number(entityData.lat)) |
|
|
|
|
|
const lon1 = Cesium.Math.toRadians(Number(entityData.lng)) |
|
|
|
|
|
const lat2 = Math.asin(Math.sin(lat1) * Math.cos(d / R) + Math.cos(lat1) * Math.sin(d / R) * Math.cos(brng)) |
|
|
|
|
|
const lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d / R) * Math.cos(lat1), Math.cos(d / R) - Math.sin(lat1) * Math.sin(lat2)) |
|
|
|
|
|
const startPoint = Cesium.Cartesian3.fromDegrees(Number(entityData.lng), Number(entityData.lat), 1000) |
|
|
|
|
|
const endPoint = Cesium.Cartesian3.fromDegrees(Cesium.Math.toDegrees(lon2), Cesium.Math.toDegrees(lat2), 1000) |
|
|
|
|
|
this.clearMissilePreview() |
|
|
|
|
|
this.missilePreviewLine = this.viewer.entities.add({ |
|
|
|
|
|
polyline: { |
|
|
|
|
|
positions: [startPoint, endPoint], |
|
|
|
|
|
width: 3, |
|
|
|
|
|
material: Cesium.Color.PURPLE.withAlpha(0.8) |
|
|
|
|
|
}, |
|
|
|
|
|
show: true |
|
|
|
|
|
}) |
|
|
|
|
|
this.missilePreviewIcon = this.viewer.entities.add({ |
|
|
|
|
|
position: startPoint, |
|
|
|
|
|
billboard: { |
|
|
|
|
|
image: require('@/assets/images/missile.png'), |
|
|
|
|
|
width: 28, |
|
|
|
|
|
height: 28, |
|
|
|
|
|
rotation: -brng |
|
|
|
|
|
}, |
|
|
|
|
|
show: true |
|
|
|
|
|
}) |
|
|
|
|
|
if (this.viewer.scene) this.viewer.scene.requestRender() |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
launchMissile() { |
|
|
|
|
|
this.missileDialogVisible = false |
|
|
|
|
|
this.clearMissilePreview() |
|
|
|
|
|
const angle = this.missileForm.angle |
|
|
|
|
|
const distance = this.missileForm.distance |
|
|
|
|
|
const entityData = this.contextMenu.entityData |
|
|
|
|
|
|
|
|
|
|
|
if (!entityData) { |
|
|
|
|
|
this.$message.error('无法获取发射平台信息') |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if (entityData.lat == null || entityData.lng == null) { |
|
|
|
|
|
if (entityData.entity && entityData.entity.position) { |
|
|
|
|
|
const pos = entityData.entity.position.getValue(Cesium.JulianDate.now()) |
|
|
|
|
|
if (pos) { |
|
|
|
|
|
const ll = this.cartesianToLatLng(pos) |
|
|
|
|
|
if (ll) { |
|
|
|
|
|
entityData.lat = ll.lat |
|
|
|
|
|
entityData.lng = ll.lng |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (entityData.lat == null || entityData.lng == null) { |
|
|
|
|
|
this.$message.error('无法获取平台位置信息') |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const launchK = Number.isFinite(Number(this.deductionTimeMinutes)) ? Number(this.deductionTimeMinutes) : 0 |
|
|
|
|
|
if (this.roomId != null && entityData.routeId != null) { |
|
|
|
|
|
const platformId = entityData.platformId != null ? entityData.platformId : 0 |
|
|
|
|
|
saveMissileParams({ |
|
|
|
|
|
roomId: this.roomId, |
|
|
|
|
|
routeId: entityData.routeId, |
|
|
|
|
|
platformId, |
|
|
|
|
|
angle, |
|
|
|
|
|
distance, |
|
|
|
|
|
launchTimeMinutesFromK: launchK, |
|
|
|
|
|
startLng: entityData.lng, |
|
|
|
|
|
startLat: entityData.lat, |
|
|
|
|
|
platformHeadingDeg: Number(this.missileForm.platformHeadingDeg) || 0 |
|
|
|
|
|
}).catch(() => {}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const platformHeadingDeg = Number(this.missileForm.platformHeadingDeg) || 0 |
|
|
|
|
|
const actualBearingDeg = ((platformHeadingDeg + Number(angle)) % 360 + 360) % 360 |
|
|
|
|
|
const R = 6371000 |
|
|
|
|
|
const d = Number(distance) * 1000 |
|
|
|
|
|
const brng = Cesium.Math.toRadians(actualBearingDeg) |
|
|
|
|
|
const lat1 = Cesium.Math.toRadians(Number(entityData.lat)) |
|
|
|
|
|
const lon1 = Cesium.Math.toRadians(Number(entityData.lng)) |
|
|
|
|
|
|
|
|
|
|
|
const lat2 = Math.asin(Math.sin(lat1) * Math.cos(d / R) + |
|
|
|
|
|
Math.cos(lat1) * Math.sin(d / R) * Math.cos(brng)) |
|
|
|
|
|
const lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d / R) * Math.cos(lat1), |
|
|
|
|
|
Math.cos(d / R) - Math.sin(lat1) * Math.sin(lat2)) |
|
|
|
|
|
|
|
|
|
|
|
const endLat = Cesium.Math.toDegrees(lat2) |
|
|
|
|
|
const endLng = Cesium.Math.toDegrees(lon2) |
|
|
|
|
|
|
|
|
|
|
|
const durationSec = d / 1000 |
|
|
|
|
|
const durationMinutes = durationSec / 60 |
|
|
|
|
|
const endK = launchK + durationMinutes |
|
|
|
|
|
|
|
|
|
|
|
const startPoint = Cesium.Cartesian3.fromDegrees(Number(entityData.lng), Number(entityData.lat), 1000) |
|
|
|
|
|
const endPoint = Cesium.Cartesian3.fromDegrees(endLng, endLat, 0) |
|
|
|
|
|
const headingRad = brng |
|
|
|
|
|
const billboardRotation = -headingRad |
|
|
|
|
|
|
|
|
|
|
|
const entity = this.viewer.entities.add({ |
|
|
|
|
|
position: startPoint, |
|
|
|
|
|
show: false, |
|
|
|
|
|
billboard: { |
|
|
|
|
|
image: require('@/assets/images/missile.png'), |
|
|
|
|
|
width: 32, |
|
|
|
|
|
height: 32, |
|
|
|
|
|
rotation: billboardRotation |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
this.missileEntities.push({ entity, launchKMinutes: launchK, endKMinutes: endK, startPoint, endPoint }) |
|
|
|
|
|
this.updateMissilesByTime() |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 根据当前推演时间直接更新所有导弹的显隐和位置。可由父组件调用并传入当前分钟数 minutesFromK,保证与时间轴一致 */ |
|
|
|
|
|
updateMissilesByTime(minutesFromK) { |
|
|
|
|
|
if (!this.viewer || !this.viewer.entities || !this.missileEntities.length) return |
|
|
|
|
|
const curNum = (minutesFromK !== undefined && minutesFromK !== null && Number.isFinite(Number(minutesFromK))) |
|
|
|
|
|
? Number(minutesFromK) |
|
|
|
|
|
: ((this.deductionTimeMinutes != null && Number.isFinite(Number(this.deductionTimeMinutes))) |
|
|
|
|
|
? Number(this.deductionTimeMinutes) |
|
|
|
|
|
: this.currentDeductionMinutes) |
|
|
|
|
|
const cur = Number.isFinite(curNum) ? curNum : 0 |
|
|
|
|
|
this.missileEntities.forEach(({ entity, launchKMinutes, endKMinutes, startPoint, endPoint }) => { |
|
|
|
|
|
if (cur < launchKMinutes) { |
|
|
|
|
|
entity.show = false |
|
|
|
|
|
entity.position = new Cesium.ConstantPositionProperty(startPoint) |
|
|
|
|
|
} else if (cur > endKMinutes) { |
|
|
|
|
|
entity.show = false |
|
|
|
|
|
entity.position = new Cesium.ConstantPositionProperty(endPoint) |
|
|
|
|
|
} else { |
|
|
|
|
|
entity.show = true |
|
|
|
|
|
const span = endKMinutes - launchKMinutes |
|
|
|
|
|
const t = span <= 0 ? 1 : Math.max(0, Math.min(1, (cur - launchKMinutes) / span)) |
|
|
|
|
|
const pos = Cesium.Cartesian3.lerp(startPoint, endPoint, t, new Cesium.Cartesian3()) |
|
|
|
|
|
entity.position = new Cesium.ConstantPositionProperty(pos) |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
if (this.viewer.scene) this.viewer.scene.requestRender() |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 从 Redis 加载指定房间+航线列表的导弹数据并创建实体(会先清空当前导弹实体) |
|
|
|
|
|
* routePlatforms: [{ routeId, platformId }] |
|
|
|
|
|
*/ |
|
|
|
|
|
async loadMissilesFromRedis(roomId, routePlatforms) { |
|
|
|
|
|
if (!this.viewer || !this.viewer.entities || !roomId || !Array.isArray(routePlatforms) || routePlatforms.length === 0) return |
|
|
|
|
|
while (this.missileEntities.length) { |
|
|
|
|
|
const { entity } = this.missileEntities.pop() |
|
|
|
|
|
this.viewer.entities.remove(entity) |
|
|
|
|
|
} |
|
|
|
|
|
for (const { routeId, platformId } of routePlatforms) { |
|
|
|
|
|
try { |
|
|
|
|
|
const res = await getMissileParams({ roomId, routeId, platformId: platformId != null ? platformId : 0 }) |
|
|
|
|
|
let data = res.data |
|
|
|
|
|
if (!data) continue |
|
|
|
|
|
const arr = Array.isArray(data) ? data : [data] |
|
|
|
|
|
for (const item of arr) { |
|
|
|
|
|
const launchK = item.launchTimeMinutesFromK != null ? Number(item.launchTimeMinutesFromK) : null |
|
|
|
|
|
const startLng = item.startLng != null ? Number(item.startLng) : null |
|
|
|
|
|
const startLat = item.startLat != null ? Number(item.startLat) : null |
|
|
|
|
|
if (launchK == null || startLng == null || startLat == null) continue |
|
|
|
|
|
const angle = Number(item.angle) || 0 |
|
|
|
|
|
const distance = Number(item.distance) || 100 |
|
|
|
|
|
const platformHeadingDeg = Number(item.platformHeadingDeg) || 0 |
|
|
|
|
|
const actualBearingDeg = ((platformHeadingDeg + angle) % 360 + 360) % 360 |
|
|
|
|
|
const R = 6371000 |
|
|
|
|
|
const d = distance * 1000 |
|
|
|
|
|
const brng = Cesium.Math.toRadians(actualBearingDeg) |
|
|
|
|
|
const lat1 = Cesium.Math.toRadians(startLat) |
|
|
|
|
|
const lon1 = Cesium.Math.toRadians(startLng) |
|
|
|
|
|
const lat2 = Math.asin(Math.sin(lat1) * Math.cos(d / R) + Math.cos(lat1) * Math.sin(d / R) * Math.cos(brng)) |
|
|
|
|
|
const lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d / R) * Math.cos(lat1), Math.cos(d / R) - Math.sin(lat1) * Math.sin(lat2)) |
|
|
|
|
|
const endLng = Cesium.Math.toDegrees(lon2) |
|
|
|
|
|
const endLat = Cesium.Math.toDegrees(lat2) |
|
|
|
|
|
const durationSec = d / 1000 |
|
|
|
|
|
const durationMinutes = durationSec / 60 |
|
|
|
|
|
const endK = launchK + durationMinutes |
|
|
|
|
|
const startPoint = Cesium.Cartesian3.fromDegrees(startLng, startLat, 1000) |
|
|
|
|
|
const endPoint = Cesium.Cartesian3.fromDegrees(endLng, endLat, 0) |
|
|
|
|
|
const billboardRotation = -brng |
|
|
|
|
|
const entity = this.viewer.entities.add({ |
|
|
|
|
|
position: startPoint, |
|
|
|
|
|
show: false, |
|
|
|
|
|
billboard: { |
|
|
|
|
|
image: require('@/assets/images/missile.png'), |
|
|
|
|
|
width: 32, |
|
|
|
|
|
height: 32, |
|
|
|
|
|
rotation: billboardRotation |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
this.missileEntities.push({ entity, launchKMinutes: launchK, endKMinutes: endK, startPoint, endPoint }) |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// ignore per-platform errors |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
this.updateMissilesByTime(this.deductionTimeMinutes != null ? this.deductionTimeMinutes : 0) |
|
|
|
|
|
if (this.viewer.scene) this.viewer.scene.requestRender() |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
checkCesiumLoaded() { |
|
|
checkCesiumLoaded() { |
|
|
if (typeof Cesium === 'undefined') { |
|
|
if (typeof Cesium === 'undefined') { |
|
|
console.error('Cesium未加载,请检查CDN链接'); |
|
|
console.error('Cesium未加载,请检查CDN链接'); |
|
|
@ -2791,6 +3195,19 @@ export default { |
|
|
platformName, |
|
|
platformName, |
|
|
labelVisible: this.routeLabelVisible[routeId] !== false |
|
|
labelVisible: this.routeLabelVisible[routeId] !== false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 尝试获取位置信息,以便后续使用 |
|
|
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
|
|
if (pickedEntity.position) { |
|
|
|
|
|
const pos = pickedEntity.position.getValue(now); |
|
|
|
|
|
if (pos) { |
|
|
|
|
|
const ll = this.cartesianToLatLng(pos); |
|
|
|
|
|
if (ll) { |
|
|
|
|
|
entityData.lat = ll.lat; |
|
|
|
|
|
entityData.lng = ll.lng; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
if (!entityData) { |
|
|
if (!entityData) { |
|
|
// 查找对应的实体数据 |
|
|
// 查找对应的实体数据 |
|
|
|