前些天写了有关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时应用偏移
- 垂直排列:根据车辆在组内的位置,计算按顺序垂直排列的偏移量
- 间距控制:使用固定的标签高度和额外间距确保标签不重叠
标签可见性与交互控制
交互控制要点:
- 可见性规则:
- 非重叠车辆始终显示
- 重叠组中的首个车辆始终显示
- 其他车辆仅在展开状态(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)线性变化
- 透明度曲线控制:
- 前10%阶段:淡入效果(0→0.8)
- 10%-70%阶段:维持最高透明度(0.8)
- 70%-100%阶段:淡出效果(0.8→0)
- 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模型与地图的集成、车辆状态管理、重叠标签处理等功能,都体现了较高的技术水平和用户体验设计。
通过组件化设计和良好的资源管理,系统能够流畅展示大量车辆实时状态,并提供丰富的交互功能,为无人车调度管理提供了直观、高效的可视化界面。