
做海洋、地质、测绘方向的人几乎都绕不开“底图”这个词。不管你是做海洋数值模拟、渔业资源评估还是搞航道设计、海上风电选址最后论文和报告里都少不了一张带海岸线、等深线和地形起伏的地图。我自己在MATLAB里试过不少方案从一开始硬用plot、patch拼图到后来换Mapping Toolbox再到GMT和Python的Cartopy最后长期留在我工作流里的反而是这个看起来“有点老”的m_map工具箱。这篇文章我就把用m_map画地形水深图的完整套路整理出来从工具箱部署、数据下载、投影选择、等深线绘制到论文级导出的避坑经验一次讲清楚。内容主要面向正在用MATLAB做海洋、地学相关图件但不想被复杂GIS工具套牢的同学。1. 为什么画海底地形图我最后留在了m_map1.1 m_map到底是个什么东西m_map是加拿大英属哥伦比亚大学Rich Pawlowicz维护的一个MATLAB第三方工具箱专门用于地图投影和地图数据可视化。它最大的特点是不依赖MATLAB自带的Mapping Toolbox纯脚本实现安装就是解压加路径用起来也直白。虽然作者已经很久没做大幅更新社区里偶尔有人修修补补但核心功能一直稳定得离谱。我接触它快十年了换了四五次MATLAB版本代码基本没怎么改过。m_map的核心哲学是“不重复造轮子”。它负责投影变换、海岸线绘制、网格生成、比例尺和指北针至于数据本身的处理还得你自己搞定。水深地形图说白了就是“地形图加上海洋深度表达”的组合m_map正好把地图投影这块最烦人的底子接住了剩下的就是组织和呈现数据。1.2 为什么不用MATLAB自带的Mapping ToolboxMATLAB有自己的Mapping Toolbox功能其实很强支持投影类型也很多但有几个现实问题。第一它不是随MATLAB基础包一起装的正版授权里要额外掏钱很多学校或公司的许可证压根不含这个模块你用尽办法也调不出来。第二Mapping Toolbox的函数体系比较繁重操作习惯也更偏向专业GIS画一张简单底图要先熟悉geodetic2ecef、worldmap、geoshow这一大套体系学习成本明显高于m_map。第三社区里的老代码、旧项目、师兄师姐留下的脚本绝大多数都是基于m_map写的你接手之后不改会造成迁移困难。对于“画一张能看清地形起伏和水深分布的地图”这个需求Mapping Toolbox属于杀鸡用了牛刀而m_map属于那种用顺手之后会形成肌肉记忆的工具。尤其在项目交付阶段改投影、调网格、换分辨率m_map的参数位置就那么几个改起来非常快。1.3 和GMT、Python比m_map的价值在哪GMTGeneric Mapping Tools在学术出版界地位很高出图确实漂亮但它走的是“命令脚本 配置文件”的路子千层套路调试一个细节经常要反复刷新而且数据组织和格式转换有门槛。Python的Cartopy近年来很火我也用但如果你是MATLAB用户尤其项目里其他数据处理全在MATLAB里专门为画个底图再搭一套Python环境实在不合算。m_map最大的优点是“离数据近”。你直接在同一个工作空间里既有水深数据矩阵又有画图函数不需要中间导出任何文件。实测下来从读取原始水深数据到出图我能控制在十行脚本以内。这种便捷性在快速验证想法时非常宝贵做科研的人都知道想法验证阶段画图速度直接决定试错效率。方案依赖学习成本出图质量与MATLAB数据流衔接m_map仅需MATLAB低中高取决于调试天然无缝Mapping Toolbox需额外授权中高高无缝但冗余GMT独立软件高很高需要文件交互CartopyPython生态中很高需要跨语言桥接2. 开工前准备工具箱部署与水深数据盘点2.1 m_map工具箱的获取与部署m_map的官方页面在UBC的服务器上搜索“m_map”一般第一个就是下载一个压缩包解压之后你会看到一个以m_map命名的文件夹里面全是以m_开头和个别以private开头的子文件夹。部署方式有三种我自己最常用也最推荐的是放到MATLAB工具箱目录里统一管理。第一步将解压后的m_map文件夹整体复制到你的MATLAB安装目录下的toolbox文件夹里比如D:\MATLAB\R2023a\toolbox\m_map。第二步打开MATLAB点击主页的“设置路径”选择“添加并包含子文件夹”选中刚才的m_map文件夹保存。第三步在命令行窗口输入m_proj(mercator)如果没有任何报错说明部署成功。这里有个细节值得提醒部署完成后不要再去改m_map文件夹里的任何文件。很多老版本的m_map在新版MATLAB下会有一两个兼容性警告有些人手痒就直接改源码结果改出一个更大的坑。后面我会专门讲兼容问题。2.2 水深地形数据源怎么选ETOPO、GEBCO、SRTM15画地形水深图数据是一切的前提。m_map只是负责画它不能替你无中生有。目前绕不开的全球水深地形融合数据有这么几套我用下来做个简单对比GEBCO是目前学术界最常用的全球海洋陆地形栅格数据集分辨率15弧秒大约450米左右。数据在GEBCO官网可以免费下载提供NetCDF和GeoTIFF两种格式。画区域性的水深地形图GEBCO是首选精度足够数据新覆盖度好。缺点是文件比较大全球数据解压后有4GB上下加载进MATLAB时需要裁剪或降采样。ETOPO系列是NOAA发布的全球地形数据集ETOPO1分辨率1弧分约1.8公里ETOPO2分辨率2弧分。优势是下载快、文件小、MATLAB读取方便适合画大范围概览图或者做底图叠其他要素。缺点是在近岸和深海沟区域细节不够。m_map官方提供了一个m_etopo2函数专门读ETOPO2数据你要是只想要个快速底图这条路很香。**SRTM15**是海洋测深数据的融合产品分辨率15弧秒比GEBCO更注重海洋深度但陆地区域相对粗糙。它来自Scripps研究所做纯水深等值线图时精度表现不错不过文件处理和坐标转换比前两个稍微麻烦一点。用的时候我一般这么分论文级大图用GEBCO放在PPT或者快速汇报里的概览图用ETOPO1做精细等深线用SRTM15做补充验证。不需要纠结哪个绝对好要看你的研究区尺度和目标。2.3 读取NetCDF格式的深海数据GEBCO和ETOPO系列下载下来普遍是NetCDF格式MATLAB从R2019a版本起用ncread和ncdisp读取非常方便不需要额外安装工具包。以GEBCO为例下载数据后先用ncdisp(GEBCO_2023.nc)看变量结构通常包含经度lon、纬度lat、高程elevation三个变量。读取区域数据时我建议先用ncread指定读取范围比如lon_all ncread(GEBCO_2023.nc, lon); lat_all ncread(GEBCO_2023.nc, lat); lon_region lon_all 105 lon_all 122; lat_region lat_all 3 lat_all 26; lon lon_all(lon_region); lat lat_all(lat_region); z ncread(GEBCO_2023.nc, elevation, [find(lon_region,1), find(lat_region,1)], [sum(lon_region), sum(lat_region)]); z z;读取时注意MATLAB的NetCDF维序习惯是(lon, lat, time)但读取出来矩阵是lon列、lat行画图前一般要转置。这一步卡了很多新手我一开始也是反复对不上坐标。另外如果读取整个全球数据再裁剪内存占用会非常大提前在ncread的参数里指定读取范围可以省掉很多资源。2.4 如果只想先跑通流程怎么不下载大文件并不是每次画图都需要真实的水深数据。m_map自带了几套演示用的地形数据可以让你在五分钟内先跑通流程。命令行运行m_demo(topo)就能看到效果。手头没有数据的时候我常用m_tbase和m_etopo2配合自带的示例数据快速测试底图样式。m_tbase函数读取的是m_map自带的全球地形粗糙数据直接调用就行。比如你想试投影效果代码可以这么写m_proj(lambert,long,[105 125],lat,[3 26]); m_tbase(contourf, [-5000:1000:0 500:500:3000]); m_gshhs(ic,color,k); m_grid(box,fancy,tickdir,in);这一段跑通后你就知道m_map的绘图逻辑了先设投影再叠数据再加海岸线最后画网格。3. 第一张标准水深地形图的完整画法3.1 投影选择不要把墨卡托当万能钥匙m_map支持一大堆投影类型常见的有mercator墨卡托、lambert兰勃特、albers阿尔伯斯、equidistant等距圆柱、mollweide摩尔维德等。选投影的本质是平衡形状、面积、距离和方向的变形。对于水深地形图我的经验是中低纬度的区域海图比如南海、阿拉伯海用mercator视觉上最自然等角航线画出来是直线对海洋要素场叠加友好。中高纬度或者东西跨度大的区域比如北太平洋航线研究用lambert更合适变形控制更均衡。小范围近岸工程海域三者差异不大随便选一个顺手的。m_proj的调用格式是设置投影类型和地理范围范围为后续所有m_函数共用m_proj(mercator, long, [105 122], lat, [3 26]);设置之后再画任何内容m_map会先把经纬度坐标做投影变换。所以后面你喂给m_pcolor、m_plot的数据一定得是经纬度坐标而不是已经投影好的平面坐标这是很多同学混淆的地方。3.2 海岸线m_coast和m_gshhs怎么选m_map画海岸线有两条路m_coast和m_gshhs。m_coast用的是内置的粗略全球海岸线数据优点是调用简单、速度极快适合出草图或者宏观大图。缺点是细节非常有限研究区域一旦缩小到几十公里级别海岸线就和实际情况差得远了。m_gshhs读取的是GSHHS全球海岸线数据库支持crude、low、intermediate、high、full五级分辨率细节程度依次提升。用GSHHS需要额外下载对应分辨率的数据文件并放到m_map的gshhs子目录里。实际做区域图我基本只用m_gshhs(ic)或m_gshhs(ih)级别分别对应中分辨率和高分辨率。比如这样叠加海岸线m_gshhs(ic, color, [0.2 0.2 0.2], linewidth, 0.8);想要让陆地部分有填充色可以加patch参数m_gshhs(ic, patch, [0.9 0.9 0.85]);这样陆地区域会呈现米黄色和海面形成天然的视觉区分。如果你需要在海岸线基础上再叠加河流m_map也内置了m_river数据调用方式和海岸线类似不过河流数据精度有限只能做宏观参考。3.3 水深地形填色m_pcolor和m_contourf的取舍把水深地形数据画成一幅连续填充图最常见的是m_pcolor和m_contourf。m_pcolor画的是网格色块图每一个栅格一个颜色分辨率高时非常细腻配合shading flat或shading interp后过度很平滑。缺点是数据网格稀疏时会出现明显锯齿。m_contourf画的是填色等值线图颜色是分层的视觉效果更接近地图册里的水深图缺点是需要指定等值线层级过度不如pcolor自然。我的做法是分场景如果展示目标是地形起伏的连续变化比如结合陆地高程的海陆一体地形图用m_pcolor加shading interp最漂亮。如果重点突出水深分级比如航道水深分类、渔业底层环境分区用m_contourf更科学因为分级填色本身就是一种信息分层。m_pcolor的标准用法[LON, LAT] meshgrid(lon, lat); m_pcolor(LON, LAT, z); shading flat; caxis([-4000 4000]); colormap(flipud(cmocean(topo))); colorbar;注意LON和LAT是meshgrid生成的二维网格很多新手拿一维的lon和lat直接丢给m_pcolor结果画出来是空白或报尺寸错误。这里多花一秒钟记一下后面能省十分钟。3.4 等深线叠加让水深数据开口说话填色图解决的是“整体观感”等深线解决的才是“可以读的信息”。尤其在做海洋工程相关图件时等深线的位置、密度和数值直接关系到可行性判断。m_map里用m_contour叠加等深线调用方式和MATLAB自带contour类似但多做了投影变换。m_contour(LON, LAT, z, [0 0], k, LineWidth, 1.5); % 零米线 m_contour(LON, LAT, z, [-50 -100 -200 -500 -1000 -3000], --, LineWidth, 0.6);第一行里的[0 0]表示只画高程为0的等值线即海陆分界线配合海岸线使用相当于给陆地勾了一圈轮廓。第二行画一系列常用深度等值线虚线样式区分层次。如果你想给等深线标数值用m_text手动标注特定位置避免自动标注过于拥挤。实测下来自动标注在海湾、海峡这类地形复杂区域会叠成一团手动标注虽然费点时间但图面质量高一个档次。3.5 网格、比例尺和指北针地图的“骨架系统”地图不是图片必须有完整的坐标参考信息。m_grid函数负责在投影图上绘制经纬网格和边框标签。这一块参数非常多我常用的配置是这样m_grid(box, fancy, tickdir, in, fontsize, 10, ... xtick, [105 110 115 120], ytick, [5 10 15 20 25], ... ylabel, Latitude (°N), xlabel, Longitude (°E));box参数fancy会画一个花式边框on是普通边框off则完全不要边框。tickdir控制刻度线方向in朝向图内是海洋图册的常见风格。如果区域较小网格线太密会干扰等深线可以通过指定xtick和ytick来控制经纬线间隔。比例尺用m_ruler放在图的底部空白区域。比如在某海域图中把比例尺放到经纬度(112°E, 3.5°N)的位置m_ruler([112 118], [3.5 3.5], ticklength, 0.03, color, [0.2 0.2 0.2], fontsize, 9);指北针方面m_map没有现成的一键函数但可以用m_annotation配合北向箭头画一个简单的指北针。我习惯把指北针放在图的右上角因为海洋图右上角通常是空白的远海区域。3.6 完整示例一分钟画出一幅南海地形水深图把前面几块拼起来就是一幅标准的地形水深底图。下面这个示例我取南海区域作为展示数据为GEBCO或ETOPO读取出的lon、lat、z代码是完整可跑的框架clear; close all; clc; % 参数设置 lon_lim [105 122]; lat_lim [3 26]; % 读取区域水深地形数据这里以变量lon、lat、z已经存在为例 load(nanhai_topo.mat, lon, lat, z); % 假设已有数据 [LON, LAT] meshgrid(lon, lat); % 1. 设置投影 m_proj(mercator, long, lon_lim, lat, lat_lim); % 2. 水深地形填色 m_pcolor(LON, LAT, z); shading flat; caxis([-5000 3000]); colormap(flipud(cmocean(topo))); % 如果没有cmocean可以用flipud(gray(256))或demcmap替代 colorbar(location, eastoutside); % 3. 海岸线 m_gshhs(ic, patch, [0.9 0.9 0.85], edgecolor, [0.3 0.3 0.3]); % 4. 等深线 m_contour(LON, LAT, z, [0 0], k, LineWidth, 1.2); m_contour(LON, LAT, z, [-100 -200 -1000 -3000 -4000], --, LineWidth, 0.5); % 5. 网格与比例尺 m_grid(box, fancy, tickdir, in, fontsize, 10, ... xtick, [105 110 115 120], ytick, [5 10 15 20 25]); m_ruler([112 118], [2.0 2.0], ticklength, 0.03, fontsize, 9);这段代码跑出来的图基本就能放到汇报PPT里了。如果只是临时验证数据不想要海岸线填充就把patch参数去掉。4. 从“能看”到“能发论文”的润色套路4.1 配色别再让MATLAB默认的parula背锅MATLAB默认的parula色带做普通曲线图很好但拿来做地形水深图就差点意思。海洋地学界有一个共识从深海到陆地理想色带应该是“深蓝-浅蓝-青绿-黄-棕-深棕”这样才能直观表达从海沟到山地的过渡。m_map官方示例里常用demcmap配caxis能自动生成一个兼顾水深与高程的色带。但真正让图质提升一个档次的是cmocean这个颜色方案包它专门为海洋和大气数据设计有topo、deep、haline等多个选项。你不需要把整包都学会下载cmocean之后只需把colormap这一行换成colormap(cmocean(topo)); colormap(cmocean(deep)); % 偏水深风格如果不想额外下载工具包退而求其次可以用demcmapdemcmap([-5000 3000], 256);它内部会根据高程范围自动生成蓝-白-棕色调效果中规中矩。但说实话等你把cmocean的topo色带用一次之后就再也回不去了。4.2 字体、标注和图例的中文化问题论文和报告里地图的字体必须和正文一致我一般会在画图前把坐标轴字体统一设置一遍。m_grid生成的经纬度标签字体大小在m_grid的fontsize参数里控制但要想全局统一建议画完图后对所有text对象统一处理set(findall(gcf, type, text), FontName, Helvetica, FontSize, 10); set(findall(gcf, type, axes), FontName, Helvetica, FontSize, 10);另外一个要命的问题是中文。MATLAB默认字体对中文支持不好直接在图里写中文字符会变成方格。我通常的做法是所有图内标注全部用英文包括图题、坐标标签唯一的中文痕迹留在Word排版时添加。如果必须在图内加中文可以在绘图后单独用text函数指定中文字体比如FontName, SimHei或Microsoft YaHei但要注意导出时字体是否嵌入否则换一台电脑打开就是乱码。4.3 导出高清图的正确姿势m_map画图是在figure窗口里导出质量受figure分辨率控制。直接用菜单栏另存为PNG出来的图经常发虚尤其是等深线密集的区域锯齿惨不忍睹。我的标准流程是在print之前先设置figure尺寸和分辨率set(gcf, PaperPositionMode, auto); set(gcf, Color, white); print(gcf, bathymetry_map, -dpng, -r300);-r300表示300DPI这个分辨率基本能满足大部分期刊的要求。如果想要矢量图方便后期编辑建议导出为EPS或PDF格式print(gcf, bathymetry_map, -depsc2, -r300);这几年MATLAB新增了exportgraphics函数处理高分辨率导出更省心尤其在R2020a之后的版本里用exportgraphics(gcf, bathymetry_map.png, Resolution, 300)一行搞定。我现在的默认做法就是预览用print出PNG定稿后用exportgraphics出PNG或PDF。5. 我踩过的坑和排查方法5.1 m_proj执行后图形窗口一片空白这是新手最常遇到的状况。代码没报错但figure窗口里什么都没有。绝大多数原因是后续绘图时传入的数据没有经纬度范围信息或者经纬度范围与m_proj设置的范围对不上。比如你设置了经度105到122但地形数据的经度矩阵里混入了NaN或者超出范围的异常值投影变换时就会把整块数据扔掉。解决方法是画图前检查数据的范围用min(max())循环看一遍lon和lat的边界再把超出设定范围的数据裁掉。另外m_pcolor接收的数据必须至少是2x2的网格如果你不小心把数据读成了一维向量m_map不会报错但画面就是空的。遇到空白时第一步检查size(LON)、size(LAT)和size(z)是不是完全一致。5.2 加载全球地形数据导致内存崩溃直接读取全球GEBCO数据再裁剪在8GB内存的电脑上十有八九会卡死或报内存不足。这个问题我遇到过好几次后来养成了好习惯先查变量范围和维度再按范围读取而不是读全量再裁剪。具体做法就是前面写的ncread指定起始位置和读取计数。还有一个更省内存的土办法先用m_etopo2读粗略数据做草稿确认区域和投影没问题之后再用GEBCO精细数据读小范围出正式图。如果数据矩阵已经读进来了但太大可以用降采样方式处理% 每3个点取1个降低分辨率 lon_ds lon(1:3:end); lat_ds lat(1:3:end); z_ds z(1:3:end, 1:3:end);这种方式会损失一点细节但画概览图完全够用速度能提升一个量级。5.3 颜色断层和NaN空洞水深地形数据经常有缺测区域这些NaN在m_pcolor绘制时会显示为白色空洞如果空洞面积大图面就会花得像狗啃一样。更麻烦的是caxis的计算范围会自动忽略NaN但不一定符合你的配色预期。我的解决办法是先统一填补或屏蔽空缺z(isnan(z)) -9999; % 或者填0取决于业务含义 z(z -10000 | z 10000) nan; % 剔除异常值填NaN之前先想清楚业务逻辑如果缺测区域是陆地填0再掩膜也可以如果是远海填一个深水常数比较合理。等深线对NaN也很敏感m_contour遇到NaN会把线切断导致等深线断断续续。我一般在画等深线前对z做一次简单的插值补洞用inpaint_nans这类工具或者干脆用fillmissing处理。5.4 新版MATLAB下的兼容性报错m_map官方版最后一次大更新年份较早部分函数用到了一些老语法在R2021b之后的MATLAB版本上可能偶尔报错或警告。最常见的是m_pcolor相关的句柄属性访问报错以及m_grid里的字体属性设置冲突。遇到这些问题先在命令行执行which m_proj确认你加载的是哪个版本的m_map。如果用的是MATLAB安装包自带的旧版建议去GitHub找社区维护的分支比如有人修过的m_map版本替换掉原文件一般问题就消失了。替换前把原来的m_map文件夹做个备份因为你可能在其他项目里依赖旧版行为。5.5 等深线文字糊成一团自动等深线标注在复杂地形区域非常容易重叠尤其菲律宾海沟这种深度急剧变化的地方。我以前用clabel给m_contour的结果加标签出来全是叠字。后来学聪明了等深线不标注数值改用图例说明或者在关键位置用m_text手动标“-200m”“-3000m”字样。手动标注虽然慢但每次只标注几条能代表地形趋势的等深线图面更干净信息也更明确。5.6 常见问题速查表问题现象最常见原因解决方案图形窗口空白投影范围与数据范围不匹配检查数据lon/lat边界裁剪或调整m_proj范围报错“Undefined function m_proj”m_map文件夹未加入路径设置路径里添加m_map及其子文件夹海岸线粗糙失真使用了m_coast默认低精度数据改用m_gshhs(ic)或(ih)并下载对应数据图出得很慢数据分辨率过高且未裁剪ncread按区域读取或降采样后再画导出后放大发虚导出分辨率太低print加-r300或用exportgraphics等深线断裂z矩阵中有NaN插值填洞或裁剪掉缺测区域图上没有颜色刻度未调用caxis/colorbar加caxis设置范围加colorbar显示色标6. 把这套画图流程变成你自己的工具6.1 封装一个出图函数当你不止一次需要画同样类型的地形水深图时就该把这套流程封装成函数了。我现在的工作方式是在项目里放一个自定义函数plot_bathy(lon_lim, lat_lim, z, lon, lat, para)传入经纬度范围、地形数据和参数结构体函数内部自动完成投影、填色、等深线、海岸线和网格绘制。这样换一个研究区只需要改一行参数。封装的时候有个小建议把caxis范围、等深线层级、色带这三项也做成参数不同区域和不同用途往往会用不同配置。比如东南亚海域水深变化快caxis范围要拉宽近岸工程海域水深浅caxis范围要收窄否则整个图都是深蓝色看不清细节。6.2 批量出图和自动排版做项目时经常需要对多个时间段或多个站点分别出图比如一个月内每天的温盐分布底图。这种批量场景我写了个简单的for循环每次循环清空figure重新调plot_bathy函数最后用exportgraphics按时间戳命名保存。一晚上跑几百张图完全没问题人只需要第二天起来挑图看。批量出图最怕的是某一组数据有问题导致脚本中断所以在循环里我会加上try-catch出错时记录文件名和错误信息然后继续下一张。这个习惯帮我省了太多重跑时间。6.3 最后想说的m_map不是那种华丽的新工具但它解决的是实实在在的痛点。我用它画过南海的水深地形图、近岸工程的环境底图、甚至还有古海洋的示意重构每一次都是把经纬度和数据丢进去它就稳稳地把底图铺好。虽然社区更新慢但基本功能的稳定性反而成了它最大的优点。如果你正在为“怎么在MATLAB里画一张像样的地形水深图”发愁照着这篇文章的流程走一遍大概率能一次成功。等你把这套流程跑通后面再往图上叠加站位、航线、等值线场、流场箭头就都是水到渠成的事了。