简介:这是一份面向物理海洋学与海洋测绘场景的MATLAB计算程序(sw_dpth.m),旨在根据声纳测深等原始观测数据估算海水深度,解决海洋研究中深度参数获取与算法实现问题,适合海洋科学专业学生、科研人员以及工程技术人员学习与复用。程序涵盖多个关键技术点:声纳信号发射接收与传播时间换算、大地水准面参考框架设定、经纬度与大地坐标转换、温度/盐度/压力对声速的影响校正、噪声滤波与误差分析、地球曲率简化与数据重采样等,并通过绘图函数实现深度结果的可视化展示,体现了从原始数据到成果图件的完整处理链条。资源包以zip格式压缩,仅含1个.m源文件,体积约1KB,代码量小、结构清晰、无多余依赖,便于逐行阅读、调试和二次修改。目前已有160人学习下载,可作为物理海洋学算法教学演示、海水深度计算入门练习或小型科研任务的参考实现;对初学者而言,这份代码也提供了从理论公式到实际编程的直观样例。
1. 从 dbar 到米的硬骨头:sw_dpth 是如何计算深度的
物理海洋 CTD 数据里,最容易被新手跳过的一步是压力到深度的转换。CTD 探头在水下直接测量的是压力,通常以分巴(dbar)为单位,而不是深度。sw_dpth.m这个 MATLAB 函数,来自海洋数据现场处理常用的 SEAWATER 工具包,作用就是把压力数组和纬度数组换算成以米为单位的几何深度。它不是声呐测深程序,也不是处理多波束回波的脚本,而是解决"CTD 剖面每一层的压力对应多少米水深"这个基础问题。无论是 Argo 浮标数据、船载 CTD 投弃,还是历史水文海洋数据,最终画温度-深度剖面时都绕不开它。适合做物理海洋数据处理、渔业资源调查或海洋工程环境评估的从业者,也适合需要读 NetCDF 压力层再插值到深度层的建模同学。
2. 压力-深度转换的物理模型与 UNESCO 经验公式
2.1 海水可压缩性带来的非线性偏差
要讲清楚为什么不能直接用depth = P / 1.005,得先看水静力学方程:dP = rho * g * dz。每增加 1 dbar 压力对应的深度增量是dz/dP = 10^4 / (rho * g),单位是 m/dbar。表面海水密度约 1025 kg/m³,因此dz/dP大约为 0.9945 m/dbar;但到了 4000 dbar 附近,密度会因为压缩增加到约 1045 kg/m³,同样的 1 dbar 压力增量只对应约 0.974 m。也就是说,压力增量到深度增量的换算系数并不是常数,必须沿压力方向逐层累积。
如果拿表面密度或者一个平均密度做单次除法,1000 dbar 处会引入 2~3 m 的偏差,6000 dbar 处偏差可以达到 80 m 以上。这个量级对深海热液羽流定位、地转流计算和温盐气候序列拼接都是不可接受的。实际海洋学里解决这个问题有两种路线:一是对现场的温盐深数据做逐层积分,二是使用已经积分好的经验多项式。sw_dpth.m走的是后者,它用一份标准海洋状态把结果固化成了闭式公式,因此调用成本极低,也不会因为某层的盐度毛刺导致深度计算发散。
2.2 sw_dpth 的多项式:UNESCO 1983 的深度公式
sw_dpth.m的核心是一个四阶多项式,来自 UNESCO 1983 标准方程。它把"从海面到某一压力面的垂直距离"写成压力的函数,同时用纬度修正重力项。化简后的形式是:
depth = (((-1.82e-15*P + 2.279e-10)*P - 2.2512e-5)*P + 9.72659)*P / grav
其中P的单位必须是 dbar,grav是下面会讲的重力纬度修正项。这个多项式不是随意拟合出来的曲线,而是用国际海水状态方程(EOS-80)对标准海洋剖面做数值积分后,再用最小二乘拟合得到的闭式解。正因为有了积分的先验结果,函数在 0~10000 dbar 范围内能保持 0.01 m 量级的精度,而且不需要循环累加,特别适合批量处理一整条航次的 CTD 文件。
| 系数 | 数值 | 对应作用 |
|---|---|---|
| c1 | 9.72659e0 | P 的一次项,决定表面附近的近似斜率 |
| c2 | -2.2512e-5 | P² 项,模拟密度随压力增加 |
| c3 | 2.279e-10 | P³ 项,订正高阶压缩误差 |
| c4 | -1.82e-15 | P⁴ 项,深水区微调 |
表格里的负二次项是理解整个公式的关键。它让多项式导数随着 P 增大而降低,和水静力学方程中rho随压力增加的现象一致。使用时要特别注意,如果读入的数据从 netCDF 里提取时已经换算成了 bar 或者 Pa,必须先把单位转回 dbar,否则结果会比真实深度差到一到两个量级。
2.3 纬度修正在公式里的作用
重力加速度在地球表面不是常量,赤道约 9.7803 m/s²,两极约 9.832 m/s²。sw_dpth里使用的重力经验式是:
g = 9.780318 * (1 + 5.2788e-3 * sin(lat)^2 + 2.36e-5 * sin(lat)^4)
这里的lat是纬度,单位是度,sin(lat)取绝对值,因此南北半球是对称的。为什么深度计算需要重力修正?因为同样的压力差,在重力较大的高纬度区,水柱会被"压"得更短,深度就略微偏小。对 6000 dbar 的深海测线,赤道和 60°纬度之间的深度差约 12 m。如果忽略纬度而把所有剖面当成赤道处理,跨海盆的深度对比会出现系统性偏差。很多脚本默认lat=0,在低纬海区问题不大,但到了北大西洋和南大洋的深水断面,会把层深算深或算浅,进而影响密度层结和地转流计算结果。
3. sw_dpth.m 代码走读与参数语义
3.1 函数签名与输入维度约束
标准版本的核心代码可以精简成下面这段,我删掉了版权头和各平台兼容分支,保留了主要的处理逻辑:
function depth = sw_dpth(P, lat) % sw_dpth 压力(dbar)转深度(m),使用 UNESCO 1983 经验多项式 % % 输入: % P - 压力,单位 dbar,标量或向量 % lat - 纬度,单位 deg,标量或与 P 等长的向量 % % 输出: % depth - 深度,单位 m,始终为正 P = P(:); if nargin < 2 || isempty(lat) lat = 0; end if isscalar(lat) lat = lat * ones(size(P)); else lat = lat(:); if length(lat) ~= length(P) error('sw_dpth:DimMismatch', ... 'P 与 lat 必须等长,或 lat 为标量'); end end x = sin(abs(lat) * pi / 180); g = 9.780318 * (1 + 5.2788e-3 * x.^2 + 2.36e-5 * x.^4); p = P; % 霍纳法逐层展开,避免高次幂数量级相差过大 depth = (((-1.82e-15 * p + 2.279e-10) .* p - 2.2512e-5) .* p + 9.72659) .* p; depth = depth ./ g; end这里所有运算都用.开头的逐元素运算符,是为了让数组P和lat可以并行计算。x.^2和x.^4同样是逐元素幂运算。如果P是 1000 行 1 列的剖面,lat是标量,函数会把标量扩展成同样长度的向量,不需要额外写 repmat。P = P(:)这一步把任何行向量或矩阵转成列向量,保证输出尺寸和输入一致。
3.2 多项式实现里的两个细节
第一个细节是霍纳法。-1.82e-15*P + 2.279e-10先乘 P,再加-2.2512e-5,再乘 P,再加9.72659,最后再乘一次 P。相比直接写9.72659*P - 2.2512e-5*P.^2 + 2.279e-10*P.^3 - 1.82e-15*P.^4,霍纳法把中间运算数值控制在相近数量级,减少浮点舍入误差。P 越接近 10000 dbar,这个优势越明显。第二个细节是纬度转弧度后的abs。重力经验公式对南北半球是对称的,取绝对值后,南纬 30° 和北纬 30° 得到完全相同的重力修正项。不要把这个abs当多余操作删掉,否则在某些遗留代码里,南半球纬度由于符号问题反而导致深度错误偏移。
另一个容易被忽略的问题是缺测值。如果lat数组里混入NaN或-999,sin(abs(lat))对负值同样有效,于是-999会被当成高纬度修正,输出一个看似合理但完全错误的深度。所以在调用前,我一般会先丢掉压力或纬度为缺测值的记录,保持输入数组干净。
3.3 常见误用:把 dbar 直接当成米
很多人在写 CTD 脚本时为了省事,直接写depth = P;,理由是"1 dbar 差不多 1 m"。这个近似在 300 dbar 以内误差不到 1 m,但深层剖面误差会随压力快速增长。另一种误用是depth = P / 1.005;,用常数 1.005 做全剖面修正。这比直接用 P 好,但依然忽略高阶压缩和纬度影响。sw_dpth 的价值不仅是精度,更在于输入规范清晰,能和后续位密、位势异常计算保持同一套标准。如果要在脚本里批量处理 100 个站位,直接调用它比每个站位手写换算系数更安全、更可审计。
注意,这个函数默认输出正深度。部分旧版代码会返回负深度以配合绘图时的翻转坐标轴,实际处理时应统一存成正数,绘图时再去设置坐标轴方向。这样数据文件里的深度永远是物理意义上的正值,不会在和其他脚本拼接时搞混符号。
4. 实战:把 CTD 剖面转成深度并交叉验证
4.1 加载 CTD 数据并调用 sw_dpth
假设你有一份船载 CTD 数据,文件里存了压力 P、温度 T、盐度 S,还有站位纬度 lat。常见做法是用 MATLAB 的load命令读取 mat 文件,或者用textscan解析 ASCII 表格。先看 mat 文件的调用:
data = load('station01.mat'); P = data.pressure; % dbar,通常从 1 递增到 6000 T = data.temperature; % deg C S = data.salinity; % psu lat = data.latitude; % 单站纬度为标量,单位 deg depth = sw_dpth(P, lat); % 检查输出 assert(size(depth, 1) == size(P, 1), '深度数组尺寸不匹配');提示:如果
data.latitude来自 NetCDF 元数据,单位通常是degrees_north,直接传入 sw_dpth 没有问题。但有些历史数据把缺测纬度存成-999,该值会被当成高纬度参与重力修正,导致整个剖面深度偏移。建议先把缺失位标记为NaN并从输入中过滤掉。
assert这一步不是多余的。CTD 数据经过格式转换后,压力列可能带有前后空格,load读入变元胞数组,之后P(:)会得到错误长度。用尺寸断言可以把问题提前暴露,而不是等到画温度剖面时才发现深度和温度不在同一维度。
4.2 与线性近似对比的误差表
拿到 depth 后,拿它和经验近似P/1.005做对比,能直观看到非线性项的影响。下面这组计算在lat=0时得到。
| P (dbar) | sw_dpth 深度 (m) | P/1.005 (m) | 偏差 (m) |
|---|---|---|---|
| 1000 | 992.2 | 995.0 | -2.8 |
| 2000 | 1980.0 | 1990.0 | -10.0 |
| 4000 | 3942.7 | 3980.1 | -37.4 |
| 6000 | 5889.0 | 5970.1 | -81.2 |
偏差是sw_dpth - P/1.005,负数表示线性近似把深度算大了。可以看出,到 6000 dbar 时,线性近似高了约 81 m,这个量级在深海锚系计算、海底地形校正和声速剖面建模里完全不能忽略。我一般在交付数据时保留原始压力数组,同时附上 sw_dpth 生成的深度数组,并在数据说明里注明深度是用压力和纬度经验换算得到的,而不是来自压力传感器直接读数。
4.3 批量处理多个站位
如果手上有整个航次的 50 个站位,不要写循环嵌套去逐层调用函数。MATLAB 的向量化能力足以让 sw_dpth 一次吃掉所有数据。假设P_all是n x m的矩阵,m是站位数,lat_vec是1 x m的纬度向量:
[n, m] = size(P_all); lat_mat = repmat(lat_vec(:)', n, 1); depth_all = sw_dpth(P_all(:), lat_mat(:)); depth_all = reshape(depth_all, n, m);这里P_all(:)把矩阵重排成一列,lat_mat(:)同步展开成和它对应的列向量。函数内部在同一列上一次完成所有站点的计算,比循环调用快很多,尤其当 n 接近 10000 层时。处理完成后reshape回原来的站位矩阵,确保每个站位仍是按压力递增排列的剖面。需要注意的是,如果某些站位剖面长度不足,矩阵会补 NaN,sw_dpth 对 NaN 输入返回 NaN,这正好能保留站位缺失信息,值得在数据说明里记录清楚。
5. 一个容易被忽略的验证技巧:压力差分检查深度数组
5.1 深度序列必须严格单调
CTD 下放和上收都会记录压力,但经过预处理后,标准剖面的压力应该是严格递增的。深度数组也必须是严格单调递增,否则后续插值会出现重复节点。检查方法是用diff:
dP = diff(P); dZ = diff(depth); if any(dP <= 0) || any(dZ <= 0) bad = find(dP <= 0 | dZ <= 0); warning('非单调位置: %d 处,建议检查原始压力记录', numel(bad)); end这里的dP和dZ都是逐元素差分,任何一处压力不增或深度不增都说明原始数据里有坏点。CTD 电路偶尔会在高压力段跳变 0.5 dbar,diff能立刻把这些点定位到具体索引。不要只看最大深度,浅层的重复节点在内插到规则网格时会直接导致插值函数报错。
5.2 用局部导数判断深度换算是否被污染
sw_dpth 的结果本身不需要校准,但你可以通过它反过来验证压力单位是否正确。计算dZ ./ dP,得到每一层压力的深度增量,单位是 m/dbar:
dzdp = dZ ./ dP; mid_p = 0.5 * (P(1:end-1) + P(2:end)); plot(mid_p, dzdp); xlabel('压力 (dbar)'); ylabel('∂z/∂p (m/dbar)'); grid on;从理论上讲,这个值在表面约为 0.9945 m/dbar,随压力增加逐渐减小,到 6000 dbar 约 0.98 m/dbar。如果画出来的曲线在某个深度突然变大,大概率是压力传感器在这个区间的分辨率不够,或者输入的压力数组曾经被乘过 10、除过 10。比如有人把 bar 当成 dbar 传进 sw_dpth,那么 100 bar 会被当成 100 dbar,算出来的深度会小接近一个量级,这条曲线就会严重偏离正常区间。
5.3 深度-温度绘图时要用 YDir reverse
最后给一个画剖面的小技巧。物理海洋学剖面图习惯把海面放在顶部,MATLAB 里直接设置set(gca,'YDir','reverse'),不要去改 depth 本身的符号。如果你为了省事写成depth = -depth再传给 plot,数据文件里的深度就变成负数,后续和网格数据集对齐时会出现符号混淆。保持数据为正,只在绘图层翻转坐标轴,是最少出错的约定。批量出图时,把 sw_dpth 封装成一个子函数统一调用,并在脚本注释里写明"深度单位 m、压力单位 dbar、纬度单位 deg",可以避免半年后再回来维护时重新推导系数。
本文还有配套的精品资源,点击获取