/** * 路线可视化 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} */ 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 || style.material(viewer), 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})`) return entity } /** * 绘制多条路线 * @param {Cesium.Viewer} viewer - Cesium viewer 实例 * @param {Array} routesArray - 路线数组 * @returns {Array} 路线实体数组 */ 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) return // 清除路线实体 routeEntities.value = routeEntities.value.filter(entity => { if (entity && entity.properties && entity.properties.routeId === routeId) { viewer.entities.remove(entity) return false } return true }) // 清除标签实体 labelEntities.value = labelEntities.value.filter(entity => { if (entity && entity.properties && entity.properties.routeId === routeId) { viewer.entities.remove(entity) return false } return true }) console.log(`[useRouteVisualization] 已清除路线: ${routeId}`) } /** * 获取所有路线的边界框 * @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