Browse Source

飞机标牌

master
ctw 2 months ago
parent
commit
b0f5453e50
  1. 15
      ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
  2. 148
      ruoyi-ui/src/views/cesiumMap/index.vue
  3. 57
      ruoyi-ui/src/views/childRoom/index.vue

15
ruoyi-ui/src/views/cesiumMap/ContextMenu.vue

@ -1,12 +1,21 @@
<template> <template>
<div class="context-menu" v-if="visible" :style="positionStyle"> <div class="context-menu" v-if="visible" :style="positionStyle">
<div class="menu-section"> <div class="menu-section" v-if="!entityData || entityData.type !== 'routePlatform'">
<div class="menu-item" @click="handleDelete"> <div class="menu-item" @click="handleDelete">
<span class="menu-icon">🗑</span> <span class="menu-icon">🗑</span>
<span>删除</span> <span>删除</span>
</div> </div>
</div> </div>
<!-- 航线上飞机显示/隐藏标牌 -->
<div class="menu-section" v-if="entityData && entityData.type === 'routePlatform'">
<div class="menu-title">飞机标牌</div>
<div class="menu-item" @click="handleToggleRouteLabel">
<span class="menu-icon">🏷</span>
<span>{{ entityData.labelVisible ? '隐藏标牌' : '显示标牌' }}</span>
</div>
</div>
<!-- 线段特有选项 --> <!-- 线段特有选项 -->
<div class="menu-section" v-if="entityData.type === 'line' && !entityData.routeId"> <div class="menu-section" v-if="entityData.type === 'line' && !entityData.routeId">
<div class="menu-title">线段属性</div> <div class="menu-title">线段属性</div>
@ -354,6 +363,10 @@ export default {
this.$emit('edit-platform-heading') this.$emit('edit-platform-heading')
}, },
handleToggleRouteLabel() {
this.$emit('toggle-route-label')
},
toggleColorPicker(property) { toggleColorPicker(property) {
if (this.showColorPickerFor === property) { if (this.showColorPickerFor === property) {
this.showColorPickerFor = null this.showColorPickerFor = null

148
ruoyi-ui/src/views/cesiumMap/index.vue

@ -37,6 +37,7 @@
@edit-platform-position="openPlatformIconPositionDialog" @edit-platform-position="openPlatformIconPositionDialog"
@edit-platform-heading="openPlatformIconHeadingDialog" @edit-platform-heading="openPlatformIconHeadingDialog"
@show-transform-box="showPlatformIconTransformBox" @show-transform-box="showPlatformIconTransformBox"
@toggle-route-label="toggleRouteLabelVisibility"
/> />
<!-- 定位弹窗 --> <!-- 定位弹窗 -->
@ -168,6 +169,8 @@ export default {
position: { x: 0, y: 0 }, position: { x: 0, y: 0 },
entityData: null entityData: null
}, },
// 线routeId -> true / false
routeLabelVisible: {},
// //
defaultStyles: { defaultStyles: {
point: { color: '#FF0000', size: 12 }, point: { color: '#FF0000', size: 12 },
@ -888,12 +891,17 @@ export default {
if (existingLine) { if (existingLine) {
this.viewer.entities.remove(existingLine); this.viewer.entities.remove(existingLine);
} }
// 线 // 线
const platformBillboardId = `route-platform-${routeId}`; const platformBillboardId = `route-platform-${routeId}`;
const platformLabelId = `route-platform-label-${routeId}`;
const existingPlatform = this.viewer.entities.getById(platformBillboardId); const existingPlatform = this.viewer.entities.getById(platformBillboardId);
if (existingPlatform) { if (existingPlatform) {
this.viewer.entities.remove(existingPlatform); this.viewer.entities.remove(existingPlatform);
} }
const existingLabel = this.viewer.entities.getById(platformLabelId);
if (existingLabel) {
this.viewer.entities.remove(existingLabel);
}
// 线 entry/exit // 线 entry/exit
waypoints.forEach((wp,index) => { waypoints.forEach((wp,index) => {
const waypointEntityId = `wp_${routeId}_${wp.id}`; const waypointEntityId = `wp_${routeId}_${wp.id}`;
@ -1017,6 +1025,43 @@ export default {
...(initialRotation !== undefined && { rotation: initialRotation }) ...(initialRotation !== undefined && { rotation: initialRotation })
} }
}); });
//
const firstWp = waypoints[0];
const firstAlt = firstWp && (firstWp.alt != null) ? Number(firstWp.alt) : 0;
const firstSpeed = firstWp && (firstWp.speed != null) ? Number(firstWp.speed) : 800;
const initialHeadingRad = pathData.path && pathData.path.length >= 2
? this.computeHeadingFromPositions(pathData.path[0], pathData.path[1]) : undefined;
const initialHeadingDeg = initialHeadingRad != null ? (initialHeadingRad * 180 / Math.PI) : 0;
const labelText = this.formatPlatformLabelText({
name: (platform && platform.name) || '平台',
altitude: firstAlt,
speed: firstSpeed,
headingDeg: initialHeadingDeg
});
const labelShow = this.routeLabelVisible[routeId] !== false
this.viewer.entities.add({
id: platformLabelId,
name: '平台标牌',
position: originalPositions[0],
show: labelShow,
properties: { routeId: routeId },
label: {
text: labelText,
font: '16px Microsoft YaHei',
fillColor: Cesium.Color.fromCssColorString('#333333'),
outlineColor: Cesium.Color.fromCssColorString('#e0e0e0'),
outlineWidth: 1,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
showBackground: true,
backgroundColor: Cesium.Color.fromCssColorString('rgba(255, 255, 255, 0.95)'),
backgroundPadding: new Cesium.Cartesian2(10, 6),
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0, -42),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scaleByDistance: new Cesium.NearFarScalar(500, 1.0, 200000, 0.65)
}
});
} }
// 线 // 线
if (waypoints.length > 1) { if (waypoints.length > 1) {
@ -1527,8 +1572,8 @@ export default {
for (let i = entityList.length - 1; i >= 0; i--) { for (let i = entityList.length - 1; i >= 0; i--) {
const entity = entityList[i]; const entity = entityList[i];
let shouldRemove = false; let shouldRemove = false;
// id // id
if (entity.id === `route-platform-${routeId}`) { if (entity.id === `route-platform-${routeId}` || entity.id === `route-platform-label-${routeId}`) {
shouldRemove = true; shouldRemove = true;
} else if (entity.properties && entity.properties.routeId) { } else if (entity.properties && entity.properties.routeId) {
const id = entity.properties.routeId.getValue && entity.properties.routeId.getValue(); const id = entity.properties.routeId.getValue && entity.properties.routeId.getValue();
@ -1536,7 +1581,7 @@ export default {
} }
if (shouldRemove) this.viewer.entities.remove(entity); if (shouldRemove) this.viewer.entities.remove(entity);
} }
this.allEntities = this.allEntities.filter(item => item.id !== routeId && item.id !== `route-platform-${routeId}`); this.allEntities = this.allEntities.filter(item => item.id !== routeId && item.id !== `route-platform-${routeId}` && item.id !== `route-platform-label-${routeId}`);
}, },
/** /**
* 根据当前点与另一点计算航向角弧度用于飞机图标朝向 * 根据当前点与另一点计算航向角弧度用于飞机图标朝向
@ -1561,8 +1606,18 @@ export default {
return heading; return heading;
}, },
/** 动态推演:更新某条航线的平台图标位置与朝向(position: { lng, lat, alt } 或 Cesium.Cartesian3;directionPoint 为用于计算机头朝向的另一点,如下一位置或上一位置) */ /** 格式化飞机标牌文案:名字、高度(m)、速度(km/h)、航向(°) */
updatePlatformPosition(routeId, position, directionPoint) { formatPlatformLabelText(data) {
const name = (data && data.name != null) ? String(data.name) : '—';
const alt = (data && data.altitude != null) ? Number(data.altitude) : 0;
const speed = (data && data.speed != null) ? Number(data.speed) : 0;
const hdg = (data && data.headingDeg != null) ? Number(data.headingDeg) : 0;
const headingNorm = ((hdg % 360) + 360) % 360;
return `${name}\n高度: ${Math.round(alt)}m 速度: ${Math.round(speed)}km/h 航向: ${Math.round(headingNorm)}°`;
},
/** 动态推演:更新某条航线的平台图标位置与朝向(position: { lng, lat, alt } 或 Cesium.Cartesian3;directionPoint 为用于计算机头朝向的另一点;labelData 可选,用于更新标牌 { name, altitude, speed, headingDeg }) */
updatePlatformPosition(routeId, position, directionPoint, labelData) {
if (!this.viewer) return; if (!this.viewer) return;
const entity = this.viewer.entities.getById(`route-platform-${routeId}`); const entity = this.viewer.entities.getById(`route-platform-${routeId}`);
if (!entity || !entity.position) return; if (!entity || !entity.position) return;
@ -1583,6 +1638,14 @@ export default {
entity.billboard.rotation = Math.PI / 2 - heading; entity.billboard.rotation = Math.PI / 2 - heading;
} }
} }
//
const labelEntity = this.viewer.entities.getById(`route-platform-label-${routeId}`);
if (labelEntity && labelEntity.position) {
labelEntity.position = cartesian;
if (labelData && labelEntity.label) {
labelEntity.label.text = this.formatPlatformLabelText(labelData);
}
}
}, },
checkCesiumLoaded() { checkCesiumLoaded() {
if (typeof Cesium === 'undefined') { if (typeof Cesium === 'undefined') {
@ -1626,8 +1689,11 @@ export default {
maximumRenderTimeChange: Infinity, maximumRenderTimeChange: Infinity,
// canvas readPixels false // canvas readPixels false
contextOptions: { contextOptions: {
preserveDrawingBuffer: true preserveDrawingBuffer: true,
} antialias: true // WebGL 齿
},
// 齿WebGL2 2/4/8
msaaSamples: 4
}) })
this.viewer.cesiumWidget.creditContainer.style.display = "none" this.viewer.cesiumWidget.creditContainer.style.display = "none"
// //
@ -1654,7 +1720,7 @@ export default {
this.initHoverHandler() this.initHoverHandler()
this.initMouseCoordinates() this.initMouseCoordinates()
console.log('Cesium离线二维地图已加载') console.log('Cesium离线二维地图已加载')
console.log('Cesium离线二维地图已加载')
// 1. // 1.
this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
this.handler.setInputAction((click) => { this.handler.setInputAction((click) => {
@ -1716,31 +1782,43 @@ export default {
const pickedObject = this.viewer.scene.pick(click.position) const pickedObject = this.viewer.scene.pick(click.position)
if (Cesium.defined(pickedObject) && pickedObject.id) { if (Cesium.defined(pickedObject) && pickedObject.id) {
const pickedEntity = pickedObject.id const pickedEntity = pickedObject.id
// const idStr = typeof pickedEntity.id === 'string' ? pickedEntity.id : (pickedEntity.id || '')
let entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity) let entityData = null
// 线线 // 线/
if (idStr.startsWith('route-platform-') && !idStr.startsWith('route-platform-label-')) {
const routeId = idStr.replace('route-platform-', '')
entityData = {
type: 'routePlatform',
routeId,
entity: pickedEntity,
labelVisible: this.routeLabelVisible[routeId] !== false
}
}
if (!entityData) { if (!entityData) {
// 线 //
for (const lineEntity of this.allEntities) { entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity)
if (lineEntity.type === 'line' && lineEntity.pointEntities) { // 线线
if (lineEntity.pointEntities.includes(pickedEntity)) { if (!entityData) {
entityData = lineEntity for (const lineEntity of this.allEntities) {
break if (lineEntity.type === 'line' && lineEntity.pointEntities) {
if (lineEntity.pointEntities.includes(pickedEntity)) {
entityData = lineEntity
break
}
} }
} }
} }
} //
// if (!entityData) {
if (!entityData) { for (const powerZoneEntity of this.allEntities) {
for (const powerZoneEntity of this.allEntities) { if (powerZoneEntity.type === 'powerZone' && powerZoneEntity.centerEntity === pickedEntity) {
if (powerZoneEntity.type === 'powerZone' && powerZoneEntity.centerEntity === pickedEntity) { entityData = powerZoneEntity
entityData = powerZoneEntity break
break }
} }
} }
} }
if (entityData && entityData.type !== 'route') { if (entityData && entityData.type !== 'route') {
//
this.contextMenu = { this.contextMenu = {
visible: true, visible: true,
position: { position: {
@ -3971,6 +4049,24 @@ export default {
this.selectedEntity = null this.selectedEntity = null
} }
}, },
/** 右键飞机:切换该航线飞机标牌的显示/隐藏 */
toggleRouteLabelVisibility() {
const ed = this.contextMenu.entityData
if (!ed || ed.type !== 'routePlatform' || ed.routeId == null) {
this.contextMenu.visible = false
return
}
const routeId = ed.routeId
const nextVisible = !(this.routeLabelVisible[routeId] !== false)
this.$set(this.routeLabelVisible, routeId, nextVisible)
const labelEntity = this.viewer.entities.getById(`route-platform-label-${routeId}`)
if (labelEntity) {
labelEntity.show = nextVisible
}
this.contextMenu.visible = false
if (this.viewer.scene.requestRenderMode) this.viewer.scene.requestRender()
this.$message && this.$message.success(nextVisible ? '已显示标牌' : '已隐藏标牌')
},
// //
deleteEntityFromContextMenu() { deleteEntityFromContextMenu() {
if (this.contextMenu.entityData) { if (this.contextMenu.entityData) {

57
ruoyi-ui/src/views/childRoom/index.vue

@ -20,9 +20,9 @@
@platform-icon-updated="onPlatformIconUpdated" @platform-icon-updated="onPlatformIconUpdated"
@platform-icon-removed="onPlatformIconRemoved" /> @platform-icon-removed="onPlatformIconRemoved" />
<div v-show="!screenshotMode" class="map-overlay-text"> <div v-show="!screenshotMode" class="map-overlay-text">
<i class="el-icon-location-outline text-3xl mb-2 block"></i> <!-- <i class="el-icon-location-outline text-3xl mb-2 block"></i> -->
<p>二维GIS地图区域</p> <!-- <p>二维GIS地图区域</p>
<p class="text-sm mt-1">支持标绘/航线/空域/实时态势</p> <p class="text-sm mt-1">支持标绘/航线/空域/实时态势</p> -->
</div> </div>
<div v-if="missionDrawingActive && missionDrawingPointsCount >= 2 && !screenshotMode" class="mission-drawing-actions" style="position:absolute; bottom:16px; left:50%; transform:translateX(-50%); z-index:10; display:flex; gap:8px; align-items:center;"> <div v-if="missionDrawingActive && missionDrawingPointsCount >= 2 && !screenshotMode" class="mission-drawing-actions" style="position:absolute; bottom:16px; left:50%; transform:translateX(-50%); z-index:10; display:flex; gap:8px; align-items:center;">
<span class="text-white text-sm"> {{ missionDrawingPointsCount }} 个航点右键结束</span> <span class="text-white text-sm"> {{ missionDrawingPointsCount }} 个航点右键结束</span>
@ -2328,9 +2328,9 @@ export default {
return last.endPos; return last.endPos;
}, },
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧/盘旋弧路径运动;返回 { position, nextPosition, previousPosition, warnings } */ /** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧/盘旋弧路径运动;返回 { position, nextPosition, previousPosition, warnings, earlyArrivalLegs, currentSegment },currentSegment 含 speedKmh 用于标牌 */
getPositionAtMinutesFromK(waypoints, minutesFromK, globalMin, globalMax) { getPositionAtMinutesFromK(waypoints, minutesFromK, globalMin, globalMax) {
if (!waypoints || waypoints.length === 0) return { position: null, nextPosition: null, previousPosition: null, warnings: [] }; if (!waypoints || waypoints.length === 0) return { position: null, nextPosition: null, previousPosition: null, warnings: [], earlyArrivalLegs: [], currentSegment: null };
let pathData = null; let pathData = null;
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) { if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) {
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(waypoints); const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(waypoints);
@ -2345,10 +2345,40 @@ export default {
const stepMin = 1 / 60; const stepMin = 1 / 60;
const nextPosition = this.getPositionFromTimeline(segments, minutesFromK + stepMin, path, segmentEndIndices); const nextPosition = this.getPositionFromTimeline(segments, minutesFromK + stepMin, path, segmentEndIndices);
const previousPosition = this.getPositionFromTimeline(segments, minutesFromK - stepMin, path, segmentEndIndices); const previousPosition = this.getPositionFromTimeline(segments, minutesFromK - stepMin, path, segmentEndIndices);
return { position, nextPosition, previousPosition, warnings, earlyArrivalLegs: earlyArrivalLegs || [] }; // speed
let currentSegment = null;
if (segments && segments.length > 0) {
if (minutesFromK <= segments[0].startTime) {
const s = segments[0];
currentSegment = { legIndex: s.legIndex, speedKmh: waypoints[s.legIndex] ? (Number(waypoints[s.legIndex].speed) || 800) : 800 };
} else if (minutesFromK >= segments[segments.length - 1].endTime) {
const s = segments[segments.length - 1];
currentSegment = { legIndex: s.legIndex, speedKmh: s.speedKmh != null ? s.speedKmh : (waypoints[s.legIndex] ? (Number(waypoints[s.legIndex].speed) || 800) : 800) };
} else {
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
if (minutesFromK >= s.startTime && minutesFromK < s.endTime) {
currentSegment = { legIndex: s.legIndex, speedKmh: s.speedKmh != null ? s.speedKmh : (waypoints[s.legIndex] ? (Number(waypoints[s.legIndex].speed) || 800) : 800) };
break;
}
}
}
}
return { position, nextPosition, previousPosition, warnings, earlyArrivalLegs: earlyArrivalLegs || [], currentSegment };
}, },
/** 仅根据当前展示的航线(activeRouteIds)更新平台图标位置,并汇总航段提示 */ /** 根据两点计算航向角(度),北为 0,顺时针为正,与数据库/标牌航向一致 */
headingDegFromPositions(fromPos, toPos) {
if (!fromPos || !toPos) return 0;
const dLng = (toPos.lng != null ? Number(toPos.lng) : 0) - (fromPos.lng != null ? Number(fromPos.lng) : 0);
const dLat = (toPos.lat != null ? Number(toPos.lat) : 0) - (fromPos.lat != null ? Number(fromPos.lat) : 0);
if (Math.abs(dLng) < 1e-10 && Math.abs(dLat) < 1e-10) return 0;
const rad = Math.atan2(dLng, dLat);
let deg = (rad * 180 / Math.PI);
return ((deg % 360) + 360) % 360;
},
/** 仅根据当前展示的航线(activeRouteIds)更新平台图标位置与标牌,并汇总航段提示 */
updateDeductionPositions() { updateDeductionPositions() {
if (!this.$refs.cesiumMap || !this.$refs.cesiumMap.updatePlatformPosition) return; if (!this.$refs.cesiumMap || !this.$refs.cesiumMap.updatePlatformPosition) return;
const minutesFromK = this.deductionMinutesFromK != null ? this.deductionMinutesFromK : 0; const minutesFromK = this.deductionMinutesFromK != null ? this.deductionMinutesFromK : 0;
@ -2357,9 +2387,18 @@ export default {
this.activeRouteIds.forEach(routeId => { this.activeRouteIds.forEach(routeId => {
const route = this.routes.find(r => r.id === routeId); const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints || route.waypoints.length === 0) return; if (!route || !route.waypoints || route.waypoints.length === 0) return;
const { position, nextPosition, previousPosition, warnings, earlyArrivalLegs } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes); const { position, nextPosition, previousPosition, warnings, earlyArrivalLegs, currentSegment } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes);
if (warnings && warnings.length) allWarnings.push(...warnings); if (warnings && warnings.length) allWarnings.push(...warnings);
if (position) this.$refs.cesiumMap.updatePlatformPosition(routeId, position, nextPosition || previousPosition); if (position) {
const directionPoint = nextPosition || previousPosition;
const labelData = {
name: (route.platform && route.platform.name) ? route.platform.name : '平台',
altitude: position.alt != null ? Number(position.alt) : 0,
speed: (currentSegment && currentSegment.speedKmh != null) ? currentSegment.speedKmh : 800,
headingDeg: directionPoint ? this.headingDegFromPositions(position, directionPoint) : 0
};
this.$refs.cesiumMap.updatePlatformPosition(routeId, position, directionPoint, labelData);
}
this.deductionEarlyArrivalByRoute[routeId] = earlyArrivalLegs || []; this.deductionEarlyArrivalByRoute[routeId] = earlyArrivalLegs || [];
}); });
this.deductionWarnings = [...new Set(allWarnings)]; this.deductionWarnings = [...new Set(allWarnings)];

Loading…
Cancel
Save