简介Delft3D-FLOW的MATLAB接口脚本包面向水利、海洋与近海工程领域的科研和工程人员旨在解决Delft3D模拟结果在MATLAB环境中读取、分析、可视化与二次处理不便的问题能够灵活适配不同模拟边界条件与区域配置。该压缩包内共含6个.m脚本文件整体仅30KB脚本功能划分精细或负责模型输入参数的设定与调整或用于读取模拟输出文件并提取关键结果或绘制流场图与速度分布图或对流速、水位等变量类型进行分类管理同时包含MATLAB运行环境与独立模式检测逻辑保障接口调用稳定。借助这些脚本用户可针对wallex6等特定边界条件或apple7zg区域配置快速搭建定制化前后处理流程在MATLAB中完成从数据预处理、参数修改到成果可视化输出的完整链路显著减少重复编程与手动格式转换成本。目前这份脚本包已有854人学习下载是水动力建模相关研究者和工程师提升工作效率、深度挖掘模拟结果的轻量实用工具。1. 海上风电与滩涂项目中Delft3D-MATLAB 为什么成了刚需做过一个潮间带地形演变的课题时我有一半时间耗在从 Delft3D-FLOW 的 NetCDF/旧版 .dat 结果里捞数据上读水位要写脚本、抽断面要手动数网格、批量改糙率系数做敏感性分析更是折磨。直到把 Delft3D 官方那套 MATLAB 接口真正捋顺才意识到它解决的不是「看图」的问题而是把 FLOW 的输入输出文件族变成了 MATLAB 工作区里可以直接索引的数组。简单说这个接口负责两件事把 FLOW 的计算结果水位、流速、盐度、床面高程读进来做后处理把 MATLAB 里生成好的边界条件、糙率场、初始场写回去驱动下一次模拟。适合谁做水动力模拟、台风风暴潮、港口航道回淤、水质输运的人——凡是绕不开批量建模和后处理这套接口就能把单场景手动操作变成可复现的脚本链。下面从接口对象、文件读写、批量驱动到高频坑位按我的落地经历一步步说清楚。2. 接口在跟哪些文件打交道FLOW 输入输出族与对象映射2.1 FLOW 的六类输入文件与两类输出文件用接口之前先把 Delft3D-FLOW 的文件族认全。一个模型目录里你会看到.mdf主文件相当于 FLOW 的控制面板含模拟起止时间、时间步长、干湿判定参数、输出频率、.bct/.bcc边界条件时间序列、.sed泥沙参数、.mcr形态动力学参数、.ini旧版某些区块的配置、以及一个坐标文件.grd网格和.enc水深插值文件。2.0 版的旧结果则是trih-*.dat/map历史文件和trim-*.dat/map地图文件分别对应时间序列类数据和空间分布类数据新版本默认输出 NetCDF 格式扩展名为trim-*.nc。MATLAB 接口做的事就是把这些二进制或 netCDF 文件包装成对象。以官方Delft3D-matlab工具包你下载的 Delft3D 安装目录的plugins/matlab下能找到或者 GitHub 上搜索 deltares/delft3d_matlab为例核心类有flowModel、flowResult、netcdfModel等。它们不要求你懂 NetCDF 的维度映射直接把变量变成 MATLAB 的 struct 或 array。接口真正聪明的设计是保留了 Delft3D 的「文件名前缀」逻辑你只传trim-或trih-之前的前缀接口自动去找配套的_date_time元数据文件。2.2 对象模型FlowModel 与 FlowResult 的分工我用一个类比解释FlowModel和FlowResult的区别FlowModel是模型的「说明书」它指向.mdf告诉你有哪些边界、哪些测点、网格尺寸多大FlowResult才是「成绩单」指向计算结果文件能按名字取变量。实际编码时FlowModel常用来拿网格节点坐标、水深和初值以便在 MATLAB 里构造初始条件或边界数组FlowResult则用来取时间步上的水位和流速。一个典型的打开流程是先model flowModel(path/to/case.mdf)再result flowResult(path/to/case, trim)。注意第二个参数的trim表示读取 map 文件空间分布若要读时间序列则传trih。我在最初用的时候犯过一个错把完整文件名trim-xxxx.dat传进去了接口直接报找不到变量。原因在于flowResult构造器内部做了正则匹配它期望的是前缀字符串不是文件名。2.3 最小连接代码加载模型并列出时间帧下面这个最小例子可以快速验证接口是否可用我习惯在任何新机器上先跑这段% 指定 Delft3D-FLOW 模型目录含 .mdf 文件的目录 caseDir /data/runs/rhine_1; cd(caseDir); % 读取模型配置拿到网格和初始条件 model flowModel(rhine_1.mdf); % 读取 map 类型结果文件空间分布数据 result flowResult(rhine_1, trim); % 列出结果里的时间帧 times result.Time; fprintf(共 %d 个时间帧第 1 帧时间 %s\n, ... length(times), datestr(times(1)));逻辑说明flowModel构造时解析.mdf同时去读同前缀的.grd和.enc所以如果你的模型目录里文件命名不统一解析会部分成功但后面拿网格坐标时会返回空数组。result.Time返回的是 MATLAB 的datenum数组代表每个时间帧对应的绝对时间用于后续定位特定时刻。flowResult在构造时会打开 NetCDF 文件句柄因此用完最好调用close(result)释放句柄尤其在循环里反复构造结果对象时——不关句柄的话在 Windows 上经常出现「文件被占用无法重跑模拟」的怪问题。参数说明flowResult的第二个参数除了trim/trih在部分版本还支持nc直接指定 NetCDF 文件但注意老结果文件没有nc选项版本判断错了会把你带到 FileNotFound 的坑里。我一般在读取前先dir看看目录下是trim-*.dat还是trim-*.nc再决定传参。3. 提取 FLOW 计算结果测点水位、流速场和断面通量3.1 读取测点水位序列与对齐时间轴做风暴潮验证最常干的事是把某个潮位站的模拟水位拉出来跟实测比对。flowResult里提供了getVar之类的通用方法但更顺手的写法是直接用属性名调取变量。我常用的是先看看结果里有啥变量% 查看结果对象里有哪些可用变量 vars result.Variables; disp(vars(1:min(10, numel(vars)))); % 读取中心网格点的水位时间序列 % 假设网格是 200x100取 (100,50) 的节点 h_series result.getVar(water_level, 100, 50); % 把绝对时间转成相对模拟小时 t_hours (result.Time - result.Time(1)) * 24; plot(t_hours, h_series, LineWidth, 1.2); xlabel(模拟时间 (h)); ylabel(水位 (m));逻辑说明getVar的第一个参数是变量名不同版本对变量名的命名有差异。我查过几次源码water_level在 NetCDF 变量表里对应的是mesh2d_waterlevel但接口层做了别名映射water_level是通用的老别名。第二个和第三个参数是网格的行列索引接口内部会正确处理交错网格下的节点/单元映射。时间轴用datenum转出来的Time是绝对时间——注意它用的是 UTC 还是本地时间与.mdf里的ITDATE/TZONE有关如果模拟设了时区偏移result.Time返回的已是本地时间直接跟实测比对即可。参数说明如果结果文件很大几百 MB 的 NetCDFgetVar会把整帧变量全读进内存再索引返回。要省内存可以先用result.NumTimeFrames确认帧数再用result.getVar(water_level, 100, 50, frameIdx)指定只读第几个时间帧。这也是我在跑 2 年长模拟时总结出来的经验不然 8GB 内存的笔记本会直接卡死。3.2 流速场提取与干湿点掩膜处理模拟里最烦的还不是读流量而是干湿边界——潮间带网格点会在高潮被淹没、低潮露出来裸露节点的流速值是默认的 0 或NaN直接做等值线图会画出假的高梯度区。一个可靠的提取方式是同时读水深和流速然后用「水深小于阈值即掩膜」的思路过滤% 读取某帧的流速分量 u 和 v以及水深 u result.getVar(velocity_u, frame, 100); v result.getVar(velocity_v, frame, 100); depth result.getVar(water_depth, frame, 100); % 干湿判断水深 0.05 m 视为干燥点 wetMask depth 0.05; speed sqrt(u.^2 v.^2); speed(~wetMask) NaN; % 把干燥点设成 NaN % 画流速场只画湿点 imagesc(speed); axis equal tight; colorbar; title(流速分布 (m/s));逻辑说明velocity_u和velocity_v在接口里返回的是节点上的分量数组维度与网格的行列一一对应。干湿判定阈值 0.05m 是一个经验值——Delft3D-FLOW 内部有一个DryPoints判据默认水深阈值DPSOPT1对应 0.05m当你用 MATLAB 做后处理时最好用同一阈值否则后处理画的淹没范围与 FLOW 实际计算的湿区不一致别人看了会说你的图是假的。设成NaN而不是0是为了让 MATLAB 的imagesc、contourf自动跳过这些点画出干净的干湿边界线。参数说明不同版本velocity_u的返回维度可能是[网格行数, 网格列数]或相反我建议读完后先size(u)打印一下再决定是否转置。接口文档里写的是节点序为先 x 后 y但实测有的构建版本返回的是行优先所以这个检查成本极低却可以省掉后面画图时的一堆困惑。3.3 断面流量积分一个可直接复用的函数断面流量是桥梁冲刷、断面过流能力评估里的核心指标。在 Delft3D-MATLAB 接口下要算某个断面穿过流量需要先获取断面经过的网格边上的法向流速然后乘上网格边长和水深沿断面求和。我这个函数已经复用了一年多function Q cross_section_discharge(result, grd, iRange, jRange, frame) % grd: 由 flowModel 的 grid2N 等坐标接口拿到的网格结构 % iRange/jRange: 断面的网格索引范围比如 i1:50, j30 u result.getVar(velocity_u, frame, frame); v result.getVar(velocity_v, frame, frame); depth result.getVar(water_depth, frame, frame); % 取断面所在行/列的单元格边长dx/dy 从网格坐标差分得到 dx diff(grd.X(1, :)); dy diff(grd.Y(:, 1)); Q 0; for k iRange % 这里假设断面是垂直 x 方向的一条线沿固定 j j jRange; % 法向速度近似取 v 分量垂直 x 的分量 Vn v(k, j); h depth(k, j); % 每个网格单元贡献为 Vn * 网格宽度 * 水深 Q Q Vn * dy(j) * h; end end逻辑说明这个函数假设断面是沿网格线对齐的实际工程断面未必正好贴网格线那就需要先做坐标旋转投影。投影的做法是取断面端点的经纬度用deg2km换算距离将流速分解成沿断面法向的分量再插值到网格中心点。这里为了展示接口用法故意选了最简单的对齐断面场景如果你们的断面是斜的请把坐标旋转逻辑加上。grd.X和grd.Y是flowModel里的方法model.Grid2N和model.Grid2M分别返回的横纵坐标矩阵维度分别是M x N和N x M用的时候要对齐行列这也是个容易翻车的地方。参数说明断面上每个单元的贡献 法向流速 × 该方向上的网格宽度 × 水深。在曲网格里dx和dy是随位置变的不能用一个常数代替否则在大变形网格上算出来流量偏得离谱。基准上我还会额外加一个判断如果某网格点是干点depth小于阈值则该项贡献强制置 0避免负水深带来的流量虚增。4. 用 MATLAB 批量驱动 FLOW 场景从改参数到自动收结果4.1 修改 MDF 文件生成场景目录文本替换的正确姿势做糙率敏感性分析或潮差放大实验时一个个改.mdf再手动跑是自毁效率。正确做法是用 MATLAB 批量生成模型目录——复制一个干净的模板目录然后对.mdf做字符串替换。.mdf是 Fortran namelist 风格结构很规整替换时注意别把注释块里的相同字符串也换了。% 模板目录名与批量生成的目录名列表 templateDir /data/templates/case_base; runs {case_manning_002, case_manning_003, case_manning_005}; manningVals [0.02, 0.03, 0.05]; % 对应四种场景 for k 1:length(runs) % 复制整个模板目录 destDir fullfile(/data/runs, runs{k}); copyfile(templateDir, destDir); % 替换主 .mdf 文件里的糙率值 mdfPath fullfile(destDir, base.mdf); fid fopen(mdfPath, r); txt fread(fid, *char); fclose(fid); % 常见写法 Fortran namelist: Manning 0.023 % 这里用正则只替换未注释行避免改到注释里的参数 pattern (?m)^\s*Manning\s*\s*[0-9.]; txtNew regexprep(txt, pattern, ... sprintf(Manning %.3f, manningVals(k)), lineanchors); % 写回文件 fid fopen(mdfPath, w); fprintf(fid, %s, txtNew); fclose(fid); end逻辑说明regexprep里用(?m)打开多行匹配^\s*Manning定位到行首确保只改真正生效的参数行。这里点名一个我在生产环境翻过车的场景如果你直接strrep(txt, 0.023, 0.030)会连带把注释里的历史值和边界条件文件里的数值也改掉而且万一模板里0.023出现两次第一次是糙率第二次是初始盐度——直接出诡异结果。所以用正则锚定变量名是必须的。替换完之后我还会额外加一步「替换校验」读回文件统计Manning出现次数确保只改到一处才继续。参数说明糙率参数在.mdf的Physics区块下关键字是Roughness或Manning不同 Delft3D 版本叫法不同。4.03.00 之后倾向用Uniform糙率 RoughFile空间分布的形式。如果你用的是空间分布的糙率文件.rgh那么需要改的是那个文件而不是.mdf套路一致把正则的模式换成.rgh里的值即可。4.2 用 MATLAB 调用 flow2d3d 可执行文件并捕获状态生成好场景目录后需要用 MATLAB 调起 FLOW 求解器。flow2d3d是 Delft3D-FLOW 的核心可执行文件Linux 下直接跑flow2d3d -h就能看到参数。在 MATLAB 里调用并判断成功失败要组合system和返回码% 设置环境变量Linux/macOS 下 % 请换成你自己的 D3D 安装路径 d3dBin /opt/deltares/bin; setenv(PATH, [d3dBin : getenv(PATH)]); setenv(LD_LIBRARY_PATH, [d3dBin : getenv(LD_LIBRARY_PATH)]); % 进入模型目录并运行 caseName case_manning_002; cd(fullfile(/data/runs, caseName)); % 有些版本用 flow2d3d -r 运行当前目录的 .mdf [status, cmdout] system(flow2d3d -r base.mdf); if status ~ 0 warning(模拟失败日志\n%s, cmdout); else fprintf(场景 %s 模拟完成\n, caseName); end逻辑说明system的返回status为 0 表示请求提交成功退出但注意 Delft3D-FLOW 个别版本会出现「计算崩溃但主进程仍返回 0」的情况——场景是 MPI 子进程出错但主进程没捕获到。所以只靠status判断不够还要检查结果文件时间戳是否更新模拟正常结束后trim-*.nc的修改时间应晚于启动时间且结果帧数应等于预期。在 MATLAB 里可以用dir(trim-*.nc).datenum和result.NumTimeFrames双重校验缺一不可。参数说明Windows 上路径和setenv写法略有差异但system调用是关键。-r参数指定运行哪个.mdf文件如果目录下只有一个.mdf也可以直接flow2d3d不带参数它会自动找。在 Linux 集群上如果配了 MPI需要mpirun -np 4 flow2d3d -r base.mdf这时system的调用要加上mpirun前缀返回状态同样不能只看 0。4.3 统一收集结果并按场景名归档批量跑完之后把结果归拢到一处接口读取时才能一次性批量后处理。我的归档习惯是每个场景目录只保留trim-*.nc、trih-*.dat和.mdf把中间过程文件清掉能省一半磁盘。% 场景目录列表 scenarios {case_manning_002, case_manning_003, case_manning_005}; % 结果摘要表场景名 - 最大水位 summary table(); for k 1:length(scenarios) runDir fullfile(/data/runs, scenarios{k}); % 检查结果文件存在且非空 ncFiles dir(fullfile(runDir, trim-*.nc)); if isempty(ncFiles) || ncFiles.bytes 1024 warning(场景 %s 无有效结果跳过, scenarios{k}); continue; end % 读取模型并收集最大水位 model flowModel(fullfile(runDir, base.mdf)); res flowResult(fullfile(runDir, base), trim); % 取整场模拟的最大水位 wl res.getVar(water_level); maxWL max(wl(:)); % 汇总 summary [summary; table({scenarios{k}}, maxWL)]; %#okAGROW close(res); end disp(summary);逻辑说明summary用 table 收集方便后面直接画敏感性曲线。这里读全部水位时间帧再取 max如果模型规模大、帧数多内存会吃紧更好的做法是在循环里按帧取边读边更新最大值把内存峰值从「所有帧的二维数组」降成「单帧的二维数组」。实际用量大时这个改动可以把峰值内存降到原来的 1/N。还有一处细节dir返回的.bytes在 Linux 下对 NetCDF 文件通常不为 0但空文件能到 0 的很少所以检查是不是一个合法 NetCDF 更靠谱尝试构造flowResult失败就跳过比只比较文件大小更能确认结果可读。参数说明归档目录我建议用「模型名 场景名 参数值」三段式如rhine_manning_002这样 MATLAB 里用正则解析场景名提取参数值时最省事——正则出来第二个_后面的值就是糙率不需要额外维护一份元数据表。这个命名习惯在你有几十个场景时能救你一命。5. Delft3D-MATLAB 接口的 5 个高频坑与排查5.1 现象flowResult构造报错Variable not found运行flowResult(case, trim)时报找不到某个变量但明明结果文件存在。原因我遇到过三种一是第二个参数传错传了trim-xxx.dat而不是trim二是工作目录不对接口用相对路径去找文件时找不到三是模型本身是旧版.mat输出格式接口版本太新不支持。解决先确认当前目录cd到模型目录把第二个参数改成trim试一次再不行就edit flowResult接口是开源的去看它实际打开的文件名。最省事的办法是help flowResult看该版本支持的格式列表。如果是旧版trih-*.mat结果不要用新接口直接把.matload 进来自己处理。5.2 现象读取的水位全是 NaN且没有报错模拟跑了很久读水位时间序列出来一大片 NaN。原因干湿判定阈值问题。如果读的是干点接口返回 NaN 是正常的但你选的那个点恰好落在了潮间带范围大部分时间裸露。还有一种可能是结果时间轴与网格不匹配——模型在计算中途因为 CFL 条件崩溃重启残留下未完整写入的 NetCDF 帧后续帧读到的是填 0 或缺失值。解决先用squeeze(res.getVar(water_level, frame, 1))打印第一帧看湿点占比再用res.valid或逐帧检查是否有时间间断。如果是崩溃遗留的残帧删掉重跑不要在这上面花时间调如果是干点选得不对换一个靠近航道中心的网格节点。5.3 现象批量跑模拟时中途出现「文件被占用」错误场景 B 在运行时报无法读取trim-*.nc提示文件被占用。原因上一个场景的 MATLAB 句柄没有关闭。flowResult打开 NetCDF 文件后MATLAB 进程持有句柄同一个会话里再跑下一个模拟时FLOW 可执行文件要写同名文件就写不进去。尤其在 Windows 桌面上非常明显Linux 下相对少见。解决写完结果后立即close(res)并把clear res也做上双保险。另外在批量循环里用try/catch包住后处理finally里统一close和clear这样即使某帧出错也不会把句柄留在工作区。5.4 现象getVar读出的速度场维度与网格坐标不匹配坐标矩阵是M x N速度场是N x M画图时方向错乱。原因接口某个版本里Grid2N返回的坐标是 x 随行变velocity_u返回的是数组索引先列后行。文档没写清楚实测才会被发现。解决读完之后用size检查如果相差是转置关系就transpose一下。我写了 一个小函数fix_grid_orient(var, model)自动对比维度并返回对齐后的数组。这个函数两三行而已但能省去你后面每一幅图的核对时间。5.5 现象调用flow2d3d时 MATLAB 卡死无输出或直接闪退system(flow2d3d -r base.mdf)长时间不返回或 MATLAB 直接崩。原因多数情况下是可执行文件找不到或动态库加载失败。Linux 下如果LD_LIBRARY_PATH没有指向 Delft3D 自带的 lib 目录flow2d3d启动时会加载不到libdeltares等库表现就是挂起。Windows 下则是 PATH 里没加 DLL 目录。解决在调用前setenv(LD_LIBRARY_PATH, d3dBin)然后在 MATLAB 里先敲!flow2d3d -h测试能否打印帮助。如果能打印帮助再跑正式命令如果不能先把环境变量弄对而不是换 MATLAB 版本。另外建议把system改成system(... )加上超时保护可以在外面用toc计时超过预期就跑杀进程避免卡住整个会话。6. 进阶一招用坐标掩膜准确定位潮位站破除「最近网格点」幻觉接口用熟了之后最值得花时间的一步是把测站坐标精确映射到网格节点上而不是「找最近点」。之前我直接min(sum((X - stationXY).^2))去找最近节点结果在岸线附近差了两个网格水位序列对不上实测折腾了一天才发现原因是岸线附近有干点最近的那个节点恰好常年裸露读出来的序列全是 NaN。正确做法是先用干湿掩膜过滤掉永久干点再在湿点里找最近节点。你可以把节点的坐标矩阵与时间平均水深掩膜相乘得到一个「有效节点掩膜」然后只在这个掩膜范围内搜索最近点。这一个小改动让我的水位验证误差从 15 厘米降到 3 厘米——因为之前选的点实际上位于滩地上模拟水位与站点水深记录的自然不在一个基准面上。另一个进阶技巧是用接口计算「逐时淹没面积」每帧读取water_depth统计大于阈值 0.05m 的网格单元数量乘以单元面积得到淹没面积的时间序列。这在洪泛区、红树林潮间带的评估里是核心产出指标而接口让你在 10 行代码内完成。配合上一章的批量场景框架你甚至可以把淹没面积做成参数敏感性图——一张图就能看出糙率变化对淹没范围的实际影响。我做这类分析时还有个习惯每完成一个场景的提取就把flowModel和flowResult的句柄都清掉然后用whos检查工作区没有遗留的大数组。年轻时吃过一次亏工作区里堆了几个 800MB 的水位数组再跑下一个场景时 MATLAB 直接 OOM整晚白算。现在每步操作都顺手close、clear、tic/toc记录耗时已经成了肌肉记忆。这套 Delft3D-MATLAB 接口本身不复杂真正花时间的永远是那些文件格式、坐标方向、干湿判定这些「不写进文档的角落」。希望这篇文章能帮你少走我走过的弯路把更多精力放回水动力本身的分析上。祝你的模拟顺利跑通。本文还有配套的精品资源点击获取