* 禁用路径轨迹可视化,以实现更简洁的实体移动 * 将路径颜色更改为红色,以提高可见度 * 将动画速度倍率从 10 提高到 40 * 添加 `onEntityComplete` 回调函数,以便在动画结束时清除路径 * 改进位置属性外推算法和实体完成度跟踪功能 * 增强 `clearRoute` 函数,以优化属性值处理和日志记录
319 lines
9.3 KiB
JavaScript
319 lines
9.3 KiB
JavaScript
/**
|
|
* 路线可视化 Composable
|
|
* 负责在 Cesium 地图上绘制路线和标签
|
|
*/
|
|
|
|
import { ref } from 'vue'
|
|
import * as Cesium from 'cesium'
|
|
import { ROUTE_STYLES, LABEL_STYLE } from '../constants/routeStyles'
|
|
import { formatDistance, formatDuration } from '../utils/geoUtils'
|
|
|
|
export function useRouteVisualization() {
|
|
// 路线实体集合
|
|
const routeEntities = ref([])
|
|
const labelEntities = ref([])
|
|
|
|
/**
|
|
* 将 polyline 字符串转换为 Cartesian3 数组
|
|
* @param {string} polylineString - 高德 polyline 格式
|
|
* @returns {Array<Cesium.Cartesian3>}
|
|
*/
|
|
const transformToCartesian3 = (polylineString) => {
|
|
if (!polylineString) return []
|
|
|
|
try {
|
|
return polylineString
|
|
.split(';')
|
|
.map(pair => {
|
|
const [lon, lat] = pair.split(',').map(Number)
|
|
if (isNaN(lon) || isNaN(lat)) return null
|
|
return Cesium.Cartesian3.fromDegrees(lon, lat, 0)
|
|
})
|
|
.filter(Boolean)
|
|
} catch (error) {
|
|
console.error('[useRouteVisualization] polyline 转换失败:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 绘制单条路线
|
|
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
|
* @param {Object} routeData - 路线数据
|
|
* @param {Object} styleOptions - 样式选项
|
|
* @returns {Cesium.Entity} 路线实体
|
|
*/
|
|
const drawRoute = (viewer, routeData, styleOptions = {}) => {
|
|
if (!viewer || !routeData || !routeData.polyline) {
|
|
console.warn('[useRouteVisualization] 参数无效')
|
|
return null
|
|
}
|
|
|
|
const positions = transformToCartesian3(routeData.polyline)
|
|
|
|
if (positions.length < 2) {
|
|
console.warn('[useRouteVisualization] 路线点数不足')
|
|
return null
|
|
}
|
|
|
|
// 获取样式
|
|
const type = routeData.isFallback ? 'fallback' : (routeData.type || 'personnel')
|
|
const style = ROUTE_STYLES[type] || ROUTE_STYLES.personnel
|
|
|
|
// 创建路线实体
|
|
const entity = viewer.entities.add({
|
|
polyline: {
|
|
positions: positions,
|
|
width: styleOptions.width || style.width,
|
|
material: styleOptions.material || (typeof style.material === 'function' ? style.material(viewer) : style.material),
|
|
clampToGround: true,
|
|
classificationType: Cesium.ClassificationType.TERRAIN
|
|
},
|
|
properties: {
|
|
type: 'route',
|
|
routeType: type,
|
|
routeId: routeData.id,
|
|
routeName: routeData.name,
|
|
distance: routeData.distance,
|
|
duration: routeData.duration,
|
|
isFallback: routeData.isFallback || false
|
|
}
|
|
})
|
|
|
|
routeEntities.value.push(entity)
|
|
|
|
console.log(`[useRouteVisualization] 绘制路线: ${routeData.name} (${type}), routeId: ${routeData.id}`)
|
|
|
|
return entity
|
|
}
|
|
|
|
/**
|
|
* 绘制多条路线
|
|
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
|
* @param {Array} routesArray - 路线数组
|
|
* @returns {Array<Cesium.Entity>} 路线实体数组
|
|
*/
|
|
const drawMultipleRoutes = (viewer, routesArray) => {
|
|
if (!viewer || !routesArray || routesArray.length === 0) {
|
|
console.warn('[useRouteVisualization] 参数无效')
|
|
return []
|
|
}
|
|
|
|
console.log(`[useRouteVisualization] 绘制 ${routesArray.length} 条路线`)
|
|
|
|
const entities = []
|
|
|
|
routesArray.forEach((route, index) => {
|
|
try {
|
|
// 绘制路线
|
|
const routeEntity = drawRoute(viewer, route)
|
|
|
|
if (routeEntity) {
|
|
entities.push(routeEntity)
|
|
|
|
// 添加标签
|
|
const labelEntity = addRouteLabel(viewer, route)
|
|
if (labelEntity) {
|
|
entities.push(labelEntity)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`[useRouteVisualization] 绘制路线 ${route.name} 失败:`, error)
|
|
}
|
|
})
|
|
|
|
console.log(`[useRouteVisualization] 成功绘制 ${entities.length / 2} 条路线`)
|
|
|
|
return entities
|
|
}
|
|
|
|
/**
|
|
* 添加路线标签(显示距离和时间)
|
|
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
|
* @param {Object} routeData - 路线数据
|
|
* @returns {Cesium.Entity} 标签实体
|
|
*/
|
|
const addRouteLabel = (viewer, routeData) => {
|
|
if (!viewer || !routeData || !routeData.polyline) {
|
|
return null
|
|
}
|
|
|
|
const positions = transformToCartesian3(routeData.polyline)
|
|
|
|
if (positions.length < 2) {
|
|
return null
|
|
}
|
|
|
|
// 计算中点位置
|
|
const midIndex = Math.floor(positions.length / 2)
|
|
const labelPosition = positions[midIndex]
|
|
|
|
// 格式化文本
|
|
const distanceText = formatDistance(routeData.distance)
|
|
const durationText = formatDuration(routeData.duration)
|
|
const labelText = `${distanceText} | ${durationText}`
|
|
|
|
// 添加降级标识
|
|
const finalText = routeData.isFallback ? `${labelText} (直线)` : labelText
|
|
|
|
// 获取标签颜色
|
|
const type = routeData.isFallback ? 'fallback' : (routeData.type || 'personnel')
|
|
const style = ROUTE_STYLES[type] || ROUTE_STYLES.personnel
|
|
|
|
// 创建标签实体
|
|
const entity = viewer.entities.add({
|
|
position: labelPosition,
|
|
label: {
|
|
text: finalText,
|
|
font: LABEL_STYLE.font,
|
|
fillColor: LABEL_STYLE.fillColor,
|
|
outlineColor: LABEL_STYLE.outlineColor,
|
|
outlineWidth: LABEL_STYLE.outlineWidth,
|
|
style: LABEL_STYLE.style,
|
|
pixelOffset: LABEL_STYLE.pixelOffset,
|
|
disableDepthTestDistance: LABEL_STYLE.disableDepthTestDistance,
|
|
heightReference: LABEL_STYLE.heightReference,
|
|
verticalOrigin: LABEL_STYLE.verticalOrigin,
|
|
// 添加背景
|
|
showBackground: true,
|
|
backgroundColor: Cesium.Color.BLACK.withAlpha(0.5),
|
|
backgroundPadding: new Cesium.Cartesian2(7, 5)
|
|
},
|
|
properties: {
|
|
type: 'routeLabel',
|
|
routeId: routeData.id,
|
|
routeName: routeData.name
|
|
}
|
|
})
|
|
|
|
labelEntities.value.push(entity)
|
|
|
|
return entity
|
|
}
|
|
|
|
/**
|
|
* 清除所有路线实体
|
|
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
|
*/
|
|
const clearRoutes = (viewer) => {
|
|
if (!viewer) return
|
|
|
|
// 清除路线实体
|
|
routeEntities.value.forEach(entity => {
|
|
if (entity) {
|
|
viewer.entities.remove(entity)
|
|
}
|
|
})
|
|
|
|
// 清除标签实体
|
|
labelEntities.value.forEach(entity => {
|
|
if (entity) {
|
|
viewer.entities.remove(entity)
|
|
}
|
|
})
|
|
|
|
routeEntities.value = []
|
|
labelEntities.value = []
|
|
|
|
console.log('[useRouteVisualization] 已清除所有路线')
|
|
}
|
|
|
|
/**
|
|
* 清除指定路线
|
|
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
|
* @param {string} routeId - 路线 ID
|
|
*/
|
|
const clearRoute = (viewer, routeId) => {
|
|
if (!viewer || !routeId) {
|
|
console.warn(`[useRouteVisualization] clearRoute 参数无效: viewer=${!!viewer}, routeId=${routeId}`)
|
|
return
|
|
}
|
|
|
|
console.log(`[useRouteVisualization] 开始清除路线: ${routeId}`)
|
|
console.log(`[useRouteVisualization] 当前路线实体数量: ${routeEntities.value.length}`)
|
|
console.log(`[useRouteVisualization] 当前标签实体数量: ${labelEntities.value.length}`)
|
|
|
|
let removedRoutes = 0
|
|
let removedLabels = 0
|
|
|
|
// 清除路线实体
|
|
routeEntities.value = routeEntities.value.filter(entity => {
|
|
if (entity && entity.properties) {
|
|
// 关键修复:使用 getValue() 获取 Cesium Property 的实际值
|
|
const entityRouteId = entity.properties.routeId?.getValue ? entity.properties.routeId.getValue() : entity.properties.routeId
|
|
console.log(`[useRouteVisualization] 检查路线实体 routeId: "${entityRouteId}", 目标: "${routeId}", 匹配: ${entityRouteId === routeId}`)
|
|
|
|
if (entityRouteId === routeId) {
|
|
console.log(`[useRouteVisualization] ✅ 匹配成功,移除路线实体: ${routeId}`)
|
|
viewer.entities.remove(entity)
|
|
removedRoutes++
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
// 清除标签实体
|
|
labelEntities.value = labelEntities.value.filter(entity => {
|
|
if (entity && entity.properties) {
|
|
// 关键修复:使用 getValue() 获取 Cesium Property 的实际值
|
|
const entityRouteId = entity.properties.routeId?.getValue ? entity.properties.routeId.getValue() : entity.properties.routeId
|
|
|
|
if (entityRouteId === routeId) {
|
|
console.log(`[useRouteVisualization] 移除标签实体: ${routeId}`)
|
|
viewer.entities.remove(entity)
|
|
removedLabels++
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
console.log(`[useRouteVisualization] 已清除路线: ${routeId}, 移除了 ${removedRoutes} 条路线和 ${removedLabels} 个标签`)
|
|
}
|
|
|
|
/**
|
|
* 获取所有路线的边界框
|
|
* @param {Array} routesArray - 路线数组
|
|
* @returns {Array} 边界点数组 [{ lon, lat }]
|
|
*/
|
|
const getRoutesBounds = (routesArray) => {
|
|
const allPoints = []
|
|
|
|
routesArray.forEach(route => {
|
|
if (route.polyline) {
|
|
const positions = transformToCartesian3(route.polyline)
|
|
positions.forEach(pos => {
|
|
const carto = Cesium.Cartographic.fromCartesian(pos)
|
|
allPoints.push({
|
|
lon: Cesium.Math.toDegrees(carto.longitude),
|
|
lat: Cesium.Math.toDegrees(carto.latitude)
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
return allPoints
|
|
}
|
|
|
|
return {
|
|
// 状态
|
|
routeEntities,
|
|
labelEntities,
|
|
|
|
// 方法
|
|
drawRoute,
|
|
drawMultipleRoutes,
|
|
addRouteLabel,
|
|
clearRoutes,
|
|
clearRoute,
|
|
getRoutesBounds,
|
|
transformToCartesian3,
|
|
|
|
// 样式常量
|
|
ROUTE_STYLES
|
|
}
|
|
}
|
|
|
|
export default useRouteVisualization
|