【深度剖析】Vue+高德地图+Three.js打造无人车实时监控系统

        前些天写了有关vue兼容高德1.4版本的3D地图,以及在地图中展示obj模型的相关要点,这几天发现高德1.4和2.0版本间互相切换的时候会有问题,于是给项目的地图版本版本升了级,现在全部使用高德2.0的script js api组件,目前看效果要比1.4更好看

项目概述

本文将深入分析一个使用Vue.js、高德地图API和Three.js开发的无人车监控系统前端实现。该系统能够实时显示车辆位置、状态、轨迹,并提供派单、监控、任务管理等功能。

技术栈

  • 前端框架:Vue.js
  • 地图组件:高德地图 API (AMap) 2.0版本
  • 3D渲染:Three.js + AMap ThreeLayer
  • 数据交互:API异步请求

核心功能模块详解

1. 地图初始化与配置

async initMap() {
  try {
    window.AMap = await AMapLoader.load({
      key: process.env.VUE_APP_AMAP_KEY,
      version: "2.0",
      plugins: ['Map3D']
    });
    this.AMaper = window.AMap;
    this.map = new this.AMaper.Map("container", {
      viewMode: '3D',
      showBuildingBlock: true,
      rotation: 0,
      center: [122.1234, 37.4567],
      pitch: 60,
      zoom: 17,
      features: ['bg', 'road', 'building', 'point'],
      mapStyle: 'amap://styles/normal'
    });

要点:

  • 使用AMapLoader.load异步加载高德地图API
  • 配置3D视图模式
  • 设置初始中心点、倾斜角度和缩放级别
  • 配置地图特性和样式
     
高德地图API异步加载流程

这段代码使用AMapLoader.load方法异步加载高德地图API。这是一个Promise-based的加载方式,通过await等待加载完成。

关键技术点:

  • 环境变量密钥:使用process.env.VUE_APP_AMAP_KEY从环境变量获取API密钥,避免硬编码敏感信息
  • 版本控制:明确指定2.0版本,确保API兼容性
  • 按需加载插件:仅加载必要的'Map3D'插件,减少不必要的网络请求
3D地图实例创建

配置参数详解:

  • viewMode: '3D':启用3D视图,使地图具有立体效果
  • showBuildingBlock: true:显示建筑物3D模型
  • pitch: 60:地图俯仰角度,值越大视角越倾斜(0-90度)
  • zoom: 17:缩放级别,范围通常为3-18,值越大显示越详细
  • features:指定需要显示的地图要素,包括背景、道路、建筑物和兴趣点
  • rotation: 0:地图初始旋转角度,0表示正北方向向上
地图加载完成事件处理

这里使用Promise结合事件监听和超时保护确保地图加载完成。通过同时设置事件监听和超时,无论哪种情况先发生都能继续执行后续代码,避免无限等待。

错误处理与优雅降级

完善的错误捕获机制,当地图初始化失败时提供用户友好的错误提示,增强应用健壮性。

2. Three.js与高德地图的集成

this.threeLayer = new ThreeLayer(this.map);
this.threeLayer.on('complete', () => {
  const ambientLight = new THREE.AmbientLight('#ffffff', 1);
  this.threeLayer.add(ambientLight);
  const directionalLight = new THREE.DirectionalLight('#ffffff', 0.8);
  directionalLight.position.set(0, 1, 0);
  this.threeLayer.add(directionalLight);
  resolve();
});

要点:

  • 使用ThreeLayer将Three.js与高德地图无缝集成
  • 添加环境光和方向光实现更好的3D效果
  • 通过Promise等待渲染层初始化完成
ThreeLayer图层创建

ThreeLayer是将Three.js与高德地图集成的核心组件,它创建一个覆盖在地图上的3D渲染层,允许在地图上放置Three.js对象。

光照系统配置

光照系统详解:

  • 环境光(AmbientLight):提供场景整体均匀照明,强度为1.0,确保3D模型在任何角度都有基础可见度
  • 方向光(DirectionalLight):创建阳光效果,强度为0.8,来自顶部(0,1,0),为3D模型产生明暗对比和立体感
  • 颜色设置:两种光源均使用白光(#ffffff),确保模型显示真实颜色
异步初始化与事件管理

使用Promise封装ThreeLayer的'complete'事件,确保在Three.js图层完全初始化后再执行后续代码,防止渲染错误。

坐标系统整合

ThreeLayer自动处理高德地图的经纬度坐标系统和Three.js的笛卡尔坐标系统之间的转换。当地图进行平移、缩放、旋转等操作时,ThreeLayer会自动调整Three.js场景,保持3D模型与地图位置的正确对应。

性能注意事项

Three.js渲染需要较高的GPU性能,ThreeLayer实现了智能渲染策略:

  • 仅在地图视图变化时重新渲染3D场景
  • 自动管理图形对象的可见性和细节层次
  • 优化几何体和材质以减少内存占用

3. 车辆3D模型加载与渲染

const gltf = new ThreeGltf(this.threeLayer, {
  url: this.vehicleGlbUrl,
  position: [vehicle.longitude, vehicle.latitude],
  height: 0,
  scale: 5,
  rotation: { x: 180, y: 0, z: 180 },
  configLoader: (loader) => {
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.143/examples/js/libs/draco/');
    loader.setDRACOLoader(dracoLoader);
  },
  onLoaded: (modelGroup) => {
    const heading = vehicle.heading || 0;
    modelGroup.rotation.set(
      THREE.MathUtils.degToRad(90),
      THREE.MathUtils.degToRad(-90 - heading),
      THREE.MathUtils.degToRad(0)
    );
    gltf.modelGroup = modelGroup;
  }
});

要点:

  • 使用ThreeGltf加载GLB格式3D模型
  • 配置DRACOLoader压缩解码器提高加载效率
  • 根据车辆朝向角调整模型旋转角度
  • 保存模型组引用以便后续更新
ThreeGltf加载器配置

参数详解:

  • url:3D模型的GLB文件路径
  • position:模型放置的经纬度坐标
  • height:模型离地高度,设为0表示贴地放置
  • scale:模型缩放比例,根据实际模型尺寸调整
  • rotation:模型初始旋转角度,以确保车头方向正确
DRACO压缩解码器集成

DRACO解码器的作用:

  • DRACO是Google开发的开源库,用于3D几何体数据压缩和解压缩
  • 可将3D模型体积压缩至原来的10%-20%,大幅减少网络传输时间
  • 通过设置CDN解码器路径,确保解码器可靠加载
  • 无缝集成到GLTFLoader中处理压缩模型
模型加载完成处理与朝向调整

技术要点:

  • modelGroup参数:接收加载完成的3D模型组
  • 角度换算:使用THREE.MathUtils.degToRad()将角度制转换为弧度制
  • 朝向计算:模型默认朝向为-90度,基于此添加车辆实际航向角
  • 模型引用保存:将模型组引用保存到gltf对象,便于后续更新朝向
错误处理与降级方案

实现容错机制,当3D模型加载失败时自动降级使用简单2D标记,确保功能可用性,提升用户体验。

4. 车辆状态管理与标记系统

createVehicleLabelHTML(vehicle, isInOverlappingGroup, isFirstInGroup, groupSize, labelOffset, shouldShow) {
  const isOffline = !vehicle.online;
  const isWarning = this.isVehicleWarning(vehicle);
  const labelContent = vehicle.no || vehicle.id.substring(0, 8);

  const stackCountHtml = isInOverlappingGroup && isFirstInGroup && groupSize > 1 ?
    `<div class="vehicle-stack-count">${groupSize}</div>` : '';

  let backgroundColor = 'rgba(0,0,0,0.7)'; // 默认黑色透明背景(离线)
  let textColor = 'white'; // 默认文字颜色为白色

  if (!isOffline) {
    if (isWarning) {
      backgroundColor = 'rgba(255,0,0,0.6)'; // 故障状态:红色透明背景
      textColor = 'white'; // 故障状态:文字为白色
    } else {
      backgroundColor = 'rgba(0,255,0,0.5)'; // 正常在线:绿色透明背景
      textColor = 'rgba(0,0,0,0.5)'; // 正常在线:文字为灰色,提高可读性
    }
  }
  // ...返回HTML标签代码
}

要点:

  • 根据车辆online属性判断在线状态
  • 通过多个条件判断车辆是否处于警告状态
  • 使用不同颜色区分车辆状态(绿色=在线,红色=故障,黑色=离线)
  • 为重叠位置的车辆提供堆叠数量显示
车辆状态判断逻辑

状态判断详解:

  • 在线状态:通过vehicle.online属性判断,双感叹号(!!)确保返回布尔值
  • 警告状态:通过多种硬件指标综合判断,包括:

                驾驶控制模式异常(drivingCmdControlMode === -1)

                驱动系统错误(driveSystemError为1或2)

                电池故障(errorPowerBattery === 1)

                电机故障(errorMotor === 1)

                直流转换器故障(errorDc === 1)

车辆标签动态生成

关键技术点:

  • 车辆标识处理:优先使用车牌号(no),无则使用ID前8位作为标识
  • 状态颜色映射:通过背景色直观区分三种状态
  • 堆叠计数器:为重叠车辆组显示数量标记,提升用户体验
  • 透明度设置:使用rgba颜色,保持与地图良好融合
标签元素创建与事件绑定

实现细节:

  • 高德Marker创建:使用自定义HTML内容作为标记
  • anchor设置:使用'center'确保标记正确定位在车辆位置
  • zIndex层级:为重叠组首个车辆设置更高层级(102),确保可见
  • 延时绑定:使用setTimeout确保DOM元素完全渲染后再绑定事件
  • 事件委托:通过ID选择器精确定位标签元素绑定事件

5. 重叠车辆标签处理

checkVehicleOverlapping() {
  const allVehicles = [...this.vehicles];
  const overlappingGroups = [];

  if (allVehicles.length < 2) {
    this.overlappingGroups = [];
    return;
  }

  const positionGroups = {};
  allVehicles.forEach(vehicle => {
    if (!vehicle.longitude || !vehicle.latitude) return;
    const key = `${Math.round(vehicle.longitude * 100000) / 100000},${Math.round(vehicle.latitude * 100000) / 100000}`;
    if (!positionGroups[key]) {
      positionGroups[key] = [];
    }
    positionGroups[key].push(vehicle.id);
  });

  Object.values(positionGroups).forEach(group => {
    if (group.length > 1) {
      overlappingGroups.push(group);
    }
  });

  this.overlappingGroups = overlappingGroups;
}

要点:

  • 通过将经纬度取整处理(精确到小数点后5位,约1米精度)识别位置重叠的车辆
  • 使用哈希表技术快速分组聚类
  • 为重叠车辆创建分组,实现悬停展开功能
位置重叠检测算法

算法详解:

  • 经纬度离散化:将经纬度舍入到小数点后5位(约1米精度)
  • 哈希表分组:使用经纬度组合字符串作为键,车辆ID数组作为值
  • 时间复杂度:O(n),只需遍历一次所有车辆
  • 空间复杂度:O(n),最坏情况下需要存储所有车辆
  • 精度控制:通过调整小数点位数可控制判定重叠的精度
分组索引与位置计算

辅助函数作用:

  • 重叠检测:判断车辆是否在任一重叠组中
  • 组索引获取:查找车辆所在的重叠组索引
  • 内部索引获取:确定车辆在重叠组内的具体位置
动态标签位置计算

偏移计算逻辑:

  • 条件判断:只为重叠组中的非首个车辆计算偏移
  • 展开控制:只在showOfflineLabels为true时应用偏移
  • 垂直排列:根据车辆在组内的位置,计算按顺序垂直排列的偏移量
  • 间距控制:使用固定的标签高度和额外间距确保标签不重叠
标签可见性与交互控制

交互控制要点:

  • 可见性规则:
  1. 非重叠车辆始终显示
  1. 重叠组中的首个车辆始终显示
  1. 其他车辆仅在展开状态(showOfflineLabels为true)时显示
  • 交互优化:隐藏标签时禁用鼠标事件(pointerEvents: 'none')
  • 视觉反馈:为可展开的首个标签添加视觉提示(has-dropdown类)
悬停展开交互实现

交互设计细节:

  • 悬停触发:鼠标移入重叠组首个标签时展开显示所有标签
  • 状态维持:当鼠标移入展开的标签时,设置hoveredVehicleId防止折叠
  • 延时折叠:使用setTimeout延迟折叠,防止鼠标在标签间快速移动时的闪烁
  • 优雅过渡:通过CSS过渡效果(opacity transition)实现平滑显示/隐藏

6. 动态标签布局与交互

updateLabelVisibility() {
  this.checkVehicleOverlapping();
  this.vehicles.forEach(vehicle => {
    const labelElement = document.getElementById(`vehicle-label-${vehicle.id}`);
    if (!labelElement) return;

    const isInOverlappingGroup = this.isVehicleInOverlappingGroup(vehicle.id);
    const groupIndex = this.getVehicleIndexInGroup(vehicle.id);
    const isFirstInGroup = groupIndex === 0;
    
    if (!isInOverlappingGroup || isFirstInGroup || this.showOfflineLabels) {
      labelElement.style.opacity = '1';
      labelElement.style.pointerEvents = 'auto';
      
      if (isInOverlappingGroup && !isFirstInGroup && this.showOfflineLabels) {
        const labelHeight = 20;
        const verticalSpacing = 5;
        labelElement.style.top = `${25 + groupIndex * (labelHeight + verticalSpacing)}px`;
      } else {
        labelElement.style.top = '25px';
      }
    } else {
      labelElement.style.opacity = '0';
      labelElement.style.pointerEvents = 'none';
    }
    
    if (isInOverlappingGroup && isFirstInGroup) {
      labelElement.classList.add('has-dropdown');
    } else {
      labelElement.classList.remove('has-dropdown');
    }
  });
}

要点:

  • 动态计算并更新标签位置和可见性
  • 实现标签堆叠和展开效果
  • 通过CSS类和样式实现交互视觉反馈
标签视觉层次设计

布局策略详解:

  • 动态层级控制:通过修改DOM元素的zIndex属性,确保标签不被其他元素遮挡
  • 可见性渐变:使用CSS透明度过渡,实现平滑的显示和隐藏效果
  • 事件响应优化:隐藏状态下禁用指针事件(pointerEvents),防止点击穿透
  • 即时更新:在车辆数据变化、选择状态改变或标签展开状态变化时实时更新布局
自适应位置调整

技术要点:

  • 垂直堆叠算法:根据车辆在组内的位置计算垂直偏移
  • 间距控制:使用固定的标签高度(20px)和垂直间距(5px)确保视觉美观
  • 基准位置:所有标签初始y偏移为25px,确保不遮挡车辆图标
  • 条件应用:只有在标签展开状态时才应用差异化布局
交互状态与用户操作体验

交互设计亮点:

  • 鼠标悬停展开:无需点击即可查看重叠车辆,提高操作效率
  • 延时收起:使用200ms延时收起标签组,防止用户鼠标快速移动时的闪烁
  • 状态保持:通过hoveredVehicleId跟踪用户当前交互的车辆
  • 自动更新:交互状态变化时自动调用updateLabelVisibility更新UI

7. 车辆选中效果与动画

addSelectionEffect(vehicleId) {
  this.removeSelectionEffect();
  const marker = this.vehicleGltfModels[vehicleId];
  if (!marker) return;
  // ...获取车辆位置
  const radius = 5;
  const maxRadius = 20;
  const ringCount = 3;
  const effects = [];

  for (let i = 0; i < ringCount; i++) {
    const circle = new this.AMaper.Circle({
      center: [position.lng, position.lat],
      radius: radius + (i * (maxRadius - radius) / ringCount),
      strokeColor: '#1890FF',
      strokeWeight: 3,
      strokeOpacity: 0.8 - (i * 0.2),
      fillColor: '#1890FF',
      fillOpacity: 0.4 - (i * 0.1),
      zIndex: 99 - i,
    });
    this.map.add(circle);
    effects.push(circle);
  }

  this.selectionEffect = {
    circles: effects,
    vehicleId: vehicleId,
    animation: {
      startRadius: radius,
      maxRadius: maxRadius,
      currentPhase: 0,
      ringCount: ringCount,
      lastUpdateTime: Date.now(),
      animationSpeed: 0.3
    }
  };
}

要点:

  • 使用多层圆形绘制选中效果
  • 设计渐变透明度和尺寸的圆环
  • 保存动画状态和参数

选中效果创建

选中效果技术详解:

  • 多层圆环设计:使用3个同心圆构建水波纹效果
  • 渐变透明度:内层到外层圆环透明度逐渐降低(0.8→0.6→0.4)
  • 大小梯度:圆环半径从内到外均匀增大,形成视觉层次
  • 主题色运用:使用蓝色(#1890FF)作为选中效果的主题色,与UI风格统一
  • 层级管理:使用递减的zIndex确保外层圆环不会遮挡内层圆环
车辆位置获取兼容性处理

兼容性处理策略:

  • 多种API兼容:支持不同类型标记的位置获取方法
  • 格式统一转换:将不同表示形式(函数/数组/对象)转换为统一的{lng, lat}对象
  • 数据降级策略:当无法从模型获取位置时,回退到从原始数据获取
  • 异常捕获与处理:通过try-catch防止位置获取失败导致整个功能崩溃
选中效果的清理

资源管理考量:

  • 完全清理:移除地图上所有圆环图形对象
  • 引用释放:设置selectionEffect为null,便于垃圾回收
  • 执行时机:在添加新效果前、取消选择时、组件销毁前等多个时机调用

8. 动画循环与平滑过渡

animateSelectionEffect() {
  if (!this.selectionEffect) return;

  const effect = this.selectionEffect;
  const now = Date.now();
  const delta = (now - effect.animation.lastUpdateTime) / 1000;
  effect.animation.lastUpdateTime = now;

  effect.animation.currentPhase = (effect.animation.currentPhase + delta * effect.animation.animationSpeed) % 1;
  // ...获取车辆位置
  for (let i = 0; i < effect.animation.ringCount; i++) {
    const phaseOffset = i / effect.animation.ringCount;
    let phase = (effect.animation.currentPhase + phaseOffset) % 1;

    const radiusDelta = effect.animation.maxRadius - effect.animation.startRadius;
    const currentRadius = effect.animation.startRadius + (phase * radiusDelta);

    let opacity;
    if (phase < 0.1) {
      opacity = phase * 10 * 0.8;
    } else if (phase > 0.7) {
      const fadeOutPhase = (phase - 0.7) / 0.3;
      opacity = 0.8 * (1 - fadeOutPhase);
    } else {
      opacity = 0.8;
    }

    if (phase > 0.95) {
      opacity = 0;
    }

    const circle = effect.circles[i];
    circle.setCenter([position.lng, position.lat]);
    circle.setRadius(currentRadius);
    circle.setOptions({
      strokeOpacity: opacity,
      fillOpacity: opacity / 2
    });
  }
}

要点:

  • 使用requestAnimationFrame实现高效动画循环
  • 基于时间增量计算动画阶段,实现帧率无关的平滑动画
  • 应用缓动函数和相位偏移创造水波纹动画效果
  • 使用透明度渐变提供自然的淡入淡出效果

动画循环实现

动画框架详解:

  • requestAnimationFrame:使用浏览器原生API优化动画性能
  • 自调用循环:设置递归调用确保动画持续进行
  • 帧ID记录:保存animationFrameId用于停止动画
  • 单一职责:动画循环只处理动画效果,不混入其他逻辑
选中效果动画计算

动画算法解析:

  • 基于时间的动画:使用时间增量(delta)计算进度,确保动画速度与帧率无关
  • 相位偏移:每个圆环的初始相位都错开(i/ringCount),实现波纹扩散效果
  • 半径动态计算:从起始半径(5)到最大半径(20)线性变化
  • 透明度曲线控制:
  1. 前10%阶段:淡入效果(0→0.8)
  1. 10%-70%阶段:维持最高透明度(0.8)
  1. 70%-100%阶段:淡出效果(0.8→0)
  1. 95%-100%阶段:完全透明,为下一轮动画做准备
  • 循环动画:使用取模运算(%)确保相位在0-1之间循环
性能优化与动态位置更新

性能与体验优化点:

  • 位置同步:每帧更新圆环中心位置,保持与车辆位置同步
  • 填充透明度协调:填充透明度始终为描边透明度的一半,保持视觉协调
  • 最小化DOM操作:只更新必要的属性(中心、半径、透明度)
  • 复用圆环对象:而非每帧创建新对象,减少内存占用和GC压力

9. 车辆轨迹显示

async updateVehicleTrack(params) {
  try {
    const response = await this.getTrack(params);

    if (this.currentTrack) {
      this.map.remove(this.currentTrack);
      this.currentTrack = null;
    }
    // ...清理旧标记
    const path = response.data;
    if (!path || !Array.isArray(path) || path.length === 0) {
      return;
    }
    // ...验证路径数据
    this.currentTrack = new this.AMaper.Polyline({
      path: validPath,
      strokeColor: "#0066FF",
      strokeWeight: 6,
      strokeOpacity: 0.8,
      lineJoin: 'round',
      lineCap: 'round',
      showDir: true,
      strokeStyle: 'dashed',
      strokeDasharray: [8, 4],
      outlineColor: '#FFFFFF',
      outlineWeight: 2,
      borderWeight: 3,
      dirColor: '#ff6a00',
      zIndex: 120
    });
    // ...添加起点和终点标记
  } catch (error) {
    console.error('更新车辆轨迹失败:', error);
  }
}

要点:

  • 使用高德地图Polyline绘制车辆轨迹
  • 添加虚线样式和方向箭头提高可读性
  • 使用不同颜色标记起点和终点

10. 弹窗系统与交互功能

// 组件导入
import MonitorPopup from '../../../components/MonitorPopup';
import LeftPopup from '../../../components/PopupLeft';
import RightPopup from '../../../components/PopupRight';
import StationPopup from '../../../components/StationPopup';
import TaskPopup from '../../../components/TaskPopup';
import UseVehiclePopup from '../../../components/UseVehiclePopup';

// 组件注册
components: {
  LeftPopup,
  RightPopup,
  StationPopup,
  UseVehiclePopup,
  TaskPopup,
  MonitorPopup,
},

// 弹窗状态管理
data() {
  return {
    isPopupVisible: false,
    isRightPopupExpanded: false,
    isStationPopupVisible: false,
    isUseVehiclePopupVisible: false,
    isTaskPopupVisible: false,
    isMonitorPopupVisible: false,
    // ...其他状态
  };
},

要点:

  • 使用Vue组件化方式构建各类弹窗
  • 通过状态变量控制弹窗显示隐藏
  • 实现父子组件间数据传递和事件通信

组件化弹窗结构

组件化设计优势:

  • 关注点分离:每个弹窗组件专注于自己的功能,降低代码复杂度
  • 重用性:组件可以在不同页面复用,减少重复代码
  • 维护性:各组件独立开发和测试,便于团队协作
  • 状态管理:通过props传递数据,events传递事件,实现清晰的数据流
弹窗状态管理

状态管理策略:

  • 集中管理:所有弹窗状态变量集中在父组件数据中
  • 布尔值控制:使用布尔值表示弹窗的显示/隐藏状态
  • 联动更新:弹窗状态变更时,同步更新相关UI元素(如标签位置)
  • 记忆选择:保存selectedVehicle等引用,使弹窗关闭后再打开时保持数据
弹窗交互与数据传递

交互流程设计:

  • 数据校验:操作前验证车辆是否存在且状态正确
  • 上下文清理:移除旧的选中效果,确保UI状态一致
  • 触发联动:车辆选择后自动请求轨迹和订单数据
  • 业务规则检查:实现如"离线车辆不能派单"等业务限制
  • 用户反馈:使用弹窗和消息提示给用户明确的操作反馈

11. 站点管理与显示

async fetchStationData() {
  try {
    const response = await listStaion();
    if (response && response.code === 200) {
      this.stations = response.data || response.rows || [];
      this.displayStationsOnMap();
    }
  } catch (error) {
    console.error("获取站点数据异常:", error);
  }
}

displayStationsOnMap() {
  if (!this.map || !this.stations.length) return;
  // ...清理旧标记
  this.stationMarkers = this.stations.map(station => {
    const iconSrc = this.getIconSrc(station.icon);
    const marker = new this.AMaper.Marker({
      position: [station.longitude, station.latitude],
      icon: new this.AMaper.Icon({
        image: iconSrc,
        size: new this.AMaper.Size(32, 32),
        imageSize: new this.AMaper.Size(32, 32),
      }),
      title: station.name,
    });
    this.map.add(marker);
    marker.on('click', () => {
      this.handleStationClick(station);
    });
    return marker;
  });
}

要点:

  • 异步获取站点数据
  • 根据站点类型使用不同图标
  • 为站点标记添加点击事件
  • 实现站点信息弹窗展示

站点数据获取

数据处理特点:

  • 异步请求:使用async/await实现非阻塞式请求
  • 响应格式兼容:支持data和rows两种返回格式
  • 空值处理:结果不存在时默认为空数组,避免后续遍历错误
  • 错误捕获:完整的try-catch结构处理网络请求异常
站点标记创建

实现技术点:

  • 资源清理:在创建新标记前先移除旧标记
  • 批量处理:使用map方法批量创建标记,返回标记数组便于后续管理
  • 图标差异化:根据站点类型(icon)使用不同图标
  • 事件绑定:为每个标记添加点击事件,显示详情弹窗
  • 标题提示:设置title属性,鼠标悬停时显示站点名称
站点图标管理

资源管理策略:

  • 动态引入:使用require动态加载图片资源
  • 类型映射:通过switch-case将站点类型映射到对应图标
  • 默认图标:提供默认图标处理未知类型
  • 资源路径约定:统一存放在@/assets/drawable目录,便于管理
  • WebPack集成:利用Vue/WebPack自动处理图片打包和引用
站点信息弹窗定位

坐标转换精髓:

  • 经纬度到像素:使用lngLatToContainer将地理坐标转换为容器内像素坐标
  • 相对位置计算:考虑容器的位置偏移(rect.left/top)和滚动偏移(scrollLeft/scrollTop)
  • 绝对定位:计算出准确的屏幕绝对坐标,使弹窗准确显示在站点位置附近
  • 状态更新:保存点击的站点信息和位置,用于弹窗渲染和定位

12. 资源管理与内存优化

beforeDestroy() {
  clearInterval(this.syncVehicleDataInterval);
  // ...清理站点标记
  if (this.animationFrameId) {
    cancelAnimationFrame(this.animationFrameId);
  }
  // ...清理标记和连接线
  if (this.vehicleGltfModels) {
    Object.keys(this.vehicleGltfModels).forEach(vehicleId => {
      const model = this.vehicleGltfModels[vehicleId];
      if (model) {
        try {
          // ...清理标签和模型
          if (model instanceof ThreeGltf) {
            if (model.modelGroup) {
              if (model.modelGroup.parent) {
                model.modelGroup.parent.remove(model.modelGroup);
              }
              // 释放模型资源
              if (model.modelGroup.traverse) {
                model.modelGroup.traverse((child) => {
                  if (child.geometry) {
                    child.geometry.dispose();
                  }
                  if (child.material) {
                    if (Array.isArray(child.material)) {
                      child.material.forEach(material => material.dispose());
                    } else {
                      child.material.dispose();
                    }
                  }
                });
              }
            }
            // ...销毁模型和移除引用
          }
        } catch (e) {
          console.warn("移除车辆模型出错:", e);
        }
      }
    });
  }
  // ...清理选中效果和地图实例
}

要点:

  • 清理定时器和动画帧防止内存泄漏
  • 正确释放Three.js几何体和材质资源
  • 移除DOM元素和事件监听器
  • 递归清理Three.js对象树
组件生命周期资源清理

资源清理全面策略:

  • 定时器销毁:清除所有setInterval,避免组件销毁后继续执行
  • 动画帧取消:取消requestAnimationFrame,停止所有动画循环
  • 地图标记移除:清除地图上的所有标记对象
  • 地图实例销毁:调用map.destroy()彻底释放地图资源
  • 引用置空:将关键对象引用设为null,辅助垃圾回收
Three.js模型资源释放

Three.js资源管理精要:

  • 场景节点解除:从父节点移除模型,断开渲染链
  • 递归资源释放:使用traverse遍历模型树,释放每个子节点资源
  • 几何体销毁:调用geometry.dispose()释放顶点和索引缓冲区
  • 材质销毁:处理单个材质和材质数组两种情况
  • 类型判断:区分ThreeGltf对象和普通标记,使用不同释放策略
  • 异常保护:使用try-catch包装,确保一个对象清理失败不影响其他对象
车辆模型更新与重建策略

智能更新机制:

  • 变化检测:比较车辆数量和关键属性(在线状态、经纬度)
  • 条件重建:只在必要时(有变化)才执行资源密集型的重建操作
  • 位置优化:无变化时只更新位置,避免不必要的重建
  • 状态重置:重建时重置UI状态,确保视觉一致性
  • 性能平衡:在保持数据准确性和视觉效果的同时最小化资源消耗

13. 实时数据同步与更新

async fetchVehicleData() {
  try {
    const response = await listOnlineVehicles();
    if (response && response.code === 200) {
      const vehicles = response.data || [];
      // ...处理数据
      const vehicleChanged = this.vehicles.length !== processedVehicles.length ||
        this.vehicles.some(oldV => {
          const newV = processedVehicles.find(v => v.id === oldV.id);
          return !newV ||
            oldV.online !== newV.online ||
            oldV.longitude !== newV.longitude ||
            oldV.latitude !== newV.latitude;
        });
      this.vehicles = processedVehicles;
      this.allVehicles = [...processedVehicles];
      // ...根据变化决定是重建还是更新
      this.$nextTick(() => {
        this.updateLabelVisibility();
      });
      // ...更新选中车辆信息
    }
  } catch (error) {
    console.error("获取车辆数据异常:", error);
  }
},
mounted() {
  // ...初始化地图
  this.syncVehicleDataInterval = setInterval(() => {
    this.fetchVehicleData();
  }, 3000);
  // ...其他初始化
},

要点:

  • 使用API异步获取车辆实时数据
  • 设置定时器周期性更新车辆信息
  • 通过数据对比确定是否需要重新创建模型
  • 使用Vue生命周期钩子管理定时器
定时数据刷新机制

实时更新设计:

  • 初始加载:组件挂载后立即请求一次数据
  • 定时轮询:设置3秒间隔的轮询,确保数据实时性
  • 依赖顺序:在地图初始化完成后再开始数据同步
  • 错误反馈:初始化失败时向用户提供明确的错误提示
  • 资源管理:保存定时器ID,便于组件销毁时清理
车辆数据处理与更新

数据处理精髓:

  • 数据标准化:补全缺失字段,统一数据格式
  • 深度比较:通过关键属性比较判断车辆状态是否有实质性变化
  • 高效更新:根据变化程度选择适当的更新策略(完全重建或仅更新位置)
  • DOM同步:使用Vue的nextTick机制,确保DOM渲染完成后再更新视觉效果
  • 引用维持:通过Object.assign更新选中车辆,保持引用一致性,避免UI组件重渲染
车辆位置平滑更新

高效更新策略:

  • 增量处理:只更新必要的属性,不重新创建模型
  • 智能判断:仅当在线状态变化时才完全重建模型
  • 位置同步:同时更新3D模型和标签位置
  • 朝向更新:检测heading变化后才调整模型旋转角度,避免不必要的计算
  • 异常隔离:使用try-catch包装更新操作,防止单个车辆的错误影响其他车辆
选中车辆信息实时更新

实时信息更新技术:

  • 维持引用:使用Object.assign()更新属性,保持this.selectedVehicle的引用不变
  • 按需更新:仅当有车辆选中时才执行更新操作
  • 自动联动:更新信息后自动检查车辆订单状态,保持UI数据一致性
  • 状态保持:即使重新获取数据,也能保持用户的当前选中状态
车辆订单状态检查

订单检查机制:

  • 条件执行:仅在有选中车辆时执行API调用,避免无效请求
  • 有效订单判定:通过状态码(status === 10)识别活跃订单
  • 状态同步:更新hasActiveOrder状态,控制任务按钮的显示/隐藏
  • 错误处理:发生异常时默认为无订单状态,避免UI不一致
  • 关联使用:选择车辆、关闭用车弹窗、关闭任务弹窗时都会触发检查

高级特性与技巧

1. 错误处理和优雅降级

try {
  // 尝试使用3D模型
  // ...3D模型相关代码
} catch (err) {
  console.error("3D模型加载失败,使用备选标记:", err);
  const backupMarker = this.createSimpleMarker(vehicle);
  this.vehicleGltfModels[vehicle.id] = backupMarker;
  return backupMarker;
}

要点:当3D模型加载失败时,系统会自动降级使用简单的2D标记,确保基本功能可用。

2. 车辆状态判断逻辑

isVehicleWarning(vehicle) {
  if (!vehicle) return false;
  return vehicle.drivingCmdControlMode === -1 ||
    vehicle.driveSystemError === 1 ||
    vehicle.driveSystemError === 2 ||
    vehicle.errorPowerBattery === 1 ||
    vehicle.errorMotor === 1 ||
    vehicle.errorDc === 1;
}

要点:通过多条件组合判断车辆故障状态,根据不同的硬件故障指标综合评估。

3. 地图与UI元素交互定位

handleStationClick(station) {
  const lnglat = new this.AMaper.LngLat(station.longitude, station.latitude);
  const pixel = this.map.lngLatToContainer(lnglat);

  const mapContainer = document.getElementById('container');
  const rect = mapContainer.getBoundingClientRect();

  const x = pixel.x + rect.left - mapContainer.scrollLeft;
  const y = pixel.y + rect.top - mapContainer.scrollTop;

  this.selectedStation = station;
  this.stationPosition = { x, y };
  this.isStationPopupVisible = true;
}

要点:将地理坐标转换为屏幕坐标,实现弹窗精确定位。

性能优化与用户体验

1. 车辆标签堆叠与展开

为解决车辆密集区域标签重叠问题,系统实现了标签堆叠与悬停展开功能:

  • 根据经纬度近似值将车辆分组
  • 默认只显示每组第一个标签
  • 显示堆叠数量提示用户有多个车辆
  • 鼠标悬停时展开显示所有标签

2. 视觉反馈与状态区分

系统使用三种颜色区分车辆状态:

  • 绿色:正常在线
  • 红色:发生故障
  • 黑色:离线状态

选中效果通过动态水波纹圆环提供明显视觉反馈。

3. 内存优化与资源释放

重点关注Three.js资源释放,包括:

  • 几何体(geometry)释放
  • 材质(material)释放
  • 模型组(group)移除
  • 动画帧和定时器清理

项目亮点总结与技术难点分析

核心亮点

1. 高效地图与3D集成

本项目成功地将高德地图API与Three.js 3D库无缝集成,实现了传统2D地图与3D模型的混合渲染。特别是ThreeLayer的使用,解决了地理坐标系与3D笛卡尔坐标系的转换问题,为车辆实时监控提供了更直观的可视化效果。

2. 智能的车辆标签系统

项目实现了创新的车辆标签系统,包括:

  • 根据车辆状态(在线、离线、警告)动态变化颜色
  • 重叠车辆的智能分组与悬停展开功能
  • 标签与3D模型的精确位置同步
  • 优雅的视觉反馈与用户交互设计
3. 高性能动画与选中效果

选中车辆时的水波纹动画效果展示了先进的前端可视化能力:

  • 基于时间的动画计算,确保帧率无关的平滑效果
  • 多层圆环的相位偏移,创造水波扩散效果
  • 透明度渐变曲线设计,提供自然的淡入淡出过渡
  • 与车辆位置实时同步,确保选中效果始终准确
4. 严格的资源管理与内存优化

项目展现了专业的前端资源管理策略:

  • Three.js几何体和材质的完整释放
  • 动画帧、定时器的妥善清理
  • DOM元素和事件监听器的移除
  • 条件更新策略,最小化资源消耗
  • 对象引用管理,辅助垃圾回收
5. 组件化架构与模块化设计

通过Vue组件化思想,项目实现了高度模块化的设计:

  • 各种弹窗功能独立封装为组件
  • 父子组件间通过props和events通信
  • 关注点分离,提高代码可维护性
  • 可重用UI组件,降低重复代码
  • 集中式状态管理,确保数据流清晰

技术难点与解决方案

1. 3D模型与地图集成挑战

难点:Three.js默认使用笛卡尔坐标系,而地图使用经纬度坐标系,两者集成困难。

解决方案:

  • 使用ThreeLayer作为桥梁,自动处理坐标转换
  • 实现高德地图与Three.js场景的视角同步
  • 为3D对象增加位置、旋转和缩放控制
  • 通过ThreeGltf加载器简化模型加载过程
2. 重叠车辆标签处理

难点:地图上车辆密集区域的标签重叠严重影响可读性。

解决方案:

  • 设计经纬度离散化算法,将相近位置车辆分组
  • 实现标签堆叠与展开交互机制
  • 使用视觉指示器(数字标签)提示重叠数量
  • 优化鼠标悬停体验,添加延迟防抖机制
3. 动态资源管理

难点:频繁更新车辆位置和状态容易导致内存泄漏和性能问题。

解决方案:

  • 实现智能更新机制,区分全量重建和增量更新场景
  • 完善的资源清理流程,特别是Three.js资源的深度释放
  • 优化DOM操作,减少重排重绘
  • 使用事件委托减少事件监听器数量
4. 实时数据同步与界面响应

难点:需要保持车辆位置、状态实时更新的同时维持良好的界面响应性。

解决方案:

  • 设置合理的数据轮询间隔(3秒)
  • 使用Vue的响应式系统和nextTick机制
  • 高效的DOM更新策略,只更新必要元素
  • 维护选中状态引用一致性,避免UI跳变

总结

这个无人车监控系统前端实现综合运用了Vue.js、高德地图API和Three.js,展示了Web前端在复杂可视化系统中的应用能力。特别是3D模型与地图的集成、车辆状态管理、重叠标签处理等功能,都体现了较高的技术水平和用户体验设计。

通过组件化设计和良好的资源管理,系统能够流畅展示大量车辆实时状态,并提供丰富的交互功能,为无人车调度管理提供了直观、高效的可视化界面。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值