香港数字化交通vue3和threejs写的真优雅!

预览截图

内容介绍

预览演示

数字孪生智慧交通数据可视化是一种利用数字孪生技术来模拟、分析和展示城市交通系统的工具。它通过整合来自各种传感器、监控设备以及交通管理系统中的实时数据,创建一个与现实世界交通状况相对应的虚拟模型。这种可视化不仅能够帮助城市规划者、交通管理部门更好地理解当前的交通状态,还能预测未来趋势,从而支持更有效的决策制定。






主要组成部分
数据采集层:包括摄像头、雷达、GPS设备、车辆传感器等硬件设施,用于收集道路、车辆及行人等多方面的数据。
数据分析层:运用大数据处理技术、机器学习算法对原始数据进行清洗、过滤、聚合和分析,提取有价值的信息。
数字孪生模型:基于地理信息系统(GIS)技术和三维建模技术构建的城市交通系统虚拟副本。该模型可以反映实际交通环境,并且随着新数据的输入不断更新其状态。
可视化平台:提供用户界面,以直观的方式呈现交通流量、速度、拥堵情况等信息。通常采用图表、地图、动态效果等形式展现数据,便于非专业人员理解。
应用服务层:根据不同的需求提供定制化的解决方案,如智能导航、事故预警、优化信号灯配时等功能。
实现步骤
建立基础模型:首先需要构建城市的静态三维模型,这包括道路网络、建筑物以及其他基础设施。
集成实时数据:将来自不同源头的数据流整合进系统中,确保模型能及时反映实际情况的变化。
开发可视化界面:设计易于使用的图形用户界面,使得管理人员可以通过桌面或移动设备随时随地访问关键性能指标(KPIs)。
实施高级功能:比如使用AI算法进行流量预测、异常检测等,进一步提升系统的智能化水平。

技术栈

高德地图API three.js vue3

代码解析

<script setup>
import { getMap, initMap, getNavRoute } from '@/utils/mainMap2.js'
import { fetchMockData } from '@/utils/mock.js'
import GLlayer from '#/gl-layers/lib/index.mjs'
import * as THREE from 'three' 
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
// import * as dat from 'dat.gui'
import {wgs84togcj02, gcj02towgs84} from '../utils/lngLat.js'

const { 
  FlowlineLayer,
  PathLayer,
  DrivingLayer,
  TilesLayer,
  LayerManager
} = GLlayer

// 高德可视化类
let loca

// 容器
const container = ref(null)

// 图层管理
const layerManger = new LayerManager()

// 是否第一人称
let isFirstView = false

// 交通事件类型图标
var ACCIDENT_ICONS = {
    201: './static/icons/accident/traffic-control.png',
    202: './static/icons/accident/jam.png',
    203: './static/icons/accident/construction.png',
    204: './static/icons/accident/close.png',
    205: './static/icons/accident/fog.png',
    0: './static/icons/accident/accident.png',
};

const allLayers = [ 
  { id: 'navPathLayer', name: '规划路线图层', visible: true },
  { id: 'accidentLayer', name: '交通事件', visible: false },
  { id: 'stopLayer', name: '公共交通', visible: false },
  { id: 'cameraLayer', name: '交通摄像机', visible: false },
  { id: 'trafficLayer', name: '交通情况', visible: false },
  { id: 'drivingLayer', name: '车辆行驶', visible: true },
  { id: 'buildingLayer', name: '建筑图层', visible: true },
  { id: 'wxLayer', name: '影像底层', visible: false },
]

const SETTING = {
  // 地图中心点
  center: [114.214033,22.318893], 
  // 各图层是否独立存在, 为true时, 图层之间不会相互影响
  alone: false, 
}

let startMarker
let endMarker
// 缓存所有主干道路数据
let routeData
// 缓存公交站点数据
let busStopData

// 缓存规划路径数据
let pathData

// 地图数据提示浮窗
var normalMarker
var infoWindow

onMounted(async () => {
  await init() 
  initDragPoints()
  await initLayers()
  animateFn()
  // initGUI()
})

//销毁前清除所有图层
onBeforeUnmount(() => {
  layerManger.clear()
})

var wxLayer

// 初始化地图
async function init() {

  const map = await initMap({
    viewMode: '3D',
    dom: container.value,
    showBuildingBlock: false,
    center: SETTING.center,
    zoom: 15.5,
    pitch: 42.0,
    rotation: 4.9,
    mapStyle: 'amap://styles/light',
    skyColor: '#c8edff'
  })

  // 添加卫星地图
  // const satelliteLayer = new AMap.TileLayer.Satellite();
  // map.add([satelliteLayer]);

  map.on('zoomend', (e) => {
    console.log(map.getZoom())
  })
  map.on('click', (e) => {
    const { lng, lat } = e.lnglat
    console.log([lng, lat])

    if (normalMarker) {
      map.remove(normalMarker)
    }

    // 定位3dtiles位置
    // layerManger.findLayerById('buildingLayer').setCenter([lng, lat])    
  })

  loca = new Loca.Container({
    map,
  });

  normalMarker = new AMap.Marker({
    offset: [70, -15],
    zooms: [1, 22]
  });

  infoWindow = new AMap.InfoWindow({
    // offset: new AMap.Pixel(-15, -30),
    isCustom: true,
    closeWhenClickMap: true
  });

  document.addEventListener('keydown', function (event) {
    var keyCode = event.keyCode;
    // 判断是否按下了数字键1、2、3
    if (keyCode === 49) { 
      switchTopic('product')
    } else if (keyCode === 50) {
      switchTopic('security')
    } else if (keyCode === 51) {
      switchTopic('value')
    }
  });
}

window.getMapView = function () {
  const center = mainMap.getCenter()
  const zoom = mainMap.getZoom()
  const pitch = mainMap.getPitch()
  const rotation = mainMap.getRotation()

  const res = {
    center,
    zoom,
    pitch,
    rotation
  }
  console.log(res)
  return res
}
window.setMapView = function ({ center, zoom, pitch, rotation }) {
  mainMap.setCenter(center)
  mainMap.setZoom(zoom)
  mainMap.setPitch(pitch)
  mainMap.setRotation(rotation)
}

async function initLayers() {
  initWxLayer()
  await initBuildingLayer()
  await initVehicleLayer()
  await initCameraLayer()
  await initTrafficLayer()
  await initBusStopLayer()
  await initAccidentLayer()

  // 导航路线图层
  await initNavPathLayer()

  // 有个bug,在生成规划路径前先移动一下, 会导致共享数据的图层错位
  // PathLayer 似乎会影响customCoords.lngLatsToCoords
  await generateRoute()
}

//在地图上创建两个可拖动的点
function initDragPoints(){
  const map = getMap()

  startMarker = new AMap.Marker({
    position: [114.20415, 22.323125],
    draggable: true,
    icon: new AMap.Icon({
      size: new AMap.Size(20, 60),
      image: `./static/icons/ico-point1.svg`,
    }),
    label:{
      content: '起点',
      direction: 'top-center',
    },
    anchor: 'bottom-center' // 设置锚点
  })

  endMarker = new AMap.Marker({
    position: [114.212801, 22.316262], // 稍微调整第二个点的位置,避免重叠
    draggable: true,
    icon: new AMap.Icon({
      size: new AMap.Size(20, 60),
      image: `./static/icons/ico-point2.svg`,
    }),
    label:{
      content: '终点',
      direction: 'top',
    },
    anchor: 'bottom-center' // 设置锚点
  })
  map.add([startMarker, endMarker])


}

/**
 * 初始化卫星影像图层
 * 天地图
 */
 async function initWxLayer() {
    // 天地图, 企业认证
    const layer = new AMap.TileLayer.WMTS({
        url: 'https://t4.tianditu.gov.cn/img_w/wmts',
        blend: false,
        tileSize: 256,
        params: {
            Layer: 'img',
            Version: '1.0.0',
            Format: 'tiles',
            TileMatrixSet: 'w',
            STYLE: 'default',
            // 免费申请 TOKEN
            // https://console.tianditu.gov.cn/api/key
            tk: 'bb0abf278c1e848823bd136f7d11ca58'            
        },
        visible: true
    });

    layer.setMap(getMap());

    layer.id = 'wxLayer'
    layerManger.add(layer);

    wxLayer = layer
}

async function initNavPathLayer() {
  const map = getMap()
  const pathLayer = new PathLayer({
      id: 'navPathLayer',
      map,
      data: {features: []},
      styles: {
        0: { lineWidth: 2, color: '#00FF00', label: '畅通' },
        1: { lineWidth: 2, color: '#FFFF00', label: '缓行' },
        2: { lineWidth: 2, color: '#FFA500', label: '拥堵' },
        3: { lineWidth: 2, color: '#FF0000', label: '严重拥堵' },
        4: { lineWidth: 2, color: '#ffffff', label: '未知' }
      },
      getStyle: (feature) => {
        return feature.properties.status
      },
      altitude: 5, // 设置高度,避免与地面重叠
      zooms: [3, 22],
      interact: true,
      alone: SETTING.alone
    })

    // 添加到图层管理器
    layerManger.add(pathLayer) 
}

// 添加交通事件图层初始化函数
async function initAccidentLayer() {
  const map = getMap()

  // 获取交通事件数据
  const { data } = await fetchMockData('hk-accident.json')

  // 转换数据为GeoJSON格式
  const features = data.map(item => {
    const { brief,x,y,eventDesc,eventType,lines,startTime, roadName} = item
    return {
      type: 'Feature',
      properties: {        
        brief,
        eventDesc,
        eventType,
        lines,
        startTime,
        roadName
      },
      geometry: {
        type: 'Point',
        coordinates: [x, y]      // 直接使用几何坐标
      }
    } 
  })

  const geo = new Loca.GeoJSONSource({
    data: {
      type: 'FeatureCollection',
      features
    }
  })

  const layer = new Loca.IconLayer({
    zooms: [10, 20],
    zIndex: 300,
    opacity: 1,
    visible: getLayerInitVisible('accidentLayer'),
  })

  layer.setSource(geo)
  layer.setStyle({
    icon: (index, feature) => {
      const {eventType} = feature.properties
      return ACCIDENT_ICONS[eventType] || ACCIDENT_ICONS[0]
    },
    iconSize: [30, 30],
    anchor: 'center',
    // 文本配置
    label: {
      content: (index, feat) => feat.properties.description,
      direction: 'top',
      style: {
        fontSize: 12,
        fillColor: '#fff',
        strokeColor: '#000',
        strokeWidth: 2
      }
    }
  })

  map.on('click', (e) => {
    const feat = layer.queryFeature(e.pixel.toArray());
    if (feat) {
      const {brief, eventType, lines, startTime, roadName} = feat.properties
      infoWindow.setContent(
        `<div class="amap-info-window">
          <p>road: ${roadName}</p>
          <p>startTime: ${startTime}</p> 
          <p>desc: ${brief}</p>          
        </div>`
      )
      infoWindow.setOffset(new AMap.Pixel(0, -30));
      infoWindow.open(map, e.lnglat);
    }
  });

  loca.add(layer)
  layer.id = 'accidentLayer'
  layerManger.add(layer)

}

/**
 * 初始化公交站点图层
 */
async function initBusStopLayer(){
  const map = getMap()
  const data1 = await fetchMockData('hk-bus-stops.geojson')
  const data2 = await fetchMockData('hk-gmb-stops.geojson')
  const data3 = await fetchMockData('hk-tram-stops.geojson')

  data1.features = data1.features.map((item) => {
    item.properties.type = 'bus'
    return item
  })
  data2.features = data2.features.map((item) => {
    item.properties.type = 'gmb'
    return item
  })
  data3.features = data3.features.map((item) => {
    item.properties.type = 'tram'
    return item 
  })
  busStopData = new Loca.GeoJSONSource({ 
    data: {
      type: 'FeatureCollection',
      features: data1.features
                .concat(data2.features) 
                .concat(data3.features) 
    } 
  });

  const layer = new Loca.IconLayer({
    zooms: [10, 22],
    zIndex: 300,
    opacity: 1,
    visible: getLayerInitVisible('stopLayer'),
  })

  layer.setSource(busStopData)
  layer.setStyle({
    icon:(index, feature)=>{
      return `./static/icons/transit/${feature.properties.type}.png`
    },
    // unit : 'meter',
    iconSize: [20, 20],
    anchor: 'center'
  })

  loca.add(layer)
  layer.id = 'stopLayer'
  layerManger.add(layer)

  // 点击事件
  map.on('click', async (e) => {
    const feat = layer.queryFeature(e.pixel.toArray());
    if (feat) {
      // 更换为服务端接口
      const res = await fetchMockData('hk-gmb-stops-detail.json')

      const busStopList = res.data.bus_stop.map(item => {
        return `<div class="bus-line">
          <p>路线: ${item.orig_sc} → ${item.dest_sc}</p>
          <p>下一站: ${item.next_station_sc} ${item.eta}分钟</p>
        </div>`
      }).join('')

      infoWindow.setContent(
        `<div class="amap-info-window">
          <div class="bus-list">
          ${busStopList}  
          </div>
        </div>`
      )
      infoWindow.setOffset(new AMap.Pixel(0, -30));
      infoWindow.open(map, e.lnglat);
      
    } 
  })
}

/**
 * 过滤公交站点图层
 * @param type 
 */
function filterStopLayer(type){
  // const map = getMap()
  const layer = layerManger.findLayerById('stopLayer')
  const featues = busStopData.options.data.features 
  // 根据type筛选features
  const filteredFeatures = type === 'all' 
    ? featues
    : featues.filter(feature => feature.properties.type === type)

  // 更新图层数据
  layer.setSource(new Loca.GeoJSONSource({
    data: {
      type: 'FeatureCollection',
      features: filteredFeatures
    }
  }))
}

function getLayerInitVisible(layerId) {
  return allLayers.find(layer => layer.id === layerId)?.visible ?? true
}

运行

npm install
npm run dev

点赞(1) 打赏

立即下载

温馨提示! 此资源需支付 ¥15.00 后下载
购买成功后,如无法下载请联系微信: code2985

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部