☰
Cesium + EPANET 水力仿真对接|管线压力云图|水锤瞬态动画
2026/11/6 1:55:49 网站建设 项目流程

简介:供水孪生高阶能力,后端基于 EPANET 做稳态 / 瞬态(水锤)水力计算,前端 Cesium 接收时序结果,实现:管线压力云图着色、流速着色、水锤冲击波传播动画、压力超限告警;采用分段 Polyline + 纹理映射 / 色带插值实现连续云图效果,时序帧驱动动态刷新。
前置:Vue3 + Cesium;后端:Python EPANET(epanet-python/epanet-toolkit)输出时序 JSON 结果;EPANET 负责管网建模、水力平衡、水锤瞬态计算,前端只做可视化渲染,不做水力求解。

⚠️ 重要边界说明
EPANET 原生是稳态水力;水锤(瞬态)一般用 EPANET-MSX / WNTR / EPANET2.2 瞬态扩展或者第三方水锤求解器(HAMMER、KYPIPE)。下面方案是前后端分离标准架构:后端批量跑仿真,输出时间步结果集,前端时序播放。
不把 EPANET 编译到浏览器,前端不做水力计算。

整体架构

  1. 后端:管网 inp 文件 → EPANET 求解 → 按时间步输出 JSON:每根管段 ID、该时刻压力 / 流速、节点压力;
  2. 接口:GET /sim/result?simId=xxx返回{ timeStep: 0, pipes: [{pid, pStart, pEnd, vStart, vEnd}] };
  3. 前端:拿到时序帧,对管线做两端压力插值着色,播放时间序列,实现水锤冲击波移动动画;
  4. 可视化方案:管线采用PolylineMaterialProperty自定义 Shader,传入起点、终点压力,内部线性插值生成连续压力色带(管线压力云)。
<template> <div class="cesium-page"> <div ref="cesiumRef" id="cesiumContainer"></div> <div class="control-panel"> <div>EPANET水锤仿真控制台</div> <button @click="loadEpanetResult">加载仿真时序结果</button> <button @click="playSim">播放水锤动画</button> <button @click="pauseSim">暂停</button> <button @click="resetSim">重置仿真</button> <div>时间步:{{ currentStep }} / {{ maxStep }}</div> <div style="margin-top:6px;color:#99ee99">{{ tipText }}</div> </div> <!-- 图例面板 --> <div class="legend"> <div class="leg-title">压力(MPa)</div> <div class="leg-bar"></div> <div class="leg-range">0.1&nbsp;&nbsp;&nbsp;&nbsp;0.3&nbsp;&nbsp;&nbsp;&nbsp;0.5</div> </div> </div> </template> <script setup> import { onMounted, onBeforeUnmount, ref, watch } from 'vue' import * as Cesium from 'cesium' import 'cesium/Build/Cesium/Widgets/widgets.css' const cesiumRef = ref(null) let viewer = null const tipText = ref('') const currentStep = ref(0) const maxStep = ref(0) let timer = null let simData = [] let pipeEntities = {} let pressureShaderMaterial = null // 压力值域映射 const P_MIN = 0.1 const P_MAX = 0.5 window.CESIUM_BASE_URL = '/cesium/' onMounted(() => { initCesium() }) onBeforeUnmount(() => { clearInterval(timer) if (viewer) viewer.destroy() }) function initCesium() { viewer = new Cesium.Viewer('cesiumContainer', { animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, fullscreenButton: false, infoBox: false, terrainProvider: Cesium.createWorldTerrain() }) viewer.creditDisplay.container.style.display = 'none' viewer.scene.globe.depthTestAgainstTerrain = true viewer.scene.postProcessStages.fxaa.enabled = true registerPressureGradientMaterial() } /** * 注册管线压力渐变云图材质 * uniforms: p0 起点压力,p1 终点压力,pMin/pMax值域 * Shader内部沿着管线st.s做线性插值,映射颜色 */ function registerPressureGradientMaterial() { pressureShaderMaterial = new Cesium.Material({ fabric: { type: 'PipePressureGrad', uniforms: { p0: 0.2, p1: 0.2, pMin: P_MIN, pMax: P_MAX }, source: ` czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material mat = czm_initMaterial(); float t = materialInput.st.s; float pVal = mix(p0, p1, t); float norm = clamp((pVal - pMin)/(pMax - pMin), 0.0, 1.0); // 色标:蓝(低压)→绿→红(高压/超压告警) vec3 lowCol = vec3(0.0,0.4,0.9); vec3 midCol = vec3(0.1,0.8,0.3); vec3 highCol = vec3(0.9,0.15,0.15); vec3 col; if(norm < 0.5){ col = mix(lowCol, midCol, norm*2.0); }else{ col = mix(midCol, highCol, (norm - 0.5)*2.0); } mat.diffuse = col; // 超压高亮闪烁 if(pVal > pMax){ float flash = sin(czm_frameNumber * 0.12) * 0.35 + 0.65; mat.alpha = flash; }else{ mat.alpha = 0.72; } return mat; } ` } }) } /** 模拟后端接口返回EPANET时序仿真结果 * simData结构:[{step:0, pipes:[{pid, p0, p1, positions}]}] */ async function loadEpanetResult() { // 这里替换为 axios 请求后端接口:/sim/runEpanet // 后端读取inp、调用WNTR/EPANET求解瞬态,输出每时间步管段两端压力 simData = mockEpanetTransientResult() maxStep.value = simData.length - 1 currentStep.value = 0 // 渲染初始管线 renderStep(simData[0]) tipText.value = `✅ 加载水锤仿真,总步数:${maxStep.value}` } // 模拟水锤时序数据:关闭阀门后压力震荡(水锤升压-负压交替) function mockEpanetTransientResult() { const steps = 40 const res = [] const pipePos = Cesium.Cartesian3.fromDegreesArrayHeights([ 108.940, 34.270, 419.5, 108.943, 34.271, 420.2, 108.946, 34.273, 421.0, 108.949, 34.275, 420.6, 108.952, 34.277, 420.1 ]) for(let s = 0; s < steps; s++){ // 水锤震荡正弦模拟:压力周期性升降 const wave = Math.sin(s * 0.32) const p0 = 0.3 + wave * 0.18 const p1 = 0.3 + wave * 0.14 res.push({ step: s, pipes: [ { pid: 'P1', p0, p1, positions: pipePos } ] }) } return res } /** 根据一个时间步更新所有管线压力着色 */ function renderStep(frame) { frame.pipes.forEach(item => { let ent = pipeEntities[item.pid] if(!ent){ const mat = pressureShaderMaterial.clone() mat.uniforms.p0 = item.p0 mat.uniforms.p1 = item.p1 ent = viewer.entities.add({ polyline: { positions: item.positions, width:7, material: mat }, _pid: item.pid }) pipeEntities[item.pid] = ent }else{ const m = ent.polyline.material.getValue(Cesium.JulianDate.now()) m.uniforms.p0 = item.p0 m.uniforms.p1 = item.p1 } }) } function playSim(){ clearInterval(timer) timer = setInterval(()=>{ if(currentStep.value >= maxStep.value){ pauseSim() return } currentStep.value += 1 const f = simData[currentStep.value] renderStep(f) }, 180) tipText.value = '▶ 水锤仿真播放中' } function pauseSim(){ clearInterval(timer) tipText.value = '⏸ 仿真暂停' } function resetSim(){ pauseSim() currentStep.value = 0 if(simData.length) renderStep(simData[0]) tipText.value = '🔄 仿真重置' } </script> <style scoped> .cesium-page { width:100%; height:100vh; position:relative; } #cesiumContainer { width:100%; height:100%; margin:0; padding:0; } .control-panel { position:absolute; top:12px; left:12px; z-index:999; background:rgba(0,20,40,0.75); padding:10px; border-radius:6px; color:#fff; } .control-panel button { margin:4px; padding:4px 8px; cursor:pointer; } .legend { position:absolute; bottom:16px; left:12px; background:rgba(0,15,30,0.85); padding:8px; border-radius:4px; color:#fff; } .leg-title { margin-bottom:4px; font-size:13px; } .leg-bar { height:10px; width:140px; background:linear-gradient(90deg,#0066dd,#22bb44,#dd2222); } .leg-range { display:flex; justify-content:space-between; font-size:12px; margin-top:2px; } </style>

核心能力拆解

1. EPANET 后端对接方案

常用 Python 栈:

  • wntr(推荐,原生支持 EPANET inp,支持瞬态、水锤、泄漏、故障模拟)
  • epanet-python封装 EPANET Toolkit
  • 流程:
    1. 前端上传 / 传入管网拓扑(节点、管线、阀门),后端生成.inp
    2. WNTR 加载 inp,设置工况:阀门关闭时刻、泵启停、水锤边界
    3. 运行瞬态仿真,按时间步导出:每个管段两端压力、流速;节点压力
    4. 输出 JSON 时序包返回前端;大仿真可以分片流式返回

EPANET 原版只做稳态;水锤(瞬态)优先 WNTR,专业水锤软件 Bentley HAMMER 输出结果也可以转成该 JSON 格式接入这套前端。

2. 管线压力云贴图(渐变着色)

不是整根管线单一颜色,而是起点压力、终点压力插值渐变,Shader 沿管线 uv 做线性插值,形成连续压力云;

  • 低压:蓝色
  • 正常:绿色
  • 超压:红色,叠加闪烁告警
  • 同样逻辑可以改成流速云、余氯云、水质云

3. 水锤瞬态动画原理

水锤本质是压力冲击波在管道来回反射:关阀瞬间升压,然后负压,来回震荡衰减。
后端求解器输出每个时刻的压力场,前端逐帧更新材质 uniformp0/p1,视觉上就是压力波沿着管线传播、来回振荡的效果。

4. 配套业务联动

  • 超压 / 负压自动告警、高亮闪烁;
  • 水锤冲击峰值时刻自动截图、记录工况;
  • 联动关阀方案:仿真对比不同关阀曲线,找到最小水锤冲击方案;
  • 水质仿真(余氯、污染物扩散)复用同一套材质云图逻辑。

工程进阶优化方向

  1. 管线多采样点高精度云图
    上面是两端插值;长管线、冲击波陡峭时,后端输出管线中间多点压力,前端把管线切分成多段短 polyline,每段独立渐变,冲击波更锐利。
  2. 节点压力球体云图
    节点处用 Billboard / 球面 Primitive,半径、颜色映射节点压力。
  3. 剖面联动水锤时序
    下方 ECharts 同步绘制压力时程曲线:选中管段,Y 压力、X 时间,动画播放时曲线游标同步移动,这是水锤报告标配。
  4. 仿真结果对比面板
    工况 A(快关阀)、工况 B(缓关阀)压力曲线并排对比,辅助调度决策。
  5. 导出仿真报告 PDF:工况参数、压力包络图、最大升压、负压值。
  6. 水力等值面、区域压力面渲染(高级)
    管网密集区做插值面,面云图展示片区压力分布。

踩坑清单

  1. ❌ 水锤动画卡顿
    ✅ 不要每帧 new Material;复用材质实例,只更新 uniform;管线数量多就减少仿真帧率、后端降采样时间步;
  2. ❌ EPANET 稳态和瞬态混淆
    ✅ EPANET2 稳态;水锤必须用 WNTR / HAMMER;普通 EPANET 不能算水锤;
  3. ❌ 管线渐变颜色反了
    ✅ 调换 p0、p1;或者 Shader 内1.0 - t;
  4. ❌ 压力单位混淆(米水头 vs MPa)
    ✅ EPANET 输出水头(m),业务需要转 MPa:100m水头≈1MPa,前后端统一单位;
  5. ❌ 大仿真数据量巨大
    ✅ 后端做降采样,或者 WebSocket 流式推送时序帧,一次性不返回全部结果。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询