简介:压缩包内是一套MATLAB调用谷歌地图API的轻量示例资源,面向需要进行地理数据可视化、路径规划与地图交互的中级MATLAB用户。资源共8个文件,主体为2个.m脚本,分别承担主程序与测试验证,另有3个txt说明文档、1个fig界面文件以及1个asv自动备份文件,包体仅15KB,结构十分精简。代码覆盖静态地图URL构造、webread抓取图片、imshow显示底图的完整通路,同时演示了用geoplot、geobubble在地图上标注经纬度数据的做法;fig文件则为用户提供了可视化交互界面的布局参考。txt文档对谷歌地图API密钥申请、免费配额限制以及动态地图调用注意事项均给出说明,方便对照完成环境配置。整体上是一份可直接运行、拆解和二次开发的入门参考,已有499人学习,适合希望将MATLAB计算能力与谷歌地图地理信息相结合的开发者快速上手。
1. 手上一批经纬度坐标,怎么才能在 MATLAB 里直接画成带底图的地图
做轨迹可视化或者采样点分布图的时候,最烦的就是数据有了、坐标也对,但 MATLAB 自带的绘图函数只能画出海岸线和国界线,道路、街区、卫星影像一概没有。想往图上加一条实际行驶轨迹,结果底图空白一片,路网全靠手画,根本没法往汇报材料里放。MATLAB 谷歌地图工具包就是为这个场景准备的:它把在线地图瓦片直接铺到当前坐标轴底下当底图,路网、卫星影像、地形图都能选,叠加散点、轨迹线、多边形用的还是原生 plot / scatter 语法,不需要学新的绘图体系。适合做 GPS 轨迹分析、采样点分布展示、路径规划验证的从业者和研究生,只要 MATLAB 能联网,就能在几分钟内出一张能直接用的地图。
2. 环境与准备:把工具包放进 MATLAB 路径前先想清楚三件事
拿到资源包之后别急着双击运行,先花五分钟看一下文件构成和路径设置方式。很多人在这一步图省事,把整个文件夹丢进当前目录就开跑,结果换了个工作目录函数就找不到了,报错信息还特别容易误导人。
2.1 工具包文件构成与 MATLAB 路径配置
解压之后,目录里通常包含核心函数文件、示例脚本和样例数据。其中的核心调用函数是plot_google_map.m,它负责请求瓦片、拼接底图、把图片贴到当前 axes 下方;另外还会有geocode.m这类根据地名查经纬度的辅助函数,以及一个或者多个 demo 脚本。建议先把目录完整看一遍,重点确认里面有没有README或者注释版的示例脚本,那里面往往写了作者建议的最低 MATLAB 版本和依赖项。
路径配置这件事,我的习惯是把它写进一个独立的初始化脚本,而不是每次手动点"设置路径"。这样换电脑、换工作目录时能一键恢复环境。
% init_map_env.m % 将工具包所在目录及其子目录加入 MATLAB 搜索路径 toolbox_root = fullfile(pwd, 'matlab_google_map_toolbox'); addpath(genpath(toolbox_root)); % 验证核心函数是否可见 assert(exist('plot_google_map', 'file') == 2, ... 'plot_google_map.m 未找到,请检查工具箱路径'); % 建立一个全局缓存目录,后面离线瓦片会用到 cache_dir = fullfile(pwd, 'map_cache'); if ~exist(cache_dir, 'dir') mkdir(cache_dir); end这段代码做的事很简单:genpath会把工具包根目录下所有子目录一次性加入搜索路径,避免遗漏嵌套文件夹;assert用来在启动时立刻发现路径配错的问题,而不是等绘图到一半才报"未定义函数";提前创建缓存目录是因为后面用到瓦片缓存时会频繁写文件,先把目录准备好能少踩很多权限坑。参数方面,toolbox_root要改成你实际解压后的路径,如果工具包放在 D 盘或者其他固定位置,建议直接用绝对路径,别用pwd,否则只能在特定目录下运行脚本。
2.2 API Key、访问参数与网络前提
很多第一次用这套工具的人会卡在 API Key 上,总觉得不填个 Key 就画不出图。实际上核心函数把 Key 做成了可选参数,不填也能用默认服务地址拉取瓦片。科研绘图这种低频请求量,默认方式完全够用;填了 Key 反而要面对配额限制、请求次数统计这些额外负担。我的建议是:先用无 Key 模式跑通流程,确认底图能正常显示,再决定要不要申请专属 Key 做高并发请求。
网络这块反而是更需要提前确认的。工具包本质上是在调用在线地图的静态瓦片接口,webread请求的超时时间默认较短,在网络波动大的场景下很容易失败。演示脚本里通常不会专门设置weboptions,所以自己要补一步超时参数调整。
% connectivity_test.m % 测试能否正常访问地图瓦片服务 test_url = 'http://maps.googleapis.com/maps/api/staticmap?center=39.9,116.4&zoom=10&size=200x200'; opts = weboptions('Timeout', 30, ... 'UserAgent', 'MATLAB-GoogleMap-Toolbox'); try img = webread(test_url, opts); fprintf('网络连接正常,返回图片大小: %d x %d\n', size(img, 2), size(img, 1)); catch ME fprintf('网络请求失败: %s\n', ME.message); fprintf('请检查网络策略或防火墙白名单\n'); end这里weboptions的Timeout设为 30 秒,比默认值宽裕很多,能覆盖大部分慢网络场景。UserAgent设置成固定的标识字符串,方便服务端识别请求来源,减少被拒绝的概率。测试脚本里的 URL 用的是静态地图服务的最小请求,如果这一步能返回图片数据,说明网络链路是通的;如果在这一步就报错,那问题基本不在工具包本身,而是网络策略层面阻止了访问,先去解决网络放行,再回头调绘图参数。
一个常见的误区是:脚本里用了weboptions,就以为plot_google_map会自动继承这个配置。实际上工具包内部有自己的请求逻辑,外部设置的weboptions只有在它内部支持透传时才会生效。所以测通网络之后,正式绘图时如果仍然报超时,需要直接看工具包源码里的请求部分,手动把weboptions加进去。
3. 首发底图:从一批经纬度坐标到轨迹叠加的完整流程
环境准备好之后,就进入到最有成就感的部分:真正画出一张带底图的地图。这一章的流程是固定的,顺序不要乱——先准备数据、再设定边界框、然后调用底图函数、最后叠加绘图元素。跳过任何一步,都可能在出图阶段返工。
3.1 数据准备与边界框设定
工具包要求你先把坐标数据准备好,并且用这些坐标算出一个边界框(bounding box)。边界框的作用是告诉瓦片请求函数"我需要覆盖哪个地理范围",范围越大,需要的瓦片数量越多,拼接耗时也越长。如果范围设置不合理,要么底图覆盖不全,要么拉取了一堆没用的瓦片拖慢速度。
示例数据一般是一组经纬度数组,比如一条车辆行驶轨迹。假设数据已经通过某种方式采集到,存在track.mat文件里:
% 1_load_track_data.m % 加载轨迹数据,并计算地图显示范围 load('track.mat'); % 假设包含变量 lat 和 lon,均为列向量 assert(length(lat) == length(lon), '经纬度长度不一致'); % 计算边界框,向外扩展 2% 作为留白,避免轨迹贴在图片边缘 lat_min = min(lat) - 0.02 * (max(lat) - min(lat)); lat_max = max(lat) + 0.02 * (max(lat) - min(lat)); lon_min = min(lon) - 0.02 * (max(lon) - min(lon)); lon_max = max(lon) + 0.02 * (max(lon) - min(lon)); fprintf('边界框范围: Lat [%.4f, %.4f], Lon [%.4f, %.4f]\n', ... lat_min, lat_max, lon_min, lon_max);这段代码的逻辑很直白:先用min/max找出轨迹坐标的极值,再按跨度的一定比例外扩。2% 这个系数是经验值,城市尺度的轨迹刚刚好;如果轨迹跨度特别大,比如跨省路线,建议改成 1%,否则底图里轨迹会显得很小。这里特别要检查的是坐标数组的长度一致性,很多实际采集数据会因为丢星导致经纬度数量不一致,这个检查能提前暴露数据问题,而不是等到画图报错才回头查。
边界框有了之后,坐标系的基准也要顺手确认一下。工具包默认按 WGS-84 坐标系处理经纬度数据,如果你的数据是其他坐标系采集的,后面叠加底图时会出现偏移。这一步检查不需要额外代码,只要你在数据采集阶段记录一下设备的坐标系设置即可。大多数手持 GPS 和手机定位输出都是 WGS-84,可以直接用。
3.2 调用 plot_google_map 并叠加轨迹
边界框算好之后,就进入核心调用环节。这一步的顺序有讲究:先画出轨迹或者散点图,再调用plot_google_map把底图铺到底层,这样才能保证底图不遮挡绘图元素。
% 2_draw_track_map.m % 绘制轨迹并叠加在线底图 figure('Color', 'w', 'Position', [100 100 900 700]); % 第一步:绘制原始数据(先画线,后画点) plot(lon, lat, 'LineWidth', 2.5, 'Color', [0.85 0.33 0.10]); hold on; scatter(lon(1), lat(1), 80, 'go', 'filled'); % 起点用绿色圆点 scatter(lon(end), lat(end), 80, 'ro', 'filled'); % 终点用红色圆点 hold off; % 第二步:把在线底图铺到坐标轴下层 plot_google_map('MapType', 'roadmap', ... 'Refresh', 1, ... 'AutoAxis', 0, ... 'APIKey', ''); % 第三步:关闭刻度显示,让地图看起来更干净 set(gca, 'XTick', [], 'YTick', [], 'Box', 'on');这段代码有几个参数值得展开说。MapType控制底图样式:roadmap是标准路网图,适合轨迹展示;satellite是卫星影像,适合地形地貌分析;hybrid是卫星图叠加路名标注,适合汇报场景。Refresh是是否强制刷新瓦片缓存,第一次画图用1,确认底图没问题后建议改成0,后续重绘直接走缓存,速度会快很多且不消耗网络请求。AutoAxis这里设为0,是因为我们已经主动设置了坐标范围,不需要工具包再去自动调整;如果你的数据没有预先算边界框,可以设成1让它自己适配。
绘图顺序的底层逻辑在于:plot_google_map内部会用image函数把瓦片贴到坐标轴底层,这个操作会重置坐标轴的范围。如果先贴底图再画轨迹,坐标轴范围被底图设置好后,再plot轨迹数据没问题;但反过来,如果先画了轨迹再贴底图,工具包默认会尝试保护已有绘图对象的坐标范围。这里把它说透:先画数据再贴底图,底图会因为AutoAxis=0而保持已有范围不变,这正是我们要的效果。
3.3 导出高分辨率图片的参数与技巧
屏幕上看到底图之后,下一步就是导出图片。常见的问题是:用截图工具直接截屏,得到的图片分辨率只有屏幕像素。要往论文或者报告里放,至少需要 300 DPI 以上的输出。
% 3_export_map.m % 将当前地图导出为高分辨率 PNG 和 PDF 两种格式 export_dir = 'output'; if ~exist(export_dir, 'dir') mkdir(export_dir); end % 先调整坐标轴占满整个 figure,去掉多余的留白 axis tight; % 导出 PNG:300 DPI 足够印刷需求 print(gcf, 'track_map.png', '-dpng', '-r300'); % 导出 PDF:矢量格式,后续可继续后期编辑 print(gcf, 'track_map.pdf', '-dpdf'); fprintf('图片已导出至 %s 目录\n', export_dir);导出这一步有两点容易疏忽。第一,axis tight必须放在plot_google_map之后,否则坐标轴范围会被底图重置,导出的图片边缘出现大片空白或者轨迹被裁掉。第二,PDF 导出看起来是矢量格式,但底图部分本质是位图瓦片拼接的,所以 PDF 文件里图片区域仍是位图,只有轨迹线、标注文字是矢量的。想进一步优化,可以把print的分辨率参数调到 600,代价是文件体积变大,可视实际用途决定。
另外一个常被忽略的点是figure的宽高比。地图瓦片是按正方形网格组织的,figure 形状越接近正方形,瓦片拼接的锯齿越少。如果你的 figure 拉的特别扁或者特别窄,底图边缘会出现明显的空白补齐带。遇到这种情况,调整Position参数让宽高比保持在 1.2:1 到 1.5:1 之间,观感会好很多。
4. 避坑排查:坐标偏移、瓦片卡死与字体乱码的四个真实现场
工具包本身不复杂,但实际用起来,翻车点都非常隐蔽。这章我把最常见的四个现场列出来,每一条都是真实发生过的问题,按"现象 → 原因 → 解决"的顺序说清楚。读完这部分,你在复现示例脚本时遇到类似情况,就能直接照方抓药。
4.1 轨迹整体偏移了几百米,路网对不上
现象:画出来的轨迹线没有在道路上,而是整体偏移了几百米,有时候是往东飘,有时候是往北飘,而且偏移量在城区特别明显,到了郊区反而小一些。
原因:这是坐标系不一致造成的。工具包默认使用 WGS-84 坐标系,但国内很多采集设备或者第三方数据平台输出的是 GCJ-02 坐标系,两者之间存在非线性偏移。偏移的大小跟经纬度相关,所以不同城市表现不一样。
解决:给工具包的数据加一道坐标转换。最稳妥的做法是使用公开的坐标转换函数库,但如果你只是偶尔处理几批数据,不想引入依赖,可以用经验修正公式。
% 4_wgs84_to_gcj02_correction.m % 经验修正:将 GCJ-02 坐标近似转换为 WGS-84 坐标 % 注意:这是近似修正,精度在 1 米量级,适合轨迹展示 function [lat_wgs, lon_wgs] = approx_gcj02_to_wgs84(lat_gcj, lon_gcj) a = 6378245.0; ee = 0.006693421622965943; dLat = transformLat(lon_gcj - 105.0, lat_gcj - 35.0); dLon = transformLon(lon_gcj - 105.0, lat_gcj - 35.0); radLat = lat_gcj / 180.0 * pi; magic = sin(radLat); magic = 1 - ee * magic * magic; sqrtMagic = sqrt(magic); dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi); lat_wgs = lat_gcj - dLat; lon_wgs = lon_gcj - dLon; end代码里的transformLat和transformLon是实现偏移量计算的两个辅助函数,具体展开是一段经纬度相关的多项式拟合代码,通常在坐标转换工具包里可以找到。使用这套近似公式时,要意识到它不适用于高精度定位场景。如果你是做厘米级测量或者精密工程,必须使用标准的七参数转换和对应的控制点校正,而不是靠这种经验公式。对于轨迹可视化和采样点分布图,这个精度已经足够让轨迹回到正确的道路上。
4.2 第一次调用卡死,长时间转圈后报 webread 超时
现象:脚本运行到plot_google_map那行就卡住不动,等了一两分钟,报错提示webread超时或者连接失败。
原因:这里有两个层面。第一层是网络问题,很多办公网络对境外地图服务的访问有一定限制,直接导致瓦片请求失败。第二层是工具包自身的超时设置太长,默认情况下它会等待较长时间才放弃,观感上就是卡死。此外,如果网络不稳定,部分瓦片请求成功、部分失败,工具包可能卡在等待某个瓦片返回的环节。
解决:分两步走。先测通网络,使用前面章节的连通性测试脚本确认能否访问瓦片服务;如果网络不通,解决方向是网络策略层面,跟工具包无关。如果网络正常但是偶尔超时,那就手动在工具包源码的请求位置加上weboptions超时控制,把单次请求超时缩短到 15 秒,失败时快速跳过。
% 修改 plot_google_map.m 内部请求段(示意) opts = weboptions('Timeout', 15, 'UserAgent', 'MATLAB'); for k = 1:numel(tile_urls) try tile_data = webread(tile_urls{k}, opts); % 处理瓦片数据... catch warning('瓦片 %d 获取失败,已跳过', k); end end这里的关键改动是加了一层try-catch,让单个瓦片失败不影响整体出图。默认工具包的逻辑是某个瓦片失败就整体报错,导致底图完全出不来;改成跳过之后,最多是底图上有小块空白,但轨迹和主要区域都能正常显示。这个技巧在弱网环境下特别实用。
另一个坑是缓存目录的写入权限。有些公司电脑对某个目录有只读限制,瓦片下载后写不进缓存,工具包会反复重试下载。把缓存目录改到用户目录下面,通常能解决这个问题。
4.3 底图正常但中文标注全部变成方框
现象:在图上用text或者title加中文标题,屏幕上显示正常,导出 PNG 后中文变成了一个个方框或者乱码。
原因:MATLAB 默认的字体列表中不包含中文字体,导出图片时字体嵌入失败,导致显示与打印不一致。这是 MATLAB 的经典问题,跟工具包本身无关。
解决:在绘图之前统一设置坐标轴字体为系统中文字体。
% 5_set_chinese_font.m % 统一设置中文字体,避免导出乱码 set(groot, 'DefaultAxesFontName', 'SimHei'); set(groot, 'DefaultTextFontName', 'SimHei'); set(groot, 'DefaultLegendFontName', 'SimHei'); title('车辆行驶轨迹图', 'FontSize', 14); legend({'轨迹', '起点', '终点'});SimHei是黑体,在大多数中文系统上都存在。如果你用的是 Mac,改成'PingFang SC';Linux 系统改成'WenQuanYi Zen Hei'或者'Noto Sans CJK SC'。这里还有一个隐藏坑:set(groot, ...)设置的是当前会话的根对象默认值,脚本重新打开后要再执行一次。有人画图时中文正常,换了个脚本中文就乱码,多半就是忘了加这一段。
4.4 底图偶尔缺失一大块,视角缩放后空白面积变大
现象:缩放到地图某个细节层级时,底图出现大块灰白色的空白区域,轨迹线悬空在空白上面,看起来像是一块拼图没拼上。
原因:瓦片缓存损坏或者缓存版本与当前地图参数不匹配。Refresh设为0后,工具包直接从缓存读取瓦片,如果缓存目录里混入了旧版本瓦片,或者之前有一次下载不完整,就会一直读到坏数据。
解决:清理缓存目录后强制刷新一次。
% 6_clean_cache.m % 清理缓存瓦片,强制重新下载 cache_dir = fullfile(pwd, 'map_cache'); if exist(cache_dir, 'dir') rmdir(cache_dir, 's'); mkdir(cache_dir); end % 重绘时需要把 Refresh 强制设为 1 plot_google_map('MapType', 'satellite', ... 'Refresh', 1, ... 'AutoAxis', 0);清理缓存的操作很简单,但要注意:如果当前处于离线环境,清理完缓存反而会让底图完全消失。所以清理前先确认网络可用,或者当前有完整的缓存备份。另一个关联问题是 zoomLevel 的变化,工具包会根据坐标轴范围自动计算瓦片缩放层级,范围变化后会请求新层级的瓦片,这属于正常现象,不算故障。
5. 进阶:离线瓦片缓存与论文级出图的三个习惯
底图画通之后,下一步往往是批量出图:几十条轨迹要一个个画,如果每次都走网络请求,速度慢不说,还容易触发服务端的请求频率限制。离线瓦片缓存就是解决这个问题的常用手段。
做法其实很简单。第一次画某个区域时,把Refresh设为1,让工具包把所有瓦片下载到本地缓存目录;后续重绘同一区域时,把Refresh设为0,工具包会直接从缓存读瓦片,完全不发网络请求。这相当于给常用区域做了一个本地瓦片地图,离线环境也能出图。批量处理 30 条轨迹时,首条轨迹花十秒拉瓦片,后面 29 条每条不到一秒,前后的效率差距非常明显。
% 7_offline_batch.m % 批量离线出图示例:先缓存底图,再离线重绘 trace_list = dir('traces/*.mat'); % 第一次画:加载第一条轨迹,绘制并缓存瓦片 load(fullfile('traces', trace_list(1).name)); % 计算边界框、绘制底图,Refresh 设为 1 plot_google_map('Refresh', 1, 'AutoAxis', 0); % 后续批量绘制:Refresh 设为 0,直接读缓存 for k = 2:numel(trace_list) load(fullfile('traces', trace_list(k).name)); figure; plot(lon, lat, 'b-', 'LineWidth', 2); plot_google_map('Refresh', 0, 'AutoAxis', 0); axis tight; print(gcf, sprintf('trace_%02d.png', k), '-dpng', '-r300'); end在这段批量绘制代码里,第一条轨迹的作用是"预热缓存"——把该区域的瓦片全部拉到本地。但要注意一个前提:只有轨迹范围重叠度很高时,缓存复用率才高。如果每条轨迹分布在不同城市,每次都要重新拉取新区域的瓦片,离线缓存就起不到加速效果了。这种场景下,更合适的做法是根据应用场景维护几个固定的"常用区域边界框"预取瓦片,而不是让轨迹去推动缓存。
论文级出图的第一个习惯是固定画布大小。每次figure使用同样的Position参数,图片比例一致,拼接多张图时不用挨个调尺寸。第二个习惯是坐标轴范围手动锁定。用xlim/ylim固定范围后,plot_google_map的AutoAxis设为0,这样每次重绘底图都不会因为数据范围波动而跳动。第三个习惯是导出前统一检查一遍刻度、图例位置和字体,把set(groot, 'DefaultAxesFontName', ...)写在脚本第一行批量出图的脚本里,避免事后发现字体问题重导出。
这三个习惯里,最值钱的是坐标范围锁定。很多人批量出图时每张图范围都不一样,读者对比时会觉得尺度不统一,观感大打折扣。固定范围后,所有轨迹图的可视化比例完全一致,数据对比一目了然。从那以后我每次批量绘制带地理坐标的数据,都会强制把边界框计算、坐标系核验、缓存状态检查走一遍,再进入绘图循环,这个流程帮我省掉了大量返工时间。希望帮到你。
本文还有配套的精品资源,点击获取