|
|
@@ -45,6 +45,9 @@ export default {
|
|
|
scanLineTimer: null,
|
|
|
currentViewMinX: 0,
|
|
|
currentScanX: 0,
|
|
|
+ // 每条波带的速度 42~48 km/h 随机(组件初始化时生成一次)
|
|
|
+ greenSpeed: Math.round((42 + Math.random() * 6) * 10) / 10,
|
|
|
+ blueSpeed: Math.round((42 + Math.random() * 6) * 10) / 10,
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -134,21 +137,21 @@ export default {
|
|
|
},
|
|
|
|
|
|
getWaveData() {
|
|
|
- const speedMs = this.speedKmh / 3.6; // 换算为 m/s
|
|
|
const startX = this.intersections[0].x;
|
|
|
const endX = this.intersections[this.intersections.length - 1].x;
|
|
|
- const travelTime = (endX - startX) / speedMs;
|
|
|
+ const greenTravelTime = (endX - startX) / (this.greenSpeed / 3.6);
|
|
|
+ const blueTravelTime = (endX - startX) / (this.blueSpeed / 3.6);
|
|
|
|
|
|
// 【终极微调】:发车时间精确控制
|
|
|
// 正向绿波 (A -> D):10秒起步,刚好完美贴合所有绿灯边缘
|
|
|
const gStartY = 10;
|
|
|
const greenBottomLine = [
|
|
|
[startX, gStartY],
|
|
|
- [endX, gStartY + travelTime]
|
|
|
+ [endX, gStartY + greenTravelTime]
|
|
|
];
|
|
|
const greenTopLine = [
|
|
|
[startX, gStartY + this.bandwidth],
|
|
|
- [endX, gStartY + travelTime + this.bandwidth]
|
|
|
+ [endX, gStartY + greenTravelTime + this.bandwidth]
|
|
|
];
|
|
|
const greenCoords = [...greenBottomLine, ...[...greenTopLine].reverse()];
|
|
|
|
|
|
@@ -156,11 +159,11 @@ export default {
|
|
|
const bStartYAtD = 100;
|
|
|
const blueBottomLine = [
|
|
|
[endX, bStartYAtD],
|
|
|
- [startX, bStartYAtD + travelTime]
|
|
|
+ [startX, bStartYAtD + blueTravelTime]
|
|
|
];
|
|
|
const blueTopLine = [
|
|
|
[endX, bStartYAtD + this.bandwidth],
|
|
|
- [startX, bStartYAtD + travelTime + this.bandwidth]
|
|
|
+ [startX, bStartYAtD + blueTravelTime + this.bandwidth]
|
|
|
];
|
|
|
const blueCoords = [...blueBottomLine, ...[...blueTopLine].reverse()];
|
|
|
|
|
|
@@ -171,7 +174,8 @@ export default {
|
|
|
topLine: greenTopLine,
|
|
|
color: this.upWaveColor,
|
|
|
lineCol: '#2ecc71',
|
|
|
- isBlue: false
|
|
|
+ isBlue: false,
|
|
|
+ speed: this.greenSpeed
|
|
|
},
|
|
|
{
|
|
|
coords: blueCoords,
|
|
|
@@ -179,7 +183,8 @@ export default {
|
|
|
topLine: blueTopLine,
|
|
|
color: this.downWaveColor,
|
|
|
lineCol: '#3498db',
|
|
|
- isBlue: true
|
|
|
+ isBlue: true,
|
|
|
+ speed: this.blueSpeed
|
|
|
}
|
|
|
];
|
|
|
},
|
|
|
@@ -246,10 +251,10 @@ export default {
|
|
|
const pT1 = mappedTop[segIdx];
|
|
|
const pT2 = mappedTop[segIdx + 1];
|
|
|
|
|
|
- let angle = Math.atan2(pB2[1] - pB1[1], pB2[0] - pB1[0]);
|
|
|
- if (item.isBlue) {
|
|
|
- angle -= Math.PI;
|
|
|
- }
|
|
|
+ // 波带角度统一用底线方向(atan2),保证文字沿着波带方向读
|
|
|
+ const angle = Math.atan2(pB2[1] - pB1[1], pB2[0] - pB1[0]);
|
|
|
+ // 绿波速度文字沿波带方向顺时针 90°;蓝波沿波带方向逆时针 90°
|
|
|
+ const speedAngle = item.isBlue ? angle - Math.PI / 2 : angle + Math.PI / 2;
|
|
|
|
|
|
const percent = 0.15;
|
|
|
const midBottomX = pB1[0] + (pB2[0] - pB1[0]) * percent;
|
|
|
@@ -257,24 +262,33 @@ export default {
|
|
|
const midTopX = pT1[0] + (pT2[0] - pT1[0]) * percent;
|
|
|
const midTopY = pT1[1] + (pT2[1] - pT1[1]) * percent;
|
|
|
|
|
|
- // 速度文字沿底线垂直方向、远离波带方向偏移
|
|
|
+ // 速度文字沿波带法线向外偏移;与虚线保持同侧(绿波→顶线侧,蓝波→底线侧)
|
|
|
const sBDx = pB2[0] - pB1[0];
|
|
|
const sBDy = pB2[1] - pB1[1];
|
|
|
const sBLen = Math.sqrt(sBDx * sBDx + sBDy * sBDy) || 1;
|
|
|
const sPerpX = sBDy / sBLen;
|
|
|
const sPerpY = -sBDx / sBLen;
|
|
|
const sDot = (pT1[0] - pB1[0]) * sPerpX + (pT1[1] - pB1[1]) * sPerpY;
|
|
|
- const sSign = sDot > 0 ? -1 : 1;
|
|
|
const speedOff = this.fs(14);
|
|
|
- const speedX = midBottomX + sSign * sPerpX * speedOff;
|
|
|
- const speedY = midBottomY + sSign * sPerpY * speedOff;
|
|
|
+ let speedX, speedY;
|
|
|
+ if (item.isBlue) {
|
|
|
+ // 蓝波:底线外侧
|
|
|
+ const sSign = sDot > 0 ? -1 : 1;
|
|
|
+ speedX = midBottomX + sSign * sPerpX * speedOff;
|
|
|
+ speedY = midBottomY + sSign * sPerpY * speedOff;
|
|
|
+ } else {
|
|
|
+ // 绿波:顶线外侧(虚线这边)
|
|
|
+ const sSign = sDot > 0 ? 1 : -1;
|
|
|
+ speedX = midTopX + sSign * sPerpX * speedOff;
|
|
|
+ speedY = midTopY + sSign * sPerpY * speedOff;
|
|
|
+ }
|
|
|
|
|
|
return {
|
|
|
type: 'group',
|
|
|
children: [
|
|
|
{ type: 'polygon', shape: { points: mappedCoords }, style: { fill: item.color } },
|
|
|
...(() => {
|
|
|
- // 虚线与绿波带之间留间隙,沿垂直于底线方向向外偏移
|
|
|
+ // 虚线沿垂直于波带方向向外偏移:绿波走顶线外侧,蓝波走底线外侧(互相镜像)
|
|
|
const lineGap = this.fs(4);
|
|
|
const bDx = mappedBottom[1][0] - mappedBottom[0][0];
|
|
|
const bDy = mappedBottom[1][1] - mappedBottom[0][1];
|
|
|
@@ -286,13 +300,22 @@ export default {
|
|
|
const midTx = (mappedTop[0][0] + mappedTop[1][0]) / 2;
|
|
|
const midTy = (mappedTop[0][1] + mappedTop[1][1]) / 2;
|
|
|
const dot = (midTx - midBx) * perpX + (midTy - midBy) * perpY;
|
|
|
- const sign = dot > 0 ? -1 : 1;
|
|
|
- const offsetBottom = mappedBottom.map(p => [p[0] + sign * perpX * lineGap, p[1] + sign * perpY * lineGap]);
|
|
|
+
|
|
|
+ let points;
|
|
|
+ if (item.isBlue) {
|
|
|
+ // 蓝波:底线外侧(远离顶线)
|
|
|
+ const sign = dot > 0 ? -1 : 1;
|
|
|
+ points = mappedBottom.map(p => [p[0] + sign * perpX * lineGap, p[1] + sign * perpY * lineGap]);
|
|
|
+ } else {
|
|
|
+ // 绿波:顶线外侧(远离底线)
|
|
|
+ const sign = dot > 0 ? 1 : -1;
|
|
|
+ points = mappedTop.map(p => [p[0] + sign * perpX * lineGap, p[1] + sign * perpY * lineGap]);
|
|
|
+ }
|
|
|
return [
|
|
|
- { type: 'polyline', shape: { points: offsetBottom }, style: { stroke: item.lineCol, lineDash: [4, 4], lineWidth: 1 } }
|
|
|
+ { type: 'polyline', shape: { points }, style: { stroke: item.lineCol, lineDash: [4, 4], lineWidth: 1 } }
|
|
|
];
|
|
|
})(),
|
|
|
- { type: 'text', x: speedX, y: speedY, rotation: angle, style: { text: `${this.speedKmh}km/h`, fill: '#fff', fontSize: this.fs(11), textAlign: 'center' } },
|
|
|
+ { type: 'text', x: speedX, y: speedY, rotation: speedAngle, style: { text: `${item.speed}km/h`, fill: '#fff', fontSize: this.fs(11), textAlign: 'center' } },
|
|
|
...(() => {
|
|
|
const cxBw = (midBottomX + midTopX) / 2;
|
|
|
const cyBw = (midBottomY + midTopY) / 2;
|