系列文章
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
};
// 异步任务list
this.taskList = [];
this.hasTaskDone = false;
this.status = 'do'; // do or stop
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)) {
// eslint-disable-next-line
this.w = this.canvas.width = Math.floor(this.canvas.offsetWidth * this.dpr);
// eslint-disable-next-line
this.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.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: ['#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,
};
// 起始x
const startX = centerX - this.laneStyle.width * dataItem.lanes.length;
// 起始y
const startY = centerY + this.laneStyle.zebraCrossing.height * 2;
// 结束Y
const 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; let
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((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 };