canvas绘制红绿灯路口(二)

2024-06-21 09:20
文章标签 canvas 红绿灯 绘制 路口

本文主要是介绍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绘制红绿灯路口(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080825

相关文章

以canvas方式绘制粒子背景效果,感觉还可以

这个是看到项目中别人写好的,感觉这种写法效果还可以,就存留记录下 就是这种的背景效果。如果想改背景颜色可以通过canvas.js文件中的fillStyle值改。 附上demo下载地址。 https://download.csdn.net/download/u012138137/11249872

XMG 绘制形状

1. 除非是绘制曲线直接使用原生的。如果绘制形状直接使用UIBezerPath  2. 命名原则,类方法以类名开头 UIBezierPath bezierPathWithRect 3.圆角半径 画圆的大小 以每个顶点为圆心。给定的半径为半径画一个1/4圆。把周边的给切掉 4.只有封闭的形状调用这个方法才有用 [path fill] 5. stroke 描边一下

CesiumJS【Basic】- #008 通过canvas绘制billboard

文章目录 通过canvas绘制billboard1 目标2 实现 通过canvas绘制billboard 1 目标 通过canvas绘制billboard 2 实现 /** @Author: alan.lau* @Date: 2024-06-16 11:15:48* @LastEditTime: 2024-06-16 11:43:02* @LastEditors: al

Android自定义系列——4.Canvas操作

1.画布操作 为什么要有画布操作? 画布操作可以帮助我们用更加容易理解的方式制作图形。 ⑴位移(translate) translate是坐标系的移动,可以为图形绘制选择一个合适的坐标系。 请注意,位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动,如下: // 省略了创建画笔的代码// 在坐标原点绘制一个黑色圆形mPaint.setColor(Color.BLACK);ca

使用AGG里面的clip_box函数裁剪画布, 绘制裁剪后的图形

// 矩形裁剪图片, 透明void agg_testImageClipbox_rgba32(unsigned char* buffer, unsigned int width, unsigned int height){// ========= 创建渲染缓冲区 =========agg::rendering_buffer rbuf;// BMP是上下倒置的,为了和GDI习惯相同,最后一个参数是

Canvas绘制图片和区域

如何使用Canvas在图片上绘制区域? 一. 首先,我们需要初始化三个canvas画布(初始化Canvas)   initCanvas() {// 初始化canvas画布let canvasWrap = document.getElementsByClassName("canvas-wrap");this.wrapWidth = canvasWrap[0].clientWidth;thi

「JCVI教程」如何绘制CNS级别的共线性图(中)

在「JCVI教程」编码序列或蛋白序列运行共线性分析流程(上)还是有一个尴尬的事情,就是只用到两个物种,不能展示出JCVI画图的方便之处,因此这里参考https://github.com/tanghaibao/jcvi/wiki/MCscan-(Python-version)的分析,只不过画图部分拓展下思路。 首先要运行如下代码获取目的数据 python -m jcvi.apps.fetch p

「JCVI教程」如何绘制CNS级别的共线性图(上)

本教程借鉴https://github.com/tanghaibao/jcvi/wiki/MCscan-(Python-version). 我们先从http://plants.ensembl.org/index.html选择两个物种做分析, 这里选择的就是前两个物种,也就是拟南芥和水稻(得亏没有小麦和玉米) 选择物种 我们下载它的GFF文件,cdna序列和蛋白序列 #A

「JCVI教程」如何基于物种的CDS的blast结果绘制点图(dotplot)

这是唐海宝老师GitHub上的JCVI工具的非官方说明书。 该工具集的功能非常多,但是教程资料目前看起来并不多,因此为了能让更多人用上那么好用的工具,我就一边探索,一边写教程 这一篇文章教大家如何利用JCVI里面的工具绘制点图,展现两个物种之间的共线性关系。 在分析之前,你需要从PhytozomeV11 下载A.thaliana和Alyrata的CDS序列,保证文件夹里有如下内容 Al

OpenGL-ES 学习(6)---- 立方体绘制

目录 立方体绘制基本原理立方体的顶点坐标和绘制顺序立方体颜色和着色器实现效果和参考代码 立方体绘制基本原理 一个立方体是由8个顶点组成,共6个面,所以绘制立方体本质上就是绘制这6个面共12个三角形 顶点的坐标体系如下图所示,三维坐标的中心原点位于立方体的中心,但是要特别注意的是,前后方向表示的是Z轴,上下方向表示的是Y轴 立方体的顶点坐标和绘制顺序 立方体坐标定义