|
|
|
@ -191,16 +191,16 @@ export default { |
|
|
|
// 过滤掉与航线相关的实体,保留用户绘制的线段等实体 |
|
|
|
this.allEntities = this.allEntities.filter(item => { |
|
|
|
// 检查实体是否与航线相关 |
|
|
|
const isRouteRelated = item.type === 'route' || item.routeId || |
|
|
|
(item.entity && item.entity.properties && |
|
|
|
const isRouteRelated = item.type === 'route' || item.routeId || |
|
|
|
(item.entity && item.entity.properties && |
|
|
|
(item.entity.properties.isMissionWaypoint || item.entity.properties.isMissionRouteLine)); |
|
|
|
|
|
|
|
|
|
|
|
// 如果是航线相关实体,从地图中移除 |
|
|
|
if (isRouteRelated && item.entity) { |
|
|
|
this.viewer.entities.remove(item.entity); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保留非航线相关实体 |
|
|
|
return true; |
|
|
|
}); |
|
|
|
@ -247,6 +247,7 @@ export default { |
|
|
|
this.drawingPoints = []; |
|
|
|
let activeCursorPosition = null; |
|
|
|
this.isDrawing = true; |
|
|
|
this.viewer.canvas.style.cursor = 'crosshair'; |
|
|
|
this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); |
|
|
|
window.addEventListener('contextmenu', this.preventContextMenu, true); |
|
|
|
// 鼠标移动预览逻辑 |
|
|
|
@ -322,8 +323,8 @@ export default { |
|
|
|
name: `WP${index + 1}`, |
|
|
|
lat: coords.lat, |
|
|
|
lng: coords.lng, |
|
|
|
alt: 500, // 默认业务属性 |
|
|
|
speed: 600 // 默认业务属性 |
|
|
|
alt: 5000, |
|
|
|
speed: 800 |
|
|
|
}; |
|
|
|
}); |
|
|
|
this.$emit('draw-complete', latLngPoints); |
|
|
|
@ -337,15 +338,37 @@ export default { |
|
|
|
}, 200); |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
//正式航线渲染函数 |
|
|
|
renderRouteWaypoints(waypoints, routeId = 'default') { |
|
|
|
if (!waypoints || waypoints.length < 1) return; |
|
|
|
const positions = []; |
|
|
|
// 1. 遍历并绘制航点标记 |
|
|
|
// 清理旧线 |
|
|
|
const lineId = `route-line-${routeId}`; |
|
|
|
const existingLine = this.viewer.entities.getById(lineId); |
|
|
|
if (existingLine) { |
|
|
|
this.viewer.entities.remove(existingLine); |
|
|
|
} |
|
|
|
// 清理所有属于该航线的旧点 |
|
|
|
waypoints.forEach((wp,index) => { |
|
|
|
const waypointEntityId = `wp_${routeId}_${wp.id}`; |
|
|
|
const existingWaypoint = this.viewer.entities.getById(waypointEntityId); |
|
|
|
if (existingWaypoint) { |
|
|
|
this.viewer.entities.remove(existingWaypoint); |
|
|
|
} |
|
|
|
const arcId = `arc-line-${routeId}-${index}`; |
|
|
|
const existingArc = this.viewer.entities.getById(arcId); |
|
|
|
if (existingArc) { |
|
|
|
this.viewer.entities.remove(existingArc); |
|
|
|
} |
|
|
|
}); |
|
|
|
// 遍历并绘制航点标记 |
|
|
|
const originalPositions = []; |
|
|
|
waypoints.forEach((wp, index) => { |
|
|
|
const lon = parseFloat(wp.lng); |
|
|
|
const lat = parseFloat(wp.lat); |
|
|
|
const pos = Cesium.Cartesian3.fromDegrees(lon, lat, parseFloat(wp.altitude || wp.alt || 500)); |
|
|
|
positions.push(pos); |
|
|
|
// 使用 Number 转换 Double 类型 |
|
|
|
const altValue = Number(wp.alt || 5000); |
|
|
|
const pos = Cesium.Cartesian3.fromDegrees(lon, lat, altValue); |
|
|
|
originalPositions.push(pos); |
|
|
|
this.viewer.entities.add({ |
|
|
|
id: `wp_${routeId}_${wp.id}`, |
|
|
|
name: wp.name || `WP${index + 1}`, |
|
|
|
@ -373,25 +396,99 @@ export default { |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
// 2. 绘制连线(仅当点数 > 1 时) |
|
|
|
if (positions.length > 1) { |
|
|
|
// 绘制连线 |
|
|
|
if (waypoints.length > 1) { |
|
|
|
let finalPathPositions = []; |
|
|
|
for (let i = 0; i < waypoints.length; i++) { |
|
|
|
const currPos = originalPositions[i]; |
|
|
|
const radius = this.getWaypointRadius(waypoints[i]); |
|
|
|
// 如果不是首尾点且有半径,我们就画红色的弧线 |
|
|
|
if (i > 0 && i < waypoints.length - 1 && radius > 0) { |
|
|
|
const prevPos = originalPositions[i - 1]; |
|
|
|
const nextPos = originalPositions[i + 1]; |
|
|
|
const arcPoints = this.computeArcPositions(prevPos, currPos, nextPos, radius); |
|
|
|
// 绘制红色实线弧 |
|
|
|
this.viewer.entities.add({ |
|
|
|
id: `arc-line-${routeId}-${i}`, |
|
|
|
polyline: { |
|
|
|
positions: arcPoints, |
|
|
|
width: 8, |
|
|
|
material: Cesium.Color.RED, |
|
|
|
clampToGround: true, |
|
|
|
zIndex: 20 |
|
|
|
} |
|
|
|
}); |
|
|
|
console.log(`>>> 航点 ${waypoints[i].name} 已渲染红色转弯弧,半径: ${radius}`); |
|
|
|
} |
|
|
|
if (i === 0 || i === waypoints.length - 1 || radius <= 0) { |
|
|
|
finalPathPositions.push(currPos); |
|
|
|
} else { |
|
|
|
const prevPos = originalPositions[i - 1]; |
|
|
|
const nextPos = originalPositions[i + 1]; |
|
|
|
// 计算弧线点集 |
|
|
|
const arcPoints = this.computeArcPositions(prevPos, currPos, nextPos, radius); |
|
|
|
finalPathPositions.push(...arcPoints); |
|
|
|
} |
|
|
|
} |
|
|
|
// 绘制包含弧线的 Polyline |
|
|
|
const routeEntity = this.viewer.entities.add({ |
|
|
|
id: `route-line-${routeId}`, |
|
|
|
id: lineId, |
|
|
|
polyline: { |
|
|
|
positions: positions, |
|
|
|
positions: finalPathPositions, |
|
|
|
width: 4, |
|
|
|
material: new Cesium.PolylineDashMaterialProperty({ |
|
|
|
color: Cesium.Color.WHITE, |
|
|
|
gapColor: Cesium.Color.BLACK, |
|
|
|
dashLength: 20.0 |
|
|
|
}), |
|
|
|
clampToGround: true |
|
|
|
clampToGround: true, |
|
|
|
zIndex: 1 |
|
|
|
}, |
|
|
|
properties: {isMissionRouteLine: true, routeId: routeId} |
|
|
|
}); |
|
|
|
this.allEntities.push({id: `route-line-${routeId}`, entity: routeEntity, type: 'line'}); |
|
|
|
if (this.allEntities) { |
|
|
|
this.allEntities.push({id: lineId, entity: routeEntity, type: 'line'}); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
// 计算单个点的转弯半径 |
|
|
|
getWaypointRadius(wp) { |
|
|
|
const speed = wp.speed || 800; |
|
|
|
const bankAngle = wp.turnAngle || 0; |
|
|
|
if (bankAngle <= 0) return 0; |
|
|
|
const v_mps = speed / 3.6; |
|
|
|
const radians = bankAngle * (Math.PI / 180); |
|
|
|
const g = 9.8; |
|
|
|
return (v_mps * v_mps) / (g * Math.tan(radians)); |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算转弯处的圆弧点集 |
|
|
|
computeArcPositions(p1, p2, p3, radius) { |
|
|
|
const v1 = Cesium.Cartesian3.subtract(p1, p2, new Cesium.Cartesian3()); |
|
|
|
const v2 = Cesium.Cartesian3.subtract(p3, p2, new Cesium.Cartesian3()); |
|
|
|
const dir1 = Cesium.Cartesian3.normalize(v1, new Cesium.Cartesian3()); |
|
|
|
const dir2 = Cesium.Cartesian3.normalize(v2, new Cesium.Cartesian3()); |
|
|
|
|
|
|
|
const angle = Cesium.Cartesian3.angleBetween(dir1, dir2); |
|
|
|
const dist = radius / Math.tan(angle / 2); |
|
|
|
|
|
|
|
const t1 = Cesium.Cartesian3.add(p2, Cesium.Cartesian3.multiplyByScalar(dir1, dist, new Cesium.Cartesian3()), new Cesium.Cartesian3()); |
|
|
|
const t2 = Cesium.Cartesian3.add(p2, Cesium.Cartesian3.multiplyByScalar(dir2, dist, new Cesium.Cartesian3()), new Cesium.Cartesian3()); |
|
|
|
|
|
|
|
let arc = []; |
|
|
|
// 采样15个点让弧线丝滑 |
|
|
|
for (let t = 0; t <= 1; t += 0.07) { |
|
|
|
const c1 = Math.pow(1 - t, 2); |
|
|
|
const c2 = 2 * (1 - t) * t; |
|
|
|
const c3 = Math.pow(t, 2); |
|
|
|
arc.push(new Cesium.Cartesian3( |
|
|
|
c1 * t1.x + c2 * p2.x + c3 * t2.x, |
|
|
|
c1 * t1.y + c2 * p2.y + c3 * t2.y, |
|
|
|
c1 * t1.z + c2 * p2.z + c3 * t2.z |
|
|
|
)); |
|
|
|
} |
|
|
|
return arc; |
|
|
|
}, |
|
|
|
removeRouteById(routeId) { |
|
|
|
// 从地图上移除所有属于该 routeId 的实体 |
|
|
|
const entityList = this.viewer.entities.values; |
|
|
|
@ -471,7 +568,7 @@ export default { |
|
|
|
this.handler.setInputAction((click) => { |
|
|
|
// 隐藏右键菜单 |
|
|
|
this.contextMenu.visible = false; |
|
|
|
|
|
|
|
|
|
|
|
if (this.isDrawing) return; |
|
|
|
|
|
|
|
const pickedObject = this.viewer.scene.pick(click.position); |
|
|
|
@ -511,10 +608,10 @@ export default { |
|
|
|
if (this.isDrawing) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 隐藏之前可能显示的菜单 |
|
|
|
this.contextMenu.visible = false; |
|
|
|
|
|
|
|
|
|
|
|
const pickedObject = this.viewer.scene.pick(click.position) |
|
|
|
if (Cesium.defined(pickedObject) && pickedObject.id) { |
|
|
|
const pickedEntity = pickedObject.id |
|
|
|
@ -579,8 +676,8 @@ export default { |
|
|
|
const length = this.calculateLineLength(entityData.positions) |
|
|
|
// 根据当前方位角类型计算方位角 |
|
|
|
const bearingType = entityData.bearingType || 'true'; |
|
|
|
const bearing = bearingType === 'magnetic' |
|
|
|
? this.calculateMagneticBearing(entityData.positions) |
|
|
|
const bearing = bearingType === 'magnetic' |
|
|
|
? this.calculateMagneticBearing(entityData.positions) |
|
|
|
: this.calculateTrueBearing(entityData.positions); |
|
|
|
// 显示小框提示 |
|
|
|
this.hoverTooltip = { |
|
|
|
@ -763,38 +860,54 @@ export default { |
|
|
|
console.log(`开始绘制 ${this.getTypeName(mode)}`) |
|
|
|
}, |
|
|
|
stopDrawing() { |
|
|
|
// 销毁处理器 |
|
|
|
if (this.drawingHandler) { |
|
|
|
this.drawingHandler.destroy(); |
|
|
|
this.drawingHandler = null; |
|
|
|
} |
|
|
|
//还原鼠标样式 |
|
|
|
this.viewer.scene.canvas.style.cursor = 'default'; |
|
|
|
// 彻底清理临时点 |
|
|
|
const allEntities = this.viewer.entities.values; |
|
|
|
for (let i = allEntities.length - 1; i >= 0; i--) { |
|
|
|
const entity = allEntities[i]; |
|
|
|
if (entity.id && ( |
|
|
|
entity.id.toString().startsWith('temp_wp_') || |
|
|
|
entity.id.toString().includes('temp-preview') |
|
|
|
)) { |
|
|
|
this.viewer.entities.remove(entity); |
|
|
|
} |
|
|
|
} |
|
|
|
//清理已知的预览实体 |
|
|
|
if (this.tempEntity) { |
|
|
|
this.viewer.entities.remove(this.tempEntity); |
|
|
|
this.tempEntity = null; |
|
|
|
} |
|
|
|
// 确保也清理预览实体 |
|
|
|
if (this.tempPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.tempPreviewEntity); |
|
|
|
this.tempPreviewEntity = null; |
|
|
|
} |
|
|
|
// 清理点实体 |
|
|
|
if (this.drawingPointEntities) { |
|
|
|
// 清理点实体数组 |
|
|
|
if (this.drawingPointEntities && this.drawingPointEntities.length > 0) { |
|
|
|
this.drawingPointEntities.forEach(pointEntity => { |
|
|
|
this.viewer.entities.remove(pointEntity); |
|
|
|
}); |
|
|
|
this.drawingPointEntities = []; |
|
|
|
} |
|
|
|
// 重置状态 |
|
|
|
this.drawingPoints = []; |
|
|
|
this.drawingStartPoint = null; |
|
|
|
this.isDrawing = false; |
|
|
|
this.activeCursorPosition = null; |
|
|
|
// 重新初始化右键点击删除处理器 |
|
|
|
// 重新初始化右键处理器 |
|
|
|
if (!this.rightClickHandler) { |
|
|
|
this.initRightClickHandler(); |
|
|
|
} |
|
|
|
// 隐藏测量结果和小框提示 |
|
|
|
// 隐藏测量结果 |
|
|
|
this.measurementResult = null; |
|
|
|
this.hoverTooltip.visible = false; |
|
|
|
this.viewer.scene.canvas.style.cursor = 'default'; |
|
|
|
console.log(">>> 绘制状态已彻底重置,临时实体已清理"); |
|
|
|
}, |
|
|
|
cancelDrawing() { |
|
|
|
// 取消绘制,清理临时实体和状态 |
|
|
|
@ -2104,23 +2217,23 @@ export default { |
|
|
|
// 获取最后一段线段的两个端点 |
|
|
|
const startPos = positions[positions.length - 2]; |
|
|
|
const endPos = positions[positions.length - 1]; |
|
|
|
|
|
|
|
|
|
|
|
// 将笛卡尔坐标转换为地理坐标 |
|
|
|
const startCartographic = Cesium.Cartographic.fromCartesian(startPos); |
|
|
|
const endCartographic = Cesium.Cartographic.fromCartesian(endPos); |
|
|
|
|
|
|
|
|
|
|
|
// 转换为弧度 |
|
|
|
const startLat = startCartographic.latitude; |
|
|
|
const startLng = startCartographic.longitude; |
|
|
|
const endLat = endCartographic.latitude; |
|
|
|
const endLng = endCartographic.longitude; |
|
|
|
|
|
|
|
|
|
|
|
// 计算真方位角 |
|
|
|
const dLng = endLng - startLng; |
|
|
|
const y = Math.sin(dLng) * Math.cos(endLat); |
|
|
|
const x = Math.cos(startLat) * Math.sin(endLat) - Math.sin(startLat) * Math.cos(endLat) * Math.cos(dLng); |
|
|
|
let bearing = Math.atan2(y, x); |
|
|
|
|
|
|
|
|
|
|
|
// 转换为角度并调整到0-360范围 |
|
|
|
bearing = Cesium.Math.toDegrees(bearing); |
|
|
|
return (bearing + 360) % 360; |
|
|
|
@ -2129,21 +2242,21 @@ export default { |
|
|
|
calculateMagneticDeclination(lat, lng) { |
|
|
|
// 注意:这是一个简化的磁偏角计算模型 |
|
|
|
// 实际应用中,应该使用更精确的模型,如 WMM (World Magnetic Model) |
|
|
|
|
|
|
|
|
|
|
|
// 转换为角度 |
|
|
|
const latDeg = Cesium.Math.toDegrees(lat); |
|
|
|
const lngDeg = Cesium.Math.toDegrees(lng); |
|
|
|
|
|
|
|
|
|
|
|
// 简化的磁偏角计算(仅适用于中国地区) |
|
|
|
// 实际值会随时间变化,此处为近似值 |
|
|
|
let declination; |
|
|
|
|
|
|
|
|
|
|
|
if (lngDeg >= 73 && lngDeg <= 135 && latDeg >= 18 && latDeg <= 53) { |
|
|
|
// 中国地区简化磁偏角模型 |
|
|
|
// 基于2020年数据,每年约变化0.1度 |
|
|
|
const year = new Date().getFullYear(); |
|
|
|
const yearOffset = (year - 2020) * 0.1; |
|
|
|
|
|
|
|
|
|
|
|
if (lngDeg < 100) { |
|
|
|
// 西部地区 |
|
|
|
declination = 2.0 - yearOffset; |
|
|
|
@ -2158,7 +2271,7 @@ export default { |
|
|
|
// 其他地区默认磁偏角为0 |
|
|
|
declination = 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return declination; |
|
|
|
}, |
|
|
|
// 计算磁方位角 |
|
|
|
@ -2166,22 +2279,22 @@ export default { |
|
|
|
if (!positions || positions.length < 2) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算真方位角 |
|
|
|
const trueBearing = this.calculateTrueBearing(positions); |
|
|
|
|
|
|
|
|
|
|
|
// 获取线段起点的地理坐标 |
|
|
|
const startPos = positions[positions.length - 2]; |
|
|
|
const startCartographic = Cesium.Cartographic.fromCartesian(startPos); |
|
|
|
const startLat = startCartographic.latitude; |
|
|
|
const startLng = startCartographic.longitude; |
|
|
|
|
|
|
|
|
|
|
|
// 计算磁偏角 |
|
|
|
const declination = this.calculateMagneticDeclination(startLat, startLng); |
|
|
|
|
|
|
|
|
|
|
|
// 计算磁方位角(真方位角加上磁偏角) |
|
|
|
let magneticBearing = trueBearing + declination; |
|
|
|
|
|
|
|
|
|
|
|
// 调整到0-360范围 |
|
|
|
return (magneticBearing + 360) % 360; |
|
|
|
}, |
|
|
|
@ -2833,7 +2946,7 @@ export default { |
|
|
|
selectedLineEntity.positions = newPositions |
|
|
|
// 更新点数据 |
|
|
|
selectedLineEntity.points[pointIndex] = this.cartesianToLatLng(newPosition) |
|
|
|
// 重新计算距离(只更新线段旁边的小框,不显示右下角弹窗) |
|
|
|
// 重新计算距离 |
|
|
|
const length = this.calculateLineLength(selectedLineEntity.positions) |
|
|
|
|
|
|
|
// 强制刷新地图渲染 |
|
|
|
|