前端~地图(openlayers)绘制车辆运动轨迹(仿高德)

  1. 绘制轨迹路线
  2. 轨迹路线描边
  3. 增加起点终点图标
  4. 绘制仿高德方向箭头
  5. 模仿车辆动态运动动画

车辆运行轨迹

车辆轨迹经纬度坐标
  const linePoints = [new Point([123.676031, 43.653421]),new Point([123.824347, 43.697124]),new Point([124.197882, 43.946811]),new Point([124.104498, 43.744764]),new Point([124.752692, 43.701096]),new Point([124.598883, 43.808225]),new Point([124.692267, 43.832006]),new Point([124.911993, 43.847854]),new Point([124.999884, 43.855777]),new Point([125.170172, 43.867658]),new Point([125.280035, 43.930989]),new Point([125.604132, 43.91912]),new Point([125.933722, 43.927033]),];
绘制轨迹路径&增加描边效果

就是绘制两边LineString,外边框比内边框宽一些,并通过控制zIndex控制在最下面即可

const drawDynamicCarLine = (linePoints: Array<Point>) => {// 内部轨迹线const innerLineFeature = new Feature(new LineString(linePoints.map((item) => item.getCoordinates())));innerLineFeature.setStyle(new Style({stroke: new Stroke({color: "rgb(237,73,23)",width: 3,}),zIndex: 3,}));// 外部边框线const outLineFeature = new Feature(new LineString(linePoints.map((item) => item.getCoordinates())));outLineFeature.setStyle(new Style({stroke: new Stroke({color: "rgba(44,74,48,1)",width: 6,}),zIndex: 1,}));const lineSource = new VectorSource({features: [innerLineFeature, outLineFeature],});const lineLayer = new VectorLayer({source: lineSource,});debugger;map.addLayer(lineLayer);
};

效果如下
在这里插入图片描述

增加起点终点图标
const addStartEndPoint = (startPoint: Point, endPoint: Point) => {const startPointFeature = new Feature({geometry: startPoint,});startPointFeature.setStyle(new Style({image: new Icon({src: new URL("/src/assets/imgs/car/move_car_icon.png", import.meta.url).href,}),zIndex: 3,}));const endPointFeature = new Feature({geometry: endPoint,});endPointFeature.setStyle(new Style({image: new Icon({src: new URL("/src/assets/imgs/car/end_point_icon.png", import.meta.url).href,// 调整图标中心点位置anchor: [0.4, 1],rotateWithView: true,rotation: 0,}),zIndex: 8,}));const startAndEndIconSource = new VectorSource({features: [startPointFeature, endPointFeature],});const startAndEndIconLayer = new VectorLayer({source: startAndEndIconSource,});map.addLayer(startAndEndIconLayer);
};

效果如下:
在这里插入图片描述

绘制仿高德方向箭头

参考了很多文章,算是把原理弄通了,简单的描述一下

  1. 首先计算路径长度,按照路径长度除以固定宽度分割,计算需要显示箭头的位置
    举个例子
    a. 当前坐标[0,0] 下一个坐标是[1,2]
    b. 计算这俩个坐标之间的距离,假设100米,每50米显示一个箭头,也就是需要三个箭头
    c. Math.atan2 可以计算[0,0]和[1,2] 之间旋转的角度,这三个箭头旋转都是这个角度

  2. 按照当前点位和下一个点位计算出箭头的方向,主要通过js 提供的函数

  3. 那知道需要绘制箭头的地方就简单了,直接增加Point即可

// Math.atan2 是 JavaScript 中的一个数学函数,用于计算平面坐标中从原点 (0, 0) 到指定点 (x, y) 的有符号弧度角。它返回的角度范围是从 -π 到 π(即 -180° 到 180°)
Math.atan2(y: number, x: number): number;

在这里插入图片描述

const drawDynamicCarLine = (linePoints: Array<Point>) => {// 内部轨迹线lineFeature = new Feature(new LineString(linePoints.map((item) => item.getCoordinates())));lineFeature.setStyle(new Style({stroke: new Stroke({color: "rgb(237,73,23)",width: 3,}),zIndex: 4,}));// 外部边框线const outLineFeature = new Feature(new LineString(linePoints.map((item) => item.getCoordinates())));outLineFeature.setStyle(new Style({stroke: new Stroke({color: "rgb(107,179,117)",width: 8,}),zIndex: 1,}));// 新增箭头绘制逻辑const arrowPoints = [];const arrowSpacing = 0.1; // 箭头间距,单位为线段长度的百分比const geometry = lineFeature.getGeometry() as LineString;const coordinates = geometry.getCoordinates();for (let i = 0; i < coordinates.length - 1; i++) {const start = coordinates[i];const end = coordinates[i + 1];const dx = end[0] - start[0];const dy = end[1] - start[1];const length = Math.sqrt(dx * dx + dy * dy); // 计算路径总长度const numArrows = Math.ceil(length / arrowSpacing); // 这个长度需要几个箭头for (let j = 1; j < numArrows; j++) {const fraction = j / numArrows;const arrowX = start[0] + dx * fraction;const arrowY = start[1] + dy * fraction;const arrowAngle = Math.atan2(dy, dx); // 计算箭头旋转角度const arrowFeature = new Feature({geometry: new Point([arrowX, arrowY]),});arrowFeature.setStyle(new Style({image: new Icon({src: new URL("/src/assets/imgs/car/arrow-right-bold.png",import.meta.url).href,rotation: -arrowAngle, scale: 0.5, // 调整箭头大小anchor: [0.5, 0.5], // 确保箭头中心点对齐rotateWithView: true, // 确保箭头随地图旋转}),}));arrowPoints.push(arrowFeature);}}const arrowSource = new VectorSource({features: arrowPoints,});const arrowLayer = new VectorLayer({source: arrowSource,zIndex: 5, // 确保箭头在轨迹线上方});map.addLayer(arrowLayer);const lineSource = new VectorSource({features: [lineFeature, outLineFeature],});lineLayer = new VectorLayer({source: lineSource,});map.addLayer(lineLayer);
};
模仿车辆动态运动动画

其实原理是和绘制箭头类似的,只不过需要一直触发重复绘制,让小车这个点位图标看起来在运动
lineLayer.on(“postrender”, ()=>{});
昨天还在吐槽官网,对于我们后端来说,这个图层可以绑定事件,那么事件的种类是不是可以列在后面,结果指向EventType,但是EventType 里面有什么都没有,感觉这个文档不是很友好,这个函数是通过别人的博客看来的,猜想是地图绘制或者类似的,就是可以一直触发,所学有限,但是感觉一直触发是不是有点影响性能呀,先实现吧,后续了解更多了在补充。


let animating = false; // 动画状态
let distance = 0;
let lastTime: any;
let speedInput = 50; // 速度输入
let lastPoint;
let currentPoint;const startMoveCartEvent = (event: RenderEvent) => {if (!animating) return; // 如果动画未启动,则不执行const speed = Number(speedInput);const time = (event as any).frameState.time;const elapsedTime = time - lastTime;distance = (distance + (speed * elapsedTime) / 1e6) % 2;lastTime = time;const currentCoordinate = lineFeature.getGeometry()?.getCoordinateAt(distance > 1 ? 2 - distance : distance);const position = startPointMarker.getGeometry()?.clone();lastPoint = position?.getCoordinates();currentPoint = currentCoordinate;// 计算车头旋转角度let dx = currentPoint[0] - lastPoint[0];let dy = currentPoint[1] - lastPoint[1];let rotation = Math.atan2(dy, dx); // 直接使用弧度值,无需转换为角度position.setCoordinates(currentCoordinate);const vectorContext = getVectorContext(event);const carStyle = startPointMarker.getStyle();carStyle.getImage().setRotation(-rotation); // 设置车头旋转角度vectorContext.setStyle(carStyle);vectorContext.drawGeometry(position);// 判断是否到达终点并停止动画if (distance >= 1) {stopAnimation(); // 调用停止动画函数}// 告诉 OpenLayers 继续渲染动画map.render();
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80112.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式之CAP原则:理解分布式系统的核心设计哲学

声明&#xff1a;CAP中的P原则都是需要带着的 在分布式系统的设计与实践中&#xff0c;CAP原则&#xff08;又称CAP定理&#xff09;是开发者必须掌握的核心理论之一。它揭示了分布式系统在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#x…

IF=40.8|肿瘤免疫:从免疫基因组学到单细胞分析和人工智能

一、写在前面 今天分享的是发表在《Signal Transduction and Targeted Therapy》上题目为"Technological advances in cancer immunity: from immunogenomics to single-cell analysis and artificial intelligence"的文章。 IF&#xff1a;40.8 DOI:10.1038/s41392…

深入理解 Spring @Bean 注解

在 Spring 框架中,@Bean 注解是用于显式地声明一个或多个 Bean 实例,并将其注册到 Spring 容器中的重要工具。与 @Component 系列注解不同的是,@Bean 是方法级别的注解,通常与 @Configuration 注解结合使用。本文将详细介绍 @Bean 注解的功能、用法及其应用场景。 1. @Bean…

Pycharm 如何删除某个 Python Interpreter

在PyCharm中&#xff0c;点击右下角的“Interpreter Settings”按钮&#xff0c;或者通过菜单栏选择“File” > “Settings”&#xff08;macOS用户选择“PyCharm” > “Preferences”&#xff09;。在设置窗口中&#xff0c;导航到“Project: [Your Project Name]” >…

如何改电脑网络ip地址完整教程

更改电脑的网络IP地址以满足特定的网络需求&#xff0c;本文将为您提供一份详细的步骤指南。其实&#xff0c;改变IP地址并不是一件复杂的事&#xff0c;能解决因为IP限制带来的麻烦。以下是操作指南&#xff1a; 方法一&#xff1a;Windows 系统&#xff0c;通过图形界面修改 …

Oracle--SQL性能优化与提升策略

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、导致性能问题的内在原因 系统性能问题的底层原因主要有三个方面&#xff1a; CPU占用率过高导致资源争用和等待内存使用率过高导致内存不足并需…

【go】什么是Go语言中的GC,作用是什么?调优,sync.Pool优化,逃逸分析演示

Go 语言中的 GC 简介与调优建议 Go语言GC工作原理 对于 Go 而言&#xff0c;Go 的 GC 目前使用的是无分代&#xff08;对象没有代际之分&#xff09;、不整理&#xff08;回收过程中不对对象进行移动与整理&#xff09;、并发&#xff08;与用户代码并发执行&#xff09;的三…

【unity实战】Animator启用root motion根运动动画,实现完美的动画动作匹配

文章目录 前言1、动画分类2、如何使用根位移动画&#xff1f; 一、根位移动画的具体使用1、导入人形模型2、导入动画3、配置动画参数4、配置角色Animator动画状态机5、使用代码控制人物前进后退 二、问题分析三、Humanoid动画中的Root Motion机制及相关配置1、Humanoid动画中的…

中间件--ClickHouse-10--海量数据存储如何抉择ClickHouse和ES?

在Mysql数据存储或性能瓶颈时&#xff0c;采用冷热数据分离的方式通常是一种选择。ClickHouse和Elasticsearch&#xff08;ES&#xff09;是两个常用的组件&#xff0c;但具体使用哪种组件取决于冷数据的存储目的、查询模式和业务需求等方面。 1、核心对比 &#xff08;1&…

服务器运维:服务器流量的二八法则是什么意思?

文章目录 用户行为角度时间分布角度应用场景角度 服务器流量的二八法则&#xff0c;又称 80/20 法则&#xff0c;源自意大利经济学家帕累托提出的帕累托法则&#xff0c;该法则指出在很多情况下&#xff0c;80% 的结果是由 20% 的因素所决定的。在服务器流量领域&#xff0c;二…

springboot对接豆包大模型

文档地址: 豆包大模型-火山引擎 模型广场地址: 账号登录-火山引擎 首先来到模型广场&#xff0c;选取你需要的模型,我这边要做图片理解的应用&#xff0c;所以选用了Doubao-1.5.vision-pro. 点立即体验&#xff0c;进入一个新的页面&#xff0c;可以上传图片&#xff0c;然后…

数据通信学习笔记之OSPF其他内容3

对发送的 LSA 进行过滤 当两台路由器之间存在多条链路时&#xff0c;可以在某些链路上通过对发送的 LSA 进行过滤&#xff0c;减少不必要的重传&#xff0c;节省带宽资源。 通过对 OSPF 接口出方向的 LSA 进行过滤可以不向邻居发送无用的 LSA&#xff0c;从而减少邻居 LSDB 的…

智能安全用电系统预防电气线路老化、线路或设备绝缘故障

智能安全用电系统预防电气线路老化、线路或设备绝缘故障 智能安全用电系统&#xff0c;犹如一位忠实而敏锐的卫士&#xff0c;主要针对低压供电网中一系列潜在的危险状况进行了全方位且行之有效的预防和保护。 智能安全用电系统在低压供电网这个复杂的体系中&#xff0c;电气线…

使用Intel Advisor工具分析程序

使用Intel Advisor工具分析程序 Intel Advisor是一款性能分析工具&#xff0c;主要用于识别代码中的向量化机会、线程化和内存访问模式等问题。以下是使用Intel Advisor分析程序的基本步骤&#xff1a; 安装与准备 从Intel官网下载并安装Intel Advisor&#xff08;通常作为I…

【UniApp】Vue2 scss 预编译器默认已由 node-sass 更换为 dart-sass

从 HBuilderX 4.56 &#xff0c;vue2 项目也将默认使用 dart-sass 预编译器。 vue2开发者sass预处理注意&#xff1a; sass的预处理器&#xff0c;早年使用node-sass&#xff0c;也就是vue2最初默认的编译器。 sass官方推出了dart-sass来替代。node-sass已经停维很久了。 另…

智慧能源安全新纪元:当能源监测遇上视频联网的无限可能

引言&#xff1a;在数字化浪潮席卷全球的今天&#xff0c;能源安全已成为国家安全战略的重要组成部分。如何构建更加智能、高效的能源安全保障体系&#xff1f;能源安全监测平台与视频监控联网平台的深度融合&#xff0c;正为我们开启一扇通向未来能源管理新世界的大门。这种创…

C++游戏服务器开发之⑦redis的使用

目录 1.当前进度 2.守护进程 3.进程监控 4.玩家姓名添加文件 5.文件删除玩家姓名 6.redis安装 7.redis存取命令 8.redis链表存取 9.redis程序结构 10.hiredisAPI使用 11.基于redis查找玩家姓名 12.MAKEFILE编写 13.游戏业务实现总结 1.当前进度 2.守护进程 3.进程监…

db中查询关于null的sql该怎么写

正确示例 # 等于null select * from 表名 where 字段名 is NULL; # 不等于null select * from 表名 where 字段名 is not NULL;若需要同时判断字段不等于某个值且不为null select * from users where age ! 30 and age is not null; select * from users where age ! 30 or a…

从“堆料竞赛”到“体验深耕”,X200 Ultra和X200s打响手机价值升维战

出品 | 何玺 排版 | 叶媛 vivo双旗舰来袭&#xff01; 4月21日&#xff0c;vivo X系列春季新品发布会盛大开启&#xff0c;带来了一场科技与创新的盛宴。会上&#xff0c;消费者期待已久的X200 Ultra及X200s两款旗舰新品正式发布。 vivo两款旗舰新品发布后&#xff0c;其打破…

多模态大语言模型arxiv论文略读(三十二)

Proximity QA: Unleashing the Power of Multi-Modal Large Language Models for Spatial Proximity Analysis ➡️ 论文标题&#xff1a;Proximity QA: Unleashing the Power of Multi-Modal Large Language Models for Spatial Proximity Analysis ➡️ 论文作者&#xff1a…