- 绘制轨迹路线
- 轨迹路线描边
- 增加起点终点图标
- 绘制仿高德方向箭头
- 模仿车辆动态运动动画
车辆运行轨迹
车辆轨迹经纬度坐标
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);
};
效果如下:
绘制仿高德方向箭头
参考了很多文章,算是把原理弄通了,简单的描述一下
-
首先计算路径长度,按照路径长度除以固定宽度分割,计算需要显示箭头的位置
举个例子
a. 当前坐标[0,0] 下一个坐标是[1,2]
b. 计算这俩个坐标之间的距离,假设100米,每50米显示一个箭头,也就是需要三个箭头
c. Math.atan2 可以计算[0,0]和[1,2] 之间旋转的角度,这三个箭头旋转都是这个角度 -
按照当前点位和下一个点位计算出箭头的方向,主要通过js 提供的函数
-
那知道需要绘制箭头的地方就简单了,直接增加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();
};