npm.io
1.0.2 • Published 5d ago

road-traffic-viewer

Licence
MIT
Version
1.0.2
Deps
0
Size
126 kB
Vulns
0
Weekly
17

RoadTrafficViewer — 车道车流可视化组件

基于 Canvas 2D 的道路俯视图车流可视化组件,支持 Vue 3 / Vue 2 / 纯 JavaScript 三种使用方式。

仿照大屏车流可视化工具类的道路俯视图渲染逻辑,在此基础上扩展了多车道路段流量染色SVG 图片渲染鼠标拖拽平移桩号灵活输入全配置化颜色等能力。


组件介绍

RoadTrafficViewer 是一款专为交通态势大屏设计的 Canvas 2D 道路可视化组件,能够在网页中实时渲染道路俯视图,直观展示多车道车辆的行驶状态和路段拥堵程度。

核心能力

  • 多车道渲染:支持上下行独立配置多车道(1-8车道),车道间白色虚线分隔,中央隔离带配合双黄线
  • 车辆图片渲染:车辆仅支持图片渲染(SVG / PNG / JPEG / GIF / WebP 等),不再使用 Canvas 默认绘制。车辆图片通过 carSvgUrl 全局配置,或每台车单独指定
  • 路段流量染色:根据交通状态(畅通/缓行/拥堵/严重拥堵)对道路区间进行颜色渲染
  • 灵活桩号系统:支持 K112k112.500K112+500112000 等多种输入格式
  • 全配置化样式:道路底色、路肩、隔离带、双黄线、车道线、文字颜色等全部可自定义
  • 鼠标拖拽平移:当视口小于路线总长时,支持鼠标拖拽平移路线
  • 高性能渲染:requestAnimationFrame 单帧调度,轻松支撑 1000+ 台车辆实时渲染
适用场景
  • 高速公路/城市道路态势监控大屏
  • 交通指挥中心实时可视化系统
  • 车路协同仿真演示平台
  • 物流运输轨迹监控系统

目录


安装

通过 npm、yarn 或 pnpm 安装组件包:

# 使用 npm 安装
npm install road-traffic-viewer

# 使用 yarn 安装
yarn add road-traffic-viewer

# 使用 pnpm 安装
pnpm add road-traffic-viewer

特性概览

特性 说明
灵活桩号 支持 "K112" / "k112.500" / "K112+500" / 112000 等多种输入格式,整公里显示 K112,非整公里显示 K112+500
多车道 上下行独立配置车道数,车道间白色虚线分隔,车辆 lane 字段定位到具体车道
全配置化颜色 道路底色(rgba)、路肩、隔离带、双黄线、车道线、文字等全部可自定义
路段流量染色 根据 TrafficFlow 数据对道路区间按 0:畅通/1:缓行/2:拥堵/3:严重拥堵 染色,颜色可配置
图片渲染 车辆必须通过 carSvgUrl 提供图片(SVG/PNG/JPEG/GIF/WebP),单台车可独立指定
SVG 图片 车辆和道路底图均支持图片 URL / data URI / 原始 SVG 字符串
鼠标拖拽 当视口小于路线总长时,鼠标拖拽平移路线(grab/grabbing 光标)
比例尺 scale = 视口可见米数,值越小放得越大,车辆移动越明显
行驶方向 上行 (direction=-1) 桩号小→大 车头朝右,下行 (direction=1) 桩号大→小 车头朝左
RAF 节流 requestAnimationFrame 单帧调度,轻松支撑 1000+ 台车辆
灵活尺寸 宽高支持 px 数值和百分比字符串
标签可控 showCarSpeed / showCarId 控制是否显示速度/ID 标签,speed 字段可选
框架无关 核心渲染类可在纯 JS 中直接 new 使用,同步提供 Vue 3 / Vue 2 组件

快速开始

Vue 3 使用方式
<template>
  <div style="width: 100%; height: 200px;">
    <RoadTrafficViewer
      ref="viewerRef"
      :config="config"
      :vehicles="vehicles"
      :traffic-flows="trafficFlows"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RoadTrafficViewer } from 'road-traffic-viewer'
import type { RoadTrafficConfig, Vehicle, TrafficFlow, RoadTrafficViewerExposed } from 'road-traffic-viewer'

// 1. 配置
const config: RoadTrafficConfig = {
  startPile: 'K112',
  endPile: 'K122',
  scale: 2000,             // 视口显示 2000 米路线
  lanesDown: 2,            // 下行 2 车道
  lanesUp: 2,              // 上行 2 车道
  segmentIntervalMeters: 1000,
  paddingStartMeters: 50,
  paddingEndMeters: 50,
  carSvgUrl: '/car-icon.svg',  // 车辆图片(必传)
}

// 2. 车辆数据(direction: 1=下行, -1=上行)
const vehicles = ref<Vehicle[]>([
  {
    id: 'V001',
    position: 'K115+200',  // K115+200 = 115200
    speed: 80,
    direction: 1,          // 下行(隔离带下方,自右向左行驶)
    carColor: '#00ff88',
    lane: 2,
  },
  {
    id: 'V002',
    position: 116500,      // 直接传米数
    speed: 30,
    direction: -1,         // 上行(隔离带上方,自左向右行驶)
    carColor: '#ff4444',
    lane: 1,
  },
])

// 3. 路段流量数据
const trafficFlows = ref<TrafficFlow[]>([
  { startPile: 'K115', endPile: 'K117', direction: 1, status: 3 },   // 下行严重拥堵
  { startPile: 'K118', endPile: 'K120', direction: -1, status: 1 },  // 上行缓行
])

const viewerRef = ref<RoadTrafficViewerExposed | null>(null)

onMounted(() => {
  console.log('核心实例就绪:', viewerRef.value?.core)
})
</script>
Vue 2 使用方式
<template>
  <div style="width: 100%; height: 200px;">
    <RoadTrafficViewerV2
      ref="viewer"
      :config="config"
      :vehicles="vehicles"
      :traffic-flows="trafficFlows"
      @ready="onReady"
    />
  </div>
</template>

<script>
import { RoadTrafficViewerV2 } from 'road-traffic-viewer'

export default {
  components: { RoadTrafficViewerV2 },
  data() {
    return {
      config: {
        startPile: 'K0',
        endPile: 'K20',
        scale: 5000,
        lanesDown: 2,
        lanesUp: 2,
        carSvgUrl: '/car-icon.svg',
      },
      vehicles: [
        { id: 1, position: 'K5', speed: 80, direction: 1 },
        { id: 2, position: 'K15', speed: 60, direction: -1, carColor: '#ffaa00' },
      ],
      trafficFlows: [
        { startPile: 'K8', endPile: 'K12', direction: 1, status: 2 },
      ],
    }
  },
  methods: {
    onReady(core) {
      console.log('核心实例就绪:', core)
    },
  },
}
</script>
纯 JS 使用方式
<div id="road-container" style="width: 100%; height: 200px;"></div>
<script type="module">
  import { RoadTrafficCore } from 'road-traffic-viewer'

  const container = document.getElementById('road-container')
  const core = new RoadTrafficCore(container, {
    startPile: 'K112',
    endPile: 'K122',
    scale: 5000,
    lanesDown: 2,
    lanesUp: 2,
    carSvgUrl: '/car-icon.svg',
  })

  core.render()

  core.updateVehicles([
    { id: 'V1', position: 'K115', speed: 80, direction: 1, lane: 1 },
    { id: 'V2', position: 'K118', speed: 45, direction: -1, lane: 2 },
  ])

  core.updateTrafficFlows([
    { startPile: 'K115', endPile: 'K117', direction: 1, status: 3 },
  ])

  window.addEventListener('resize', () => core.render())
</script>

桩号系统

输入格式(自由)
输入 含义 解析结果(米)
"K112" K112 112000
"k112.500" K112+500 112500
"K112+500" K112+500 112500
112000 纯米数 112000
"112000" 数字字符串 112000
"k0.123" K0+123 123
显示格式

内部统一以为单位存储和计算,展示时整公里省略 +000

formatPile(112000)  // => "K112"
formatPile(112500)  // => "K112+500"
formatPile(123)     // => "K0+123"
工具函数
import { parsePile, formatPile, formatPileShort } from 'road-traffic-viewer'

parsePile('K112+500')   // => 112500 (米)
formatPile(112000)      // => "K112"(整公里省略 +000
formatPile(112500)      // => "K112+500"
formatPileShort(112500) // => "K112"(始终只显示公里数)

配置项详解

以下列出 RoadTrafficConfig全部可配置属性,含类型、默认值和详细注释。

路线与桩号
配置项 类型 默认值 说明
startPile number | string 必填 路线起点桩号。支持 "K112" / "K112+500" / 112000 等格式
endPile number | string 必填 路线终点桩号。格式同 startPile
paddingStartMeters number 0 起点外侧额外空白米数,防止起点桩号标记被遮挡
paddingEndMeters number 0 终点外侧额外空白米数,防止终点桩号标记被遮挡
segmentIntervalMeters number 1000 桩号标尺分段间隔(米)。默认 1000 米一段
比例尺与尺寸
配置项 类型 默认值 说明
scale number 1000 显示比例尺:画布视口内可见的路线米数。值越小看得越细(放大)。若 scale < 路线总长,支持鼠标拖拽平移
canvasWidth number | string "100%" 画布宽度。支持 px 数值或百分比字符串
canvasHeight number | string 自动计算 画布高度。不传则根据车道数自动计算。支持 px 数值或百分比字符串

比例尺参考(scale = 视口可见米数):

scale 视口可见 10km 路线能否全览 适用场景
500 500 米 需拖拽 放大细览,车辆清晰可见
2000 2 公里 需拖拽 中览,适合主视图
5000 5 公里 需拖拽 粗览
10000 10 公里 全览 总览,刚好显示整条路线
20000 20 公里 全览 超粗览
方向定义
配置项 类型 默认值 说明
directionDown number | string 1 下行(桩号由大变小,隔离带下方)对应的 direction 值。可自定义映射
directionUp number | string -1 上行(桩号由小变大,隔离带上方)对应的 direction 值。可自定义映射
swapDirections boolean false 是否颠倒上下行视觉位置。true 时隔离带上方为下行、下方为上行
车道布局
配置项 类型 默认值 说明
lanesUp number 1 上行车道数量(隔离带上方,桩号小→大方向)。传 0 则不显示
lanesDown number 1 下行车道数量(隔离带下方,桩号大→小方向)。传 0 则不显示
laneHeight number 28 单条车道高度(像素)。影响整个道路区域的总高度
laneLineColor string "#ffffff" 车道间间隔虚线颜色。默认白色
roadPadding number 3 路肩高度(像素),车道外侧到道路边缘的间距
medianHeight number 6 中央隔离带高度(像素),上下行车道之间的分隔带高度

车道编号规则:

  • 车道编号从 1 开始,1 号车道 = 最靠近中央隔离带的车道
  • 编号越大,离中央隔离带越远
  • 车辆的 lane 字段默认值为 1
     ┌──────────────────────────────────────┐
     │          路肩 (roadPadding)            │
     ├──────────────────────────────────────┤
     │  上行 车道3 (离隔离带最远)             │  lane: 3
     │  - - - - - - - - - - - - - - - - -   │  ← 虚线 (laneLineColor)
     │  上行 车道2                            │  lane: 2
     │  - - - - - - - - - - - - - - - - -   │  ← 虚线
     │  上行 车道1 (离隔离带最近)             │  lane: 1
     │          direction=-1  桩号小→大 →→→   │  (车头朝右)
     ├══════════════════════════════════════┤
     │  ═══ 隔离带 (medianHeight) ════════════ │  + 双黄线 (medianLineColor)
     ├══════════════════════════════════════┤
     │  下行 车道1 (离隔离带最近)             │  lane: 1
     │          ←←← direction=1  桩号大→小    │  (车头朝左)
     │  - - - - - - - - - - - - - - - - -   │  ← 虚线
     │  下行 车道2                            │  lane: 2
     │  - - - - - - - - - - - - - - - - -   │  ← 虚线
     │  下行 车道3 (离隔离带最远)             │  lane: 3
     ├──────────────────────────────────────┤
     │          路肩 (roadPadding)            │
     └──────────────────────────────────────┘
道路颜色
配置项 类型 默认值 说明
roadBg string "#141a28" 道路主底色。支持 rgba 透明度,如 "rgba(20,26,40,0.8)"
roadBgImage string 道路底图 SVG(URL / data URI / 原始 SVG)。传入后替代 roadBg
roadShoulder string "#1e2a3e" 路肩区域颜色
roadLine string "#2a3a55" 路边线颜色(最外侧边界线)
roadEdge string "#3a5070" 道路边缘刻度/标尺线颜色
medianBg string "#1a2535" 中央隔离带背景填充色
medianLineColor string "#ffb020" 中央隔离带双黄线颜色。默认金黄色
canvasBg string "#0a0f1a" 画布整体背景色(道路区域之外的填充色)
markerColor string "#3a5070" 桩号标记文字和刻度线的颜色
laneLabelColor string "#2a3a55" 车道标签文字("上行"/"下行")的颜色
车辆渲染
配置项 类型 默认值 说明
defaultCarColor string "#00ff88" 车辆标签颜色(数据中无 carColor 时使用)
carLength number 16 车辆图标长度(像素)
carWidth number 12 车辆图标宽度(像素)
carSvgUrl string 必传 车辆图片地址。支持所有图片类型(SVG/PNG/JPEG/GIF/WebP),URL / data URI / 原始 SVG 字符串。车辆必须通过此字段或单车 carSvgUrl 提供图片
showCarSpeed boolean true 是否在车身上方显示速度标签(km/h)
showCarId boolean true 是否在车身下方显示车辆 ID 标签
路段流量状态颜色
配置项 类型 默认值 说明
trafficStatusColors { key: number; color: string }[] 见下方 拥堵状态 → 路面颜色映射。key 为状态值,color 为 CSS 颜色

默认映射:

[
  { key: 3, color: '#ff4d6a' },  // 严重拥堵 → 红色
  { key: 2, color: '#ff9500' },  // 拥堵     → 橙色
  { key: 1, color: '#00bfff' },  // 缓行     → 青色
  { key: 0, color: '#00ff88' },  // 畅通     → 绿色
]

数据格式

车辆数据 Vehicle
interface Vehicle {
  /** 车辆唯一 ID */
  id: string | number
  /** 车辆所在桩号 */
  position: number | string
  /** 速度 km/h(可选),不传则不显示速度标签 */
  speed?: number
  /** 行驶方向:1=下行, -1=上行 */
  direction: number
  /** 车辆颜色(可选) */
  carColor?: string
  /** 所在车道编号(可选),从 1 开始,默认 1 */
  lane?: number
  /** 车辆图片地址(必传),优先级高于全局 carSvgUrl。支持所有图片类型 */
  carSvgUrl?: string
}
路段流量数据 TrafficFlow
interface TrafficFlow {
  /** 流量状态起点桩号 */
  startPile: number | string
  /** 流量状态终点桩号 */
  endPile: number | string
  /** 方向:1=下行, -1=上行 */
  direction: number
  /** 拥堵状态:0=畅通 / 1=缓行 / 2=拥堵 / 3=严重拥堵 */
  status: number | string
}

API 方法

RoadTrafficCore(核心类)
class RoadTrafficCore {
  constructor(container: HTMLElement, config: RoadTrafficConfig)

  /** 重绘道路(窗口 resize 后调用) */
  render(): void

  /** 更新车辆数据(RAF 节流) */
  updateVehicles(vehicles: Vehicle[]): void

  /** 更新路段流量数据 */
  updateTrafficFlows(flows: TrafficFlow[]): void

  /** 同时更新车辆和流量 */
  updateAll(vehicles: Vehicle[], flows: TrafficFlow[]): void

  /** 部分更新配置并重绘 */
  updateConfig(partial: Partial<RoadTrafficConfig>): void

  /** 获取 Canvas 元素 */
  getCanvas(): HTMLCanvasElement

  /** 获取渲染度量参数 */
  getMetrics(): RenderMetrics | null

  /** 销毁实例 */
  destroy(): void
}
RoadTrafficViewer(Vue 3 组件)
Prop 类型 说明
config RoadTrafficConfig 渲染配置(必填)
vehicles Vehicle[] 车辆数据
trafficFlows TrafficFlow[] 路段流量数据
事件 参数 说明
ready core: RoadTrafficCore 渲染引擎就绪时触发
暴露 类型 说明
core RoadTrafficCore | null 核心渲染实例
containerRef HTMLDivElement | null 容器 DOM 引用
RoadTrafficViewerV2(Vue 2 组件)
Prop 类型 说明
config Object 渲染配置(必填)
vehicles Array 车辆数据
trafficFlows Array 路段流量数据
事件 参数 说明
ready core 渲染引擎就绪时触发

使用案例

案例 1:基础双向单车道
<template>
  <div style="width: 100%; height: 180px;">
    <RoadTrafficViewer ref="viewerRef" :config="config" :vehicles="vehicles" />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RoadTrafficViewer } from 'road-traffic-viewer'
import type { RoadTrafficConfig, Vehicle, RoadTrafficViewerExposed } from 'road-traffic-viewer'

const config: RoadTrafficConfig = {
  startPile: 'K100',
  endPile: 'K120',
  scale: 2000, // 视口显示 2000 米路线
  carSvgUrl: '/car-icon.svg',
}

// direction: 1=下行(隔离带下方), -1=上行(隔离带上方)
const vehicles = ref<Vehicle[]>([
  { id: 1, position: 'K105', speed: 80, direction: 1 },   // 下行
  { id: 2, position: 'K108', speed: 60, direction: 1, carColor: '#ffaa00' },
  { id: 3, position: 'K110', speed: 90, direction: -1 },  // 上行
])

const viewerRef = ref<RoadTrafficViewerExposed | null>(null)
onMounted(() => { console.log(viewerRef.value?.core) })
</script>
案例 2:双向 3 车道 + 流量染色
<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K50', endPile: 'K70', scale: 3000,
  lanesDown: 3, lanesUp: 3,
  segmentIntervalMeters: 500,
  paddingStartMeters: 100, paddingEndMeters: 100,
  carSvgUrl: '/car-icon.svg',
}

const vehicles = ref<Vehicle[]>([
  { id: 'A1', position: 'K52', speed: 75, direction: 1, lane: 1 },
  { id: 'A2', position: 'K53', speed: 70, direction: 1, lane: 2 },
  { id: 'B1', position: 'K60', speed: 85, direction: -1, lane: 1 },
  { id: 'B2', position: 'K61', speed: 80, direction: -1, lane: 2 },
])

const trafficFlows = ref<TrafficFlow[]>([
  { startPile: 'K55', endPile: 'K58', direction: 1, status: 3 },
  { startPile: 'K60', endPile: 'K63', direction: -1, status: 1 },
])
</script>
案例 3:自定义颜色 + SVG 车辆图片
<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K0', endPile: 'K10', scale: 5000,
  // 亮色道路主题 + 透明度
  roadBg: 'rgba(232,232,232,0.9)',
  roadShoulder: '#cccccc',
  medianBg: '#dddddd',
  medianLineColor: '#ffaa00',
  canvasBg: '#ffffff',
  // SVG 车辆图片
  carSvgUrl: '<svg viewBox="0 0 1024 1024" ...>...</svg>',
  carLength: 24, carWidth: 18,
  // 自定义流量颜色
  trafficStatusColors: [
    { key: 3, color: '#cc0000' },
    { key: 2, color: '#ee8800' },
    { key: 1, color: '#eebb00' },
    { key: 0, color: '#00aa44' },
  ],
}
</script>
案例 4:纯 JS 环境 + 定时器
<div id="road" style="width: 1200px; height: 200px;"></div>
<script type="module">
  import { RoadTrafficCore, parsePile } from 'road-traffic-viewer'

  const core = new RoadTrafficCore(document.getElementById('road'), {
    startPile: 'K0', endPile: 'K20', scale: 5000,
    lanesDown: 2, lanesUp: 2,
    carSvgUrl: '/car-icon.svg',
  })
  core.render()

  setInterval(() => {
    core.updateVehicles(
      Array.from({ length: 200 }, (_, i) => ({
        id: i,
        position: Math.random() * 20000,
        speed: Math.random() * 120,
        direction: Math.random() > 0.5 ? 1 : -1,
        lane: Math.ceil(Math.random() * 2),
        carColor: ['#00ff88', '#ffaa00', '#ff4444'][Math.floor(Math.random() * 3)],
      }))
    )
  }, 1000)
</script>
案例 5:单车道 + WebSocket 实时推送
<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K0', endPile: 'K50', scale: 3000,
  lanesDown: 1, lanesUp: 0, // 仅下行单车道
  segmentIntervalMeters: 2000,
  paddingStartMeters: 80, paddingEndMeters: 80,
  carSvgUrl: '/car-icon.svg',
}

const vehicles = ref<Vehicle[]>([])
const trafficFlows = ref<TrafficFlow[]>([])

let ws: WebSocket | null = null

onMounted(() => {
  ws = new WebSocket('wss://your-server.com/traffic')
  ws.onmessage = (e) => {
    const raw = JSON.parse(e.data)
    vehicles.value = raw.vehicles || []
    if (raw.flows) trafficFlows.value = raw.flows
  }
})

onUnmounted(() => { ws?.close() })
</script>
案例 6:双向多车道 + WebSocket 实时推送

高速公路场景:上下行各 3 车道,WebSocket 高频推送 500+ 台车辆,含心跳保活和预过滤。

<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K100', endPile: 'K160', scale: 5000,
  lanesDown: 3, lanesUp: 3, laneHeight: 24,
  paddingStartMeters: 100, paddingEndMeters: 100,
  carSvgUrl: '/car-icon.svg',
}

const vehicles = ref<Vehicle[]>([])
const trafficFlows = ref<TrafficFlow[]>([])
let ws: WebSocket | null = null
let heartbeatTimer: ReturnType<typeof setInterval> | null = null

onMounted(() => {
  ws = new WebSocket('wss://highway.example.com/ws')
  ws.onopen = () => {
    heartbeatTimer = setInterval(() => ws?.send(JSON.stringify({ type: 'ping' })), 30000)
  }
  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data)
    switch (msg.type) {
      case 'vehicles':
        vehicles.value = msg.data.filter((v: Vehicle) => {
          const pos = typeof v.position === 'number' ? v.position : parsePile(v.position)
          return pos >= 100000 - 200 && pos <= 160000 + 200
        })
        break
      case 'traffic_flow':
        trafficFlows.value = msg.data
        break
    }
  }
})

onUnmounted(() => {
  if (heartbeatTimer) clearInterval(heartbeatTimer)
  ws?.close()
})
</script>
案例 7:道路底图 SVG + 车辆 SVG + rgba 透明度
<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K0', endPile: 'K30', scale: 5000,
  lanesDown: 2, lanesUp: 2,
  // 道路底图
  roadBgImage: '<svg viewBox="0 0 1024 200" xmlns="...">...</svg>',
  // 半透明底色兜底(SVG 加载前显示)
  roadBg: 'rgba(20, 26, 40, 0.6)',
  // SVG 车辆
  carSvgUrl: 'https://example.com/car-icon.svg',
  carLength: 20, carWidth: 16,
}
</script>
案例 8:隐藏标签 + speed 为空

不显示速度和 ID 标签,适合纯图标展示场景。

<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K0', endPile: 'K50', scale: 5000,
  lanesDown: 2, lanesUp: 2,
  showCarSpeed: false,  // 隐藏速度标签
  showCarId: false,     // 隐藏 ID 标签
}

const vehicles = ref<Vehicle[]>([
  { id: 1, position: 'K10', direction: 1, carSvgUrl: '<svg ...>...</svg>' },
  { id: 2, position: 'K20', direction: -1, carSvgUrl: '<svg ...>...</svg>' }, // speed 为空,不显示速度标签
])
</script>
案例 9:定时器模拟行驶 — 全部配置展示(深色主题)

setInterval 模拟两辆车在上下行车道来回行驶,展示全部 30+ 配置项含默认值注释。

<template>
  <div style="width: 100%; height: 220px;">
    <RoadTrafficViewer ref="viewer" :config="config" :vehicles="vehicles" :traffic-flows="flows" />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { RoadTrafficViewer, parsePile } from 'road-traffic-viewer'
import type { RoadTrafficConfig, Vehicle, TrafficFlow } from 'road-traffic-viewer'

const config: RoadTrafficConfig = {
  // ---- 路线与桩号 ----
  startPile: 'K100',              // 起点桩号,K100 = 100000
  endPile: 'K120',                // 终点桩号,K120 = 120000
  paddingStartMeters: 60,         // 起点空白米数,默认 0
  paddingEndMeters: 60,           // 终点空白米数,默认 0
  segmentIntervalMeters: 1000,    // 分段间隔(米),默认 1000

  // ---- 比例尺与尺寸 ----
  scale: 5000,                    // 视口可见 5000 米,默认 1000
  canvasWidth: '100%',            // 画布宽度,默认 "100%"
  // canvasHeight 不传,自动计算

  // ---- 方向定义 ----
  directionDown: 1,               // 下行方向值,默认 1
  directionUp: -1,                // 上行方向值,默认 -1
  swapDirections: false,          // 颠倒上下行视觉位置,默认 false

  // ---- 车道布局 ----
  lanesDown: 1,                   // 下行车道数,默认 1
  lanesUp: 1,                     // 上行车道数,默认 1
  laneHeight: 28,                 // 单车道高度(px),默认 28
  laneLineColor: '#ffffff',       // 车道虚线颜色,默认白色
  roadPadding: 3,                 // 路肩高度(px),默认 3
  medianHeight: 6,                // 隔离带高度(px),默认 6

  // ---- 道路颜色 ----
  roadBg: '#141a28',              // 道路底色(支持 rgba),默认深蓝灰
  roadBgImage: undefined,         // 道路底图 SVG(可选)
  roadShoulder: '#1e2a3e',        // 路肩颜色
  roadLine: '#2a3a55',            // 路边线颜色
  roadEdge: '#3a5070',            // 道路边缘颜色
  medianBg: '#1a2535',            // 隔离带背景色
  medianLineColor: '#ffb020',     // 双黄线颜色
  canvasBg: '#0a0f1a',            // 画布背景色
  markerColor: '#3a5070',         // 桩号文字颜色
  laneLabelColor: '#2a3a55',      // 车道标签颜色

  // ---- 车辆渲染 ----
  defaultCarColor: '#00ff88',     // 默认车辆标签颜色
  carLength: 16,                  // 车辆长度(px),默认 16
  carWidth: 12,                   // 车辆宽度(px),默认 12
  carSvgUrl: '/car-icon.svg',     // 车辆图片(必传)

  // ---- 标签显示 ----
  showCarSpeed: true,             // 显示速度标签,默认 true
  showCarId: true,                // 显示 ID 标签,默认 true

  // ---- 流量状态颜色 ----
  trafficStatusColors: [
    { key: 3, color: '#ff4d6a' }, // 严重拥堵→红
    { key: 2, color: '#ff9500' }, // 拥堵→橙
    { key: 1, color: '#00bfff' }, // 缓行→青
    { key: 0, color: '#00ff88' }, // 畅通→绿
  ],
}

// 路段流量
const flows = ref<TrafficFlow[]>([
  { startPile: 'K105', endPile: 'K108', direction: 1, status: 2 },
  { startPile: 'K115', endPile: 'K118', direction: -1, status: 1 },
])

// 两台车
const vehicles = ref<Vehicle[]>([
  { id: 'CAR-DOWN', position: 'K102', speed: 65, direction: 1, lane: 1 },
  { id: 'CAR-UP',   position: 'K118', speed: 55, direction: -1, lane: 1 },
])

// 定时模拟移动
const R_MIN = parsePile(config.startPile), R_MAX = parsePile(config.endPile)
let timer: ReturnType<typeof setInterval> | null = null
onMounted(() => {
  timer = setInterval(() => {
    vehicles.value = vehicles.value.map(v => {
      const pos = parsePile(v.position)
      const step = v.speed! / 18
      let newPos = v.direction === -1 ? pos + step : pos - step
      if (newPos >= R_MAX || newPos <= R_MIN) {
        return { ...v, position: newPos >= R_MAX ? R_MAX : R_MIN, direction: -v.direction as (1 | -1) }
      }
      return { ...v, position: newPos }
    })
  }, 200)
})
onUnmounted(() => { if (timer) clearInterval(timer) })
</script>
案例 10:定时器多车 + 亮色主题 — 全部配置展示

双向各 2 车道,4 辆车在不同车道行驶,到达终点后折返+切换车道。

<script setup lang="ts">
const config: RoadTrafficConfig = {
  startPile: 'K30', endPile: 'K48', scale: 4000,
  paddingStartMeters: 80, paddingEndMeters: 80,
  segmentIntervalMeters: 2000,
  directionDown: 1, directionUp: -1,

  lanesDown: 2, lanesUp: 2,
  laneHeight: 24, laneLineColor: '#cccccc',
  roadPadding: 4, medianHeight: 8,

  // 亮色主题
  roadBg: '#e0e0e0', roadShoulder: '#bdbdbd',
  roadLine: '#9e9e9e', roadEdge: '#757575',
  medianBg: '#d0d0d0', medianLineColor: '#ff8f00',
  canvasBg: '#f5f5f5', markerColor: '#616161',
  laneLabelColor: '#9e9e9e',

  defaultCarColor: '#1976d2',
  carLength: 18, carWidth: 14,
  carSvgUrl: '/car-icon.svg',
  showCarSpeed: true, showCarId: true,

  trafficStatusColors: [
    { key: 3, color: '#d32f2f' },
    { key: 2, color: '#f57c00' },
    { key: 1, color: '#fbc02d' },
    { key: 0, color: '#388e3c' },
  ],
}

const vehicles = ref<Vehicle[]>([
  { id: 'D1', position: 'K31+200', speed: 80, direction: 1, carColor: '#e91e63', lane: 1 },
  { id: 'D2', position: 'K35+800', speed: 50, direction: 1, carColor: '#2196f3', lane: 2 },
  { id: 'U1', position: 'K46+500', speed: 70, direction: -1, carColor: '#4caf50', lane: 1 },
  { id: 'U2', position: 'K40+100', speed: 45, direction: -1, carColor: '#ff9800', lane: 2 },
])

const flows = ref<TrafficFlow[]>([
  { startPile: 'K33', endPile: 'K36', direction: 1, status: 3 },
  { startPile: 'K42', endPile: 'K45', direction: -1, status: 2 },
])
</script>
案例 11:Vue 2 使用方式
<template>
  <div style="width: 100%; height: 200px;">
    <RoadTrafficViewerV2
      :config="config"
      :vehicles="vehicles"
      :traffic-flows="trafficFlows"
      @ready="onReady"
    />
  </div>
</template>

<script>
import { RoadTrafficViewerV2 } from 'road-traffic-viewer'

export default {
  components: { RoadTrafficViewerV2 },
  data() {
    return {
      config: {
        startPile: 'K0', endPile: 'K20', scale: 5000,
        lanesDown: 2, lanesUp: 2,
        carSvgUrl: '/car-icon.svg',
      },
      vehicles: [
        { id: 1, position: 'K5', speed: 60, direction: 1 },
        { id: 2, position: 'K15', speed: 80, direction: -1 },
      ],
      trafficFlows: [],
    }
  },
  methods: {
    onReady(core) {
      console.log('核心实例就绪:', core)
      // 可保存 core 引用用于后续手动操作
      this.core = core
    },
  },
}
</script>

性能说明

节流机制
  • 车辆渲染使用 requestAnimationFrame 单帧调度
  • 高频调用 updateVehicles() 时数据缓存,仅在下一帧执行一次渲染
  • 一帧内多次调用只触发一次实际绘制
车辆数量建议
车辆数 性能 建议
< 100 毫无压力 无需特殊处理
100–1000 流畅 默认模式即可
1000–3000 基本流畅 建议预过滤超出视口的车辆
> 3000 可能掉帧 强烈建议预过滤
优化提示
  1. 大数组使用不可变替换vehicles.value = [...newData] 而非 push/splice
  2. 预过滤:只保留视口范围内 ± 缓冲的车辆
  3. 合理设置 scale:scale 越小视口越窄,需渲染的桩号标记越少

文件结构

src/
  types.ts                  — TypeScript 类型定义
  pile-utils.ts             — 桩号解析/格式化工具
  road-traffic-core.ts      — 核心 Canvas 渲染类(框架无关,~1400 行)
  RoadTrafficViewer.vue     — Vue 3 组件封装
  RoadTrafficViewerV2.vue   — Vue 2 组件封装
  index.ts                  — 统一导出入口
README.md                   — 本文档

Keywords