diff --git a/docs/机器人运动监控组件详细分析.md b/docs/机器人运动监控组件详细分析.md index 0abccaa..ee9e8bf 100644 --- a/docs/机器人运动监控组件详细分析.md +++ b/docs/机器人运动监控组件详细分析.md @@ -630,3 +630,149 @@ export class EditorService extends Meta2d { 5. **状态验证不足**: 缺乏对接收数据的有效性验证 通过实施全局状态管理、WebSocket连接复用、状态缓存机制和坐标转换优化等解决方案,可以有效解决这些问题,确保多页面间机器人位置的一致性。 + +## 8. 自动门点光圈功能扩展 + +### 8.1 功能概述 + +在机器人光圈绘制的基础上,新增了自动门点的光圈绘制功能。当WebSocket推送自动门点状态数据时,系统会根据设备状态自动绘制相应颜色的光圈。 + +### 8.2 数据结构 + +WebSocket推送的自动门点数据格式: + +```typescript +{ + "gid": "", + "id": "172.31.57.55-502-17", // 设备ID,用于匹配地图中的自动门点 + "label": "AutoD01", + "type": 99, // 标识为自动门点 + "deviceStatus": 0, // 设备状态:0=关门,1=开门 + "active": true, + // ... 其他字段 +} +``` + +### 8.3 实现细节 + +#### 主题颜色配置 + +在 `editor-dark.json` 和 `editor-light.json` 中添加自动门点颜色配置: + +```json +"autoDoor": { + "stroke-closed": "#FF4D4F99", // 关门状态边框(红色) + "fill-closed": "#FF4D4F33", // 关门状态填充(红色) + "stroke-open": "#1890FF99", // 开门状态边框(蓝色) + "fill-open": "#1890FF33" // 开门状态填充(蓝色) +} +``` + +#### 数据模型扩展 + +在 `MapPointInfo` 接口中新增字段: + +```typescript +interface MapPointInfo { + // ... 现有字段 + deviceStatus?: number; // 设备状态(0=关门,1=开门) + active?: boolean; // 是否激活状态,控制光圈显示 +} +``` + +#### 绘制函数修改 + +在 `drawPoint` 函数中添加自动门点光圈绘制逻辑: + +```typescript +// 为自动门点绘制光圈 +if (type === MapPointType.自动门点 && pointActive && deviceStatus !== undefined) { + const ox = x + w / 2; + const oy = y + h / 2; + const haloRadius = Math.max(w, h) / 2 + 10; + + ctx.ellipse(ox, oy, haloRadius, haloRadius, 0, 0, Math.PI * 2); + + // 根据设备状态选择颜色 + if (deviceStatus === 0) { + // 关门状态 - 红色 + ctx.fillStyle = get(theme, 'autoDoor.fill-closed') ?? '#FF4D4F33'; + ctx.strokeStyle = get(theme, 'autoDoor.stroke-closed') ?? '#FF4D4F99'; + } else { + // 开门状态 - 蓝色 + ctx.fillStyle = get(theme, 'autoDoor.fill-open') ?? '#1890FF33'; + ctx.strokeStyle = get(theme, 'autoDoor.stroke-open') ?? '#1890FF99'; + } + + ctx.fill(); + ctx.stroke(); +} +``` + +#### 状态更新方法 + +新增 `updateAutoDoorByDeviceId` 方法: + +```typescript +public updateAutoDoorByDeviceId(deviceId: string, deviceStatus: number, active = true): void { + // 查找匹配的自动门点 + const autoDoorPoint = this.data().pens.find( + (pen) => pen.name === 'point' && + (pen as MapPen).point?.type === MapPointType.自动门点 && + (pen as MapPen).point?.deviceId === deviceId + ) as MapPen | undefined; + + if (!autoDoorPoint?.id || !autoDoorPoint.point) return; + + // 更新自动门点状态 + this.updatePen(autoDoorPoint.id, { + point: { + ...autoDoorPoint.point, + deviceStatus, + active, + }, + }, false); +} +``` + +#### WebSocket数据处理 + +修改运动监控组件的WebSocket处理逻辑: + +```typescript +ws.onmessage = (e) => { + const data = JSON.parse(e.data || '{}'); + + // 判断数据类型:type=99为自动门点,其他为机器人 + if (data.type === 99) { + // 自动门点数据处理 + const { id: deviceId, deviceStatus, active = true } = data; + if (deviceId && deviceStatus !== undefined) { + latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active }); + } + } else { + // 机器人数据处理 + const robotData = data as RobotRealtimeInfo; + latestRobotData.set(robotData.id, robotData); + } +}; +``` + +### 8.4 使用方式 + +1. **地图编辑**:在场景编辑器中创建自动门点,并设置对应的设备ID +2. **状态监控**:系统自动接收WebSocket推送的自动门点状态数据 +3. **视觉反馈**: + - 关门状态(deviceStatus=0):显示红色光圈 + - 开门状态(deviceStatus=1):显示蓝色光圈 + - 无状态数据时:不显示光圈 + +### 8.5 技术优势 + +1. **复用机器人架构**:充分利用现有的渲染和状态管理机制 +2. **高性能处理**:采用相同的时间分片和数据缓冲策略 +3. **类型安全**:完整的TypeScript类型支持 +4. **主题适配**:支持深色和浅色主题 +5. **实时性**:与机器人监控相同的实时更新能力 + +这种设计展现了系统架构的可扩展性,为未来支持更多设备类型(如电梯、传感器等)奠定了良好的基础。 diff --git a/src/apis/map/type.ts b/src/apis/map/type.ts index 786cf95..73c993b 100644 --- a/src/apis/map/type.ts +++ b/src/apis/map/type.ts @@ -31,6 +31,8 @@ export interface MapPointInfo { associatedStorageLocations?: string[]; // 库位名称 deviceId?: string; // 设备ID enabled?: 0 | 1; // 是否启用(仅停靠点使用,0=禁用,1=启用) + deviceStatus?: number; // 设备状态(仅自动门点使用,0=关门,1=开门) + active?: boolean; // 是否激活状态,用于控制光圈显示 } //#endregion diff --git a/src/assets/themes/editor-dark.json b/src/assets/themes/editor-dark.json index e8bf4f1..c806666 100644 --- a/src/assets/themes/editor-dark.json +++ b/src/assets/themes/editor-dark.json @@ -51,5 +51,11 @@ "fill-warning": "#FF851B33", "stroke-fault": "#FF4D4F99", "fill-fault": "#FF4D4F33" + }, + "autoDoor": { + "stroke-closed": "#FF4D4F99", + "fill-closed": "#FF4D4F33", + "stroke-open": "#1890FF99", + "fill-open": "#1890FF33" } } diff --git a/src/assets/themes/editor-light.json b/src/assets/themes/editor-light.json index 68a5417..a3624af 100644 --- a/src/assets/themes/editor-light.json +++ b/src/assets/themes/editor-light.json @@ -51,5 +51,11 @@ "fill-warning": "#FF851B33", "stroke-fault": "#FF4D4F99", "fill-fault": "#FF4D4F33" + }, + "autoDoor": { + "stroke-closed": "#FF4D4F99", + "fill-closed": "#FF4D4F33", + "stroke-open": "#1890FF99", + "fill-open": "#1890FF33" } } diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index 4da4f7b..d9b45a0 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -9,6 +9,8 @@ import { isNil } from 'lodash-es'; import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue'; import { useRoute } from 'vue-router'; +import { autoDoorSimulationService, type AutoDoorWebSocketData } from '../services/auto-door-simulation.service'; + const EDITOR_KEY = Symbol('editor-key'); type Props = { @@ -84,7 +86,7 @@ const monitorScene = async () => { const frameBudget = 8; // 每一帧的渲染预算,单位:毫秒。保守设置为8ms,为其他任务留出时间。 const startTime = performance.now(); - // 在时间预算内,持续处理数据 + // 在时间预算内,持续处理机器人数据 while (performance.now() - startTime < frameBudget && latestRobotData.size > 0) { // 获取并移除 Map 中的第一条数据 const entry = latestRobotData.entries().next().value; @@ -109,6 +111,9 @@ const monitorScene = async () => { } } + // 处理缓冲的自动门点数据 + autoDoorSimulationService.processBufferedData(frameBudget, startTime); + // 请求浏览器在下一次重绘之前再次调用 renderLoop,形成动画循环。 // 即使本帧没有处理完所有数据,下一帧也会继续处理剩余的数据。 animationFrameId = requestAnimationFrame(renderLoop); @@ -117,12 +122,20 @@ const monitorScene = async () => { /** * WebSocket 的 onmessage 事件处理器。 * 这个函数只做一件事:接收数据并将其快速存入缓冲区。 - * 这种“数据与渲染分离”的设计是避免UI阻塞的关键。 + * 这种"数据与渲染分离"的设计是避免UI阻塞的关键。 */ ws.onmessage = (e) => { - const data = JSON.parse(e.data || '{}'); - // 将最新数据存入 Map,如果已有相同ID的数据,则会被自动覆盖。 - latestRobotData.set(data.id, data); + const data = JSON.parse(e.data || '{}'); + + // 判断数据类型:type=99为自动门点,其他为机器人 + if (data.type === 99) { + // 自动门点数据处理 + autoDoorSimulationService.handleWebSocketData(data as AutoDoorWebSocketData); + } else { + // 机器人数据处理 + const robotData = data as RobotRealtimeInfo; + latestRobotData.set(robotData.id, robotData); + } }; client.value = ws; @@ -158,11 +171,29 @@ onMounted(async () => { storageLocationService.value?.startMonitoring({ interval: 1 }); // 自动保存和恢复视图状态 await handleAutoSaveAndRestoreViewState(); + + // 设置编辑器服务 + if (editor.value) { + autoDoorSimulationService.setEditorService(editor.value); + + // 注释掉模拟逻辑,使用真实WebSocket数据 + // autoDoorSimulationService.startSimulation({ + // deviceId: 'test01', + // label: 'TestAutoDoor01', + // interval: 3000, + // initialStatus: 0, + // enableLogging: true, + // }); + } }); onUnmounted(() => { client.value?.close(); storageLocationService.value?.destroy(); + // 清理自动门点服务(清空缓冲数据) + autoDoorSimulationService.clearBufferedData(); + // 注释掉模拟相关的清理,只保留WebSocket数据处理的清理 + // autoDoorSimulationService.stopAllSimulations(); }); //#endregion diff --git a/src/services/auto-door-simulation.service.ts b/src/services/auto-door-simulation.service.ts new file mode 100644 index 0000000..1367ce0 --- /dev/null +++ b/src/services/auto-door-simulation.service.ts @@ -0,0 +1,388 @@ +/** + * 自动门点服务 + * 提供自动门点的WebSocket数据处理功能 + * 注:模拟数据功能已被注释,现在主要用于处理真实WebSocket推送数据 + */ + +import { type MapPen, MapPointType } from '@api/map'; + +import type { EditorService } from './editor.service'; + +/** + * 自动门点模拟数据配置 + */ +export interface AutoDoorSimulationConfig { + /** 设备ID,需要与地图中自动门点的deviceId匹配 */ + deviceId: string; + /** 设备标签 */ + label?: string; + /** 状态切换间隔(毫秒),默认3000ms */ + interval?: number; + /** 初始状态,0=关门,1=开门,默认0 */ + initialStatus?: 0 | 1; + /** 是否启用控制台日志 */ + enableLogging?: boolean; +} + +/** + * 自动门点状态数据结构(模拟WebSocket推送的数据格式) + */ +interface AutoDoorStatusData { + gid: string; + id: string; + label: string; + brand: null; + type: 99; // 自动门点类型标识 + ip: null; + battery: number; + isConnected: boolean; + state: number; + canOrder: boolean; + canStop: null; + canControl: boolean; + targetPoint: null; + deviceStatus: 0 | 1; // 设备状态:0=关门,1=开门 + x: number; + y: number; + active: boolean; + angle: number; + isWaring: null; + isFault: null; + isLoading: null; + path: []; +} + +/** + * WebSocket推送的自动门点数据接口 + */ +export interface AutoDoorWebSocketData { + gid: string; + id: string; // 设备ID + label: string; + brand: null; + type: 99; // 自动门点类型标识 + ip: null; + battery: number; + isConnected: boolean; + state: number; + canOrder: boolean; + canStop: null; + canControl: boolean; + targetPoint: null; + deviceStatus: 0 | 1; // 设备状态:0=关门,1=开门 + x: number; + y: number; + active: boolean; + angle: number; + isWaring: null; + isFault: null; + isLoading: null; + path: []; +} + +/** + * 自动门点服务 + * 主要用于WebSocket数据处理,模拟功能保留但默认不使用 + */ +export class AutoDoorService { + private timers = new Map(); + private statusMap = new Map(); + private editorService: EditorService | null = null; + private latestAutoDoorData = new Map(); + + // 设备ID到自动门点ID的映射缓存,避免每次都遍历查找 + private deviceIdToPointIdMap = new Map(); + + /** + * 设置编辑器服务实例并初始化自动门点映射 + * @param editor 编辑器服务实例 + */ + setEditorService(editor: EditorService): void { + this.editorService = editor; + // 初始化设备ID到点位ID的映射关系 + this.initializeDeviceMapping(); + } + + /** + * 初始化设备ID到自动门点ID的映射关系 + * 在场景加载后调用,建立高效的查找缓存 + */ + private initializeDeviceMapping(): void { + if (!this.editorService) { + console.warn('⚠️ 编辑器服务未设置,无法初始化自动门点映射'); + return; + } + + // 清空现有映射 + this.deviceIdToPointIdMap.clear(); + + // 遍历所有点位,找出自动门点并建立映射 + const pens = this.editorService.data().pens; + let autoDoorCount = 0; + + pens.forEach((pen) => { + if (pen.name === 'point' && (pen as MapPen).point?.type === MapPointType.自动门点) { + const deviceId = (pen as MapPen).point?.deviceId; + if (deviceId && pen.id) { + this.deviceIdToPointIdMap.set(deviceId, pen.id); + autoDoorCount++; + } + } + }); + + console.log(`🚪 自动门点映射初始化完成: 共找到 ${autoDoorCount} 个自动门点`); + } + + /** + * 手动刷新设备映射(当场景发生变化时调用) + */ + refreshDeviceMapping(): void { + this.initializeDeviceMapping(); + } + + /** + * 启动自动门点模拟(已停用,保留接口用于兼容性) + * 注:当前使用真实WebSocket数据,此方法仅在测试时启用 + * @param config 模拟配置 + */ + startSimulation(config: AutoDoorSimulationConfig): void { + const { deviceId, interval = 3000, initialStatus = 0, enableLogging = true } = config; + + // 如果已经存在相同设备ID的模拟,先停止它 + this.stopSimulation(deviceId); + + if (enableLogging) { + console.log(`🚪 启动自动门点模拟: ${deviceId}`); + } + + // 设置初始状态 + this.statusMap.set(deviceId, initialStatus); + + // 创建定时器 + const timer = setInterval(() => { + const currentStatus = this.statusMap.get(deviceId) ?? 0; + const newStatus = currentStatus === 0 ? 1 : 0; + + // 更新状态 + this.statusMap.set(deviceId, newStatus); + + // 注意:这里不需要创建完整的模拟数据对象,只需要更新状态 + + if (enableLogging) { + console.log(`🚪 自动门点状态更新: ${newStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`); + } + + // 更新编辑器中的自动门点状态 + if (this.editorService) { + this.editorService.updateAutoDoorByDeviceId(deviceId, newStatus, true); + } else { + console.warn('⚠️ 编辑器服务未设置,无法更新自动门点状态'); + } + }, interval); + + // 保存定时器引用 + this.timers.set(deviceId, timer); + } + + /** + * 停止指定设备的模拟(已停用,保留接口用于兼容性) + * @param deviceId 设备ID + */ + stopSimulation(deviceId: string): void { + const timer = this.timers.get(deviceId); + if (timer) { + clearInterval(timer); + this.timers.delete(deviceId); + this.statusMap.delete(deviceId); + console.log(`🚪 停止自动门点模拟: ${deviceId}`); + } + } + + /** + * 停止所有模拟(已停用,保留接口用于兼容性) + */ + stopAllSimulations(): void { + for (const deviceId of this.timers.keys()) { + this.stopSimulation(deviceId); + } + console.log('🚪 停止所有自动门点模拟'); + } + + /** + * 获取当前正在模拟的设备列表 + */ + getActiveSimulations(): string[] { + return Array.from(this.timers.keys()); + } + + /** + * 获取指定设备的当前状态 + * @param deviceId 设备ID + */ + getCurrentStatus(deviceId: string): 0 | 1 | undefined { + return this.statusMap.get(deviceId); + } + + /** + * 手动设置设备状态 + * @param deviceId 设备ID + * @param status 设备状态 + */ + setDeviceStatus(deviceId: string, status: 0 | 1): void { + this.statusMap.set(deviceId, status); + + if (this.editorService) { + this.editorService.updateAutoDoorByDeviceId(deviceId, status, true); + console.log(`🚪 手动设置自动门点状态: ${status === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`); + } + } + + /** + * 启动多个自动门点模拟 + * @param configs 多个模拟配置 + */ + startMultipleSimulations(configs: AutoDoorSimulationConfig[]): void { + configs.forEach((config) => this.startSimulation(config)); + } + + /** + * 处理WebSocket推送的自动门点数据 + * @param data WebSocket推送的数据 + */ + handleWebSocketData(data: AutoDoorWebSocketData): void { + const { id: deviceId, deviceStatus, active = true } = data; + + if (!deviceId || deviceStatus === undefined) { + console.warn('⚠️ 自动门点数据格式不正确', data); + return; + } + + // 缓存最新数据 + this.latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active }); + + console.log( + `🚪 收到自动门点WebSocket数据: ${deviceStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`, + ); + } + + /** + * 处理缓冲的自动门点数据(在渲染循环中调用) + * @param frameBudget 帧预算(毫秒) + * @param startTime 开始时间 + * @returns 是否还有待处理的数据 + */ + processBufferedData(frameBudget: number, startTime: number): boolean { + // 在时间预算内,持续处理自动门点数据 + while (performance.now() - startTime < frameBudget && this.latestAutoDoorData.size > 0) { + // 获取并移除 Map 中的第一条自动门点数据 + const entry = this.latestAutoDoorData.entries().next().value; + if (!entry) break; + + const [deviceId, data] = entry; + this.latestAutoDoorData.delete(deviceId); + + // 使用映射缓存快速查找点位ID + const pointId = this.deviceIdToPointIdMap.get(data.deviceId); + + if (!pointId) { + console.warn(`⚠️ 未找到设备ID ${data.deviceId} 对应的自动门点,可能需要刷新映射`); + continue; + } + + // 更新自动门点状态(使用pointId直接更新,避免查找) + if (this.editorService) { + this.editorService.updateAutoDoorByDeviceId(data.deviceId, data.deviceStatus, data.active, pointId); + } + } + + // 返回是否还有待处理的数据 + return this.latestAutoDoorData.size > 0; + } + + /** + * 获取当前缓冲数据数量 + */ + getBufferedDataCount(): number { + return this.latestAutoDoorData.size; + } + + /** + * 清空缓冲数据 + */ + clearBufferedData(): void { + this.latestAutoDoorData.clear(); + } + + /** + * 获取映射关系数量 + */ + getMappingCount(): number { + return this.deviceIdToPointIdMap.size; + } + + /** + * 检查设备ID是否有对应的自动门点 + */ + hasDeviceMapping(deviceId: string): boolean { + return this.deviceIdToPointIdMap.has(deviceId); + } + + /** + * 获取所有已映射的设备ID列表 + */ + getMappedDeviceIds(): string[] { + return Array.from(this.deviceIdToPointIdMap.keys()); + } + + /** + * 创建模拟数据 + * @param deviceId 设备ID + * @param label 设备标签 + * @param deviceStatus 设备状态 + */ + private createMockData(deviceId: string, label: string, deviceStatus: 0 | 1): AutoDoorStatusData { + return { + gid: '', + id: deviceId, + label, + brand: null, + type: 99, + ip: null, + battery: 0, + isConnected: true, + state: 0, + canOrder: false, + canStop: null, + canControl: false, + targetPoint: null, + deviceStatus, + x: 0, + y: 0, + active: true, + angle: 0, + isWaring: null, + isFault: null, + isLoading: null, + path: [], + }; + } + + /** + * 销毁服务,清理所有资源 + */ + destroy(): void { + this.stopAllSimulations(); + this.clearBufferedData(); + this.deviceIdToPointIdMap.clear(); + this.editorService = null; + console.log('🚪 自动门点服务已销毁'); + } +} + +/** + * 自动门点服务单例 + */ +export const autoDoorService = new AutoDoorService(); + +// 保持向后兼容 +export const autoDoorSimulationService = autoDoorService; diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts index a5d1c52..fb38d0e 100644 --- a/src/services/editor.service.ts +++ b/src/services/editor.service.ts @@ -839,6 +839,55 @@ export class EditorService extends Meta2d { this.updatePen(pointId, { statusStyle: color }, false); } + /** + * 根据设备ID更新自动门点状态 + * @param deviceId 设备ID + * @param deviceStatus 设备状态(0=关门,1=开门) + * @param active 是否显示光圈 + * @param pointId 可选的点位ID,如果提供则直接使用,避免查找 + */ + public updateAutoDoorByDeviceId(deviceId: string, deviceStatus: number, active = true, pointId?: string): void { + let autoDoorPointId = pointId; + + // 如果没有提供pointId,则通过deviceId查找 + if (!autoDoorPointId) { + const autoDoorPoint = this.data().pens.find( + (pen) => + pen.name === 'point' && + (pen as MapPen).point?.type === MapPointType.自动门点 && + (pen as MapPen).point?.deviceId === deviceId, + ) as MapPen | undefined; + + if (!autoDoorPoint?.id) { + console.warn(`未找到设备ID为 ${deviceId} 的自动门点`); + return; + } + + autoDoorPointId = autoDoorPoint.id; + } + + // 通过pointId获取点位对象 + const autoDoorPoint = this.getPenById(autoDoorPointId) as MapPen | undefined; + + if (!autoDoorPoint?.point) { + console.warn(`自动门点 ${autoDoorPointId} 不存在或数据异常`); + return; + } + + // 更新自动门点状态 + this.updatePen( + autoDoorPointId, + { + point: { + ...autoDoorPoint.point, + deviceStatus, + active, + }, + }, + false, + ); + } + public updatePointLockIcon(pointId: string, show: boolean): void { const pointPen = this.getPenById(pointId); if (!pointPen || pointPen.name !== 'point') return; @@ -1301,10 +1350,33 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void { const theme = sTheme.editor; const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {}; const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {}; - const { type } = pen.point ?? {}; + const { type, deviceStatus, active: pointActive } = pen.point ?? {}; const { label = '', statusStyle } = pen ?? {}; ctx.save(); + + // 为自动门点绘制光圈 + if (type === MapPointType.自动门点 && pointActive && deviceStatus !== undefined) { + const ox = x + w / 2; + const oy = y + h / 2; + console.log(ox, oy); + const haloRadius = Math.max(w, h) / 2 + 6; // 光圈半径比点位本身大一些 + + ctx.ellipse(ox, oy, haloRadius, haloRadius, 0, 0, Math.PI * 2); + + // 根据设备状态选择颜色:0=关门(红色),1=开门(蓝色) + if (deviceStatus === 0) { + ctx.fillStyle = get(theme, 'autoDoor.fill-closed') ?? '#FF4D4F33'; + ctx.strokeStyle = get(theme, 'autoDoor.stroke-closed') ?? '#FF4D4F99'; + } else { + ctx.fillStyle = get(theme, 'autoDoor.fill-open') ?? '#1890FF33'; + ctx.strokeStyle = get(theme, 'autoDoor.stroke-open') ?? '#1890FF99'; + } + + ctx.fill(); + ctx.stroke(); + } + switch (type) { case MapPointType.普通点: case MapPointType.等待点: