|
|
|
@ -54,6 +54,7 @@ |
|
|
|
@add-waypoint-at="handleAddWaypointAt" |
|
|
|
@toggle-waypoint-hold="handleToggleWaypointHold" |
|
|
|
@launch-missile="openLaunchMissileDialog" |
|
|
|
@adjust-airspace-position="startAirspacePositionEdit" |
|
|
|
/> |
|
|
|
|
|
|
|
<!-- 定位弹窗 --> |
|
|
|
@ -75,6 +76,11 @@ |
|
|
|
点击地图放置复制航线,右键取消 |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 空域位置调整提示 --> |
|
|
|
<div v-if="airspacePositionEditContext" class="copy-route-tip"> |
|
|
|
移动鼠标调整位置,左键确认,右键取消 |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 半径输入弹窗 --> |
|
|
|
<radius-dialog |
|
|
|
:visible="radiusDialogVisible" |
|
|
|
@ -495,10 +501,10 @@ export default { |
|
|
|
defaultStyles: { |
|
|
|
point: { color: '#FF0000', size: 8 }, |
|
|
|
line: { color: '#000000', width: 1 }, |
|
|
|
polygon: { color: '#0000FF', opacity: 0, width: 4 }, |
|
|
|
rectangle: { color: '#FFA500', opacity: 0, width: 4 }, |
|
|
|
circle: { color: '#800080', opacity: 0, width: 4 }, |
|
|
|
sector: { color: '#FF6347', opacity: 0, width: 3 }, |
|
|
|
polygon: { color: '#0000FF', opacity: 0, width: 2 }, |
|
|
|
rectangle: { color: '#FFA500', opacity: 0, width: 2 }, |
|
|
|
circle: { color: '#800080', opacity: 0, width: 2 }, |
|
|
|
sector: { color: '#FF6347', opacity: 0, width: 2 }, |
|
|
|
arrow: { color: '#FF0000', width: 6 }, |
|
|
|
text: { color: '#000000', font: '14px PingFang SC, Microsoft YaHei, Helvetica Neue, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.92)' }, |
|
|
|
image: { width: 150, height: 150 } |
|
|
|
@ -557,7 +563,10 @@ export default { |
|
|
|
copyPreviewMouseCartesian: null, |
|
|
|
// 在航点前/后增加航点:{ routeId, waypointIndex, mode: 'before'|'after', waypoints },预览折线实体 |
|
|
|
addWaypointContext: null, |
|
|
|
addWaypointPreviewEntity: null |
|
|
|
addWaypointPreviewEntity: null, |
|
|
|
// 空域位置调整:{ entityData },预览实体用 CallbackProperty 实时跟随鼠标(与测距绘制一致) |
|
|
|
airspacePositionEditContext: null, |
|
|
|
airspacePositionEditPreviewEntity: null |
|
|
|
} |
|
|
|
}, |
|
|
|
components: { |
|
|
|
@ -3510,6 +3519,15 @@ export default { |
|
|
|
this.clearAddWaypointContext(); |
|
|
|
return; |
|
|
|
} |
|
|
|
// 空域位置调整模式:左键确认 |
|
|
|
if (this.airspacePositionEditContext) { |
|
|
|
const placePosition = this.getClickPosition(click.position); |
|
|
|
if (placePosition) { |
|
|
|
this.applyAirspacePositionEdit(placePosition); |
|
|
|
} |
|
|
|
this.clearAirspacePositionEdit(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const pickedObject = this.viewer.scene.pick(click.position); |
|
|
|
if (Cesium.defined(pickedObject) && pickedObject.id) { |
|
|
|
@ -3675,6 +3693,13 @@ export default { |
|
|
|
if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.airspacePositionEditContext) { |
|
|
|
const cartesian = this.getClickPosition(movement.endPosition); |
|
|
|
if (cartesian) { |
|
|
|
this.airspacePositionEditContext.mouseCartesian = cartesian; |
|
|
|
if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); |
|
|
|
|
|
|
|
// 拖拽结束:恢复地图操作、持久化新位置并清除状态;未超过阈值则只清 pending(视为点击) |
|
|
|
@ -3734,6 +3759,13 @@ export default { |
|
|
|
this.$message && this.$message.info('已取消增加航点'); |
|
|
|
return; |
|
|
|
} |
|
|
|
// 若处于空域位置调整,右键取消 |
|
|
|
if (this.airspacePositionEditContext) { |
|
|
|
this.clearAirspacePositionEdit(); |
|
|
|
this.contextMenu.visible = false; |
|
|
|
this.$message && this.$message.info('已取消调整位置'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 隐藏之前可能显示的菜单 |
|
|
|
this.contextMenu.visible = false; |
|
|
|
@ -5369,7 +5401,7 @@ export default { |
|
|
|
return angle; |
|
|
|
}, |
|
|
|
// 生成扇形顶点位置 |
|
|
|
generateSectorPositions(center, radius, startAngle, endAngle) { |
|
|
|
generateSectorPositions(center, radius, startAngle, endAngle, maxPoints) { |
|
|
|
const positions = []; |
|
|
|
const centerLL = Cesium.Cartographic.fromCartesian(center); |
|
|
|
// 添加圆心 |
|
|
|
@ -5381,8 +5413,9 @@ export default { |
|
|
|
} |
|
|
|
// 确保角度差不为零 |
|
|
|
angleDiff = Math.max(0.01, angleDiff); |
|
|
|
// 计算扇形的顶点数(根据角度差确定,确保平滑) |
|
|
|
const numPoints = Math.max(200, Math.ceil(angleDiff * 180 / Math.PI)); |
|
|
|
// 计算扇形的顶点数(根据角度差确定,确保平滑;maxPoints 用于预览时减少点数) |
|
|
|
let numPoints = Math.max(200, Math.ceil(angleDiff * 180 / Math.PI)); |
|
|
|
if (maxPoints != null && maxPoints > 0) numPoints = Math.max(8, Math.min(numPoints, maxPoints)); |
|
|
|
const angleStep = angleDiff / (numPoints - 1); |
|
|
|
// 生成扇形的顶点(顺时针方向) |
|
|
|
for (let i = 0; i < numPoints; i++) { |
|
|
|
@ -5491,8 +5524,10 @@ export default { |
|
|
|
return null; |
|
|
|
} |
|
|
|
}, |
|
|
|
// 添加箭头实体 |
|
|
|
// 添加箭头实体(箭头仅保留首尾两点,避免 PolylineArrowMaterial 在中间段显示额外箭头) |
|
|
|
addArrowEntity(positions) { |
|
|
|
if (!positions || positions.length < 2) return null |
|
|
|
const arrowPositions = positions.length === 2 ? positions : [positions[0], positions[positions.length - 1]] |
|
|
|
this.entityCounter++ |
|
|
|
const id = `arrow_${this.entityCounter}` |
|
|
|
// 创建箭头实体,使用固定宽度以确保等比例放大 |
|
|
|
@ -5500,7 +5535,7 @@ export default { |
|
|
|
id: id, |
|
|
|
name: `箭头 ${this.entityCounter}`, |
|
|
|
polyline: { |
|
|
|
positions: positions, |
|
|
|
positions: arrowPositions, |
|
|
|
width: 12, // 增加宽度以获得更大的箭头头部 |
|
|
|
material: new Cesium.PolylineArrowMaterialProperty( |
|
|
|
Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) |
|
|
|
@ -5513,8 +5548,8 @@ export default { |
|
|
|
const entityData = { |
|
|
|
id, |
|
|
|
type: 'arrow', |
|
|
|
points: positions.map(p => this.cartesianToLatLng(p)), |
|
|
|
positions: positions, |
|
|
|
points: arrowPositions.map(p => this.cartesianToLatLng(p)), |
|
|
|
positions: arrowPositions, |
|
|
|
entity: entity, |
|
|
|
color: this.defaultStyles.arrow.color, |
|
|
|
width: this.defaultStyles.arrow.width, |
|
|
|
@ -6417,6 +6452,403 @@ export default { |
|
|
|
} |
|
|
|
this.addWaypointContext = null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 为空域图形(多边形/矩形/圆形/扇形)添加或更新中心名称标签,微软雅黑、不加粗,清晰可读 */ |
|
|
|
updateAirspaceEntityLabel(entityData) { |
|
|
|
if (!entityData || !entityData.entity || !['polygon', 'rectangle', 'circle', 'sector'].includes(entityData.type)) return |
|
|
|
const entity = entityData.entity |
|
|
|
const name = entityData.name |
|
|
|
const font = '16px Microsoft YaHei' |
|
|
|
const labelStyle = { |
|
|
|
text: name || '', |
|
|
|
font, |
|
|
|
fillColor: Cesium.Color.fromCssColorString('#1a1a1a'), |
|
|
|
style: Cesium.LabelStyle.FILL, |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.CENTER, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
backgroundColor: Cesium.Color.fromCssColorString('rgba(255, 255, 255, 0.95)'), |
|
|
|
backgroundPadding: new Cesium.Cartesian2(10, 6), |
|
|
|
disableDepthTestDistance: Number.POSITIVE_INFINITY |
|
|
|
} |
|
|
|
if (!name) { |
|
|
|
entity.label = undefined |
|
|
|
return |
|
|
|
} |
|
|
|
const that = this |
|
|
|
const getCenter = () => { |
|
|
|
const c = that.getAirspaceCenter(entityData) |
|
|
|
return c || Cesium.Cartesian3.ZERO |
|
|
|
} |
|
|
|
entity.label = { |
|
|
|
...labelStyle, |
|
|
|
position: new Cesium.CallbackProperty(getCenter, false) |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 开始空域位置调整模式:右键菜单点击「调整位置」后进入,移动鼠标预览,左键确认、右键取消 */ |
|
|
|
startAirspacePositionEdit() { |
|
|
|
const ed = this.contextMenu.entityData; |
|
|
|
if (!ed || !['polygon', 'rectangle', 'circle', 'sector', 'arrow'].includes(ed.type)) { |
|
|
|
this.contextMenu.visible = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
const entityData = this.allEntities.find(e => (e.entity === ed.entity || e === ed) && e.type === ed.type) || ed; |
|
|
|
if (!entityData.entity) { |
|
|
|
this.contextMenu.visible = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
this.contextMenu.visible = false; |
|
|
|
entityData.entity.show = false; |
|
|
|
const center = this.getAirspaceCenter(entityData); |
|
|
|
this.airspacePositionEditContext = { entityData, mouseCartesian: center || null }; |
|
|
|
this.createAirspacePositionPreviewEntity(); |
|
|
|
this.$message && this.$message.info('移动鼠标调整位置,左键确认,右键取消'); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 创建空域位置调整的预览实体(使用 CallbackProperty,每帧渲染时读取 mouseCartesian,与测距绘制一致) */ |
|
|
|
createAirspacePositionPreviewEntity() { |
|
|
|
const ctx = this.airspacePositionEditContext; |
|
|
|
if (!ctx || !ctx.entityData || this.airspacePositionEditPreviewEntity) return; |
|
|
|
const entityData = ctx.entityData; |
|
|
|
const color = entityData.color || this.defaultStyles.polygon.color; |
|
|
|
const borderColor = entityData.borderColor || color; |
|
|
|
const opacity = entityData.opacity != null ? entityData.opacity : 0; |
|
|
|
const width = entityData.width != null ? entityData.width : 2; |
|
|
|
const that = this; |
|
|
|
|
|
|
|
switch (entityData.type) { |
|
|
|
case 'polygon': { |
|
|
|
if (!entityData.positions || entityData.positions.length < 3) return; |
|
|
|
this.airspacePositionEditPreviewEntity = this.viewer.entities.add({ |
|
|
|
polygon: { |
|
|
|
hierarchy: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const oldCenter = that.getAirspaceCenter(entityData); |
|
|
|
if (!mc || !oldCenter) return new Cesium.PolygonHierarchy(entityData.positions); |
|
|
|
const delta = Cesium.Cartesian3.subtract(mc, oldCenter, new Cesium.Cartesian3()); |
|
|
|
const newPositions = entityData.positions.map(p => Cesium.Cartesian3.add(p, delta, new Cesium.Cartesian3())); |
|
|
|
return new Cesium.PolygonHierarchy(newPositions); |
|
|
|
}, false), |
|
|
|
material: Cesium.Color.TRANSPARENT |
|
|
|
}, |
|
|
|
polyline: { |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const oldCenter = that.getAirspaceCenter(entityData); |
|
|
|
if (!mc || !oldCenter) return entityData.positions; |
|
|
|
const delta = Cesium.Cartesian3.subtract(mc, oldCenter, new Cesium.Cartesian3()); |
|
|
|
const newPositions = entityData.positions.map(p => Cesium.Cartesian3.add(p, delta, new Cesium.Cartesian3())); |
|
|
|
return [...newPositions, newPositions[0]]; |
|
|
|
}, false), |
|
|
|
width, |
|
|
|
material: Cesium.Color.fromCssColorString(borderColor), |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}); |
|
|
|
break; |
|
|
|
} |
|
|
|
case 'rectangle': { |
|
|
|
this.airspacePositionEditPreviewEntity = this.viewer.entities.add({ |
|
|
|
rectangle: { |
|
|
|
coordinates: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const rect = entityData.entity.rectangle.coordinates.getValue(Cesium.JulianDate.now()); |
|
|
|
if (!mc || !rect) return rect || Cesium.Rectangle.fromDegrees(0, 0, 0, 0); |
|
|
|
const oldCenter = that.getAirspaceCenter(entityData); |
|
|
|
if (!oldCenter) return rect; |
|
|
|
const oldCarto = Cesium.Cartographic.fromCartesian(oldCenter); |
|
|
|
const newCarto = Cesium.Cartographic.fromCartesian(mc); |
|
|
|
const dLon = newCarto.longitude - oldCarto.longitude; |
|
|
|
const dLat = newCarto.latitude - oldCarto.latitude; |
|
|
|
return new Cesium.Rectangle( |
|
|
|
Cesium.Math.negativePiToPi(rect.west + dLon), |
|
|
|
Math.max(-Cesium.Math.PI_OVER_TWO, Math.min(Cesium.Math.PI_OVER_TWO, rect.south + dLat)), |
|
|
|
Cesium.Math.negativePiToPi(rect.east + dLon), |
|
|
|
Math.max(-Cesium.Math.PI_OVER_TWO, Math.min(Cesium.Math.PI_OVER_TWO, rect.north + dLat)) |
|
|
|
); |
|
|
|
}, false), |
|
|
|
material: Cesium.Color.fromCssColorString(color).withAlpha(opacity) |
|
|
|
}, |
|
|
|
polyline: { |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const rect = entityData.entity.rectangle.coordinates.getValue(Cesium.JulianDate.now()); |
|
|
|
if (!rect) return []; |
|
|
|
const oldCenter = that.getAirspaceCenter(entityData); |
|
|
|
if (!oldCenter) { |
|
|
|
const sw = Cesium.Cartesian3.fromRadians(rect.west, rect.south); |
|
|
|
const se = Cesium.Cartesian3.fromRadians(rect.east, rect.south); |
|
|
|
const ne = Cesium.Cartesian3.fromRadians(rect.east, rect.north); |
|
|
|
const nw = Cesium.Cartesian3.fromRadians(rect.west, rect.north); |
|
|
|
return [sw, se, ne, nw, sw]; |
|
|
|
} |
|
|
|
const center = mc || oldCenter; |
|
|
|
const oldCarto = Cesium.Cartographic.fromCartesian(oldCenter); |
|
|
|
const newCarto = Cesium.Cartographic.fromCartesian(center); |
|
|
|
const dLon = newCarto.longitude - oldCarto.longitude; |
|
|
|
const dLat = newCarto.latitude - oldCarto.latitude; |
|
|
|
const newWest = Cesium.Math.negativePiToPi(rect.west + dLon); |
|
|
|
const newEast = Cesium.Math.negativePiToPi(rect.east + dLon); |
|
|
|
const newSouth = Math.max(-Cesium.Math.PI_OVER_TWO, Math.min(Cesium.Math.PI_OVER_TWO, rect.south + dLat)); |
|
|
|
const newNorth = Math.max(-Cesium.Math.PI_OVER_TWO, Math.min(Cesium.Math.PI_OVER_TWO, rect.north + dLat)); |
|
|
|
const sw = Cesium.Cartesian3.fromRadians(newWest, newSouth); |
|
|
|
const se = Cesium.Cartesian3.fromRadians(newEast, newSouth); |
|
|
|
const ne = Cesium.Cartesian3.fromRadians(newEast, newNorth); |
|
|
|
const nw = Cesium.Cartesian3.fromRadians(newWest, newNorth); |
|
|
|
return [sw, se, ne, nw, sw]; |
|
|
|
}, false), |
|
|
|
width, |
|
|
|
material: Cesium.Color.fromCssColorString(borderColor), |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}); |
|
|
|
break; |
|
|
|
} |
|
|
|
case 'circle': { |
|
|
|
const radius = entityData.radius || 1000; |
|
|
|
this.airspacePositionEditPreviewEntity = this.viewer.entities.add({ |
|
|
|
position: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
return mc || that.getAirspaceCenter(entityData) || Cesium.Cartesian3.ZERO; |
|
|
|
}, false), |
|
|
|
ellipse: { |
|
|
|
semiMajorAxis: radius, |
|
|
|
semiMinorAxis: radius, |
|
|
|
material: Cesium.Color.fromCssColorString(color).withAlpha(opacity), |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
}, |
|
|
|
polyline: { |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const center = mc || that.getAirspaceCenter(entityData); |
|
|
|
if (!center) return []; |
|
|
|
return that.generateCirclePositions(center, radius, 64); |
|
|
|
}, false), |
|
|
|
width, |
|
|
|
material: Cesium.Color.fromCssColorString(borderColor), |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}); |
|
|
|
break; |
|
|
|
} |
|
|
|
case 'sector': { |
|
|
|
const radius = entityData.radius || 1000; |
|
|
|
const startAngle = entityData.startAngle != null ? entityData.startAngle : 0; |
|
|
|
const endAngle = entityData.endAngle != null ? entityData.endAngle : Math.PI * 0.5; |
|
|
|
this.airspacePositionEditPreviewEntity = this.viewer.entities.add({ |
|
|
|
polygon: { |
|
|
|
hierarchy: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const center = mc || that.getAirspaceCenter(entityData); |
|
|
|
if (!center) return new Cesium.PolygonHierarchy([]); |
|
|
|
const positions = that.generateSectorPositions(center, radius, startAngle, endAngle, 64); |
|
|
|
return new Cesium.PolygonHierarchy(positions); |
|
|
|
}, false), |
|
|
|
material: Cesium.Color.fromCssColorString(color).withAlpha(opacity) |
|
|
|
}, |
|
|
|
polyline: { |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const center = mc || that.getAirspaceCenter(entityData); |
|
|
|
if (!center) return []; |
|
|
|
return that.generateSectorPositions(center, radius, startAngle, endAngle, 64); |
|
|
|
}, false), |
|
|
|
width, |
|
|
|
material: Cesium.Color.fromCssColorString(borderColor), |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}); |
|
|
|
break; |
|
|
|
} |
|
|
|
case 'arrow': { |
|
|
|
const arrowColor = entityData.color || this.defaultStyles.arrow.color; |
|
|
|
this.airspacePositionEditPreviewEntity = this.viewer.entities.add({ |
|
|
|
polyline: { |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
const mc = that.airspacePositionEditContext?.mouseCartesian; |
|
|
|
const oldCenter = that.getAirspaceCenter(entityData); |
|
|
|
if (!mc || !oldCenter || !entityData.positions || entityData.positions.length < 2) return entityData.positions || []; |
|
|
|
const delta = Cesium.Cartesian3.subtract(mc, oldCenter, new Cesium.Cartesian3()); |
|
|
|
const first = entityData.positions[0]; |
|
|
|
const last = entityData.positions[entityData.positions.length - 1]; |
|
|
|
return [ |
|
|
|
Cesium.Cartesian3.add(first, delta, new Cesium.Cartesian3()), |
|
|
|
Cesium.Cartesian3.add(last, delta, new Cesium.Cartesian3()) |
|
|
|
]; |
|
|
|
}, false), |
|
|
|
width: 12, |
|
|
|
material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString(arrowColor)), |
|
|
|
arcType: Cesium.ArcType.NONE, |
|
|
|
widthInMeters: false |
|
|
|
} |
|
|
|
}); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 获取空域图形的中心点(笛卡尔坐标) */ |
|
|
|
getAirspaceCenter(entityData) { |
|
|
|
if (!entityData) return null; |
|
|
|
switch (entityData.type) { |
|
|
|
case 'polygon': |
|
|
|
if (entityData.positions && entityData.positions.length > 0) { |
|
|
|
const sum = entityData.positions.reduce((acc, p) => Cesium.Cartesian3.add(acc, p, new Cesium.Cartesian3()), new Cesium.Cartesian3()); |
|
|
|
return Cesium.Cartesian3.divideByScalar(sum, entityData.positions.length, new Cesium.Cartesian3()); |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'rectangle': |
|
|
|
if (entityData.entity && entityData.entity.rectangle && entityData.entity.rectangle.coordinates) { |
|
|
|
const rect = entityData.entity.rectangle.coordinates.getValue(Cesium.JulianDate.now()); |
|
|
|
if (rect) { |
|
|
|
const west = rect.west, south = rect.south, east = rect.east, north = rect.north; |
|
|
|
return Cesium.Cartesian3.fromRadians((west + east) / 2, (south + north) / 2); |
|
|
|
} |
|
|
|
} |
|
|
|
if (entityData.points && entityData.points.length >= 2) { |
|
|
|
const lngs = entityData.points.map(p => p.lng), lats = entityData.points.map(p => p.lat); |
|
|
|
return Cesium.Cartesian3.fromDegrees((Math.min(...lngs) + Math.max(...lngs)) / 2, (Math.min(...lats) + Math.max(...lats)) / 2); |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'circle': |
|
|
|
if (entityData.entity && entityData.entity.position) { |
|
|
|
return entityData.entity.position.getValue(Cesium.JulianDate.now()); |
|
|
|
} |
|
|
|
if (entityData.points && entityData.points[0]) { |
|
|
|
return Cesium.Cartesian3.fromDegrees(entityData.points[0].lng, entityData.points[0].lat); |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'sector': |
|
|
|
if (entityData.center) { |
|
|
|
return Cesium.Cartesian3.fromDegrees(entityData.center.lng, entityData.center.lat); |
|
|
|
} |
|
|
|
if (entityData.positions && entityData.positions.length > 0) { |
|
|
|
const sum = entityData.positions.reduce((acc, p) => Cesium.Cartesian3.add(acc, p, new Cesium.Cartesian3()), new Cesium.Cartesian3()); |
|
|
|
return Cesium.Cartesian3.divideByScalar(sum, entityData.positions.length, new Cesium.Cartesian3()); |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'arrow': |
|
|
|
if (entityData.positions && entityData.positions.length >= 2) { |
|
|
|
const sum = entityData.positions.reduce((acc, p) => Cesium.Cartesian3.add(acc, p, new Cesium.Cartesian3()), new Cesium.Cartesian3()); |
|
|
|
return Cesium.Cartesian3.divideByScalar(sum, entityData.positions.length, new Cesium.Cartesian3()); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
return null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 应用空域位置调整并退出模式 */ |
|
|
|
applyAirspacePositionEdit(newCenter) { |
|
|
|
const ctx = this.airspacePositionEditContext; |
|
|
|
if (!ctx || !ctx.entityData) return; |
|
|
|
const entityData = ctx.entityData; |
|
|
|
// 先移除预览实体,避免与真实实体同时显示导致“两个箭头”现象(尤其当箭头穿过矩形/圆形空域时) |
|
|
|
if (this.airspacePositionEditPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.airspacePositionEditPreviewEntity); |
|
|
|
this.airspacePositionEditPreviewEntity = null; |
|
|
|
} |
|
|
|
const oldCenter = this.getAirspaceCenter(entityData); |
|
|
|
if (!oldCenter) return; |
|
|
|
const delta = Cesium.Cartesian3.subtract(newCenter, oldCenter, new Cesium.Cartesian3()); |
|
|
|
const oldCarto = Cesium.Cartographic.fromCartesian(oldCenter); |
|
|
|
const newCarto = Cesium.Cartographic.fromCartesian(newCenter); |
|
|
|
const dLon = newCarto.longitude - oldCarto.longitude; |
|
|
|
const dLat = newCarto.latitude - oldCarto.latitude; |
|
|
|
|
|
|
|
switch (entityData.type) { |
|
|
|
case 'polygon': |
|
|
|
if (entityData.positions && entityData.positions.length > 0) { |
|
|
|
entityData.positions = entityData.positions.map(p => Cesium.Cartesian3.add(p, delta, new Cesium.Cartesian3())); |
|
|
|
entityData.points = entityData.positions.map(p => this.cartesianToLatLng(p)); |
|
|
|
const closed = [...entityData.positions, entityData.positions[0]]; |
|
|
|
entityData.entity.polygon.hierarchy = new Cesium.PolygonHierarchy(entityData.positions); |
|
|
|
entityData.entity.polyline.positions = closed; |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'rectangle': { |
|
|
|
const rect = entityData.entity.rectangle.coordinates.getValue(Cesium.JulianDate.now()); |
|
|
|
if (rect) { |
|
|
|
const newWest = Cesium.Math.negativePiToPi(rect.west + dLon); |
|
|
|
const newEast = Cesium.Math.negativePiToPi(rect.east + dLon); |
|
|
|
const newSouth = Math.max(-Cesium.Math.PI_OVER_TWO, Math.min(Cesium.Math.PI_OVER_TWO, rect.south + dLat)); |
|
|
|
const newNorth = Math.max(-Cesium.Math.PI_OVER_TWO, Math.min(Cesium.Math.PI_OVER_TWO, rect.north + dLat)); |
|
|
|
const newRect = new Cesium.Rectangle(newWest, newSouth, newEast, newNorth); |
|
|
|
entityData.entity.rectangle.coordinates = newRect; |
|
|
|
const sw = Cesium.Cartesian3.fromRadians(newRect.west, newRect.south); |
|
|
|
const se = Cesium.Cartesian3.fromRadians(newRect.east, newRect.south); |
|
|
|
const ne = Cesium.Cartesian3.fromRadians(newRect.east, newRect.north); |
|
|
|
const nw = Cesium.Cartesian3.fromRadians(newRect.west, newRect.north); |
|
|
|
entityData.entity.polyline.positions = [sw, se, ne, nw, sw]; |
|
|
|
entityData.points = [this.cartesianToLatLng(sw), this.cartesianToLatLng(se), this.cartesianToLatLng(ne), this.cartesianToLatLng(nw)]; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
case 'circle': |
|
|
|
entityData.entity.position = newCenter; |
|
|
|
const radius = entityData.radius || 1000; |
|
|
|
entityData.entity.polyline.positions = this.generateCirclePositions(newCenter, radius); |
|
|
|
entityData.points = [this.cartesianToLatLng(newCenter), entityData.points && entityData.points[1] ? entityData.points[1] : this.cartesianToLatLng(newCenter)]; |
|
|
|
break; |
|
|
|
case 'sector': { |
|
|
|
const newCenterLL = this.cartesianToLatLng(newCenter); |
|
|
|
entityData.center = newCenterLL; |
|
|
|
const r = entityData.radius || 1000; |
|
|
|
const sa = entityData.startAngle != null ? entityData.startAngle : 0; |
|
|
|
const ea = entityData.endAngle != null ? entityData.endAngle : Math.PI * 0.5; |
|
|
|
const positions = this.generateSectorPositions(newCenter, r, sa, ea); |
|
|
|
entityData.positions = positions; |
|
|
|
entityData.entity.polygon.hierarchy = new Cesium.PolygonHierarchy(positions); |
|
|
|
entityData.entity.polyline.positions = positions; |
|
|
|
break; |
|
|
|
} |
|
|
|
case 'arrow': |
|
|
|
if (entityData.positions && entityData.positions.length >= 2) { |
|
|
|
const first = entityData.positions[0]; |
|
|
|
const last = entityData.positions[entityData.positions.length - 1]; |
|
|
|
const newFirst = Cesium.Cartesian3.add(first, delta, new Cesium.Cartesian3()); |
|
|
|
const newLast = Cesium.Cartesian3.add(last, delta, new Cesium.Cartesian3()); |
|
|
|
const arrowPositions = [newFirst, newLast]; |
|
|
|
const arrowColor = entityData.color || this.defaultStyles.arrow.color; |
|
|
|
const oldEntity = entityData.entity; |
|
|
|
this.viewer.entities.remove(oldEntity); |
|
|
|
const newEntity = this.viewer.entities.add({ |
|
|
|
id: oldEntity.id, |
|
|
|
name: oldEntity.name, |
|
|
|
polyline: { |
|
|
|
positions: arrowPositions, |
|
|
|
width: 12, |
|
|
|
material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString(arrowColor)), |
|
|
|
arcType: Cesium.ArcType.NONE, |
|
|
|
widthInMeters: false |
|
|
|
} |
|
|
|
}); |
|
|
|
entityData.entity = newEntity; |
|
|
|
entityData.positions = arrowPositions; |
|
|
|
entityData.points = [this.cartesianToLatLng(newFirst), this.cartesianToLatLng(newLast)]; |
|
|
|
newEntity.clickHandler = (e) => { |
|
|
|
this.selectEntity(entityData); |
|
|
|
e.stopPropagation(); |
|
|
|
}; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
entityData.entity.show = true; |
|
|
|
this.notifyDrawingEntitiesChanged(); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 清除空域位置调整模式 */ |
|
|
|
clearAirspacePositionEdit() { |
|
|
|
if (this.airspacePositionEditPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.airspacePositionEditPreviewEntity); |
|
|
|
this.airspacePositionEditPreviewEntity = null; |
|
|
|
} |
|
|
|
if (this.airspacePositionEditContext && this.airspacePositionEditContext.entityData) { |
|
|
|
this.airspacePositionEditContext.entityData.entity.show = true; |
|
|
|
} |
|
|
|
this.airspacePositionEditContext = null; |
|
|
|
if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); |
|
|
|
}, |
|
|
|
|
|
|
|
toggleRouteLock() { |
|
|
|
const ed = this.contextMenu.entityData; |
|
|
|
if (!ed || ed.type !== 'route' || ed.routeId == null) { |
|
|
|
@ -7424,6 +7856,10 @@ export default { |
|
|
|
if (property === 'color' && (entityData.type === 'polygon' || entityData.type === 'rectangle' || entityData.type === 'circle' || entityData.type === 'sector' || entityData.type === 'powerZone')) { |
|
|
|
entityData.opacity = 0.2 |
|
|
|
} |
|
|
|
// 命名:为多边形/矩形/圆形/扇形添加或更新中心标签 |
|
|
|
if (property === 'name' && ['polygon', 'rectangle', 'circle', 'sector'].includes(entityData.type)) { |
|
|
|
this.updateAirspaceEntityLabel(entityData) |
|
|
|
} |
|
|
|
// 更新实体样式 |
|
|
|
this.updateEntityStyle(entityData) |
|
|
|
// 若修改的是空域/威力区图形的样式,触发自动保存到房间,避免刷新后样式丢失 |
|
|
|
@ -7723,8 +8159,9 @@ export default { |
|
|
|
data = { |
|
|
|
points: entity.points || [], |
|
|
|
opacity: entity.opacity != null ? entity.opacity : 0, |
|
|
|
width: entity.width != null ? entity.width : 4, |
|
|
|
borderColor: entity.borderColor || entity.color |
|
|
|
width: entity.width != null ? entity.width : 2, |
|
|
|
borderColor: entity.borderColor || entity.color, |
|
|
|
name: entity.name || '' |
|
|
|
}; break |
|
|
|
case 'rectangle': |
|
|
|
if (entity.points && entity.points.length >= 2) { |
|
|
|
@ -7734,16 +8171,18 @@ export default { |
|
|
|
coordinates: { west: Math.min(...lngs), south: Math.min(...lats), east: Math.max(...lngs), north: Math.max(...lats) }, |
|
|
|
points: entity.points, |
|
|
|
opacity: entity.opacity != null ? entity.opacity : 0, |
|
|
|
width: entity.width != null ? entity.width : 4, |
|
|
|
borderColor: entity.borderColor || entity.color |
|
|
|
width: entity.width != null ? entity.width : 2, |
|
|
|
borderColor: entity.borderColor || entity.color, |
|
|
|
name: entity.name || '' |
|
|
|
} |
|
|
|
} else { |
|
|
|
data = { |
|
|
|
coordinates: entity.coordinates || {}, |
|
|
|
points: entity.points || [], |
|
|
|
opacity: entity.opacity != null ? entity.opacity : 0, |
|
|
|
width: entity.width != null ? entity.width : 4, |
|
|
|
borderColor: entity.borderColor || entity.color |
|
|
|
width: entity.width != null ? entity.width : 2, |
|
|
|
borderColor: entity.borderColor || entity.color, |
|
|
|
name: entity.name || '' |
|
|
|
} |
|
|
|
} |
|
|
|
break |
|
|
|
@ -7753,8 +8192,9 @@ export default { |
|
|
|
center: entity.points && entity.points[0] ? entity.points[0] : (entity.positions && entity.positions[0] ? this.cartesianToLatLng(entity.positions[0]) : (entity.center && typeof entity.center.lat === 'number' ? entity.center : null)), |
|
|
|
radius: entity.radius, |
|
|
|
opacity: entity.opacity != null ? entity.opacity : 0, |
|
|
|
width: entity.width != null ? entity.width : 4, |
|
|
|
borderColor: entity.borderColor || entity.color |
|
|
|
width: entity.width != null ? entity.width : 2, |
|
|
|
borderColor: entity.borderColor || entity.color, |
|
|
|
name: entity.name || '' |
|
|
|
}; break |
|
|
|
case 'ellipse': |
|
|
|
case 'hold_ellipse': |
|
|
|
@ -7771,8 +8211,9 @@ export default { |
|
|
|
startAngle: entity.startAngle, |
|
|
|
endAngle: entity.endAngle, |
|
|
|
opacity: entity.opacity != null ? entity.opacity : 0, |
|
|
|
width: entity.width != null ? entity.width : 3, |
|
|
|
borderColor: entity.borderColor || entity.color |
|
|
|
width: entity.width != null ? entity.width : 2, |
|
|
|
borderColor: entity.borderColor || entity.color, |
|
|
|
name: entity.name || '' |
|
|
|
}; break |
|
|
|
case 'arrow': |
|
|
|
data = { points: entity.points || [] }; break |
|
|
|
@ -8024,7 +8465,7 @@ export default { |
|
|
|
case 'polygon': { |
|
|
|
const polygonPositions = entityData.data.points.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat)) |
|
|
|
const polyOpacity = entityData.data.opacity != null ? entityData.data.opacity : 0 |
|
|
|
const polyWidth = entityData.data.width != null ? entityData.data.width : 4 |
|
|
|
const polyWidth = entityData.data.width != null ? entityData.data.width : 2 |
|
|
|
const polyBorderColor = entityData.data.borderColor || color |
|
|
|
entity = this.viewer.entities.add({ |
|
|
|
polygon: { |
|
|
|
@ -8038,7 +8479,23 @@ export default { |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}) |
|
|
|
break |
|
|
|
const polyEntityData = { |
|
|
|
id: entity.id, |
|
|
|
type: 'polygon', |
|
|
|
points: entityData.data.points, |
|
|
|
positions: polygonPositions, |
|
|
|
entity, |
|
|
|
color, |
|
|
|
borderColor: polyBorderColor, |
|
|
|
opacity: polyOpacity, |
|
|
|
width: polyWidth, |
|
|
|
label: entityData.label || '面', |
|
|
|
name: entityData.data.name || '' |
|
|
|
} |
|
|
|
this.allEntities.push(polyEntityData) |
|
|
|
if (polyEntityData.name) this.updateAirspaceEntityLabel(polyEntityData) |
|
|
|
this.notifyDrawingEntitiesChanged() |
|
|
|
return |
|
|
|
} |
|
|
|
case 'rectangle': { |
|
|
|
let rectCoords = entityData.data.coordinates |
|
|
|
@ -8049,7 +8506,7 @@ export default { |
|
|
|
} |
|
|
|
if (!rectCoords) break |
|
|
|
const rectOpacity = entityData.data.opacity != null ? entityData.data.opacity : 0 |
|
|
|
const rectWidth = entityData.data.width != null ? entityData.data.width : 4 |
|
|
|
const rectWidth = entityData.data.width != null ? entityData.data.width : 2 |
|
|
|
const rectBorderColor = entityData.data.borderColor || color |
|
|
|
const rectColor = Cesium.Color.fromCssColorString(color).withAlpha(rectOpacity) |
|
|
|
const southwest = Cesium.Cartesian3.fromDegrees(rectCoords.west, rectCoords.south) |
|
|
|
@ -8069,7 +8526,7 @@ export default { |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}) |
|
|
|
this.allEntities.push({ |
|
|
|
const rectEntityData = { |
|
|
|
id: entity.id, |
|
|
|
type: 'rectangle', |
|
|
|
points: entityData.data.points || [], |
|
|
|
@ -8079,8 +8536,11 @@ export default { |
|
|
|
borderColor: rectBorderColor, |
|
|
|
opacity: rectOpacity, |
|
|
|
width: rectWidth, |
|
|
|
label: entityData.label || '矩形' |
|
|
|
}) |
|
|
|
label: entityData.label || '矩形', |
|
|
|
name: entityData.data.name || '' |
|
|
|
} |
|
|
|
this.allEntities.push(rectEntityData) |
|
|
|
if (rectEntityData.name) this.updateAirspaceEntityLabel(rectEntityData) |
|
|
|
return |
|
|
|
} |
|
|
|
case 'circle': { |
|
|
|
@ -8092,7 +8552,7 @@ export default { |
|
|
|
const circleCenter = Cesium.Cartesian3.fromDegrees(entityData.data.center.lng, entityData.data.center.lat) |
|
|
|
const circlePositions = this.generateCirclePositions(circleCenter, radius) |
|
|
|
const circleOpacity = entityData.data.opacity != null ? entityData.data.opacity : 0 |
|
|
|
const circleWidth = entityData.data.width != null ? entityData.data.width : 4 |
|
|
|
const circleWidth = entityData.data.width != null ? entityData.data.width : 2 |
|
|
|
const circleBorderColor = entityData.data.borderColor || color |
|
|
|
entity = this.viewer.entities.add({ |
|
|
|
position: circleCenter, |
|
|
|
@ -8109,7 +8569,24 @@ export default { |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}) |
|
|
|
break |
|
|
|
const circleEntityData = { |
|
|
|
id: entity.id, |
|
|
|
type: 'circle', |
|
|
|
points: [entityData.data.center], |
|
|
|
positions: [circleCenter], |
|
|
|
entity, |
|
|
|
radius, |
|
|
|
color, |
|
|
|
borderColor: circleBorderColor, |
|
|
|
opacity: circleOpacity, |
|
|
|
width: circleWidth, |
|
|
|
label: entityData.label || '圆形', |
|
|
|
name: entityData.data.name || '' |
|
|
|
} |
|
|
|
this.allEntities.push(circleEntityData) |
|
|
|
if (circleEntityData.name) this.updateAirspaceEntityLabel(circleEntityData) |
|
|
|
this.notifyDrawingEntitiesChanged() |
|
|
|
return |
|
|
|
} |
|
|
|
case 'hold_circle': { |
|
|
|
const hcRadius = entityData.data.radius || 1000 |
|
|
|
@ -8182,7 +8659,7 @@ export default { |
|
|
|
const startAngle = d.startAngle != null ? d.startAngle : 0 |
|
|
|
const endAngle = d.endAngle != null ? d.endAngle : Math.PI * 0.5 |
|
|
|
const sectorOpacity = d.opacity != null ? d.opacity : 0 |
|
|
|
const sectorWidth = d.width != null ? d.width : 3 |
|
|
|
const sectorWidth = d.width != null ? d.width : 2 |
|
|
|
const sectorBorderColor = d.borderColor || color |
|
|
|
const positions = this.generateSectorPositions(center, d.radius, startAngle, endAngle) |
|
|
|
entity = this.viewer.entities.add({ |
|
|
|
@ -8197,7 +8674,7 @@ export default { |
|
|
|
arcType: Cesium.ArcType.NONE |
|
|
|
} |
|
|
|
}) |
|
|
|
this.allEntities.push({ |
|
|
|
const sectorEntityData = { |
|
|
|
id: entity.id, |
|
|
|
type: 'sector', |
|
|
|
center: d.center, |
|
|
|
@ -8210,15 +8687,19 @@ export default { |
|
|
|
borderColor: sectorBorderColor, |
|
|
|
opacity: sectorOpacity, |
|
|
|
width: sectorWidth, |
|
|
|
label: entityData.label || '扇形' |
|
|
|
}) |
|
|
|
label: entityData.label || '扇形', |
|
|
|
name: entityData.data.name || '' |
|
|
|
} |
|
|
|
this.allEntities.push(sectorEntityData) |
|
|
|
if (sectorEntityData.name) this.updateAirspaceEntityLabel(sectorEntityData) |
|
|
|
this.notifyDrawingEntitiesChanged() |
|
|
|
return |
|
|
|
} |
|
|
|
case 'arrow': { |
|
|
|
const pts = entityData.data.points |
|
|
|
if (!pts || pts.length < 2) break |
|
|
|
const arrowPositions = pts.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat)) |
|
|
|
const allPositions = pts.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat)) |
|
|
|
const arrowPositions = allPositions.length === 2 ? allPositions : [allPositions[0], allPositions[allPositions.length - 1]] |
|
|
|
entity = this.viewer.entities.add({ |
|
|
|
polyline: { |
|
|
|
positions: arrowPositions, |
|
|
|
@ -8231,7 +8712,7 @@ export default { |
|
|
|
this.allEntities.push({ |
|
|
|
id: entity.id, |
|
|
|
type: 'arrow', |
|
|
|
points: pts, |
|
|
|
points: [pts[0], pts[pts.length - 1]], |
|
|
|
positions: arrowPositions, |
|
|
|
entity, |
|
|
|
color, |
|
|
|
@ -8353,14 +8834,18 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
if (entity) { |
|
|
|
this.allEntities.push({ |
|
|
|
const pushed = { |
|
|
|
id: entity.id, |
|
|
|
type: entityData.type, |
|
|
|
label: entityData.label, |
|
|
|
color: color, |
|
|
|
entity, |
|
|
|
...entityData.data |
|
|
|
}) |
|
|
|
} |
|
|
|
this.allEntities.push(pushed) |
|
|
|
if (['polygon', 'circle'].includes(entityData.type) && pushed.name) { |
|
|
|
this.updateAirspaceEntityLabel(pushed) |
|
|
|
} |
|
|
|
if (this.getDrawingEntityTypes().includes(entityData.type)) this.notifyDrawingEntitiesChanged() |
|
|
|
} |
|
|
|
}, |
|
|
|
|