canvas绘制红绿灯路口

news2025/7/13 14:55:10

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

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

<script setup>
import { ref, onMounted } from 'vue'
import Lane from '@/utils/lane.js'

let laneCanvas = ref(null)
/**
 * 车道方向,进口方向
 * 1 - 北,2 - 东北,3 - 东,4 - 东南,
 * 5 - 南,6 - 西南,7 - 西,8 - 西北
 * 
 * 直行放行 nThrough 0不放行 1放行
 * 左转放行 nTurnLeft 0不放行 1放行
 * 右转放行 nTurnRight 0不放行 1放行
 * 调头 nTurnAround 0不放行 1放行
 * 
 * 通道相位 nChannelNumberPhase 1-红灯 2绿灯 3黄灯
 */
const randData = () => {
    let data = []
    let cdireCtions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']
    cdireCtions.forEach((item, index) => {
        if (rand(0, 1)) {
            let lanes = []
            let lanesLength = rand(1, 6)
            if (lanesLength === 1) {
                lanes = [{
                    nThrough: 1,
                    nTurnLeft: rand(0, 1),
                    nTurnRight: rand(0, 1),
                    nTurnAround: rand(0, 1),
                    nChannelNumberPhase: rand(1, 3)
                }]
            } else if (lanesLength === 2) {
                lanes = [{
                    nThrough: 1,
                    nTurnLeft: rand(0, 1),
                    nTurnRight: 0,
                    nTurnAround: rand(0, 1),
                    nChannelNumberPhase: rand(1, 3)
                }, {
                    nThrough: 1,
                    nTurnLeft: 0,
                    nTurnRight: rand(0, 1),
                    nTurnAround: 0,
                    nChannelNumberPhase: rand(1, 3)
                }]
            } else {
                for (let i = 0; i < lanesLength; i++) {
                    let nThrough = 0
                    let nTurnLeft = 0
                    let nTurnRight = 0
                    let nTurnAround = 0
                    if (i === 0) {
                        nThrough = rand(0, 1)
                        nTurnLeft = 1
                        nTurnAround = rand(0, 1)
                    }
                    if (i === lanesLength - 1) {
                        nThrough = rand(0, 1)
                        nTurnRight = 1
                        nTurnAround = 0
                    }
                    if (i > 0 && i < lanesLength - 1) {
                        nThrough = 1
                        if (lanes[i - 1].nTurnLeft) {
                            nTurnLeft = rand(0, 1)
                            nTurnRight = 0
                        } else if (lanes[i - 1].nTurnRight) {
                            nTurnLeft = 0
                            nTurnRight = 1
                        } else {
                            nTurnLeft = 0
                            nTurnRight = rand(0, 1)
                        }
                        nTurnAround = 0
                    }
                    lanes.push({
                        nThrough,
                        nTurnLeft,
                        nTurnRight,
                        nTurnAround,
                        nChannelNumberPhase: rand(1, 3)
                    })
                }
            }
            data.push({
                nApproachDirection: index + 1,
                cdireCtion: cdireCtions[index],
                lanes
            })
        }
    })
    if (data.length < 2) {
        data = randData()
    }
    return data
}
const dataTest = [{
    nApproachDirection: 1,
    cdireCtion: '北',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 3
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 2
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 2,
    cdireCtion: '东北',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 3,
    cdireCtion: '东',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 4,
    cdireCtion: '东南',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 5,
    cdireCtion: '南',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 6,
    cdireCtion: '西南',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 7,
    cdireCtion: '西',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 8,
    cdireCtion: '西北',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}]
const dataTest2 = [{
    nApproachDirection: 1,
    cdireCtion: '东',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}, {
    nApproachDirection: 2,
    cdireCtion: '南',
    lanes: [{
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 1,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 1,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 1,
        nTurnLeft: 0,
        nTurnRight: 0,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }, {
        nThrough: 0,
        nTurnLeft: 0,
        nTurnRight: 1,
        nTurnAround: 0,
        nChannelNumberPhase: 1
    }]
}]
const rand = (n, m) => {
    var c = m - n + 1
    return Math.floor(Math.random() * c + n)
}
onMounted(() => {
    let data = randData()
    console.log(data)
    let laneC = new Lane({
        canvas: laneCanvas.value,
        data: [...data]
    })

    setInterval(() => {
        data.forEach(dataItem => {
            dataItem.lanes.forEach(lane => {
                lane.nChannelNumberPhase = rand(1, 3)
            })
        })
        laneC.setData(data)
    }, 5000)
})

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

lane.js源码

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

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

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

        this.init()
    }
    init() {
        if (!this.canvas) {
            return
        }

        if (this.canvas.width !== Math.floor(this.canvas.offsetWidth * this.dpr) || this.canvas.height !== Math.floor(this.canvas.offsetHeight * this.dpr)) {
            this.w = this.canvas.width = Math.floor(this.canvas.offsetWidth * this.dpr)
            this.h = this.canvas.height = Math.floor(this.canvas.offsetHeight * this.dpr)
        }
        this.ctx = this.canvas.getContext('2d')

        this.getLaneStyle()
        this.formatDataXY()
        this.getRegion()
        this.draw()
    }
    // 获取车道样式
    getLaneStyle() {
        let laneStyle = {
            // 车道范围
            region: {
                width: 2 * this.dpr,
                color: '#fff',
                type: 'solid',
                CurveType: 'quadratic', // normal: 插值曲线, quadratic: 二次贝塞尔, arc: 圆弧线。arc有问题,请勿使用
                background: '#1f2748'
            },
            // 车道左侧车道线
            innerLeft: {
                width: 1 * this.dpr,
                color: '#999',
                type: [10 * this.dpr, 10 * this.dpr],
            },
            // 车道右侧车道线
            innerRight: {
                width: 1 * this.dpr,
                color: '#eee',
                type: [10 * this.dpr, 10 * this.dpr],
            },
            // 车道分割线
            innerDivider: {
                width: 2 * this.dpr,
                color: '#f0bf0a',
                type: 'solid'
            },
            // 车道标识
            direction: {
                widthProportion: 0.1, // 占车道比例,建议小于0.2
                HeightWidthProportion: 10, // 高宽比,建议大于5
                maxWidth: 20 * this.dpr,
                arrowWidth: 2, // 箭头/方向线的比例, 建议大于1小于2
                background: '#ddd'
            },
            // 斑马线
            zebraCrossing: {
                widthProportion: 0.05, // 单个斑马线宽占车道比例,建议小于0.2
                widthHeightProportion: 0.2, // 单个斑马线宽高比,建议小于0.5
                color: '#ddd'
            },
            // 红绿灯
            trafficLight: {
                rProportion: 0.3, // 单个红绿灯半径占车道比例,建议小于0.5,
                colors: ['#FF0033', '#33CC00', '#FFFF33'],
            }
        }
        if (this.laneStyle) {
            this.laneStyle = Object.assign(laneStyle, this.laneStyle)
        } else {
            this.laneStyle = laneStyle
        }

        let laneMaxNum = this.getLaneMaxNum()
        let sideLength = this.getSideLength()
        // 车道宽度 / 2 表示双向
        this.laneStyle.width = sideLength / 2 / laneMaxNum
        // 方向表示线宽高
        this.laneStyle.direction.width = this.laneStyle.width * this.laneStyle.direction.widthProportion
        if (this.laneStyle.direction.width > this.laneStyle.direction.maxWidth) {
            this.laneStyle.direction.width = this.laneStyle.direction.maxWidth
        }
        this.laneStyle.direction.height = this.laneStyle.direction.width * this.laneStyle.direction.HeightWidthProportion
        // 斑马线宽高
        this.laneStyle.zebraCrossing.width = this.laneStyle.width * this.laneStyle.zebraCrossing.widthProportion
        this.laneStyle.zebraCrossing.height = this.laneStyle.zebraCrossing.width / this.laneStyle.zebraCrossing.widthHeightProportion
        this.laneStyle.zebraCrossing.type = [this.laneStyle.zebraCrossing.width, this.laneStyle.zebraCrossing.width * 4]
        // 红绿灯半径
        this.laneStyle.trafficLight.r = this.laneStyle.width * this.laneStyle.trafficLight.rProportion
    }
    // 获取最大车道数
    getLaneMaxNum() {
        let laneMaxNum = 0
        this.data.forEach(item => {
            if (item.lanes.length > laneMaxNum) {
                laneMaxNum = item.lanes.length
            }
        })
        if(laneMaxNum === 1){
            laneMaxNum = 2 
        }
        return laneMaxNum
    }
    // 获取中心路口八边形边长
    getSideLength() {
        let minW = this.w > this.h ? this.h : this.w
        let sideLength = minW * this.laneCenterProportion / (Math.sqrt(2) + 1)
        return sideLength
    }
    // 计算车道坐标
    formatDataXY() {
        let dataXY = []
        // this.laneStyle
        // 车道起始中心位置
        let centerX = this.w / 2
        let centerY = this.h - this.h * (1 - this.laneCenterProportion) / 2
        // 车道长度
        let laneLength = Math.sqrt(Math.pow(this.w, 2) * Math.pow(this.h, 2))
        this.data.forEach(dataItem => {
            let dataXYItem = {
                nApproachDirection: dataItem.nApproachDirection,
            }
            // 起始x
            let startX = centerX - this.laneStyle.width * dataItem.lanes.length
            // 起始y
            let startY = centerY + this.laneStyle.zebraCrossing.height * 2
            // 结束Y
            let endY = startY + laneLength
            // 线
            let lines = []
            // 单向车道分割线数量
            let innerLines = dataItem.lanes.length - 1

            // 车道左边线
            lines.push({
                x0: startX,
                y0: startY - this.laneStyle.zebraCrossing.height * 2,
                x1: startX,
                y1: endY,
                type: 'outer'
            })
            // 车道左侧分割线
            for (let i = 0; i < innerLines; i++) {
                let x = startX + (i + 1) * this.laneStyle.width
                lines.push({
                    x0: x,
                    y0: startY,
                    x1: x,
                    y1: endY,
                    style: { ...this.laneStyle.innerLeft }
                })
            }
            // 左右车道分割线
            let dividerX = startX + (innerLines + 1) * this.laneStyle.width
            lines.push({
                x0: dividerX,
                y0: startY,
                x1: dividerX,
                y1: endY,
                style: { ...this.laneStyle.innerDivider }
            })
            // 车道右侧分割线
            for (let i = 0; i < innerLines; i++) {
                let x = startX + (innerLines + i + 2) * this.laneStyle.width
                lines.push({
                    x0: x,
                    y0: startY,
                    x1: x,
                    y1: endY,
                    style: { ...this.laneStyle.innerRight }
                })
            }
            // 车道右边线
            let outerRightx = startX + (innerLines + 1) * 2 * this.laneStyle.width
            lines.push({
                x0: outerRightx,
                y0: startY - this.laneStyle.zebraCrossing.height * 2,
                x1: outerRightx,
                y1: endY,
                type: 'outer'
            })
            dataXYItem.lines = lines

            // 方向标识
            let directionIdentifyings = []
            for (let i = 0; i < dataItem.lanes.length; i++) {
                let laneItem = dataItem.lanes[i]
                let key = [laneItem.nThrough, laneItem.nTurnLeft, laneItem.nTurnRight, laneItem.nTurnAround].join('')
                let line = lines[innerLines + i + 1]
                directionIdentifyings.push(this.getDirectionIdentifyings(key, this.laneStyle.direction.width, this.laneStyle.direction.height, {
                    x: line.x0 + this.laneStyle.width / 2,
                    y: line.y0 + this.laneStyle.direction.height / 2 + this.laneStyle.trafficLight.r * 4
                }))
            }
            dataXYItem.directionIdentifyings = directionIdentifyings

            // 斑马线
            dataXYItem.zebraCrossing = [{
                x: lines[0].x0,
                y: startY - this.laneStyle.zebraCrossing.height
            }, {
                x: lines[lines.length - 1].x0,
                y: startY - this.laneStyle.zebraCrossing.height
            }]

            // 红绿灯
            let trafficLights = []
            for (let i = 0; i < dataItem.lanes.length; i++) {
                let laneItem = dataItem.lanes[i]
                let line = lines[innerLines + i + 1]
                trafficLights.push({
                    x: line.x0 + this.laneStyle.width / 2,
                    y: line.y0 + this.laneStyle.trafficLight.r * 2,
                    r: this.laneStyle.trafficLight.r,
                    color: this.laneStyle.trafficLight.colors[laneItem.nChannelNumberPhase - 1]
                })
            }
            dataXYItem.trafficLights = trafficLights

            dataXY.push(dataXYItem)
        })

        this.dataXYByRotate(dataXY)

        this.dataXY = dataXY
    }
    // 获取方向标识坐标
    getDirectionIdentifyings(key, w, h, centerXY) {
        // 标识边界
        let topY = centerXY.y - h / 2
        let bottomY = centerXY.y + h / 2
        let leftX = centerXY.x - w / 2 * 3
        let rightX = centerXY.x + w / 2 * 3
        // 直行线中心位置
        let cX = centerXY.x + w
        let cY = centerXY.y
        // 箭头宽高
        let arrowW = w * this.laneStyle.direction.arrowWidth
        let arrowH = arrowW * Math.sin(Math.PI / 3)
        // 线坐标
        let points = []
        // 三角形坐标
        let arrowPoints = []
        switch (key) {
            case '0001':
                // 调头
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: topY + w / 2 },
                    { x: leftX + w / 2 * 5, y: topY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                break;
            case '0100':
                // 左转
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1000':
                // 直行
                leftX = centerXY.x - w / 2
                rightX = centerXY.x + w / 2
                cX = centerXY.x
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                break;
            case '0010':
                // 右转
                cX = centerXY.x - w
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '0101':
                // 调头左转
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1001':
                // 调头直行
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                break;
            case '0011':
                // 调头右转
                leftX = centerXY.x - w / 2 * 5
                rightX = centerXY.x + w / 2 * 5
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                cX = centerXY.x
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1100':
                // 左转直行
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                break;
            case '0110':
                // 左转右转
                leftX = centerXY.x - w / 2 * 5
                rightX = centerXY.x + w / 2 * 5
                cX = centerXY.x
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1010':
                // 直行右转
                cX = centerXY.x - w
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1101':
                // 调头左转直行
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1011':
                // 调头直行右转
                leftX = centerXY.x - w / 2 * 5
                rightX = centerXY.x + w / 2 * 5
                cX = centerXY.x
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[2][0].x + arrowPoints[2][2].x) / 2, y: (arrowPoints[2][0].y + arrowPoints[2][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '0111':
                // 调头左转右转
                leftX = centerXY.x - w / 2 * 5
                rightX = centerXY.x + w / 2 * 5
                cX = centerXY.x
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[2][0].x + arrowPoints[2][2].x) / 2, y: (arrowPoints[2][0].y + arrowPoints[2][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1110':
                // 左转直行右转
                leftX = centerXY.x - w / 2 * 5
                rightX = centerXY.x + w / 2 * 5
                cX = centerXY.x
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[0][0].x + arrowPoints[0][2].x) / 2, y: (arrowPoints[0][0].y + arrowPoints[0][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[2][0].x + arrowPoints[2][2].x) / 2, y: (arrowPoints[2][0].y + arrowPoints[2][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
            case '1111':
                // 调头左转直行右转
                leftX = centerXY.x - w / 2 * 5
                rightX = centerXY.x + w / 2 * 5
                cX = centerXY.x
                arrowPoints.push(this.getArrow(1, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: leftX + w / 2, y: bottomY - arrowH },
                    { x: leftX + w / 2, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: cY + w / 2 },
                    { x: leftX + w / 2 * 5, y: bottomY },
                ])
                arrowPoints.push(this.getArrow(2, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[1][0].x + arrowPoints[1][2].x) / 2, y: (arrowPoints[1][0].y + arrowPoints[1][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(3, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: cX, y: topY + arrowH },
                    { x: cX, y: bottomY }
                ])
                arrowPoints.push(this.getArrow(4, arrowW, arrowH, w, h, topY, bottomY, leftX, rightX, cX, cY))
                points.push([
                    { x: (arrowPoints[3][0].x + arrowPoints[3][2].x) / 2, y: (arrowPoints[3][0].y + arrowPoints[3][2].y) / 2 },
                    { x: cX, y: cY },
                    { x: cX, y: bottomY }
                ])
                break;
        }

        return { arrowPoints, points }
    }
    getArrow(key, w, h, w2, h2, topY, bottomY, leftX, rightX, cX, cY) {
        let point = []
        let wd = (w - w2) / 2 // 三角形边长与线宽的差值的一半

        let rotateDeg = 30// 左转右转旋转角度
        let hv = h2 / Math.cos(Math.PI / 180 * rotateDeg) // 计算左转右转虚拟线长
        let topYv = topY - (hv - h2) / 2 // 虚拟起始高度

        switch (key) {
            case 1:
                // 调头
                point = [
                    { x: leftX - wd, y: bottomY - h },
                    { x: leftX + w2 / 2, y: bottomY },
                    { x: leftX + w2 + wd, y: bottomY - h }
                ]
                break;
            case 2:
                // 左转
                point = [
                    { x: cX + w / 2, y: topYv + h },
                    { x: cX, y: topYv },
                    { x: cX - w / 2, y: topYv + h }
                ]
                point.forEach(item => {
                    let newXY = this.computePosition(item.x, item.y, -rotateDeg, cX, cY)
                    item.x = newXY.x
                    item.y = newXY.y
                })
                break;
            case 3:
                // 直行
                point = [
                    { x: cX + w / 2, y: topY + h },
                    { x: cX, y: topY },
                    { x: cX - w / 2, y: topY + h }
                ]
                break;
            case 4:
                // 右转
                point = [
                    { x: cX + w / 2, y: topYv + h },
                    { x: cX, y: topYv },
                    { x: cX - w / 2, y: topYv + h }
                ]
                point.forEach(item => {
                    let newXY = this.computePosition(item.x, item.y, rotateDeg, cX, cY)
                    item.x = newXY.x
                    item.y = newXY.y
                })
                break;
        }
        return point
    }
    // 计算旋转坐标
    dataXYByRotate(dataXY) {
        let centerX = this.w / 2
        let centerY = this.h / 2
        dataXY.forEach(dataXYItem => {
            // 八边形,一个边占45度
            let rotateReg = -180 + (dataXYItem.nApproachDirection - 1) * 45
            dataXYItem.lines.forEach(line => {
                let xy0 = this.computePosition(line.x0, line.y0, rotateReg, centerX, centerY)
                line.x0 = xy0.x
                line.y0 = xy0.y

                let xy1 = this.computePosition(line.x1, line.y1, rotateReg, centerX, centerY)
                line.x1 = xy1.x
                line.y1 = xy1.y
            })
            dataXYItem.directionIdentifyings.forEach(directionIdentifying => {
                directionIdentifying.points.forEach(point => {
                    point.forEach(item => {
                        let { x, y } = this.computePosition(item.x, item.y, rotateReg, centerX, centerY)
                        item.x = x
                        item.y = y
                    })
                })
                directionIdentifying.arrowPoints.forEach(arrowPoint => {
                    arrowPoint.forEach(item => {
                        let { x, y } = this.computePosition(item.x, item.y, rotateReg, centerX, centerY)
                        item.x = x
                        item.y = y
                    })
                })
            })
            dataXYItem.zebraCrossing.forEach(zebraCrossing => {
                let { x, y } = this.computePosition(zebraCrossing.x, zebraCrossing.y, rotateReg, centerX, centerY)
                zebraCrossing.x = x
                zebraCrossing.y = y
            })
            dataXYItem.trafficLights.forEach(trafficLight => {
                let { x, y } = this.computePosition(trafficLight.x, trafficLight.y, rotateReg, centerX, centerY)
                trafficLight.x = x
                trafficLight.y = y
            })
        })
    }
    // 旋转计算
    computePosition(x, y, angle, centerX, centerY) {
        // 圆心
        let a = centerX;
        let b = centerY;
        // 计算
        let c = Math.PI / 180 * angle;
        let rx = (x - a) * Math.cos(c) - (y - b) * Math.sin(c) + a;
        let ry = (y - b) * Math.cos(c) + (x - a) * Math.sin(c) + b;
        return { x: rx, y: ry };
    }
    // 获取车道范围
    getRegion() {
        let region = []
        for (let i = 0; i < this.dataXY.length; i++) {
            let dataXYItem = this.dataXY[i]
            let linesLength = dataXYItem.lines.length
            if (i !== 0) {
                // 衔接上一车道
                let prevDataXYItem = this.dataXY[i - 1]
                let data = {
                    prevNApproachDirection: prevDataXYItem.nApproachDirection,
                    nApproachDirection: dataXYItem.nApproachDirection,
                    type: 'connect'
                }
                let diffNApproachDirection = dataXYItem.nApproachDirection - prevDataXYItem.nApproachDirection
                if(diffNApproachDirection > 4){
                    diffNApproachDirection = (prevDataXYItem.nApproachDirection + 8) - dataXYItem.nApproachDirection
                }
                if (diffNApproachDirection === 4) {
                    // 车道正对,直线即可
                    data.point = [
                        { x: prevDataXYItem.lines[0].x0, y: prevDataXYItem.lines[0].y0 },
                        { x: dataXYItem.lines[linesLength - 1].x0, y: dataXYItem.lines[linesLength - 1].y0 },
                    ]
                    region.push(data)
                } else {
                    if (this.laneStyle.region.CurveType === 'arc') {
                        let angle = 45 * diffNApproachDirection
                        let startAngle = 45 * (prevDataXYItem.nApproachDirection - 5)
                        data.OR = this.findCircleCenter(
                            prevDataXYItem.lines[0].x0, prevDataXYItem.lines[0].y0,
                            dataXYItem.lines[linesLength - 1].x0, dataXYItem.lines[linesLength - 1].y0,
                            angle,
                            true
                        )
                        data.OR.startAngle = startAngle
                        data.OR.endAngle = startAngle - angle
                        data.OR.anticlockwise = true
                    } else {
                        // 曲线
                        let laneXY0 = this.calculateIntersection([
                            [prevDataXYItem.lines[0].x0, prevDataXYItem.lines[0].y0],
                            [prevDataXYItem.lines[0].x1, prevDataXYItem.lines[0].y1],
                        ], [
                            [dataXYItem.lines[linesLength - 1].x0, dataXYItem.lines[linesLength - 1].y0],
                            [dataXYItem.lines[linesLength - 1].x1, dataXYItem.lines[linesLength - 1].y1],
                        ])
                        let laneXY1 = [(prevDataXYItem.lines[0].x0 + dataXYItem.lines[linesLength - 1].x0) / 2, (prevDataXYItem.lines[0].y0 + dataXYItem.lines[linesLength - 1].y0) / 2]
                        let originPoints = [
                            { x: prevDataXYItem.lines[0].x0, y: prevDataXYItem.lines[0].y0 },
                            { x: laneXY1[0] - (laneXY1[0] - laneXY0[0]) * diffNApproachDirection / 4, y: laneXY1[1] - (laneXY1[1] - laneXY0[1]) * diffNApproachDirection / 4 },
                            { x: dataXYItem.lines[linesLength - 1].x0, y: dataXYItem.lines[linesLength - 1].y0 },
                        ]
                        if (this.laneStyle.region.CurveType === 'normal') {
                            let point = this.getCurveVertex(originPoints)
                            data.point = point
                        } else {
                            data.point = originPoints
                        }
                    }
                    region.push(data)
                }
            }
            // 车道范围
            region.push({
                nApproachDirection: dataXYItem.nApproachDirection,
                x0: dataXYItem.lines[linesLength - 1].x0,
                y0: dataXYItem.lines[linesLength - 1].y0,
                x1: dataXYItem.lines[linesLength - 1].x1,
                y1: dataXYItem.lines[linesLength - 1].y1,
                x2: dataXYItem.lines[0].x1,
                y2: dataXYItem.lines[0].y1,
                x3: dataXYItem.lines[0].x0,
                y3: dataXYItem.lines[0].y0,
                type: 'lane'
            })
            if (i === this.dataXY.length - 1) {
                // 衔接起始车道
                let startDataXYItem = this.dataXY[0]
                let startLinesLength = startDataXYItem.lines.length
                let data = {
                    startNApproachDirection: startDataXYItem.nApproachDirection,
                    nApproachDirection: dataXYItem.nApproachDirection,
                    type: 'connect'
                }
                let diffNApproachDirection = startDataXYItem.nApproachDirection + 8 - dataXYItem.nApproachDirection
                if(diffNApproachDirection > 4){
                    diffNApproachDirection = dataXYItem.nApproachDirection - startDataXYItem.nApproachDirection
                }
                if (diffNApproachDirection === 4) {
                    // 车道正对,直线即可
                    data.point = [
                        { x: dataXYItem.lines[0].x0, y: dataXYItem.lines[0].y0 },
                        { x: startDataXYItem.lines[startLinesLength - 1].x0, y: startDataXYItem.lines[startLinesLength - 1].y0 },
                    ]
                    region.push(data)
                } else {
                    if (this.laneStyle.region.CurveType === 'arc') {
                        let angle = 45 * diffNApproachDirection
                        let startAngle = 45 * (dataXYItem.nApproachDirection - 1)
                        data.OR = this.findCircleCenter(
                            dataXYItem.lines[0].x0, dataXYItem.lines[0].y0,
                            startDataXYItem.lines[linesLength - 1].x0, startDataXYItem.lines[linesLength - 1].y0,
                            angle,
                            true
                        )
                        data.OR.endAngle = startAngle + angle
                        data.OR.startAngle = startAngle
                        data.OR.anticlockwise = false
                    } else {
                        // 曲线
                        let laneXY0 = this.calculateIntersection([
                            [dataXYItem.lines[0].x0, dataXYItem.lines[0].y0],
                            [dataXYItem.lines[0].x1, dataXYItem.lines[0].y1],
                        ], [
                            [startDataXYItem.lines[startLinesLength - 1].x0, startDataXYItem.lines[startLinesLength - 1].y0],
                            [startDataXYItem.lines[startLinesLength - 1].x1, startDataXYItem.lines[startLinesLength - 1].y1],
                        ])
                        let laneXY1 = [(startDataXYItem.lines[startLinesLength - 1].x0 + dataXYItem.lines[0].x0) / 2, (startDataXYItem.lines[startLinesLength - 1].y0 + dataXYItem.lines[0].y0) / 2]
                        let originPoints = [
                            { x: dataXYItem.lines[0].x0, y: dataXYItem.lines[0].y0 },
                            { x: laneXY1[0] - (laneXY1[0] - laneXY0[0]) * diffNApproachDirection / 4, y: laneXY1[1] - (laneXY1[1] - laneXY0[1]) * diffNApproachDirection / 4 },
                            { x: startDataXYItem.lines[startLinesLength - 1].x0, y: startDataXYItem.lines[startLinesLength - 1].y0 },
                        ]
                        if (this.laneStyle.region.CurveType === 'normal') {
                            let point = this.getCurveVertex(originPoints)
                            data.point = point
                        } else {
                            data.point = originPoints
                        }
                    }
                    region.push(data)
                }
            }
        }
        this.region = region
    }
    // 获取两条直线的交点
    calculateIntersection(line1, line2) {
        // 解方程组
        const x1 = line1[0][0];
        const y1 = line1[0][1];
        const x2 = line1[1][0];
        const y2 = line1[1][1];

        const x3 = line2[0][0];
        const y3 = line2[0][1];
        const x4 = line2[1][0];
        const y4 = line2[1][1];

        const denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

        if (denominator === 0) {
            // 直线平行,没有交点
            return null;
        }

        const intersectionX = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denominator;
        const intersectionY = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denominator;

        return [intersectionX, intersectionY];
    }
    // 以下四个方法获取曲线
    getCurveVertex(vertex, pointsPow = 0.4) {
        let length = 0
        for (let i = 0; i < vertex.length - 1; i++) {
            length += Math.sqrt(Math.pow(vertex[i].x - vertex[i + 1].x, 2) + Math.pow(vertex[i].y - vertex[i + 1].y, 2))
        }
        length = Math.ceil(length)
        return this.getNewData(vertex, length * pointsPow)
    }
    // 曲线 插值
    getNewData(pointsOrigin, pointsPow) {
        const points = []
        const divisions = (pointsOrigin.length - 1) * pointsPow
        for (let i = 0; i < divisions; i++) {
            points.push(this.getPoint(i, divisions, pointsOrigin, pointsPow))
        }
        return points
    }
    getPoint(i, divisions, pointsOrigin, pointsPow) {
        const isRealI = (i * divisions) % pointsPow
        const p = ((pointsOrigin.length - 1) * i) / divisions
        const intPoint = Math.floor(p)
        const weight = p - intPoint
        const p0 = pointsOrigin[intPoint === 0 ? intPoint : intPoint - 1]
        const p1 = pointsOrigin[intPoint]
        const p2 = pointsOrigin[intPoint > pointsOrigin.length - 2 ? pointsOrigin.length - 1 : intPoint + 1]
        const p3 = pointsOrigin[intPoint > pointsOrigin.length - 3 ? pointsOrigin.length - 1 : intPoint + 2]
        return {
            isReal: isRealI === 0,
            x: this.catmullRom(weight, p0.x, p1.x, p2.x, p3.x),
            y: this.catmullRom(weight, p0.y, p1.y, p2.y, p3.y)
        }
    }
    catmullRom(t, p0, p1, p2, p3) {
        const v0 = (p2 - p0) * 0.5
        const v1 = (p3 - p1) * 0.5
        const t2 = t * t
        const t3 = t * t2
        return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1
    }
    // 根据圆上两点以及夹角角度 求 圆心
    findCircleCenter(x1, y1, x2, y2, theta, isNeg) {
        let cx = 0;
        let cy = 0;
        let dDistance = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
        let dRadius = dDistance * 0.5 / Math.sin(Math.PI / 180 * theta * 0.5);
        if (dDistance == 0.0) {
            // cout << "\n输入了相同的点!\n";
            return false;
        }
        if ((2 * dRadius) < dDistance) {
            // cout << "\n两点间距离大于直径!\n";
            return false;
        }
        let k_verticle = 0.0;
        let mid_x = 0.0
        let mid_y = 0.0;
        let a = 1.0;
        let b = 1.0;
        let c = 1.0;
        let k = (y2 - y1) / (x2 - x1);
        let cx1, cy1, cx2, cy2;
        if (k == 0) {
            cx1 = (x1 + x2) / 2.0;
            cx2 = (x1 + x2) / 2.0;
            cy1 = y1 + Math.sqrt(dRadius * dRadius - (x1 - x2) * (x1 - x2) / 4.0);
            cy2 = y2 - Math.sqrt(dRadius * dRadius - (x1 - x2) * (x1 - x2) / 4.0);
        }
        else {
            k_verticle = -1.0 / k;
            mid_x = (x1 + x2) / 2.0;
            mid_y = (y1 + y2) / 2.0;
            a = 1.0 + k_verticle * k_verticle;
            b = -2 * mid_x - k_verticle * k_verticle * (x1 + x2);
            c = mid_x * mid_x + k_verticle * k_verticle * (x1 + x2) * (x1 + x2) / 4.0 -
                (dRadius * dRadius - ((mid_x - x1) * (mid_x - x1) + (mid_y - y1) * (mid_y - y1)));
    
            cx1 = (-1.0 * b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
            cx2 = (-1.0 * b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
            cy1 = this.y_Coordinates(mid_x, mid_y, k_verticle, cx1);
            cy2 = this.y_Coordinates(mid_x, mid_y, k_verticle, cx2);
        }
        //cx2,cy2为顺时针圆心坐标,cx1,cy1为逆时针圆心坐标
    
        if (isNeg) {
            cx = cx1;
            cy = cy1;
        }
        else {
            cx = cx2;
            cy = cy2;
        }
    
        return {x: cx, y: cy, r: Math.sqrt(Math.pow(cx - x1, 2) + Math.pow(cy - y1, 2))};
    }
    y_Coordinates(x, y, k, x0) {
        return k * x0 - k * x + y;
    }
    // 设置新的红绿灯数据
    setData(data){
        this.dataXY.forEach((dataXYItem, dataXYIndex) => {
            dataXYItem.trafficLights.forEach((trafficLight, trafficLightIndex) => {
                trafficLight.color = this.laneStyle.trafficLight.colors[data[dataXYIndex].lanes[trafficLightIndex].nChannelNumberPhase - 1]
            })
        })
        this.ctx.clearRect(0,0,this.w,this.h)
        this.draw()
    }
    // 绘制
    draw() {
        this.drawRegion()
        this.drawLines()
        this.drawDirectionIdentifyings()
        this.drawZebraCrossing()
        this.drawTrafficLight()
        // this.drawHelper()
    }
    drawRegion() {
        this.ctx.save()
        // 缩放
        // this.ctx.translate(this.w / 2, this.h / 2)
        // this.ctx.scale(this.scaleC, this.scaleC)
        // this.ctx.translate(-this.w / 2, -this.h / 2)

        this.ctx.beginPath()
        this.ctx.fillStyle = this.laneStyle.region.background
        this.ctx.lineWidth = this.laneStyle.region.width
        this.ctx.strokeStyle = this.laneStyle.region.color
        this.ctx.lineJoin = 'round';
        for (let i = 0; i < this.region.length; i++) {
            let regionItem = this.region[i]
            if (regionItem.type === 'connect') {
                if (regionItem?.point?.length === 2 && this.laneStyle.region.CurveType !== 'arc') {
                    // 直线
                    regionItem.point.forEach(item => {
                        this.ctx.lineTo(item.x, item.y)
                    })
                } else if (this.laneStyle.region.CurveType === 'arc') {
                    // 圆
                    if(regionItem.OR)
                    this.ctx.arc(regionItem.OR.x, regionItem.OR.y, regionItem.OR.r, regionItem.OR.startAngle * Math.PI / 180, regionItem.OR.endAngle * Math.PI / 180, regionItem.OR.anticlockwise)
                } else if (this.laneStyle.region.CurveType === 'normal') {
                    // 插值
                    regionItem.point.forEach(item => {
                        this.ctx.lineTo(item.x, item.y)
                    })
                } else {
                    // 二次贝塞尔
                    this.ctx.lineTo(regionItem.point[0].x, regionItem.point[0].y)
                    this.ctx.quadraticCurveTo(regionItem.point[1].x, regionItem.point[1].y, regionItem.point[2].x, regionItem.point[2].y)
                }
            } else {
                this.ctx.lineTo(regionItem.x0, regionItem.y0)
                this.ctx.lineTo(regionItem.x1, regionItem.y1)
                this.ctx.lineTo(regionItem.x2, regionItem.y2)
                this.ctx.lineTo(regionItem.x3, regionItem.y3)
            }
        }
        this.ctx.fill()
        this.ctx.stroke()
        this.ctx.closePath()

        this.ctx.restore()
    }
    drawLines() {
        this.dataXY.forEach((dataXYItem, dataXYIndex) => {
            dataXYItem.lines.forEach(lineItem => {
                if (lineItem.type !== 'outer') {
                    this.ctx.save()
                    // 缩放
                    // this.ctx.translate(this.w / 2, this.h / 2)
                    // this.ctx.scale(this.scaleC, this.scaleC)
                    // this.ctx.translate(-this.w / 2, -this.h / 2)

                    this.ctx.beginPath()
                    this.ctx.lineWidth = lineItem.style.width
                    this.ctx.strokeStyle = lineItem.style.color
                    if (lineItem.style.type !== 'solid') {
                        this.ctx.setLineDash(lineItem.style.type);
                    }
                    this.ctx.lineTo(lineItem.x0, lineItem.y0)
                    this.ctx.lineTo(lineItem.x1, lineItem.y1)
                    this.ctx.stroke()
                    this.ctx.closePath()
                    this.ctx.restore()
                }
            })
        })
    }
    drawDirectionIdentifyings() {
        this.dataXY.forEach((dataXYItem, dataXYIndex) => {
            dataXYItem.directionIdentifyings.forEach(directionIdentifying => {
                this.ctx.save()
                // 缩放
                // this.ctx.translate(this.w / 2, this.h / 2)
                // this.ctx.scale(this.scaleC, this.scaleC)
                // this.ctx.translate(-this.w / 2, -this.h / 2)

                directionIdentifying.points.forEach(pointItem => {
                    this.ctx.beginPath()
                    this.ctx.lineWidth = directionIdentifying.w
                    this.ctx.strokeStyle = this.laneStyle.direction.background
                    pointItem.forEach(item => {
                        this.ctx.lineTo(item.x, item.y)
                    })
                    this.ctx.stroke()
                    this.ctx.closePath()
                })

                directionIdentifying.arrowPoints.forEach(arrowPoint => {
                    this.ctx.beginPath()
                    this.ctx.fillStyle = this.laneStyle.direction.background
                    arrowPoint.forEach(item => {
                        this.ctx.lineTo(item.x, item.y)
                    })
                    this.ctx.fill()
                    this.ctx.closePath()
                })

                this.ctx.restore()
            })
        })
    }
    drawTrafficLight() {
        this.dataXY.forEach((dataXYItem, dataXYIndex) => {
            dataXYItem.trafficLights.forEach(trafficLight => {
                this.ctx.save()
                // 缩放
                // this.ctx.translate(this.w / 2, this.h / 2)
                // this.ctx.scale(this.scaleC, this.scaleC)
                // this.ctx.translate(-this.w / 2, -this.h / 2)

                this.ctx.beginPath()
                this.ctx.fillStyle = trafficLight.color
                this.ctx.arc(trafficLight.x, trafficLight.y, trafficLight.r, 0, Math.PI * 2)
                this.ctx.fill()
                this.ctx.closePath()

                this.ctx.restore()
            })
        })
    }
    drawZebraCrossing() {
        this.dataXY.forEach((dataXYItem, dataXYIndex) => {
            this.ctx.save()
            // 缩放
            // this.ctx.translate(this.w / 2, this.h / 2)
            // this.ctx.scale(this.scaleC, this.scaleC)
            // this.ctx.translate(-this.w / 2, -this.h / 2)

            this.ctx.beginPath()
            this.ctx.lineWidth = this.laneStyle.zebraCrossing.height
            this.ctx.strokeStyle = this.laneStyle.zebraCrossing.color
            this.ctx.setLineDash(this.laneStyle.zebraCrossing.type);
            dataXYItem.zebraCrossing.forEach(zebraCrossing => {
                this.ctx.lineTo(zebraCrossing.x, zebraCrossing.y)
            })
            this.ctx.stroke()
            this.ctx.closePath()
            this.ctx.restore()
        })
    }
    drawHelper() {
        // 绘制车道方向数字,用来查看车道是否正确
        this.ctx.beginPath()
        this.ctx.fillStyle = '#fff'
        this.ctx.font = 20 * this.dpr + 'px Arial'
        for (let i = 0; i < this.region.length; i++) {
            let regionItem = this.region[i]
            this.ctx.fillText(regionItem.nApproachDirection, regionItem.x0, regionItem.y0)
        }
        this.ctx.closePath()

        // 绘制坐标线
        this.ctx.save()
        this.ctx.lineWidth = 2 * this.dpr
        this.ctx.strokeStyle = 'red'

        this.ctx.beginPath()
        this.ctx.lineTo(this.w / 2, 0)
        this.ctx.lineTo(this.w / 2, this.h)
        this.ctx.stroke()
        this.ctx.closePath()

        this.ctx.beginPath()
        this.ctx.lineTo(0, this.h / 2)
        this.ctx.lineTo(this.w, this.h / 2)
        this.ctx.stroke()
        this.ctx.closePath()
        this.ctx.restore()

        // 绘制方向标识
        let testKeys = ['0001', '0100', '1000', '0010', '0101', '1001', '0011', '1100', '0110', '1010', '1101', '1011', '0111', '1110', '1111']
        let width = this.w / testKeys.length
        let w = width * this.laneStyle.direction.widthProportion
        let h = w * this.laneStyle.direction.HeightWidthProportion
        testKeys.forEach((key, index) => {
            let { arrowPoints, points } = this.getDirectionIdentifyings(key, w, h, { x: width * index + width / 2, y: this.h / 2 })

            this.ctx.save()
            // 缩放
            // this.ctx.translate(this.w / 2, this.h / 2)
            // this.ctx.scale(this.scaleC, this.scaleC)
            // this.ctx.translate(-this.w / 2, -this.h / 2)
            points.forEach(pointItem => {
                this.ctx.beginPath()
                this.ctx.lineWidth = w
                this.ctx.strokeStyle = '#fff'
                pointItem.forEach(item => {
                    this.ctx.lineTo(item.x, item.y)
                })
                this.ctx.stroke()
                this.ctx.closePath()
            })

            arrowPoints.forEach(arrowPoint => {
                this.ctx.beginPath()
                this.ctx.fillStyle = '#fff'
                arrowPoint.forEach(item => {
                    this.ctx.lineTo(item.x, item.y)
                })
                this.ctx.fill()
                this.ctx.closePath()
            })

            this.ctx.restore()
        })
    }
}

export default Lane

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

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

相关文章

IDEA中允许开启多个客户端

这个时候不要在客户端里创建socket对象时指定端口号了&#xff0c;否则会报错BindException

普通人如何月入过万?2024普通人创业适合干什么?

如果你的月收入不到1万块&#xff0c;也从来没有体验过一天就赚1万块是什么感觉的话&#xff0c;你还想创业&#xff1f;你如果想通过创业逆天改命&#xff0c;麻烦你一定要看完这篇文章。 普通人你要是想赚钱&#xff0c;一定要去赚那种能看得见的钱。 什么叫看得见的钱&…

35.搜索插入位置

给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2示例 2: 输入:…

服务器运行状况监控工具

服务器运行状况监视提供了每个服务器状态和性能的广泛概述&#xff0c;通过监控服务器指标&#xff0c;如 CPU 使用率、内存消耗、I/O、磁盘使用率、进程等&#xff0c;服务器运行状况监控可以避免服务器停机。 服务器性能监控指标 服务器是网络中最重要的组件之一&#xff0…

Android studio课程设计开发实现---日记APP

Android studio课程设计开发实现—日记APP 文章目录 Android studio课程设计开发实现---日记APP前言一、效果二、功能介绍1.主要功能2.涉及知识点 三、实现思路下载链接总结 前言 你们好&#xff0c;我是oy&#xff0c;介绍一个简易日记APP。 一、效果 1.启动页、引导页及登…

【北亚服务器数据恢复】san环境下LUN Mapping出错导致文件系统一致性出错的数据恢复案例

服务器数据恢复环境&#xff1a; san环境下的存储上一组由6块硬盘组建的RAID6&#xff0c;划分为若干LUN&#xff0c;MAP到跑不同业务的服务器上&#xff0c;服务器上层是SOLARIS操作系统UFS文件系统。 服务器故障&#xff1a; 业务需求需要增加一台服务器跑新增的应用&#xf…

社区医院挂号预约服务管理系统95an6

社区医院管理服务系统具有社区医院信息管理功能的选择。社区医院管理服务系统采用p[ython技术&#xff0c;基于django框架&#xff0c;mysql数据库进行开发&#xff0c;实现了首页、个人中心、用户管理、医生管理、预约医生管理、就诊信息管理、诊疗方案管理、病历信息管理、健…

人工智能工程师的前景怎么样

人工智能是未来的发展趋势&#xff0c;因此&#xff0c;人工智能工程师也将成为就业爆款。人工智能工程师负责创建和开发自动化系统、算法和机器学习模型&#xff0c;以实现自主决策和任务执行。由于人工智能在可穿戴设备、家庭自动化、智能城市和自动驾驶等领域都有广泛应用&a…

北斗卫星助力物流企业,打造智慧运输新时代

北斗卫星助力物流企业&#xff0c;打造智慧运输新时代 北斗卫星系统作为我国自主研发的卫星导航系统&#xff0c;近年来在智慧物流领域发挥了重要作用。通过北斗卫星系统的应用&#xff0c;物流企业可以实现智能化管理&#xff0c;提高运输效率&#xff0c;降低物流成本。越来…

前端三件套html/css/js的基本认识以及示例程序

简介 本文简要讲解了html,css,js.主要是让大家简要了解网络知识 因为实际开发中很少直接写html&css,所以不必过多纠结,了解一下架构就好 希望深度学习可以参考MDN和w3school HTML 基础 HTML (Hyper Text Markup Language) 不是一门编程语言,而是一种用来告知浏览器如…

微服务(1)

目录 1.什么是微服务&#xff1f;谈谈你对微服务的理解&#xff1f; 2.什么是Spring Cloud&#xff1f; 3.Springcloud中的组件有哪些&#xff1f; 3.具体说说SpringCloud主要项目&#xff1f; 5.SpringCloud项目部署架构&#xff1f; 1.什么是微服务&#xff1f;谈谈你对微…

OpenCV-Python(30):Harris角点检测

目标 理解Harris角点检测的概念掌握函数cv2.cornerHarris()、cv2.cornerSubPix()的用法 Harris算法原理 通过前面的图像特征介绍&#xff0c;我们知知道了角点的一个特性&#xff1a;向任何方向移动变化都很大。Chris_Harris 和Mike_Stephens 在1988 年的文章《A Combined Co…

【滑动窗口】C++算法:K 个不同整数的子数组

作者推荐 动态规划 多源路径 字典树 LeetCode2977:转换字符串的最小成本 本题涉及知识点 滑动窗口 LeetCoe992 K 个不同整数的子数组 给定一个正整数数组 nums和一个整数 k&#xff0c;返回 nums 中 「好子数组」 的数目。 如果 nums 的某个子数组中不同整数的个数恰好为 …

【DevOps 工具链】日志管理工具 - 22种 选型(读这一篇就够了)

文章目录 1、简述2、内容分类3、归纳对比表&#xff08;排序不分先后&#xff09;4、日志管理主要目的5、日志管理工具 22种 详细&#xff08;排序不分先后&#xff09;5.1、ManageEngine EventLog Analyzer5.1.1、简介5.1.2、效果图5.1.3、日志管理架构5.1.4、EventLog Analyz…

进行VMware日志管理

随着公司转向虚拟化其 IT 空间&#xff0c;虚拟环境日志监控正在占据日志管理的很大一部分,除了确保网络安全外&#xff0c;虚拟机日志监控还有助于管理虚拟化工具&#xff0c;这是最复杂的任务之一。 对虚拟环境日志的监控分析 当今公司中最受欢迎的虚拟平台之一是 VMware。…

基于SSM的蛋糕甜品店管理系统的设计与开发论文

基于SSM的蛋糕甜品店管理系统的设计与开发 摘要 如今&#xff0c;科学技术的力量越来越强大&#xff0c;通过结合较为成熟的计算机技术&#xff0c;促进了学校、医疗、商城等许多行业领域的发展。为了顺应时代的变化&#xff0c;各行业结合互联网、人工智能等技术&#xff0c…

软件工程导论——(为什么要学习软件工程?软件工程能学到什么?如何学习软件工程?)

导论&#xff08;引言&#xff09;&#xff1a; 1.为什么要学习软件工程&#xff1f; 软件工程知识并不只是项目管理可以用&#xff0c;同样适用于开发岗。比如开发也要做需求分析和架构设计&#xff0c;也要做计划。学习软件工程后也可以帮助开发人员更好的理解软件项目的整个…

Solidworks学习笔记

本内容为solidworks的学习笔记&#xff0c;根据自己的理解进行记录&#xff0c;部分可能不正确&#xff0c;请自行判断。 学习视频参考&#xff1a;【SolidWorks2018视频教程 SW2018中文版软件基础教学知识 SolidWorks自学教程软件操作教程 sw视频教程 零基础教程 视频教程】 h…

智能透明加密、半透明加密和落地加密的区别是什么?

智能透明加密、半透明加密和落地加密的主要区别如下&#xff1a; PC端访问地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 保护对象和方式&#xff1a; 智能透明加密&#xff1a;系统根据预设的敏感数据特征&#xff0c;对正…

FindMy技术用于遥控器

遥控器已经悄然成为我们生活中的常客。无论是控制电视机的开关&#xff0c;调整音量&#xff0c;切换频道&#xff0c;还是控制空调的温度&#xff0c;调节灯光亮度&#xff0c;甚至远程操控智能家居设备&#xff0c;遥控器都为我们提供了极大的便利。 将遥控器与FindMy技术相结…