简介:这是一份面向水文水动力领域ADCP数据后处理的MATLAB三维可视化资源。针对声学多普勒流速剖面仪采集的多个断面数据,提供从VMT软件摘要文件中提取、整理到绘制3D图形的完整流程,适合需要将断面流速信息立体呈现的水文研究人员、工程师及高年级相关专业学生。资源共58个文件,压缩包约20.66MB,主体包括29个xlsx断面测量数据表、22个png三维效果示例图、1个MATLAB绘图脚本、2个docx操作说明和2个txt说明文档,目录按原始摘要、提取文件、成图输出分工明确,便于对照学习。项目覆盖14个测线断面,运行脚本可自动读取数据并绘制含全部信息的3D图,配合MATLAB的旋转3D功能,可自由切换法向视角、XY、XZ、YZ等观察角度。目前该资源已有269人学习,适合积累ADCP数据后处理和可视化经验,并可直接改造用于自己的水文项目。 做海洋或河流连续站观测的人,多半都有这个体验:ADCP网口数据拿回来,一列是时间,一列是深度,还有北向流速、东向流速、垂直流速、回波强度……信息很全,但常规的2D云图只能显示两个维度,流向要么压成几十个箭头,要么干脆不画。于是很多人都动过同一个念头——能不能做一张“包含所有信息的ADCP数据的3D图”,把时间、深度、流速大小、流向甚至回波强度一次摆进同一张图里。
这篇文章就以“ADCP数据3D图”为主题,聊聊我是怎么设计坐标、处理数据、用Python把它画出来的,适合刚接触ADCP后处理的人,也适合已经画烦了2D图、想换一种信息表达方式的同行参考。
1. 先想清楚:ADCP数据里到底藏了哪些信息
1.1 一束声波带回的数据结构
ADCP的全称是声学多普勒流速剖面仪,工作时向水层发射声脉冲,利用水中悬浮颗粒的散射信号,通过多普勒频移反演出不同深度层的流速。一个连续站位的ADCP,一天下来可以获得上千个时间剖面,每个剖面又有几十个深度层,每个格点都对应东向流速u、北向流速v、垂直流速w、误差速度、回波强度等一堆物理量。
从数据结构上看,这其实是一个三维时空场:时间是一个坐标,深度是一个坐标,物理量本身又是第三维。而一旦涉及流向,就需要在同一张图里同时表达“大小”和“方向”,信息维度进一步上升。所谓“包含所有信息”,并不是字面意义上把所有物理量都塞进去,而是把最核心的时空演变和流速矢量表达完整。
1.2 传统2D绘图的局限
常规画法里,最常用的是时间-深度云图,用颜色表示流速大小。这种图看流速强弱很直观,但流向完全丢失,实际的潮流往复、水体剪切层位置都看不出来。另一种常见画法是某一时刻的流速剖面图,横轴是流速,纵轴是深度,能够看清单条剖面的流速细节,但无法呈现连续时间的变化。还有人尝试在时间-深度图上叠加箭头表示流向,箭头一密就糊成一团,能量和方向混在一起,读图反而更累。
3D图的优势在于多出一个物理轴,可以把“流速大小”作为曲面的高度,同时用颜色表达流向,这样信息密度高很多,也方便观察峰值流速的时间位置和深度位置。
2. 3D图的坐标系怎么定,才能“一图全包”
2.1 哪些信息必须上
动手画图之前,先拆需求。一次连续站观测,最需要看的是四件事:流速大小的时间变化、流速大小随深度的分布、流向往复的规律、以及异常层的位置(比如强剪切层或底边界层)。按优先级排下来,速度大小必须用高度或者颜色表现,流向最好用颜色或者箭头表现,时间和深度则固定作为两个轴。
回波强度这类辅助量,我的做法是放到第二张图里做对照,而不是强行叠进同一张3D图。因为信息量太大时,可读性会直线下降,一张图里塞五个维度,最后谁的眼睛都救不回来。
2.2 坐标分配和颜色编码
我采用的坐标方案是:X轴放时间,Y轴放深度(海面方向取负深度,让水面在图上侧),Z轴放流速大小。这样曲面越高,表示流速越强,时间和深度的分布一眼就能看懂。
流向怎么处理?有两个方案。第一个方案是给曲面贴上HSV颜色:色相表示流向角度(0度表示北,90度表示东),亮度表示流速大小。这样一张图里同时表达了方向、大小、时间和深度。第二个方案更传统,曲面颜色用viridis表示流速大小,再在关键位置叠加少量流向箭头。我实际推荐先做HSV版本,因为流向箭头在3D空间里画不好就会挡住曲面,还容易给人错误的方向暗示。HSV版本对新手更友好,颜色环解释起来也不复杂。
3. 从netCDF到图:数据处理和Python画图实操
3.1 数据预处理不能省
拿到原始ADCP数据之后,不要急着画图。我的习惯是先用后处理工具把波束坐标转换成地球坐标,确保u和v是真正的东向和北向分量。这一步很关键,因为仪器默认输出的坐标系和船体朝向有关,如果中间没有做姿态校正,画出来的流向会整体偏一个角度。
质量控制也必须在画图前做。ADCP在表层盲区、底边界旁瓣区经常出现明显错误的数据,相关性(correlation)过低、信噪比过低的bin都要设为NaN。处理完之后,把数据写成规矩的NetCDF文件,变量名清晰,后续画图就很省事。
3.2 完整的3D曲面代码
下面这份代码以处理好的NetCDF数据为例,画一张带HSV流向着色的3D速度曲面图。需要安装的库是xarray、numpy和matplotlib,安装命令如下:
pip install xarray netcdf4 matplotlib numpyimport xarray as xr import numpy as np import matplotlib import matplotlib.pyplot as plt # 读取处理后的数据 # 假设变量名:u_earth(time, depth), v_earth(time, depth), depth(1D), time(1D) ds = xr.open_dataset("adcp_timeseries.nc") u = ds["u_earth"].values v = ds["v_earth"].values depth = ds["depth"].values # 正值,自海面向下 time_num = matplotlib.dates.date2num(ds["time"].values) # 构建网格 T, D = np.meshgrid(time_num, depth, indexing="ij") # 流速大小和流向角 speed = np.sqrt(u**2 + v**2) theta = (np.degrees(np.arctan2(v, u)) % 360) / 360.0 # 使用HSV做颜色编码:色相=流向,亮度=流速大小 vmin, vmax = np.nanpercentile(speed, 2), np.nanpercentile(speed, 98) hsv = np.zeros(speed.shape + (3,)) hsv[..., 0] = theta hsv[..., 1] = 0.85 hsv[..., 2] = np.clip((speed - vmin) / (vmax - vmin), 0, 1) rgb = matplotlib.colors.hsv_to_rgb(hsv) rgb[np.isnan(speed)] = 1.0 # 缺测区域显示为白色 # 如果只想看流速大小,直接替换成 viridis 颜色即可: # rgb = plt.cm.viridis((speed - vmin) / (vmax - vmin))[:, :, :3] fig = plt.figure(figsize=(14, 8)) ax = fig.add_subplot(111, projection="3d") surf = ax.plot_surface(T, D, speed, facecolors=rgb, rstride=2, cstride=2, linewidth=0, antialiased=True) # 观察角度 ax.view_init(elev=25, azim=-60) # 坐标轴标签 ax.set_xlabel("Time") ax.set_ylabel("Depth (m)") ax.set_zlabel("Speed (m/s)") # 时间轴格式化 ax.xaxis.set_major_locator(matplotlib.dates.AutoDateLocator()) ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%m-%d\n%H:%M")) # 流向角度色条 direction_cmap = matplotlib.colors.ListedColormap([ matplotlib.colors.hsv_to_rgb((i / 360, 0.85, 1.0)) for i in range(360) ]) cbar = fig.colorbar( plt.cm.ScalarMappable(norm=matplotlib.colors.Normalize(0, 360), cmap=direction_cmap), ax=ax, shrink=0.6, pad=0.1 ) cbar.set_label("Direction (deg from north)") plt.show()代码里有几个细节需要解释一下。date2num这步很关键,它把datetime对象转成数值,这样plot_surface的三维数据才能正常计算。坐标轴创建之后再传日期格式化器,让刻度显示成“月-日 时:分”,可读性比一串数字好太多。rstride和cstride是抽样步长,如果数据网格很密,设成2可以明显减少面片数量,否则Notebook容易卡死。
3.3 方案2:流向轨迹三维图
如果观测方式不是固定站位,而是走航或者单点垂线流场,我更推荐另一种3D图:XYZ三个轴分别放东向流速u、北向流速v和深度,把时间作为颜色渐变。这样的图能直接看出流速矢量随深度的旋转和大小变化,很像一条在三维空间里游走的螺旋线。
例如用plotly画交互版本的代码很简短:
import plotly.graph_objects as go fig = go.Figure(data=[go.Surface( x=ds["time"].values, y=depth, z=speed, surfacecolor=np.degrees(np.arctan2(v, u)) % 360, colorscale="HSV" )]) fig.update_scenes(xaxis_title="time", yaxis_title="depth", zaxis_title="speed") fig.show()plotly的好处是可以用鼠标旋转、缩放、查看具体数值,适合需要和不太熟悉3D图的人沟通的场景。而matplotlib版本更适合出图投稿,格式可控性更强。
4. 我踩过的坑:方向、坏点和画图性能
4.1 流向的颜色编码容易翻车
起初我用的是连续色带(比如jet)来映射流向角,结果在0度和360度交界处颜色突然跳变,明明流向很连续,图里却出现了一条明显的颜色断层。后来才意识到流向是周期变量,必须用HSV这类循环色带,或者自己拼接色带,确保首尾颜色一致。
另一个和流向有关的坑是数据本身没有做磁偏角校正。仪器输出的时候默认参考地磁北极,而我们要画的是相对于真北的流向。相差几度的磁偏角,在时间-深度曲面上的颜色变化虽小,但一旦要做流速矢量对比,误差就会被放大。建议在数据预处理阶段统一校准,别把问题留到画图时才发现。
4.2 边界坏点带来的大坑
ADCP在表层盲区和底边界附近的流速值经常是假的。如果不处理,plot_surface会把这些异常值直接画成巨大的尖峰,把整个曲面的Z轴拉伸到离谱的范围,颜色也跟着失真。
我后来养成一个习惯:质量控制后的数据中,把相关系数低于阈值或者信噪比过低的数值统一设为NaN,画图前再用nanpercentile来计算颜色范围。这样即使存在极端异常,色标也不会被个别坏点带偏。缺测区域在HSV转换成RGB之后会被赋成白色,曲面会形成空洞,这是正常现象,说明该区域数据不可靠,反而是好事。
4.3 常见问题速查表
| 问题 | 原因 | 处理方法 |
|---|---|---|
| 曲面一团黑,看不出颜色差别 | 颜色范围过大,或facecolors数值未归一化 | 用nanpercentile限定2%~98%范围,检查hsv蓝通道是否clip到0~1 |
| 时间轴显示一长串数字 | 没有用date2num或格式化函数 | 使用matplotlib.dates.date2num转换,并设置DateFormatter |
| 0度附近颜色跳变 | 流向角用普通连续色带映射 | 改用HSV循环色带或自建首尾一致的颜色列表 |
| 曲面出现异常尖峰 | 边界层坏数据未剔除 | 先做质量控制,将correlation/SNR低的bin设为NaN |
| 图片文件巨大,Notebook卡死 | 网格太密,面片数量过多 | 调大rstride和cstride步长,或降采样数据后再画 |
| 方向看着不对,偏了个角度 | 数据未经磁偏角校正 | 在坐标转换阶段统一把磁偏角引入heading校正 |
| 颜色条和曲面颜色对不上 | Normalize范围不一致 | 确保surfacecolor和colorbar使用同一套vmin/vmax |
4.4 给新手的两点实操建议
第一,先做2D图做交叉验证。我画完3D图之后,一定会再画一张传统的时间-深度速度云图,用同样的色标对比一下,确认3D曲面没有在数据插值或者颜色转换过程中引入新的问题。3D图是用来“看趋势”的,2D图才是用来“读数值”的。
第二,颜色和视角都需要反复调整。view_init(elev=25, azim=-60)这个视角是我认为比较稳的起点,它既能看清峰值流速的高点,又不会让曲面完全遮挡住近底层的细节。实际使用时我会根据数据分布微调,让曲线最高点处在视线最佳位置。
最后说点个人经验。我试过把同一组数据分别画成2D云图和3D图,拿给非专业的同事对比,3D图在展示“流速峰值在哪里、流向怎么旋转”时说服力明显更强。但真要读取某个深度层的精确速度,还是得回到2D剖面图或者直接查数据。所以我的做法是先用3D图做整体形态判断,再在关键位置补2D切片图。3D图不一定是每一篇报告的主角,但在你需要让一张图说话的时候,它确实很能撑场面。
本文还有配套的精品资源,点击获取