香港数字化交通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
本站所有资料默认均用于学习和研究,禁止用于商业用途。如有需要请联系站长确认,同时支持定制开发。
站长亲测,如有付费仅用于服务器维护及运营开支,敬请理解。
转载请注明出处: 2985 » 香港数字化交通vue3和threejs写的真优雅!

发表评论 取消回复