无图不欢,先上图
使用方法(以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 = 0
let nTurnLeft = 0
let nTurnRight = 0
let nTurnAround = 0
if (i === 0) {
nThrough = rand(0, 1)
nTurnLeft = 1
nTurnAround = rand(0, 1)
}
if (i === lanesLength - 1) {
nThrough = rand(0, 1)
nTurnRight = 1
nTurnAround = 0
}
if (i > 0 && i < lanesLength - 1) {
nThrough = 1
if (lanes[i - 1].nTurnLeft) {
nTurnLeft = rand(0, 1)
nTurnRight = 0
} else if (lanes[i - 1].nTurnRight) {
nTurnLeft = 0
nTurnRight = 1
} else {
nTurnLeft = 0
nTurnRight = 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 + 1
return 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 || 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 = 0.6 || opt.laneCenterProportion
// 车道样式
this.laneStyle = opt.laneStyle
this.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.2
HeightWidthProportion: 10, // 高宽比,建议大于5
maxWidth: 20 * this.dpr,
arrowWidth: 2, // 箭头/方向线的比例, 建议大于1小于2
background: '#ddd'
},
// 斑马线
zebraCrossing: {
widthProportion: 0.05, // 单个斑马线宽占车道比例,建议小于0.2
widthHeightProportion: 0.2, // 单个斑马线宽高比,建议小于0.5
color: '#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.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, this.laneStyle.zebraCrossing.width * 4]
// 红绿灯半径
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() {
let minW = this.w > this.h ? this.h : this.w
let sideLength = minW * this.laneCenterProportion / (Math.sqrt(2) + 1)
return sideLength
}
// 计算车道坐标
formatDataXY() {
let dataXY = []
// this.laneStyle
// 车道起始中心位置
let centerX = this.w / 2
let 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,
}
// 起始x
let startX = centerX - this.laneStyle.width * dataItem.lanes.length
// 起始y
let startY = centerY + this.laneStyle.zebraCrossing.height * 2
// 结束Y
let 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.width
lines.push({
x0: x,
y0: startY,
x1: x,
y1: endY,
style: { ...this.laneStyle.innerLeft }
})
}
// 左右车道分割线
let 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++) {
let x = startX + (innerLines + i + 2) * this.laneStyle.width
lines.push({
x0: x,
y0: startY,
x1: x,
y1: endY,
style: { ...this.laneStyle.innerRight }
})
}
// 车道右边线
let 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
// 方向标识
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 = trafficLights
dataXY.push(dataXYItem)
})
this.dataXYByRotate(dataXY)
this.dataXY = dataXY
}
// 获取方向标识坐标
getDirectionIdentifyings(key, w, h, centerXY) {
// 标识边界
let topY = centerXY.y - h / 2
let bottomY = centerXY.y + h / 2
let leftX = centerXY.x - w / 2 * 3
let rightX = centerXY.x + w / 2 * 3
// 直行线中心位置
let cX = centerXY.x + w
let cY = centerXY.y
// 箭头宽高
let arrowW = w * this.laneStyle.direction.arrowWidth
let 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 / 2
rightX = centerXY.x + w / 2
cX = centerXY.x
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 '0010':
// 右转
cX = centerXY.x - w
arrowPoints.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 * 5
rightX = centerXY.x + w / 2 * 5
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 },
])
cX = centerXY.x
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 '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 * 5
rightX = centerXY.x + w / 2 * 5
cX = centerXY.x
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(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(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 * 5
rightX = centerXY.x + w / 2 * 5
cX = centerXY.x
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 }
])
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 * 5
rightX = centerXY.x + w / 2 * 5
cX = centerXY.x
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(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(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 * 5
rightX = centerXY.x + w / 2 * 5
cX = centerXY.x
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 }
])
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.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 => {
let newXY = this.computePosition(item.x, item.y, rotateDeg, cX, cY)
item.x = newXY.x
item.y = newXY.y
})
break;
}
return point
}
// 计算旋转坐标
dataXYByRotate(dataXY) {
let centerX = this.w / 2
let centerY = this.h / 2
dataXY.forEach(dataXYItem => {
// 八边形,一个边占45度
let rotateReg = -180 + (dataXYItem.nApproachDirection - 1) * 45
dataXYItem.lines.forEach(line => {
let xy0 = this.computePosition(line.x0, line.y0, rotateReg, centerX, centerY)
line.x0 = xy0.x
line.y0 = xy0.y
let xy1 = this.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 => {
let { x, y } = this.computePosition(item.x, item.y, rotateReg, centerX, centerY)
item.x = x
item.y = y
})
})
directionIdentifying.arrowPoints.forEach(arrowPoint => {
arrowPoint.forEach(item => {
let { x, y } = this.computePosition(item.x, item.y, rotateReg, centerX, centerY)
item.x = x
item.y = y
})
})
})
dataXYItem.zebraCrossing.forEach(zebraCrossing => {
let { x, y } = this.computePosition(zebraCrossing.x, zebraCrossing.y, rotateReg, centerX, centerY)
zebraCrossing.x = x
zebraCrossing.y = y
})
dataXYItem.trafficLights.forEach(trafficLight => {
let { x, y } = this.computePosition(trafficLight.x, trafficLight.y, rotateReg, centerX, centerY)
trafficLight.x = x
trafficLight.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.length
if (i !== 0) {
// 衔接上一车道
let prevDataXYItem = this.dataXY[i - 1]
let data = {
prevNApproachDirection: prevDataXYItem.nApproachDirection,
nApproachDirection: dataXYItem.nApproachDirection,
type: 'connect'
}
let diffNApproachDirection = dataXYItem.nApproachDirection - prevDataXYItem.nApproachDirection
if(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 * diffNApproachDirection
let 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 = startAngle
data.OR.endAngle = startAngle - angle
data.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.length
let data = {
startNApproachDirection: startDataXYItem.nApproachDirection,
nApproachDirection: dataXYItem.nApproachDirection,
type: 'connect'
}
let diffNApproachDirection = startDataXYItem.nApproachDirection + 8 - dataXYItem.nApproachDirection
if(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 * diffNApproachDirection
let 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 + angle
data.OR.startAngle = startAngle
data.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 = 0
for (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) * 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;
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.0
let 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.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++) {
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.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, 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.w
this.ctx.strokeStyle = this.laneStyle.direction.background
pointItem.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.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, 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.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, 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.height
this.ctx.strokeStyle = this.laneStyle.zebraCrossing.color
this.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.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()
// 绘制方向标识
let testKeys = ['0001', '0100', '1000', '0010', '0101', '1001', '0011', '1100', '0110', '1010', '1101', '1011', '0111', '1110', '1111']
let width = this.w / testKeys.length
let w = width * this.laneStyle.direction.widthProportion
let h = w * this.laneStyle.direction.HeightWidthProportion
testKeys.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 = w
this.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