在水利工程 AI 运行管理平台中,利用 Cesium 实现设备资产的三维模拟与可视化查看,是构建"数字孪生"大屏的核心环节。
下面我将为你提供一套完整的 Vue 3 + Cesium 实现方案,涵盖:场景初始化、设备资产(点位/3D模型)渲染、状态动态模拟(正常/告警)、以及点击交互查看详情。
一、 环境准备
在 Vue 3 项目中安装 Cesium 及其 Vite 插件(如果你用的是 Webpack,请使用 copy-webpack-plugin)。
bash
npm install cesium vite-plugin-cesium
修改 vite.config.ts 以正确加载 Cesium 的静态资源(Workers, Assets, Widgets):
typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cesium from 'vite-plugin-cesium'
export default defineConfig({
plugins: [vue(), cesium()],
})
二、 核心代码实现
我们将创建一个完整的组件 WaterProjectViewer.vue,它包含地图渲染、设备加载和交互逻辑。
WaterProjectViewer.vue
vue
<template>
<div class="cesium-container">
<!-- Cesium 地图容器 -->
<div ref="cesiumContainer" class="map-viewer"></div>
<!-- 设备详情信息面板 (点击设备后显示) -->
<transition name="slide-fade">
<div v-if="selectedDevice" class="info-panel">
<div class="panel-header">
<h3>{{ selectedDevice.name }}</h3>
<button @click="closePanel" class="close-btn">×</button>
</div>
<div class="panel-body">
<div class="info-item">
<span class="label">设备类型:</span>
<span class="value">{{ getDeviceTypeName(selectedDevice.type) }}</span>
</div>
<div class="info-item">
<span class="label">运行状态:</span>
<span :class="['status-badge', selectedDevice.status === 'normal' ? 'status-normal' : 'status-alert']">
{{ selectedDevice.status === 'normal' ? '正常运行' : '异常告警' }}
</span>
</div>
<div class="info-item">
<span class="label">实时数据:</span>
<span class="value highlight">{{ selectedDevice.realtimeData }}</span>
</div>
<div class="info-item">
<span class="label">经纬度:</span>
<span class="value">{{ selectedDevice.longitude }}, {{ selectedDevice.latitude }}</span>
</div>
<!-- 模拟 AI 预测数据展示 -->
<div class="ai-section" v-if="selectedDevice.aiPrediction">
<h4>🤖 AI 智能分析</h4>
<p>{{ selectedDevice.aiPrediction }}</p>
</div>
<button class="action-btn" @click="viewCamera(selectedDevice)">查看实时监控</button>
</div>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as Cesium from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'
// 1. 初始化 Cesium Token (请替换为你自己的 Cesium Ion Token)
Cesium.Ion.defaultAccessToken = 'YOUR_CESIUM_ION_TOKEN_HERE'
const cesiumContainer = ref<HTMLElement | null>(null)
let viewer: Cesium.Viewer | null = null
let handler: Cesium.ScreenSpaceEventHandler | null = null
// 选中的设备状态
const selectedDevice = ref<any>(null)
// 模拟从 Django 后端获取的设备资产数据
// 实际项目中应通过 axios.get('/api/devices/') 获取
const mockDevices = [
{
id: 'dev_001',
name: '1号水库水位计',
type: 'water_level',
longitude: 116.397,
latitude: 39.908,
height: 50, // 海拔高度或相对高度
status: 'normal',
realtimeData: '当前水位: 45.2m (警戒线: 48.0m)',
aiPrediction: 'AI预测未来2小时水位平稳,无超标风险。'
},
{
id: 'dev_002',
name: '溢洪道监控摄像头',
type: 'camera',
longitude: 116.399,
latitude: 39.910,
height: 55,
status: 'alert', // 告警状态
realtimeData: 'AI识别: 检测到人员靠近危险区域',
aiPrediction: '已触发三级告警,建议立即广播驱离。'
},
{
id: 'dev_003',
name: '2号泵站流量计',
type: 'flow_meter',
longitude: 116.395,
latitude: 39.906,
height: 48,
status: 'normal',
realtimeData: '瞬时流量: 12.5 m³/s',
aiPrediction: '设备运行效率 98%,状态良好。'
}
]
// 设备类型映射
const getDeviceTypeName = (type: string) => {
const map: Record<string, string> = {
water_level: '水位监测计',
camera: 'AI 视频监控',
flow_meter: '流量监测计',
rain_gauge: '雨量计'
}
return map[type] || '未知设备'
}
// 2. 初始化 Cesium 场景
const initCesium = () => {
if (!cesiumContainer.value) return
viewer = new Cesium.Viewer(cesiumContainer.value, {
animation: false, // 隐藏动画控件
timeline: false, // 隐藏时间轴
baseLayerPicker: false,// 隐藏底图选择器
geocoder: false, // 隐藏地名查找
homeButton: false, // 隐藏Home按钮
sceneModePicker: false,// 隐藏2D/3D切换
navigationHelpButton: false,
infoBox: false, // 禁用默认的 InfoBox,使用我们自定义的 Vue 面板
selectionIndicator: false, // 禁用默认的选中框
terrainProvider: Cesium.createWorldTerrain() // 加载全球地形
})
// 隐藏 Cesium logo (仅供开发测试,生产环境请遵守 Cesium 许可协议)
viewer.cesiumWidget.creditContainer.style.display = 'none'
// 飞行定位到水利工程所在区域
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.397, 39.908, 2000), // 经度, 纬度, 高度(米)
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-45), // 俯视角度
roll: 0.0
},
duration: 2 // 飞行时间(秒)
})
// (可选) 如果你有真实的 3D Tiles 模型 (如倾斜摄影、BIM模型),取消下方注释加载:
// const tileset = await Cesium.Cesium3DTileset.fromUrl('YOUR_3D_TILES_URL')
// viewer.scene.primitives.add(tileset)
loadDeviceAssets()
setupInteraction()
}
// 3. 加载设备资产 (使用 Entity API)
const loadDeviceAssets = () => {
if (!viewer) return
mockDevices.forEach(device => {
// 根据设备状态决定颜色
const color = device.status === 'normal' ? Cesium.Color.LIME : Cesium.Color.RED
const iconUrl = device.type === 'camera'
? 'https://cdn-icons-png.flaticon.com/512/2907/2907111.png' // 摄像头图标示例
: 'https://cdn-icons-png.flaticon.com/512/2917/2917995.png' // 传感器图标示例
const entity = viewer.entities.add({
id: device.id,
name: device.name,
position: Cesium.Cartesian3.fromDegrees(device.longitude, device.latitude, device.height),
// 使用 Billboard (广告牌/图标) 表示设备
billboard: {
image: iconUrl,
width: 40,
height: 40,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐到坐标点
disableDepthTestDistance: Number.POSITIVE_INFINITY, // 始终显示在最上层,不被地形遮挡
},
// 添加标签
label: {
text: device.name,
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.TOP,
pixelOffset: new Cesium.Cartesian2(0, 10)
},
// 如果是告警状态,添加一个动态脉冲光圈效果 (模拟)
...(device.status === 'alert' ? {
ellipse: {
semiMinorAxis: 30.0,
semiMajorAxis: 30.0,
height: device.height,
material: new Cesium.ColorMaterialProperty(color.withAlpha(0.3)),
outline: true,
outlineColor: color,
outlineWidth: 2
}
} : {})
})
})
}
// 4. 设置交互事件 (点击设备)
const setupInteraction = () => {
if (!viewer) return
handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((click: any) => {
// 拾取点击位置的实体
const pickedObject = viewer.scene.pick(click.position)
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entityId = pickedObject.id.id
// 从数据源中找到对应的设备信息
const device = mockDevices.find(d => d.id === entityId)
if (device) {
selectedDevice.value = device
// 相机平滑飞向选中的设备
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(device.longitude, device.latitude, 500),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-30),
roll: 0.0
},
duration: 1.5
})
}
} else {
// 点击空白处关闭面板
closePanel()
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
const closePanel = () => {
selectedDevice.value = null
if (viewer) {
// 取消选中状态
viewer.selectedEntity = undefined
}
}
const viewCamera = (device: any) => {
alert(`正在调取设备 [${device.name}] 的实时 RTSP 视频流...\n(此处可对接 Vue 视频播放组件如 flv.js 或 WebRTC)`);
}
onMounted(() => {
initCesium()
})
onUnmounted(() => {
if (handler) {
handler.destroy()
}
if (viewer) {
viewer.destroy()
}
})
</script>
<style scoped>
.cesium-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.map-viewer {
width: 100%;
height: 100%;
}
/* 自定义信息面板样式 */
.info-panel {
position: absolute;
top: 20px;
right: 20px;
width: 320px;
background: rgba(15, 23, 42, 0.85); /* 深色半透明背景,适合大屏 */
backdrop-filter: blur(10px);
border: 1px solid rgba(56, 189, 248, 0.3);
border-radius: 8px;
color: #fff;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
z-index: 100;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.panel-header h3 {
margin: 0;
font-size: 16px;
color: #38bdf8;
}
.close-btn {
background: none;
border: none;
color: #fff;
font-size: 24px;
cursor: pointer;
line-height: 1;
}
.panel-body {
padding: 15px;
}
.info-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.info-item .label {
color: #94a3b8;
width: 80px;
flex-shrink: 0;
}
.info-item .value {
color: #e2e8f0;
flex: 1;
}
.info-item .value.highlight {
color: #38bdf8;
font-weight: bold;
}
.status-badge {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-normal {
background: rgba(34, 197, 94, 0.2);
color: #4ade80;
border: 1px solid #4ade80;
}
.status-alert {
background: rgba(239, 68, 68, 0.2);
color: #f87171;
border: 1px solid #f87171;
animation: pulse 1.5s infinite;
}
.ai-section {
margin-top: 15px;
padding: 10px;
background: rgba(56, 189, 248, 0.1);
border-left: 3px solid #38bdf8;
border-radius: 4px;
}
.ai-section h4 {
margin: 0 0 8px 0;
font-size: 14px;
color: #38bdf8;
}
.ai-section p {
margin: 0;
font-size: 13px;
color: #cbd5e1;
line-height: 1.5;
}
.action-btn {
width: 100%;
margin-top: 15px;
padding: 10px;
background: #0ea5e9;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: background 0.3s;
}
.action-btn:hover {
background: #0284c7;
}
/* 动画效果 */
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
.slide-fade-enter-active, .slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-enter-from, .slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
三、 进阶优化与真实生产环境建议
-
替换为真实的 3D 资产 (3D Tiles) :
上面的代码使用了 2D 图标 (
Billboard) 来模拟设备。在真实的数字孪生平台中,你应该:- 使用 Blender 或 SketchUp 建立水闸、泵站、水位计的精细 3D 模型。
- 导出为 glTF/glb 格式,Cesium 可以直接通过
Cesium.Model.fromGltfAsync加载单个设备模型。 - 对于整个水利枢纽,使用无人机倾斜摄影生成 3D Tiles ,通过
Cesium.Cesium3DTileset.fromUrl加载作为底图,设备作为 Entity 叠加在上面。
-
动态数据驱动 (WebSocket) :
目前的
mockDevices是静态的。在生产环境中,应建立 WebSocket 连接:javascriptconst ws = new WebSocket('ws://your-django-backend/ws/device-status/') ws.onmessage = (event) => { const data = JSON.parse(event.data) // 根据 data.device_id 找到对应的 Entity const entity = viewer.entities.getById(data.device_id) if (entity) { // 动态更新状态和颜色 if (data.status === 'alert') { entity.billboard.color = Cesium.Color.RED // 动态添加光圈 entity.ellipse = { /* ... */ } } } } -
性能优化 (Primitive API) :
如果你的水利工程有 成百上千个 传感器设备,使用
Entity API会导致性能下降。此时应改用底层的Primitive API或Cesium.PointPrimitiveCollection来批量渲染点位,可轻松支撑 10 万+ 点位的流畅渲染。 -
与 YOLO 告警联动 :
当 Django 接收到前文提到的 YOLO 视频告警 Webhook 时,可以通过 WebSocket 推送给前端。前端收到后,不仅弹出系统通知,还可以让 Cesium 相机自动飞行 (flyTo) 到该告警摄像头的位置,并高亮闪烁,实现真正的"AI 驱动三维联动"。