|
|
|
@ -40,14 +40,11 @@ |
|
|
|
@show-transform-box="showPlatformIconTransformBox" |
|
|
|
@toggle-route-label="toggleRouteLabelVisibility" |
|
|
|
@toggle-route-lock="toggleRouteLock" |
|
|
|
<<<<<<< HEAD |
|
|
|
@edit-platform="openEditPlatformDialog" |
|
|
|
@power-zone="openPowerZoneDialog" |
|
|
|
======= |
|
|
|
@start-route-before-platform="handleStartRouteBeforePlatform" |
|
|
|
@start-route-after-platform="handleStartRouteAfterPlatform" |
|
|
|
@copy-route="handleCopyRouteFromMenu" |
|
|
|
>>>>>>> 9363256d79ba1d213479769fa0a97c737d179e32 |
|
|
|
@edit-platform="openEditPlatformDialog" |
|
|
|
@power-zone="openPowerZoneDialog" |
|
|
|
/> |
|
|
|
|
|
|
|
<!-- 定位弹窗 --> |
|
|
|
@ -136,7 +133,7 @@ |
|
|
|
/> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="填充颜色"> |
|
|
|
<el-color-picker v-model="powerZoneForm.color" show-alpha size="small" /> |
|
|
|
<el-color-picker v-model="powerZoneForm.color" show-alpha size="small" /> |
|
|
|
</el-form-item> |
|
|
|
</el-form> |
|
|
|
<span slot="footer" class="dialog-footer"> |
|
|
|
@ -291,16 +288,13 @@ export default { |
|
|
|
}, |
|
|
|
// 航线飞机标牌显示状态:routeId -> true 显示 / false 隐藏,不设则默认显示 |
|
|
|
routeLabelVisible: {}, |
|
|
|
<<<<<<< HEAD |
|
|
|
// 航线飞机标牌样式:routeId -> { fontSize, fontColor } |
|
|
|
routeLabelStyles: {}, |
|
|
|
// 航线上锁状态:routeId -> true 上锁(不可编辑)/ false 或未设 可编辑 |
|
|
|
routeLocked: {}, |
|
|
|
======= |
|
|
|
// 航线上锁状态由父组件通过 prop routeLocked 传入,与右侧列表锁图标同步 |
|
|
|
// 从平台右键进入的航线绘制:{ platformInfo: { platformId, platform }, mode: 'before'|'after' } |
|
|
|
platformRouteDrawing: null, |
|
|
|
>>>>>>> 9363256d79ba1d213479769fa0a97c737d179e32 |
|
|
|
// 默认样式 |
|
|
|
defaultStyles: { |
|
|
|
point: { color: '#FF0000', size: 12 }, |
|
|
|
@ -1031,11 +1025,11 @@ export default { |
|
|
|
canvas.height = img.height; |
|
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
ctx.drawImage(img, 0, 0); |
|
|
|
|
|
|
|
|
|
|
|
// 获取像素数据 |
|
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|
|
|
const data = imageData.data; |
|
|
|
|
|
|
|
|
|
|
|
// 遍历像素,将所有非透明像素转为白色 |
|
|
|
for(let i = 0; i < data.length; i += 4) { |
|
|
|
// Alpha > 0 则处理 |
|
|
|
@ -1046,7 +1040,7 @@ export default { |
|
|
|
// Alpha 保持不变 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ctx.putImageData(imageData, 0, 0); |
|
|
|
resolve(canvas); // Cesium 支持直接使用 Canvas |
|
|
|
} catch (e) { |
|
|
|
@ -1074,19 +1068,19 @@ export default { |
|
|
|
*/ |
|
|
|
createRoundedLabelCanvas(options) { |
|
|
|
const { name, altitude, speed, heading, fontSize = 16, fontColor = '#000000' } = options; |
|
|
|
|
|
|
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
|
|
|
|
|
|
// 设置字体 |
|
|
|
const font = `${fontSize}px "Microsoft YaHei"`; |
|
|
|
ctx.font = font; |
|
|
|
|
|
|
|
|
|
|
|
// 颜色定义 |
|
|
|
const colorName = '#0078FF'; // 平台名蓝色 |
|
|
|
const colorLabel = '#888888'; // 属性名灰色 |
|
|
|
const colorValue = fontColor; // 属性值(默认黑,可配置) |
|
|
|
|
|
|
|
|
|
|
|
// 文本内容 |
|
|
|
const labelAlt = '高度: '; |
|
|
|
const labelSpeed = ' 速度: '; |
|
|
|
@ -1097,13 +1091,13 @@ export default { |
|
|
|
|
|
|
|
// 计算各部分宽度 |
|
|
|
const wName = ctx.measureText(name).width; |
|
|
|
|
|
|
|
|
|
|
|
const wLabelAlt = ctx.measureText(labelAlt).width; |
|
|
|
const wValAlt = ctx.measureText(textAlt).width; |
|
|
|
|
|
|
|
|
|
|
|
const wLabelSpeed = ctx.measureText(labelSpeed).width; |
|
|
|
const wValSpeed = ctx.measureText(textSpeed).width; |
|
|
|
|
|
|
|
|
|
|
|
const wLabelHeading = ctx.measureText(labelHeading).width; |
|
|
|
const wValHeading = ctx.measureText(textHeading).width; |
|
|
|
|
|
|
|
@ -1112,7 +1106,7 @@ export default { |
|
|
|
// 第二行:高度 + 速度 + 航向 |
|
|
|
const line1Width = wName; |
|
|
|
const line2Width = wLabelAlt + wValAlt + wLabelSpeed + wValSpeed + wLabelHeading + wValHeading; |
|
|
|
|
|
|
|
|
|
|
|
const paddingX = 12; |
|
|
|
const paddingY = 8; |
|
|
|
const lineHeight = fontSize * 1.4; |
|
|
|
@ -1122,7 +1116,7 @@ export default { |
|
|
|
// 设置 Canvas 大小 (根据设备像素比优化清晰度,这里简单处理,Cesium Billboard 会自动缩放) |
|
|
|
canvas.width = totalWidth; |
|
|
|
canvas.height = totalHeight; |
|
|
|
|
|
|
|
|
|
|
|
// 重新设置字体(因为重设 width 会重置 context) |
|
|
|
ctx.font = font; |
|
|
|
ctx.textBaseline = 'top'; |
|
|
|
@ -1142,7 +1136,7 @@ export default { |
|
|
|
ctx.quadraticCurveTo(0, 0, r, 0); |
|
|
|
ctx.closePath(); |
|
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
|
|
|
// 描边(可选,这里加个淡淡的边框) |
|
|
|
ctx.strokeStyle = '#e0e0e0'; |
|
|
|
ctx.lineWidth = 1; |
|
|
|
@ -1163,7 +1157,7 @@ export default { |
|
|
|
ctx.fillStyle = colorLabel; |
|
|
|
ctx.fillText(labelAlt, currentX, line2Y); |
|
|
|
currentX += wLabelAlt; |
|
|
|
|
|
|
|
|
|
|
|
ctx.fillStyle = colorValue; |
|
|
|
ctx.fillText(textAlt, currentX, line2Y); |
|
|
|
currentX += wValAlt; |
|
|
|
@ -1172,7 +1166,7 @@ export default { |
|
|
|
ctx.fillStyle = colorLabel; |
|
|
|
ctx.fillText(labelSpeed, currentX, line2Y); |
|
|
|
currentX += wLabelSpeed; |
|
|
|
|
|
|
|
|
|
|
|
ctx.fillStyle = colorValue; |
|
|
|
ctx.fillText(textSpeed, currentX, line2Y); |
|
|
|
currentX += wValSpeed; |
|
|
|
@ -1181,7 +1175,7 @@ export default { |
|
|
|
ctx.fillStyle = colorLabel; |
|
|
|
ctx.fillText(labelHeading, currentX, line2Y); |
|
|
|
currentX += wLabelHeading; |
|
|
|
|
|
|
|
|
|
|
|
ctx.fillStyle = colorValue; |
|
|
|
ctx.fillText(textHeading, currentX, line2Y); |
|
|
|
|
|
|
|
@ -1642,7 +1636,7 @@ export default { |
|
|
|
// 注意:此时如果用户没有修改过颜色,target.billboard.color 默认为 WHITE,显示白图 |
|
|
|
// 如果用户希望"未编辑时显示原图",这里可能会把黑图变成白图。 |
|
|
|
// 但根据用户需求"一开始加入平台时将平台的颜色固定为白色",这正是想要的效果。 |
|
|
|
|
|
|
|
|
|
|
|
// 触发一次渲染刷新 |
|
|
|
if (this.viewer.scene.requestRenderMode) { |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
@ -1664,10 +1658,10 @@ export default { |
|
|
|
headingDeg: initialHeadingDeg |
|
|
|
}); |
|
|
|
const labelShow = this.routeLabelVisible[routeId] !== false |
|
|
|
|
|
|
|
|
|
|
|
// 初始化样式 |
|
|
|
if (!this.routeLabelStyles[routeId]) { |
|
|
|
this.$set(this.routeLabelStyles, routeId, { fontSize: 16, fontColor: '#000000' }); |
|
|
|
this.$set(this.routeLabelStyles, routeId, { fontSize: 16, fontColor: '#000000' }); |
|
|
|
} |
|
|
|
const currentStyle = this.routeLabelStyles[routeId]; |
|
|
|
|
|
|
|
@ -1698,10 +1692,10 @@ export default { |
|
|
|
}); |
|
|
|
// 缓存初始数据,供样式更新使用 |
|
|
|
labelEntity.labelDataCache = { |
|
|
|
name: (platform && platform.name) || '平台', |
|
|
|
altitude: firstAlt, |
|
|
|
speed: firstSpeed, |
|
|
|
headingDeg: initialHeadingDeg |
|
|
|
name: (platform && platform.name) || '平台', |
|
|
|
altitude: firstAlt, |
|
|
|
speed: firstSpeed, |
|
|
|
headingDeg: initialHeadingDeg |
|
|
|
}; |
|
|
|
} |
|
|
|
// 绘制连线(含盘旋弧) |
|
|
|
@ -2333,62 +2327,62 @@ export default { |
|
|
|
Object.assign(labelEntity.labelDataCache, labelData); |
|
|
|
|
|
|
|
if (labelEntity.billboard) { |
|
|
|
const style = this.routeLabelStyles[routeId] || { fontSize: 16, fontColor: '#000000' }; |
|
|
|
|
|
|
|
// 预处理显示数据(取整),既解决了小数点过长问题,也用于差异检测 |
|
|
|
const displayData = { |
|
|
|
name: labelData.name || '平台', |
|
|
|
altitude: Math.round(Number(labelData.altitude || 0)), |
|
|
|
speed: Math.round(Number(labelData.speed || 0)), |
|
|
|
heading: Math.round(Number(labelData.headingDeg || 0)) |
|
|
|
}; |
|
|
|
|
|
|
|
// 差异检测:如果关键数据和样式都没变,就不重绘 |
|
|
|
const last = labelEntity._lastRenderParams; |
|
|
|
const now = Date.now(); |
|
|
|
// 节流控制:只有当数据变化且距离上次更新超过 1000ms 时才更新(首次除外) |
|
|
|
// 这样可以保证位置平滑移动(position每帧更新),但纹理贴图每秒只更新一次,彻底解决闪烁 |
|
|
|
if (last && |
|
|
|
last.name === displayData.name && |
|
|
|
last.altitude === displayData.altitude && |
|
|
|
last.speed === displayData.speed && |
|
|
|
last.heading === displayData.heading && |
|
|
|
last.fontSize === style.fontSize && |
|
|
|
last.fontColor === style.fontColor) { |
|
|
|
// 数据完全一致,无需更新 |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果数据变了,但还没到 1秒 间隔,且不是强制更新(比如刚修改了样式),则跳过 |
|
|
|
if (last && (now - (labelEntity._lastUpdateTime || 0) < 1000)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const canvas = this.createRoundedLabelCanvas({ |
|
|
|
name: displayData.name, |
|
|
|
altitude: displayData.altitude, |
|
|
|
speed: displayData.speed, |
|
|
|
heading: displayData.heading, |
|
|
|
fontSize: style.fontSize, |
|
|
|
fontColor: style.fontColor |
|
|
|
}); |
|
|
|
// 直接赋值 canvas,避免 ConstantProperty 包装可能带来的额外开销(Cesium 会自动处理) |
|
|
|
labelEntity.billboard.image = canvas; |
|
|
|
|
|
|
|
// 记录本次渲染参数和时间 |
|
|
|
labelEntity._lastRenderParams = { |
|
|
|
...displayData, |
|
|
|
fontSize: style.fontSize, |
|
|
|
fontColor: style.fontColor |
|
|
|
}; |
|
|
|
labelEntity._lastUpdateTime = now; |
|
|
|
|
|
|
|
// 确保在 requestRenderMode 下能刷出来 |
|
|
|
if (this.viewer.scene.requestRenderMode) { |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
const style = this.routeLabelStyles[routeId] || { fontSize: 16, fontColor: '#000000' }; |
|
|
|
|
|
|
|
// 预处理显示数据(取整),既解决了小数点过长问题,也用于差异检测 |
|
|
|
const displayData = { |
|
|
|
name: labelData.name || '平台', |
|
|
|
altitude: Math.round(Number(labelData.altitude || 0)), |
|
|
|
speed: Math.round(Number(labelData.speed || 0)), |
|
|
|
heading: Math.round(Number(labelData.headingDeg || 0)) |
|
|
|
}; |
|
|
|
|
|
|
|
// 差异检测:如果关键数据和样式都没变,就不重绘 |
|
|
|
const last = labelEntity._lastRenderParams; |
|
|
|
const now = Date.now(); |
|
|
|
// 节流控制:只有当数据变化且距离上次更新超过 1000ms 时才更新(首次除外) |
|
|
|
// 这样可以保证位置平滑移动(position每帧更新),但纹理贴图每秒只更新一次,彻底解决闪烁 |
|
|
|
if (last && |
|
|
|
last.name === displayData.name && |
|
|
|
last.altitude === displayData.altitude && |
|
|
|
last.speed === displayData.speed && |
|
|
|
last.heading === displayData.heading && |
|
|
|
last.fontSize === style.fontSize && |
|
|
|
last.fontColor === style.fontColor) { |
|
|
|
// 数据完全一致,无需更新 |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果数据变了,但还没到 1秒 间隔,且不是强制更新(比如刚修改了样式),则跳过 |
|
|
|
if (last && (now - (labelEntity._lastUpdateTime || 0) < 1000)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const canvas = this.createRoundedLabelCanvas({ |
|
|
|
name: displayData.name, |
|
|
|
altitude: displayData.altitude, |
|
|
|
speed: displayData.speed, |
|
|
|
heading: displayData.heading, |
|
|
|
fontSize: style.fontSize, |
|
|
|
fontColor: style.fontColor |
|
|
|
}); |
|
|
|
// 直接赋值 canvas,避免 ConstantProperty 包装可能带来的额外开销(Cesium 会自动处理) |
|
|
|
labelEntity.billboard.image = canvas; |
|
|
|
|
|
|
|
// 记录本次渲染参数和时间 |
|
|
|
labelEntity._lastRenderParams = { |
|
|
|
...displayData, |
|
|
|
fontSize: style.fontSize, |
|
|
|
fontColor: style.fontColor |
|
|
|
}; |
|
|
|
labelEntity._lastUpdateTime = now; |
|
|
|
|
|
|
|
// 确保在 requestRenderMode 下能刷出来 |
|
|
|
if (this.viewer.scene.requestRenderMode) { |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
} else if (labelEntity.label) { |
|
|
|
labelEntity.label.text = this.formatPlatformLabelText(labelData); |
|
|
|
labelEntity.label.text = this.formatPlatformLabelText(labelData); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -5245,15 +5239,15 @@ export default { |
|
|
|
return |
|
|
|
} |
|
|
|
const routeId = ed.routeId |
|
|
|
|
|
|
|
|
|
|
|
// 读取标牌设置 |
|
|
|
const labelEntity = this.viewer.entities.getById(`route-platform-label-${routeId}`) |
|
|
|
let fontSize = 16 |
|
|
|
let fontColor = '#333333' |
|
|
|
|
|
|
|
if (this.routeLabelStyles[routeId]) { |
|
|
|
fontSize = this.routeLabelStyles[routeId].fontSize; |
|
|
|
fontColor = this.routeLabelStyles[routeId].fontColor; |
|
|
|
fontSize = this.routeLabelStyles[routeId].fontSize; |
|
|
|
fontColor = this.routeLabelStyles[routeId].fontColor; |
|
|
|
} else if (labelEntity && labelEntity.label) { |
|
|
|
const now = Cesium.JulianDate.now() |
|
|
|
const fontValue = labelEntity.label.font && labelEntity.label.font.getValue |
|
|
|
@ -5296,7 +5290,7 @@ export default { |
|
|
|
this.editPlatformForm.fontColor = fontColor |
|
|
|
this.editPlatformForm.iconSize = iconSize |
|
|
|
this.editPlatformForm.iconColor = iconColor |
|
|
|
|
|
|
|
|
|
|
|
this.contextMenu.visible = false |
|
|
|
this.editPlatformDialogVisible = true |
|
|
|
}, |
|
|
|
@ -5315,7 +5309,7 @@ export default { |
|
|
|
this.editPlatformDialogVisible = false |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存样式 |
|
|
|
const fontSize = Math.max(10, Math.min(32, Number(this.editPlatformForm.fontSize) || 16)); |
|
|
|
const fontColor = this.editPlatformForm.fontColor || '#333333'; |
|
|
|
@ -5325,23 +5319,23 @@ export default { |
|
|
|
const labelEntity = this.viewer.entities.getById(`route-platform-label-${routeId}`) |
|
|
|
if (labelEntity) { |
|
|
|
if (labelEntity.billboard) { |
|
|
|
const data = labelEntity.labelDataCache || { name: '平台', altitude: 0, speed: 0, headingDeg: 0 }; |
|
|
|
const canvas = this.createRoundedLabelCanvas({ |
|
|
|
name: data.name, |
|
|
|
altitude: data.altitude, |
|
|
|
speed: data.speed, |
|
|
|
heading: data.headingDeg, |
|
|
|
fontSize: fontSize, |
|
|
|
fontColor: fontColor |
|
|
|
}); |
|
|
|
// 使用 ConstantProperty 确保更新 |
|
|
|
labelEntity.billboard.image = new Cesium.ConstantProperty(canvas); |
|
|
|
const data = labelEntity.labelDataCache || { name: '平台', altitude: 0, speed: 0, headingDeg: 0 }; |
|
|
|
const canvas = this.createRoundedLabelCanvas({ |
|
|
|
name: data.name, |
|
|
|
altitude: data.altitude, |
|
|
|
speed: data.speed, |
|
|
|
heading: data.headingDeg, |
|
|
|
fontSize: fontSize, |
|
|
|
fontColor: fontColor |
|
|
|
}); |
|
|
|
// 使用 ConstantProperty 确保更新 |
|
|
|
labelEntity.billboard.image = new Cesium.ConstantProperty(canvas); |
|
|
|
} else if (labelEntity.label) { |
|
|
|
labelEntity.label.font = `${fontSize}px Microsoft YaHei` |
|
|
|
labelEntity.label.fillColor = Cesium.Color.fromCssColorString(fontColor) |
|
|
|
|
|
|
|
// 优化透明度:背景颜色透明度调低,看起来更清爽 |
|
|
|
labelEntity.label.backgroundColor = Cesium.Color.fromCssColorString('rgba(255, 255, 255, 0.6)') |
|
|
|
labelEntity.label.font = `${fontSize}px Microsoft YaHei` |
|
|
|
labelEntity.label.fillColor = Cesium.Color.fromCssColorString(fontColor) |
|
|
|
|
|
|
|
// 优化透明度:背景颜色透明度调低,看起来更清爽 |
|
|
|
labelEntity.label.backgroundColor = Cesium.Color.fromCssColorString('rgba(255, 255, 255, 0.6)') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -5394,7 +5388,7 @@ export default { |
|
|
|
this.powerZoneDialogVisible = false |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 移除旧的威力区(如果存在) |
|
|
|
const oldZoneId = `power-zone-${routeId}` |
|
|
|
this.viewer.entities.removeById(oldZoneId) |
|
|
|
|