|
|
|
@ -1064,15 +1064,17 @@ export default { |
|
|
|
* @param {string} options.heading 航向值 |
|
|
|
* @param {number} options.fontSize 字号,默认 16 |
|
|
|
* @param {string} options.fontColor 属性值颜色,默认黑色 |
|
|
|
* @returns {HTMLCanvasElement} |
|
|
|
* @returns {{ canvas: HTMLCanvasElement, scale: number }} 高分屏下 scale 供 billboard.scale 使用以保持视觉尺寸 |
|
|
|
*/ |
|
|
|
createRoundedLabelCanvas(options) { |
|
|
|
const { name, altitude, speed, heading, fontSize = 16, fontColor = '#000000' } = options; |
|
|
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
// 高分屏清晰度:按设备像素比放大绘制,再通过 billboard.scale 缩回,文字边缘更锐利 |
|
|
|
const dpr = Math.min(2, window.devicePixelRatio || 1); |
|
|
|
|
|
|
|
// 设置字体 |
|
|
|
// 设置字体(先用于测量) |
|
|
|
const font = `${fontSize}px "Microsoft YaHei"`; |
|
|
|
ctx.font = font; |
|
|
|
|
|
|
|
@ -1089,7 +1091,7 @@ export default { |
|
|
|
const textSpeed = speed + 'km/h'; |
|
|
|
const textHeading = Math.round(heading) + '°'; |
|
|
|
|
|
|
|
// 计算各部分宽度 |
|
|
|
// 计算各部分宽度(逻辑尺寸) |
|
|
|
const wName = ctx.measureText(name).width; |
|
|
|
|
|
|
|
const wLabelAlt = ctx.measureText(labelAlt).width; |
|
|
|
@ -1101,9 +1103,7 @@ export default { |
|
|
|
const wLabelHeading = ctx.measureText(labelHeading).width; |
|
|
|
const wValHeading = ctx.measureText(textHeading).width; |
|
|
|
|
|
|
|
// 总宽与总高 |
|
|
|
// 第一行:名字居中 |
|
|
|
// 第二行:高度 + 速度 + 航向 |
|
|
|
// 总宽与总高(逻辑尺寸) |
|
|
|
const line1Width = wName; |
|
|
|
const line2Width = wLabelAlt + wValAlt + wLabelSpeed + wValSpeed + wLabelHeading + wValHeading; |
|
|
|
|
|
|
|
@ -1113,9 +1113,10 @@ export default { |
|
|
|
const totalWidth = Math.max(line1Width, line2Width) + paddingX * 2; |
|
|
|
const totalHeight = lineHeight * 2 + paddingY * 2; |
|
|
|
|
|
|
|
// 设置 Canvas 大小 (根据设备像素比优化清晰度,这里简单处理,Cesium Billboard 会自动缩放) |
|
|
|
canvas.width = totalWidth; |
|
|
|
canvas.height = totalHeight; |
|
|
|
// Canvas 实际像素尺寸按 dpr 放大,绘制时用 scale 保持逻辑坐标 |
|
|
|
canvas.width = Math.ceil(totalWidth * dpr); |
|
|
|
canvas.height = Math.ceil(totalHeight * dpr); |
|
|
|
ctx.scale(dpr, dpr); |
|
|
|
|
|
|
|
// 重新设置字体(因为重设 width 会重置 context) |
|
|
|
ctx.font = font; |
|
|
|
@ -1179,7 +1180,8 @@ export default { |
|
|
|
ctx.fillStyle = colorValue; |
|
|
|
ctx.fillText(textHeading, currentX, line2Y); |
|
|
|
|
|
|
|
return canvas; |
|
|
|
// 返回 canvas 与显示缩放比,供 billboard 设置 scale 以保持视觉尺寸一致 |
|
|
|
return { canvas, scale: 1 / dpr }; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 伸缩框旋转手柄图标 SVG:蓝底、白边、白色弧形箭头 */ |
|
|
|
@ -1619,7 +1621,7 @@ export default { |
|
|
|
image: fullUrl, |
|
|
|
width: 144, |
|
|
|
height: 144, |
|
|
|
color: Cesium.Color.WHITE, // 初始颜色为白色(不染色) |
|
|
|
color: Cesium.Color.BLACK, // 航线飞机默认黑色,可通过编辑飞机颜色功能修改 |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.CENTER, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
scaleByDistance: new Cesium.NearFarScalar(500, 2.0, 200000, 0.4), |
|
|
|
@ -1633,9 +1635,7 @@ export default { |
|
|
|
const target = this.viewer.entities.getById(platformBillboardId); |
|
|
|
if (target && target.billboard) { |
|
|
|
target.billboard.image = whiteImage; |
|
|
|
// 注意:此时如果用户没有修改过颜色,target.billboard.color 默认为 WHITE,显示白图 |
|
|
|
// 如果用户希望"未编辑时显示原图",这里可能会把黑图变成白图。 |
|
|
|
// 但根据用户需求"一开始加入平台时将平台的颜色固定为白色",这正是想要的效果。 |
|
|
|
// 此时 target.billboard.color 已在创建时设为 BLACK(航线飞机默认黑色),染色后显示为黑色图标 |
|
|
|
|
|
|
|
// 触发一次渲染刷新 |
|
|
|
if (this.viewer.scene.requestRenderMode) { |
|
|
|
@ -1665,8 +1665,8 @@ export default { |
|
|
|
} |
|
|
|
const currentStyle = this.routeLabelStyles[routeId]; |
|
|
|
|
|
|
|
// 使用 Canvas 生成圆角标牌 |
|
|
|
const labelCanvas = this.createRoundedLabelCanvas({ |
|
|
|
// 使用 Canvas 生成圆角标牌(高分屏下 scale 用于保持视觉尺寸) |
|
|
|
const labelResult = this.createRoundedLabelCanvas({ |
|
|
|
name: (platform && platform.name) || '平台', |
|
|
|
altitude: firstAlt, |
|
|
|
speed: firstSpeed, |
|
|
|
@ -1682,7 +1682,8 @@ export default { |
|
|
|
show: labelShow, |
|
|
|
properties: { routeId: routeId }, |
|
|
|
billboard: { |
|
|
|
image: labelCanvas, // 使用 Canvas 作为图片 |
|
|
|
image: labelResult.canvas, |
|
|
|
scale: labelResult.scale, |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
pixelOffset: new Cesium.Cartesian2(0, -42), |
|
|
|
@ -2327,60 +2328,60 @@ 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; |
|
|
|
} |
|
|
|
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 labelResult = this.createRoundedLabelCanvas({ |
|
|
|
name: displayData.name, |
|
|
|
altitude: displayData.altitude, |
|
|
|
speed: displayData.speed, |
|
|
|
heading: displayData.heading, |
|
|
|
fontSize: style.fontSize, |
|
|
|
fontColor: style.fontColor |
|
|
|
}); |
|
|
|
labelEntity.billboard.image = labelResult.canvas; |
|
|
|
labelEntity.billboard.scale = labelResult.scale; |
|
|
|
|
|
|
|
// 记录本次渲染参数和时间 |
|
|
|
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); |
|
|
|
} |
|
|
|
@ -2407,6 +2408,8 @@ export default { |
|
|
|
// 确保 Cesium 已加载 |
|
|
|
Cesium.buildModuleUrl.setBaseUrl(window.CESIUM_BASE_URL) |
|
|
|
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjN2MzMmE5OS01NGU3LTQzOGQtYjdjZi1mNGIwZTFjZjQ0NmEiLCJpZCI6MTQ0MDc2LCJpYXQiOjE2ODU3NjY1OTN9.iCmFY-5WNdvyAT-EO2j-unrFm4ZN9J6aSuB2wElQZ-I' |
|
|
|
// 使用设备像素比提升高分屏下字体与图形清晰度(上限 2 兼顾性能) |
|
|
|
const resolutionScale = Math.min(2, window.devicePixelRatio || 1); |
|
|
|
this.viewer = new Cesium.Viewer('cesiumViewer', { |
|
|
|
animation: false, |
|
|
|
fullscreenButton: false, |
|
|
|
@ -2424,6 +2427,7 @@ export default { |
|
|
|
imageryProvider: false, |
|
|
|
terrainProvider: new Cesium.EllipsoidTerrainProvider(), |
|
|
|
baseLayer: false, |
|
|
|
resolutionScale, |
|
|
|
requestRenderMode: true, |
|
|
|
maximumRenderTimeChange: Infinity, |
|
|
|
contextOptions: { |
|
|
|
@ -5319,23 +5323,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 labelResult = this.createRoundedLabelCanvas({ |
|
|
|
name: data.name, |
|
|
|
altitude: data.altitude, |
|
|
|
speed: data.speed, |
|
|
|
heading: data.headingDeg, |
|
|
|
fontSize: fontSize, |
|
|
|
fontColor: fontColor |
|
|
|
}); |
|
|
|
labelEntity.billboard.image = new Cesium.ConstantProperty(labelResult.canvas); |
|
|
|
labelEntity.billboard.scale = labelResult.scale; |
|
|
|
} else if (labelEntity.label) { |
|
|
|
labelEntity.label.font = `${fontSize}px Microsoft YaHei` |
|
|
|
labelEntity.label.fillColor = Cesium.Color.fromCssColorString(fontColor) |
|
|
|
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.backgroundColor = Cesium.Color.fromCssColorString('rgba(255, 255, 255, 0.6)') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|