canvas绘制红绿灯路口

无图不欢,先上图
在这里插入图片描述
使用方法(以vue3为例)

<template><canvas class="lane" ref="laneCanvas"></canvas>
</template><script setup>
import { ref, onMounted } from 'vue'
import Lane from '@/utils/lane.js'let 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 randData = () => {let data = []let cdireCtions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']cdireCtions.forEach((item, index) => {if (rand(0, 1)) {let lanes = []let lanesLength = rand(1, 6)if (lanesLength === 1) {lanes = [{nThrough: 1,nTurnLeft: rand(0, 1),nTurnRight: rand(0, 1),nTurnAround: rand(0, 1),nChannelNumberPhase: rand(1, 3)}]} else if (lanesLength === 2) {lanes = [{nThrough: 1,nTurnLeft: rand(0, 1),nTurnRight: 0,nTurnAround: rand(0, 1),nChannelNumberPhase: rand(1, 3)}, {nThrough: 1,nTurnLeft: 0,nTurnRight: rand(0, 1),nTurnAround: 0,nChannelNumberPhase: rand(1, 3)}]} else {for (let i = 0; i < lanesLength; i++) {let nThrough = 0let nTurnLeft = 0let nTurnRight = 0let nTurnAround = 0if (i === 0) {nThrough = rand(0, 1)nTurnLeft = 1nTurnAround = rand(0, 1)}if (i === lanesLength - 1) {nThrough = rand(0, 1)nTurnRight = 1nTurnAround = 0}if (i > 0 && i < lanesLength - 1) {nThrough = 1if (lanes[i - 1].nTurnLeft) {nTurnLeft = rand(0, 1)nTurnRight = 0} else if (lanes[i - 1].nTurnRight) {nTurnLeft = 0nTurnRight = 1} else {nTurnLeft = 0nTurnRight = rand(0, 1)}nTurnAround = 0}lanes.push({nThrough,nTurnLeft,nTurnRight,nTurnAround,nChannelNumberPhase: rand(1, 3)})}}data.push({nApproachDirection: index + 1,cdireCtion: cdireCtions[index],lanes})}})if (data.length < 2) {data = randData()}return data
}
const dataTest = [{nApproachDirection: 1,cdireCtion: '北',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 3}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 2}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 2,cdireCtion: '东北',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 3,cdireCtion: '东',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 4,cdireCtion: '东南',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 5,cdireCtion: '南',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 6,cdireCtion: '西南',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 7,cdireCtion: '西',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 8,cdireCtion: '西北',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}]
const dataTest2 = [{nApproachDirection: 1,cdireCtion: '东',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}, {nApproachDirection: 2,cdireCtion: '南',lanes: [{nThrough: 0,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 1,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 1,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 1,nTurnLeft: 0,nTurnRight: 0,nTurnAround: 0,nChannelNumberPhase: 1}, {nThrough: 0,nTurnLeft: 0,nTurnRight: 1,nTurnAround: 0,nChannelNumberPhase: 1}]
}]
const rand = (n, m) => {var c = m - n + 1return Math.floor(Math.random() * c + n)
}
onMounted(() => {let data = randData()console.log(data)let laneC = new Lane({canvas: laneCanvas.value,data: [...data]})setInterval(() => {data.forEach(dataItem => {dataItem.lanes.forEach(lane => {lane.nChannelNumberPhase = rand(1, 3)})})laneC.setData(data)}, 5000)
})</script><style scoped lang="scss">
.lane {width: 100%;height: 100%;background-color: #325e76;
}
</style>

lane.js源码

class Lane {constructor(opt) {this.dpr = window.devicePixelRatio || 1this.canvas = opt.canvasthis.w = nullthis.h = nullthis.ctx = nullthis.data = opt.data// 车道范围坐标this.region = []// 车道线坐标this.dataXY = []// 路中心空白区域占canvas宽高最小值的比,用来计算车道宽度。占比越大,中心空白区域越大,车道越宽,线路越短。取值范围0-1,不允许取0,1this.laneCenterProportion = 0.6 || opt.laneCenterProportion// 车道样式this.laneStyle = opt.laneStylethis.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)) {this.w = this.canvas.width = Math.floor(this.canvas.offsetWidth * this.dpr)this.h = this.canvas.height = Math.floor(this.canvas.offsetHeight * this.dpr)}this.ctx = this.canvas.getContext('2d')this.getLaneStyle()this.formatDataXY()this.getRegion()this.draw()}// 获取车道样式getLaneStyle() {let 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: ['#FF0033', '#33CC00', '#FFFF33'],}}if (this.laneStyle) {this.laneStyle = Object.assign(laneStyle, this.laneStyle)} else {this.laneStyle = laneStyle}let laneMaxNum = this.getLaneMaxNum()let sideLength = this.getSideLength()// 车道宽度 / 2 表示双向this.laneStyle.width = sideLength / 2 / laneMaxNum// 方向表示线宽高this.laneStyle.direction.width = this.laneStyle.width * this.laneStyle.direction.widthProportionif (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.widthProportionthis.laneStyle.zebraCrossing.height = this.laneStyle.zebraCrossing.width / this.laneStyle.zebraCrossing.widthHeightProportionthis.laneStyle.zebraCrossing.type = [this.laneStyle.zebraCrossing.width, this.laneStyle.zebraCrossing.width * 4]// 红绿灯半径this.laneStyle.trafficLight.r = this.laneStyle.width * this.laneStyle.trafficLight.rProportion}// 获取最大车道数getLaneMaxNum() {let laneMaxNum = 0this.data.forEach(item => {if (item.lanes.length > laneMaxNum) {laneMaxNum = item.lanes.length}})if(laneMaxNum === 1){laneMaxNum = 2 }return laneMaxNum}// 获取中心路口八边形边长getSideLength() {let minW = this.w > this.h ? this.h : this.wlet sideLength = minW * this.laneCenterProportion / (Math.sqrt(2) + 1)return sideLength}// 计算车道坐标formatDataXY() {let dataXY = []// this.laneStyle// 车道起始中心位置let centerX = this.w / 2let centerY = this.h - this.h * (1 - this.laneCenterProportion) / 2// 车道长度let laneLength = Math.sqrt(Math.pow(this.w, 2) * Math.pow(this.h, 2))this.data.forEach(dataItem => {let dataXYItem = {nApproachDirection: dataItem.nApproachDirection,}// 起始xlet startX = centerX - this.laneStyle.width * dataItem.lanes.length// 起始ylet startY = centerY + this.laneStyle.zebraCrossing.height * 2// 结束Ylet endY = startY + laneLength// 线let lines = []// 单向车道分割线数量let 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++) {let x = startX + (i + 1) * this.laneStyle.widthlines.push({x0: x,y0: startY,x1: x,y1: endY,style: { ...this.laneStyle.innerLeft }})}// 左右车道分割线let dividerX = startX + (innerLines + 1) * this.laneStyle.widthlines.push({x0: dividerX,y0: startY,x1: dividerX,y1: endY,style: { ...this.laneStyle.innerDivider }})// 车道右侧分割线for (let i = 0; i < innerLines; i++) {let x = startX + (innerLines + i + 2) * this.laneStyle.widthlines.push({x0: x,y0: startY,x1: x,y1: endY,style: { ...this.laneStyle.innerRight }})}// 车道右边线let outerRightx = startX + (innerLines + 1) * 2 * this.laneStyle.widthlines.push({x0: outerRightx,y0: startY - this.laneStyle.zebraCrossing.height * 2,x1: outerRightx,y1: endY,type: 'outer'})dataXYItem.lines = lines// 方向标识let directionIdentifyings = []for (let i = 0; i < dataItem.lanes.length; i++) {let laneItem = dataItem.lanes[i]let key = [laneItem.nThrough, laneItem.nTurnLeft, laneItem.nTurnRight, laneItem.nTurnAround].join('')let line = lines[innerLines + i + 1]directionIdentifyings.push(this.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}))}dataXYItem.directionIdentifyings = directionIdentifyings// 斑马线dataXYItem.zebraCrossing = [{x: lines[0].x0,y: startY - this.laneStyle.zebraCrossing.height}, {x: lines[lines.length - 1].x0,y: startY - this.laneStyle.zebraCrossing.height}]// 红绿灯let trafficLights = []for (let i = 0; i < dataItem.lanes.length; i++) {let laneItem = dataItem.lanes[i]let 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.nChannelNumberPhase - 1]})}dataXYItem.trafficLights = trafficLightsdataXY.push(dataXYItem)})this.dataXYByRotate(dataXY)this.dataXY = dataXY}// 获取方向标识坐标getDirectionIdentifyings(key, w, h, centerXY) {// 标识边界let topY = centerXY.y - h / 2let bottomY = centerXY.y + h / 2let leftX = centerXY.x - w / 2 * 3let rightX = centerXY.x + w / 2 * 3// 直行线中心位置let cX = centerXY.x + wlet cY = centerXY.y// 箭头宽高let arrowW = w * this.laneStyle.direction.arrowWidthlet arrowH = arrowW * Math.sin(Math.PI / 3)// 线坐标let points = []// 三角形坐标let arrowPoints = []switch (key) {case '0001':// 调头arrowPoints.push(this.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(this.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 / 2rightX = centerXY.x + w / 2cX = centerXY.xarrowPoints.push(this.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 - warrowPoints.push(this.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(this.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(this.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(this.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(this.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 * 5rightX = centerXY.x + w / 2 * 5arrowPoints.push(this.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.xarrowPoints.push(this.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(this.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(this.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 * 5rightX = centerXY.x + w / 2 * 5cX = centerXY.xarrowPoints.push(this.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(this.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 - warrowPoints.push(this.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(this.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(this.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(this.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(this.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 * 5rightX = centerXY.x + w / 2 * 5cX = centerXY.xarrowPoints.push(this.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(this.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(this.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 * 5rightX = centerXY.x + w / 2 * 5cX = centerXY.xarrowPoints.push(this.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(this.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(this.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 * 5rightX = centerXY.x + w / 2 * 5cX = centerXY.xarrowPoints.push(this.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(this.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(this.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 * 5rightX = centerXY.x + w / 2 * 5cX = centerXY.xarrowPoints.push(this.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(this.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(this.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(this.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;}return { arrowPoints, points }}getArrow(key, w, h, w2, h2, topY, bottomY, leftX, rightX, cX, cY) {let point = []let wd = (w - w2) / 2 // 三角形边长与线宽的差值的一半let rotateDeg = 30// 左转右转旋转角度let hv = h2 / Math.cos(Math.PI / 180 * rotateDeg) // 计算左转右转虚拟线长let 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 => {let newXY = this.computePosition(item.x, item.y, -rotateDeg, cX, cY)item.x = newXY.xitem.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 => {let newXY = this.computePosition(item.x, item.y, rotateDeg, cX, cY)item.x = newXY.xitem.y = newXY.y})break;}return point}// 计算旋转坐标dataXYByRotate(dataXY) {let centerX = this.w / 2let centerY = this.h / 2dataXY.forEach(dataXYItem => {// 八边形,一个边占45度let rotateReg = -180 + (dataXYItem.nApproachDirection - 1) * 45dataXYItem.lines.forEach(line => {let xy0 = this.computePosition(line.x0, line.y0, rotateReg, centerX, centerY)line.x0 = xy0.xline.y0 = xy0.ylet xy1 = this.computePosition(line.x1, line.y1, rotateReg, centerX, centerY)line.x1 = xy1.xline.y1 = xy1.y})dataXYItem.directionIdentifyings.forEach(directionIdentifying => {directionIdentifying.points.forEach(point => {point.forEach(item => {let { x, y } = this.computePosition(item.x, item.y, rotateReg, centerX, centerY)item.x = xitem.y = y})})directionIdentifying.arrowPoints.forEach(arrowPoint => {arrowPoint.forEach(item => {let { x, y } = this.computePosition(item.x, item.y, rotateReg, centerX, centerY)item.x = xitem.y = y})})})dataXYItem.zebraCrossing.forEach(zebraCrossing => {let { x, y } = this.computePosition(zebraCrossing.x, zebraCrossing.y, rotateReg, centerX, centerY)zebraCrossing.x = xzebraCrossing.y = y})dataXYItem.trafficLights.forEach(trafficLight => {let { x, y } = this.computePosition(trafficLight.x, trafficLight.y, rotateReg, centerX, centerY)trafficLight.x = xtrafficLight.y = y})})}// 旋转计算computePosition(x, y, angle, centerX, centerY) {// 圆心let a = centerX;let b = centerY;// 计算let c = Math.PI / 180 * angle;let rx = (x - a) * Math.cos(c) - (y - b) * Math.sin(c) + a;let ry = (y - b) * Math.cos(c) + (x - a) * Math.sin(c) + b;return { x: rx, y: ry };}// 获取车道范围getRegion() {let region = []for (let i = 0; i < this.dataXY.length; i++) {let dataXYItem = this.dataXY[i]let linesLength = dataXYItem.lines.lengthif (i !== 0) {// 衔接上一车道let prevDataXYItem = this.dataXY[i - 1]let data = {prevNApproachDirection: prevDataXYItem.nApproachDirection,nApproachDirection: dataXYItem.nApproachDirection,type: 'connect'}let diffNApproachDirection = dataXYItem.nApproachDirection - prevDataXYItem.nApproachDirectionif(diffNApproachDirection > 4){diffNApproachDirection = (prevDataXYItem.nApproachDirection + 8) - dataXYItem.nApproachDirection}if (diffNApproachDirection === 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') {let angle = 45 * diffNApproachDirectionlet startAngle = 45 * (prevDataXYItem.nApproachDirection - 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 = startAngledata.OR.endAngle = startAngle - angledata.OR.anticlockwise = true} else {// 曲线let 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],])let laneXY1 = [(prevDataXYItem.lines[0].x0 + dataXYItem.lines[linesLength - 1].x0) / 2, (prevDataXYItem.lines[0].y0 + dataXYItem.lines[linesLength - 1].y0) / 2]let originPoints = [{ x: prevDataXYItem.lines[0].x0, y: prevDataXYItem.lines[0].y0 },{ x: laneXY1[0] - (laneXY1[0] - laneXY0[0]) * diffNApproachDirection / 4, y: laneXY1[1] - (laneXY1[1] - laneXY0[1]) * diffNApproachDirection / 4 },{ x: dataXYItem.lines[linesLength - 1].x0, y: dataXYItem.lines[linesLength - 1].y0 },]if (this.laneStyle.region.CurveType === 'normal') {let point = this.getCurveVertex(originPoints)data.point = point} else {data.point = originPoints}}region.push(data)}}// 车道范围region.push({nApproachDirection: dataXYItem.nApproachDirection,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) {// 衔接起始车道let startDataXYItem = this.dataXY[0]let startLinesLength = startDataXYItem.lines.lengthlet data = {startNApproachDirection: startDataXYItem.nApproachDirection,nApproachDirection: dataXYItem.nApproachDirection,type: 'connect'}let diffNApproachDirection = startDataXYItem.nApproachDirection + 8 - dataXYItem.nApproachDirectionif(diffNApproachDirection > 4){diffNApproachDirection = dataXYItem.nApproachDirection - startDataXYItem.nApproachDirection}if (diffNApproachDirection === 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') {let angle = 45 * diffNApproachDirectionlet startAngle = 45 * (dataXYItem.nApproachDirection - 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 + angledata.OR.startAngle = startAngledata.OR.anticlockwise = false} else {// 曲线let 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],])let laneXY1 = [(startDataXYItem.lines[startLinesLength - 1].x0 + dataXYItem.lines[0].x0) / 2, (startDataXYItem.lines[startLinesLength - 1].y0 + dataXYItem.lines[0].y0) / 2]let originPoints = [{ x: dataXYItem.lines[0].x0, y: dataXYItem.lines[0].y0 },{ x: laneXY1[0] - (laneXY1[0] - laneXY0[0]) * diffNApproachDirection / 4, y: laneXY1[1] - (laneXY1[1] - laneXY0[1]) * diffNApproachDirection / 4 },{ x: startDataXYItem.lines[startLinesLength - 1].x0, y: startDataXYItem.lines[startLinesLength - 1].y0 },]if (this.laneStyle.region.CurveType === 'normal') {let 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 = 0for (let i = 0; i < vertex.length - 1; i++) {length += Math.sqrt(Math.pow(vertex[i].x - vertex[i + 1].x, 2) + Math.pow(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) * pointsPowfor (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) % pointsPowconst p = ((pointsOrigin.length - 1) * i) / divisionsconst intPoint = Math.floor(p)const weight = p - intPointconst 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.5const v1 = (p3 - p1) * 0.5const t2 = t * tconst t3 = t * t2return (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;let dDistance = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));let 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.0let mid_y = 0.0;let a = 1.0;let b = 1.0;let c = 1.0;let k = (y2 - y1) / (x2 - x1);let cx1, cy1, cx2, cy2;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(Math.pow(cx - x1, 2) + Math.pow(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) => {trafficLight.color = this.laneStyle.trafficLight.colors[data[dataXYIndex].lanes[trafficLightIndex].nChannelNumberPhase - 1]})})this.ctx.clearRect(0,0,this.w,this.h)this.draw()}// 绘制draw() {this.drawRegion()this.drawLines()this.drawDirectionIdentifyings()this.drawZebraCrossing()this.drawTrafficLight()// this.drawHelper()}drawRegion() {this.ctx.save()// 缩放// 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.beginPath()this.ctx.fillStyle = this.laneStyle.region.backgroundthis.ctx.lineWidth = this.laneStyle.region.widththis.ctx.strokeStyle = this.laneStyle.region.colorthis.ctx.lineJoin = 'round';for (let i = 0; i < this.region.length; i++) {let 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, dataXYIndex) => {dataXYItem.lines.forEach(lineItem => {if (lineItem.type !== 'outer') {this.ctx.save()// 缩放// 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.beginPath()this.ctx.lineWidth = lineItem.style.widththis.ctx.strokeStyle = lineItem.style.colorif (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, dataXYIndex) => {dataXYItem.directionIdentifyings.forEach(directionIdentifying => {this.ctx.save()// 缩放// 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)directionIdentifying.points.forEach(pointItem => {this.ctx.beginPath()this.ctx.lineWidth = directionIdentifying.wthis.ctx.strokeStyle = this.laneStyle.direction.backgroundpointItem.forEach(item => {this.ctx.lineTo(item.x, item.y)})this.ctx.stroke()this.ctx.closePath()})directionIdentifying.arrowPoints.forEach(arrowPoint => {this.ctx.beginPath()this.ctx.fillStyle = this.laneStyle.direction.backgroundarrowPoint.forEach(item => {this.ctx.lineTo(item.x, item.y)})this.ctx.fill()this.ctx.closePath()})this.ctx.restore()})})}drawTrafficLight() {this.dataXY.forEach((dataXYItem, dataXYIndex) => {dataXYItem.trafficLights.forEach(trafficLight => {this.ctx.save()// 缩放// 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.beginPath()this.ctx.fillStyle = trafficLight.colorthis.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, dataXYIndex) => {this.ctx.save()// 缩放// 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.beginPath()this.ctx.lineWidth = this.laneStyle.zebraCrossing.heightthis.ctx.strokeStyle = this.laneStyle.zebraCrossing.colorthis.ctx.setLineDash(this.laneStyle.zebraCrossing.type);dataXYItem.zebraCrossing.forEach(zebraCrossing => {this.ctx.lineTo(zebraCrossing.x, zebraCrossing.y)})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++) {let regionItem = this.region[i]this.ctx.fillText(regionItem.nApproachDirection, regionItem.x0, regionItem.y0)}this.ctx.closePath()// 绘制坐标线this.ctx.save()this.ctx.lineWidth = 2 * this.dprthis.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()// 绘制方向标识let testKeys = ['0001', '0100', '1000', '0010', '0101', '1001', '0011', '1100', '0110', '1010', '1101', '1011', '0111', '1110', '1111']let width = this.w / testKeys.lengthlet w = width * this.laneStyle.direction.widthProportionlet h = w * this.laneStyle.direction.HeightWidthProportiontestKeys.forEach((key, index) => {let { arrowPoints, points } = this.getDirectionIdentifyings(key, w, h, { x: width * index + width / 2, y: this.h / 2 })this.ctx.save()// 缩放// 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)points.forEach(pointItem => {this.ctx.beginPath()this.ctx.lineWidth = wthis.ctx.strokeStyle = '#fff'pointItem.forEach(item => {this.ctx.lineTo(item.x, item.y)})this.ctx.stroke()this.ctx.closePath()})arrowPoints.forEach(arrowPoint => {this.ctx.beginPath()this.ctx.fillStyle = '#fff'arrowPoint.forEach(item => {this.ctx.lineTo(item.x, item.y)})this.ctx.fill()this.ctx.closePath()})this.ctx.restore()})}
}export default Lane

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

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

相关文章

C实现数组奇数在前偶数在后排序

一、运行结果&#xff1b; 二、源码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现调整函数move_odd_even函数&#xff1b; void move_odd_even(int arr[], int sz) {//初始化变量值&#xff1b;int left 0;int right sz - 1;//循环判断和…

CSP CCF 201312-2 ISBN号码 C++满分题解

解题思路&#xff1a; 1.用一个int数组单独存储每个数字&#xff1b;用char数组存储原始标识符串&#xff0c;方便输出 2.计算后得到标识码&#xff0c;判断是否正确 #include<iostream> using namespace std;int main() {int num[15]; //存储每个位置的数char arr[15…

【高性能篇】QPS概念、RT概念

什么是QPS&#xff0c;什么是RT&#xff1f; ✔️典型解析✔️扩展知识仓✔️RT ✔️QPS✔️ QPS和TPS✔️并发用户数✔️最佳线程数 ✔️典型解析 QPS&#xff0c;指的是系统每秒能处理的请求数(Query Per Second)&#xff0c;在Web应用中我们更关注的是Web应用每秒能处理的re…

Radar System Pro - Plug Play Solution

Radar System Pro是一款功能多样且可定制的资源,旨在通过功能齐全且易于使用的雷达系统增强您的Unity项目。无论您是在开发第一人称射击游戏、策略游戏还是太空探索模拟器,我们的雷达系统都将为您提供所需的工具,以创建引人入胜且身临其境的体验。 雷达系统是一个模块化资产…

2023年华为OD机试(python)B卷-符合要求的结对方式

一、题目 题目描述&#xff1a; 用一个数组A代表程序员的工作能力&#xff0c;公司想通过结对编程的方式提高员工的能力&#xff0c;假设结对后的能力为两个员工的能力之和&#xff0c;求一共有多少种结对方式使结对后能力为N。 二、输入输出 输入描述: 5 1 2 2 2 3 4 第一行为…

Unity 贝塞尔曲线工具获取运动轨迹

Unity 贝塞尔曲线工具获取运动轨迹 一、介绍贝塞尔曲线二、Unity中贝塞尔曲线工具介绍1.创建一个空物体挂在上BezierSpline.cs脚本组件2.由上图可知刚创建出来的有两个点和两个手柄组成3.我们可修改其坐标看下效果4.这样我们就可以获得这两个点之间的指定数量的点来作为某个物体…

openGauss学习笔记-177 openGauss 数据库运维-逻辑复制-逻辑解码-逻辑解码概述

文章目录 openGauss学习笔记-177 openGauss 数据库运维-逻辑复制-逻辑解码-逻辑解码概述177.1 功能描述177.2 注意事项177.3 性能 openGauss学习笔记-177 openGauss 数据库运维-逻辑复制-逻辑解码-逻辑解码概述 177.1 功能描述 openGauss对数据复制能力的支持情况为&#xff…

资助26项!基金委公布一批原创探索项目资助结果!

根据《国家自然科学基金原创探索计划项目实施方案&#xff08;试行&#xff09;》要求&#xff0c;现将2023年度数理科学部资助的专项项目&#xff08;指南引导类原创探索计划项目&#xff09;相关信息予以公示&#xff1a; 国家自然科学基金委员会 数理科学部 2023年12月26日…

Linux 内核学习笔记: hlist 的理解

前言 最近阅读 Linux 内核时&#xff0c;遇到了 hlist&#xff0c;这个 hlist 用起来像是普通的链表&#xff0c;但是为何使用 hlist&#xff0c;hlist 是怎么工作的&#xff1f; 相关代码 hlist_add_head(&clk->clks_node, &core->clks); /*** clk_core_link_…

vue3项目使用pako库解压后端返回zip数据

文章目录 前言一、pako 介绍一些特点和功能&#xff1a;简单示例 二、vue3 实战示例1.安装后引入库安装:引用用自定义hooks 抽取共用逻辑部署小插曲 前言 外部接口返回一个图片数据是经过zip压缩的&#xff0c;前端需要把这个数据处理成可以显示的图片。大概思路&#xff1a;z…

68内网安全-域横向PTHPTKPTT哈希票据传递

今天讲PTH&PTK&PTT&#xff0c; PTH(pass the hash) #利用 lm 或 ntlm 的值进行的渗透测试 PTT(pass the ticket) #利用的票据凭证 TGT 进行的渗透测试 用的Kerberos 协议 PTK(pass the key) #利用的 ekeys aes256 进行的渗透测试 lm加密算法是2003以前的老版&…

vitis HLS中实现canny算法的IP核

一、前言 canny边缘检测主要用于提取图像的边缘&#xff0c;是最常用且有效的边缘检测算法。在AMD赛灵思提供的库函数中&#xff0c;使用xf::cv::Canny和xf::cv::EdgeTracing两个函数实现canny边缘提取。本文举例说明如何在vitis HLS 2023.1中实现canny算法。 二、xf::cv::Cann…

JUC常用并发工具类

JUC常用并发工具类 1、什么是JUC? JUC 就是 java.util.concurrent 包&#xff0c;这个包俗称 JUC&#xff0c;里面都是解决并发问题的一些东西&#xff0c;该包的位置位于 java 下 面的 rt.jar 包下面。 2、4大常用并发工具类 2.1 CountDownLatch CountDownLatch&#x…

基于Java车间工时管理系统(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

《新传奇》期刊投稿论文发表

《新传奇》杂志是经国家新闻出版总署批准、面向国内外公开发行的综合性社科期刊&#xff0c;由湖北省文联主管&#xff0c;湖北今古传奇传媒集团有限公司主办&#xff0c;湖北优秀期刊。本刊旨在坚守初心、引领创新&#xff0c;展示高水平研究成果&#xff0c;支持优秀学术人才…

如何使用 NFTScan NFT API 在 Gnosis 网络上开发 Web3 应用

Gnosis Chain 是一个兼容 EVM 的区块链&#xff0c;专注于快速且低成本的交易功能&#xff0c;采用独特的双通证模型&#xff1b;xDai 是一种用于交易、支付和手续费的稳定币&#xff0c;权益证明&#xff08;PoS&#xff09;保护将由 GNO 通过共识层 Gnosis Beacon Chain 提供…

【JVM】虚拟机的组成+字节码文件组成+类的生命周期

什么是JVM&#xff1f; JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 JVM的功能 1.解释和运行&#xff1a;对字节码文件中的指令实时的解释成机器码让计算机执行。 2.内存管理&#xff1a;自动为对象、方法等分配内存&#xff0c;自动…

【C++干货铺】STL中set和map的介绍和使用

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 序列式容器 关联式容器 键值对 树形结构的关联式容器 set set的介绍 set的使用 set的模板参数列表 set的构造 ​编辑 set的容量 set的删除和查找 mult…

web等保评测需要实机查看的操作系统、服务器、数据库和应用部分

“等保测评”全称是信息安全等级保护测评。是经公安部认证的具有资质的测评机构&#xff0c;依据国家信息安全等级保护规范规定&#xff0c;受有关单位委托&#xff0c;按照有关管理规范和技术标准&#xff0c;对信息系统安全等级保护状况进行检测评估的活动。 本文陆续将遇到的…