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>
<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">
<span class="menu-icon">🗑</span>
<span>删除</span>
</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-title">线段属性</div>
@ -354,6 +363,10 @@ export default {
this.$emit('edit-platform-heading')
},
handleToggleRouteLabel() {
this.$emit('toggle-route-label')
},
toggleColorPicker(property) {
if (this.showColorPickerFor === property) {
this.showColorPickerFor = null

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

@ -37,6 +37,7 @@
@edit-platform-position="openPlatformIconPositionDialog"
@edit-platform-heading="openPlatformIconHeadingDialog"
@show-transform-box="showPlatformIconTransformBox"
@toggle-route-label="toggleRouteLabelVisibility"
/>
<!-- 定位弹窗 -->
@ -168,6 +169,8 @@ export default {
position: { x: 0, y: 0 },
entityData: null
},
// 线routeId -> true / false
routeLabelVisible: {},
//
defaultStyles: {
point: { color: '#FF0000', size: 12 },
@ -888,12 +891,17 @@ export default {
if (existingLine) {
this.viewer.entities.remove(existingLine);
}
// 线
// 线
const platformBillboardId = `route-platform-${routeId}`;
const platformLabelId = `route-platform-label-${routeId}`;
const existingPlatform = this.viewer.entities.getById(platformBillboardId);
if (existingPlatform) {
this.viewer.entities.remove(existingPlatform);
}
const existingLabel = this.viewer.entities.getById(platformLabelId);
if (existingLabel) {
this.viewer.entities.remove(existingLabel);
}
// 线 entry/exit
waypoints.forEach((wp,index) => {
const waypointEntityId = `wp_${routeId}_${wp.id}`;
@ -1017,6 +1025,43 @@ export default {
...(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) {
@ -1527,8 +1572,8 @@ export default {
for (let i = entityList.length - 1; i >= 0; i--) {
const entity = entityList[i];
let shouldRemove = false;
// id
if (entity.id === `route-platform-${routeId}`) {
// id
if (entity.id === `route-platform-${routeId}` || entity.id === `route-platform-label-${routeId}`) {
shouldRemove = true;
} else if (entity.properties && entity.properties.routeId) {
const id = entity.properties.routeId.getValue && entity.properties.routeId.getValue();
@ -1536,7 +1581,7 @@ export default {
}
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;
},
/** 动态推演:更新某条航线的平台图标位置与朝向(position: { lng, lat, alt } 或 Cesium.Cartesian3;directionPoint 为用于计算机头朝向的另一点,如下一位置或上一位置) */
updatePlatformPosition(routeId, position, directionPoint) {
/** 格式化飞机标牌文案:名字、高度(m)、速度(km/h)、航向(°) */
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;
const entity = this.viewer.entities.getById(`route-platform-${routeId}`);
if (!entity || !entity.position) return;
@ -1583,6 +1638,14 @@ export default {
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() {
if (typeof Cesium === 'undefined') {
@ -1626,8 +1689,11 @@ export default {
maximumRenderTimeChange: Infinity,
// canvas readPixels false
contextOptions: {
preserveDrawingBuffer: true
}
preserveDrawingBuffer: true,
antialias: true // WebGL 齿
},
// 齿WebGL2 2/4/8
msaaSamples: 4
})
this.viewer.cesiumWidget.creditContainer.style.display = "none"
//
@ -1654,7 +1720,7 @@ export default {
this.initHoverHandler()
this.initMouseCoordinates()
console.log('Cesium离线二维地图已加载')
console.log('Cesium离线二维地图已加载')
// 1.
this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
this.handler.setInputAction((click) => {
@ -1716,31 +1782,43 @@ export default {
const pickedObject = this.viewer.scene.pick(click.position)
if (Cesium.defined(pickedObject) && pickedObject.id) {
const pickedEntity = pickedObject.id
//
let entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity)
// 线线
const idStr = typeof pickedEntity.id === 'string' ? pickedEntity.id : (pickedEntity.id || '')
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) {
// 线
for (const lineEntity of this.allEntities) {
if (lineEntity.type === 'line' && lineEntity.pointEntities) {
if (lineEntity.pointEntities.includes(pickedEntity)) {
entityData = lineEntity
break
//
entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity)
// 线线
if (!entityData) {
for (const lineEntity of this.allEntities) {
if (lineEntity.type === 'line' && lineEntity.pointEntities) {
if (lineEntity.pointEntities.includes(pickedEntity)) {
entityData = lineEntity
break
}
}
}
}
}
//
if (!entityData) {
for (const powerZoneEntity of this.allEntities) {
if (powerZoneEntity.type === 'powerZone' && powerZoneEntity.centerEntity === pickedEntity) {
entityData = powerZoneEntity
break
//
if (!entityData) {
for (const powerZoneEntity of this.allEntities) {
if (powerZoneEntity.type === 'powerZone' && powerZoneEntity.centerEntity === pickedEntity) {
entityData = powerZoneEntity
break
}
}
}
}
if (entityData && entityData.type !== 'route') {
//
this.contextMenu = {
visible: true,
position: {
@ -3971,6 +4049,24 @@ export default {
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() {
if (this.contextMenu.entityData) {

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

@ -20,9 +20,9 @@
@platform-icon-updated="onPlatformIconUpdated"
@platform-icon-removed="onPlatformIconRemoved" />
<div v-show="!screenshotMode" class="map-overlay-text">
<i class="el-icon-location-outline text-3xl mb-2 block"></i>
<p>二维GIS地图区域</p>
<p class="text-sm mt-1">支持标绘/航线/空域/实时态势</p>
<!-- <i class="el-icon-location-outline text-3xl mb-2 block"></i> -->
<!-- <p>二维GIS地图区域</p>
<p class="text-sm mt-1">支持标绘/航线/空域/实时态势</p> -->
</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;">
<span class="text-white text-sm"> {{ missionDrawingPointsCount }} 个航点右键结束</span>
@ -2328,9 +2328,9 @@ export default {
return last.endPos;
},
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧/盘旋弧路径运动;返回 { position, nextPosition, previousPosition, warnings } */
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧/盘旋弧路径运动;返回 { position, nextPosition, previousPosition, warnings, earlyArrivalLegs, currentSegment },currentSegment 含 speedKmh 用于标牌 */
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;
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) {
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(waypoints);
@ -2345,10 +2345,40 @@ export default {
const stepMin = 1 / 60;
const nextPosition = 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() {
if (!this.$refs.cesiumMap || !this.$refs.cesiumMap.updatePlatformPosition) return;
const minutesFromK = this.deductionMinutesFromK != null ? this.deductionMinutesFromK : 0;
@ -2357,9 +2387,18 @@ export default {
this.activeRouteIds.forEach(routeId => {
const route = this.routes.find(r => r.id === routeId);
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 (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.deductionWarnings = [...new Set(allWarnings)];

Loading…
Cancel
Save