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 };

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑开机后出现Aptio Setup Utility 处理方法

电脑开机后出现Aptio Setup Utility怎么处理 Aptio Setup Utility界面的原因&#xff1a; 这是由于 bios设置与真实的硬件情况不匹配硬盘故障找不到可启动的硬盘情况 我的问题是找不到可启动的硬盘情况 解决方式如下&#xff1a; 进入如下界面了&#xff0c;选择Boot选项…

通信系统的最佳线性均衡器(2)---自适应滤波算法

本篇文章是博主在通信等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对通信等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在通信领域笔记&#xff1a;…

干货 | 如何进行群体DNA甲基化分析

目前&#xff0c;针对群体的研究基本上还是以重测序为主&#xff0c;基于对遗传多样性丰富的自然群体中的个体进行全基因组重测序&#xff0c;研究物种遗传进化多样性&#xff0c;结合准确的目标性状的表型数据及统计方法进行全基因组关联分析&#xff0c;可对动植物复杂农艺性…

【数据分析】用Python做事件抽取任务-快速上手方案

目录 方法一&#xff1a;使用OmniEvent库安装OmniEvent使用OmniEvent进行事件抽取OmniEvent优点缺点 方法二&#xff1a;使用大模型使用GPT网页版进行事件抽取事件类型列表 大模型优点缺点 总结 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;事件抽取是一项关键任…

迅狐短视频商城系统:打造直播带货、社区种草和商品分销一站式解决方案

迅狐短视频商城系统作为一体化电商解决方案&#xff0c;致力于为用户提供全方位、便捷的购物体验。从直播带货到社区种草、再到商品分销&#xff0c;系统提供了全面的功能模块&#xff0c;满足用户多元化的需求。 一、直播带货功能模块 迅狐短视频商城系统的直播带货功能模块&…

SAMBA(简单混合状态空间模型用于高效的无限上下文语言建模)及其对长文本模型的改进

论文地址&#xff1a; https://arxiv.org/pdf/2406.07522 SAMBA&#xff08;Simple Hybrid State Space Models for Efficient Unlimited Context Language Modeling&#xff09;是一种新型的基于Transformer的语言模型&#xff0c;旨在解决传统大语言模型在处理长文本时遇到的…

【初阶数据结构】二叉树(附题)

目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念&#xff08;树结构的相关概念命名参考自然树和人的血缘关系&#xff09; 1.3 树的表示 1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff0c;初次之外网盘中使用到&#xff09; 2.二叉树概念及结构 …

关于OS中逻辑地址与物理地址转换

首先将逻辑地址134D从十六进制转为2进制 0001 0011 0100 1101 1&#xff09;1K的时候对应2的10次方 页面大小占10位 从后往前数 0001 00 || 11 0100 1101 前面的转为十进制为4 对应页号4内容1A转为2进制01 1010将这个替换原来的前六位数字 即0110 1011 0100 1101 再转换为…

『互联网三驾马车』

某天开会的时候&#xff0c;老板问了大家一个问题&#xff0c;对目前各个角色分工合作有哪些不满意的地方。有人回答到&#xff0c;能不能别让产品同学每次都在假期前几天发布需求&#xff0c;让开发同学周末或者假期加班搞需求&#xff0c;然后在还在假期看着产品同学到处去玩…

【React 】折叠面板,点击展开时再请求数据

需求背景&#xff1a;使用折叠面板的形式展示数据&#xff0c;面板内部数据需要在打开时请求接口获取。 遇到问题&#xff1a;最开始使用Antd 的折叠面板组件&#xff0c;它对于数据直接渲染是没问题的&#xff0c;但是不好满足打开面板时再动态加载数据的需求&#xff0c;于是…

Linux机器通过Docker-Compose安装Jenkins发送Allure报告

目录 一、安装Docker 二、安装Docker Compose 三、准备测试用例 四、配置docker-compose.yml 五、启动Jenkins 六、配置Jenkins和Allure插件 七、创建含pytest的Jenkins任务 八、项目结果通知 1.通过企业微信通知 2.通过邮件通知 九、配置域名DNS解析 最近小编接到一…

MyBatis 源码分析--SqlSessionFactory

前言&#xff1a; 前文我们简单的回顾了 MyBatis 的基本概念&#xff0c;有聊到核心组件&#xff0c;工作流程等&#xff0c;本篇我们开始深入剖析 MyBatis 的核心源码&#xff0c;欢迎大家持续关注。 Mybatis 知识传送门 初识 MyBatis 【MyBatis 核心概念】 MyBatis 源码解…

深度学习500问——Chapter12:网络搭建及训练(3)

文章目录 12.3.5 Caffe有哪些接口 12.4 网络搭建有什么原则 12.4.1 新手原则 12.4.2 深度优先原则 12.4.3 卷积核size一般为奇数 12.4.4 卷积核不是越大越好 12.5 有哪些经典的网络模型值得我们去学习的 12.6 网络训练有哪些技巧 12.6.1 合适的数据集 12.6.2 合适的预…

【数据库】数据库脚本编写规范(Word原件)

编写本文档的目的是保证在开发过程中产出高效、格式统一、易阅读、易维护的SQL代码。 1 编写目的 2 SQL书写规范 3 SQL编写原则 软件全套资料获取进主页或者本文末个人名片直接获取。

[图解]企业应用架构模式2024新译本讲解15-行数据入口

1 00:00:01,060 --> 00:00:02,770 数据算完了 2 00:00:03,070 --> 00:00:07,720 接下来就是我们这一节的主要内容了 3 00:00:08,500 --> 00:00:13,630 应用服务调用第三方的&#xff0c;Email 4 00:00:13,640 --> 00:00:18,280 包括集成应用的接口来发Email 5 …

Springboot获取resources中的文件

1.Springboot以文件的形式获取resources中的文件 import com.google.gson.JsonIOException; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import org.springframework.util.ResourceUtils; import j…

【Linux】进程信号2——阻塞信号,捕捉信号

1.阻塞信号 1.1. 信号其他相关常见概念 在开始内容之前&#xff0c;先介绍一些信号的专业名词&#xff1a; 实际执行信号的处理动作称为信号递达&#xff08;Delivery&#xff09;信号从产生到递达之间的状态&#xff0c;称为信号未决&#xff08;Pending&#xff09;&#…

Swift Combine — zip和combineLatest的理解与使用

Publisher 上还有一些其他的操作&#xff0c;比如 zip 和 combineLatest&#xff0c;能让我们在时序上对控制多个 Publisher 的结果进行类似 and 和 or 的合并&#xff0c;它们在构建复杂 Publisher 逻辑时也十分有用。 zip Publisher 中的 zip 和 Sequence 的 zip 相类似&am…

【备考指南】CDA Level Ⅰ 最全备考攻略

很多考生朋友在报名前后&#xff0c;一直不知道需要怎么备考&#xff0c;这里给大家盘点一下最全的备考攻略&#xff0c;希望对你有用&#xff1a; 1、需要准备好之后再报名吗&#xff1f; 不需要&#xff0c;CDA认证考试是报名后自行预约考试的&#xff0c;您可以先报名同时…

qml:一个基础的界面设计

文章目录 文章说明效果图重要代码说明组件矩形卡片窗口最大化后组件全部居中菜单栏Repeater实现重复8行图片加载直接加载图片文本转图片FluentUI中可供选择的图标 文章说明 qt6.5.3 qml写的一个界面配置设计软件&#xff0c;目前不含任何c代码&#xff0c;纯qml。windoms风格的…