当前位置: 首页 > news >正文

canvas绘制红绿灯路口(二)

系列文章
canvas绘制红绿灯路口(一)

无图不欢,先上图
在这里插入图片描述

优化项:
一:加入人行道红绿信号
二:加入专用车道标识(无方向标识时采用专用车道标识)
三:东南西北四项路口优化绘制逻辑,美化图像
四:加入拖拽、缩放图例

使用方法(以vue3为例)

<template><canvas class="lane" ref="laneCanvas" />
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import Lane from 'services/roadCanvas/lane';const laneCanvas = ref(null);
/*** 车道方向,进口方向* 1 - 北,2 - 东北,3 - 东,4 - 东南,* 5 - 南,6 - 西南,7 - 西,8 - 西北** 直行放行 nThrough 0不放行 1放行* 左转放行 nTurnLeft 0不放行 1放行* 右转放行 nTurnRight 0不放行 1放行* 调头 nTurnAround 0不放行 1放行** 通道相位 nChannelNumberPhase 1-红灯 2绿灯 3黄灯*/const data = [{'approachDirection': 1,'cdireCtion': '北','lanes': [{'laneNo': '1','through': 0,'turnLeft': 1,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '左转','channelNumberPhase': '2','trafficLightColor': '#33CC00'},{'laneNo': '2','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '3','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '4','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '5','through': 0,'turnLeft': 0,'turnRight': 1,'turnAround': 0,'directionIdentifyings': '右转','channelNumberPhase': 0,'trafficLightColor': '#ccc'}],'peoples': [{'laneNo': '0','lfd': '0(人行)','channelNumberPhase': '1'},{'laneNo': '99','lfd': '99(人行)','channelNumberPhase': '1'}]},{'approachDirection': 3,'cdireCtion': '东','lanes': [{'laneNo': '1','through': 0,'turnLeft': 1,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '左转','channelNumberPhase': 0,'trafficLightColor': '#ccc'},{'laneNo': '2','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '3','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '4','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '5','through': 0,'turnLeft': 0,'turnRight': 1,'turnAround': 0,'directionIdentifyings': '右转','channelNumberPhase': 0,'trafficLightColor': '#ccc'}],'peoples': [{'laneNo': '0','lfd': '0(人行)','channelNumberPhase': '2'},{'laneNo': '99','lfd': '99(人行)','channelNumberPhase': '1'}]},{'approachDirection': 5,'cdireCtion': '南','lanes': [{'laneNo': '1','through': 0,'turnLeft': 1,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '左转','channelNumberPhase': '2','trafficLightColor': '#33CC00'},{'laneNo': '2','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '3','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '4','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '5','through': 0,'turnLeft': 0,'turnRight': 1,'turnAround': 0,'directionIdentifyings': '右转','channelNumberPhase': 0,'trafficLightColor': '#ccc'}],'peoples': [{'laneNo': '0','lfd': '0(人行)','channelNumberPhase': '1'},{'laneNo': '99','lfd': '99(人行)','channelNumberPhase': '1'}]},{'approachDirection': 7,'cdireCtion': '西','lanes': [{'laneNo': '1','through': 0,'turnLeft': 1,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '左转','channelNumberPhase': 0,'trafficLightColor': '#ccc'},{'laneNo': '2','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '3','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '4','through': 1,'turnLeft': 0,'turnRight': 0,'turnAround': 0,'directionIdentifyings': '直行','channelNumberPhase': '1','trafficLightColor': '#FF0033'},{'laneNo': '5','through': 0,'turnLeft': 0,'turnRight': 1,'turnAround': 0,'directionIdentifyings': '右转','channelNumberPhase': 0,'trafficLightColor': '#ccc'}],'peoples': [{'laneNo': '0','lfd': '0(人行)','channelNumberPhase': '2'},{'laneNo': '99','lfd': '99(人行)','channelNumberPhase': '1'}]}
];
let laneC = null;onMounted(() => {laneC = new Lane({canvas: laneCanvas.value,data});// 如红绿数据更新可采用setData方法刷新红绿状态// laneC.setData(data)
});onUnmounted(() => {laneC?.destroy();laneC = null;
});</script><style scoped lang="scss">
.lane {width: 100%;height: 100%;background-color: #325e76;
}
</style>

lane.js封装如下

import { getDirectionIdentifyings, computePosition } from './baseDI';class Lane {constructor(opt) {this.dpr = window.devicePixelRatio || 1;this.canvas = opt.canvas;this.w = null;this.h = null;this.ctx = null;this.data = opt.data;// 车道范围坐标this.region = [];// 车道线坐标this.dataXY = [];// 路中心空白区域占canvas宽高最小值的比,用来计算车道宽度。占比越大,中心空白区域越大,车道越宽,线路越短。取值范围0-1,不允许取0,1。this.laneCenterProportion = 'auto' || opt.laneCenterProportion; // ex: 0.8// 车道样式this.laneStyle = opt.laneStyle;// 缩放this.scaleFlag = false;this.mouseScaleSpeed = 5; // 缩放速度this.scaleIndex = 100; // 初始缩放系数this.normalScaleIndex = 100; // 标准缩放系数this.minScaleIndex = 50; // 最小缩放系数this.scaleC = this.scaleIndex / this.normalScaleIndex; // 缩放比例// 平移this.translate = {x: 0,y: 0};// 异步任务listthis.taskList = [];this.hasTaskDone = false;this.status = 'do'; // do or stopthis.init();}init() {if (!this.canvas) {return;}if (this.canvas.width !== Math.floor(this.canvas.offsetWidth * this.dpr) || this.canvas.height !== Math.floor(this.canvas.offsetHeight * this.dpr)) {// eslint-disable-next-linethis.w = this.canvas.width = Math.floor(this.canvas.offsetWidth * this.dpr);// eslint-disable-next-linethis.h = this.canvas.height = Math.floor(this.canvas.offsetHeight * this.dpr);} else {this.w = this.canvas.width;this.h = this.canvas.height;}this.ctx = this.canvas.getContext('2d');this.getLaneStyle();this.formatDataXY();this.getRegion();this.draw();this.addEvent();this.addAnimationFrame();}// 获取车道样式getLaneStyle() {const laneStyle = {// 车道范围region: {width: 2 * this.dpr,color: '#fff',type: 'solid',CurveType: 'quadratic', // normal: 插值曲线, quadratic: 二次贝塞尔, arc: 圆弧线。arc有问题,请勿使用background: '#1f2748'},// 车道左侧车道线innerLeft: {width: 1 * this.dpr,color: '#999',type: [10 * this.dpr, 10 * this.dpr],},// 车道右侧车道线innerRight: {width: 1 * this.dpr,color: '#eee',type: [10 * this.dpr, 10 * this.dpr],},// 车道分割线innerDivider: {width: 2 * this.dpr,color: '#f0bf0a',type: 'solid'},// 车道标识direction: {widthProportion: 0.1, // 占车道比例,建议小于0.2HeightWidthProportion: 10, // 高宽比,建议大于5maxWidth: 20 * this.dpr,arrowWidth: 2, // 箭头/方向线的比例, 建议大于1小于2background: '#ddd'},// 斑马线zebraCrossing: {widthProportion: 0.05, // 单个斑马线宽占车道比例,建议小于0.2widthHeightProportion: 0.2, // 单个斑马线宽高比,建议小于0.5color: '#ddd'},// 红绿灯trafficLight: {rProportion: 0.3, // 单个红绿灯半径占车道比例,建议小于0.5,colors: ['#fff', '#FF0033', '#33CC00', '#FFFF33'],}};if (this.laneStyle) {this.laneStyle = Object.assign(laneStyle, this.laneStyle);} else {this.laneStyle = laneStyle;}const laneMaxNum = this.getLaneMaxNum();const sideLength = this.getSideLength();// 车道宽度 / 2 表示双向this.laneStyle.width = sideLength / 2 / laneMaxNum;// 方向表示线宽高this.laneStyle.direction.width = this.laneStyle.width * this.laneStyle.direction.widthProportion;if (this.laneStyle.direction.width > this.laneStyle.direction.maxWidth) {this.laneStyle.direction.width = this.laneStyle.direction.maxWidth;}this.laneStyle.direction.height = this.laneStyle.direction.width * this.laneStyle.direction.HeightWidthProportion;// 斑马线宽高this.laneStyle.zebraCrossing.width = this.laneStyle.width * this.laneStyle.zebraCrossing.widthProportion;this.laneStyle.zebraCrossing.height = this.laneStyle.zebraCrossing.width / this.laneStyle.zebraCrossing.widthHeightProportion;this.laneStyle.zebraCrossing.type = [this.laneStyle.zebraCrossing.width * 4, this.laneStyle.zebraCrossing.width];// 红绿灯半径this.laneStyle.trafficLight.r = this.laneStyle.width * this.laneStyle.trafficLight.rProportion;}// 获取最大车道数getLaneMaxNum() {let laneMaxNum = 0;this.data.forEach(item => {if (item.lanes.length > laneMaxNum) {laneMaxNum = item.lanes.length;}});if (laneMaxNum === 1) {laneMaxNum = 2;}return laneMaxNum;}// 获取中心路口(四边形/八边形)边长getSideLength() {const minW = this.w > this.h ? this.h : this.w;let legitimate = true;let maxLans = 0;const cdireCtions = ['东', '南', '西', '北'];for (let i = 0; i < this.data.length; i++) {if (cdireCtions.indexOf(this.data[i].cdireCtion) === -1) {legitimate = false;}if (this.data[i].lanes.length > maxLans) {maxLans = this.data[i].lanes.length;}}if (this.laneCenterProportion === 'auto') {this.laneCenterProportion = maxLans / 10 > 0.8 ? 0.8 : maxLans / 10;}if (legitimate) {return minW * this.laneCenterProportion / 1.1;}return minW * this.laneCenterProportion / (Math.sqrt(2) + 1);}// 计算车道坐标formatDataXY() {const dataXY = [];// 车道起始中心位置const centerX = this.w / 2;const centerY = this.h - this.h * (1 - this.laneCenterProportion) / 2;// 车道长度const laneLength = Math.sqrt(this.w ** 2 * this.h ** 2);this.data.forEach(dataItem => {const dataXYItem = {approachDirection: dataItem.approachDirection,};// 起始xconst startX = centerX - this.laneStyle.width * dataItem.lanes.length;// 起始yconst startY = centerY + this.laneStyle.zebraCrossing.height * 2;// 结束Yconst endY = startY + laneLength;// 线const lines = [];// 单向车道分割线数量const innerLines = dataItem.lanes.length - 1;// 车道左边线lines.push({x0: startX,y0: startY - this.laneStyle.zebraCrossing.height * 2,x1: startX,y1: endY,type: 'outer'});// 车道左侧分割线for (let i = 0; i < innerLines; i++) {const x = startX + (i + 1) * this.laneStyle.width;lines.push({x0: x,y0: startY,x1: x,y1: endY,style: { ...this.laneStyle.innerLeft }});}// 左右车道分割线const dividerX = startX + (innerLines + 1) * this.laneStyle.width;lines.push({x0: dividerX,y0: startY,x1: dividerX,y1: endY,style: { ...this.laneStyle.innerDivider }});// 车道右侧分割线for (let i = 0; i < innerLines; i++) {const x = startX + (innerLines + i + 2) * this.laneStyle.width;lines.push({x0: x,y0: startY,x1: x,y1: endY,style: { ...this.laneStyle.innerRight }});}// 车道右边线const outerRightx = startX + (innerLines + 1) * 2 * this.laneStyle.width;lines.push({x0: outerRightx,y0: startY - this.laneStyle.zebraCrossing.height * 2,x1: outerRightx,y1: endY,type: 'outer'});dataXYItem.lines = lines;// 方向标识const directionIdentifyings = [];for (let i = 0; i < dataItem.lanes.length; i++) {const laneItem = dataItem.lanes[i];const key = [laneItem.through, laneItem.turnLeft, laneItem.turnRight, laneItem.turnAround].join('');const line = lines[innerLines + i + 1];directionIdentifyings.push(getDirectionIdentifyings(key, this.laneStyle.direction.width, this.laneStyle.direction.height, {x: line.x0 + this.laneStyle.width / 2,y: line.y0 + this.laneStyle.direction.height / 2 + this.laneStyle.trafficLight.r * 4}, this.laneStyle.direction.arrowWidth));}dataXYItem.directionIdentifyings = directionIdentifyings;// 斑马线if (dataItem.peoples.length === 1) {dataXYItem.zebraCrossing = [{x0: lines[0].x0 + this.laneStyle.zebraCrossing.width * 4,y0: startY - this.laneStyle.zebraCrossing.height,x1: lines[lines.length - 1].x0 - this.laneStyle.zebraCrossing.width * 4,y1: startY - this.laneStyle.zebraCrossing.height,color: this.laneStyle.trafficLight.colors[dataItem.peoples[0].channelNumberPhase]}];} else if (dataItem.peoples.length === 2) {dataXYItem.zebraCrossing = [{x0: lines[0].x0 + this.laneStyle.zebraCrossing.width * 4,y0: startY - this.laneStyle.zebraCrossing.height,x1: lines[(lines.length - 1) / 2].x0,y1: startY - this.laneStyle.zebraCrossing.height,color: this.laneStyle.trafficLight.colors[dataItem.peoples[0].channelNumberPhase]}, {x0: lines[(lines.length - 1) / 2].x0,y0: startY - this.laneStyle.zebraCrossing.height,x1: lines[lines.length - 1].x0 - this.laneStyle.zebraCrossing.width * 4,y1: startY - this.laneStyle.zebraCrossing.height,color: this.laneStyle.trafficLight.colors[dataItem.peoples[1].channelNumberPhase]}];} else {dataXYItem.zebraCrossing = [{x0: lines[0].x0 + this.laneStyle.zebraCrossing.width * 4,y0: startY - this.laneStyle.zebraCrossing.height,x1: lines[lines.length - 1].x0 - this.laneStyle.zebraCrossing.width * 4,y1: startY - this.laneStyle.zebraCrossing.height,color: this.laneStyle.trafficLight.colors[0]}];}// 红绿灯const trafficLights = [];for (let i = 0; i < dataItem.lanes.length; i++) {const laneItem = dataItem.lanes[i];const line = lines[innerLines + i + 1];trafficLights.push({x: line.x0 + this.laneStyle.width / 2,y: line.y0 + this.laneStyle.trafficLight.r * 2,r: this.laneStyle.trafficLight.r,color: this.laneStyle.trafficLight.colors[laneItem.channelNumberPhase]});}dataXYItem.trafficLights = trafficLights;dataXY.push(dataXYItem);});this.dataXYByRotate(dataXY);this.dataXY = dataXY;}// 计算旋转坐标dataXYByRotate(dataXY) {const centerX = this.w / 2;const centerY = this.h / 2;dataXY.forEach(dataXYItem => {// 八边形,一个边占45度const rotateReg = -180 + (dataXYItem.approachDirection - 1) * 45;dataXYItem.lines.forEach(line => {const xy0 = computePosition(line.x0, line.y0, rotateReg, centerX, centerY);line.x0 = xy0.x;line.y0 = xy0.y;const xy1 = computePosition(line.x1, line.y1, rotateReg, centerX, centerY);line.x1 = xy1.x;line.y1 = xy1.y;});dataXYItem.directionIdentifyings.forEach(directionIdentifying => {directionIdentifying.points.forEach(point => {point.forEach(item => {const { x, y } = computePosition(item.x, item.y, rotateReg, centerX, centerY);item.x = x;item.y = y;});});directionIdentifying.arrowPoints.forEach(arrowPoint => {arrowPoint.forEach(item => {const { x, y } = computePosition(item.x, item.y, rotateReg, centerX, centerY);item.x = x;item.y = y;});});});dataXYItem.zebraCrossing.forEach(zebraCrossing => {const xy0 = computePosition(zebraCrossing.x0, zebraCrossing.y0, rotateReg, centerX, centerY);zebraCrossing.x0 = xy0.x;zebraCrossing.y0 = xy0.y;const xy1 = computePosition(zebraCrossing.x1, zebraCrossing.y1, rotateReg, centerX, centerY);zebraCrossing.x1 = xy1.x;zebraCrossing.y1 = xy1.y;});dataXYItem.trafficLights.forEach(trafficLight => {const { x, y } = computePosition(trafficLight.x, trafficLight.y, rotateReg, centerX, centerY);trafficLight.x = x;trafficLight.y = y;});});}// 获取车道范围getRegion() {const region = [];for (let i = 0; i < this.dataXY.length; i++) {const dataXYItem = this.dataXY[i];const linesLength = dataXYItem.lines.length;if (i !== 0) {// 衔接上一车道const prevDataXYItem = this.dataXY[i - 1];const data = {prevapproachDirection: prevDataXYItem.approachDirection,approachDirection: dataXYItem.approachDirection,type: 'connect'};let diffapproachDirection = dataXYItem.approachDirection - prevDataXYItem.approachDirection;if (diffapproachDirection > 4) {diffapproachDirection = (prevDataXYItem.approachDirection + 8) - dataXYItem.approachDirection;}if (diffapproachDirection === 4) {// 车道正对,直线即可data.point = [{ x: prevDataXYItem.lines[0].x0, y: prevDataXYItem.lines[0].y0 },{ x: dataXYItem.lines[linesLength - 1].x0, y: dataXYItem.lines[linesLength - 1].y0 },];region.push(data);} else {if (this.laneStyle.region.CurveType === 'arc') {const angle = 45 * diffapproachDirection;const startAngle = 45 * (prevDataXYItem.approachDirection - 5);data.OR = this.findCircleCenter(prevDataXYItem.lines[0].x0,prevDataXYItem.lines[0].y0,dataXYItem.lines[linesLength - 1].x0,dataXYItem.lines[linesLength - 1].y0,angle,true);data.OR.startAngle = startAngle;data.OR.endAngle = startAngle - angle;data.OR.anticlockwise = true;} else {// 曲线const laneXY0 = this.calculateIntersection([[prevDataXYItem.lines[0].x0, prevDataXYItem.lines[0].y0],[prevDataXYItem.lines[0].x1, prevDataXYItem.lines[0].y1],], [[dataXYItem.lines[linesLength - 1].x0, dataXYItem.lines[linesLength - 1].y0],[dataXYItem.lines[linesLength - 1].x1, dataXYItem.lines[linesLength - 1].y1],]);const laneXY1 = [(prevDataXYItem.lines[0].x0 + dataXYItem.lines[linesLength - 1].x0) / 2, (prevDataXYItem.lines[0].y0 + dataXYItem.lines[linesLength - 1].y0) / 2];const originPoints = [{ x: prevDataXYItem.lines[0].x0, y: prevDataXYItem.lines[0].y0 },{ x: laneXY1[0] - (laneXY1[0] - laneXY0[0]) * diffapproachDirection / 4, y: laneXY1[1] - (laneXY1[1] - laneXY0[1]) * diffapproachDirection / 4 },{ x: dataXYItem.lines[linesLength - 1].x0, y: dataXYItem.lines[linesLength - 1].y0 },];if (this.laneStyle.region.CurveType === 'normal') {const point = this.getCurveVertex(originPoints);data.point = point;} else {data.point = originPoints;}}region.push(data);}}// 车道范围region.push({approachDirection: dataXYItem.approachDirection,x0: dataXYItem.lines[linesLength - 1].x0,y0: dataXYItem.lines[linesLength - 1].y0,x1: dataXYItem.lines[linesLength - 1].x1,y1: dataXYItem.lines[linesLength - 1].y1,x2: dataXYItem.lines[0].x1,y2: dataXYItem.lines[0].y1,x3: dataXYItem.lines[0].x0,y3: dataXYItem.lines[0].y0,type: 'lane'});if (i === this.dataXY.length - 1) {// 衔接起始车道const startDataXYItem = this.dataXY[0];const startLinesLength = startDataXYItem.lines.length;const data = {startapproachDirection: startDataXYItem.approachDirection,approachDirection: dataXYItem.approachDirection,type: 'connect'};let diffapproachDirection = startDataXYItem.approachDirection + 8 - dataXYItem.approachDirection;if (diffapproachDirection > 4) {diffapproachDirection = dataXYItem.approachDirection - startDataXYItem.approachDirection;}if (diffapproachDirection === 4) {// 车道正对,直线即可data.point = [{ x: dataXYItem.lines[0].x0, y: dataXYItem.lines[0].y0 },{ x: startDataXYItem.lines[startLinesLength - 1].x0, y: startDataXYItem.lines[startLinesLength - 1].y0 },];region.push(data);} else {if (this.laneStyle.region.CurveType === 'arc') {const angle = 45 * diffapproachDirection;const startAngle = 45 * (dataXYItem.approachDirection - 1);data.OR = this.findCircleCenter(dataXYItem.lines[0].x0,dataXYItem.lines[0].y0,startDataXYItem.lines[linesLength - 1].x0,startDataXYItem.lines[linesLength - 1].y0,angle,true);data.OR.endAngle = startAngle + angle;data.OR.startAngle = startAngle;data.OR.anticlockwise = false;} else {// 曲线const laneXY0 = this.calculateIntersection([[dataXYItem.lines[0].x0, dataXYItem.lines[0].y0],[dataXYItem.lines[0].x1, dataXYItem.lines[0].y1],], [[startDataXYItem.lines[startLinesLength - 1].x0, startDataXYItem.lines[startLinesLength - 1].y0],[startDataXYItem.lines[startLinesLength - 1].x1, startDataXYItem.lines[startLinesLength - 1].y1],]);const laneXY1 = [(startDataXYItem.lines[startLinesLength - 1].x0 + dataXYItem.lines[0].x0) / 2, (startDataXYItem.lines[startLinesLength - 1].y0 + dataXYItem.lines[0].y0) / 2];const originPoints = [{ x: dataXYItem.lines[0].x0, y: dataXYItem.lines[0].y0 },{ x: laneXY1[0] - (laneXY1[0] - laneXY0[0]) * diffapproachDirection / 4, y: laneXY1[1] - (laneXY1[1] - laneXY0[1]) * diffapproachDirection / 4 },{ x: startDataXYItem.lines[startLinesLength - 1].x0, y: startDataXYItem.lines[startLinesLength - 1].y0 },];if (this.laneStyle.region.CurveType === 'normal') {const point = this.getCurveVertex(originPoints);data.point = point;} else {data.point = originPoints;}}region.push(data);}}}this.region = region;}// 获取两条直线的交点calculateIntersection(line1, line2) {// 解方程组const x1 = line1[0][0];const y1 = line1[0][1];const x2 = line1[1][0];const y2 = line1[1][1];const x3 = line2[0][0];const y3 = line2[0][1];const x4 = line2[1][0];const y4 = line2[1][1];const denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);if (denominator === 0) {// 直线平行,没有交点return null;}const intersectionX = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denominator;const intersectionY = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denominator;return [intersectionX, intersectionY];}// 以下四个方法获取曲线getCurveVertex(vertex, pointsPow = 0.4) {let length = 0;for (let i = 0; i < vertex.length - 1; i++) {length += Math.sqrt((vertex[i].x - vertex[i + 1].x) ** 2 + (vertex[i].y - vertex[i + 1].y) ** 2);}length = Math.ceil(length);return this.getNewData(vertex, length * pointsPow);}// 曲线 插值getNewData(pointsOrigin, pointsPow) {const points = [];const divisions = (pointsOrigin.length - 1) * pointsPow;for (let i = 0; i < divisions; i++) {points.push(this.getPoint(i, divisions, pointsOrigin, pointsPow));}return points;}getPoint(i, divisions, pointsOrigin, pointsPow) {const isRealI = (i * divisions) % pointsPow;const p = ((pointsOrigin.length - 1) * i) / divisions;const intPoint = Math.floor(p);const weight = p - intPoint;const p0 = pointsOrigin[intPoint === 0 ? intPoint : intPoint - 1];const p1 = pointsOrigin[intPoint];const p2 = pointsOrigin[intPoint > pointsOrigin.length - 2 ? pointsOrigin.length - 1 : intPoint + 1];const p3 = pointsOrigin[intPoint > pointsOrigin.length - 3 ? pointsOrigin.length - 1 : intPoint + 2];return {isReal: isRealI === 0,x: this.catmullRom(weight, p0.x, p1.x, p2.x, p3.x),y: this.catmullRom(weight, p0.y, p1.y, p2.y, p3.y)};}catmullRom(t, p0, p1, p2, p3) {const v0 = (p2 - p0) * 0.5;const v1 = (p3 - p1) * 0.5;const t2 = t * t;const t3 = t * t2;return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;}// 根据圆上两点以及夹角角度 求 圆心findCircleCenter(x1, y1, x2, y2, theta, isNeg) {let cx = 0;let cy = 0;const dDistance = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));const dRadius = dDistance * 0.5 / Math.sin(Math.PI / 180 * theta * 0.5);if (dDistance === 0.0) {// cout << "\n输入了相同的点!\n";return false;}if ((2 * dRadius) < dDistance) {// cout << "\n两点间距离大于直径!\n";return false;}let k_verticle = 0.0;let mid_x = 0.0;let mid_y = 0.0;let a = 1.0;let b = 1.0;let c = 1.0;const k = (y2 - y1) / (x2 - x1);let cx1; let cy1; let cx2; letcy2;if (k === 0) {cx1 = (x1 + x2) / 2.0;cx2 = (x1 + x2) / 2.0;cy1 = y1 + Math.sqrt(dRadius * dRadius - (x1 - x2) * (x1 - x2) / 4.0);cy2 = y2 - Math.sqrt(dRadius * dRadius - (x1 - x2) * (x1 - x2) / 4.0);} else {k_verticle = -1.0 / k;mid_x = (x1 + x2) / 2.0;mid_y = (y1 + y2) / 2.0;a = 1.0 + k_verticle * k_verticle;b = -2 * mid_x - k_verticle * k_verticle * (x1 + x2);c = mid_x * mid_x + k_verticle * k_verticle * (x1 + x2) * (x1 + x2) / 4.0- (dRadius * dRadius - ((mid_x - x1) * (mid_x - x1) + (mid_y - y1) * (mid_y - y1)));cx1 = (-1.0 * b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);cx2 = (-1.0 * b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);cy1 = this.y_Coordinates(mid_x, mid_y, k_verticle, cx1);cy2 = this.y_Coordinates(mid_x, mid_y, k_verticle, cx2);}// cx2,cy2为顺时针圆心坐标,cx1,cy1为逆时针圆心坐标if (isNeg) {cx = cx1;cy = cy1;} else {cx = cx2;cy = cy2;}return { x: cx, y: cy, r: Math.sqrt((cx - x1) ** 2 + (cy - y1) ** 2) };}y_Coordinates(x, y, k, x0) {return k * x0 - k * x + y;}// 设置新的红绿灯数据setData(data) {this.dataXY.forEach((dataXYItem, dataXYIndex) => {dataXYItem.trafficLights.forEach((trafficLight, trafficLightIndex) => {if (data[dataXYIndex]?.lanes[trafficLightIndex]) {trafficLight.color = this.laneStyle.trafficLight.colors[data[dataXYIndex].lanes[trafficLightIndex].channelNumberPhase];}});dataXYItem.zebraCrossing.forEach((zebra, zebraIndex) => {if (data[dataXYIndex]?.peoples[zebraIndex]) {zebra.color = this.laneStyle.trafficLight.colors[data[dataXYIndex].peoples[zebraIndex].channelNumberPhase];}});});this.addTask();}// 重新绘制reDraw() {this.ctx.clearRect(0, 0, this.w, this.h);this.draw();}// 绘制draw() {this.drawRegion();this.drawLines();this.drawDirectionIdentifyings();this.drawZebraCrossing();this.drawTrafficLight();// this.drawHelper()}// 缩放、平移translateAndScale() {// 缩放this.ctx.translate(this.w / 2, this.h / 2);this.ctx.scale(this.scaleC, this.scaleC);this.ctx.translate(-this.w / 2, -this.h / 2);// 平移this.ctx.translate(this.translate.x, this.translate.y);}// 绘制车道范围drawRegion() {this.ctx.save();this.translateAndScale();this.ctx.beginPath();this.ctx.fillStyle = this.laneStyle.region.background;this.ctx.lineWidth = this.laneStyle.region.width;this.ctx.strokeStyle = this.laneStyle.region.color;this.ctx.lineJoin = 'round';for (let i = 0; i < this.region.length; i++) {const regionItem = this.region[i];if (regionItem.type === 'connect') {if (regionItem?.point?.length === 2 && this.laneStyle.region.CurveType !== 'arc') {// 直线regionItem.point.forEach(item => {this.ctx.lineTo(item.x, item.y);});} else if (this.laneStyle.region.CurveType === 'arc') {// 圆if (regionItem.OR) this.ctx.arc(regionItem.OR.x, regionItem.OR.y, regionItem.OR.r, regionItem.OR.startAngle * Math.PI / 180, regionItem.OR.endAngle * Math.PI / 180, regionItem.OR.anticlockwise);} else if (this.laneStyle.region.CurveType === 'normal') {// 插值regionItem.point.forEach(item => {this.ctx.lineTo(item.x, item.y);});} else {// 二次贝塞尔this.ctx.lineTo(regionItem.point[0].x, regionItem.point[0].y);this.ctx.quadraticCurveTo(regionItem.point[1].x, regionItem.point[1].y, regionItem.point[2].x, regionItem.point[2].y);}} else {this.ctx.lineTo(regionItem.x0, regionItem.y0);this.ctx.lineTo(regionItem.x1, regionItem.y1);this.ctx.lineTo(regionItem.x2, regionItem.y2);this.ctx.lineTo(regionItem.x3, regionItem.y3);}}this.ctx.fill();this.ctx.stroke();this.ctx.closePath();this.ctx.restore();}// 绘制车道线drawLines() {this.dataXY.forEach((dataXYItem) => {dataXYItem.lines.forEach(lineItem => {if (lineItem.type !== 'outer') {this.ctx.save();this.translateAndScale();this.ctx.beginPath();this.ctx.lineWidth = lineItem.style.width;this.ctx.strokeStyle = lineItem.style.color;if (lineItem.style.type !== 'solid') {this.ctx.setLineDash(lineItem.style.type);}this.ctx.lineTo(lineItem.x0, lineItem.y0);this.ctx.lineTo(lineItem.x1, lineItem.y1);this.ctx.stroke();this.ctx.closePath();this.ctx.restore();}});});}// 绘制方向箭头drawDirectionIdentifyings() {this.dataXY.forEach((dataXYItem) => {dataXYItem.directionIdentifyings.forEach(directionIdentifying => {this.ctx.save();this.translateAndScale();directionIdentifying.points.forEach(pointItem => {this.ctx.beginPath();this.ctx.lineWidth = directionIdentifying.w;this.ctx.strokeStyle = this.laneStyle.direction.background;if (directionIdentifying.exclusive) {this.ctx.setLineDash([directionIdentifying.w, directionIdentifying.w]);}pointItem.forEach(item => {this.ctx.lineTo(item.x, item.y);});this.ctx.stroke();this.ctx.closePath();this.ctx.setLineDash([]);});directionIdentifying.arrowPoints.forEach(arrowPoint => {this.ctx.beginPath();this.ctx.fillStyle = this.laneStyle.direction.background;arrowPoint.forEach(item => {this.ctx.lineTo(item.x, item.y);});this.ctx.fill();this.ctx.closePath();});this.ctx.restore();});});}// 绘制信号灯drawTrafficLight() {this.dataXY.forEach((dataXYItem) => {dataXYItem.trafficLights.forEach(trafficLight => {this.ctx.save();this.translateAndScale();this.ctx.beginPath();this.ctx.fillStyle = trafficLight.color;this.ctx.arc(trafficLight.x, trafficLight.y, trafficLight.r, 0, Math.PI * 2);this.ctx.fill();this.ctx.closePath();this.ctx.restore();});});}// 绘制斑马线drawZebraCrossing() {this.dataXY.forEach((dataXYItem) => {this.ctx.save();this.translateAndScale();this.ctx.beginPath();this.ctx.lineWidth = this.laneStyle.zebraCrossing.height;dataXYItem.zebraCrossing.forEach(zebraCrossing => {this.ctx.strokeStyle = zebraCrossing.color;this.ctx.lineTo(zebraCrossing.x0, zebraCrossing.y0);this.ctx.lineTo(zebraCrossing.x1, zebraCrossing.y1);});this.ctx.stroke();this.ctx.closePath();this.ctx.beginPath();this.ctx.lineWidth = this.laneStyle.zebraCrossing.height + 1;this.ctx.strokeStyle = this.laneStyle.region.background;this.ctx.setLineDash(this.laneStyle.zebraCrossing.type);dataXYItem.zebraCrossing.forEach(zebraCrossing => {this.ctx.lineTo(zebraCrossing.x0, zebraCrossing.y0);this.ctx.lineTo(zebraCrossing.x1, zebraCrossing.y1);});this.ctx.stroke();this.ctx.closePath();this.ctx.restore();});}drawHelper() {// 绘制车道方向数字,用来查看车道是否正确this.ctx.beginPath();this.ctx.fillStyle = '#fff';this.ctx.font = 20 * this.dpr + 'px Arial';for (let i = 0; i < this.region.length; i++) {const regionItem = this.region[i];this.ctx.fillText(regionItem.approachDirection, regionItem.x0, regionItem.y0);}this.ctx.closePath();// 绘制坐标线this.ctx.save();this.ctx.lineWidth = 2 * this.dpr;this.ctx.strokeStyle = 'red';this.ctx.beginPath();this.ctx.lineTo(this.w / 2, 0);this.ctx.lineTo(this.w / 2, this.h);this.ctx.stroke();this.ctx.closePath();this.ctx.beginPath();this.ctx.lineTo(0, this.h / 2);this.ctx.lineTo(this.w, this.h / 2);this.ctx.stroke();this.ctx.closePath();this.ctx.restore();}// 事件相关addEvent() {// 缩放this.mousewheelBind = this.mousewheel.bind(this);this.canvas.addEventListener('mousewheel', this.mousewheelBind);// 平移this.canvasMousedownBind = this.canvasMousedown.bind(this);this.documentMouseupBind = this.documentMouseup.bind(this);this.documentMouseMoveBind = this.documentMouseMove.bind(this);this.canvas.addEventListener('mousedown', this.canvasMousedownBind);document.addEventListener('mousemove', this.documentMouseMoveBind);}removeEvent() {if (this.mousewheelBind) {this.canvas.removeEventListener('mousewheel', this.mousewheelBind);}if (this.canvasMousedownBind) {this.canvas.removeEventListener('mousedown', this.canvasMousedownBind);}if (this.documentMouseMoveBind) {document.removeEventListener('mousemove', this.documentMouseMoveBind);}if (this.documentMouseupBind) {document.removeEventListener('mouseup', this.documentMouseupBind);}}mousewheel(e) {if (this.scaleFlag) {return;}this.scaleFlag = true;if (e.wheelDelta > 0) {this.scaleIndex += this.mouseScaleSpeed;} else if (this.scaleIndex > this.minScaleIndex) {this.scaleIndex -= this.mouseScaleSpeed;}this.scaleC = this.scaleIndex / this.normalScaleIndex;// canvas缩放操作this.addTask();this.scaleFlag = false;}canvasMousedown(e) {this.mousedownXY = {x: e.clientX,y: e.clientY};document.addEventListener('mouseup', this.documentMouseupBind);e.preventDefault();}documentMouseMove(e) {if (this.moveFlag) {return;}if (!this.mousedownXY) {return;}// 长按移动this.moveFlag = true;const E = {x: e.clientX,y: e.clientY};this.translate.x += ((E.x - this.mousedownXY.x) * this.dpr) / this.scaleC;this.translate.y += ((E.y - this.mousedownXY.y) * this.dpr) / this.scaleC;// canvas拖拽操作this.addTask();this.mousedownXY = {x: e.clientX,y: e.clientY};this.moveFlag = false;}documentMouseup() {document.removeEventListener('mouseup', this.documentMouseupBind);this.mousedownXY = null;}// 异步处理重绘机制addAnimationFrame() {this.requestAnimationFrame = null;this.requestAnimationFrameDrawBind = this.requestAnimationFrameDraw.bind(this);}removeAnimationFrame() {this.stop();this.clearRequestAnimationFrame();}addTask(func = () => {}) {this.taskList.push(func);if (this.requestAnimationFrame === null) {this.addRequestAnimationFrame();this.do();}}do() {this.status = 'do';new Promise(res => {if (this.taskList[0]) {this.taskList[0]();this.taskList.shift();}this.hasTaskDone = true;res();}).then(() => {if (this.status === 'do' && this.taskList.length) {this.do();}});}stop() {this.status = 'stop';}requestAnimationFrameDraw() {this.stop();if (this.hasTaskDone && this.reDraw) {this.hasTaskDone = false;this.reDraw();}if (this.taskList.length) {this.addRequestAnimationFrame();this.do();} else {this.clearRequestAnimationFrame();}}addRequestAnimationFrame() {this.requestAnimationFrame = window.requestAnimationFrame(this.requestAnimationFrameDrawBind);}clearRequestAnimationFrame() {window.cancelAnimationFrame(this.requestAnimationFrame);this.requestAnimationFrame = null;}// 销毁destroy() {this.removeEvent();this.removeAnimationFrame();}
}export default Lane;

baseDI.js封装如下

const getType = val => {return Object.prototype.toString.call(val).replace(/\[object (\w+)\]/, '$1');
};// 旋转计算
const computePosition = (x, y, angle, centerX, centerY) => {// 圆心const a = centerX;const b = centerY;// 计算const c = Math.PI / 180 * angle;const rx = (x - a) * Math.cos(c) - (y - b) * Math.sin(c) + a;const ry = (y - b) * Math.cos(c) + (x - a) * Math.sin(c) + b;return { x: rx, y: ry };
};// 获取方向标识坐标
const getArrow = (key, w, h, w2, h2, topY, bottomY, leftX, rightX, cX, cY) => {let point = [];const wd = (w - w2) / 2; // 三角形边长与线宽的差值的一半const rotateDeg = 30;// 左转右转旋转角度const hv = h2 / Math.cos(Math.PI / 180 * rotateDeg); // 计算左转右转虚拟线长const topYv = topY - (hv - h2) / 2; // 虚拟起始高度switch (key) {case 1:// 调头point = [{ x: leftX - wd, y: bottomY - h },{ x: leftX + w2 / 2, y: bottomY },{ x: leftX + w2 + wd, y: bottomY - h }];break;case 2:// 左转point = [{ x: cX + w / 2, y: topYv + h },{ x: cX, y: topYv },{ x: cX - w / 2, y: topYv + h }];point.forEach(item => {const newXY = computePosition(item.x, item.y, -rotateDeg, cX, cY);item.x = newXY.x;item.y = newXY.y;});break;case 3:// 直行point = [{ x: cX + w / 2, y: topY + h },{ x: cX, y: topY },{ x: cX - w / 2, y: topY + h }];break;case 4:// 右转point = [{ x: cX + w / 2, y: topYv + h },{ x: cX, y: topYv },{ x: cX - w / 2, y: topYv + h }];point.forEach(item => {const newXY = computePosition(item.x, item.y, rotateDeg, cX, cY);item.x = newXY.x;item.y = newXY.y;});break;default:break;}return point;
};
const getDirectionIdentifyings = (key, w, h, centerXY, arrowWidth) => {// 标识边界const topY = centerXY.y - h / 2;const bottomY = centerXY.y + h / 2;let leftX = centerXY.x - w / 2 * 3;let rightX = centerXY.x + w / 2 * 3;// 直行线中心位置let cX = centerXY.x + w;const cY = centerXY.y;// 箭头宽高const arrowW = w * arrowWidth;const arrowH = arrowW * Math.sin(Math.PI / 3);// 线坐标const points = [];// 三角形坐标const arrowPoints = [];// 专用车道(没有方向标识的车道)let exclusive = false;switch (key) {case '0001':// 调头arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: topY + w / 2 },{ x: leftX + w / 2 * 5, y: topY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);break;case '0100':// 左转arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1000':// 直行leftX = centerXY.x - w / 2;rightX = centerXY.x + w / 2;cX = centerXY.x;arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);break;case '0010':// 右转cX = centerXY.x - w;arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '0101':// 左转调头arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1001':// 直行调头arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);break;case '0011':// 右转调头leftX = centerXY.x - w / 2 * 5;rightX = centerXY.x + w / 2 * 5;arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);cX = centerXY.x;arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1100':// 直行左转arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);break;case '0110':// 左转右转leftX = centerXY.x - w / 2 * 5;rightX = centerXY.x + w / 2 * 5;cX = centerXY.x;arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1010':// 直行右转cX = centerXY.x - w;arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1101':// 直行左转调头arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);break;case '1011':// 直行右转调头leftX = centerXY.x - w / 2 * 5;rightX = centerXY.x + w / 2 * 5;cX = centerXY.x;arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[2][0].x + arrowPoints[2][2].x) / 2, y: (arrowPoints[2][0].y + arrowPoints[2][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '0111':// 左转右转调头leftX = centerXY.x - w / 2 * 5;rightX = centerXY.x + w / 2 * 5;cX = centerXY.x;arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[2][0].x + arrowPoints[2][2].x) / 2, y: (arrowPoints[2][0].y + arrowPoints[2][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1110':// 直行左转右转leftX = centerXY.x - w / 2 * 5;rightX = centerXY.x + w / 2 * 5;cX = centerXY.x;arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[2][0].x + arrowPoints[2][2].x) / 2, y: (arrowPoints[2][0].y + arrowPoints[2][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;case '1111':// 直行左转右转调头leftX = centerXY.x - w / 2 * 5;rightX = centerXY.x + w / 2 * 5;cX = centerXY.x;arrowPoints.push(getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: leftX + w / 2, y: bottomY - arrowH },{ x: leftX + w / 2, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: cY + w / 2 },{ x: leftX + w / 2 * 5, y: bottomY },]);arrowPoints.push(getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);arrowPoints.push(getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: (arrowPoints[3][0].x + arrowPoints[3][2].x) / 2, y: (arrowPoints[3][0].y + arrowPoints[3][2].y) / 2 },{ x: cX, y: cY },{ x: cX, y: bottomY }]);break;default:// 专用车道,采用直行坐标exclusive = true;leftX = centerXY.x - w / 2;rightX = centerXY.x + w / 2;cX = centerXY.x;arrowPoints.push(getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY));points.push([{ x: cX, y: topY + arrowH },{ x: cX, y: bottomY }]);break;}return { arrowPoints, points, w, exclusive };
};
const getDirectionLabel = (key) => {let label = '';switch (key) {case '0001':// 调头label = '调头';break;case '0100':// 左转label = '左转';break;case '1000':// 直行label = '直行';break;case '0010':// 右转label = '右转';break;case '0101':// 左转调头label = '左转调头';break;case '1001':// 直行调头label = '直行调头';break;case '0011':// 右转调头label = '右转调头';break;case '1100':// 直行左转label = '直行左转';break;case '0110':// 左转右转label = '左转右转';break;case '1010':// 直行右转label = '直行右转';break;case '1101':// 直行左转调头label = '直行左转调头';break;case '1011':// 直行右转调头label = '直行右转调头';break;case '0111':// 左转右转调头label = '左转右转调头';break;case '1110':// 直行左转右转label = '直行左转右转';break;case '1111':// 直行左转右转调头label = '直行左转右转调头';break;default:// 专用车道,采用直行坐标label = '专用车道';break;}return label;
};// 获取人行标识坐标
const getWalkIdentifyings = (w, h, boundaryW, centerXY) => {// 标识边界const topY = centerXY.y - h / 2;const bottomY = centerXY.y + h / 2;const leftX = centerXY.x - w / 2;const rightX = centerXY.x + w / 2;const boundaryW2 = boundaryW / 2;const boundaryLine = [[{ x: leftX + boundaryW2, y: topY },{ x: leftX + boundaryW2, y: bottomY },],[{ x: rightX - boundaryW2, y: topY },{ x: rightX - boundaryW2, y: bottomY },],[{ x: leftX, y: centerXY.y },{ x: rightX, y: centerXY.y },]];const walkLine = [{ x: leftX, y: centerXY.y - h / 4 },{ x: rightX, y: centerXY.y - h / 4 },];return { boundaryLine, walkLine, w: boundaryW };
};// 转换margin/padding
const convertMorP = (val, dpr) => {let MorP = [];const type = getType(val);if (type === 'Number') {MorP = [val * dpr, val * dpr, val * dpr, val * dpr];} else if (type === 'Array') {switch (val.length) {case 1:MorP = [val[0] * dpr, val[0] * dpr, val[0] * dpr, val[0] * dpr];break;case 2:MorP = [val[0] * dpr, val[1] * dpr, val[0] * dpr, val[1] * dpr];break;case 3:MorP = [val[0] * dpr, val[1] * dpr, val[2] * dpr, val[1] * dpr];break;case 4:MorP = val;break;default:MorP = [0, 0, 0, 0];break;}} else {MorP = [0, 0, 0, 0];}return MorP;
};export { getDirectionIdentifyings, getDirectionLabel, computePosition, getWalkIdentifyings, convertMorP };

相关文章:

canvas绘制红绿灯路口(二)

系列文章 canvas绘制红绿灯路口&#xff08;一&#xff09; 无图不欢&#xff0c;先上图 优化项&#xff1a; 一&#xff1a;加入人行道红绿信号 二&#xff1a;加入专用车道标识&#xff08;无方向标识时采用专用车道标识&#xff09; 三&#xff1a;东南西北四项路口优化绘…...

Semantic Kernel 直接调用本地大模型与阿里云灵积 DashScope

本文主要介绍如何在无需网关&#xff0c;无需配置 HttpClient 的情况下&#xff0c;使用 Semantic Kernel 直接调用本地大模型与阿里云灵积 DashScope 等 OpenAI 接口兼容的大模型服务。 1. 背景 一直以来&#xff0c;我们都在探索如何更好地利用大型语言模型&#xff08;LLM&…...

【人工智能】深度解读 ChatGPT基本原理

ChatGPT是OpenAI开发的一种基于人工智能技术的自然语言处理工具&#xff0c;它代表了自然语言处理&#xff08;NLP&#xff09;技术的前沿进展。ChatGPT的基本原理建立在一系列先进技术和方法之上&#xff0c;主要包括GPT&#xff08;Generative Pre-trained Transformer&#…...

【教程】2024年如何快速提取爆款视频的视频文案?

关于如何提取爆款视频的视频文案&#xff0c;很朋友都不是很清楚&#xff0c;今天小编就带大家了解一下&#xff0c;希望这个知识点对大家有所帮助。 剪辑工作者有剪映、arctime、视频字幕等&#xff0c;但唯独编辑工作者或者编导没用直接提取视频文案的工具今天就说说可直接在…...

【MySQL连接器(Python)指南】02-MySQL连接器(Python)版本与实现

文章目录 前言MySQL连接器(Python)版本MySQL连接器(Python)实现总结前言 MySQL连接器(Python),用于让Python程序能够访问MySQL数据库。要想让Python应用程序正确高效地使用MySQL数据,就需要深入了解MySQL连接器的特性和使用方法。 MySQL连接器(Python)版本 下表总结了可用的…...

Vim入门教程

Vim是一个高度可配置的文本编辑器&#xff0c;用于创建和修改各种类型的文本文件。以下是一些基本的Vim使用示例&#xff0c;展示如何在Vim中进行编辑和操作。 1. 打开和保存文件 打开一个名为example.txt的文件&#xff1a; vim example.txt 打开多个文件&#xff0c;使用大…...

机器学习课程复习——隐马尔可夫

不考计算题 Q:概率图有几种结构? 条件独立性的公式? 顺序结构发散结构汇总结构Q:隐马尔可夫模型理解? 概念 集合:状态集合、观测集合 序列:状态序列、观测序列...

大数据-数据分析初步学习,待补充

参考视频&#xff1a;数据分析只需3小时从入门到进阶&#xff08;up亲身实践&#xff09;_哔哩哔哩_bilibili 数据指标&#xff1a; 对当前业务有参考价值的统计数据 分类&#xff1a;用户数据&#xff0c;业务数据&#xff0c;行为数据 用户数据 存量&#xff1a; DAU&#…...

微服务为什么使用RPC而不使用HTTP通信

微服务架构中使用RPC&#xff08;Remote Procedure Call&#xff09;而不是HTTP通信&#xff0c;主要是因为RPC在某些方面相比HTTP具有显著的优势。以下是一些关键原因&#xff1a; 性能&#xff1a; RPC通常比HTTP性能更高。RPC协议可以使用二进制序列化格式&#xff08;如gRP…...

怪物猎人物语什么时候上线?游戏售价多少?

怪物猎人物语是一款全新的RPG游戏&#xff0c;玩家在游戏中将化身为骑士&#xff0c;不断与怪物建立羁绊、不断成长&#xff0c;踏上前往外面世界的旅程&#xff0c;且最终目的地是以狩猎怪物为生的猎人世界。因为最近有不少玩家在关注这款游戏&#xff0c;所以下面就给大家分享…...

以创新思维点亮盲盒小程序:探索未来零售新趋势

随着科技的飞速发展和消费者需求的不断变化&#xff0c;零售行业正迎来一场前所未有的变革。在这个变革的浪潮中&#xff0c;盲盒小程序凭借其独特的魅力和巨大的潜力&#xff0c;成为未来零售新趋势的代表之一。本文将探讨如何以创新思维点亮盲盒小程序&#xff0c;探索未来零…...

DzzOffice集成功能最丰富的开源PHP+MySQL办公系统套件

DzzOffice是一套开源办公套件&#xff0c;旨在为企业和团队提供类似“Google企业应用套件”和“微软Office365”的协同办公平台。以下是对DzzOffice的详细介绍&#xff1a; 主要功能和应用&#xff1a; 网盘&#xff1a;支持企业、团队文件的集中管理&#xff0c;提供文件标签…...

关于生成式人工智能的发展

近年来&#xff0c;人工智能的发展引起了广泛关注&#xff0c;尤其是在深度学习领域&#xff0c;以深度神经网络为代表的人工智能技术已经取得了重大突破。然而&#xff0c;深度神经网络也有其局限性。深度学习技术在处理一些复杂问题时表现良好&#xff0c;但在解决更广泛的任…...

Python魔法方法__call__深入详解

目录 1、魔法方法__call__初探 🧙‍♂️ 1.1 什么是__call__? 1.2 基础用法演示 1.3 自定义行为与参数传递 2、实现轻量级装饰器模式 🎗️ 2.1 装饰器概念回顾 2.2 利用__call__构建装饰器 2.3 深入理解装饰器应用场景 3、类实例变身函数调用 🔮 3.1 类似函数的…...

PyQt5 生成py文件不能运行;pushButton点击事件;QTextEdit 获取输入框内容

目录 cant open file c.pyuic: c.pyuic $FileName$ -o $FileNameWithoutExtension$.p PyQt5 生成py文件不能运行 pushButton点击事件 QTextEdit 获取输入框内容 整体运行代码: Creating a Qt Widget Based Application | Qt Creator Manual cant open file c.pyuic: c.…...

HarmonyOS最佳实践文档总结汇总(面试题可能会问)

api12 上面来了最佳实现方案&#xff0c;未来面试题有的问了 编号分类内容子类链接 1性能体验设计体验设计概述 文档中心用户体验设计 文档中心流畅评测指标 文档中心交互流畅体验设计 文档中心视觉流畅体验设计 文档中心2性能优化开发高性能ArkUIUI组件性能优化文档中心合…...

leetcode 56合并区间

思路 合并就是首先应该按照left左边界排序&#xff0c;排完序以后&#xff0c;如果i的左边界小于等于i-1的右边界&#xff0c;说明有重合&#xff0c;此时这两个可以合并&#xff0c;右边界应该取最大值。 代码 排序 我是定义了一个类,存储左右边界&#xff0c;先将数组转化…...

企业微信内嵌H5项目接入聊天功能

产品需求是,在列表中把符合条件的列表接入聊天功能,以下是详细步骤: 1.引入企业微信 <script src"https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js"></script> 2.获取wx签名(必须要) /*** 获取wx签名**/ export function getWxJsApi(data) {r…...

微信小程序 this.setData高级用法(只更改单个数据)

合理使用 setData | 微信开放文档 1、页面 <view class"h-100px"></view> <view>最简单的数据&#xff1a;</view> <button bind:tap"handleAdd" data-type"1">点我加 1&#xff1a; {{text}}</button> &…...

使用npm发布自己的插件包

文章目录 1. 准备工作1.1 拥有一个npm账号1.2 准备你的插件代码1.3 编写package.json文件 2. 本地测试3. 发布到npm3.1 登录npm3.2 发布插件3.3 更新插件 4. 注意事项 在JavaScript和Node.js的生态系统中&#xff0c;npm&#xff08;Node Package Manager&#xff09;是一个非常…...

前端入门篇(五十二)练习6:transition过渡小动画

所以应该先找到第n个li&#xff0c;找到li再找img&#xff0c;li没有找错&#xff0c;底下又各自只有一个img&#xff0c;解决 ul li:nth-child(1) img { } 描述文字从下往上&#xff1a; 一开始描述也在框框下面&#xff0c;当hover时&#xff0c;translateY(0)&#xff0…...

scrapy模块的基础使用

scrapy模块是爬虫工作者最常用的一个模块之一&#xff0c;因它有许多好用的模板&#xff0c;和丰富的中间件&#xff0c;深受欢迎。 一&#xff0c;scrapy的安装 可以通过pypi的指引进行安装 在终端内输入以下代码&#xff1a; pip install scrapy 二&#xff0c;项目的建…...

如何在不降低网络安全防护的前提下,优化pcdn的流量清洗效率?

在不降低网络安全防护的前提下&#xff0c;优化PCDN的流量清洗效率是一个复杂但至关重要的任务。以下是一些建议&#xff0c;帮助您实现这一目标&#xff1a; 一&#xff0e;升级硬件与网络设备&#xff1a; 投资于高性能的硬件和网络设备&#xff0c;以确保流量清洗过程中的…...

linux发行版CentOS、Debian和Ubuntu的对比

一、CentOS、Debian和Ubuntu优缺点比较 CentOS、Debian和Ubuntu是目前国内云服务市场上最常见三个linux发行版本&#xff0c;在我们选购云服务时&#xff0c;要怎么选择&#xff1f;以下表格详细介绍了三者之间的优缺点和适用场景。 特性CentOSDebianUbuntu优点稳定性高&…...

WordPress如何删除内存中的缓存?

今天boke112百科将某篇文章修改分类和内容更新后&#xff0c;发现文章底部的相关文章显示的内容跟文章分类、标签毫无关系&#xff0c;还是显示原来的旧内容。后来查看YIA主题相关文章的代码&#xff0c;才发现相关文章的数据保存到内存中的&#xff0c;而且是永不过期&#xf…...

【XML模版文件参数初始化】

XML 模版文件&#xff0c;内部存在需要自定义的数据&#xff0c;使用 Python 进行初始化。 1、存在一个 XML 模版文件&#xff0c;定义如下 <!-- 文件名称 index.xml --> <root><HEAD><VER>1.0</VER><SRC>10000000000000</SRC><…...

Golang | Leetcode Golang题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; func getIntersectionNode(headA, headB *ListNode) *ListNode {if headA nil || headB nil {return nil}pa, pb : headA, headBfor pa ! pb {if pa nil {pa headB} else {pa pa.Next}if pb nil {pb headA} else {pb pb.Next}}retu…...

基于FOC控制器的BLDC无刷直流电机控制系统matlab编程与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于FOC控制器的BLDC无刷直流电机控制系统matlab编程与仿真&#xff0c;使用MATLAB编程实现&#xff0c;包括FOC控制器&#xff0c;clark&#xff0c;park等&#xff0c;不使用…...

ffmpeg转换视频格式

ffmpeg -i "录屏 2024-06-16 01-56-40.webm" -vf "scale1912:1070" -vcodec libx264 pit.mp4如果你觉得视频压缩速度太慢&#xff0c;可以尝试以下几种方法来加速视频处理&#xff1a; 1. 使用多线程 FFmpeg 支持多线程&#xff0c;可以利用多个 CPU 核心…...

设计程序,利用栈实现数值转换

二、利用栈实现数值转换&#xff0c;先定义一个栈的顺序存储结构&#xff0c;那么我们需要定义一个结构体&#xff0c;结构体里面有个int类型的数组&#xff0c;还有一个top用来存储栈顶元素的下标。栈是一种基本的数据结构&#xff0c;它遵循先进后出的原则。这意味着最后添加…...

网站建设意向书/电工培训机构

对比Linux开发&#xff0c;Android开发让我注意到了一些在Linux开发中认为理所当然的知识。这次遇到了一个链接库的问题。代码中加入了log打印&#xff0c;编译时出现__android_log_print找不到的问题&#xff0c;需要链接库&#xff0c;网上搜了搜需要链接libutils、libcutils…...

电商网站服务排名/西安百度公司开户

最近一直在看wse3.0&#xff0c;从一个例子中偶然的收获。虽然通过后台操作&#xff0c;从而减少用户交互时的“僵硬”体验一直是每个程序员的追求&#xff0c;在今天这样ajax的时代里面更加显的重要。一切为了用户&#xff0c;一切为了更丰富愉快的体验。本文并不是ajax相关的…...

java做网站的主要技术/百度广告收费

0x01 前言NumPy是一个功能强大的Python库&#xff0c;主要用于对多维数组执行计算。NumPy提供了大量的库函数和操作&#xff0c;可以帮助程序员轻松地进行数值计算。这类数值计算广泛用于以下任务&#xff1a;机器学习模型&#xff1a;在编写机器学习算法时&#xff0c;需要对矩…...

重庆网站建设的价格低/网络营销理论包括哪些

原博文出自于&#xff1a;  http://www.cnblogs.com/xdp-gacl/p/3641769.html        感谢&#xff01; 1、一个".java"源文件中是否可以包括多个类&#xff08;不是内部类&#xff09;&#xff1f;有什么限制&#xff1f; 可以有多个类&#xff0c;但只能…...

java做网站没有php好吗/优化关键词首页排行榜

正排索引&#xff08;正向索引&#xff09; 正排表是以文档的ID为关键字&#xff0c;表中记录文档中每个字的位置信息&#xff0c;查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。 正排表结构如图1所示&#xff0c;这种组织方法在建立索引的时候结构比较简…...

网站怎做百度代码统计/百度推广价格价目表

今天新增环境变量的时候不小心把冒号错打成了分号 export PATH/usr/local/php5/bin;$PATH; 导致PATH变量为/usr/local/php/bin 解决办法&#xff1a;[ubuntu系统] export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:$PATH…...