基于 Cesium 的真实地形二维水动力模拟:从 DEM、洪水过程线到降雨产流的 Web 端实时实现

最近完成了一个基于 Cesium + JavaScript + Web Worker + GPU Shader 的二维水动力实时模拟原型,并且已经在真实地形场景中完整跑通。

这个项目最开始只是希望在 Cesium 三维场景中实现类似洪水演进的动态效果,但随着功能逐步完善,最终已经从最初的“模拟峡谷 + 固定上游来水”,扩展到了:

真实地形选取 → DEM 高程采样 → 二维水动力网格 → 多入流/多出流 → Q(t) 洪水过程线 → P(t) 降雨过程线 → 空间分区降雨 → 入渗/产流 → 坡面汇流 → 河道洪水 → GPU 实时水面渲染。

目前同时接入了 天地图影像与中文注记,用于真实地理场景展示。

本文记录一下整个系统的技术路线、关键实现以及可以进一步落地的应用场景。


一、为什么要做这个系统?

传统 WebGIS 中的洪水展示,大量项目采用的是:

  • 已计算好的淹没面加载;

  • GeoJSON 动态播放;

  • 热力图;

  • 水面 Polygon 高程变化;

  • 预生成洪水动画。

这类方法非常适合“结果展示”,但有一个明显的问题:

地图本身并没有真正进行水动力计算。

如果改变上游流量、降雨、地形或者边界条件,通常需要重新调用后端模型计算,然后再把结果传回前端。

我这次想尝试的是另一条路线:

能不能直接在浏览器中,让真实 DEM、降雨和边界条件参与二维水动力计算,然后实时把计算结果映射到 Cesium 三维场景?

因此整个系统的核心目标并不是单纯“把水做得好看”,而是建立一条完整的数据链。

真实地图
   ↓
Cesium World Terrain
   ↓
用户框选计算区域
   ↓
DEM 高程采样
   ↓
二维水动力网格
   ↓
Q(t) / P(t) / 入渗产流
   ↓
Web Worker 求解
   ↓
h / u / v
   ↓
GPU Shader
   ↓
Cesium 三维洪水动态显示

二、真实地形:从模拟峡谷切换到实际 DEM

早期版本为了验证水动力和渲染流程,地形是通过程序生成的峡谷 DEM。

这种方式适合算法调试,但很快就遇到了问题:

模拟峡谷能够说明程序“能跑”,却不能说明它能够用于真实区域。

因此后续版本将地形源改成了真实 DEM。

现在的工作流程是:

  1. 加载 Cesium 三维场景;

  2. 用户在地图中框选目标区域;

  3. 得到框选区域的经纬度 Bounds;

  4. 在区域内部建立规则采样点;

  5. 从 Cesium Terrain 获取真实高程;

  6. 对 DEM 进行必要预处理;

  7. 重采样成二维水动力求解网格;

  8. 根据新的 DEM 初始化水动力模型。

这一步很重要,因为从这里开始:

水流方向不再由程序预设的“峡谷形状”决定,而真正受到当地地形高程控制。

为了提高实际使用体验,DEM 获取也采用了分批采样,而不是一次请求几十万个高程点。


三、天地图 + Cesium World Terrain

系统中现在实际上使用了两套不同的数据源:

天地图
→ 负责影像底图与中文地名注记

Cesium World Terrain
→ 负责真实地形高程

天地图接入:

  • img_w:卫星影像;

  • cia_w:影像中文注记。

这样做的好处是:

视觉上使用国内更熟悉的影像与地名数据,而二维水动力需要的高程仍然独立来自 Terrain。

天地图 Key 和 Cesium Ion Token 都没有硬编码到源码中,而是通过界面配置。


四、真正参与计算的二维水动力模型

水动力部分没有运行在 Cesium Shader 内,而是在独立的:

solver.worker.js

中执行。

使用 Web Worker 的主要原因是:

如果把二维网格数值计算直接放在浏览器主线程,Cesium 相机操作、UI 和动画很容易出现明显卡顿。

因此整个程序分为两个线程。

主线程
Cesium / UI / GPU Rendering
        ↑
        │ h,u,v
        ↓
Web Worker
2D Hydrodynamic Solver

目前求解器采用的是 local-inertial / shallow-water approximation 思路。

主要计算量包括:

  • 地形高程 z

  • 水深 h

  • X、Y 方向面流量;

  • 水面高程 z + h

  • 重力坡降;

  • Manning 阻力;

  • 自适应时间步长;

  • Wet/Dry 湿干处理;

  • 边界入流;

  • 自由出流;

  • 降雨有效产流;

  • 质量守恒统计;

  • 二维速度 u / v 恢复。

因此屏幕上看到的洪水不是一张沿河道移动的纹理。

每一个时刻的水深和速度都是根据当前 DEM、边界和降雨重新计算出来的。


五、从固定“北进南出”到任意河流方向

真实地形接入后,很快又出现了一个问题。

早期模型默认:

北侧 → 入流
南侧 → 出流

这对于程序生成的南北向峡谷没有问题。

但是到了真实区域,一条河可能是:

西 → 东
东 → 西
南 → 北
西 → 南
多个支流 → 一个主河道

因此后续将边界系统完全重构。

现在可以直接在地图上:

点击添加入流边界

以及:

点击添加出流边界

程序会自动将鼠标位置吸附到距离最近的计算域边界。

每一个边界都保存:

边界方向
+
沿边界的位置
+
作用宽度

没有配置的边界默认保持封闭。

因此模拟区域不再要求特定朝向。


六、多个入流和多个出流

为了模拟真实流域,仅有一个入口同样不够。

例如:

       支流 A
          ↓
支流 B → 主河道 → 出口 1
          ↓
       支流 C
          ↓
        出口 2

因此当前系统支持:

inlets[]
outlets[]

每个入流口可以单独设置:

流量 Q
边界宽度
位置

多个入流会同时参与水动力计算。

这意味着已经可以模拟:

  • 两条支流汇流;

  • 多条山沟同时来水;

  • 主河道来水 + 支流来水;

  • 多出口排洪;

  • 分洪口。


七、Q(t):从固定流量升级为洪水过程线

真实洪水显然不会一直保持固定流量。

典型洪水过程通常经历:

基础流量
   ↓
快速上涨
   ↓
洪峰
   ↓
逐渐退水

因此系统继续加入了 Q(t) 流量过程线

每个入口都可以独立导入 CSV:

time_s,flow_m3s
0,5
300,12
600,35
900,80
1200,120
1500,95
1800,60
2400,25
3000,10
3600,0

每一条支流都可以拥有不同的洪水过程。

例如:

支流 A → Q1(t)
支流 B → Q2(t)
支流 C → 固定 15 m³/s

过程线节点之间采用线性插值。

更重要的是,在一个数值时间步跨越过程线节点时,并不是简单读取某一个节点的 Q,而是对:

[t, t + dt]

范围内的分段线性过程线进行积分。

因此能够更准确地保持累计入流体积。


八、从“上游给水”进一步升级到“天空下雨”

只有 Q(t),本质上仍然是在告诉模型:

河流入口现在来了多少水。

但真实的山洪、流域洪水和城市内涝还有另一条非常重要的数据链:

降雨
 ↓
入渗
 ↓
产流
 ↓
坡面汇流
 ↓
河道汇流
 ↓
洪峰

因此 V8 增加了 P(t) 降雨过程线

CSV 格式类似:

time_s,rain_mm_h
0,0
300,12
600,35
900,80
1200,120
1500,90
1800,55
2400,20
3000,5
3600,0

单位为:

mm/h

同样支持节点间线性变化和时间步积分。

这意味着现在可以模拟一场完整的设计暴雨过程,而不是只能设置:

降雨 = 50 mm/h

然后从头下到尾。


九、空间分区降雨

真实暴雨通常并不是整个区域完全一致。

例如山洪场景可能出现:

流域西北部:120 mm/h
流域中部:70 mm/h
流域下游:25 mm/h

所以系统进一步增加了地图框选降雨分区。

用户可以:

  1. 在地图选择一个矩形区域;

  2. 为该区域设置自己的降雨;

  3. 导入独立 P(t);

  4. 设置独立入渗参数。

而且支持多个区域覆盖。

采用的优先规则是:

后添加的小区域优先。

因此可以先设置一个大范围背景降雨,再设置局部暴雨中心。

例如:

全流域背景雨:20 mm/h

局部区域 A:
P1(t),峰值 80 mm/h

局部区域 B:
P2(t),峰值 150 mm/h

这样比全域统一降雨更接近真实暴雨空间结构。


十、降雨并不等于全部变成洪水

这是 V8 中一个比较关键的变化。

如果直接使用:

降雨量 = 地表产流量

那么结果通常会明显偏大。

因为真实降雨之后,一部分水会:

  • 入渗;

  • 被植被截留;

  • 洼地蓄积;

  • 蒸发;

  • 产生其他损失。

当前版本首先加入了三种简化模型。

1. 无损失

有效产流 = 降雨

适用于:

  • 算法测试;

  • 完全不透水区域;

  • 极端简化场景。

2. 径流系数法

有效产流 = C × P

如果:

C = 0.6

那么 100 mm 的降雨中按照模型简化为约 60 mm 形成有效产流。

这种方法虽然简单,但是对于数据不足的快速场景非常实用。

3. Horton 入渗

Horton 模型中,入渗能力随持续降雨逐步降低:

f(t) = fc + (f0 - fc)e^(-kt)

其中:

f0 = 初始入渗能力
fc = 稳定入渗能力
k  = 衰减系数

系统另外增加了:

recovery

用于模拟停止降雨之后土壤入渗能力逐渐恢复。

同时可以设置:

impervious

即不透水面积比例。

因此,同样一场暴雨作用于两个不同区域,可以产生完全不同的径流响应。


十一、V8 已经支持纯降雨驱动

这里是一个我觉得比较有意思的变化。

早期模型一定要存在:

上游入流
+
下游出流

否则没有水可以进入模型。

现在则完全不同。

可以直接运行:

P(t)
+
无河流入口
+
无河流出口

也就是说:

水直接来自天空。

因此可以模拟封闭低洼地区持续降雨后的积水。

也可以:

P(t)
+
一个或多个出口

用于坡面汇流。

甚至可以:

Q1(t)
+
Q2(t)
+
多个 P(t)
+
Horton
+
多个 Outlet

模拟较复杂的流域洪水过程。


十二、质量守恒是一个非常重要的检查指标

做这种实时水动力 Demo 时,很容易只关注:

水有没有流起来?

但数值模型真正需要持续关注的是:

水从哪里来,又去了哪里?

因此 Worker 会累计统计:

边界入流
+
局部水源
+
降雨有效产流
-
边界出流

并与当前网格水量进行对比。

降雨部分还会分别记录:

累计总降雨
累计入渗
累计有效产流

理论上满足:

总降雨
≈
入渗/损失
+
有效产流

而水动力网格满足:

当前水量
≈
初始水量
+
边界入流
+
有效产流
+
其他水源
-
出流

开发过程中这个指标非常有价值。

很多看起来像“渲染问题”的异常,最终其实是:

  • Wet/Dry 处理丢水;

  • 边界通量符号错误;

  • 时间积分错误;

  • 降雨量单位换算错误。


十三、GPU 连续水面,而不是一个格子一个 Primitive

数值求解使用规则网格,但如果直接把每个网格画成 Cesium Polygon,网格一多性能会快速下降,而且水面会产生明显的“马赛克”。

因此当前渲染采用:

一个连续 glTF Water Mesh
+
Cesium CustomShader
+
动态状态 Texture

求解器返回:

h
u
v

即:

水深
X 方向流速
Y 方向流速

然后编码到 GPU 状态纹理。

Shader 中完成:

  • 水深颜色;

  • 流速颜色;

  • 水面透明度;

  • Wet/Dry 显示;

  • 动态高光;

  • 流向箭头;

  • 时间插值。

水体网格的基础高程直接从 Solver DEM 双线性采样,因此水面和水动力计算使用同一套高程基准,可以减少水面“悬空”和穿地问题。


十四、为什么水面动画比较平滑?

水动力求解器并不需要按照显示器 60 FPS 的频率运行。

Worker 负责计算物理状态。

浏览器:

requestAnimationFrame

负责视觉刷新。

主线程保留相邻计算状态:

State A
State B

Shader 再通过插值:

mix(A, B, t)

得到显示时刻的状态。

所以实现了:

求解频率和显示帧率解耦。

这是浏览器实时数值模拟中非常有用的一种结构。


十五、目前可以应用在哪些场景?

虽然当前模型仍然属于研究/演示原型,但从系统架构来看,已经可以覆盖不少应用方向。

1. 山洪灾害模拟

真实山区 DEM + 暴雨 P(t) + Horton 入渗,可以观察:

  • 山谷汇流;

  • 沟道涨水;

  • 洪峰传播;

  • 下游受影响区域。

后续如果进一步加入土地利用和土壤参数,这个方向会更有意义。

2. 中小流域洪水演进

配置:

多支流 Q(t)
+
流域降雨 P(t)
+
多个出口

可以用于展示不同支流洪峰叠加后对主河道的影响。

3. 城市内涝快速演示

如果有高精度城市 DEM,可以采用:

纯降雨 P(t)
+
高不透水率
+
道路/排水出口

观察低洼区域积水过程。

当然,要真正用于城市内涝工程计算,还需要进一步加入:

  • 建筑物阻水;

  • 雨水管网;

  • 泵站;

  • 雨水口;

  • 更精细 DEM。

4. 水库泄洪和下游淹没演示

可以将泄洪过程作为:

Q(t)

输入模型。

然后观察不同泄洪过程对下游水深和流速的影响。

5. 堤坝溃决快速情景推演

可以把溃口看成一个随时间快速变化的 Q(t) 边界。

用于演示:

溃口形成
→
流量快速增加
→
洪水向下游传播

如果进一步加入动态溃口宽度模型,可以继续深化。

6. 应急指挥三维可视化

Cesium 最大的优势之一,是模拟结果天然处于真实三维地理空间。

后续可以叠加:

  • 道路;

  • 村庄;

  • 医院;

  • 学校;

  • 避难场所;

  • 人口;

  • 摄像头;

  • 水位站;

  • 风险点。

最终做成:

洪水模拟
+
GIS
+
应急资源
+
风险分析

7. 防洪方案比选

可以建立多个方案:

方案 A:现状
方案 B:增加排洪口
方案 C:改变出口宽度
方案 D:降低洪峰

然后比较:

  • 最大水深;

  • 最大流速;

  • 淹没范围;

  • 退水时间。

8. 水动力教学与算法验证

由于整个系统运行在浏览器中,并且地形、边界、降雨都可以交互配置,因此也非常适合用于:

  • 水动力教学;

  • WebGPU/WebGL 数值可视化研究;

  • GIS + 数值模型耦合研究;

  • 水文水动力算法原型验证。


十六、当前版本仍然有哪些不足?

这一点必须说明。

目前系统是:

演示/研究原型级二维水动力模型。

并不是经过工程认证、率定和验证的专业洪水预测软件。

目前仍需要继续解决:

  1. DEM 水文校正;

  2. 河道真实断面;

  3. 土地利用空间 Manning n;

  4. 土壤空间参数;

  5. SCS-CN / Green-Ampt;

  6. 建筑物阻水;

  7. 城市排水管网;

  8. 雷达降雨时空栅格;

  9. 实测雨量站/水位站率定;

  10. 与 HEC-RAS、MIKE、InfoWorks 等成熟模型结果对比验证。

因此目前更适合定位为:

Web 端实时水动力技术原型 / 数字孪生可视化底座 / 洪水情景推演 Demo。

而不是直接用于工程设计结论。


十七、后续准备继续做什么?

完成 V8 后,下一步技术路线其实已经比较明确。

我希望继续把“手工参数”逐步替换成真实 GIS 数据:

土地利用 GeoJSON / Raster
         ↓
空间 Manning n

土壤数据
         ↓
空间入渗参数

雷达降雨
         ↓
P(x,y,t)

河网 / 建筑物
         ↓
真实阻水与汇流结构

进一步可以加入:

  • SCS-CN;

  • Green-Ampt;

  • GeoTIFF / COG;

  • 雷达降雨时序;

  • 土地利用;

  • 土壤类型;

  • 空间 Manning;

  • 建筑物;

  • 道路;

  • 河网;

  • 实时雨量站;

  • 水位站;

  • IoT 数据。

最终希望形成的是:

真实 GIS 数据
+
实时水文数据
+
二维水动力模型
+
Cesium 三维地理场景
+
GPU 实时渲染

也就是一个真正的数据驱动型流域洪水数字孪生原型。


结语

这个项目从最开始的程序生成峡谷,逐渐走到了真实 DEM、多边界、Q(t)、P(t)、降雨产流和三维动态显示。

过程中最大的体会是:

WebGIS 不一定只能“展示”水动力模型的结果,也可以逐渐承担模型交互、实时计算和结果表达的一整套工作。

Cesium 负责真实地理空间,Web Worker 负责数值求解,GPU Shader 负责高性能连续水面显示。

三者结合之后,Web 端洪水模拟的可玩空间其实非常大。

目前 V8 已经完整跑通并完成录屏。

后续还会继续往 GIS 数据驱动、雷达降雨、空间参数以及更真实的水文产流模型方向完善。

如果大家对 Cesium、水动力、WebGIS、洪水模拟、数字孪生 这几个方向感兴趣,欢迎交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值