canvas绘制红绿灯路口(二)

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

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

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

使用方法(以vue3为例)

<template>
    <canvas class="lane" ref="laneCanvas" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import Lane from 'services/roadCanvas/lane';

const laneCanvas = ref(null);
/**
 * 车道方向,进口方向
 * 1 - 北,2 - 东北,3 - 东,4 - 东南,
 * 5 - 南,6 - 西南,7 - 西,8 - 西北
 *
 * 直行放行 nThrough 0不放行 1放行
 * 左转放行 nTurnLeft 0不放行 1放行
 * 右转放行 nTurnRight 0不放行 1放行
 * 调头 nTurnAround 0不放行 1放行
 *
 * 通道相位 nChannelNumberPhase 1-红灯 2绿灯 3黄灯
 */

const data = [
    {
        'approachDirection': 1,
        'cdireCtion': '北',
        'lanes': [
            {
                'laneNo': '1',
                'through': 0,
                'turnLeft': 1,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '左转',
                'channelNumberPhase': '2',
                'trafficLightColor': '#33CC00'
            },
            {
                'laneNo': '2',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '3',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '4',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '5',
                'through': 0,
                'turnLeft': 0,
                'turnRight': 1,
                'turnAround': 0,
                'directionIdentifyings': '右转',
                'channelNumberPhase': 0,
                'trafficLightColor': '#ccc'
            }
        ],
        'peoples': [
            {
                'laneNo': '0',
                'lfd': '0(人行)',
                'channelNumberPhase': '1'
            },
            {
                'laneNo': '99',
                'lfd': '99(人行)',
                'channelNumberPhase': '1'
            }
        ]
    },
    {
        'approachDirection': 3,
        'cdireCtion': '东',
        'lanes': [
            {
                'laneNo': '1',
                'through': 0,
                'turnLeft': 1,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '左转',
                'channelNumberPhase': 0,
                'trafficLightColor': '#ccc'
            },
            {
                'laneNo': '2',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '3',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '4',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '5',
                'through': 0,
                'turnLeft': 0,
                'turnRight': 1,
                'turnAround': 0,
                'directionIdentifyings': '右转',
                'channelNumberPhase': 0,
                'trafficLightColor': '#ccc'
            }
        ],
        'peoples': [
            {
                'laneNo': '0',
                'lfd': '0(人行)',
                'channelNumberPhase': '2'
            },
            {
                'laneNo': '99',
                'lfd': '99(人行)',
                'channelNumberPhase': '1'
            }
        ]
    },
    {
        'approachDirection': 5,
        'cdireCtion': '南',
        'lanes': [
            {
                'laneNo': '1',
                'through': 0,
                'turnLeft': 1,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '左转',
                'channelNumberPhase': '2',
                'trafficLightColor': '#33CC00'
            },
            {
                'laneNo': '2',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '3',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '4',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '5',
                'through': 0,
                'turnLeft': 0,
                'turnRight': 1,
                'turnAround': 0,
                'directionIdentifyings': '右转',
                'channelNumberPhase': 0,
                'trafficLightColor': '#ccc'
            }
        ],
        'peoples': [
            {
                'laneNo': '0',
                'lfd': '0(人行)',
                'channelNumberPhase': '1'
            },
            {
                'laneNo': '99',
                'lfd': '99(人行)',
                'channelNumberPhase': '1'
            }
        ]
    },
    {
        'approachDirection': 7,
        'cdireCtion': '西',
        'lanes': [
            {
                'laneNo': '1',
                'through': 0,
                'turnLeft': 1,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '左转',
                'channelNumberPhase': 0,
                'trafficLightColor': '#ccc'
            },
            {
                'laneNo': '2',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '3',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '4',
                'through': 1,
                'turnLeft': 0,
                'turnRight': 0,
                'turnAround': 0,
                'directionIdentifyings': '直行',
                'channelNumberPhase': '1',
                'trafficLightColor': '#FF0033'
            },
            {
                'laneNo': '5',
                'through': 0,
                'turnLeft': 0,
                'turnRight': 1,
                'turnAround': 0,
                'directionIdentifyings': '右转',
                'channelNumberPhase': 0,
                'trafficLightColor': '#ccc'
            }
        ],
        'peoples': [
            {
                'laneNo': '0',
                'lfd': '0(人行)',
                'channelNumberPhase': '2'
            },
            {
                'laneNo': '99',
                'lfd': '99(人行)',
                'channelNumberPhase': '1'
            }
        ]
    }
];
let laneC = null;

onMounted(() => {
    laneC = new Lane({
        canvas: laneCanvas.value,
        data
    });

    // 如红绿数据更新可采用setData方法刷新红绿状态
    // laneC.setData(data)
});

onUnmounted(() => {
    laneC?.destroy();
    laneC = null;
});

</script>

<style scoped lang="scss">
.lane {
    width: 100%;
    height: 100%;
    background-color: #325e76;
}
</style>

lane.js封装如下

import { getDirectionIdentifyings, computePosition } from './baseDI';

class Lane {
    constructor(opt) {
        this.dpr = window.devicePixelRatio || 1;
        this.canvas = opt.canvas;
        this.w = null;
        this.h = null;
        this.ctx = null;

        this.data = opt.data;
        // 车道范围坐标
        this.region = [];
        // 车道线坐标
        this.dataXY = [];

        // 路中心空白区域占canvas宽高最小值的比,用来计算车道宽度。占比越大,中心空白区域越大,车道越宽,线路越短。取值范围0-1,不允许取0,1。
        this.laneCenterProportion = 'auto' || opt.laneCenterProportion; // ex: 0.8
        // 车道样式
        this.laneStyle = opt.laneStyle;

        // 缩放
        this.scaleFlag = false;
        this.mouseScaleSpeed = 5; // 缩放速度
        this.scaleIndex = 100; // 初始缩放系数
        this.normalScaleIndex = 100; // 标准缩放系数
        this.minScaleIndex = 50; // 最小缩放系数
        this.scaleC = this.scaleIndex / this.normalScaleIndex; // 缩放比例

        // 平移
        this.translate = {
            x: 0,
            y: 0
        };

        // 异步任务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 };

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风舞红枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值